diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index ce22a9a12b..21f3fa92d8 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,12 +1,19 @@ -#### Pull Request Checklist - -- [ ] All changes are Go version 1.16 compliant -- [ ] Your PR passes all CI checks. If a check cannot be passed for a justifiable reason, that reason must be stated in the commit message and PR description. -- [ ] If this is your first time contributing, we recommend you read the [Code Contribution Guidelines](https://github.com/lightningnetwork/lnd/blob/master/docs/code_contribution_guidelines.md) - - [ ] The code being submitted is commented according to [Code Documentation and Commenting](https://github.com/lightningnetwork/lnd/blob/master/docs/code_contribution_guidelines.md#CodeDocumentation) - - [ ] Commits have a logical structure according to [Ideal Git Commit Structure](https://github.com/lightningnetwork/lnd/blob/master/docs/code_contribution_guidelines.md#IdealGitCommitStructure) -- [ ] For new code: Code is accompanied by tests which exercise both the positive and negative (error paths) conditions (if applicable) -- [ ] For bug fixes: If possible, code is accompanied by new tests which trigger the bug being fixed to prevent regressions -- [ ] Any new logging statements use an appropriate subsystem and logging level -- [ ] For code and documentation: lines are wrapped at 80 characters (the tab character should be counted as 8 characters, not 4, as some IDEs do per default) -- [ ] A description of your changes [should be added to running the release notes](https://github.com/lightningnetwork/lnd/tree/master/docs/release-notes) for the milestone your change will land in. +## Change Description +Description of change / link to associated issue. + +## Steps to Test +Steps for reviewers to follow to test the change. + +## Pull Request Checklist +### Testing +- [ ] Your PR passes all CI checks. +- [ ] Tests covering the positive and negative (error paths) are included. +- [ ] Bug fixes contain tests triggering the bug to prevent regressions. + +### Code Style and Documentation +- [ ] The change obeys the [Code Documentation and Commenting](https://github.com/lightningnetwork/lnd/blob/master/docs/code_contribution_guidelines.md#CodeDocumentation) guidelines, and lines wrap at 80. +- [ ] Commits follow the [Ideal Git Commit Structure](https://github.com/lightningnetwork/lnd/blob/master/docs/code_contribution_guidelines.md#IdealGitCommitStructure). +- [ ] Any new logging statements use an appropriate subsystem and logging level. +- [ ] [There is a change description in the release notes](https://github.com/lightningnetwork/lnd/tree/master/docs/release-notes), or `[skip ci]` in the commit message for small changes. + +📝 Please see our [Contribution Guidelines](https://github.com/lightningnetwork/lnd/blob/master/docs/code_contribution_guidelines.md) for further guidance. \ No newline at end of file diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 36ca56bccd..b6e763db3f 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -4,6 +4,9 @@ on: push: tags: - 'v*' + schedule: + # Every day at midnight (UTC). + - cron: '0 0 * * *' defaults: run: @@ -30,7 +33,15 @@ jobs: password: ${{ secrets.DOCKER_API_KEY }} - name: Set env - run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV + run: | + echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV + echo "IMAGE_TAG=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV + + - name: Set daily tag + if: github.event.schedule == '0 0 * * *' + run: | + echo "RELEASE_VERSION=master" >> $GITHUB_ENV + echo "IMAGE_TAG=daily-testing-$(date -u +%Y%m%d),${DOCKER_REPO}/${DOCKER_IMAGE}:daily-testing-only" >> $GITHUB_ENV - name: Build and push id: docker_build @@ -38,7 +49,7 @@ jobs: with: push: true platforms: linux/amd64,linux/arm64 - tags: "${{ env.DOCKER_REPO }}/${{ env.DOCKER_IMAGE }}:${{ env.RELEASE_VERSION }}" + tags: "${{ env.DOCKER_REPO }}/${{ env.DOCKER_IMAGE }}:${{ env.IMAGE_TAG }}" build-args: checkout=${{ env.RELEASE_VERSION }} - name: Image digest diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6f35225ab2..5e568885ad 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,7 +16,7 @@ env: # go needs absolute directories, using the $HOME variable doesn't work here. GOCACHE: /home/runner/work/go/pkg/build GOPATH: /home/runner/work/go - BITCOIN_VERSION: 0.20.1 + BITCOIN_VERSION: "23.0" # If you change this value, please change it in the following files as well: # /.travis.yml @@ -24,7 +24,7 @@ env: # /dev.Dockerfile # /make/builder.Dockerfile # /.github/workflows/release.yml - GO_VERSION: 1.17.1 + GO_VERSION: 1.18 jobs: ######################## @@ -206,8 +206,9 @@ jobs: matrix: unit_type: - btcd unit-cover - - unit tags="kvdb_etcd kvdb_postgres" - - travis-race + - unit tags="kvdb_etcd" + - unit tags="kvdb_postgres" + - btcd unit-race steps: - name: git checkout uses: actions/checkout@v2 @@ -258,6 +259,8 @@ jobs: args: backend=bitcoind - name: bitcoind-notxindex args: backend="bitcoind notxindex" + - name: bitcoind-rpcpolling + args: backend="bitcoind rpcpolling" - name: bitcoind-etcd args: backend=bitcoind dbbackend=etcd - name: bitcoind-postgres @@ -292,6 +295,7 @@ jobs: - name: Zip log files on failure if: ${{ failure() }} + timeout-minutes: 1 # timeout after 1 minute run: 7z a logs-itest-${{ matrix.name }}.zip lntest/itest/**/*.log - name: Upload log files on failure diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 40bc652537..e7bf7e830c 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -16,7 +16,7 @@ env: # /dev.Dockerfile # /make/builder.Dockerfile # /.github/workflows/main.yml - GO_VERSION: 1.17.1 + GO_VERSION: 1.18 jobs: main: @@ -94,7 +94,7 @@ jobs: Our release binaries are fully reproducible. Third parties are able to verify that the release binaries were produced properly without having to trust the release manager(s). See our [reproducible builds guide](https://github.com/lightningnetwork/lnd/tree/master/build/release) for how this can be achieved. The release binaries are compiled with `go${{ env.GO_VERSION }}`, which is required by verifiers to arrive at the same ones. - They include the following build tags: `autopilotrpc`, `signrpc`, `walletrpc`, `chainrpc`, `invoicesrpc`, `routerrpc`, `watchtowerrpc` and `monitoring`. Note that these are already included in the release script, so they do not need to be provided. + They include the following build tags: `autopilotrpc`, `signrpc`, `walletrpc`, `chainrpc`, `invoicesrpc`, `neutrinorpc`, `routerrpc`, `watchtowerrpc`, `monitoring`, `peersrpc`, `kvdb_postrgres`, and `kvdb_etcd`. Note that these are already included in the release script, so they do not need to be provided. The `make release` command can be used to ensure one rebuilds with all the same flags used for the release. If one wishes to build for only a single platform, then `make release sys= tag=` can be used. diff --git a/.gitignore b/.gitignore index 5942262730..e5e5bbfeb1 100644 --- a/.gitignore +++ b/.gitignore @@ -74,3 +74,6 @@ profile.tmp # Coverage test coverage.txt + +# Visual Studio cache/options directory +.vs/ diff --git a/.golangci.yml b/.golangci.yml index fed729c410..1098fcc3d6 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -15,10 +15,15 @@ run: build-tags: - autopilotrpc - chainrpc + - dev - invoicesrpc + - neutrinorpc + - peersrpc - signrpc - walletrpc - watchtowerrpc + - kvdb_etcd + - kvdb_postgres linters-settings: govet: @@ -27,6 +32,20 @@ linters-settings: gofmt: # simplify code: gofmt with `-s` option, true by default simplify: true + tagliatelle: + case: + rules: + json: snake + whitespace: + multi-func: true + multi-if: true + gosec: + excludes: + - G402 # Look for bad TLS connection settings. + - G306 # Poor file permissions used when writing to a new file. + staticcheck: + go: "1.18" + checks: ["-SA1019"] linters: enable-all: true @@ -38,18 +57,23 @@ linters: # even longer by marking them as 'nolint'. - lll - # We don't care (enough) about misaligned structs to lint that. - - maligned + # We want to allow short variable names. + - varnamelen + + # We want to allow TODOs. + - godox # We have long functions, especially in tests. Moving or renaming those would # trigger funlen problems that we may not want to solve at that time. - funlen # Disable for now as we haven't yet tuned the sensitivity to our codebase - # yet. Enabling by default for example, would also force new contributors to + # yet. Enabling by default for example, would also force new contributors to # potentially extensively refactor code, when they want to smaller change to # land. - gocyclo + - gocognit + - cyclop # Instances of table driven tests that don't pre-allocate shouldn't trigger # the linter. @@ -57,6 +81,45 @@ linters: # Init functions are used by loggers throughout the codebase. - gochecknoinits + + # Causes stack overflow, see https://github.com/polyfloyd/go-errorlint/issues/19. + - errorlint + + # Deprecated linters. See https://golangci-lint.run/usage/linters/. + - interfacer + - golint + - maligned + - scopelint + + # New linters that need a code adjustment first. + - wrapcheck + - nolintlint + - paralleltest + - tparallel + - testpackage + - gofumpt + - gomoddirectives + - ireturn + - maintidx + - nlreturn + - dogsled + - gci + - containedctx + - contextcheck + - errname + - exhaustivestruct + - goerr113 + - gomnd + - ifshort + - noctx + - nestif + - wsl + - exhaustive + - forcetypeassert + - nilerr + - nilnil + - stylecheck + - thelper issues: # Only show newly introduced problems. @@ -68,6 +131,36 @@ issues: - path: _test\.go linters: - gosec + - path: test* + linters: + - gosec + + # Allow duplicated code and fmt.Printf() in DB migrations. + - path: channeldb/migration* + linters: + - dupl + - forbidigo + - godot + + # Allow duplicated code and fmt.Printf() in DB migration tests. + - path: channeldb/migtest + linters: + - dupl + - forbidigo + - godot + + # Allow fmt.Printf() in lncli. + - path: cmd/lncli/* + linters: + - forbidigo + + # Allow fmt.Printf() in config parsing. + - path: config\.go + linters: + - forbidigo + - path: lnd\.go + linters: + - forbidigo # Fix false positives because of build flags in itest directory. - path: lntest/itest/.* diff --git a/.travis.yml b/.travis.yml index a6f4a4a4cc..e65c2137ca 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,12 +21,12 @@ go: # /make/builder.Dockerfile # /.github/workflows/main.yml # /.github/workflows/release.yml - - "1.17.x" + - "1.18.x" env: global: - GOCACHE=$HOME/.go-build - - BITCOIN_VERSION=0.20.1 + - BITCOIN_VERSION="22.0" sudo: required diff --git a/Dockerfile b/Dockerfile index 38d47c189e..0237fb88c9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ # /make/builder.Dockerfile # /.github/workflows/main.yml # /.github/workflows/release.yml -FROM golang:1.17.1-alpine as builder +FROM golang:1.18-alpine as builder # Force Go to use the cgo based DNS resolver. This is required to ensure DNS # queries required to connect to linked containers succeed. @@ -14,13 +14,14 @@ ENV GODEBUG netdns=cgo # image to be built from a specified Git state. The default image # will use the Git tip of master by default. ARG checkout="master" +ARG git_url="https://github.com/lightningnetwork/lnd" # Install dependencies and build the binaries. RUN apk add --no-cache --update alpine-sdk \ git \ make \ gcc \ -&& git clone https://github.com/lightningnetwork/lnd /go/src/github.com/lightningnetwork/lnd \ +&& git clone $git_url /go/src/github.com/lightningnetwork/lnd \ && cd /go/src/github.com/lightningnetwork/lnd \ && git checkout $checkout \ && make release-install diff --git a/LICENSE b/LICENSE index 70f1f0df30..cfab3dac7c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (C) 2015-2018 Lightning Labs and The Lightning Network Developers +Copyright (C) 2015-2022 Lightning Labs and The Lightning Network Developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile index 5374e77219..4fcb6ae4bb 100644 --- a/Makefile +++ b/Makefile @@ -1,21 +1,19 @@ PKG := github.com/lightningnetwork/lnd ESCPKG := github.com\/lightningnetwork\/lnd MOBILE_PKG := $(PKG)/mobile +TOOLS_DIR := tools BTCD_PKG := github.com/btcsuite/btcd -GOVERALLS_PKG := github.com/mattn/goveralls -LINT_PKG := github.com/golangci/golangci-lint/cmd/golangci-lint GOACC_PKG := github.com/ory/go-acc -GOIMPORTS_PKG := golang.org/x/tools/cmd/goimports +GOIMPORTS_PKG := github.com/rinchsan/gosimports/cmd/gosimports GOFUZZ_BUILD_PKG := github.com/dvyukov/go-fuzz/go-fuzz-build GOFUZZ_PKG := github.com/dvyukov/go-fuzz/go-fuzz GOFUZZ_DEP_PKG := github.com/dvyukov/go-fuzz/go-fuzz-dep GO_BIN := ${GOPATH}/bin BTCD_BIN := $(GO_BIN)/btcd +GOIMPORTS_BIN := $(GO_BIN)/gosimports GOMOBILE_BIN := GO111MODULE=off $(GO_BIN)/gomobile -GOVERALLS_BIN := $(GO_BIN)/goveralls -LINT_BIN := $(GO_BIN)/golangci-lint GOACC_BIN := $(GO_BIN)/go-acc GOFUZZ_BUILD_BIN := $(GO_BIN)/go-fuzz-build GOFUZZ_BIN := $(GO_BIN)/go-fuzz @@ -29,23 +27,12 @@ ANDROID_BUILD := $(ANDROID_BUILD_DIR)/Lndmobile.aar COMMIT := $(shell git describe --tags --dirty) COMMIT_HASH := $(shell git rev-parse HEAD) -BTCD_COMMIT := $(shell cat go.mod | \ - grep $(BTCD_PKG) | \ - head -n1 | \ - awk -F " " '{ print $$2 }' | \ - awk -F "/" '{ print $$1 }') - -LINT_COMMIT := v1.18.0 -GOACC_COMMIT :=80342ae2e0fcf265e99e76bcc4efd022c7c3811b -GOFUZZ_COMMIT := b1f3d6f - -DEPGET := cd /tmp && GO111MODULE=on go get -v -GOBUILD := GO111MODULE=on go build -v -GOINSTALL := GO111MODULE=on go install -v -GOTEST := GO111MODULE=on go test +GOBUILD := go build -v +GOINSTALL := go install -v +GOTEST := go test GOVERSION := $(shell go version | awk '{print $$3}') -GOFILES_NOVENDOR = $(shell find . -type f -name '*.go' -not -path "./vendor/*" -not -name "*pb.go" -not -name "*pb.gw.go") +GOFILES_NOVENDOR = $(shell find . -type f -name '*.go' -not -path "./vendor/*" -not -name "*pb.go" -not -name "*pb.gw.go" -not -name "*.pb.json.go") RM := rm -f CP := cp @@ -66,6 +53,7 @@ make_ldflags = $(2) -X $(PKG)/build.Commit=$(COMMIT) \ -X $(PKG)/build.GoVersion=$(GOVERSION) \ -X $(PKG)/build.RawTags=$(shell echo $(1) | sed -e 's/ /,/g') +DEV_GCFLAGS := -gcflags "all=-N -l" LDFLAGS := -ldflags "$(call make_ldflags, ${tags}, -s -w)" DEV_LDFLAGS := -ldflags "$(call make_ldflags, $(DEV_TAGS))" ITEST_LDFLAGS := -ldflags "$(call make_ldflags, $(ITEST_TAGS))" @@ -80,9 +68,7 @@ ifneq ($(workers),) LINT_WORKERS = --concurrency=$(workers) endif -LINT = $(LINT_BIN) run -v $(LINT_WORKERS) - -GOFUZZ_DEP_PKG_FETCH = go get -v $(GOFUZZ_DEP_PKG)@$(GOFUZZ_COMMIT) +DOCKER_TOOLS = docker run -v $$(pwd):/build lnd-tools GREEN := "\\033[0;32m" NC := "\\033[0m" @@ -97,34 +83,29 @@ all: scratch check install # ============ # DEPENDENCIES # ============ - -$(GOVERALLS_BIN): - @$(call print, "Fetching goveralls.") - go get -u $(GOVERALLS_PKG) - -$(LINT_BIN): - @$(call print, "Fetching linter") - $(DEPGET) $(LINT_PKG)@$(LINT_COMMIT) - $(GOACC_BIN): - @$(call print, "Fetching go-acc") - $(DEPGET) $(GOACC_PKG)@$(GOACC_COMMIT) + @$(call print, "Installing go-acc.") + cd $(TOOLS_DIR); go install -trimpath -tags=tools $(GOACC_PKG) -btcd: +$(BTCD_BIN): @$(call print, "Installing btcd.") - $(DEPGET) $(BTCD_PKG)@$(BTCD_COMMIT) + cd $(TOOLS_DIR); go install -trimpath $(BTCD_PKG) -goimports: +$(GOIMPORTS_BIN): @$(call print, "Installing goimports.") - $(DEPGET) $(GOIMPORTS_PKG) + cd $(TOOLS_DIR); go install -trimpath $(GOIMPORTS_PKG) $(GOFUZZ_BIN): - @$(call print, "Fetching go-fuzz") - $(DEPGET) $(GOFUZZ_PKG)@$(GOFUZZ_COMMIT) + @$(call print, "Installing go-fuzz.") + cd $(TOOLS_DIR); go install -trimpath $(GOFUZZ_PKG) $(GOFUZZ_BUILD_BIN): - @$(call print, "Fetching go-fuzz-build") - $(DEPGET) $(GOFUZZ_BUILD_PKG)@$(GOFUZZ_COMMIT) + @$(call print, "Installing go-fuzz-build.") + cd $(TOOLS_DIR); go install -trimpath $(GOFUZZ_BUILD_PKG) + +$(GOFUZZ_DEP_BIN): + @$(call print, "Installing go-fuzz-dep.") + cd $(TOOLS_DIR); go install -trimpath $(GOFUZZ_DEP_PKG) # ============ # INSTALLATION @@ -132,8 +113,8 @@ $(GOFUZZ_BUILD_BIN): build: @$(call print, "Building debug lnd and lncli.") - $(GOBUILD) -tags="$(DEV_TAGS)" -o lnd-debug $(DEV_LDFLAGS) $(PKG)/cmd/lnd - $(GOBUILD) -tags="$(DEV_TAGS)" -o lncli-debug $(DEV_LDFLAGS) $(PKG)/cmd/lncli + $(GOBUILD) -tags="$(DEV_TAGS)" -o lnd-debug $(DEV_GCFLAGS) $(DEV_LDFLAGS) $(PKG)/cmd/lnd + $(GOBUILD) -tags="$(DEV_TAGS)" -o lncli-debug $(DEV_GCFLAGS) $(DEV_LDFLAGS) $(PKG)/cmd/lncli build-itest: @$(call print, "Building itest btcd and lnd.") @@ -178,6 +159,10 @@ docker-release: # that we might want to overwrite in manual tests. $(DOCKER_RELEASE_HELPER) make release tag="$(tag)" sys="$(sys)" COMMIT="$(COMMIT)" COMMIT_HASH="$(COMMIT_HASH)" +docker-tools: + @$(call print, "Building tools docker image.") + docker build -q -t lnd-tools $(TOOLS_DIR) + scratch: build @@ -192,8 +177,9 @@ ifeq ($(dbbackend),postgres) # Remove a previous postgres instance if it exists. docker rm lnd-postgres --force || echo "Starting new postgres container" - # Start a fresh postgres instance. Allow a maximum of 500 connections. - # This is required for the async benchmark to pass. + # Start a fresh postgres instance. Allow a maximum of 500 connections so + # that multiple lnd instances with a maximum number of connections of 50 + # each can run concurrently. docker run --name lnd-postgres -e POSTGRES_PASSWORD=postgres -p 6432:5432 -d postgres:13-alpine -N 500 docker logs -f lnd-postgres & @@ -215,11 +201,15 @@ itest-parallel: build-itest db-instance rm -rf lntest/itest/*.log lntest/itest/.logs-*; date EXEC_SUFFIX=$(EXEC_SUFFIX) echo "$$(seq 0 $$(expr $(ITEST_PARALLELISM) - 1))" | xargs -P $(ITEST_PARALLELISM) -n 1 -I {} scripts/itest_part.sh {} $(NUM_ITEST_TRANCHES) $(TEST_FLAGS) $(ITEST_FLAGS) -unit: btcd +itest-clean: + @$(call print, "Cleaning old itest processes") + killall lnd-itest || echo "no running lnd-itest process found"; + +unit: $(BTCD_BIN) @$(call print, "Running unit tests.") $(UNIT) -unit-debug: btcd +unit-debug: $(BTCD_BIN) @$(call print, "Running debug unit tests.") $(UNIT_DEBUG) @@ -227,20 +217,10 @@ unit-cover: $(GOACC_BIN) @$(call print, "Running unit coverage tests.") $(GOACC_BIN) $(COVER_PKG) -- -tags="$(DEV_TAGS) $(LOG_TAGS)" - unit-race: @$(call print, "Running unit race tests.") env CGO_ENABLED=1 GORACE="history_size=7 halt_on_errors=1" $(UNIT_RACE) -goveralls: $(GOVERALLS_BIN) - @$(call print, "Sending coverage report.") - $(GOVERALLS_BIN) -coverprofile=coverage.txt -service=travis-ci - - -travis-race: btcd unit-race - -travis-cover: btcd unit-cover goveralls - # ============= # FLAKE HUNTING # ============= @@ -260,9 +240,7 @@ flakehunter-parallel: # ============= # FUZZING # ============= -fuzz-build: $(GOFUZZ_BUILD_BIN) - @$(call print, "Fetching go-fuzz-dep package") - $(GOFUZZ_DEP_PKG_FETCH) +fuzz-build: $(GOFUZZ_BUILD_BIN) $(GOFUZZ_DEP_BIN) @$(call print, "Creating fuzz harnesses for packages '$(FUZZPKG)'.") scripts/fuzz.sh build "$(FUZZPKG)" @@ -274,15 +252,15 @@ fuzz-run: $(GOFUZZ_BIN) # UTILITIES # ========= -fmt: goimports +fmt: $(GOIMPORTS_BIN) @$(call print, "Fixing imports.") - goimports -w $(GOFILES_NOVENDOR) + gosimports -w $(GOFILES_NOVENDOR) @$(call print, "Formatting source.") gofmt -l -w -s $(GOFILES_NOVENDOR) -lint: $(LINT_BIN) +lint: docker-tools @$(call print, "Linting source.") - $(LINT) + $(DOCKER_TOOLS) golangci-lint run -v $(LINT_WORKERS) list: @$(call print, "Listing commands.") @@ -302,33 +280,43 @@ rpc-format: rpc-check: rpc @$(call print, "Verifying protos.") cd ./lnrpc; ../scripts/check-rest-annotations.sh - if test -n "$$(git describe --dirty | grep dirty)"; then echo "Protos not properly formatted or not compiled with v3.4.0"; git status; git diff; exit 1; fi + if test -n "$$(git status --porcelain)"; then echo "Protos not properly formatted or not compiled with v3.4.0"; git status; git diff; exit 1; fi rpc-js-compile: @$(call print, "Compiling JSON/WASM stubs.") - GOOS=js GOARCH=wasm $(GOBUILD) -tags="$(RELEASE_TAGS)" $(PKG)/lnrpc/... + GOOS=js GOARCH=wasm $(GOBUILD) -tags="$(WASM_RELEASE_TAGS)" $(PKG)/lnrpc/... sample-conf-check: @$(call print, "Making sure every flag has an example in the sample-lnd.conf file") for flag in $$(GO_FLAGS_COMPLETION=1 go run -tags="$(RELEASE_TAGS)" $(PKG)/cmd/lnd -- | grep -v help | cut -c3-); do if ! grep -q $$flag sample-lnd.conf; then echo "Command line flag --$$flag not added to sample-lnd.conf"; exit 1; fi; done mobile-rpc: - @$(call print, "Creating mobile RPC from protos (prefix=$(prefix)).") - cd ./lnrpc; COMPILE_MOBILE=1 SUBSERVER_PREFIX=$(prefix) ./gen_protos_docker.sh + @$(call print, "Creating mobile RPC from protos.") + cd ./lnrpc; COMPILE_MOBILE=1 SUBSERVER_PREFIX=1 ./gen_protos_docker.sh vendor: @$(call print, "Re-creating vendor directory.") - rm -r vendor/; GO111MODULE=on go mod vendor + rm -r vendor/; go mod vendor + +apple: vendor mobile-rpc + @$(call print, "Building iOS and macOS cxframework ($(IOS_BUILD)).") + mkdir -p $(IOS_BUILD_DIR) + $(GOMOBILE_BIN) bind -target=ios,iossimulator,macos -tags="mobile $(DEV_TAGS) $(RPC_TAGS)" $(LDFLAGS) -v -o $(IOS_BUILD) $(MOBILE_PKG) ios: vendor mobile-rpc - @$(call print, "Building iOS framework ($(IOS_BUILD)).") + @$(call print, "Building iOS cxframework ($(IOS_BUILD)).") + mkdir -p $(IOS_BUILD_DIR) + $(GOMOBILE_BIN) bind -target=ios,iossimulator -tags="mobile $(DEV_TAGS) $(RPC_TAGS)" $(LDFLAGS) -v -o $(IOS_BUILD) $(MOBILE_PKG) + +macos: vendor mobile-rpc + @$(call print, "Building macOS cxframework ($(IOS_BUILD)).") mkdir -p $(IOS_BUILD_DIR) - $(GOMOBILE_BIN) bind -target=ios -tags="mobile $(DEV_TAGS) autopilotrpc" $(LDFLAGS) -v -o $(IOS_BUILD) $(MOBILE_PKG) + $(GOMOBILE_BIN) bind -target=macos -tags="mobile $(DEV_TAGS) $(RPC_TAGS)" $(LDFLAGS) -v -o $(IOS_BUILD) $(MOBILE_PKG) android: vendor mobile-rpc @$(call print, "Building Android library ($(ANDROID_BUILD)).") mkdir -p $(ANDROID_BUILD_DIR) - $(GOMOBILE_BIN) bind -target=android -tags="mobile $(DEV_TAGS) autopilotrpc" $(LDFLAGS) -v -o $(ANDROID_BUILD) $(MOBILE_PKG) + $(GOMOBILE_BIN) bind -target=android -tags="mobile $(DEV_TAGS) $(RPC_TAGS)" $(LDFLAGS) -v -o $(ANDROID_BUILD) $(MOBILE_PKG) mobile: ios android @@ -356,10 +344,6 @@ clean-mobile: unit-debug \ unit-cover \ unit-race \ - goveralls \ - travis-race \ - travis-cover \ - travis-itest \ flakehunter \ flake-unit \ fmt \ diff --git a/aezeed/cipherseed.go b/aezeed/cipherseed.go index a288dae39d..a55bb5d3e1 100644 --- a/aezeed/cipherseed.go +++ b/aezeed/cipherseed.go @@ -10,7 +10,6 @@ import ( "github.com/Yawning/aez" "github.com/kkdai/bstream" - "golang.org/x/crypto/scrypt" ) @@ -498,7 +497,7 @@ func decipherCipherSeed(cipherSeedBytes [EncipheredCipherSeedSize]byte, } // Decipher attempts to decipher the encoded mnemonic by first mapping to the -// original chipertext, then applying our deciphering scheme. ErrInvalidPass +// original ciphertext, then applying our deciphering scheme. ErrInvalidPass // will be returned if the passphrase is incorrect. func (m *Mnemonic) Decipher(pass []byte) ([DecipheredCipherSeedSize]byte, error) { diff --git a/autopilot/agent.go b/autopilot/agent.go index 989840a78e..22eb6c90a3 100644 --- a/autopilot/agent.go +++ b/autopilot/agent.go @@ -8,8 +8,8 @@ import ( "sync" "time" - "github.com/btcsuite/btcd/btcec" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/lnwire" ) @@ -731,7 +731,7 @@ func (a *Agent) executeDirective(directive AttachmentDirective) { // We'll start out by attempting to connect to the peer in order to // begin the funding workflow. nodeID := directive.NodeID - pub, err := btcec.ParsePubKey(nodeID[:], btcec.S256()) + pub, err := btcec.ParsePubKey(nodeID[:]) if err != nil { log.Errorf("Unable to parse pubkey %x: %v", nodeID, err) return diff --git a/autopilot/agent_constraints.go b/autopilot/agent_constraints.go index dadfc35d62..26282116ea 100644 --- a/autopilot/agent_constraints.go +++ b/autopilot/agent_constraints.go @@ -1,7 +1,7 @@ package autopilot import ( - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) // AgentConstraints is an interface the agent will query to determine what diff --git a/autopilot/agent_constraints_test.go b/autopilot/agent_constraints_test.go index 144107a151..64152cef8a 100644 --- a/autopilot/agent_constraints_test.go +++ b/autopilot/agent_constraints_test.go @@ -1,12 +1,11 @@ package autopilot import ( + prand "math/rand" "testing" "time" - prand "math/rand" - - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/lnwire" ) diff --git a/autopilot/agent_test.go b/autopilot/agent_test.go index b12ab792fc..037a49626d 100644 --- a/autopilot/agent_test.go +++ b/autopilot/agent_test.go @@ -8,9 +8,9 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" ) type moreChansResp struct { @@ -339,7 +339,7 @@ func TestAgentHeuristicUpdateSignal(t *testing.T) { // initial check. respondMoreChans(t, testCtx, moreChansResp{0, 0}) - // Next we'll signal that one of the heuristcs have been updated. + // Next we'll signal that one of the heuristics have been updated. testCtx.agent.OnHeuristicUpdate(testCtx.heuristic) // The update should trigger the agent to ask for a channel budget.so @@ -1256,7 +1256,7 @@ func TestAgentChannelSizeAllocation(t *testing.T) { "had %v", len(arg.chans)) } if arg.balance != testCtx.walletBalance { - t.Fatalf("expectd agent to have %v balance, had %v", + t.Fatalf("expected agent to have %v balance, had %v", testCtx.walletBalance, arg.balance) } case <-time.After(time.Second * 3): diff --git a/autopilot/betweenness_centrality.go b/autopilot/betweenness_centrality.go index aa0b9fced7..db45bcf665 100644 --- a/autopilot/betweenness_centrality.go +++ b/autopilot/betweenness_centrality.go @@ -51,7 +51,7 @@ func (q *queue) empty() bool { // BetweennessCentrality is a NodeMetric that calculates node betweenness // centrality using Brandes' algorithm. Betweenness centrality for each node -// is the number of shortest paths passing trough that node, not counting +// is the number of shortest paths passing through that node, not counting // shortest paths starting or ending at that node. This is a useful metric // to measure control of individual nodes over the whole network. type BetweennessCentrality struct { @@ -167,7 +167,7 @@ func betweennessCentrality(g *SimpleGraph, s int, centrality []float64) { } } -// Refresh recaculates and stores centrality values. +// Refresh recalculates and stores centrality values. func (bc *BetweennessCentrality) Refresh(graph ChannelGraph) error { cache, err := NewSimpleGraph(graph) if err != nil { @@ -186,7 +186,7 @@ func (bc *BetweennessCentrality) Refresh(graph ChannelGraph) error { partial := make([]float64, len(cache.Nodes)) // Consume the next node, update centrality - // parital to avoid unnecessary synchronizaton. + // parital to avoid unnecessary synchronization. for node := range work { betweennessCentrality(cache, node, partial) } diff --git a/autopilot/centrality_testdata_test.go b/autopilot/centrality_testdata_test.go index 4675fa4d43..38a9045af5 100644 --- a/autopilot/centrality_testdata_test.go +++ b/autopilot/centrality_testdata_test.go @@ -3,8 +3,8 @@ package autopilot import ( "testing" - "github.com/btcsuite/btcd/btcec" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/stretchr/testify/require" ) @@ -35,7 +35,7 @@ var normalizedTestGraphCentrality = []float64{ 0.2, 0.0, 0.2, 1.0, 0.4, 0.4, 7.0 / 15.0, 0.0, 0.0, } -// buildTestGraph builds a test graph from a passed graph desriptor. +// buildTestGraph builds a test graph from a passed graph descriptor. func buildTestGraph(t *testing.T, graph testGraph, desc testGraphDesc) map[int]*btcec.PublicKey { diff --git a/autopilot/choice.go b/autopilot/choice.go index 661f58d93f..26bcd3ce6d 100644 --- a/autopilot/choice.go +++ b/autopilot/choice.go @@ -11,7 +11,7 @@ import ( var ErrNoPositive = errors.New("no positive weights left") // weightedChoice draws a random index from the slice of weights, with a -// probability propotional to the weight at the given index. +// probability proportional to the weight at the given index. func weightedChoice(w []float64) (int, error) { // Calculate the sum of weights. var sum float64 @@ -25,7 +25,7 @@ func weightedChoice(w []float64) (int, error) { // Pick a random number in the range [0.0, 1.0) and multiply it with // the sum of weights. Then we'll iterate the weights until the number - // goes below 0. This means that each index is picked with a probablity + // goes below 0. This means that each index is picked with a probability // equal to their normalized score. // // Example: diff --git a/autopilot/combinedattach.go b/autopilot/combinedattach.go index 56041d084c..3b92d14d7a 100644 --- a/autopilot/combinedattach.go +++ b/autopilot/combinedattach.go @@ -3,7 +3,7 @@ package autopilot import ( "fmt" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) // WeightedHeuristic is a tuple that associates a weight to an @@ -92,7 +92,7 @@ func (c *WeightedCombAttachment) NodeScores(g ChannelGraph, chans []LocalChannel } // We combine the scores given by the sub-heuristics by using the - // heruistics' given weight factor. + // heuristics' given weight factor. scores := make(map[NodeID]*NodeScore) for nID := range nodes { score := &NodeScore{ diff --git a/autopilot/externalscoreattach.go b/autopilot/externalscoreattach.go index 015492185a..1144d7d4a5 100644 --- a/autopilot/externalscoreattach.go +++ b/autopilot/externalscoreattach.go @@ -4,7 +4,7 @@ import ( "fmt" "sync" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) // ExternalScoreAttachment is an implementation of the AttachmentHeuristic diff --git a/autopilot/externalscoreattach_test.go b/autopilot/externalscoreattach_test.go index 4e274aea92..aa0f87fb26 100644 --- a/autopilot/externalscoreattach_test.go +++ b/autopilot/externalscoreattach_test.go @@ -3,14 +3,14 @@ package autopilot_test import ( "testing" - "github.com/btcsuite/btcd/btcec" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/autopilot" ) // randKey returns a random public key. func randKey() (*btcec.PublicKey, error) { - priv, err := btcec.NewPrivateKey(btcec.S256()) + priv, err := btcec.NewPrivateKey() if err != nil { return nil, err } diff --git a/autopilot/graph.go b/autopilot/graph.go index e630f8d35d..0b062a5358 100644 --- a/autopilot/graph.go +++ b/autopilot/graph.go @@ -2,14 +2,15 @@ package autopilot import ( "bytes" - "math/big" + "encoding/hex" "net" "sort" "sync/atomic" "time" - "github.com/btcsuite/btcd/btcec" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lnwire" @@ -17,12 +18,13 @@ import ( ) var ( - testSig = &btcec.Signature{ - R: new(big.Int), - S: new(big.Int), - } - _, _ = testSig.R.SetString("63724406601629180062774974542967536251589935445068131219452686511677818569431", 10) - _, _ = testSig.S.SetString("18801056069249825825291287104931333862866033135609736119018462340006816851118", 10) + testRBytes, _ = hex.DecodeString("8ce2bc69281ce27da07e6683571319d18e949ddfa2965fb6caa1bf0314f882d7") + testSBytes, _ = hex.DecodeString("299105481d63e0f4bc2a88121167221b6700d72a0ead154c03be696a292d24ae") + testRScalar = new(btcec.ModNScalar) + testSScalar = new(btcec.ModNScalar) + _ = testRScalar.SetByteSlice(testRBytes) + _ = testSScalar.SetByteSlice(testSBytes) + testSig = ecdsa.NewSignature(testRScalar, testSScalar) chanIDCounter uint64 // To be used atomically. ) @@ -344,7 +346,7 @@ func randChanID() lnwire.ShortChannelID { // randKey returns a random public key. func randKey() (*btcec.PublicKey, error) { - priv, err := btcec.NewPrivateKey(btcec.S256()) + priv, err := btcec.NewPrivateKey() if err != nil { return nil, err } @@ -455,6 +457,96 @@ func (m *memChannelGraph) addRandNode() (*btcec.PublicKey, error) { return newPub, nil } +// databaseChannelGraphCached wraps a channeldb.ChannelGraph instance with the +// necessary API to properly implement the autopilot.ChannelGraph interface. +type databaseChannelGraphCached struct { + db *channeldb.ChannelGraph +} + +// A compile time assertion to ensure databaseChannelGraphCached meets the +// autopilot.ChannelGraph interface. +var _ ChannelGraph = (*databaseChannelGraphCached)(nil) + +// ChannelGraphFromCachedDatabase returns an instance of the +// autopilot.ChannelGraph backed by a live, open channeldb instance. +func ChannelGraphFromCachedDatabase(db *channeldb.ChannelGraph) ChannelGraph { + return &databaseChannelGraphCached{ + db: db, + } +} + +// dbNodeCached is a wrapper struct around a database transaction for a +// channeldb.LightningNode. The wrapper methods implement the autopilot.Node +// interface. +type dbNodeCached struct { + node route.Vertex + channels map[uint64]*channeldb.DirectedChannel +} + +// A compile time assertion to ensure dbNodeCached meets the autopilot.Node +// interface. +var _ Node = (*dbNodeCached)(nil) + +// PubKey is the identity public key of the node. +// +// NOTE: Part of the autopilot.Node interface. +func (nc dbNodeCached) PubKey() [33]byte { + return nc.node +} + +// Addrs returns a slice of publicly reachable public TCP addresses that the +// peer is known to be listening on. +// +// NOTE: Part of the autopilot.Node interface. +func (nc dbNodeCached) Addrs() []net.Addr { + // TODO: Add addresses to be usable by autopilot. + return []net.Addr{} +} + +// ForEachChannel is a higher-order function that will be used to iterate +// through all edges emanating from/to the target node. For each active +// channel, this function should be called with the populated ChannelEdge that +// describes the active channel. +// +// NOTE: Part of the autopilot.Node interface. +func (nc dbNodeCached) ForEachChannel(cb func(ChannelEdge) error) error { + for cid, channel := range nc.channels { + edge := ChannelEdge{ + ChanID: lnwire.NewShortChanIDFromInt(cid), + Capacity: channel.Capacity, + Peer: dbNodeCached{ + node: channel.OtherNode, + }, + } + + if err := cb(edge); err != nil { + return err + } + } + + return nil +} + +// ForEachNode is a higher-order function that should be called once for each +// connected node within the channel graph. If the passed callback returns an +// error, then execution should be terminated. +// +// NOTE: Part of the autopilot.ChannelGraph interface. +func (dc *databaseChannelGraphCached) ForEachNode(cb func(Node) error) error { + return dc.db.ForEachNodeCached(func(n route.Vertex, + channels map[uint64]*channeldb.DirectedChannel) error { + + if len(channels) > 0 { + node := dbNodeCached{ + node: n, + channels: channels, + } + return cb(node) + } + return nil + }) +} + // memNode is a purely in-memory implementation of the autopilot.Node // interface. type memNode struct { diff --git a/autopilot/graph_test.go b/autopilot/graph_test.go index a83d7f3aa1..9a52e67c89 100644 --- a/autopilot/graph_test.go +++ b/autopilot/graph_test.go @@ -3,7 +3,7 @@ package autopilot_test import ( "testing" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/autopilot" ) diff --git a/autopilot/interface.go b/autopilot/interface.go index 7858be436d..671d343325 100644 --- a/autopilot/interface.go +++ b/autopilot/interface.go @@ -3,9 +3,9 @@ package autopilot import ( "net" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/lnwire" ) diff --git a/autopilot/manager.go b/autopilot/manager.go index 04f778d59d..e2b09f11ef 100644 --- a/autopilot/manager.go +++ b/autopilot/manager.go @@ -4,7 +4,7 @@ import ( "fmt" "sync" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" diff --git a/autopilot/prefattach.go b/autopilot/prefattach.go index c25009d12e..4f4ff635fa 100644 --- a/autopilot/prefattach.go +++ b/autopilot/prefattach.go @@ -4,8 +4,8 @@ import ( prand "math/rand" "time" - "github.com/btcsuite/btcd/btcec" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" ) // minMedianChanSizeFraction determines the minimum size a channel must have to diff --git a/autopilot/prefattach_test.go b/autopilot/prefattach_test.go index 24934592bc..3b1dcfec3f 100644 --- a/autopilot/prefattach_test.go +++ b/autopilot/prefattach_test.go @@ -3,14 +3,13 @@ package autopilot import ( "bytes" "io/ioutil" + prand "math/rand" "os" "testing" "time" - prand "math/rand" - - "github.com/btcsuite/btcd/btcec" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/channeldb" ) @@ -253,9 +252,7 @@ func TestPrefAttachmentSelectGreedyAllocation(t *testing.T) { t1.Fatalf("unable to create channel: %v", err) } peerPubBytes := edge1.Peer.PubKey() - peerPub, err := btcec.ParsePubKey( - peerPubBytes[:], btcec.S256(), - ) + peerPub, err := btcec.ParsePubKey(peerPubBytes[:]) if err != nil { t.Fatalf("unable to parse pubkey: %v", err) } diff --git a/autopilot/simple_graph.go b/autopilot/simple_graph.go index 208a784e9f..f7bf4c12c9 100644 --- a/autopilot/simple_graph.go +++ b/autopilot/simple_graph.go @@ -1,5 +1,12 @@ package autopilot +// diameterCutoff is used to discard nodes in the diameter calculation. +// It is the multiplier for the eccentricity of the highest-degree node, +// serving as a cutoff to discard all nodes with a smaller hop distance. This +// number should not be set close to 1 and is a tradeoff for computation cost, +// where 0 is maximally costly. +const diameterCutoff = 0.75 + // SimpleGraph stores a simplifed adj graph of a channel graph to speed // up graph processing by eliminating all unnecessary hashing and map access. type SimpleGraph struct { @@ -62,5 +69,162 @@ func NewSimpleGraph(g ChannelGraph) (*SimpleGraph, error) { graph.Nodes[nodeIndex] = nodeID } + // We prepare to give some debug output about the size of the graph. + totalChannels := 0 + for _, channels := range graph.Adj { + totalChannels += len(channels) + } + + // The number of channels is double counted, so divide by two. + log.Debugf("Initialized simple graph with %d nodes and %d "+ + "channels", len(graph.Adj), totalChannels/2) return graph, nil } + +// maxVal is a helper function to get the maximal value of all values of a map. +func maxVal(mapping map[int]uint32) uint32 { + maxValue := uint32(0) + for _, value := range mapping { + if maxValue < value { + maxValue = value + } + } + return maxValue +} + +// degree determines the number of edges for a node in the graph. +func (graph *SimpleGraph) degree(node int) int { + return len(graph.Adj[node]) +} + +// nodeMaxDegree determines the node with the max degree and its degree. +func (graph *SimpleGraph) nodeMaxDegree() (int, int) { + var maxNode, maxDegree int + for node := range graph.Adj { + degree := graph.degree(node) + if degree > maxDegree { + maxNode = node + maxDegree = degree + } + } + return maxNode, maxDegree +} + +// shortestPathLengths performs a breadth-first-search from a node to all other +// nodes, returning the lengths of the paths. +func (graph *SimpleGraph) shortestPathLengths(node int) map[int]uint32 { + // level indicates the shell of the search around the root node. + var level uint32 + graphOrder := len(graph.Adj) + + // nextLevel tracks which nodes should be visited in the next round. + nextLevel := make([]int, 0, graphOrder) + + // The root node is put as a starting point for the exploration. + nextLevel = append(nextLevel, node) + + // Seen tracks already visited nodes and tracks how far away they are. + seen := make(map[int]uint32, graphOrder) + + // Mark the root node as seen. + seen[node] = level + + // thisLevel contains the nodes that are explored in the round. + thisLevel := make([]int, 0, graphOrder) + + // We discover other nodes in a ring-like structure as long as we don't + // have more nodes to explore. + for len(nextLevel) > 0 { + level++ + + // We swap the queues for efficient memory management. + thisLevel, nextLevel = nextLevel, thisLevel + + // Visit all neighboring nodes of the level and mark them as + // seen if they were not discovered before. + for _, thisNode := range thisLevel { + for _, neighbor := range graph.Adj[thisNode] { + _, ok := seen[neighbor] + if !ok { + nextLevel = append(nextLevel, neighbor) + seen[neighbor] = level + } + + // If we have seen all nodes, we return early. + if len(seen) == graphOrder { + return seen + } + } + } + + // Empty the queue to be used in the next level. + thisLevel = thisLevel[:0:cap(thisLevel)] + } + + return seen +} + +// nodeEccentricity calculates the eccentricity (longest shortest path to all +// other nodes) of a node. +func (graph *SimpleGraph) nodeEccentricity(node int) uint32 { + pathLengths := graph.shortestPathLengths(node) + return maxVal(pathLengths) +} + +// nodeEccentricities calculates the eccentricities for the given nodes. +func (graph *SimpleGraph) nodeEccentricities(nodes []int) map[int]uint32 { + eccentricities := make(map[int]uint32, len(graph.Adj)) + for _, node := range nodes { + eccentricities[node] = graph.nodeEccentricity(node) + } + return eccentricities +} + +// Diameter returns the maximal eccentricity (longest shortest path +// between any node pair) in the graph. +// +// Note: This method is exact but expensive, use DiameterRadialCutoff instead. +func (graph *SimpleGraph) Diameter() uint32 { + nodes := make([]int, len(graph.Adj)) + for a := range nodes { + nodes[a] = a + } + eccentricities := graph.nodeEccentricities(nodes) + return maxVal(eccentricities) +} + +// DiameterRadialCutoff is a method to efficiently evaluate the diameter of a +// graph. The highest-degree node is usually central in the graph. We can +// determine its eccentricity (shortest-longest path length to any other node) +// and use it as an approximation for the radius of the network. We then +// use this radius to compute a cutoff. All the nodes within a distance of the +// cutoff are discarded, as they represent the inside of the graph. We then +// loop over all outer nodes and determine their eccentricities, from which we +// get the diameter. +func (graph *SimpleGraph) DiameterRadialCutoff() uint32 { + // Determine the reference node as the node with the highest degree. + nodeMaxDegree, _ := graph.nodeMaxDegree() + + distances := graph.shortestPathLengths(nodeMaxDegree) + eccentricityMaxDegreeNode := maxVal(distances) + + // We use the eccentricity to define a cutoff for the interior of the + // graph from the reference node. + cutoff := uint32(float32(eccentricityMaxDegreeNode) * diameterCutoff) + log.Debugf("Cutoff radius is %d hops (max-degree node's "+ + "eccentricity is %d)", cutoff, eccentricityMaxDegreeNode) + + // Remove the nodes that are close to the reference node. + var nodes []int + for node, distance := range distances { + if distance > cutoff { + nodes = append(nodes, node) + } + } + log.Debugf("Evaluated nodes: %d, discarded nodes %d", + len(nodes), len(graph.Adj)-len(nodes)) + + // Compute the diameter of the remaining nodes. + eccentricities := graph.nodeEccentricities(nodes) + return maxVal(eccentricities) +} diff --git a/autopilot/simple_graph_test.go b/autopilot/simple_graph_test.go new file mode 100644 index 0000000000..03fb2ed902 --- /dev/null +++ b/autopilot/simple_graph_test.go @@ -0,0 +1,94 @@ +package autopilot + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +var ( + testShortestPathLengths = map[int]uint32{ + 0: 0, + 1: 1, + 2: 1, + 3: 1, + 4: 2, + 5: 2, + 6: 3, + 7: 3, + 8: 4, + } + testNodeEccentricities = map[int]uint32{ + 0: 4, + 1: 5, + 2: 4, + 3: 3, + 4: 3, + 5: 3, + 6: 4, + 7: 4, + 8: 5, + } +) + +// NewTestSimpleGraph is a helper that generates a SimpleGraph from a test +// graph description. +// Assumes that the graph description is internally consistent, i.e. edges are +// not repeatedly defined. +func NewTestSimpleGraph(graph testGraphDesc) SimpleGraph { + // We convert the test graph description into an adjacency list. + adjList := make([][]int, graph.nodes) + for node, neighbors := range graph.edges { + for _, neighbor := range neighbors { + adjList[node] = append(adjList[node], neighbor) + adjList[neighbor] = append(adjList[neighbor], node) + } + } + + return SimpleGraph{Adj: adjList} +} + +func TestShortestPathLengths(t *testing.T) { + simpleGraph := NewTestSimpleGraph(centralityTestGraph) + + // Test the shortest path lengths from node 0 to all other nodes. + shortestPathLengths := simpleGraph.shortestPathLengths(0) + require.Equal(t, shortestPathLengths, testShortestPathLengths) +} + +func TestEccentricities(t *testing.T) { + simpleGraph := NewTestSimpleGraph(centralityTestGraph) + + // Test the node eccentricities for all nodes. + nodes := make([]int, len(simpleGraph.Adj)) + for a := range nodes { + nodes[a] = a + } + nodeEccentricities := simpleGraph.nodeEccentricities(nodes) + require.Equal(t, nodeEccentricities, testNodeEccentricities) +} + +func TestDiameterExact(t *testing.T) { + simpleGraph := NewTestSimpleGraph(centralityTestGraph) + + // Test the diameter in a brute-force manner. + diameter := simpleGraph.Diameter() + require.Equal(t, uint32(5), diameter) +} + +func TestDiameterCutoff(t *testing.T) { + simpleGraph := NewTestSimpleGraph(centralityTestGraph) + + // Test the diameter by cutting out the inside of the graph. + diameter := simpleGraph.DiameterRadialCutoff() + require.Equal(t, uint32(5), diameter) +} + +func BenchmarkShortestPathOpt(b *testing.B) { + // TODO: a method that generates a huge graph is needed + simpleGraph := NewTestSimpleGraph(centralityTestGraph) + + for n := 0; n < b.N; n++ { + _ = simpleGraph.shortestPathLengths(0) + } +} diff --git a/autopilot/top_centrality.go b/autopilot/top_centrality.go index 354b20ffc3..65157c6212 100644 --- a/autopilot/top_centrality.go +++ b/autopilot/top_centrality.go @@ -3,7 +3,7 @@ package autopilot import ( "runtime" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) // TopCentrality is a simple greedy technique to create connections to nodes diff --git a/autopilot/top_centrality_test.go b/autopilot/top_centrality_test.go index dbc627e57b..2688f40902 100644 --- a/autopilot/top_centrality_test.go +++ b/autopilot/top_centrality_test.go @@ -3,8 +3,8 @@ package autopilot import ( "testing" - "github.com/btcsuite/btcd/btcec" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/stretchr/testify/require" ) diff --git a/blockcache/blockcache.go b/blockcache/blockcache.go index 34db764a7e..a7cf070b57 100644 --- a/blockcache/blockcache.go +++ b/blockcache/blockcache.go @@ -1,9 +1,9 @@ package blockcache import ( + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightninglabs/neutrino/cache" "github.com/lightninglabs/neutrino/cache/lru" "github.com/lightningnetwork/lnd/lntypes" diff --git a/blockcache/blockcache_test.go b/blockcache/blockcache_test.go index 5ea5ae9ca1..1453a90120 100644 --- a/blockcache/blockcache_test.go +++ b/blockcache/blockcache_test.go @@ -6,9 +6,9 @@ import ( "sync" "testing" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightninglabs/neutrino/cache" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/brontide/conn.go b/brontide/conn.go index bb1efb7f8c..e83c8a5a89 100644 --- a/brontide/conn.go +++ b/brontide/conn.go @@ -7,7 +7,7 @@ import ( "net" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/tor" diff --git a/brontide/noise.go b/brontide/noise.go index e8b71a8775..a243e12b66 100644 --- a/brontide/noise.go +++ b/brontide/noise.go @@ -10,11 +10,10 @@ import ( "math" "time" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/lightningnetwork/lnd/keychain" "golang.org/x/crypto/chacha20poly1305" "golang.org/x/crypto/hkdf" - - "github.com/btcsuite/btcd/btcec" - "github.com/lightningnetwork/lnd/keychain" ) const ( @@ -64,7 +63,7 @@ var ( // ephemeralGen is the default ephemeral key generator, used to derive a // unique ephemeral key for each brontide handshake. ephemeralGen = func() (*btcec.PrivateKey, error) { - return btcec.NewPrivateKey(btcec.S256()) + return btcec.NewPrivateKey() } ) @@ -501,7 +500,7 @@ func (b *Machine) RecvActOne(actOne [ActOneSize]byte) error { copy(p[:], actOne[34:]) // e - b.remoteEphemeral, err = btcec.ParsePubKey(e[:], btcec.S256()) + b.remoteEphemeral, err = btcec.ParsePubKey(e[:]) if err != nil { return err } @@ -579,7 +578,7 @@ func (b *Machine) RecvActTwo(actTwo [ActTwoSize]byte) error { copy(p[:], actTwo[34:]) // e - b.remoteEphemeral, err = btcec.ParsePubKey(e[:], btcec.S256()) + b.remoteEphemeral, err = btcec.ParsePubKey(e[:]) if err != nil { return err } @@ -655,7 +654,7 @@ func (b *Machine) RecvActThree(actThree [ActThreeSize]byte) error { if err != nil { return err } - b.remoteStatic, err = btcec.ParsePubKey(remotePub, btcec.S256()) + b.remoteStatic, err = btcec.ParsePubKey(remotePub) if err != nil { return err } @@ -891,23 +890,3 @@ func (b *Machine) ReadBody(r io.Reader, buf []byte) ([]byte, error) { // buffer to decode the plaintext. return b.recvCipher.Decrypt(nil, buf[:0], buf) } - -// SetCurveToNil sets the 'Curve' parameter to nil on the handshakeState keys. -// This allows us to log the Machine object without spammy log messages. -func (b *Machine) SetCurveToNil() { - if b.localStatic != nil { - b.localStatic.PubKey().Curve = nil - } - - if b.localEphemeral != nil { - b.localEphemeral.PubKey().Curve = nil - } - - if b.remoteStatic != nil { - b.remoteStatic.Curve = nil - } - - if b.remoteEphemeral != nil { - b.remoteEphemeral.Curve = nil - } -} diff --git a/brontide/noise_test.go b/brontide/noise_test.go index 720219a072..20c6d6b247 100644 --- a/brontide/noise_test.go +++ b/brontide/noise_test.go @@ -10,7 +10,7 @@ import ( "testing" "testing/iotest" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/tor" @@ -23,7 +23,7 @@ type maybeNetConn struct { func makeListener() (*Listener, *lnwire.NetAddress, error) { // First, generate the long-term private keys for the brontide listener. - localPriv, err := btcec.NewPrivateKey(btcec.S256()) + localPriv, err := btcec.NewPrivateKey() if err != nil { return nil, nil, err } @@ -56,7 +56,7 @@ func establishTestConnection() (net.Conn, net.Conn, func(), error) { // Nos, generate the long-term private keys remote end of the connection // within our test. - remotePriv, err := btcec.NewPrivateKey(btcec.S256()) + remotePriv, err := btcec.NewPrivateKey() if err != nil { return nil, nil, nil, err } @@ -193,7 +193,7 @@ func TestConcurrentHandshakes(t *testing.T) { // Now, construct a new private key and use the brontide dialer to // connect to the listener. - remotePriv, err := btcec.NewPrivateKey(btcec.S256()) + remotePriv, err := btcec.NewPrivateKey() if err != nil { t.Fatalf("unable to generate private key: %v", err) } @@ -326,7 +326,7 @@ func TestBolt0008TestVectors(t *testing.T) { t.Fatalf("unable to decode hex: %v", err) } initiatorPriv, _ := btcec.PrivKeyFromBytes( - btcec.S256(), initiatorKeyBytes, + initiatorKeyBytes, ) initiatorKeyECDH := &keychain.PrivKeyECDH{PrivKey: initiatorPriv} @@ -337,7 +337,7 @@ func TestBolt0008TestVectors(t *testing.T) { t.Fatalf("unable to decode hex: %v", err) } responderPriv, responderPub := btcec.PrivKeyFromBytes( - btcec.S256(), responderKeyBytes, + responderKeyBytes, ) responderKeyECDH := &keychain.PrivKeyECDH{PrivKey: responderPriv} @@ -353,7 +353,7 @@ func TestBolt0008TestVectors(t *testing.T) { return nil, err } - priv, _ := btcec.PrivKeyFromBytes(btcec.S256(), eBytes) + priv, _ := btcec.PrivKeyFromBytes(eBytes) return priv, nil }) responderEphemeral := EphemeralGenerator(func() (*btcec.PrivateKey, error) { @@ -364,7 +364,7 @@ func TestBolt0008TestVectors(t *testing.T) { return nil, err } - priv, _ := btcec.PrivKeyFromBytes(btcec.S256(), eBytes) + priv, _ := btcec.PrivKeyFromBytes(eBytes) return priv, nil }) diff --git a/build/dependencies.go b/build/dependencies.go new file mode 100644 index 0000000000..10d30a6d7e --- /dev/null +++ b/build/dependencies.go @@ -0,0 +1,15 @@ +//go:build buildtagdoesnotexist +// +build buildtagdoesnotexist + +package build + +// This file is a workaround to make sure go mod keeps around the btcd and fuzz +// dependencies in the go.sum file that we only use during certain tasks (such +// as integration tests or fuzzing) or only for certain operating systems. For +// example, the specific btcd import makes sure the indirect dependency +// github.com/btcsuite/winsvc is kept in the go.sum file. Because of the build +// tag, this dependency never ends up in the final lnd binary. +import ( + _ "github.com/btcsuite/btcd" + _ "github.com/dvyukov/go-fuzz/go-fuzz-dep" +) diff --git a/build/log_test.go b/build/log_test.go index b26c125b58..001b8e08b3 100644 --- a/build/log_test.go +++ b/build/log_test.go @@ -32,7 +32,7 @@ func (m *mockSubLogger) SetLogLevels(logLevel string) { m.globalLogLevel = logLevel } -// TestParseAndSetDebugLevels tests tha we can properly set the log levels for +// TestParseAndSetDebugLevels tests that we can properly set the log levels for // all andspecified subsystems. func TestParseAndSetDebugLevels(t *testing.T) { testCases := []struct { diff --git a/build/version.go b/build/version.go index 236c5d81d3..f4276f1d30 100644 --- a/build/version.go +++ b/build/version.go @@ -1,7 +1,7 @@ // Copyright (c) 2013-2017 The btcsuite developers // Copyright (c) 2015-2016 The Decred developers // Heavily inspired by https://github.com/btcsuite/btcd/blob/master/version.go -// Copyright (C) 2015-2017 The Lightning Network Developers +// Copyright (C) 2015-2022 The Lightning Network Developers package build @@ -26,7 +26,7 @@ var ( RawTags string // GoVersion stores the go version that the executable was compiled - // with. This hsould be set using -ldflags during compilation. + // with. This should be set using -ldflags during compilation. GoVersion string ) @@ -41,7 +41,7 @@ const ( AppMajor uint = 0 // AppMinor defines the minor version of this binary. - AppMinor uint = 13 + AppMinor uint = 14 // AppPatch defines the application patch for this binary. AppPatch uint = 99 diff --git a/cert/go.mod b/cert/go.mod index 8cd6cc1a48..e80fc5d70d 100644 --- a/cert/go.mod +++ b/cert/go.mod @@ -2,4 +2,7 @@ module github.com/lightningnetwork/lnd/cert go 1.16 -require github.com/stretchr/testify v1.5.1 +require ( + github.com/btcsuite/btcd v0.22.0-beta.0.20211005184431-e3449998be39 + github.com/stretchr/testify v1.5.1 +) diff --git a/cert/go.sum b/cert/go.sum index 331fa69822..0d03750862 100644 --- a/cert/go.sum +++ b/cert/go.sum @@ -1,11 +1,55 @@ +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btcd v0.22.0-beta.0.20211005184431-e3449998be39 h1:o6qacOzpKubr16y0RrE2fBauRZN1rDZ1YsE26ixCgQ0= +github.com/btcsuite/btcd v0.22.0-beta.0.20211005184431-e3449998be39/go.mod h1:3PH+KbvLFfzBTCevQenPiDedjGQGt6aa70dVjJDWGTA= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/cert/selfsigned.go b/cert/selfsigned.go index a0ae23a71e..e7d2d852aa 100644 --- a/cert/selfsigned.go +++ b/cert/selfsigned.go @@ -194,10 +194,10 @@ func IsOutdated(cert *x509.Certificate, tlsExtraIPs, // real PKI. // // This function is adapted from https://github.com/btcsuite/btcd and -// https://github.com/btcsuite/btcutil +// https://github.com/btcsuite/btcd/btcutil func GenCertPair(org, certFile, keyFile string, tlsExtraIPs, tlsExtraDomains []string, tlsDisableAutofill bool, - certValidity time.Duration) error { + certValidity time.Duration) ([]byte, []byte, error) { now := time.Now() validUntil := now.Add(certValidity) @@ -210,7 +210,7 @@ func GenCertPair(org, certFile, keyFile string, tlsExtraIPs, // Generate a serial number that's below the serialNumberLimit. serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) if err != nil { - return fmt.Errorf("failed to generate serial number: %s", err) + return nil, nil, fmt.Errorf("failed to generate serial number: %s", err) } // Get all DNS names and IP addresses to use when creating the @@ -218,13 +218,13 @@ func GenCertPair(org, certFile, keyFile string, tlsExtraIPs, host, dnsNames := dnsNames(tlsExtraDomains, tlsDisableAutofill) ipAddresses, err := ipAddresses(tlsExtraIPs, tlsDisableAutofill) if err != nil { - return err + return nil, nil, err } // Generate a private key for the certificate. priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { - return err + return nil, nil, err } // Construct the certificate template. @@ -250,35 +250,40 @@ func GenCertPair(org, certFile, keyFile string, tlsExtraIPs, derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) if err != nil { - return fmt.Errorf("failed to create certificate: %v", err) + return nil, nil, fmt.Errorf("failed to create certificate: %v", err) } certBuf := &bytes.Buffer{} err = pem.Encode(certBuf, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) if err != nil { - return fmt.Errorf("failed to encode certificate: %v", err) + return nil, nil, fmt.Errorf("failed to encode certificate: %v", err) } keybytes, err := x509.MarshalECPrivateKey(priv) if err != nil { - return fmt.Errorf("unable to encode privkey: %v", err) + return nil, nil, fmt.Errorf("unable to encode privkey: %v", err) } keyBuf := &bytes.Buffer{} err = pem.Encode(keyBuf, &pem.Block{Type: "EC PRIVATE KEY", Bytes: keybytes}) if err != nil { - return fmt.Errorf("failed to encode private key: %v", err) + return nil, nil, fmt.Errorf("failed to encode private key: %v", err) } // Write cert and key files. - if err = ioutil.WriteFile(certFile, certBuf.Bytes(), 0644); err != nil { - return err + if certFile != "" { + if err = ioutil.WriteFile(certFile, certBuf.Bytes(), 0644); err != nil { + return nil, nil, err + } } - if err = ioutil.WriteFile(keyFile, keyBuf.Bytes(), 0600); err != nil { - os.Remove(certFile) - return err + + if keyFile != "" { + if err = ioutil.WriteFile(keyFile, keyBuf.Bytes(), 0600); err != nil { + os.Remove(certFile) + return nil, nil, err + } } - return nil + return certBuf.Bytes(), keyBuf.Bytes(), nil } diff --git a/cert/selfsigned_test.go b/cert/selfsigned_test.go index dd9953e2a3..be3b520255 100644 --- a/cert/selfsigned_test.go +++ b/cert/selfsigned_test.go @@ -1,10 +1,12 @@ package cert_test import ( + "fmt" "io/ioutil" "testing" "time" + "github.com/btcsuite/btcd/btcec" "github.com/lightningnetwork/lnd/cert" "github.com/stretchr/testify/require" ) @@ -16,6 +18,16 @@ const ( var ( extraIPs = []string{"1.1.1.1", "123.123.123.1", "199.189.12.12"} extraDomains = []string{"home", "and", "away"} + privKeyBytes = [32]byte{ + 0xb7, 0x94, 0x38, 0x5f, 0x2d, 0x1e, 0xf7, 0xab, + 0x4d, 0x92, 0x73, 0xd1, 0x90, 0x63, 0x81, 0xb4, + 0x4f, 0x2f, 0x6f, 0x25, 0x88, 0xa3, 0xef, 0xb9, + 0x6a, 0x49, 0x18, 0x83, 0x31, 0x98, 0x47, 0x53, + } + + privKey, _ = btcec.PrivKeyFromBytes( + btcec.S256(), privKeyBytes[:], + ) ) // TestIsOutdatedCert checks that we'll consider the TLS certificate outdated @@ -30,7 +42,7 @@ func TestIsOutdatedCert(t *testing.T) { keyPath := tempDir + "/tls.key" // Generate TLS files with two extra IPs and domains. - err = cert.GenCertPair( + _, _, err = cert.GenCertPair( "lnd autogenerated cert", certPath, keyPath, extraIPs[:2], extraDomains[:2], false, testTLSCertDuration, ) @@ -42,8 +54,17 @@ func TestIsOutdatedCert(t *testing.T) { // number of IPs and domains. for numIPs := 1; numIPs <= len(extraIPs); numIPs++ { for numDomains := 1; numDomains <= len(extraDomains); numDomains++ { + certBytes, err := ioutil.ReadFile(certPath) + if err != nil { + t.Fatal(err) + } + keyBytes, err := ioutil.ReadFile(keyPath) + if err != nil { + t.Fatal(err) + } + _, parsedCert, err := cert.LoadCert( - certPath, keyPath, + certBytes, keyBytes, ) if err != nil { t.Fatal(err) @@ -85,14 +106,22 @@ func TestIsOutdatedPermutation(t *testing.T) { keyPath := tempDir + "/tls.key" // Generate TLS files from the IPs and domains. - err = cert.GenCertPair( + _, _, err = cert.GenCertPair( "lnd autogenerated cert", certPath, keyPath, extraIPs[:], extraDomains[:], false, testTLSCertDuration, ) if err != nil { t.Fatal(err) } - _, parsedCert, err := cert.LoadCert(certPath, keyPath) + certBytes, err := ioutil.ReadFile(certPath) + if err != nil { + t.Fatal(err) + } + keyBytes, err := ioutil.ReadFile(keyPath) + if err != nil { + t.Fatal(err) + } + _, parsedCert, err := cert.LoadCert(certBytes, keyBytes) if err != nil { t.Fatal(err) } @@ -152,7 +181,7 @@ func TestTLSDisableAutofill(t *testing.T) { keyPath := tempDir + "/tls.key" // Generate TLS files with two extra IPs and domains and no interface IPs. - err = cert.GenCertPair( + _, _, err = cert.GenCertPair( "lnd autogenerated cert", certPath, keyPath, extraIPs[:2], extraDomains[:2], true, testTLSCertDuration, ) @@ -161,8 +190,19 @@ func TestTLSDisableAutofill(t *testing.T) { "unable to generate tls certificate pair", ) + // Read certs from disk + certBytes, err := ioutil.ReadFile(certPath) + if err != nil { + t.Fatal(err) + } + keyBytes, err := ioutil.ReadFile(keyPath) + if err != nil { + t.Fatal(err) + } + + // Load the certificate _, parsedCert, err := cert.LoadCert( - certPath, keyPath, + certBytes, keyBytes, ) require.NoError( t, err, @@ -195,3 +235,71 @@ func TestTLSDisableAutofill(t *testing.T) { "TLS Certificate was not marked as outdated when it should be", ) } + +// TestTlsConfig tests to ensure we can generate a TLS Config from +// a tls cert and tls key. +func TestTlsConfig(t *testing.T) { + tempDir, err := ioutil.TempDir("", "certtest") + if err != nil { + t.Fatal(err) + } + + certPath := tempDir + "/tls.cert" + keyPath := tempDir + "/tls.key" + + // Generate TLS files with an extra IP and domain. + _, _, err = cert.GenCertPair( + "lnd autogenerated cert", certPath, keyPath, []string{extraIPs[0]}, + []string{extraDomains[0]}, false, testTLSCertDuration, + ) + if err != nil { + t.Fatal(err) + } + + // Read certs from disk + certBytes, err := ioutil.ReadFile(certPath) + if err != nil { + t.Fatal(err) + } + keyBytes, err := ioutil.ReadFile(keyPath) + if err != nil { + t.Fatal(err) + } + + // Load the certificate + certData, parsedCert, err := cert.LoadCert( + certBytes, keyBytes, + ) + if err != nil { + t.Fatal(err) + } + + // Check to make sure the IP and domain are in the cert + var foundDomain bool + var foundIp bool + for _, domain := range parsedCert.DNSNames { + if domain == extraDomains[0] { + foundDomain = true + break + } + } + for _, ip := range parsedCert.IPAddresses { + if ip.String() == extraIPs[0] { + foundIp = true + break + } + } + if !foundDomain || !foundIp { + t.Fatal(fmt.Errorf("Did not find required information inside "+ + "of TLS Certificate. foundDomain: %v, foundIp: %v", + foundDomain, foundIp)) + } + + // Create TLS Config + tlsCfg := cert.TLSConfFromCert(certData) + + if len(tlsCfg.Certificates) != 1 { + t.Fatal(fmt.Errorf("Found incorrect number of TLS certificates "+ + "in TLS Config: %v", len(tlsCfg.Certificates))) + } +} diff --git a/cert/tls.go b/cert/tls.go index a8783158e1..6d90f2896d 100644 --- a/cert/tls.go +++ b/cert/tls.go @@ -3,6 +3,8 @@ package cert import ( "crypto/tls" "crypto/x509" + "io/ioutil" + "sync" ) var ( @@ -24,17 +26,36 @@ var ( } ) +type TlsReloader struct { + certMu sync.RWMutex + cert *tls.Certificate +} + +// GetCertBytesFromPath reads the TLS certificate and key files at the given +// certPath and keyPath and returns the file bytes. +func GetCertBytesFromPath(certPath, keyPath string) (certBytes, keyBytes []byte, err error) { + certBytes, err = ioutil.ReadFile(certPath) + if err != nil { + return nil, nil, err + } + keyBytes, err = ioutil.ReadFile(keyPath) + if err != nil { + return nil, nil, err + } + return certBytes, keyBytes, nil +} + // LoadCert loads a certificate and its corresponding private key from the PEM -// files indicated and returns the certificate in the two formats it is most +// bytes indicated and returns the certificate in the two formats it is most // commonly used. -func LoadCert(certPath, keyPath string) (tls.Certificate, *x509.Certificate, +func LoadCert(certBytes, keyBytes []byte) (tls.Certificate, *x509.Certificate, error) { // The certData returned here is just a wrapper around the PEM blocks // loaded from the file. The PEM is not yet fully parsed but a basic // check is performed that the certificate and private key actually // belong together. - certData, err := tls.LoadX509KeyPair(certPath, keyPath) + certData, err := tls.X509KeyPair(certBytes, keyBytes) if err != nil { return tls.Certificate{}, nil, err } @@ -58,3 +79,39 @@ func TLSConfFromCert(certData tls.Certificate) *tls.Config { MinVersion: tls.VersionTLS12, } } + +// NewTLSReloader is used to create a new TLS Reloader that will be used +// to update the TLS certificate without restarting the server. +func NewTLSReloader(certBytes, keyBytes []byte) (*TlsReloader, error) { + result := &TlsReloader{} + cert, _, err := LoadCert(certBytes, keyBytes) + if err != nil { + return nil, err + } + result.cert = &cert + return result, nil +} + +// AttemptReload will make an attempt to update the TLS certificate +// and key used by the server. +func (tlsr *TlsReloader) AttemptReload(certBytes, keyBytes []byte) error { + newCert, _, err := LoadCert(certBytes, keyBytes) + if err != nil { + return err + } + tlsr.certMu.Lock() + defer tlsr.certMu.Unlock() + tlsr.cert = &newCert + return nil +} + +// GetCertificateFunc is used in the server's TLS configuration to +// determine the correct TLS certificate to server on a request. +func (tlsr *TlsReloader) GetCertificateFunc() func(*tls.ClientHelloInfo) ( + *tls.Certificate, error) { + return func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) { + tlsr.certMu.RLock() + defer tlsr.certMu.RUnlock() + return tlsr.cert, nil + } +} diff --git a/chainntnfs/bitcoindnotify/bitcoind.go b/chainntnfs/bitcoindnotify/bitcoind.go index d4206d310e..a83b26e067 100644 --- a/chainntnfs/bitcoindnotify/bitcoind.go +++ b/chainntnfs/bitcoindnotify/bitcoind.go @@ -7,11 +7,11 @@ import ( "sync/atomic" "github.com/btcsuite/btcd/btcjson" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/btcsuite/btcwallet/chain" "github.com/lightningnetwork/lnd/blockcache" "github.com/lightningnetwork/lnd/chainntnfs" @@ -19,9 +19,14 @@ import ( ) const ( - // notifierType uniquely identifies this concrete implementation of the - // ChainNotifier interface. - notifierType = "bitcoind" + // notifierType uniquely identifies a concrete implementation of the + // ChainNotifier interface that makes use of the bitcoind ZMQ interface. + notifierTypeZMQ = "bitcoind" + + // notifierTypeRPCPolling uniquely identifies a concrete implementation + // of the ChainNotifier interface that makes use of the bitcoind RPC + // interface. + notifierTypeRPCPolling = "bitcoind-rpc-polling" ) // TODO(roasbeef): generalize struct below: diff --git a/chainntnfs/bitcoindnotify/bitcoind_test.go b/chainntnfs/bitcoindnotify/bitcoind_test.go index a3568c168e..f9511ffe09 100644 --- a/chainntnfs/bitcoindnotify/bitcoind_test.go +++ b/chainntnfs/bitcoindnotify/bitcoind_test.go @@ -108,13 +108,18 @@ func syncNotifierWithMiner(t *testing.T, notifier *BitcoindNotifier, // TestHistoricalConfDetailsTxIndex ensures that we correctly retrieve // historical confirmation details using the backend node's txindex. func TestHistoricalConfDetailsTxIndex(t *testing.T) { + testHistoricalConfDetailsTxIndex(t, true) + testHistoricalConfDetailsTxIndex(t, false) +} + +func testHistoricalConfDetailsTxIndex(t *testing.T, rpcPolling bool) { miner, tearDown := chainntnfs.NewMiner( t, []string{"--txindex"}, true, 25, ) defer tearDown() bitcoindConn, cleanUp := chainntnfs.NewBitcoindBackend( - t, miner.P2PAddress(), true, + t, miner.P2PAddress(), true, rpcPolling, ) defer cleanUp() @@ -175,7 +180,7 @@ func TestHistoricalConfDetailsTxIndex(t *testing.T) { switch txStatus { case chainntnfs.TxFoundMempool: default: - t.Fatal("should have found the transaction within the "+ + t.Fatalf("should have found the transaction within the "+ "mempool, but did not: %v", txStatus) } @@ -206,11 +211,16 @@ func TestHistoricalConfDetailsTxIndex(t *testing.T) { // historical confirmation details using the set of fallback methods when the // backend node's txindex is disabled. func TestHistoricalConfDetailsNoTxIndex(t *testing.T) { + testHistoricalConfDetailsNoTxIndex(t, true) + testHistoricalConfDetailsNoTxIndex(t, false) +} + +func testHistoricalConfDetailsNoTxIndex(t *testing.T, rpcpolling bool) { miner, tearDown := chainntnfs.NewMiner(t, nil, true, 25) defer tearDown() bitcoindConn, cleanUp := chainntnfs.NewBitcoindBackend( - t, miner.P2PAddress(), false, + t, miner.P2PAddress(), false, rpcpolling, ) defer cleanUp() diff --git a/chainntnfs/bitcoindnotify/driver.go b/chainntnfs/bitcoindnotify/driver.go index 634aa35450..1968f74c65 100644 --- a/chainntnfs/bitcoindnotify/driver.go +++ b/chainntnfs/bitcoindnotify/driver.go @@ -56,13 +56,21 @@ func createNewNotifier(args ...interface{}) (chainntnfs.ChainNotifier, error) { // chainntnfs.ChainNotifier interface. func init() { // Register the driver. - notifier := &chainntnfs.NotifierDriver{ - NotifierType: notifierType, + notifierZMQ := &chainntnfs.NotifierDriver{ + NotifierType: notifierTypeZMQ, New: createNewNotifier, } + if err := chainntnfs.RegisterNotifier(notifierZMQ); err != nil { + panic(fmt.Sprintf("failed to register notifier driver '%s': %v", + notifierTypeZMQ, err)) + } - if err := chainntnfs.RegisterNotifier(notifier); err != nil { + notifierRPC := &chainntnfs.NotifierDriver{ + NotifierType: notifierTypeRPCPolling, + New: createNewNotifier, + } + if err := chainntnfs.RegisterNotifier(notifierRPC); err != nil { panic(fmt.Sprintf("failed to register notifier driver '%s': %v", - notifierType, err)) + notifierTypeRPCPolling, err)) } } diff --git a/chainntnfs/btcdnotify/btcd.go b/chainntnfs/btcdnotify/btcd.go index 9ddcbb93cb..335adfa901 100644 --- a/chainntnfs/btcdnotify/btcd.go +++ b/chainntnfs/btcdnotify/btcd.go @@ -8,12 +8,12 @@ import ( "time" "github.com/btcsuite/btcd/btcjson" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/rpcclient" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/blockcache" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/queue" diff --git a/chainntnfs/height_hint_cache.go b/chainntnfs/height_hint_cache.go index b2859ac372..53ee0469a4 100644 --- a/chainntnfs/height_hint_cache.go +++ b/chainntnfs/height_hint_cache.go @@ -35,7 +35,7 @@ var ( ErrConfirmHintNotFound = errors.New("confirm hint not found") ) -// CacheConfig contains the HeightHintCache configuration +// CacheConfig contains the HeightHintCache configuration. type CacheConfig struct { // QueryDisable prevents reliance on the Height Hint Cache. This is // necessary to recover from an edge case when the height recorded in diff --git a/chainntnfs/neutrinonotify/neutrino.go b/chainntnfs/neutrinonotify/neutrino.go index 8f7d702a8d..35d789e113 100644 --- a/chainntnfs/neutrinonotify/neutrino.go +++ b/chainntnfs/neutrinonotify/neutrino.go @@ -9,12 +9,12 @@ import ( "time" "github.com/btcsuite/btcd/btcjson" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/gcs/builder" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/rpcclient" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" - "github.com/btcsuite/btcutil/gcs/builder" "github.com/lightninglabs/neutrino" "github.com/lightninglabs/neutrino/headerfs" "github.com/lightningnetwork/lnd/blockcache" diff --git a/chainntnfs/test/bitcoind/bitcoind_test.go b/chainntnfs/test/bitcoind/bitcoind_test.go index 32335e7bb8..38779f8c7e 100644 --- a/chainntnfs/test/bitcoind/bitcoind_test.go +++ b/chainntnfs/test/bitcoind/bitcoind_test.go @@ -13,4 +13,5 @@ import ( // powered chain notifier. func TestInterfaces(t *testing.T) { chainntnfstest.TestInterfaces(t, "bitcoind") + chainntnfstest.TestInterfaces(t, "bitcoind-rpc-polling") } diff --git a/chainntnfs/test/test_interface.go b/chainntnfs/test/test_interface.go index bdbb532f8c..291d72a565 100644 --- a/chainntnfs/test/test_interface.go +++ b/chainntnfs/test/test_interface.go @@ -12,11 +12,11 @@ import ( "testing" "time" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/integration/rpctest" "github.com/btcsuite/btcd/rpcclient" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/btcsuite/btcwallet/chain" _ "github.com/btcsuite/btcwallet/walletdb/bdb" // Required to auto-register the boltdb walletdb implementation. "github.com/lightninglabs/neutrino" @@ -26,6 +26,7 @@ import ( "github.com/lightningnetwork/lnd/chainntnfs/btcdnotify" "github.com/lightningnetwork/lnd/chainntnfs/neutrinonotify" "github.com/lightningnetwork/lnd/channeldb" + "github.com/stretchr/testify/require" ) func testSingleConfirmationNotification(miner *rpctest.Harness, @@ -418,7 +419,7 @@ func testBlockEpochNotification(miner *rpctest.Harness, // hash. blockEpoch := <-epochClient.Epochs if blockEpoch.BlockHeader == nil { - fmt.Println(i) + t.Logf("%d", i) clientErrors <- fmt.Errorf("block " + "header is nil") return @@ -1178,7 +1179,8 @@ func testReorgConf(miner *rpctest.Harness, // Reorganize transaction out of the chain by generating a longer fork // from the other miner. The transaction is not included in this fork. - miner2.Client.Generate(2) + _, err = miner2.Client.Generate(2) + require.NoError(t, err) // Reconnect nodes to reach consensus on the longest chain. miner2's chain // should win and become active on miner1. @@ -1964,7 +1966,19 @@ func TestInterfaces(t *testing.T, targetBackEnd string) { case "bitcoind": var bitcoindConn *chain.BitcoindConn bitcoindConn, cleanUp = chainntnfs.NewBitcoindBackend( - t, p2pAddr, true, + t, p2pAddr, true, false, + ) + newNotifier = func() (chainntnfs.TestChainNotifier, error) { + return bitcoindnotify.New( + bitcoindConn, chainntnfs.NetParams, + hintCache, hintCache, blockCache, + ), nil + } + + case "bitcoind-rpc-polling": + var bitcoindConn *chain.BitcoindConn + bitcoindConn, cleanUp = chainntnfs.NewBitcoindBackend( + t, p2pAddr, true, true, ) newNotifier = func() (chainntnfs.TestChainNotifier, error) { return bitcoindnotify.New( diff --git a/chainntnfs/test_utils.go b/chainntnfs/test_utils.go index 291c26a644..65eaeb0724 100644 --- a/chainntnfs/test_utils.go +++ b/chainntnfs/test_utils.go @@ -14,18 +14,19 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcjson" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/integration/rpctest" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/btcsuite/btcwallet/chain" "github.com/btcsuite/btcwallet/walletdb" "github.com/lightninglabs/neutrino" "github.com/lightningnetwork/lnd/kvdb" + "github.com/lightningnetwork/lnd/lntest/wait" ) var ( @@ -42,7 +43,7 @@ var ( // randPubKeyHashScript generates a P2PKH script that pays to the public key of // a randomly-generated private key. func randPubKeyHashScript() ([]byte, *btcec.PrivateKey, error) { - privKey, err := btcec.NewPrivateKey(btcec.S256()) + privKey, err := btcec.NewPrivateKey() if err != nil { return nil, nil, err } @@ -193,10 +194,12 @@ func NewMiner(t *testing.T, extraArgs []string, createChain bool, // NewBitcoindBackend spawns a new bitcoind node that connects to a miner at the // specified address. The txindex boolean can be set to determine whether the -// backend node should maintain a transaction index. A connection to the newly -// spawned bitcoind node is returned. -func NewBitcoindBackend(t *testing.T, minerAddr string, - txindex bool) (*chain.BitcoindConn, func()) { +// backend node should maintain a transaction index. The rpcpolling boolean +// can be set to determine whether bitcoind's RPC polling interface should be +// used for block and tx notifications or if its ZMQ interface should be used. +// A connection to the newly spawned bitcoind node is returned. +func NewBitcoindBackend(t *testing.T, minerAddr string, txindex, + rpcpolling bool) (*chain.BitcoindConn, func()) { t.Helper() @@ -231,29 +234,41 @@ func NewBitcoindBackend(t *testing.T, minerAddr string, } // Wait for the bitcoind instance to start up. - time.Sleep(time.Second) - host := fmt.Sprintf("127.0.0.1:%d", rpcPort) - conn, err := chain.NewBitcoindConn(&chain.BitcoindConfig{ - ChainParams: NetParams, - Host: host, - User: "weks", - Pass: "weks", - ZMQBlockHost: zmqBlockHost, - ZMQTxHost: zmqTxHost, - ZMQReadDeadline: 5 * time.Second, + cfg := &chain.BitcoindConfig{ + ChainParams: NetParams, + Host: host, + User: "weks", + Pass: "weks", // Fields only required for pruned nodes, not needed for these // tests. Dialer: nil, PrunedModeMaxPeers: 0, - }) - if err != nil { - bitcoind.Process.Kill() - bitcoind.Wait() - os.RemoveAll(tempBitcoindDir) - t.Fatalf("unable to establish connection to bitcoind: %v", err) } - if err := conn.Start(); err != nil { + + if rpcpolling { + cfg.ZMQConfig = &chain.ZMQConfig{ + ZMQBlockHost: zmqBlockHost, + ZMQTxHost: zmqTxHost, + ZMQReadDeadline: 5 * time.Second, + } + } else { + cfg.PollingConfig = &chain.PollingConfig{ + BlockPollingInterval: time.Millisecond * 20, + TxPollingInterval: time.Millisecond * 20, + } + } + + var conn *chain.BitcoindConn + err = wait.NoError(func() error { + conn, err = chain.NewBitcoindConn(cfg) + if err != nil { + return err + } + + return conn.Start() + }, 10*time.Second) + if err != nil { bitcoind.Process.Kill() bitcoind.Wait() os.RemoveAll(tempBitcoindDir) diff --git a/chainntnfs/txnotifier.go b/chainntnfs/txnotifier.go index 7a328e77c7..409002346f 100644 --- a/chainntnfs/txnotifier.go +++ b/chainntnfs/txnotifier.go @@ -7,10 +7,10 @@ import ( "sync" "sync/atomic" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/channeldb" ) @@ -39,6 +39,19 @@ var ( // notifier to match _and_ dispatch upon detecting the spend of the // script on-chain, rather than the outpoint. ZeroOutPoint wire.OutPoint + + // zeroV1KeyPush is a pkScript that pushes an all-zero 32-byte Taproot + // SegWit v1 key to the stack. + zeroV1KeyPush = [34]byte{ + txscript.OP_1, txscript.OP_DATA_32, // 32 byte of zeroes here + } + + // ZeroTaprootPkScript is the parsed txscript.PkScript of an empty + // Taproot SegWit v1 key being pushed to the stack. This allows the + // notifier to match _and_ dispatch upon detecting the spend of the + // outpoint on-chain, rather than the pkScript (which cannot be derived + // from the witness alone in the SegWit v1 case). + ZeroTaprootPkScript, _ = txscript.ParsePkScript(zeroV1KeyPush[:]) ) var ( @@ -254,7 +267,7 @@ type ConfNtfn struct { dispatched bool } -// HistoricalConfDispatch parameterizes a manual rescan for a particular +// HistoricalConfDispatch parametrizes a manual rescan for a particular // transaction/output script. The parameters include the start and end block // heights specifying the range of blocks to scan. type HistoricalConfDispatch struct { @@ -322,6 +335,24 @@ func NewSpendRequest(op *wire.OutPoint, pkScript []byte) (SpendRequest, error) { } r.PkScript = outputScript + // For Taproot spends we have the main problem that for the key spend + // path we cannot derive the pkScript from only looking at the input's + // witness. So we need to rely on the outpoint information alone. + // + // TODO(guggero): For script path spends we can derive the pkScript from + // the witness, since we have the full control block and the spent + // script available. + if outputScript.Class() == txscript.WitnessV1TaprootTy { + if op == nil { + return r, fmt.Errorf("cannot register witness v1 " + + "spend request without outpoint") + } + + // We have an outpoint, so we can set the pkScript to an all + // zero Taproot key that we'll compare this spend request to. + r.PkScript = ZeroTaprootPkScript + } + return r, nil } @@ -408,12 +439,12 @@ type SpendNtfn struct { // an entry for it. HeightHint uint32 - // dispatched signals whether a spend notification has been disptached + // dispatched signals whether a spend notification has been dispatched // to the client. dispatched bool } -// HistoricalSpendDispatch parameterizes a manual rescan to determine the +// HistoricalSpendDispatch parametrizes a manual rescan to determine the // spending details (if any) of an outpoint/output script. The parameters // include the start and end block heights specifying the range of blocks to // scan. @@ -523,7 +554,7 @@ type TxNotifier struct { // NewTxNotifier creates a TxNotifier. The current height of the blockchain is // accepted as a parameter. The different hint caches (confirm and spend) are // used as an optimization in order to retrieve a better starting point when -// dispatching a recan for a historical event in the chain. +// dispatching a rescan for a historical event in the chain. func NewTxNotifier(startHeight uint32, reorgSafetyLimit uint32, confirmHintCache ConfirmHintCache, spendHintCache SpendHintCache) *TxNotifier { @@ -614,8 +645,8 @@ func (n *TxNotifier) RegisterConf(txid *chainhash.Hash, pkScript []byte, if err == nil { if hint > startHeight { Log.Debugf("Using height hint %d retrieved from cache "+ - "for %v instead of %d", hint, ntfn.ConfRequest, - startHeight) + "for %v instead of %d for conf subscription", + hint, ntfn.ConfRequest, startHeight) startHeight = hint } } else if err != ErrConfirmHintNotFound { @@ -1009,8 +1040,8 @@ func (n *TxNotifier) RegisterSpend(outpoint *wire.OutPoint, pkScript []byte, if err == nil { if hint > startHeight { Log.Debugf("Using height hint %d retrieved from cache "+ - "for %v instead of %d", hint, ntfn.SpendRequest, - startHeight) + "for %v instead of %d for spend subscription", + hint, ntfn.SpendRequest, startHeight) startHeight = hint } } else if err != ErrSpendHintNotFound { @@ -1488,6 +1519,19 @@ func (n *TxNotifier) filterTx(tx *btcutil.Tx, blockHash *chainhash.Hash, if _, ok := n.spendNotifications[spendRequest]; ok { notifyDetails(spendRequest, prevOut, uint32(i)) } + + // Now try with an empty taproot key pkScript, since we + // cannot derive the spent pkScript directly from the + // witness. But we have the outpoint, which should be + // enough. + spendRequest.PkScript = ZeroTaprootPkScript + if _, ok := n.spendNotifications[spendRequest]; ok { + notifyDetails(spendRequest, prevOut, uint32(i)) + } + + // Restore the pkScript but try with a zero outpoint + // instead (won't be possible for Taproot). + spendRequest.PkScript = pkScript spendRequest.OutPoint = ZeroOutPoint if _, ok := n.spendNotifications[spendRequest]; ok { notifyDetails(spendRequest, prevOut, uint32(i)) diff --git a/chainntnfs/txnotifier_test.go b/chainntnfs/txnotifier_test.go index 28c6e73a30..801b2a1f9e 100644 --- a/chainntnfs/txnotifier_test.go +++ b/chainntnfs/txnotifier_test.go @@ -6,9 +6,9 @@ import ( "testing" "time" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/stretchr/testify/require" ) @@ -282,7 +282,7 @@ func TestTxNotifierFutureConfDispatch(t *testing.T) { t.Fatal("Expected confirmation update for tx1") } - // A confirmation notification for this tranaction should be dispatched, + // A confirmation notification for this transaction should be dispatched, // as it only required one confirmation. select { case txConf := <-ntfn1.Event.Confirmed: @@ -2148,7 +2148,7 @@ func TestTxNotifierSpendHintCache(t *testing.T) { op2Height = 203 ) - // Intiialize our TxNotifier instance backed by a height hint cache. + // Initialize our TxNotifier instance backed by a height hint cache. hintCache := newMockHintCache() n := chainntnfs.NewTxNotifier( startingHeight, chainntnfs.ReorgSafetyLimit, hintCache, @@ -2331,7 +2331,7 @@ func TestTxNotifierSpendDuringHistoricalRescan(t *testing.T) { reorgSafety = 10 ) - // Intiialize our TxNotifier instance backed by a height hint cache. + // Initialize our TxNotifier instance backed by a height hint cache. hintCache := newMockHintCache() n := chainntnfs.NewTxNotifier( startingHeight, reorgSafety, hintCache, hintCache, @@ -2511,7 +2511,7 @@ func TestTxNotifierSpendDuringHistoricalRescan(t *testing.T) { // matured. err = n.UpdateSpendDetails(ntfn1.HistoricalDispatch.SpendRequest, nil) if err == nil { - t.Fatalf("expcted updating spend details to fail") + t.Fatalf("expected updating spend details to fail") } // Finally, check that the height hint is still there, unchanged. diff --git a/chainreg/chainregistry.go b/chainreg/chainregistry.go index 0980b729e9..bd6f34b0b0 100644 --- a/chainreg/chainregistry.go +++ b/chainreg/chainregistry.go @@ -7,15 +7,16 @@ import ( "fmt" "io/ioutil" "net" + "net/url" "os" "strconv" "strings" "sync" "time" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/rpcclient" - "github.com/btcsuite/btcutil" "github.com/btcsuite/btcwallet/chain" "github.com/lightninglabs/neutrino" "github.com/lightningnetwork/lnd/blockcache" @@ -53,7 +54,8 @@ type Config struct { // queries if true. HeightHintCacheQueryDisable bool - // NeutrinoMode defines settings for connecting to a neutrino light-client. + // NeutrinoMode defines settings for connecting to a neutrino + // light-client. NeutrinoMode *lncfg.Neutrino // BitcoindMode defines settings for connecting to a bitcoind node. @@ -83,8 +85,8 @@ type Config struct { // the main wallet. WalletUnlockParams *walletunlocker.WalletUnlockParams - // NeutrinoCS is a pointer to a neutrino ChainService. Must be non-nil if - // using neutrino. + // NeutrinoCS is a pointer to a neutrino ChainService. Must be non-nil + // if using neutrino. NeutrinoCS *neutrino.ChainService // ActiveNetParams details the current chain we are on. @@ -152,8 +154,8 @@ const ( BtcToLtcConversionRate = 60 ) -// DefaultLtcChannelConstraints is the default set of channel constraints that are -// meant to be used when initially funding a Litecoin channel. +// DefaultLtcChannelConstraints is the default set of channel constraints that +// are meant to be used when initially funding a Litecoin channel. var DefaultLtcChannelConstraints = channeldb.ChannelConstraints{ DustLimit: DefaultLitecoinDustLimit, MaxAcceptedHtlcs: input.MaxHTLCNumber / 2, @@ -285,8 +287,8 @@ func NewPartialChainControl(cfg *Config) (*PartialChainControl, func(), error) { DefaultLitecoinStaticFeePerKW, 0, ) default: - return nil, nil, fmt.Errorf("default routing policy for chain %v is "+ - "unknown", cfg.PrimaryChain()) + return nil, nil, fmt.Errorf("default routing policy for chain "+ + "%v is unknown", cfg.PrimaryChain()) } var err error @@ -329,7 +331,8 @@ func NewPartialChainControl(cfg *Config) (*PartialChainControl, func(), error) { if cfg.NeutrinoMode.FeeURL != "" { if cfg.FeeURL != "" { return nil, nil, errors.New("feeurl and " + - "neutrino.feeurl are mutually exclusive") + "neutrino.feeurl are mutually " + + "exclusive") } cfg.FeeURL = cfg.NeutrinoMode.FeeURL @@ -396,26 +399,38 @@ func NewPartialChainControl(cfg *Config) (*PartialChainControl, func(), error) { } } - // Establish the connection to bitcoind and create the clients - // required for our relevant subsystems. - bitcoindConn, err := chain.NewBitcoindConn(&chain.BitcoindConfig{ + bitcoindCfg := &chain.BitcoindConfig{ ChainParams: cfg.ActiveNetParams.Params, Host: bitcoindHost, User: bitcoindMode.RPCUser, Pass: bitcoindMode.RPCPass, - ZMQBlockHost: bitcoindMode.ZMQPubRawBlock, - ZMQTxHost: bitcoindMode.ZMQPubRawTx, - ZMQReadDeadline: 5 * time.Second, Dialer: cfg.Dialer, PrunedModeMaxPeers: bitcoindMode.PrunedNodeMaxPeers, - }) + } + + if bitcoindMode.RPCPolling { + bitcoindCfg.PollingConfig = &chain.PollingConfig{ + BlockPollingInterval: bitcoindMode.BlockPollingInterval, + TxPollingInterval: bitcoindMode.TxPollingInterval, + } + } else { + bitcoindCfg.ZMQConfig = &chain.ZMQConfig{ + ZMQBlockHost: bitcoindMode.ZMQPubRawBlock, + ZMQTxHost: bitcoindMode.ZMQPubRawTx, + ZMQReadDeadline: bitcoindMode.ZMQReadDeadline, + } + } + + // Establish the connection to bitcoind and create the clients + // required for our relevant subsystems. + bitcoindConn, err := chain.NewBitcoindConn(bitcoindCfg) if err != nil { return nil, nil, err } if err := bitcoindConn.Start(); err != nil { - return nil, nil, fmt.Errorf("unable to connect to bitcoind: "+ - "%v", err) + return nil, nil, fmt.Errorf("unable to connect to "+ + "bitcoind: %v", err) } cc.ChainNotifier = bitcoindnotify.New( @@ -439,8 +454,8 @@ func NewPartialChainControl(cfg *Config) (*PartialChainControl, func(), error) { HTTPPostMode: true, } if cfg.Bitcoin.Active && !cfg.Bitcoin.RegTest { - log.Infof("Initializing bitcoind backed fee estimator in "+ - "%s mode", bitcoindMode.EstimateMode) + log.Infof("Initializing bitcoind backed fee estimator "+ + "in %s mode", bitcoindMode.EstimateMode) // Finally, we'll re-initialize the fee estimator, as // if we're using bitcoind as a backend, then we can @@ -455,8 +470,9 @@ func NewPartialChainControl(cfg *Config) (*PartialChainControl, func(), error) { return nil, nil, err } } else if cfg.Litecoin.Active && !cfg.Litecoin.RegTest { - log.Infof("Initializing litecoind backed fee estimator in "+ - "%s mode", bitcoindMode.EstimateMode) + log.Infof("Initializing litecoind backed fee "+ + "estimator in %s mode", + bitcoindMode.EstimateMode) // Finally, we'll re-initialize the fee estimator, as // if we're using litecoind as a backend, then we can @@ -482,11 +498,92 @@ func NewPartialChainControl(cfg *Config) (*PartialChainControl, func(), error) { // The api we will use for our health check depends on the // bitcoind version. - cmd, err := getBitcoindHealthCheckCmd(chainConn) + cmd, ver, err := getBitcoindHealthCheckCmd(chainConn) if err != nil { return nil, nil, err } + // If the getzmqnotifications api is available (was added in + // version 0.17.0) we make sure lnd subscribes to the correct + // zmq events. We do this to avoid a situation in which we are + // not notified of new transactions or blocks. + if ver >= 170000 && !bitcoindMode.RPCPolling { + zmqPubRawBlockURL, err := url.Parse(bitcoindMode.ZMQPubRawBlock) + if err != nil { + return nil, nil, err + } + zmqPubRawTxURL, err := url.Parse(bitcoindMode.ZMQPubRawTx) + if err != nil { + return nil, nil, err + } + + // Fetch all active zmq notifications from the bitcoind client. + resp, err := chainConn.RawRequest("getzmqnotifications", nil) + if err != nil { + return nil, nil, err + } + + zmq := []struct { + Type string `json:"type"` + Address string `json:"address"` + }{} + + if err = json.Unmarshal([]byte(resp), &zmq); err != nil { + return nil, nil, err + } + + pubRawBlockActive := false + pubRawTxActive := false + + for i := range zmq { + if zmq[i].Type == "pubrawblock" { + url, err := url.Parse(zmq[i].Address) + if err != nil { + return nil, nil, err + } + if url.Port() != zmqPubRawBlockURL.Port() { + log.Warnf( + "unable to subscribe to zmq block events on "+ + "%s (bitcoind is running on %s)", + zmqPubRawBlockURL.Host, + url.Host, + ) + } + pubRawBlockActive = true + } + if zmq[i].Type == "pubrawtx" { + url, err := url.Parse(zmq[i].Address) + if err != nil { + return nil, nil, err + } + if url.Port() != zmqPubRawTxURL.Port() { + log.Warnf( + "unable to subscribe to zmq tx events on "+ + "%s (bitcoind is running on %s)", + zmqPubRawTxURL.Host, + url.Host, + ) + } + pubRawTxActive = true + } + } + + // Return an error if raw tx or raw block notification over + // zmq is inactive. + if !pubRawBlockActive { + return nil, nil, errors.New( + "block notification over zmq is inactive on " + + "bitcoind", + ) + } + if !pubRawTxActive { + return nil, nil, errors.New( + "tx notification over zmq is inactive on " + + "bitcoind", + ) + } + } + cc.HealthCheck = func() error { _, err := chainConn.RawRequest(cmd, nil) return err @@ -558,8 +655,8 @@ func NewPartialChainControl(cfg *Config) (*PartialChainControl, func(), error) { return nil, nil, err } - // Finally, we'll create an instance of the default chain view to be - // used within the routing layer. + // Finally, we'll create an instance of the default chain view + // to be used within the routing layer. cc.ChainView, err = chainview.NewBtcdFilteredChainView( *rpcConfig, cfg.BlockCache, ) @@ -568,10 +665,12 @@ func NewPartialChainControl(cfg *Config) (*PartialChainControl, func(), error) { return nil, nil, err } - // Create a special websockets rpc client for btcd which will be used - // by the wallet for notifications, calls, etc. - chainRPC, err := chain.NewRPCClient(cfg.ActiveNetParams.Params, btcdHost, - btcdUser, btcdPass, rpcCert, false, 20) + // Create a special websockets rpc client for btcd which will be + // used by the wallet for notifications, calls, etc. + chainRPC, err := chain.NewRPCClient( + cfg.ActiveNetParams.Params, btcdHost, btcdUser, + btcdPass, rpcCert, false, 20, + ) if err != nil { return nil, nil, err } @@ -603,6 +702,22 @@ func NewPartialChainControl(cfg *Config) (*PartialChainControl, func(), error) { return nil, nil, err } } + + case "nochainbackend": + backend := &NoChainBackend{} + source := &NoChainSource{ + BestBlockTime: time.Now(), + } + + cc.ChainNotifier = backend + cc.ChainView = backend + cc.FeeEstimator = backend + + cc.ChainSource = source + cc.HealthCheck = func() error { + return nil + } + default: return nil, nil, fmt.Errorf("unknown node type: %s", homeChainConfig.Node) @@ -615,8 +730,8 @@ func NewPartialChainControl(cfg *Config) (*PartialChainControl, func(), error) { case cfg.FeeURL == "" && cfg.Bitcoin.MainNet && homeChainConfig.Node == "neutrino": - return nil, nil, fmt.Errorf("--feeurl parameter required when " + - "running neutrino on mainnet") + return nil, nil, fmt.Errorf("--feeurl parameter required " + + "when running neutrino on mainnet") // Override default fee estimator if an external service is specified. case cfg.FeeURL != "": @@ -638,7 +753,8 @@ func NewPartialChainControl(cfg *Config) (*PartialChainControl, func(), error) { ccCleanup := func() { if cc.FeeEstimator != nil { if err := cc.FeeEstimator.Stop(); err != nil { - log.Errorf("Failed to stop feeEstimator: %v", err) + log.Errorf("Failed to stop feeEstimator: %v", + err) } } } @@ -686,12 +802,12 @@ func NewChainControl(walletConfig lnwallet.Config, lnWallet, err := lnwallet.NewLightningWallet(walletConfig) if err != nil { - fmt.Printf("unable to create wallet: %v\n", err) - return nil, ccCleanup, err + return nil, ccCleanup, fmt.Errorf("unable to create wallet: %v", + err) } if err := lnWallet.Startup(); err != nil { - fmt.Printf("unable to start wallet: %v\n", err) - return nil, ccCleanup, err + return nil, ccCleanup, fmt.Errorf("unable to create wallet: %v", + err) } log.Info("LightningWallet opened") @@ -705,11 +821,11 @@ func NewChainControl(walletConfig lnwallet.Config, // command, because it has no locking and is an inexpensive call, which was // added in version 0.15. If we are on an earlier version, we fallback to using // getblockchaininfo. -func getBitcoindHealthCheckCmd(client *rpcclient.Client) (string, error) { +func getBitcoindHealthCheckCmd(client *rpcclient.Client) (string, int64, error) { // Query bitcoind to get our current version. resp, err := client.RawRequest("getnetworkinfo", nil) if err != nil { - return "", err + return "", 0, err } // Parse the response to retrieve bitcoind's version. @@ -717,7 +833,7 @@ func getBitcoindHealthCheckCmd(client *rpcclient.Client) (string, error) { Version int64 `json:"version"` }{} if err := json.Unmarshal(resp, &info); err != nil { - return "", err + return "", 0, err } // Bitcoind returns a single value representing the semantic version: @@ -727,10 +843,10 @@ func getBitcoindHealthCheckCmd(client *rpcclient.Client) (string, error) { // The uptime call was added in version 0.15.0, so we return it for // any version value >= 150000, as per the above calculation. if info.Version >= 150000 { - return "uptime", nil + return "uptime", info.Version, nil } - return "getblockchaininfo", nil + return "getblockchaininfo", info.Version, nil } var ( @@ -797,7 +913,7 @@ var ( // the network seed. // // TODO(roasbeef): extend and collapse these and chainparams.go into - // struct like chaincfg.Params + // struct like chaincfg.Params. ChainDNSSeeds = map[chainhash.Hash][][2]string{ BitcoinMainnetGenesis: { { @@ -831,7 +947,7 @@ var ( } ) -// ChainRegistry keeps track of the current chains +// ChainRegistry keeps track of the current chains. type ChainRegistry struct { sync.RWMutex @@ -872,7 +988,9 @@ func (c *ChainRegistry) LookupChain(targetChain ChainCode) ( // LookupChainByHash attempts to look up an active ChainControl which // corresponds to the passed genesis hash. -func (c *ChainRegistry) LookupChainByHash(chainHash chainhash.Hash) (*ChainControl, bool) { +func (c *ChainRegistry) LookupChainByHash( + chainHash chainhash.Hash) (*ChainControl, bool) { + c.RLock() defer c.RUnlock() diff --git a/chainreg/no_chain_backend.go b/chainreg/no_chain_backend.go new file mode 100644 index 0000000000..244fe025e3 --- /dev/null +++ b/chainreg/no_chain_backend.go @@ -0,0 +1,215 @@ +package chainreg + +import ( + "errors" + "time" + + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcwallet/chain" + "github.com/btcsuite/btcwallet/waddrmgr" + "github.com/lightningnetwork/lnd/chainntnfs" + "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/lnwallet/chainfee" + "github.com/lightningnetwork/lnd/routing/chainview" +) + +var ( + // defaultFee is the fee that is returned by NoChainBackend. + defaultFee = chainfee.FeePerKwFloor + + // noChainBackendName is the backend name returned by NoChainBackend. + noChainBackendName = "nochainbackend" + + // errNotImplemented is the error that is returned by NoChainBackend for + // any operation that is not supported by it. Such paths should in + // practice never been hit, so seeing this error either means a remote + // signing instance was used for an unsupported purpose or a previously + // forgotten edge case path was hit. + errNotImplemented = errors.New("not implemented in nochainbackend " + + "mode") + + // noChainBackendBestHash is the chain hash of the chain tip that is + // returned by NoChainBackend. + noChainBackendBestHash = &chainhash.Hash{0x01} + + // noChainBackendBestHeight is the best height that is returned by + // NoChainBackend. + noChainBackendBestHeight int32 = 1 +) + +// NoChainBackend is a mock implementation of the following interfaces: +// - chainview.FilteredChainView +// - chainntnfs.ChainNotifier +// - chainfee.Estimator +type NoChainBackend struct { +} + +func (n *NoChainBackend) EstimateFeePerKW(uint32) (chainfee.SatPerKWeight, + error) { + + return defaultFee, nil +} + +func (n *NoChainBackend) RelayFeePerKW() chainfee.SatPerKWeight { + return defaultFee +} + +func (n *NoChainBackend) RegisterConfirmationsNtfn(*chainhash.Hash, []byte, + uint32, uint32) (*chainntnfs.ConfirmationEvent, error) { + + return nil, errNotImplemented +} + +func (n *NoChainBackend) RegisterSpendNtfn(*wire.OutPoint, []byte, + uint32) (*chainntnfs.SpendEvent, error) { + + return nil, errNotImplemented +} + +func (n *NoChainBackend) RegisterBlockEpochNtfn( + *chainntnfs.BlockEpoch) (*chainntnfs.BlockEpochEvent, error) { + + epochChan := make(chan *chainntnfs.BlockEpoch) + return &chainntnfs.BlockEpochEvent{ + Epochs: epochChan, + Cancel: func() { + close(epochChan) + }, + }, nil +} + +func (n *NoChainBackend) Started() bool { + return true +} + +func (n *NoChainBackend) FilteredBlocks() <-chan *chainview.FilteredBlock { + return make(chan *chainview.FilteredBlock) +} + +func (n *NoChainBackend) DisconnectedBlocks() <-chan *chainview.FilteredBlock { + return make(chan *chainview.FilteredBlock) +} + +func (n *NoChainBackend) UpdateFilter([]channeldb.EdgePoint, uint32) error { + return nil +} + +func (n *NoChainBackend) FilterBlock(*chainhash.Hash) (*chainview.FilteredBlock, + error) { + + return nil, errNotImplemented +} + +func (n *NoChainBackend) Start() error { + return nil +} + +func (n *NoChainBackend) Stop() error { + return nil +} + +var _ chainview.FilteredChainView = (*NoChainBackend)(nil) +var _ chainntnfs.ChainNotifier = (*NoChainBackend)(nil) +var _ chainfee.Estimator = (*NoChainBackend)(nil) + +// NoChainSource is a mock implementation of chain.Interface. +// The mock is designed to return static values where necessary to make any +// caller believe the chain is fully synced to virtual block height 1 (hash +// 0x0000..0001). That should avoid calls to other methods completely since they +// are only used for advancing the chain forward. +type NoChainSource struct { + notifChan chan interface{} + + BestBlockTime time.Time +} + +func (n *NoChainSource) Start() error { + n.notifChan = make(chan interface{}) + + go func() { + n.notifChan <- &chain.RescanFinished{ + Hash: noChainBackendBestHash, + Height: noChainBackendBestHeight, + Time: n.BestBlockTime, + } + }() + + return nil +} + +func (n *NoChainSource) Stop() { +} + +func (n *NoChainSource) WaitForShutdown() { +} + +func (n *NoChainSource) GetBestBlock() (*chainhash.Hash, int32, error) { + return noChainBackendBestHash, noChainBackendBestHeight, nil +} + +func (n *NoChainSource) GetBlock(*chainhash.Hash) (*wire.MsgBlock, error) { + return &wire.MsgBlock{ + Header: wire.BlockHeader{ + Timestamp: n.BestBlockTime, + }, + Transactions: []*wire.MsgTx{}, + }, nil +} + +func (n *NoChainSource) GetBlockHash(int64) (*chainhash.Hash, error) { + return noChainBackendBestHash, nil +} + +func (n *NoChainSource) GetBlockHeader(*chainhash.Hash) (*wire.BlockHeader, + error) { + + return &wire.BlockHeader{ + Timestamp: n.BestBlockTime, + }, nil +} + +func (n *NoChainSource) IsCurrent() bool { + return true +} + +func (n *NoChainSource) FilterBlocks( + *chain.FilterBlocksRequest) (*chain.FilterBlocksResponse, error) { + + return nil, errNotImplemented +} + +func (n *NoChainSource) BlockStamp() (*waddrmgr.BlockStamp, error) { + return nil, errNotImplemented +} + +func (n *NoChainSource) SendRawTransaction(*wire.MsgTx, bool) (*chainhash.Hash, + error) { + + return nil, errNotImplemented +} + +func (n *NoChainSource) Rescan(*chainhash.Hash, []btcutil.Address, + map[wire.OutPoint]btcutil.Address) error { + + return nil +} + +func (n *NoChainSource) NotifyReceived([]btcutil.Address) error { + return nil +} + +func (n *NoChainSource) NotifyBlocks() error { + return nil +} + +func (n *NoChainSource) Notifications() <-chan interface{} { + return n.notifChan +} + +func (n *NoChainSource) BackEnd() string { + return noChainBackendName +} + +var _ chain.Interface = (*NoChainSource)(nil) diff --git a/chanacceptor/acceptor_test.go b/chanacceptor/acceptor_test.go index 899d922f2b..88c8007da3 100644 --- a/chanacceptor/acceptor_test.go +++ b/chanacceptor/acceptor_test.go @@ -2,13 +2,12 @@ package chanacceptor import ( "errors" - "math/big" "testing" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnwallet/chancloser" "github.com/lightningnetwork/lnd/lnwire" @@ -126,10 +125,10 @@ func (c *channelAcceptorCtx) stop() { // request in a goroutine and then asserts that we get the outcome we expect. func (c *channelAcceptorCtx) queryAndAssert(queries map[*lnwire.OpenChannel]*ChannelAcceptResponse) { var ( - node = &btcec.PublicKey{ - X: big.NewInt(1), - Y: big.NewInt(1), - } + node = btcec.NewPublicKey( + new(btcec.FieldVal).SetInt(1), + new(btcec.FieldVal).SetInt(1), + ) responses = make(chan struct{}) ) diff --git a/chanacceptor/interface.go b/chanacceptor/interface.go index 1f0b8cdc3c..9aee8f9860 100644 --- a/chanacceptor/interface.go +++ b/chanacceptor/interface.go @@ -3,8 +3,8 @@ package chanacceptor import ( "errors" - "github.com/btcsuite/btcd/btcec" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/lnwire" ) diff --git a/chanacceptor/merge.go b/chanacceptor/merge.go index 852329f5cd..058efbbda0 100644 --- a/chanacceptor/merge.go +++ b/chanacceptor/merge.go @@ -4,7 +4,7 @@ import ( "bytes" "fmt" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/lnwire" ) @@ -22,77 +22,77 @@ const ( // fieldMismatchError returns a merge error for a named field when we get two // channel acceptor responses which have different values set. -func fieldMismatchError(name string, current, new interface{}) error { +func fieldMismatchError(name string, current, newValue interface{}) error { return fmt.Errorf("multiple values set for: %v, %v and %v", - name, current, new) + name, current, newValue) } // mergeInt64 merges two int64 values, failing if they have different non-zero // values. -func mergeInt64(name string, current, new int64) (int64, error) { +func mergeInt64(name string, current, newValue int64) (int64, error) { switch { case current == 0: - return new, nil + return newValue, nil - case new == 0: + case newValue == 0: return current, nil - case current != new: - return 0, fieldMismatchError(name, current, new) + case current != newValue: + return 0, fieldMismatchError(name, current, newValue) default: - return new, nil + return newValue, nil } } // mergeMillisatoshi merges two msat values, failing if they have different // non-zero values. func mergeMillisatoshi(name string, current, - new lnwire.MilliSatoshi) (lnwire.MilliSatoshi, error) { + newValue lnwire.MilliSatoshi) (lnwire.MilliSatoshi, error) { switch { case current == 0: - return new, nil + return newValue, nil - case new == 0: + case newValue == 0: return current, nil - case current != new: - return 0, fieldMismatchError(name, current, new) + case current != newValue: + return 0, fieldMismatchError(name, current, newValue) default: - return new, nil + return newValue, nil } } // mergeDeliveryAddress merges two delivery address values, failing if they have // different non-zero values. func mergeDeliveryAddress(name string, current, - new lnwire.DeliveryAddress) (lnwire.DeliveryAddress, error) { + newValue lnwire.DeliveryAddress) (lnwire.DeliveryAddress, error) { switch { case current == nil: - return new, nil + return newValue, nil - case new == nil: + case newValue == nil: return current, nil - case !bytes.Equal(current, new): - return nil, fieldMismatchError(name, current, new) + case !bytes.Equal(current, newValue): + return nil, fieldMismatchError(name, current, newValue) default: - return new, nil + return newValue, nil } } // mergeResponse takes two channel accept responses, and attempts to merge their // fields, failing if any fields conflict (are non-zero and not equal). It // returns a new response that has all the merged fields in it. -func mergeResponse(current, new ChannelAcceptResponse) (ChannelAcceptResponse, - error) { +func mergeResponse(current, + newValue ChannelAcceptResponse) (ChannelAcceptResponse, error) { csv, err := mergeInt64( - fieldCSV, int64(current.CSVDelay), int64(new.CSVDelay), + fieldCSV, int64(current.CSVDelay), int64(newValue.CSVDelay), ) if err != nil { return current, err @@ -101,7 +101,7 @@ func mergeResponse(current, new ChannelAcceptResponse) (ChannelAcceptResponse, htlcLimit, err := mergeInt64( fieldHtlcLimit, int64(current.HtlcLimit), - int64(new.HtlcLimit), + int64(newValue.HtlcLimit), ) if err != nil { return current, err @@ -110,7 +110,7 @@ func mergeResponse(current, new ChannelAcceptResponse) (ChannelAcceptResponse, minDepth, err := mergeInt64( fieldMinDep, int64(current.MinAcceptDepth), - int64(new.MinAcceptDepth), + int64(newValue.MinAcceptDepth), ) if err != nil { return current, err @@ -118,7 +118,7 @@ func mergeResponse(current, new ChannelAcceptResponse) (ChannelAcceptResponse, current.MinAcceptDepth = uint16(minDepth) reserve, err := mergeInt64( - fieldReserve, int64(current.Reserve), int64(new.Reserve), + fieldReserve, int64(current.Reserve), int64(newValue.Reserve), ) if err != nil { return current, err @@ -126,7 +126,7 @@ func mergeResponse(current, new ChannelAcceptResponse) (ChannelAcceptResponse, current.Reserve = btcutil.Amount(reserve) current.MinHtlcIn, err = mergeMillisatoshi( - fieldMinIn, current.MinHtlcIn, new.MinHtlcIn, + fieldMinIn, current.MinHtlcIn, newValue.MinHtlcIn, ) if err != nil { return current, err @@ -134,7 +134,7 @@ func mergeResponse(current, new ChannelAcceptResponse) (ChannelAcceptResponse, current.InFlightTotal, err = mergeMillisatoshi( fieldInFlightTotal, current.InFlightTotal, - new.InFlightTotal, + newValue.InFlightTotal, ) if err != nil { return current, err @@ -142,7 +142,7 @@ func mergeResponse(current, new ChannelAcceptResponse) (ChannelAcceptResponse, current.UpfrontShutdown, err = mergeDeliveryAddress( fieldUpfrontShutdown, current.UpfrontShutdown, - new.UpfrontShutdown, + newValue.UpfrontShutdown, ) if err != nil { return current, err diff --git a/chanacceptor/rpcacceptor.go b/chanacceptor/rpcacceptor.go index bec9143559..3504c2b7af 100644 --- a/chanacceptor/rpcacceptor.go +++ b/chanacceptor/rpcacceptor.go @@ -7,8 +7,8 @@ import ( "sync" "time" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnwallet/chancloser" @@ -243,7 +243,7 @@ func (r *RPCAcceptor) sendAcceptRequests(errChan chan error, defer close(r.done) // Create a map of pending channel IDs to our original open channel - // request and a response channel. We keep the original chanel open + // request and a response channel. We keep the original channel open // message so that we can validate our response against it. acceptRequests := make(map[[32]byte]*chanAcceptInfo) diff --git a/chanacceptor/rpcacceptor_test.go b/chanacceptor/rpcacceptor_test.go index 9588775a09..b755924ebd 100644 --- a/chanacceptor/rpcacceptor_test.go +++ b/chanacceptor/rpcacceptor_test.go @@ -5,8 +5,8 @@ import ( "strings" "testing" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnwallet/chancloser" diff --git a/chanbackup/backup.go b/chanbackup/backup.go index dce1210b26..9a61d23cd3 100644 --- a/chanbackup/backup.go +++ b/chanbackup/backup.go @@ -4,7 +4,7 @@ import ( "fmt" "net" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/kvdb" diff --git a/chanbackup/backup_test.go b/chanbackup/backup_test.go index ff321c1884..0c0e79c060 100644 --- a/chanbackup/backup_test.go +++ b/chanbackup/backup_test.go @@ -5,7 +5,7 @@ import ( "net" "testing" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/kvdb" diff --git a/chanbackup/backupfile_test.go b/chanbackup/backupfile_test.go index 19733c3601..1bedad0e2c 100644 --- a/chanbackup/backupfile_test.go +++ b/chanbackup/backupfile_test.go @@ -8,6 +8,8 @@ import ( "os" "path/filepath" "testing" + + "github.com/lightningnetwork/lnd/lntest/mock" ) func makeFakePackedMulti() (PackedMulti, error) { @@ -188,7 +190,9 @@ func assertMultiEqual(t *testing.T, a, b *Multi) { func TestExtractMulti(t *testing.T) { t.Parallel() - keyRing := &mockKeyRing{} + keyRing := &mock.SecretKeyRing{ + RootKey: privKey, + } // First, as prep, we'll create a single chan backup, then pack that // fully into a multi backup. diff --git a/chanbackup/multi.go b/chanbackup/multi.go index e90bd613e4..e3ea71f131 100644 --- a/chanbackup/multi.go +++ b/chanbackup/multi.go @@ -6,6 +6,7 @@ import ( "io" "github.com/lightningnetwork/lnd/keychain" + "github.com/lightningnetwork/lnd/lnencrypt" "github.com/lightningnetwork/lnd/lnwire" ) @@ -89,7 +90,7 @@ func (m Multi) PackToWriter(w io.Writer, keyRing keychain.KeyRing) error { // With the plaintext multi backup assembled, we'll now encrypt it // directly to the passed writer. - return encryptPayloadToWriter(multiBackupBuffer, w, keyRing) + return lnencrypt.EncryptPayloadToWriter(multiBackupBuffer, w, keyRing) } // UnpackFromReader attempts to unpack (decrypt+deserialize) a packed @@ -99,7 +100,7 @@ func (m *Multi) UnpackFromReader(r io.Reader, keyRing keychain.KeyRing) error { // We'll attempt to read the entire packed backup, and also decrypt it // using the passed key ring which is expected to be able to derive the // encryption keys. - plaintextBackup, err := decryptPayloadFromReader(r, keyRing) + plaintextBackup, err := lnencrypt.DecryptPayloadFromReader(r, keyRing) if err != nil { return err } diff --git a/chanbackup/multi_test.go b/chanbackup/multi_test.go index a6317e09ef..ebf802acd8 100644 --- a/chanbackup/multi_test.go +++ b/chanbackup/multi_test.go @@ -4,6 +4,9 @@ import ( "bytes" "net" "testing" + + "github.com/lightningnetwork/lnd/lnencrypt" + "github.com/lightningnetwork/lnd/lntest/mock" ) // TestMultiPackUnpack... @@ -25,7 +28,9 @@ func TestMultiPackUnpack(t *testing.T) { multi.StaticBackups = append(multi.StaticBackups, single) } - keyRing := &mockKeyRing{} + keyRing := &mock.SecretKeyRing{ + RootKey: privKey, + } versionTestCases := []struct { // version is the pack/unpack version that we should use to @@ -97,7 +102,7 @@ func TestMultiPackUnpack(t *testing.T) { fakeRawMulti := bytes.NewBuffer( bytes.Repeat([]byte{99}, 20), ) - err := encryptPayloadToWriter( + err := lnencrypt.EncryptPayloadToWriter( *fakeRawMulti, &fakePackedMulti, keyRing, ) if err != nil { @@ -122,7 +127,9 @@ func TestMultiPackUnpack(t *testing.T) { func TestPackedMultiUnpack(t *testing.T) { t.Parallel() - keyRing := &mockKeyRing{} + keyRing := &mock.SecretKeyRing{ + RootKey: privKey, + } // First, we'll make a new unpacked multi with a random channel. testChannel, err := genRandomOpenChannelShell() diff --git a/chanbackup/pubsub.go b/chanbackup/pubsub.go index 4eef45e1c9..3e1580ddd2 100644 --- a/chanbackup/pubsub.go +++ b/chanbackup/pubsub.go @@ -137,7 +137,7 @@ func NewSubSwapper(startingChans []Single, chanNotifier ChannelNotifier, func (s *SubSwapper) Start() error { var startErr error s.started.Do(func() { - log.Infof("Starting chanbackup.SubSwapper") + log.Infof("chanbackup.SubSwapper starting") // Before we enter our main loop, we'll update the on-disk // state with the latest Single state, as nodes may have new diff --git a/chanbackup/pubsub_test.go b/chanbackup/pubsub_test.go index aca52df2dd..6d5bec3308 100644 --- a/chanbackup/pubsub_test.go +++ b/chanbackup/pubsub_test.go @@ -7,6 +7,7 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/keychain" + "github.com/lightningnetwork/lnd/lntest/mock" ) type mockSwapper struct { @@ -79,7 +80,9 @@ func (m *mockChannelNotifier) SubscribeChans(chans map[wire.OutPoint]struct{}) ( func TestNewSubSwapperSubscribeFail(t *testing.T) { t.Parallel() - keyRing := &mockKeyRing{} + keyRing := &mock.SecretKeyRing{ + RootKey: privKey, + } var swapper mockSwapper chanNotifier := mockChannelNotifier{ @@ -151,7 +154,9 @@ func assertExpectedBackupSwap(t *testing.T, swapper *mockSwapper, func TestSubSwapperIdempotentStartStop(t *testing.T) { t.Parallel() - keyRing := &mockKeyRing{} + keyRing := &mock.SecretKeyRing{ + RootKey: privKey, + } var chanNotifier mockChannelNotifier @@ -182,7 +187,9 @@ func TestSubSwapperIdempotentStartStop(t *testing.T) { func TestSubSwapperUpdater(t *testing.T) { t.Parallel() - keyRing := &mockKeyRing{} + keyRing := &mock.SecretKeyRing{ + RootKey: privKey, + } chanNotifier := newMockChannelNotifier() swapper := newMockSwapper(keyRing) diff --git a/chanbackup/recover.go b/chanbackup/recover.go index 2340e97775..ebd48dccfd 100644 --- a/chanbackup/recover.go +++ b/chanbackup/recover.go @@ -3,7 +3,7 @@ package chanbackup import ( "net" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/keychain" diff --git a/chanbackup/recover_test.go b/chanbackup/recover_test.go index e2b9d71e35..71ea42638d 100644 --- a/chanbackup/recover_test.go +++ b/chanbackup/recover_test.go @@ -6,7 +6,8 @@ import ( "net" "testing" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/lightningnetwork/lnd/lntest/mock" ) type mockChannelRestorer struct { @@ -48,7 +49,9 @@ func (m *mockPeerConnector) ConnectPeer(node *btcec.PublicKey, func TestUnpackAndRecoverSingles(t *testing.T) { t.Parallel() - keyRing := &mockKeyRing{} + keyRing := &mock.SecretKeyRing{ + RootKey: privKey, + } // First, we'll create a number of single chan backups that we'll // shortly back to so we can begin our recovery attempt. @@ -124,7 +127,7 @@ func TestUnpackAndRecoverSingles(t *testing.T) { } // If we modify the keyRing, then unpacking should fail. - keyRing.fail = true + keyRing.Fail = true err = UnpackAndRecoverSingles( packedBackups, keyRing, &chanRestorer, &peerConnector, ) @@ -140,7 +143,9 @@ func TestUnpackAndRecoverSingles(t *testing.T) { func TestUnpackAndRecoverMulti(t *testing.T) { t.Parallel() - keyRing := &mockKeyRing{} + keyRing := &mock.SecretKeyRing{ + RootKey: privKey, + } // First, we'll create a number of single chan backups that we'll // shortly back to so we can begin our recovery attempt. @@ -220,7 +225,7 @@ func TestUnpackAndRecoverMulti(t *testing.T) { } // If we modify the keyRing, then unpacking should fail. - keyRing.fail = true + keyRing.Fail = true err = UnpackAndRecoverMulti( packedMulti, keyRing, &chanRestorer, &peerConnector, ) diff --git a/chanbackup/single.go b/chanbackup/single.go index 6960f90167..228e668a4a 100644 --- a/chanbackup/single.go +++ b/chanbackup/single.go @@ -6,12 +6,13 @@ import ( "io" "net" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/keychain" + "github.com/lightningnetwork/lnd/lnencrypt" "github.com/lightningnetwork/lnd/lnwire" ) @@ -160,7 +161,7 @@ func NewSingle(channel *channeldb.OpenChannel, // the backups plaintext don't carry any private information. When // we go to recover, we'll present this in order to derive the // private key. - _, shaChainPoint := btcec.PrivKeyFromBytes(btcec.S256(), b.Bytes()) + _, shaChainPoint := btcec.PrivKeyFromBytes(b.Bytes()) shaChainRootDesc = keychain.KeyDescriptor{ PubKey: shaChainPoint, @@ -315,10 +316,10 @@ func (s *Single) Serialize(w io.Writer) error { // global counter to use as a sequence number for nonces, and want to ensure // that we're able to decrypt these blobs without any additional context. We // derive the key that we use for encryption via a SHA2 operation of the with -// the golden keychain.KeyFamilyStaticBackup base encryption key. We then take -// the serialized resulting shared secret point, and hash it using sha256 to -// obtain the key that we'll use for encryption. When using the AEAD, we pass -// the nonce as associated data such that we'll be able to package the two +// the golden keychain.KeyFamilyBaseEncryption base encryption key. We then +// take the serialized resulting shared secret point, and hash it using sha256 +// to obtain the key that we'll use for encryption. When using the AEAD, we +// pass the nonce as associated data such that we'll be able to package the two // together for storage. Before writing out the encrypted payload, we prepend // the nonce to the final blob. func (s *Single) PackToWriter(w io.Writer, keyRing keychain.KeyRing) error { @@ -333,7 +334,7 @@ func (s *Single) PackToWriter(w io.Writer, keyRing keychain.KeyRing) error { // Finally, we'll encrypt the raw serialized SCB (using the nonce as // associated data), and write out the ciphertext prepend with the // nonce that we used to the passed io.Reader. - return encryptPayloadToWriter(rawBytes, w, keyRing) + return lnencrypt.EncryptPayloadToWriter(rawBytes, w, keyRing) } // readLocalKeyDesc reads a KeyDescriptor encoded within an unpacked Single. @@ -369,13 +370,11 @@ func readRemoteKeyDesc(r io.Reader) (keychain.KeyDescriptor, error) { return keychain.KeyDescriptor{}, err } - keyDesc.PubKey, err = btcec.ParsePubKey(pub[:], btcec.S256()) + keyDesc.PubKey, err = btcec.ParsePubKey(pub[:]) if err != nil { return keychain.KeyDescriptor{}, err } - keyDesc.PubKey.Curve = nil - return keyDesc, nil } @@ -480,7 +479,7 @@ func (s *Single) Deserialize(r io.Reader) error { // been specified or not. if !bytes.Equal(shaChainPub[:], zeroPub[:]) { s.ShaChainRootDesc.PubKey, err = btcec.ParsePubKey( - shaChainPub[:], btcec.S256(), + shaChainPub[:], ) if err != nil { return err @@ -512,7 +511,7 @@ func (s *Single) Deserialize(r io.Reader) error { // payload for whatever reason (wrong key, wrong nonce, etc), then this method // will return an error. func (s *Single) UnpackFromReader(r io.Reader, keyRing keychain.KeyRing) error { - plaintext, err := decryptPayloadFromReader(r, keyRing) + plaintext, err := lnencrypt.DecryptPayloadFromReader(r, keyRing) if err != nil { return err } diff --git a/chanbackup/single_test.go b/chanbackup/single_test.go index 97141ec9f3..cefe65707b 100644 --- a/chanbackup/single_test.go +++ b/chanbackup/single_test.go @@ -8,12 +8,14 @@ import ( "reflect" "testing" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/keychain" + "github.com/lightningnetwork/lnd/lntest/channels" + "github.com/lightningnetwork/lnd/lntest/mock" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/shachain" ) @@ -33,6 +35,10 @@ var ( addr1, _ = net.ResolveTCPAddr("tcp", "10.0.0.2:9000") addr2, _ = net.ResolveTCPAddr("tcp", "10.0.0.3:9000") + + privKeyBytes = channels.AlicesPrivKey + + privKey, _ = btcec.PrivKeyFromBytes(privKeyBytes) ) func assertSingleEqual(t *testing.T, a, b Single) { @@ -101,15 +107,13 @@ func genRandomOpenChannelShell() (*channeldb.OpenChannel, error) { return nil, err } - _, pub := btcec.PrivKeyFromBytes(btcec.S256(), testPriv[:]) + _, pub := btcec.PrivKeyFromBytes(testPriv[:]) var chanPoint wire.OutPoint if _, err := rand.Read(chanPoint.Hash[:]); err != nil { return nil, err } - pub.Curve = nil - chanPoint.Index = uint32(rand.Intn(math.MaxUint16)) var shaChainRoot [32]byte @@ -209,9 +213,10 @@ func TestSinglePackUnpack(t *testing.T) { } singleChanBackup := NewSingle(channel, []net.Addr{addr1, addr2}) - singleChanBackup.RemoteNodePub.Curve = nil - keyRing := &mockKeyRing{} + keyRing := &mock.SecretKeyRing{ + RootKey: privKey, + } versionTestCases := []struct { // version is the pack/unpack version that we should use to @@ -285,7 +290,6 @@ func TestSinglePackUnpack(t *testing.T) { t.Fatalf("#%v unable to unpack single: %v", i, err) } - unpackedSingle.RemoteNodePub.Curve = nil assertSingleEqual(t, singleChanBackup, unpackedSingle) @@ -317,7 +321,9 @@ func TestSinglePackUnpack(t *testing.T) { func TestPackedSinglesUnpack(t *testing.T) { t.Parallel() - keyRing := &mockKeyRing{} + keyRing := &mock.SecretKeyRing{ + RootKey: privKey, + } // To start, we'll create 10 new singles, and them assemble their // packed forms into a slice. @@ -368,7 +374,9 @@ func TestPackedSinglesUnpack(t *testing.T) { func TestSinglePackStaticChanBackups(t *testing.T) { t.Parallel() - keyRing := &mockKeyRing{} + keyRing := &mock.SecretKeyRing{ + RootKey: privKey, + } // First, we'll create a set of random single, and along the way, // create a map that will let us look up each single by its chan point. @@ -416,8 +424,9 @@ func TestSinglePackStaticChanBackups(t *testing.T) { // If we attempt to pack again, but force the key ring to fail, then // the entire method should fail. + keyRing.Fail = true _, err = PackStaticChanBackups( - unpackedSingles, &mockKeyRing{true}, + unpackedSingles, keyRing, ) if err == nil { t.Fatalf("pack attempt should fail") @@ -443,8 +452,9 @@ func TestSingleUnconfirmedChannel(t *testing.T) { channel.FundingBroadcastHeight = fundingBroadcastHeight singleChanBackup := NewSingle(channel, []net.Addr{addr1, addr2}) - keyRing := &mockKeyRing{} - + keyRing := &mock.SecretKeyRing{ + RootKey: privKey, + } // Pack it and then unpack it again to make sure everything is written // correctly, then check that the block height of the unpacked // is the funding broadcast height we set before. diff --git a/chanfitness/chanevent.go b/chanfitness/chanevent.go index 0dc4b29dab..b829a6c306 100644 --- a/chanfitness/chanevent.go +++ b/chanfitness/chanevent.go @@ -404,7 +404,7 @@ func (p *peerLog) uptime(start, end time.Time) (time.Duration, error) { p.start = start } - // If the online period ends before our range, shift the end + // If the online period ends after our range, shift the end // time forward so that we only calculate uptime until the end // of the range. if p.end.After(end) { diff --git a/chanfitness/chanevent_test.go b/chanfitness/chanevent_test.go index 111c8e4e48..43046db253 100644 --- a/chanfitness/chanevent_test.go +++ b/chanfitness/chanevent_test.go @@ -402,7 +402,6 @@ func TestGetOnlinePeriod(t *testing.T) { require.Equal(t, test.expectedOnline, online) }) - } } diff --git a/chanfitness/chaneventstore.go b/chanfitness/chaneventstore.go index d6a6347931..53a699932b 100644 --- a/chanfitness/chaneventstore.go +++ b/chanfitness/chaneventstore.go @@ -142,6 +142,8 @@ func NewChannelEventStore(config *Config) *ChannelEventStore { // information from the store. If this function fails, it cancels its existing // subscriptions and returns an error. func (c *ChannelEventStore) Start() error { + log.Info("ChannelEventStore starting") + // Create a subscription to channel events. channelClient, err := c.cfg.SubscribeChannelEvents() if err != nil { diff --git a/chanfitness/chaneventstore_test.go b/chanfitness/chaneventstore_test.go index 0c9921119a..0608889a98 100644 --- a/chanfitness/chaneventstore_test.go +++ b/chanfitness/chaneventstore_test.go @@ -2,11 +2,10 @@ package chanfitness import ( "errors" - "math/big" "testing" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/clock" @@ -88,11 +87,10 @@ func TestStartStoreError(t *testing.T) { // of uptime and lifespan requests, as they are tested in their own tests. func TestMonitorChannelEvents(t *testing.T) { var ( - pubKey = &btcec.PublicKey{ - X: big.NewInt(0), - Y: big.NewInt(1), - Curve: btcec.S256(), - } + pubKey = btcec.NewPublicKey( + new(btcec.FieldVal).SetInt(0), + new(btcec.FieldVal).SetInt(1), + ) chan1 = wire.OutPoint{Index: 1} chan2 = wire.OutPoint{Index: 2} @@ -155,7 +153,6 @@ func TestMonitorChannelEvents(t *testing.T) { testEventStore(t, gen, peer1, 1) }) - } // testEventStore creates a new test contexts, generates a set of events for it diff --git a/chanfitness/chaneventstore_testctx_test.go b/chanfitness/chaneventstore_testctx_test.go index 8b49831099..aff4c5fca5 100644 --- a/chanfitness/chaneventstore_testctx_test.go +++ b/chanfitness/chaneventstore_testctx_test.go @@ -1,11 +1,10 @@ package chanfitness import ( - "math/big" "testing" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" @@ -144,11 +143,10 @@ func (c *chanEventStoreTestCtx) newChannel() (route.Vertex, *btcec.PublicKey, wire.OutPoint) { // Create a pubkey for our channel peer. - pubKey := &btcec.PublicKey{ - X: big.NewInt(int64(c.testVarIdx)), - Y: big.NewInt(int64(c.testVarIdx)), - Curve: btcec.S256(), - } + pubKey := btcec.NewPublicKey( + new(btcec.FieldVal).SetInt(uint16(c.testVarIdx)), + new(btcec.FieldVal).SetInt(uint16(c.testVarIdx)), + ) // Create vertex from our pubkey. vertex, err := route.NewVertexFromBytes(pubKey.SerializeCompressed()) diff --git a/channel_notifier.go b/channel_notifier.go index 02d9e6d262..5995eb4097 100644 --- a/channel_notifier.go +++ b/channel_notifier.go @@ -4,7 +4,7 @@ import ( "fmt" "net" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chanbackup" "github.com/lightningnetwork/lnd/channeldb" diff --git a/channeldb/channel.go b/channeldb/channel.go index 2461efecb1..afd00fae97 100644 --- a/channeldb/channel.go +++ b/channeldb/channel.go @@ -12,10 +12,10 @@ import ( "strings" "sync" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/kvdb" @@ -49,7 +49,7 @@ var ( // outpointBucket stores all of our channel outpoints and a tlv // stream containing channel data. // - // outpoint -> tlv stream + // outpoint -> tlv stream. // outpointBucket = []byte("outpoint-bucket") @@ -117,21 +117,14 @@ var ( // TODO(roasbeef): rename to commit chain? commitDiffKey = []byte("commit-diff-key") - // revocationLogBucket is dedicated for storing the necessary delta - // state between channel updates required to re-construct a past state - // in order to punish a counterparty attempting a non-cooperative - // channel closure. This key should be accessed from within the - // sub-bucket of a target channel, identified by its channel point. - revocationLogBucket = []byte("revocation-log-key") - // frozenChanKey is the key where we store the information for any // active "frozen" channels. This key is present only in the leaf // bucket for a given channel. frozenChanKey = []byte("frozen-chans") - // lastWasRevokeKey is a key that stores true when the last update we sent - // was a revocation and false when it was a commitment signature. This is - // nil in the case of new channels with no updates exchanged. + // lastWasRevokeKey is a key that stores true when the last update we + // sent was a revocation and false when it was a commitment signature. + // This is nil in the case of new channels with no updates exchanged. lastWasRevokeKey = []byte("last-was-revoke") ) @@ -176,18 +169,9 @@ var ( // channel. ErrChanBorked = fmt.Errorf("cannot mutate borked channel") - // ErrLogEntryNotFound is returned when we cannot find a log entry at - // the height requested in the revocation log. - ErrLogEntryNotFound = fmt.Errorf("log entry not found") - // ErrMissingIndexEntry is returned when a caller attempts to close a // channel and the outpoint is missing from the index. ErrMissingIndexEntry = fmt.Errorf("missing outpoint from index") - - // errHeightNotFound is returned when a query for channel balances at - // a height that we have not reached yet is made. - errHeightNotReached = fmt.Errorf("height requested greater than " + - "current commit height") ) const ( @@ -287,7 +271,7 @@ func (c ChannelType) HasFundingTx() bool { return c&NoFundingTxBit == 0 } -// HasAnchors returns true if this channel type has anchor ouputs on its +// HasAnchors returns true if this channel type has anchor outputs on its // commitment. func (c ChannelType) HasAnchors() bool { return c&AnchorOutputsBit == AnchorOutputsBit @@ -657,6 +641,15 @@ type OpenChannel struct { // received within this channel. TotalMSatReceived lnwire.MilliSatoshi + // InitialLocalBalance is the balance we have during the channel + // opening. When we are not the initiator, this value represents the + // push amount. + InitialLocalBalance lnwire.MilliSatoshi + + // InitialRemoteBalance is the balance they have during the channel + // opening. + InitialRemoteBalance lnwire.MilliSatoshi + // LocalChanCfg is the channel configuration for the local node. LocalChanCfg ChannelConfig @@ -882,8 +875,9 @@ func fetchChanBucket(tx kvdb.RTx, nodeKey *btcec.PublicKey, // channel's data resides in given: the public key for the node, the outpoint, // and the chainhash that the channel resides on. This differs from // fetchChanBucket in that it returns a writeable bucket. -func fetchChanBucketRw(tx kvdb.RwTx, nodeKey *btcec.PublicKey, // nolint:interfacer - outPoint *wire.OutPoint, chainHash chainhash.Hash) (kvdb.RwBucket, error) { +func fetchChanBucketRw(tx kvdb.RwTx, nodeKey *btcec.PublicKey, + outPoint *wire.OutPoint, chainHash chainhash.Hash) (kvdb.RwBucket, + error) { // First fetch the top level bucket which stores all data related to // current, active channels. @@ -1103,10 +1097,10 @@ func (c *OpenChannel) MarkBorked() error { // reconnection with the remote peer that we're maintaining this channel with. // The information contained within this message is necessary to re-sync our // commitment chains in the case of a last or only partially processed message. -// When the remote party receiver this message one of three things may happen: +// When the remote party receives this message one of three things may happen: // // 1. We're fully synced and no messages need to be sent. -// 2. We didn't get the last CommitSig message they sent, to they'll re-send +// 2. We didn't get the last CommitSig message they sent, so they'll re-send // it. // 3. We didn't get the last RevokeAndAck message they sent, so they'll // re-send it. @@ -1643,44 +1637,6 @@ func (c *OpenChannel) UpdateCommitment(newCommitment *ChannelCommitment, return nil } -// BalancesAtHeight returns the local and remote balances on our commitment -// transactions as of a given height. -// -// NOTE: these are our balances *after* subtracting the commitment fee and -// anchor outputs. -func (c *OpenChannel) BalancesAtHeight(height uint64) (lnwire.MilliSatoshi, - lnwire.MilliSatoshi, error) { - - if height > c.LocalCommitment.CommitHeight && - height > c.RemoteCommitment.CommitHeight { - - return 0, 0, errHeightNotReached - } - - // If our current commit is as the desired height, we can return our - // current balances. - if c.LocalCommitment.CommitHeight == height { - return c.LocalCommitment.LocalBalance, - c.LocalCommitment.RemoteBalance, nil - } - - // If our current remote commit is at the desired height, we can return - // the current balances. - if c.RemoteCommitment.CommitHeight == height { - return c.RemoteCommitment.LocalBalance, - c.RemoteCommitment.RemoteBalance, nil - } - - // If we are not currently on the height requested, we need to look up - // the previous height to obtain our balances at the given height. - commit, err := c.FindPreviousState(height) - if err != nil { - return 0, 0, err - } - - return commit.LocalBalance, commit.RemoteBalance, nil -} - // ActiveHtlcs returns a slice of HTLC's which are currently active on *both* // commitment transactions. func (c *OpenChannel) ActiveHtlcs() []HTLC { @@ -1717,6 +1673,8 @@ func (c *OpenChannel) ActiveHtlcs() []HTLC { // // TODO(roasbeef): save space by using smaller ints at tail end? type HTLC struct { + // TODO(yy): can embed an HTLCEntry here. + // Signature is the signature for the second level covenant transaction // for this HTLC. The second level transaction is a timeout tx in the // case that this is an outgoing HTLC, and a success tx in the case @@ -1891,7 +1849,7 @@ func (k *CircuitKey) SetBytes(bs []byte) error { // Bytes returns the serialized bytes for this circuit key. func (k CircuitKey) Bytes() []byte { - var bs = make([]byte, 16) + bs := make([]byte, 16) binary.BigEndian.PutUint64(bs[:8], k.ChanID.ToUint64()) binary.BigEndian.PutUint64(bs[8:], k.HtlcID) return bs @@ -2348,7 +2306,7 @@ func (c *OpenChannel) InsertNextRevocation(revKey *btcec.PublicKey) error { // set of local updates that the peer still needs to send us a signature for. // We store this set of updates in case we go down. func (c *OpenChannel) AdvanceCommitChainTail(fwdPkg *FwdPkg, - updates []LogUpdate) error { + updates []LogUpdate, ourOutputIndex, theirOutputIndex uint32) error { c.Lock() defer c.Unlock() @@ -2421,9 +2379,10 @@ func (c *OpenChannel) AdvanceCommitChainTail(fwdPkg *FwdPkg, // With the commitment pointer swapped, we can now add the // revoked (prior) state to the revocation log. - // - // TODO(roasbeef): store less - err = appendChannelLogEntry(logBucket, &c.RemoteCommitment) + err = putRevocationLog( + logBucket, &c.RemoteCommitment, + ourOutputIndex, theirOutputIndex, + ) if err != nil { return err } @@ -2607,22 +2566,24 @@ func (c *OpenChannel) RemoveFwdPkgs(heights ...uint64) error { }, func() {}) } -// RevocationLogTail returns the "tail", or the end of the current revocation -// log. This entry represents the last previous state for the remote node's -// commitment chain. The ChannelDelta returned by this method will always lag -// one state behind the most current (unrevoked) state of the remote node's -// commitment chain. -func (c *OpenChannel) RevocationLogTail() (*ChannelCommitment, error) { +// revocationLogTailCommitHeight returns the commit height at the end of the +// revocation log. This entry represents the last previous state for the remote +// node's commitment chain. The ChannelDelta returned by this method will +// always lag one state behind the most current (unrevoked) state of the remote +// node's commitment chain. +// NOTE: used in unit test only. +func (c *OpenChannel) revocationLogTailCommitHeight() (uint64, error) { c.RLock() defer c.RUnlock() + var height uint64 + // If we haven't created any state updates yet, then we'll exit early as // there's nothing to be found on disk in the revocation bucket. if c.RemoteCommitment.CommitHeight == 0 { - return nil, nil + return height, nil } - var commit ChannelCommitment if err := kvdb.View(c.Db.backend, func(tx kvdb.RTx) error { chanBucket, err := fetchChanBucket( tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash, @@ -2631,33 +2592,25 @@ func (c *OpenChannel) RevocationLogTail() (*ChannelCommitment, error) { return err } - logBucket := chanBucket.NestedReadBucket(revocationLogBucket) - if logBucket == nil { - return ErrNoPastDeltas + logBucket, err := fetchLogBucket(chanBucket) + if err != nil { + return err } // Once we have the bucket that stores the revocation log from - // this channel, we'll jump to the _last_ key in bucket. As we - // store the update number on disk in a big-endian format, - // this will retrieve the latest entry. + // this channel, we'll jump to the _last_ key in bucket. Since + // the key is the commit height, we'll decode the bytes and + // return it. cursor := logBucket.ReadCursor() - _, tailLogEntry := cursor.Last() - logEntryReader := bytes.NewReader(tailLogEntry) - - // Once we have the entry, we'll decode it into the channel - // delta pointer we created above. - var dbErr error - commit, dbErr = deserializeChanCommit(logEntryReader) - if dbErr != nil { - return dbErr - } + rawHeight, _ := cursor.Last() + height = byteOrder.Uint64(rawHeight) return nil }, func() {}); err != nil { - return nil, err + return height, err } - return &commit, nil + return height, nil } // CommitmentHeight returns the current commitment height. The commitment @@ -2702,11 +2655,15 @@ func (c *OpenChannel) CommitmentHeight() (uint64, error) { // intended to be used for obtaining the relevant data needed to claim all // funds rightfully spendable in the case of an on-chain broadcast of the // commitment transaction. -func (c *OpenChannel) FindPreviousState(updateNum uint64) (*ChannelCommitment, error) { +func (c *OpenChannel) FindPreviousState( + updateNum uint64) (*RevocationLog, *ChannelCommitment, error) { + c.RLock() defer c.RUnlock() - var commit ChannelCommitment + commit := &ChannelCommitment{} + rl := &RevocationLog{} + err := kvdb.View(c.Db.backend, func(tx kvdb.RTx) error { chanBucket, err := fetchChanBucket( tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash, @@ -2715,24 +2672,24 @@ func (c *OpenChannel) FindPreviousState(updateNum uint64) (*ChannelCommitment, e return err } - logBucket := chanBucket.NestedReadBucket(revocationLogBucket) - if logBucket == nil { - return ErrNoPastDeltas - } - - c, err := fetchChannelLogEntry(logBucket, updateNum) + // Find the revocation log from both the new and the old + // bucket. + r, c, err := fetchRevocationLogCompatible(chanBucket, updateNum) if err != nil { return err } + rl = r commit = c return nil }, func() {}) if err != nil { - return nil, err + return nil, nil, err } - return &commit, nil + // Either the `rl` or the `commit` is nil here. We return them as-is + // and leave it to the caller to decide its following action. + return rl, commit, nil } // ClosureType is an enum like structure that details exactly _how_ a channel @@ -2929,12 +2886,8 @@ func (c *OpenChannel) CloseChannel(summary *ChannelCloseSummary, // With the base channel data deleted, attempt to delete the // information stored within the revocation log. - logBucket := chanBucket.NestedReadWriteBucket(revocationLogBucket) - if logBucket != nil { - err = chanBucket.DeleteNestedBucket(revocationLogBucket) - if err != nil { - return err - } + if err := deleteLogBucket(chanBucket); err != nil { + return err } err = chainBucket.DeleteNestedBucket(chanPointBuf.Bytes()) @@ -3191,7 +3144,7 @@ func serializeChannelCloseSummary(w io.Writer, cs *ChannelCloseSummary) error { // The RemoteNextRevocation field is optional, as it's possible for a // channel to be closed before we learn of the next unrevoked - // revocation point for the remote party. Write a boolen indicating + // revocation point for the remote party. Write a boolean indicating // whether this field is present or not. if err := WriteElements(w, cs.RemoteNextRevocation != nil); err != nil { return err @@ -3310,7 +3263,7 @@ func writeChanConfig(b io.Writer, c *ChannelConfig) error { } // fundingTxPresent returns true if expect the funding transcation to be found -// on disk or already populated within the passed oen chanel struct. +// on disk or already populated within the passed open channel struct. func fundingTxPresent(channel *OpenChannel) bool { chanType := channel.ChanType @@ -3327,7 +3280,8 @@ func putChanInfo(chanBucket kvdb.RwBucket, channel *OpenChannel) error { channel.chanStatus, channel.FundingBroadcastHeight, channel.NumConfsRequired, channel.ChannelFlags, channel.IdentityPub, channel.Capacity, channel.TotalMSatSent, - channel.TotalMSatReceived, + channel.TotalMSatReceived, channel.InitialLocalBalance, + channel.InitialRemoteBalance, ); err != nil { return err } @@ -3468,7 +3422,6 @@ func putChanCommitments(chanBucket kvdb.RwBucket, channel *OpenChannel) error { } func putChanRevocationState(chanBucket kvdb.RwBucket, channel *OpenChannel) error { - var b bytes.Buffer err := WriteElements( &b, channel.RemoteCurrentRevocation, channel.RevocationProducer, @@ -3515,7 +3468,8 @@ func fetchChanInfo(chanBucket kvdb.RBucket, channel *OpenChannel) error { &channel.chanStatus, &channel.FundingBroadcastHeight, &channel.NumConfsRequired, &channel.ChannelFlags, &channel.IdentityPub, &channel.Capacity, &channel.TotalMSatSent, - &channel.TotalMSatReceived, + &channel.TotalMSatReceived, &channel.InitialLocalBalance, + &channel.InitialRemoteBalance, ); err != nil { return err } @@ -3659,7 +3613,6 @@ func fetchChanRevocationState(chanBucket kvdb.RBucket, channel *OpenChannel) err } func deleteOpenChannel(chanBucket kvdb.RwBucket) error { - if err := chanBucket.Delete(chanInfoKey); err != nil { return err } @@ -3682,7 +3635,6 @@ func deleteOpenChannel(chanBucket kvdb.RwBucket) error { } return nil - } // makeLogKey converts a uint64 into an 8 byte array. @@ -3692,31 +3644,6 @@ func makeLogKey(updateNum uint64) [8]byte { return key } -func appendChannelLogEntry(log kvdb.RwBucket, - commit *ChannelCommitment) error { - - var b bytes.Buffer - if err := serializeChanCommit(&b, commit); err != nil { - return err - } - - logEntrykey := makeLogKey(commit.CommitHeight) - return log.Put(logEntrykey[:], b.Bytes()) -} - -func fetchChannelLogEntry(log kvdb.RBucket, - updateNum uint64) (ChannelCommitment, error) { - - logEntrykey := makeLogKey(updateNum) - commitBytes := log.Get(logEntrykey[:]) - if commitBytes == nil { - return ChannelCommitment{}, ErrLogEntryNotFound - } - - commitReader := bytes.NewReader(commitBytes) - return deserializeChanCommit(commitReader) -} - func fetchThawHeight(chanBucket kvdb.RBucket) (uint32, error) { var height uint32 diff --git a/channeldb/channel_test.go b/channeldb/channel_test.go index 044308f887..35607fd55a 100644 --- a/channeldb/channel_test.go +++ b/channeldb/channel_test.go @@ -8,12 +8,10 @@ import ( "runtime" "testing" - "github.com/stretchr/testify/require" - - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" _ "github.com/btcsuite/btcwallet/walletdb/bdb" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/clock" @@ -22,6 +20,7 @@ import ( "github.com/lightningnetwork/lnd/lntest/channels" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/shachain" + "github.com/stretchr/testify/require" ) var ( @@ -36,7 +35,7 @@ var ( 0x48, 0x59, 0xe6, 0x96, 0x31, 0x13, 0xa1, 0x17, 0x2d, 0xe7, 0x93, 0xe4, } - privKey, pubKey = btcec.PrivKeyFromBytes(btcec.S256(), key[:]) + privKey, pubKey = btcec.PrivKeyFromBytes(key[:]) wireSig, _ = lnwire.NewSigFromSignature(testSig) @@ -53,8 +52,17 @@ var ( Port: 18555, } - // keyLocIndex is the KeyLocator Index we use for TestKeyLocatorEncoding. + // keyLocIndex is the KeyLocator Index we use for + // TestKeyLocatorEncoding. keyLocIndex = uint32(2049) + + // dummyLocalOutputIndex specifics a default value for our output index + // in this test. + dummyLocalOutputIndex = uint32(0) + + // dummyRemoteOutIndex specifics a default value for their output index + // in this test. + dummyRemoteOutIndex = uint32(1) ) // testChannelParams is a struct which details the specifics of how a channel @@ -79,25 +87,6 @@ type testChannelParams struct { // default channel that is creates for testing. type testChannelOption func(params *testChannelParams) -// channelCommitmentOption is an option which allows overwriting of the default -// commitment height and balances. The local boolean can be used to set these -// balances on the local or remote commit. -func channelCommitmentOption(height uint64, localBalance, - remoteBalance lnwire.MilliSatoshi, local bool) testChannelOption { - - return func(params *testChannelParams) { - if local { - params.channel.LocalCommitment.CommitHeight = height - params.channel.LocalCommitment.LocalBalance = localBalance - params.channel.LocalCommitment.RemoteBalance = remoteBalance - } else { - params.channel.RemoteCommitment.CommitHeight = height - params.channel.RemoteCommitment.LocalBalance = localBalance - params.channel.RemoteCommitment.RemoteBalance = remoteBalance - } - } -} - // pendingHeightOption is an option which can be used to set the height the // channel is marked as pending at. func pendingHeightOption(height uint32) testChannelOption { @@ -353,6 +342,8 @@ func createTestChannelState(t *testing.T, cdb *ChannelStateDB) *OpenChannel { Packager: NewChannelPackager(chanID), FundingTxn: channels.TestFundingTx, ThawHeight: uint32(defaultPendingHeight), + InitialLocalBalance: lnwire.MilliSatoshi(9000), + InitialRemoteBalance: lnwire.MilliSatoshi(3000), } } @@ -413,7 +404,7 @@ func TestOpenChannelPutGetDelete(t *testing.T) { // We'll also test that the channel is properly able to hot swap the // next revocation for the state machine. This tests the initial // post-funding revocation exchange. - nextRevKey, err := btcec.NewPrivateKey(btcec.S256()) + nextRevKey, err := btcec.NewPrivateKey() if err != nil { t.Fatalf("unable to create new private key: %v", err) } @@ -566,6 +557,32 @@ func assertCommitmentEqual(t *testing.T, a, b *ChannelCommitment) { } } +// assertRevocationLogEntryEqual asserts that, for all the fields of a given +// revocation log entry, their values match those on a given ChannelCommitment. +func assertRevocationLogEntryEqual(t *testing.T, c *ChannelCommitment, + r *RevocationLog) { + + // Check the common fields. + require.EqualValues( + t, r.CommitTxHash, c.CommitTx.TxHash(), "CommitTx mismatch", + ) + + // Now check the common fields from the HTLCs. + require.Equal(t, len(r.HTLCEntries), len(c.Htlcs), "HTLCs len mismatch") + for i, rHtlc := range r.HTLCEntries { + cHtlc := c.Htlcs[i] + require.Equal(t, rHtlc.RHash, cHtlc.RHash, "RHash mismatch") + require.Equal(t, rHtlc.Amt, cHtlc.Amt.ToSatoshis(), + "Amt mismatch") + require.Equal(t, rHtlc.RefundTimeout, cHtlc.RefundTimeout, + "RefundTimeout mismatch") + require.EqualValues(t, rHtlc.OutputIndex, cHtlc.OutputIndex, + "OutputIndex mismatch") + require.Equal(t, rHtlc.Incoming, cHtlc.Incoming, + "Incoming mismatch") + } +} + func TestChannelStateTransition(t *testing.T) { t.Parallel() @@ -659,6 +676,7 @@ func TestChannelStateTransition(t *testing.T) { if !reflect.DeepEqual( dbUnsignedAckedUpdates[0], unsignedAckedUpdates[0], ) { + t.Fatalf("unexpected update: expected %v, got %v", spew.Sdump(unsignedAckedUpdates[0]), spew.Sdump(dbUnsignedAckedUpdates)) @@ -756,7 +774,7 @@ func TestChannelStateTransition(t *testing.T) { // current uncollapsed revocation state to simulate a state transition // by the remote party. channel.RemoteCurrentRevocation = channel.RemoteNextRevocation - newPriv, err := btcec.NewPrivateKey(btcec.S256()) + newPriv, err := btcec.NewPrivateKey() if err != nil { t.Fatalf("unable to generate key: %v", err) } @@ -765,7 +783,9 @@ func TestChannelStateTransition(t *testing.T) { fwdPkg := NewFwdPkg(channel.ShortChanID(), oldRemoteCommit.CommitHeight, diskCommitDiff.LogUpdates, nil) - err = channel.AdvanceCommitChainTail(fwdPkg, nil) + err = channel.AdvanceCommitChainTail( + fwdPkg, nil, dummyLocalOutputIndex, dummyRemoteOutIndex, + ) if err != nil { t.Fatalf("unable to append to revocation log: %v", err) } @@ -778,24 +798,32 @@ func TestChannelStateTransition(t *testing.T) { // We should be able to fetch the channel delta created above by its // update number with all the state properly reconstructed. - diskPrevCommit, err := channel.FindPreviousState( + diskPrevCommit, _, err := channel.FindPreviousState( oldRemoteCommit.CommitHeight, ) if err != nil { t.Fatalf("unable to fetch past delta: %v", err) } + // Check the output indexes are saved as expected. + require.EqualValues( + t, dummyLocalOutputIndex, diskPrevCommit.OurOutputIndex, + ) + require.EqualValues( + t, dummyRemoteOutIndex, diskPrevCommit.TheirOutputIndex, + ) + // The two deltas (the original vs the on-disk version) should // identical, and all HTLC data should properly be retained. - assertCommitmentEqual(t, &oldRemoteCommit, diskPrevCommit) + assertRevocationLogEntryEqual(t, &oldRemoteCommit, diskPrevCommit) // The state number recovered from the tail of the revocation log // should be identical to this current state. - logTail, err := channel.RevocationLogTail() + logTailHeight, err := channel.revocationLogTailCommitHeight() if err != nil { t.Fatalf("unable to retrieve log: %v", err) } - if logTail.CommitHeight != oldRemoteCommit.CommitHeight { + if logTailHeight != oldRemoteCommit.CommitHeight { t.Fatal("update number doesn't match") } @@ -813,25 +841,38 @@ func TestChannelStateTransition(t *testing.T) { fwdPkg = NewFwdPkg(channel.ShortChanID(), oldRemoteCommit.CommitHeight, nil, nil) - err = channel.AdvanceCommitChainTail(fwdPkg, nil) + err = channel.AdvanceCommitChainTail( + fwdPkg, nil, dummyLocalOutputIndex, dummyRemoteOutIndex, + ) if err != nil { t.Fatalf("unable to append to revocation log: %v", err) } // Once again, fetch the state and ensure it has been properly updated. - prevCommit, err := channel.FindPreviousState(oldRemoteCommit.CommitHeight) + prevCommit, _, err := channel.FindPreviousState( + oldRemoteCommit.CommitHeight, + ) if err != nil { t.Fatalf("unable to fetch past delta: %v", err) } - assertCommitmentEqual(t, &oldRemoteCommit, prevCommit) + + // Check the output indexes are saved as expected. + require.EqualValues( + t, dummyLocalOutputIndex, diskPrevCommit.OurOutputIndex, + ) + require.EqualValues( + t, dummyRemoteOutIndex, diskPrevCommit.TheirOutputIndex, + ) + + assertRevocationLogEntryEqual(t, &oldRemoteCommit, prevCommit) // Once again, state number recovered from the tail of the revocation // log should be identical to this current state. - logTail, err = channel.RevocationLogTail() + logTailHeight, err = channel.revocationLogTailCommitHeight() if err != nil { t.Fatalf("unable to retrieve log: %v", err) } - if logTail.CommitHeight != oldRemoteCommit.CommitHeight { + if logTailHeight != oldRemoteCommit.CommitHeight { t.Fatal("update number doesn't match") } @@ -877,7 +918,9 @@ func TestChannelStateTransition(t *testing.T) { // Attempting to find previous states on the channel should fail as the // revocation log has been deleted. - _, err = updatedChannel[0].FindPreviousState(oldRemoteCommit.CommitHeight) + _, _, err = updatedChannel[0].FindPreviousState( + oldRemoteCommit.CommitHeight, + ) if err == nil { t.Fatal("revocation log search should have failed") } @@ -1413,174 +1456,6 @@ func TestCloseChannelStatus(t *testing.T) { } } -// TestBalanceAtHeight tests lookup of our local and remote balance at a given -// height. -func TestBalanceAtHeight(t *testing.T) { - const ( - // Values that will be set on our current local commit in - // memory. - localHeight = 2 - localLocalBalance = 1000 - localRemoteBalance = 1500 - - // Values that will be set on our current remote commit in - // memory. - remoteHeight = 3 - remoteLocalBalance = 2000 - remoteRemoteBalance = 2500 - - // Values that will be written to disk in the revocation log. - oldHeight = 0 - oldLocalBalance = 200 - oldRemoteBalance = 300 - - // Heights to test error cases. - unknownHeight = 1 - unreachedHeight = 4 - ) - - // putRevokedState is a helper function used to put commitments is - // the revocation log bucket to test lookup of balances at heights that - // are not our current height. - putRevokedState := func(c *OpenChannel, height uint64, local, - remote lnwire.MilliSatoshi) error { - - err := kvdb.Update(c.Db.backend, func(tx kvdb.RwTx) error { - chanBucket, err := fetchChanBucketRw( - tx, c.IdentityPub, &c.FundingOutpoint, - c.ChainHash, - ) - if err != nil { - return err - } - - logKey := revocationLogBucket - logBucket, err := chanBucket.CreateBucketIfNotExists( - logKey, - ) - if err != nil { - return err - } - - // Make a copy of our current commitment so we do not - // need to re-fill all the required fields and copy in - // our new desired values. - commit := c.LocalCommitment - commit.CommitHeight = height - commit.LocalBalance = local - commit.RemoteBalance = remote - - return appendChannelLogEntry(logBucket, &commit) - }, func() {}) - - return err - } - - tests := []struct { - name string - targetHeight uint64 - expectedLocalBalance lnwire.MilliSatoshi - expectedRemoteBalance lnwire.MilliSatoshi - expectedError error - }{ - { - name: "target is current local height", - targetHeight: localHeight, - expectedLocalBalance: localLocalBalance, - expectedRemoteBalance: localRemoteBalance, - expectedError: nil, - }, - { - name: "target is current remote height", - targetHeight: remoteHeight, - expectedLocalBalance: remoteLocalBalance, - expectedRemoteBalance: remoteRemoteBalance, - expectedError: nil, - }, - { - name: "need to lookup commit", - targetHeight: oldHeight, - expectedLocalBalance: oldLocalBalance, - expectedRemoteBalance: oldRemoteBalance, - expectedError: nil, - }, - { - name: "height not found", - targetHeight: unknownHeight, - expectedLocalBalance: 0, - expectedRemoteBalance: 0, - expectedError: ErrLogEntryNotFound, - }, - { - name: "height not reached", - targetHeight: unreachedHeight, - expectedLocalBalance: 0, - expectedRemoteBalance: 0, - expectedError: errHeightNotReached, - }, - } - - for _, test := range tests { - test := test - - t.Run(test.name, func(t *testing.T) { - t.Parallel() - - fullDB, cleanUp, err := MakeTestDB() - if err != nil { - t.Fatalf("unable to make test database: %v", - err) - } - defer cleanUp() - - cdb := fullDB.ChannelStateDB() - - // Create options to set the heights and balances of - // our local and remote commitments. - localCommitOpt := channelCommitmentOption( - localHeight, localLocalBalance, - localRemoteBalance, true, - ) - - remoteCommitOpt := channelCommitmentOption( - remoteHeight, remoteLocalBalance, - remoteRemoteBalance, false, - ) - - // Create an open channel. - channel := createTestChannel( - t, cdb, openChannelOption(), - localCommitOpt, remoteCommitOpt, - ) - - // Write an older commit to disk. - err = putRevokedState(channel, oldHeight, - oldLocalBalance, oldRemoteBalance) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - - local, remote, err := channel.BalancesAtHeight( - test.targetHeight, - ) - if err != test.expectedError { - t.Fatalf("expected: %v, got: %v", - test.expectedError, err) - } - - if local != test.expectedLocalBalance { - t.Fatalf("expected local: %v, got: %v", - test.expectedLocalBalance, local) - } - - if remote != test.expectedRemoteBalance { - t.Fatalf("expected remote: %v, got: %v", - test.expectedRemoteBalance, remote) - } - }) - } -} - // TestHasChanStatus asserts the behavior of HasChanStatus by checking the // behavior of various status flags in addition to the special case of // ChanStatusDefault which is treated like a flag in the code base even though diff --git a/channeldb/codec.go b/channeldb/codec.go index 424f7c6e84..832b919234 100644 --- a/channeldb/codec.go +++ b/channeldb/codec.go @@ -7,10 +7,10 @@ import ( "io" "net" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/shachain" @@ -346,7 +346,7 @@ func ReadElement(r io.Reader, element interface{}) error { return err } - priv, _ := btcec.PrivKeyFromBytes(btcec.S256(), b[:]) + priv, _ := btcec.PrivKeyFromBytes(b[:]) *e = priv case **btcec.PublicKey: @@ -355,7 +355,7 @@ func ReadElement(r io.Reader, element interface{}) error { return err } - pubKey, err := btcec.ParsePubKey(b[:], btcec.S256()) + pubKey, err := btcec.ParsePubKey(b[:]) if err != nil { return err } diff --git a/channeldb/db.go b/channeldb/db.go index 173aada2d3..b1e202c735 100644 --- a/channeldb/db.go +++ b/channeldb/db.go @@ -8,7 +8,7 @@ import ( "net" "os" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/wire" "github.com/go-errors/errors" mig "github.com/lightningnetwork/lnd/channeldb/migration" @@ -19,6 +19,7 @@ import ( "github.com/lightningnetwork/lnd/channeldb/migration21" "github.com/lightningnetwork/lnd/channeldb/migration23" "github.com/lightningnetwork/lnd/channeldb/migration24" + "github.com/lightningnetwork/lnd/channeldb/migration25" "github.com/lightningnetwork/lnd/channeldb/migration_01_to_11" "github.com/lightningnetwork/lnd/clock" "github.com/lightningnetwork/lnd/kvdb" @@ -205,6 +206,12 @@ var ( number: 24, migration: migration24.MigrateFwdPkgCleanup, }, + { + // Save the initial local/remote balances in channel + // info. + number: 25, + migration: migration25.MigrateInitialBalances, + }, } // Big endian is the preferred byte order, due to cursor scans over @@ -263,15 +270,17 @@ func Open(dbPath string, modifiers ...OptionModifier) (*DB, error) { // CreateWithBackend creates channeldb instance using the passed kvdb.Backend. // Any necessary schemas migrations due to updates will take place as necessary. func CreateWithBackend(backend kvdb.Backend, modifiers ...OptionModifier) (*DB, error) { - if err := initChannelDB(backend); err != nil { - return nil, err - } - opts := DefaultOptions() for _, modifier := range modifiers { modifier(&opts) } + if !opts.NoMigration { + if err := initChannelDB(backend); err != nil { + return nil, err + } + } + chanDB := &DB{ Backend: backend, channelStateDB: &ChannelStateDB{ @@ -291,16 +300,18 @@ func CreateWithBackend(backend kvdb.Backend, modifiers ...OptionModifier) (*DB, chanDB.graph, err = NewChannelGraph( backend, opts.RejectCacheSize, opts.ChannelCacheSize, opts.BatchCommitInterval, opts.PreAllocCacheNumNodes, - opts.UseGraphCache, + opts.UseGraphCache, opts.NoMigration, ) if err != nil { return nil, err } // Synchronize the version of database and apply migrations if needed. - if err := chanDB.syncVersions(dbVersions); err != nil { - backend.Close() - return nil, err + if !opts.NoMigration { + if err := chanDB.syncVersions(dbVersions); err != nil { + backend.Close() + return nil, err + } } return chanDB, nil @@ -325,6 +336,7 @@ var dbTopLevelBuckets = [][]byte{ metaBucket, closeSummaryBucket, outpointBucket, + historicalChannelBucket, } // Wipe completely deletes all saved state within all used buckets within the @@ -1032,7 +1044,7 @@ func (c *ChannelStateDB) pruneLinkNode(openChannels []*OpenChannel, return c.linkNodeDB.DeleteLinkNode(remotePub) } -// PruneLinkNodes attempts to prune all link nodes found within the databse with +// PruneLinkNodes attempts to prune all link nodes found within the database with // whom we no longer have any open channels with. func (c *ChannelStateDB) PruneLinkNodes() error { allLinkNodes, err := c.linkNodeDB.FetchAllLinkNodes() @@ -1184,7 +1196,7 @@ func (c *ChannelStateDB) AbandonChannel(chanPoint *wire.OutPoint, } // If the channel was already closed, then we don't return an - // error as we'd like fro this step to be repeatable. + // error as we'd like this step to be repeatable. return nil case err != nil: return err @@ -1410,9 +1422,12 @@ func (c *ChannelStateDB) FetchHistoricalChannel(outPoint *wire.OutPoint) ( } channel, err = fetchOpenChannel(chanBucket, outPoint) + if err != nil { + return err + } channel.Db = c - return err + return nil }, func() { channel = nil }) diff --git a/channeldb/db_test.go b/channeldb/db_test.go index 5731c03a8a..b741a2ad71 100644 --- a/channeldb/db_test.go +++ b/channeldb/db_test.go @@ -10,10 +10,10 @@ import ( "reflect" "testing" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/kvdb" @@ -25,6 +25,11 @@ import ( func TestOpenWithCreate(t *testing.T) { t.Parallel() + // Checking for db file existence is not possible with postgres. + if kvdb.PostgresBackend { + t.Skip() + } + // First, create a temporary directory to be used for the duration of // this test. tempDirName, err := ioutil.TempDir("", "channeldb") @@ -295,15 +300,13 @@ func genRandomChannelShell() (*ChannelShell, error) { return nil, err } - _, pub := btcec.PrivKeyFromBytes(btcec.S256(), testPriv[:]) + _, pub := btcec.PrivKeyFromBytes(testPriv[:]) var chanPoint wire.OutPoint if _, err := rand.Read(chanPoint.Hash[:]); err != nil { return nil, err } - pub.Curve = nil - chanPoint.Index = uint32(rand.Intn(math.MaxUint16)) chanStatus := ChanStatusDefault | ChanStatusRestored @@ -406,7 +409,9 @@ func TestRestoreChannelShells(t *testing.T) { if err != ErrNoRestoredChannelMutation { t.Fatalf("able to mutate restored channel") } - err = channel.AdvanceCommitChainTail(nil, nil) + err = channel.AdvanceCommitChainTail( + nil, nil, dummyLocalOutputIndex, dummyRemoteOutIndex, + ) if err != ErrNoRestoredChannelMutation { t.Fatalf("able to mutate restored channel") } @@ -443,7 +448,7 @@ func TestRestoreChannelShells(t *testing.T) { // The node should have the same address, as specified in the channel // shell. if reflect.DeepEqual(linkNode.Addresses, channelShell.NodeAddrs) { - t.Fatalf("addr mismach: expected %v, got %v", + t.Fatalf("addr mismatch: expected %v, got %v", linkNode.Addresses, channelShell.NodeAddrs) } } @@ -712,10 +717,11 @@ func TestFetchHistoricalChannel(t *testing.T) { // Create a an open channel in the database. channel := createTestChannel(t, cdb, openChannelOption()) - // First, try to lookup a channel when the bucket does not - // exist. + // First, try to lookup a channel when nothing is in the bucket. As the + // bucket is auto-created (on start up), we'll get a channel not found + // error. _, err = cdb.FetchHistoricalChannel(&channel.FundingOutpoint) - if err != ErrNoHistoricalBucket { + if err != ErrChannelNotFound { t.Fatalf("expected no bucket, got: %v", err) } @@ -732,7 +738,7 @@ func TestFetchHistoricalChannel(t *testing.T) { histChannel, err := cdb.FetchHistoricalChannel(&channel.FundingOutpoint) if err != nil { - t.Fatalf("unexepected error getting channel: %v", err) + t.Fatalf("unexpected error getting channel: %v", err) } // FetchHistoricalChannel will attach the cdb to channel.Db, we set it @@ -753,5 +759,4 @@ func TestFetchHistoricalChannel(t *testing.T) { if err != ErrChannelNotFound { t.Fatalf("expected chan not found, got: %v", err) } - } diff --git a/channeldb/doc.go b/channeldb/doc.go deleted file mode 100644 index d03b34062a..0000000000 --- a/channeldb/doc.go +++ /dev/null @@ -1 +0,0 @@ -package channeldb diff --git a/channeldb/duplicate_payments.go b/channeldb/duplicate_payments.go index 7dad7df706..2f6a0a2788 100644 --- a/channeldb/duplicate_payments.go +++ b/channeldb/duplicate_payments.go @@ -7,7 +7,7 @@ import ( "io" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwire" @@ -150,7 +150,6 @@ func fetchDuplicatePayment(bucket kvdb.RBucket) (*MPPayment, error) { creationInfo, err := deserializeDuplicatePaymentCreationInfo(r) if err != nil { return nil, err - } // Get failure reason if available. diff --git a/channeldb/fees.go b/channeldb/fees.go deleted file mode 100644 index d03b34062a..0000000000 --- a/channeldb/fees.go +++ /dev/null @@ -1 +0,0 @@ -package channeldb diff --git a/channeldb/forwarding_log.go b/channeldb/forwarding_log.go index 3261665fda..f527b78dd0 100644 --- a/channeldb/forwarding_log.go +++ b/channeldb/forwarding_log.go @@ -47,7 +47,7 @@ func (d *DB) ForwardingLog() *ForwardingLog { } } -// ForwardingLog is a time series database that logs the fulfilment of payment +// ForwardingLog is a time series database that logs the fulfillment of payment // circuits by a lightning network daemon. The log contains a series of // forwarding events which map a timestamp to a forwarding event. A forwarding // event describes which channels were used to create+settle a circuit, and the @@ -204,7 +204,7 @@ type ForwardingEventQuery struct { // ForwardingLogTimeSlice is the response to a forwarding query. It includes // the original query, the set events that match the query, and an integer -// which represents the offset index of the last item in the set of retuned +// which represents the offset index of the last item in the set of returned // events. This integer allows callers to resume their query using this offset // in the event that the query's response exceeds the max number of returnable // events. diff --git a/channeldb/graph.go b/channeldb/graph.go index d1bb85b17c..ced5588293 100644 --- a/channeldb/graph.go +++ b/channeldb/graph.go @@ -14,11 +14,12 @@ import ( "sync" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/batch" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lnwire" @@ -189,10 +190,12 @@ type ChannelGraph struct { // returned instance has its own unique reject cache and channel cache. func NewChannelGraph(db kvdb.Backend, rejectCacheSize, chanCacheSize int, batchCommitInterval time.Duration, preAllocCacheNumNodes int, - useGraphCache bool) (*ChannelGraph, error) { + useGraphCache, noMigrations bool) (*ChannelGraph, error) { - if err := initChannelGraph(db); err != nil { - return nil, err + if !noMigrations { + if err := initChannelGraph(db); err != nil { + return nil, err + } } g := &ChannelGraph{ @@ -214,15 +217,29 @@ func NewChannelGraph(db kvdb.Backend, rejectCacheSize, chanCacheSize int, startTime := time.Now() log.Debugf("Populating in-memory channel graph, this might " + "take a while...") + err := g.ForEachNodeCacheable( func(tx kvdb.RTx, node GraphCacheNode) error { - return g.graphCache.AddNode(tx, node) + g.graphCache.AddNodeFeatures(node) + + return nil }, ) if err != nil { return nil, err } + err = g.ForEachChannel(func(info *ChannelEdgeInfo, + policy1, policy2 *ChannelEdgePolicy) error { + + g.graphCache.AddChannel(info, policy1, policy2) + + return nil + }) + if err != nil { + return nil, err + } + log.Debugf("Finished populating in-memory channel graph (took "+ "%v, %s)", time.Since(startTime), g.graphCache.Stats()) } @@ -230,6 +247,71 @@ func NewChannelGraph(db kvdb.Backend, rejectCacheSize, chanCacheSize int, return g, nil } +// channelMapKey is the key structure used for storing channel edge policies. +type channelMapKey struct { + nodeKey route.Vertex + chanID [8]byte +} + +// getChannelMap loads all channel edge policies from the database and stores +// them in a map. +func (c *ChannelGraph) getChannelMap(edges kvdb.RBucket) ( + map[channelMapKey]*ChannelEdgePolicy, error) { + + // Create a map to store all channel edge policies. + channelMap := make(map[channelMapKey]*ChannelEdgePolicy) + + err := kvdb.ForAll(edges, func(k, edgeBytes []byte) error { + // Skip embedded buckets. + if bytes.Equal(k, edgeIndexBucket) || + bytes.Equal(k, edgeUpdateIndexBucket) || + bytes.Equal(k, zombieBucket) || + bytes.Equal(k, disabledEdgePolicyBucket) || + bytes.Equal(k, channelPointBucket) { + + return nil + } + + // Validate key length. + if len(k) != 33+8 { + return fmt.Errorf("invalid edge key %x encountered", k) + } + + var key channelMapKey + copy(key.nodeKey[:], k[:33]) + copy(key.chanID[:], k[33:]) + + // No need to deserialize unknown policy. + if bytes.Equal(edgeBytes, unknownPolicy) { + return nil + } + + edgeReader := bytes.NewReader(edgeBytes) + edge, err := deserializeChanEdgePolicyRaw( + edgeReader, + ) + + switch { + // If the db policy was missing an expected optional field, we + // return nil as if the policy was unknown. + case err == ErrEdgePolicyOptionalFieldNotFound: + return nil + + case err != nil: + return err + } + + channelMap[key] = edge + + return nil + }) + if err != nil { + return nil, err + } + + return channelMap, nil +} + var graphTopLevelBuckets = [][]byte{ nodeBucket, edgeBucket, @@ -330,50 +412,47 @@ func (c *ChannelGraph) NewPathFindTx() (kvdb.RTx, error) { func (c *ChannelGraph) ForEachChannel(cb func(*ChannelEdgeInfo, *ChannelEdgePolicy, *ChannelEdgePolicy) error) error { - // TODO(roasbeef): ptr map to reduce # of allocs? no duplicates - - return kvdb.View(c.db, func(tx kvdb.RTx) error { - // First, grab the node bucket. This will be used to populate - // the Node pointers in each edge read from disk. - nodes := tx.ReadBucket(nodeBucket) - if nodes == nil { - return ErrGraphNotFound - } - - // Next, grab the edge bucket which stores the edges, and also - // the index itself so we can group the directed edges together - // logically. + return c.db.View(func(tx kvdb.RTx) error { edges := tx.ReadBucket(edgeBucket) if edges == nil { return ErrGraphNoEdgesFound } + + // First, load all edges in memory indexed by node and channel + // id. + channelMap, err := c.getChannelMap(edges) + if err != nil { + return err + } + edgeIndex := edges.NestedReadBucket(edgeIndexBucket) if edgeIndex == nil { return ErrGraphNoEdgesFound } - // For each edge pair within the edge index, we fetch each edge - // itself and also the node information in order to fully - // populated the object. - return edgeIndex.ForEach(func(chanID, edgeInfoBytes []byte) error { - infoReader := bytes.NewReader(edgeInfoBytes) - edgeInfo, err := deserializeChanEdgeInfo(infoReader) - if err != nil { - return err - } - edgeInfo.db = c.db + // Load edge index, recombine each channel with the policies + // loaded above and invoke the callback. + return kvdb.ForAll(edgeIndex, func(k, edgeInfoBytes []byte) error { + var chanID [8]byte + copy(chanID[:], k) - edge1, edge2, err := fetchChanEdgePolicies( - edgeIndex, edges, nodes, chanID, c.db, - ) + edgeInfoReader := bytes.NewReader(edgeInfoBytes) + info, err := deserializeChanEdgeInfo(edgeInfoReader) if err != nil { return err } - // With both edges read, execute the call back. IF this - // function returns an error then the transaction will - // be aborted. - return cb(&edgeInfo, edge1, edge2) + policy1 := channelMap[channelMapKey{ + nodeKey: info.NodeKey1Bytes, + chanID: chanID, + }] + + policy2 := channelMap[channelMapKey{ + nodeKey: info.NodeKey2Bytes, + chanID: chanID, + }] + + return cb(&info, policy1, policy2) }) }, func() {}) } @@ -405,9 +484,12 @@ func (c *ChannelGraph) ForEachNodeChannel(tx kvdb.RTx, node route.Vertex, dbCallback := func(tx kvdb.RTx, e *ChannelEdgeInfo, p1, p2 *ChannelEdgePolicy) error { - cachedInPolicy := NewCachedPolicy(p2) - cachedInPolicy.ToNodePubKey = toNodeCallback - cachedInPolicy.ToNodeFeatures = toNodeFeatures + var cachedInPolicy *CachedEdgePolicy + if p2 != nil { + cachedInPolicy = NewCachedPolicy(p2) + cachedInPolicy.ToNodePubKey = toNodeCallback + cachedInPolicy.ToNodeFeatures = toNodeFeatures + } directedChannel := &DirectedChannel{ ChannelID: e.ChannelID, @@ -439,7 +521,6 @@ func (c *ChannelGraph) FetchNodeFeatures( // Fallback that uses the database. targetNode, err := c.FetchLightningNode(node) switch err { - // If the node exists and has features, return them directly. case nil: return targetNode.Features, nil @@ -455,6 +536,71 @@ func (c *ChannelGraph) FetchNodeFeatures( } } +// ForEachNodeCached is similar to ForEachNode, but it utilizes the channel +// graph cache instead. Note that this doesn't return all the information the +// regular ForEachNode method does. +// +// NOTE: The callback contents MUST not be modified. +func (c *ChannelGraph) ForEachNodeCached(cb func(node route.Vertex, + chans map[uint64]*DirectedChannel) error) error { + + if c.graphCache != nil { + return c.graphCache.ForEachNode(cb) + } + + // Otherwise call back to a version that uses the database directly. + // We'll iterate over each node, then the set of channels for each + // node, and construct a similar callback functiopn signature as the + // main funcotin expects. + return c.ForEachNode(func(tx kvdb.RTx, node *LightningNode) error { + channels := make(map[uint64]*DirectedChannel) + + err := node.ForEachChannel(tx, func(tx kvdb.RTx, + e *ChannelEdgeInfo, p1 *ChannelEdgePolicy, + p2 *ChannelEdgePolicy) error { + + toNodeCallback := func() route.Vertex { + return node.PubKeyBytes + } + toNodeFeatures, err := c.FetchNodeFeatures( + node.PubKeyBytes, + ) + if err != nil { + return err + } + + var cachedInPolicy *CachedEdgePolicy + if p2 != nil { + cachedInPolicy := NewCachedPolicy(p2) + cachedInPolicy.ToNodePubKey = toNodeCallback + cachedInPolicy.ToNodeFeatures = toNodeFeatures + } + + directedChannel := &DirectedChannel{ + ChannelID: e.ChannelID, + IsNode1: node.PubKeyBytes == e.NodeKey1Bytes, + OtherNode: e.NodeKey2Bytes, + Capacity: e.Capacity, + OutPolicySet: p1 != nil, + InPolicy: cachedInPolicy, + } + + if node.PubKeyBytes == e.NodeKey2Bytes { + directedChannel.OtherNode = e.NodeKey1Bytes + } + + channels[e.ChannelID] = directedChannel + + return nil + }) + if err != nil { + return err + } + + return cb(node.PubKeyBytes, channels) + }) +} + // DisabledChannelIDs returns the channel ids of disabled channels. // A channel is disabled when two of the associated ChanelEdgePolicies // have their disabled bit on. @@ -507,7 +653,9 @@ func (c *ChannelGraph) DisabledChannelIDs() ([]uint64, error) { // // TODO(roasbeef): add iterator interface to allow for memory efficient graph // traversal when graph gets mega -func (c *ChannelGraph) ForEachNode(cb func(kvdb.RTx, *LightningNode) error) error { // nolint:interfacer +func (c *ChannelGraph) ForEachNode( + cb func(kvdb.RTx, *LightningNode) error) error { + traversal := func(tx kvdb.RTx) error { // First grab the nodes bucket which stores the mapping from // pubKey to node information. @@ -555,7 +703,6 @@ func (c *ChannelGraph) ForEachNodeCacheable(cb func(kvdb.RTx, return ErrGraphNotFound } - cacheableNode := newGraphCacheNode(route.Vertex{}, nil) return nodes.ForEach(func(pubKey, nodeBytes []byte) error { // If this is the source key, then we skip this // iteration as the value for this key is a pubKey @@ -565,8 +712,8 @@ func (c *ChannelGraph) ForEachNodeCacheable(cb func(kvdb.RTx, } nodeReader := bytes.NewReader(nodeBytes) - err := deserializeLightningNodeCacheable( - nodeReader, cacheableNode, + cacheableNode, err := deserializeLightningNodeCacheable( + nodeReader, ) if err != nil { return err @@ -791,7 +938,6 @@ func (c *ChannelGraph) deleteLightningNode(nodes kvdb.RwBucket, } if err := nodes.Delete(compressedPubKey); err != nil { - return err } @@ -909,7 +1055,6 @@ func (c *ChannelGraph) addChannelEdge(tx kvdb.RwTx, edge *ChannelEdgeInfo) error if err != nil { return fmt.Errorf("unable to create shell node "+ "for: %x", edge.NodeKey1Bytes) - } case node1Err != nil: return err @@ -926,7 +1071,6 @@ func (c *ChannelGraph) addChannelEdge(tx kvdb.RwTx, edge *ChannelEdgeInfo) error if err != nil { return fmt.Errorf("unable to create shell node "+ "for: %x", edge.NodeKey2Bytes) - } case node2Err != nil: return err @@ -941,11 +1085,12 @@ func (c *ChannelGraph) addChannelEdge(tx kvdb.RwTx, edge *ChannelEdgeInfo) error // Mark edge policies for both sides as unknown. This is to enable // efficient incoming channel lookup for a node. - for _, key := range []*[33]byte{&edge.NodeKey1Bytes, - &edge.NodeKey2Bytes} { - - err := putChanEdgePolicyUnknown(edges, edge.ChannelID, - key[:]) + keys := []*[33]byte{ + &edge.NodeKey1Bytes, + &edge.NodeKey2Bytes, + } + for _, key := range keys { + err := putChanEdgePolicyUnknown(edges, edge.ChannelID, key[:]) if err != nil { return err } @@ -2283,7 +2428,6 @@ func makeZombiePubkeys(info *ChannelEdgeInfo, e1, e2 *ChannelEdgePolicy) ([33]byte, [33]byte) { switch { - // If we don't have either edge policy, we'll return both pubkeys so // that the channel can be resurrected by either party. case e1 == nil && e2 == nil: @@ -2397,7 +2541,6 @@ func updateEdgePolicy(tx kvdb.RwTx, edge *ChannelEdgePolicy, edges := tx.ReadWriteBucket(edgeBucket) if edges == nil { return false, ErrEdgeNotFound - } edgeIndex := edges.NestedReadWriteBucket(edgeIndexBucket) if edgeIndex == nil { @@ -2518,7 +2661,7 @@ func (l *LightningNode) PubKey() (*btcec.PublicKey, error) { return l.pubKey, nil } - key, err := btcec.ParsePubKey(l.PubKeyBytes[:], btcec.S256()) + key, err := btcec.ParsePubKey(l.PubKeyBytes[:]) if err != nil { return nil, err } @@ -2532,8 +2675,8 @@ func (l *LightningNode) PubKey() (*btcec.PublicKey, error) { // // NOTE: By having this method to access an attribute, we ensure we only need // to fully deserialize the signature if absolutely necessary. -func (l *LightningNode) AuthSig() (*btcec.Signature, error) { - return btcec.ParseSignature(l.AuthSigBytes, btcec.S256()) +func (l *LightningNode) AuthSig() (*ecdsa.Signature, error) { + return ecdsa.ParseSignature(l.AuthSigBytes) } // AddPubKey is a setter-link method that can be used to swap out the public @@ -2670,8 +2813,6 @@ func (c *ChannelGraph) FetchLightningNode(nodePub route.Vertex) ( type graphCacheNode struct { pubKeyBytes route.Vertex features *lnwire.FeatureVector - - nodeScratch [8]byte } // newGraphCacheNode returns a new cache optimized node. @@ -2964,7 +3105,7 @@ func (c *ChannelEdgeInfo) NodeKey1() (*btcec.PublicKey, error) { return c.nodeKey1, nil } - key, err := btcec.ParsePubKey(c.NodeKey1Bytes[:], btcec.S256()) + key, err := btcec.ParsePubKey(c.NodeKey1Bytes[:]) if err != nil { return nil, err } @@ -2986,7 +3127,7 @@ func (c *ChannelEdgeInfo) NodeKey2() (*btcec.PublicKey, error) { return c.nodeKey2, nil } - key, err := btcec.ParsePubKey(c.NodeKey2Bytes[:], btcec.S256()) + key, err := btcec.ParsePubKey(c.NodeKey2Bytes[:]) if err != nil { return nil, err } @@ -3006,7 +3147,7 @@ func (c *ChannelEdgeInfo) BitcoinKey1() (*btcec.PublicKey, error) { return c.bitcoinKey1, nil } - key, err := btcec.ParsePubKey(c.BitcoinKey1Bytes[:], btcec.S256()) + key, err := btcec.ParsePubKey(c.BitcoinKey1Bytes[:]) if err != nil { return nil, err } @@ -3026,7 +3167,7 @@ func (c *ChannelEdgeInfo) BitcoinKey2() (*btcec.PublicKey, error) { return c.bitcoinKey2, nil } - key, err := btcec.ParsePubKey(c.BitcoinKey2Bytes[:], btcec.S256()) + key, err := btcec.ParsePubKey(c.BitcoinKey2Bytes[:]) if err != nil { return nil, err } @@ -3054,7 +3195,8 @@ func (c *ChannelEdgeInfo) OtherNodeKeyBytes(thisNodeKey []byte) ( // the target node in the channel. This is useful when one knows the pubkey of // one of the nodes, and wishes to obtain the full LightningNode for the other // end of the channel. -func (c *ChannelEdgeInfo) FetchOtherNode(tx kvdb.RTx, thisNodeKey []byte) (*LightningNode, error) { +func (c *ChannelEdgeInfo) FetchOtherNode(tx kvdb.RTx, + thisNodeKey []byte) (*LightningNode, error) { // Ensure that the node passed in is actually a member of the channel. var targetNodeBytes [33]byte @@ -3108,28 +3250,28 @@ func (c *ChannelEdgeInfo) FetchOtherNode(tx kvdb.RTx, thisNodeKey []byte) (*Ligh // features. type ChannelAuthProof struct { // nodeSig1 is a cached instance of the first node signature. - nodeSig1 *btcec.Signature + nodeSig1 *ecdsa.Signature // NodeSig1Bytes are the raw bytes of the first node signature encoded // in DER format. NodeSig1Bytes []byte // nodeSig2 is a cached instance of the second node signature. - nodeSig2 *btcec.Signature + nodeSig2 *ecdsa.Signature // NodeSig2Bytes are the raw bytes of the second node signature // encoded in DER format. NodeSig2Bytes []byte // bitcoinSig1 is a cached instance of the first bitcoin signature. - bitcoinSig1 *btcec.Signature + bitcoinSig1 *ecdsa.Signature // BitcoinSig1Bytes are the raw bytes of the first bitcoin signature // encoded in DER format. BitcoinSig1Bytes []byte // bitcoinSig2 is a cached instance of the second bitcoin signature. - bitcoinSig2 *btcec.Signature + bitcoinSig2 *ecdsa.Signature // BitcoinSig2Bytes are the raw bytes of the second bitcoin signature // encoded in DER format. @@ -3142,12 +3284,12 @@ type ChannelAuthProof struct { // // NOTE: By having this method to access an attribute, we ensure we only need // to fully deserialize the signature if absolutely necessary. -func (c *ChannelAuthProof) Node1Sig() (*btcec.Signature, error) { +func (c *ChannelAuthProof) Node1Sig() (*ecdsa.Signature, error) { if c.nodeSig1 != nil { return c.nodeSig1, nil } - sig, err := btcec.ParseSignature(c.NodeSig1Bytes, btcec.S256()) + sig, err := ecdsa.ParseSignature(c.NodeSig1Bytes) if err != nil { return nil, err } @@ -3163,12 +3305,12 @@ func (c *ChannelAuthProof) Node1Sig() (*btcec.Signature, error) { // // NOTE: By having this method to access an attribute, we ensure we only need // to fully deserialize the signature if absolutely necessary. -func (c *ChannelAuthProof) Node2Sig() (*btcec.Signature, error) { +func (c *ChannelAuthProof) Node2Sig() (*ecdsa.Signature, error) { if c.nodeSig2 != nil { return c.nodeSig2, nil } - sig, err := btcec.ParseSignature(c.NodeSig2Bytes, btcec.S256()) + sig, err := ecdsa.ParseSignature(c.NodeSig2Bytes) if err != nil { return nil, err } @@ -3183,12 +3325,12 @@ func (c *ChannelAuthProof) Node2Sig() (*btcec.Signature, error) { // // NOTE: By having this method to access an attribute, we ensure we only need // to fully deserialize the signature if absolutely necessary. -func (c *ChannelAuthProof) BitcoinSig1() (*btcec.Signature, error) { +func (c *ChannelAuthProof) BitcoinSig1() (*ecdsa.Signature, error) { if c.bitcoinSig1 != nil { return c.bitcoinSig1, nil } - sig, err := btcec.ParseSignature(c.BitcoinSig1Bytes, btcec.S256()) + sig, err := ecdsa.ParseSignature(c.BitcoinSig1Bytes) if err != nil { return nil, err } @@ -3203,12 +3345,12 @@ func (c *ChannelAuthProof) BitcoinSig1() (*btcec.Signature, error) { // // NOTE: By having this method to access an attribute, we ensure we only need // to fully deserialize the signature if absolutely necessary. -func (c *ChannelAuthProof) BitcoinSig2() (*btcec.Signature, error) { +func (c *ChannelAuthProof) BitcoinSig2() (*ecdsa.Signature, error) { if c.bitcoinSig2 != nil { return c.bitcoinSig2, nil } - sig, err := btcec.ParseSignature(c.BitcoinSig2Bytes, btcec.S256()) + sig, err := ecdsa.ParseSignature(c.BitcoinSig2Bytes) if err != nil { return nil, err } @@ -3240,7 +3382,7 @@ type ChannelEdgePolicy struct { SigBytes []byte // sig is a cached fully parsed signature. - sig *btcec.Signature + sig *ecdsa.Signature // ChannelID is the unique channel ID for the channel. The first 3 // bytes are the block height, the next 3 the index within the block, @@ -3300,12 +3442,12 @@ type ChannelEdgePolicy struct { // // NOTE: By having this method to access an attribute, we ensure we only need // to fully deserialize the signature if absolutely necessary. -func (c *ChannelEdgePolicy) Signature() (*btcec.Signature, error) { +func (c *ChannelEdgePolicy) Signature() (*ecdsa.Signature, error) { if c.sig != nil { return c.sig, nil } - sig, err := btcec.ParseSignature(c.SigBytes, btcec.S256()) + sig, err := ecdsa.ParseSignature(c.SigBytes) if err != nil { return nil, err } @@ -4019,51 +4161,60 @@ func fetchLightningNode(nodeBucket kvdb.RBucket, return deserializeLightningNode(nodeReader) } -func deserializeLightningNodeCacheable(r io.Reader, node *graphCacheNode) error { +func deserializeLightningNodeCacheable(r io.Reader) (*graphCacheNode, error) { // Always populate a feature vector, even if we don't have a node // announcement and short circuit below. - node.features = lnwire.EmptyFeatureVector() + node := newGraphCacheNode( + route.Vertex{}, + lnwire.EmptyFeatureVector(), + ) + + var nodeScratch [8]byte // Skip ahead: // - LastUpdate (8 bytes) - if _, err := r.Read(node.nodeScratch[:]); err != nil { - return err + if _, err := r.Read(nodeScratch[:]); err != nil { + return nil, err } if _, err := io.ReadFull(r, node.pubKeyBytes[:]); err != nil { - return err + return nil, err } // Read the node announcement flag. - if _, err := r.Read(node.nodeScratch[:2]); err != nil { - return err + if _, err := r.Read(nodeScratch[:2]); err != nil { + return nil, err } - hasNodeAnn := byteOrder.Uint16(node.nodeScratch[:2]) + hasNodeAnn := byteOrder.Uint16(nodeScratch[:2]) // The rest of the data is optional, and will only be there if we got a // node announcement for this node. if hasNodeAnn == 0 { - return nil + return node, nil } // We did get a node announcement for this node, so we'll have the rest // of the data available. var rgb uint8 if err := binary.Read(r, byteOrder, &rgb); err != nil { - return err + return nil, err } if err := binary.Read(r, byteOrder, &rgb); err != nil { - return err + return nil, err } if err := binary.Read(r, byteOrder, &rgb); err != nil { - return err + return nil, err } if _, err := wire.ReadVarString(r, 0); err != nil { - return err + return nil, err } - return node.features.Decode(r) + if err := node.features.Decode(r); err != nil { + return nil, err + } + + return node, nil } func deserializeLightningNode(r io.Reader) (LightningNode, error) { @@ -4581,6 +4732,27 @@ func serializeChanEdgePolicy(w io.Writer, edge *ChannelEdgePolicy, func deserializeChanEdgePolicy(r io.Reader, nodes kvdb.RBucket) (*ChannelEdgePolicy, error) { + // Deserialize the policy. Note that in case an optional field is not + // found, both an error and a populated policy object are returned. + edge, deserializeErr := deserializeChanEdgePolicyRaw(r) + if deserializeErr != nil && + deserializeErr != ErrEdgePolicyOptionalFieldNotFound { + + return nil, deserializeErr + } + + // Populate full LightningNode struct. + pub := edge.Node.PubKeyBytes[:] + node, err := fetchLightningNode(nodes, pub) + if err != nil { + return nil, fmt.Errorf("unable to fetch node: %x, %v", pub, err) + } + edge.Node = &node + + return edge, deserializeErr +} + +func deserializeChanEdgePolicyRaw(r io.Reader) (*ChannelEdgePolicy, error) { edge := &ChannelEdgePolicy{} var err error @@ -4630,13 +4802,9 @@ func deserializeChanEdgePolicy(r io.Reader, if _, err := r.Read(pub[:]); err != nil { return nil, err } - - node, err := fetchLightningNode(nodes, pub[:]) - if err != nil { - return nil, fmt.Errorf("unable to fetch node: %x, %v", - pub[:], err) + edge.Node = &LightningNode{ + PubKeyBytes: pub, } - edge.Node = &node // We'll try and see if there are any opaque bytes left, if not, then // we'll ignore the EOF error and return the edge as is. diff --git a/channeldb/graph_cache.go b/channeldb/graph_cache.go index 891004bfc6..1aae21d06d 100644 --- a/channeldb/graph_cache.go +++ b/channeldb/graph_cache.go @@ -4,8 +4,7 @@ import ( "fmt" "sync" - "github.com/btcsuite/btcutil" - + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" @@ -205,9 +204,8 @@ func (c *GraphCache) Stats() string { numChannels) } -// AddNode adds a graph node, including all the (directed) channels of that -// node. -func (c *GraphCache) AddNode(tx kvdb.RTx, node GraphCacheNode) error { +// AddNodeFeatures adds a graph node and its features to the cache. +func (c *GraphCache) AddNodeFeatures(node GraphCacheNode) { nodePubKey := node.PubKey() // Only hold the lock for a short time. The `ForEachChannel()` below is @@ -217,6 +215,12 @@ func (c *GraphCache) AddNode(tx kvdb.RTx, node GraphCacheNode) error { c.mtx.Lock() c.nodeFeatures[nodePubKey] = node.Features() c.mtx.Unlock() +} + +// AddNode adds a graph node, including all the (directed) channels of that +// node. +func (c *GraphCache) AddNode(tx kvdb.RTx, node GraphCacheNode) error { + c.AddNodeFeatures(node) return node.ForEachChannel( tx, func(tx kvdb.RTx, info *ChannelEdgeInfo, @@ -401,10 +405,9 @@ func (c *GraphCache) UpdateChannel(info *ChannelEdgeInfo) { } } -// ForEachChannel invokes the given callback for each channel of the given node. -func (c *GraphCache) ForEachChannel(node route.Vertex, - cb func(channel *DirectedChannel) error) error { - +// getChannels returns a copy of the passed node's channels or nil if there +// isn't any. +func (c *GraphCache) getChannels(node route.Vertex) []*DirectedChannel { c.mtx.RLock() defer c.mtx.RUnlock() @@ -428,6 +431,8 @@ func (c *GraphCache) ForEachChannel(node route.Vertex, return node } + i := 0 + channelsCopy := make([]*DirectedChannel, len(channels)) for _, channel := range channels { // We need to copy the channel and policy to avoid it being // updated in the cache if the path finding algorithm sets @@ -439,7 +444,51 @@ func (c *GraphCache) ForEachChannel(node route.Vertex, channelCopy.InPolicy.ToNodeFeatures = features } - if err := cb(channelCopy); err != nil { + channelsCopy[i] = channelCopy + i++ + } + + return channelsCopy +} + +// ForEachChannel invokes the given callback for each channel of the given node. +func (c *GraphCache) ForEachChannel(node route.Vertex, + cb func(channel *DirectedChannel) error) error { + + // Obtain a copy of the node's channels. We need do this in order to + // avoid deadlocks caused by interaction with the graph cache, channel + // state and the graph database from multiple goroutines. This snapshot + // is only used for path finding where being stale is acceptable since + // the real world graph and our representation may always become + // slightly out of sync for a short time and the actual channel state + // is stored separately. + channels := c.getChannels(node) + for _, channel := range channels { + if err := cb(channel); err != nil { + return err + } + } + + return nil +} + +// ForEachNode iterates over the adjacency list of the graph, executing the +// call back for each node and the set of channels that emanate from the given +// node. +// +// NOTE: This method should be considered _read only_, the channels or nodes +// passed in MUST NOT be modified. +func (c *GraphCache) ForEachNode(cb func(node route.Vertex, + channels map[uint64]*DirectedChannel) error) error { + + c.mtx.RLock() + defer c.mtx.RUnlock() + + for node, channels := range c.nodeChannels { + // We don't make a copy here since this is a read-only RPC + // call. We also don't need the node features either for this + // call. + if err := cb(node, channels); err != nil { return err } } diff --git a/channeldb/graph_cache_test.go b/channeldb/graph_cache_test.go index 09cfbf2373..b408ec36d8 100644 --- a/channeldb/graph_cache_test.go +++ b/channeldb/graph_cache_test.go @@ -60,6 +60,8 @@ func (n *node) ForEachChannel(tx kvdb.RTx, // TestGraphCacheAddNode tests that a channel going from node A to node B can be // cached correctly, independent of the direction we add the channel as. func TestGraphCacheAddNode(t *testing.T) { + t.Parallel() + runTest := func(nodeA, nodeB route.Vertex) { t.Helper() @@ -118,6 +120,24 @@ func TestGraphCacheAddNode(t *testing.T) { require.Equal(t, inPolicy1 != nil, toChannels[0].OutPolicySet) assertCachedPolicyEqual(t, outPolicy1, toChannels[0].InPolicy) + + // Now that we've inserted two nodes into the graph, check that + // we'll recover the same set of channels during ForEachNode. + nodes := make(map[route.Vertex]struct{}) + chans := make(map[uint64]struct{}) + _ = cache.ForEachNode(func(node route.Vertex, + edges map[uint64]*DirectedChannel) error { + + nodes[node] = struct{}{} + for chanID := range edges { + chans[chanID] = struct{}{} + } + + return nil + }) + + require.Len(t, nodes, 2) + require.Len(t, chans, 1) } runTest(pubKey1, pubKey2) diff --git a/channeldb/graph_test.go b/channeldb/graph_test.go index f66b57958f..72032ebfd3 100644 --- a/channeldb/graph_test.go +++ b/channeldb/graph_test.go @@ -3,12 +3,12 @@ package channeldb import ( "bytes" "crypto/sha256" + "encoding/hex" "errors" "fmt" "image/color" "io/ioutil" "math" - "math/big" prand "math/rand" "net" "os" @@ -18,10 +18,11 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lnwire" @@ -36,12 +37,13 @@ var ( "[2001:db8:85a3:0:0:8a2e:370:7334]:80") testAddrs = []net.Addr{testAddr, anotherAddr} - testSig = &btcec.Signature{ - R: new(big.Int), - S: new(big.Int), - } - _, _ = testSig.R.SetString("63724406601629180062774974542967536251589935445068131219452686511677818569431", 10) - _, _ = testSig.S.SetString("18801056069249825825291287104931333862866033135609736119018462340006816851118", 10) + testRBytes, _ = hex.DecodeString("8ce2bc69281ce27da07e6683571319d18e949ddfa2965fb6caa1bf0314f882d7") + testSBytes, _ = hex.DecodeString("299105481d63e0f4bc2a88121167221b6700d72a0ead154c03be696a292d24ae") + testRScalar = new(btcec.ModNScalar) + testSScalar = new(btcec.ModNScalar) + _ = testRScalar.SetByteSlice(testRBytes) + _ = testSScalar.SetByteSlice(testSBytes) + testSig = ecdsa.NewSignature(testRScalar, testSScalar) testFeatures = lnwire.NewFeatureVector( lnwire.NewRawFeatureVector(lnwire.GossipQueriesRequired), @@ -77,7 +79,7 @@ func MakeTestGraph(modifiers ...OptionModifier) (*ChannelGraph, func(), error) { graph, err := NewChannelGraph( backend, opts.RejectCacheSize, opts.ChannelCacheSize, opts.BatchCommitInterval, opts.PreAllocCacheNumNodes, - true, + true, false, ) if err != nil { backendCleanup() @@ -114,7 +116,7 @@ func createLightningNode(db kvdb.Backend, priv *btcec.PrivateKey) (*LightningNod } func createTestVertex(db kvdb.Backend) (*LightningNode, error) { - priv, err := btcec.NewPrivateKey(btcec.S256()) + priv, err := btcec.NewPrivateKey() if err != nil { return nil, err } @@ -1103,6 +1105,34 @@ func TestGraphTraversal(t *testing.T) { const numChannels = 5 chanIndex, nodeList := fillTestGraph(t, graph, numNodes, numChannels) + // Make an index of the node list for easy look up below. + nodeIndex := make(map[route.Vertex]struct{}) + for _, node := range nodeList { + nodeIndex[node.PubKeyBytes] = struct{}{} + } + + // If we turn the channel graph cache _off_, then iterate through the + // set of channels (to force the fall back), we should find all the + // channel as well as the nodes included. + graph.graphCache = nil + err = graph.ForEachNodeCached(func(node route.Vertex, + chans map[uint64]*DirectedChannel) error { + + if _, ok := nodeIndex[node]; !ok { + return fmt.Errorf("node %x not found in graph", node) + } + + for chanID := range chans { + if _, ok := chanIndex[chanID]; !ok { + return fmt.Errorf("chan %v not found in "+ + "graph", chanID) + } + } + + return nil + }) + require.NoError(t, err) + // Iterate through all the known channels within the graph DB, once // again if the map is empty that indicates that all edges have // properly been reached. @@ -1179,11 +1209,22 @@ func TestGraphTraversalCacheable(t *testing.T) { // Iterate through all the known channels within the graph DB by // iterating over each node, once again if the map is empty that // indicates that all edges have properly been reached. + var nodes []GraphCacheNode err = graph.ForEachNodeCacheable( func(tx kvdb.RTx, node GraphCacheNode) error { delete(nodeMap, node.PubKey()) - return node.ForEachChannel( + nodes = append(nodes, node) + + return nil + }, + ) + require.NoError(t, err) + require.Len(t, nodeMap, 0) + + err = graph.db.View(func(tx kvdb.RTx) error { + for _, node := range nodes { + err := node.ForEachChannel( tx, func(tx kvdb.RTx, info *ChannelEdgeInfo, policy *ChannelEdgePolicy, policy2 *ChannelEdgePolicy) error { @@ -1192,10 +1233,15 @@ func TestGraphTraversalCacheable(t *testing.T) { return nil }, ) - }, - ) + if err != nil { + return err + } + } + + return nil + }, func() {}) + require.NoError(t, err) - require.Len(t, nodeMap, 0) require.Len(t, chanIndex, 0) } @@ -1235,6 +1281,7 @@ func TestGraphCacheTraversal(t *testing.T) { if !bytes.Equal( inPolicyNodeKey[:], node.PubKeyBytes[:], ) { + return fmt.Errorf("wrong outgoing edge") } @@ -3446,15 +3493,12 @@ func TestLightningNodeSigVerification(t *testing.T) { } // Create private key and sign the data with it. - priv, err := btcec.NewPrivateKey(btcec.S256()) + priv, err := btcec.NewPrivateKey() if err != nil { t.Fatalf("unable to crete priv key: %v", err) } - sign, err := priv.Sign(data[:]) - if err != nil { - t.Fatalf("unable to sign: %v", err) - } + sign := ecdsa.Sign(priv, data[:]) // Sanity check that the signature checks out. if !sign.Verify(data[:], priv.PubKey()) { @@ -3667,9 +3711,20 @@ func BenchmarkForEachChannel(b *testing.B) { totalCapacity btcutil.Amount maxHTLCs lnwire.MilliSatoshi ) - err := graph.ForEachNodeCacheable( - func(tx kvdb.RTx, n GraphCacheNode) error { - return n.ForEachChannel( + + var nodes []GraphCacheNode + err = graph.ForEachNodeCacheable( + func(tx kvdb.RTx, node GraphCacheNode) error { + nodes = append(nodes, node) + + return nil + }, + ) + require.NoError(b, err) + + err = graph.db.View(func(tx kvdb.RTx) error { + for _, n := range nodes { + err := n.ForEachChannel( tx, func(tx kvdb.RTx, info *ChannelEdgeInfo, policy *ChannelEdgePolicy, @@ -3687,8 +3742,101 @@ func BenchmarkForEachChannel(b *testing.B) { return nil }, ) - }, - ) + if err != nil { + return err + } + } + + return nil + }, func() {}) require.NoError(b, err) } } + +// TestGraphCacheForEachNodeChannel tests that the ForEachNodeChannel method +// works as expected, and is able to handle nil self edges. +func TestGraphCacheForEachNodeChannel(t *testing.T) { + graph, cleanUp, err := MakeTestGraph() + defer cleanUp() + require.NoError(t, err) + + // Unset the channel graph cache to simulate the user running with the + // option turned off. + graph.graphCache = nil + + node1, err := createTestVertex(graph.db) + require.Nil(t, err) + err = graph.AddLightningNode(node1) + require.Nil(t, err) + node2, err := createTestVertex(graph.db) + require.Nil(t, err) + err = graph.AddLightningNode(node2) + require.Nil(t, err) + + // Create an edge and add it to the db. + edgeInfo, _, _ := createChannelEdge(graph.db, node1, node2) + + // Add the channel, but only insert a single edge into the graph. + require.NoError(t, graph.AddChannelEdge(edgeInfo)) + + // We should be able to accumulate the single channel added, even + // though we have a nil edge policy here. + var numChans int + err = graph.ForEachNodeChannel(nil, node1.PubKeyBytes, + func(channel *DirectedChannel) error { + numChans++ + return nil + }) + require.NoError(t, err) + + require.Equal(t, numChans, 1) +} + +// TestGraphLoading asserts that the cache is properly reconstructed after a +// restart. +func TestGraphLoading(t *testing.T) { + // First, create a temporary directory to be used for the duration of + // this test. + tempDirName, err := ioutil.TempDir("", "channelgraph") + require.NoError(t, err) + defer os.RemoveAll(tempDirName) + + // Next, create the graph for the first time. + backend, backendCleanup, err := kvdb.GetTestBackend(tempDirName, "cgr") + require.NoError(t, err) + defer backend.Close() + defer backendCleanup() + + opts := DefaultOptions() + graph, err := NewChannelGraph( + backend, opts.RejectCacheSize, opts.ChannelCacheSize, + opts.BatchCommitInterval, opts.PreAllocCacheNumNodes, + true, false, + ) + require.NoError(t, err) + + // Populate the graph with test data. + const numNodes = 100 + const numChannels = 4 + _, _ = fillTestGraph(t, graph, numNodes, numChannels) + + // Recreate the graph. This should cause the graph cache to be + // populated. + graphReloaded, err := NewChannelGraph( + backend, opts.RejectCacheSize, opts.ChannelCacheSize, + opts.BatchCommitInterval, opts.PreAllocCacheNumNodes, + true, false, + ) + require.NoError(t, err) + + // Assert that the cache content is identical. + require.Equal( + t, graph.graphCache.nodeChannels, + graphReloaded.graphCache.nodeChannels, + ) + + require.Equal( + t, graph.graphCache.nodeFeatures, + graphReloaded.graphCache.nodeFeatures, + ) +} diff --git a/channeldb/invoice_test.go b/channeldb/invoice_test.go index b4535f79bd..c235899cec 100644 --- a/channeldb/invoice_test.go +++ b/channeldb/invoice_test.go @@ -1010,7 +1010,6 @@ func TestScanInvoices(t *testing.T) { invoices = make(map[lntypes.Hash]*Invoice) callCount = 0 resetCount++ - } scanFunc := func(paymentHash lntypes.Hash, invoice *Invoice) error { @@ -1661,7 +1660,6 @@ func TestHTLCSet(t *testing.T) { ampData = &InvoiceHtlcAMPData{ Record: *record.NewAMP([32]byte{0}, *h.setID, 0), } - } // Add the HTLC to the invoice's set of HTLCs. @@ -2045,7 +2043,7 @@ func TestUnexpectedInvoicePreimage(t *testing.T) { getUpdateInvoice(invoice.Terms.Value), ) - //Assert that we get ErrUnexpectedInvoicePreimage. + // Assert that we get ErrUnexpectedInvoicePreimage. require.Error(t, ErrUnexpectedInvoicePreimage, err) } diff --git a/channeldb/invoices.go b/channeldb/invoices.go index 709c0d77aa..cbda88d63c 100644 --- a/channeldb/invoices.go +++ b/channeldb/invoices.go @@ -422,7 +422,7 @@ func (c ContractState) String() string { return "Unknown" } -// IsFinal returns a boolean indicating whether an invoice state is final +// IsFinal returns a boolean indicating whether an invoice state is final. func (c ContractState) IsFinal() bool { return c == ContractSettled || c == ContractCanceled } @@ -895,7 +895,7 @@ func validateInvoice(i *Invoice, paymentHash lntypes.Hash) error { return nil } -// IsPending returns ture if the invoice is in ContractOpen state. +// IsPending returns true if the invoice is in ContractOpen state. func (i *Invoice) IsPending() bool { return i.State == ContractOpen || i.State == ContractAccepted } @@ -1156,7 +1156,6 @@ func fetchInvoiceNumByRef(invoiceIndex, payAddrIndex, setIDIndex kvdb.RBucket, invoiceNumByHash := getInvoiceNumByHash() invoiceNumByAddr := getInvoiceNumByAddr() switch { - // If payment address and payment hash both reference an existing // invoice, ensure they reference the _same_ invoice. case invoiceNumByAddr != nil && invoiceNumByHash != nil: @@ -1189,7 +1188,7 @@ func fetchInvoiceNumByRef(invoiceIndex, payAddrIndex, setIDIndex kvdb.RBucket, } } -// ScanInvoices scans trough all invoices and calls the passed scanFunc for +// ScanInvoices scans through all invoices and calls the passed scanFunc for // for each invoice with its respective payment hash. Additionally a reset() // closure is passed which is used to reset/initialize partial results and also // to signal if the kvdb.View transaction has been retried. @@ -1464,7 +1463,6 @@ func (d *DB) InvoicesSettledSince(sinceSettleIndex uint64) ([]Invoice, error) { seqNo, indexValue := invoiceCursor.Next() for ; seqNo != nil && bytes.Compare(seqNo, startIndex[:]) > 0; seqNo, indexValue = invoiceCursor.Next() { - // Depending on the length of the index value, this may // or may not be an AMP invoice, so we'll extract the // invoice value into two components: the invoice num, @@ -2307,7 +2305,9 @@ func ampStateDecoder(r io.Reader, val interface{}, buf *[8]byte, l uint64) error return nil } - return tlv.NewTypeForEncodingErr(val, "channeldb.AMPInvoiceState") + return tlv.NewTypeForDecodingErr( + val, "channeldb.AMPInvoiceState", l, l, + ) } // deserializeHtlcs reads a list of invoice htlcs from a reader and returns it @@ -2814,7 +2814,6 @@ func (d *DB) updateInvoice(hash *lntypes.Hash, refSetID *SetID, invoices, if setID != nil && update.State != nil { preimage, ok := update.State.HTLCPreimages[key] switch { - // If we don't already have a preimage for this HTLC, we // can set it now. case ok && htlc.AMP.Preimage == nil: @@ -2890,7 +2889,6 @@ func (d *DB) updateInvoice(hash *lntypes.Hash, refSetID *SetID, invoices, if !invoiceIsAMP { invoice.AmtPaid = amtPaid } else { - invoice.AmtPaid += amtPaid } @@ -2941,7 +2939,6 @@ func updateInvoiceState(invoice *Invoice, hash *lntypes.Hash, } switch invoice.State { - // Once a contract is accepted, we can only transition to settled or // canceled. Forbid transitioning back into this state. Otherwise this // state is identical to ContractOpen, so we fallthrough to apply the @@ -2979,7 +2976,6 @@ func updateInvoiceState(invoice *Invoice, hash *lntypes.Hash, } switch { - // If an invoice-level preimage was supplied, but the InvoiceRef // doesn't specify a hash (e.g. AMP invoices) we fail. case update.Preimage != nil && hash == nil: @@ -3020,7 +3016,7 @@ func updateInvoiceState(invoice *Invoice, hash *lntypes.Hash, } } -// cancelSingleHtlc validates cancelation of a single htlc and update its state. +// cancelSingleHtlc validates cancellation of a single htlc and update its state. func cancelSingleHtlc(resolveTime time.Time, htlc *InvoiceHTLC, invState ContractState) error { @@ -3063,7 +3059,6 @@ func updateHtlc(resolveTime time.Time, htlc *InvoiceHTLC, // the invoice level. For AMP HTLCs, verify that the // per-HTLC preimage-hash pair is valid. switch { - // Non-AMP HTLCs can be settle immediately since we // already know the preimage is valid due to checks at // the invoice level. @@ -3112,7 +3107,6 @@ func updateHtlc(resolveTime time.Time, htlc *InvoiceHTLC, } switch invState { - case ContractCanceled: if htlc.State == HtlcStateAccepted { htlc.State = HtlcStateCanceled @@ -3369,7 +3363,6 @@ func (d *DB) DeleteInvoice(invoicesToDelete []InvoiceDeleteRef) error { if err != nil { return err } - } // In addition to deleting the main invoice state, if diff --git a/channeldb/migration/lnwire21/accept_channel.go b/channeldb/migration/lnwire21/accept_channel.go index da9daa69b3..6069d3d001 100644 --- a/channeldb/migration/lnwire21/accept_channel.go +++ b/channeldb/migration/lnwire21/accept_channel.go @@ -3,8 +3,8 @@ package lnwire import ( "io" - "github.com/btcsuite/btcd/btcec" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" ) // AcceptChannel is the message Bob sends to Alice after she initiates the diff --git a/channeldb/migration/lnwire21/channel_reestablish.go b/channeldb/migration/lnwire21/channel_reestablish.go index 6fa8f8ac69..37c953adbe 100644 --- a/channeldb/migration/lnwire21/channel_reestablish.go +++ b/channeldb/migration/lnwire21/channel_reestablish.go @@ -3,7 +3,7 @@ package lnwire import ( "io" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" ) // ChannelReestablish is a message sent between peers that have an existing diff --git a/channeldb/migration/lnwire21/closing_signed.go b/channeldb/migration/lnwire21/closing_signed.go index 91b90646a0..b4838854ee 100644 --- a/channeldb/migration/lnwire21/closing_signed.go +++ b/channeldb/migration/lnwire21/closing_signed.go @@ -3,7 +3,7 @@ package lnwire import ( "io" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) // ClosingSigned is sent by both parties to a channel once the channel is clear diff --git a/channeldb/migration/lnwire21/funding_locked.go b/channeldb/migration/lnwire21/funding_locked.go index c441b0be62..83c22578cf 100644 --- a/channeldb/migration/lnwire21/funding_locked.go +++ b/channeldb/migration/lnwire21/funding_locked.go @@ -3,7 +3,7 @@ package lnwire import ( "io" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" ) // FundingLocked is the message that both parties to a new channel creation diff --git a/channeldb/migration/lnwire21/gossip_timestamp_range.go b/channeldb/migration/lnwire21/gossip_timestamp_range.go index 3c28cd056c..039257c269 100644 --- a/channeldb/migration/lnwire21/gossip_timestamp_range.go +++ b/channeldb/migration/lnwire21/gossip_timestamp_range.go @@ -75,6 +75,6 @@ func (g *GossipTimestampRange) MsgType() MessageType { func (g *GossipTimestampRange) MaxPayloadLength(uint32) uint32 { // 32 + 4 + 4 // - // TODO(roasbeef): update to 8 byte timestmaps? + // TODO(roasbeef): update to 8 byte timestamps? return 40 } diff --git a/channeldb/migration/lnwire21/lnwire.go b/channeldb/migration/lnwire21/lnwire.go index ca0e449e5a..a88c5e50b5 100644 --- a/channeldb/migration/lnwire21/lnwire.go +++ b/channeldb/migration/lnwire21/lnwire.go @@ -7,13 +7,12 @@ import ( "image/color" "io" "math" - "net" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/tor" ) @@ -530,7 +529,7 @@ func ReadElement(r io.Reader, element interface{}) error { return err } - pubKey, err := btcec.ParsePubKey(b[:], btcec.S256()) + pubKey, err := btcec.ParsePubKey(b[:]) if err != nil { return err } diff --git a/channeldb/migration/lnwire21/message.go b/channeldb/migration/lnwire21/message.go index b5c27339e9..cf88e48a6f 100644 --- a/channeldb/migration/lnwire21/message.go +++ b/channeldb/migration/lnwire21/message.go @@ -1,7 +1,7 @@ // Copyright (c) 2013-2017 The btcsuite developers // Copyright (c) 2015-2016 The Decred developers // code derived from https://github .com/btcsuite/btcd/blob/master/wire/message.go -// Copyright (C) 2015-2017 The Lightning Network Developers +// Copyright (C) 2015-2022 The Lightning Network Developers package lnwire diff --git a/channeldb/migration/lnwire21/msat.go b/channeldb/migration/lnwire21/msat.go index d3789dfa31..7473d72c82 100644 --- a/channeldb/migration/lnwire21/msat.go +++ b/channeldb/migration/lnwire21/msat.go @@ -3,7 +3,7 @@ package lnwire import ( "fmt" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) const ( diff --git a/channeldb/migration/lnwire21/netaddress.go b/channeldb/migration/lnwire21/netaddress.go index f31ac1f92a..dd5a7c57b8 100644 --- a/channeldb/migration/lnwire21/netaddress.go +++ b/channeldb/migration/lnwire21/netaddress.go @@ -4,7 +4,7 @@ import ( "fmt" "net" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/wire" ) diff --git a/channeldb/migration/lnwire21/onion_error.go b/channeldb/migration/lnwire21/onion_error.go index 35555e266d..362ab48b05 100644 --- a/channeldb/migration/lnwire21/onion_error.go +++ b/channeldb/migration/lnwire21/onion_error.go @@ -2,13 +2,12 @@ package lnwire import ( "bufio" + "bytes" "crypto/sha256" "encoding/binary" "fmt" "io" - "bytes" - "github.com/davecgh/go-spew/spew" "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/tlv" diff --git a/channeldb/migration/lnwire21/open_channel.go b/channeldb/migration/lnwire21/open_channel.go index a165ef75e8..36747894e9 100644 --- a/channeldb/migration/lnwire21/open_channel.go +++ b/channeldb/migration/lnwire21/open_channel.go @@ -3,9 +3,9 @@ package lnwire import ( "io" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcutil" ) // FundingFlag represents the possible bit mask values for the ChannelFlags diff --git a/channeldb/migration/lnwire21/revoke_and_ack.go b/channeldb/migration/lnwire21/revoke_and_ack.go index 0cfa2bc231..1cb554cfbe 100644 --- a/channeldb/migration/lnwire21/revoke_and_ack.go +++ b/channeldb/migration/lnwire21/revoke_and_ack.go @@ -3,7 +3,7 @@ package lnwire import ( "io" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" ) // RevokeAndAck is sent by either side once a CommitSig message has been diff --git a/channeldb/migration/lnwire21/signature.go b/channeldb/migration/lnwire21/signature.go index 13a2f25c3a..51ff9e6666 100644 --- a/channeldb/migration/lnwire21/signature.go +++ b/channeldb/migration/lnwire21/signature.go @@ -3,14 +3,14 @@ package lnwire import ( "fmt" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/lightningnetwork/lnd/input" ) // Sig is a fixed-sized ECDSA signature. Unlike Bitcoin, we use fixed sized // signatures on the wire, instead of DER encoded signatures. This type // provides several methods to convert to/from a regular Bitcoin DER encoded -// signature (raw bytes and *btcec.Signature). +// signature (raw bytes and *ecdsa.Signature). type Sig [64]byte // NewSigFromRawSignature returns a Sig from a Bitcoin raw signature encoded in @@ -27,7 +27,7 @@ func NewSigFromRawSignature(sig []byte) (Sig, error) { // which means the length of R is the 4th byte and the length of S // is the second byte after R ends. 0x02 signifies a length-prefixed, // zero-padded, big-endian bigint. 0x30 signifies a DER signature. - // See the Serialize() method for btcec.Signature for details. + // See the Serialize() method for ecdsa.Signature for details. rLen := sig[3] sLen := sig[5+rLen] @@ -64,7 +64,7 @@ func NewSigFromRawSignature(sig []byte) (Sig, error) { } // NewSigFromSignature creates a new signature as used on the wire, from an -// existing btcec.Signature. +// existing ecdsa.Signature. func NewSigFromSignature(e input.Signature) (Sig, error) { if e == nil { return Sig{}, fmt.Errorf("cannot decode empty signature") @@ -74,12 +74,12 @@ func NewSigFromSignature(e input.Signature) (Sig, error) { return NewSigFromRawSignature(e.Serialize()) } -// ToSignature converts the fixed-sized signature to a btcec.Signature objects +// ToSignature converts the fixed-sized signature to a ecdsa.Signature objects // which can be used for signature validation checks. -func (b *Sig) ToSignature() (*btcec.Signature, error) { +func (b *Sig) ToSignature() (*ecdsa.Signature, error) { // Parse the signature with strict checks. sigBytes := b.ToSignatureBytes() - sig, err := btcec.ParseDERSignature(sigBytes, btcec.S256()) + sig, err := ecdsa.ParseDERSignature(sigBytes) if err != nil { return nil, err } diff --git a/channeldb/migration/lnwire21/update_fail_htlc.go b/channeldb/migration/lnwire21/update_fail_htlc.go index 194f2ecd00..0ecb0baec3 100644 --- a/channeldb/migration/lnwire21/update_fail_htlc.go +++ b/channeldb/migration/lnwire21/update_fail_htlc.go @@ -14,7 +14,7 @@ type OpaqueReason []byte // next commitment transaction, with the UpdateFailHTLC propagated backwards in // the route to fully undo the HTLC. type UpdateFailHTLC struct { - // ChanIDPoint is the particular active channel that this + // ChanID is the particular active channel that this // UpdateFailHTLC is bound to. ChanID ChannelID diff --git a/channeldb/migration21/common/enclosed_types.go b/channeldb/migration21/common/enclosed_types.go index 86dc9f3b39..58f5c9cf22 100644 --- a/channeldb/migration21/common/enclosed_types.go +++ b/channeldb/migration21/common/enclosed_types.go @@ -5,10 +5,10 @@ import ( "encoding/binary" "io" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" "github.com/lightningnetwork/lnd/keychain" ) diff --git a/channeldb/migration21/current/current_codec.go b/channeldb/migration21/current/current_codec.go index 0257f40a8f..c78319a4f7 100644 --- a/channeldb/migration21/current/current_codec.go +++ b/channeldb/migration21/current/current_codec.go @@ -6,10 +6,10 @@ import ( "fmt" "io" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" "github.com/lightningnetwork/lnd/channeldb/migration21/common" "github.com/lightningnetwork/lnd/keychain" @@ -317,7 +317,7 @@ func ReadElement(r io.Reader, element interface{}) error { return err } - priv, _ := btcec.PrivKeyFromBytes(btcec.S256(), b[:]) + priv, _ := btcec.PrivKeyFromBytes(b[:]) *e = priv case **btcec.PublicKey: @@ -326,7 +326,7 @@ func ReadElement(r io.Reader, element interface{}) error { return err } - pubKey, err := btcec.ParsePubKey(b[:], btcec.S256()) + pubKey, err := btcec.ParsePubKey(b[:]) if err != nil { return err } diff --git a/channeldb/migration21/current/current_encoding.go b/channeldb/migration21/current/current_encoding.go index 0c62c31184..02c5b20ce7 100644 --- a/channeldb/migration21/current/current_encoding.go +++ b/channeldb/migration21/current/current_encoding.go @@ -280,7 +280,7 @@ func SerializeChannelCloseSummary(w io.Writer, cs *common.ChannelCloseSummary) e // The RemoteNextRevocation field is optional, as it's possible for a // channel to be closed before we learn of the next unrevoked - // revocation point for the remote party. Write a boolen indicating + // revocation point for the remote party. Write a boolean indicating // whether this field is present or not. if err := WriteElements(w, cs.RemoteNextRevocation != nil); err != nil { return err diff --git a/channeldb/migration21/legacy/legacy_codec.go b/channeldb/migration21/legacy/legacy_codec.go index 2bc6a94ffd..8416ff39b6 100644 --- a/channeldb/migration21/legacy/legacy_codec.go +++ b/channeldb/migration21/legacy/legacy_codec.go @@ -5,10 +5,10 @@ import ( "fmt" "io" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" "github.com/lightningnetwork/lnd/channeldb/migration21/common" "github.com/lightningnetwork/lnd/keychain" @@ -276,7 +276,7 @@ func ReadElement(r io.Reader, element interface{}) error { return err } - pubKey, err := btcec.ParsePubKey(b[:], btcec.S256()) + pubKey, err := btcec.ParsePubKey(b[:]) if err != nil { return err } diff --git a/channeldb/migration21/legacy/legacy_decoding.go b/channeldb/migration21/legacy/legacy_decoding.go index 8a8482494f..ea44e18356 100644 --- a/channeldb/migration21/legacy/legacy_decoding.go +++ b/channeldb/migration21/legacy/legacy_decoding.go @@ -257,7 +257,8 @@ func readChanConfig(b io.Reader, c *common.ChannelConfig) error { // nolint: dup ) } -func DeserializeCloseChannelSummary(r io.Reader) (*common.ChannelCloseSummary, error) { // nolint: dupl +func DeserializeCloseChannelSummary( + r io.Reader) (*common.ChannelCloseSummary, error) { // nolint: dupl c := &common.ChannelCloseSummary{} @@ -379,7 +380,7 @@ func SerializeChannelCloseSummary(w io.Writer, cs *common.ChannelCloseSummary) e // The RemoteNextRevocation field is optional, as it's possible for a // channel to be closed before we learn of the next unrevoked - // revocation point for the remote party. Write a boolen indicating + // revocation point for the remote party. Write a boolean indicating // whether this field is present or not. if err := WriteElements(w, cs.RemoteNextRevocation != nil); err != nil { return err diff --git a/channeldb/migration21/migration.go b/channeldb/migration21/migration.go index 2ee286868b..51882c756b 100644 --- a/channeldb/migration21/migration.go +++ b/channeldb/migration21/migration.go @@ -221,7 +221,7 @@ func migrateOpenChanBucket(tx kvdb.RwTx) error { } } - // Remote unsiged updates as well. + // Remote unsigned updates as well. updateBytes = chanBucket.Get(remoteUnsignedLocalUpdatesKey) if updateBytes != nil { legacyUnsignedUpdates, err := legacy.DeserializeLogUpdates( @@ -316,7 +316,7 @@ func migrateForwardingPackages(tx kvdb.RwTx) error { return err } - // Now load all forwading packages using the legacy encoding. + // Now load all forwarding packages using the legacy encoding. var pkgsToMigrate []*common.FwdPkg for _, source := range sources { packager := legacy.NewChannelPackager(source) diff --git a/channeldb/migration21/migration_test.go b/channeldb/migration21/migration_test.go index 9e58989c7d..735e45dc0d 100644 --- a/channeldb/migration21/migration_test.go +++ b/channeldb/migration21/migration_test.go @@ -2,12 +2,13 @@ package migration21 import ( "bytes" + "encoding/hex" "fmt" - "math/big" "reflect" "testing" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/davecgh/go-spew/spew" @@ -27,16 +28,17 @@ var ( 0x1e, 0xb, 0x4c, 0xf9, 0x9e, 0xc5, 0x8c, 0xe9, } - _, pubKey = btcec.PrivKeyFromBytes(btcec.S256(), key[:]) + _, pubKey = btcec.PrivKeyFromBytes(key[:]) wireSig, _ = lnwire.NewSigFromSignature(testSig) - testSig = &btcec.Signature{ - R: new(big.Int), - S: new(big.Int), - } - _, _ = testSig.R.SetString("63724406601629180062774974542967536251589935445068131219452686511677818569431", 10) - _, _ = testSig.S.SetString("18801056069249825825291287104931333862866033135609736119018462340006816851118", 10) + testRBytes, _ = hex.DecodeString("8ce2bc69281ce27da07e6683571319d18e949ddfa2965fb6caa1bf0314f882d7") + testSBytes, _ = hex.DecodeString("299105481d63e0f4bc2a88121167221b6700d72a0ead154c03be696a292d24ae") + testRScalar = new(btcec.ModNScalar) + testSScalar = new(btcec.ModNScalar) + _ = testRScalar.SetByteSlice(testRBytes) + _ = testSScalar.SetByteSlice(testSBytes) + testSig = ecdsa.NewSignature(testRScalar, testSScalar) testTx = &wire.MsgTx{ Version: 1, @@ -175,7 +177,6 @@ var ( // migrate three areas: open channel commit diffs, open channel unacked updates, // and network results in the switch. func TestMigrateDatabaseWireMessages(t *testing.T) { - var pub [33]byte copy(pub[:], key[:]) @@ -344,6 +345,7 @@ func TestMigrateDatabaseWireMessages(t *testing.T) { if !reflect.DeepEqual( newUpdates, testCommitDiff.LogUpdates, ) { + return fmt.Errorf("updates mismatch: expected "+ "%v, got %v", spew.Sdump(testCommitDiff.LogUpdates), @@ -365,6 +367,7 @@ func TestMigrateDatabaseWireMessages(t *testing.T) { if !reflect.DeepEqual( newUpdates, testCommitDiff.LogUpdates, ) { + return fmt.Errorf("updates mismatch: expected "+ "%v, got %v", spew.Sdump(testCommitDiff.LogUpdates), @@ -389,19 +392,10 @@ func TestMigrateDatabaseWireMessages(t *testing.T) { return err } - testChanCloseSummary.RemotePub.Curve = nil - testChanCloseSummary.RemoteCurrentRevocation.Curve = nil - testChanCloseSummary.RemoteNextRevocation.Curve = nil - testChanCloseSummary.LastChanSyncMsg.LocalUnrevokedCommitPoint.Curve = nil - - newChanCloseSummary.RemotePub.Curve = nil - newChanCloseSummary.RemoteCurrentRevocation.Curve = nil - newChanCloseSummary.RemoteNextRevocation.Curve = nil - newChanCloseSummary.LastChanSyncMsg.LocalUnrevokedCommitPoint.Curve = nil - if !reflect.DeepEqual( newChanCloseSummary, testChanCloseSummary, ) { + return fmt.Errorf("summary mismatch: expected "+ "%v, got %v", spew.Sdump(testChanCloseSummary), diff --git a/channeldb/migration23/migration.go b/channeldb/migration23/migration.go index 7e48791ec5..f3043e2577 100644 --- a/channeldb/migration23/migration.go +++ b/channeldb/migration23/migration.go @@ -140,7 +140,6 @@ func migrateHtlcsBucket(htlcs kvdb.RwBucket) error { if err := htlcs.Put(newKey, settleInfo); err != nil { return err } - } failInfo := attempt.Get(oldFailInfoKey) diff --git a/channeldb/migration24/migration.go b/channeldb/migration24/migration.go index 1110ea3600..8382a9ba63 100644 --- a/channeldb/migration24/migration.go +++ b/channeldb/migration24/migration.go @@ -70,7 +70,7 @@ func MigrateFwdPkgCleanup(tx kvdb.RwTx) error { // Iterate over all close channels and remove their forwarding packages. for _, summery := range chanSummaries { - sourceBytes := makeLogKey(summery.ShortChanID.ToUint64()) + sourceBytes := MakeLogKey(summery.ShortChanID.ToUint64()) // First, we will try to find the corresponding bucket. If there // is not a nested bucket matching the ShortChanID, we will skip @@ -81,8 +81,8 @@ func MigrateFwdPkgCleanup(tx kvdb.RwTx) error { // Otherwise, wipe all the forwarding packages. if err := fwdPkgBkt.DeleteNestedBucket( - sourceBytes[:]); err != nil { - + sourceBytes[:], + ); err != nil { return err } } @@ -112,7 +112,7 @@ func deserializeCloseChannelSummary( } // makeLogKey converts a uint64 into an 8 byte array. -func makeLogKey(updateNum uint64) [8]byte { +func MakeLogKey(updateNum uint64) [8]byte { var key [8]byte binary.BigEndian.PutUint64(key[:], updateNum) return key diff --git a/channeldb/migration24/migration_test.go b/channeldb/migration24/migration_test.go index f02977146e..417c3d0edf 100644 --- a/channeldb/migration24/migration_test.go +++ b/channeldb/migration24/migration_test.go @@ -9,10 +9,10 @@ import ( "math/rand" "testing" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" mig "github.com/lightningnetwork/lnd/channeldb/migration_01_to_11" "github.com/lightningnetwork/lnd/channeldb/migtest" @@ -62,7 +62,7 @@ var ( }, LockTime: 5, } - _, pubKey = btcec.PrivKeyFromBytes(btcec.S256(), key[:]) + _, pubKey = btcec.PrivKeyFromBytes(key[:]) ) // TestMigrateFwdPkgCleanup asserts that the migration will delete all the @@ -187,7 +187,7 @@ func genAfterMigration(deleted, untouched []int) func(kvdb.RwTx) error { // Reading deleted buckets should return nil for _, id := range deleted { chanID := lnwire.NewShortChanIDFromInt(uint64(id)) - sourceKey := makeLogKey(chanID.ToUint64()) + sourceKey := MakeLogKey(chanID.ToUint64()) sourceBkt := fwdPkgBkt.NestedReadBucket(sourceKey[:]) if sourceBkt != nil { return fmt.Errorf( @@ -200,7 +200,7 @@ func genAfterMigration(deleted, untouched []int) func(kvdb.RwTx) error { // Reading untouched buckets should return not nil for _, id := range untouched { chanID := lnwire.NewShortChanIDFromInt(uint64(id)) - sourceKey := makeLogKey(chanID.ToUint64()) + sourceKey := MakeLogKey(chanID.ToUint64()) sourceBkt := fwdPkgBkt.NestedReadBucket(sourceKey[:]) if sourceBkt == nil { return fmt.Errorf( @@ -259,7 +259,7 @@ func createTestFwdPkgBucket(tx kvdb.RwTx, chanID lnwire.ShortChannelID) error { return err } - source := makeLogKey(chanID.ToUint64()) + source := MakeLogKey(chanID.ToUint64()) if _, err := fwdPkgBkt.CreateBucketIfNotExists(source[:]); err != nil { return err } diff --git a/channeldb/migration25/channel.go b/channeldb/migration25/channel.go new file mode 100644 index 0000000000..c8b93bb397 --- /dev/null +++ b/channeldb/migration25/channel.go @@ -0,0 +1,722 @@ +package migration25 + +import ( + "bytes" + "fmt" + "io" + "strconv" + "strings" + + lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" + mig24 "github.com/lightningnetwork/lnd/channeldb/migration24" + mig "github.com/lightningnetwork/lnd/channeldb/migration_01_to_11" + "github.com/lightningnetwork/lnd/keychain" + "github.com/lightningnetwork/lnd/kvdb" + "github.com/lightningnetwork/lnd/tlv" +) + +const ( + // A tlv type definition used to serialize and deserialize a KeyLocator + // from the database. + keyLocType tlv.Type = 1 +) + +var ( + // chanCommitmentKey can be accessed within the sub-bucket for a + // particular channel. This key stores the up to date commitment state + // for a particular channel party. Appending a 0 to the end of this key + // indicates it's the commitment for the local party, and appending a 1 + // to the end of this key indicates it's the commitment for the remote + // party. + chanCommitmentKey = []byte("chan-commitment-key") + + // revocationLogBucketLegacy is the legacy bucket where we store the + // revocation log in old format. + revocationLogBucketLegacy = []byte("revocation-log-key") + + // localUpfrontShutdownKey can be accessed within the bucket for a + // channel (identified by its chanPoint). This key stores an optional + // upfront shutdown script for the local peer. + localUpfrontShutdownKey = []byte("local-upfront-shutdown-key") + + // remoteUpfrontShutdownKey can be accessed within the bucket for a + // channel (identified by its chanPoint). This key stores an optional + // upfront shutdown script for the remote peer. + remoteUpfrontShutdownKey = []byte("remote-upfront-shutdown-key") + + // lastWasRevokeKey is a key that stores true when the last update we + // sent was a revocation and false when it was a commitment signature. + // This is nil in the case of new channels with no updates exchanged. + lastWasRevokeKey = []byte("last-was-revoke") + + // ErrNoChanInfoFound is returned when a particular channel does not + // have any channels state. + ErrNoChanInfoFound = fmt.Errorf("no chan info found") + + // ErrNoPastDeltas is returned when the channel delta bucket hasn't been + // created. + ErrNoPastDeltas = fmt.Errorf("channel has no recorded deltas") + + // ErrLogEntryNotFound is returned when we cannot find a log entry at + // the height requested in the revocation log. + ErrLogEntryNotFound = fmt.Errorf("log entry not found") + + // ErrNoCommitmentsFound is returned when a channel has not set + // commitment states. + ErrNoCommitmentsFound = fmt.Errorf("no commitments found") +) + +// ChannelType is an enum-like type that describes one of several possible +// channel types. Each open channel is associated with a particular type as the +// channel type may determine how higher level operations are conducted such as +// fee negotiation, channel closing, the format of HTLCs, etc. Structure-wise, +// a ChannelType is a bit field, with each bit denoting a modification from the +// base channel type of single funder. +type ChannelType uint8 + +const ( + // NOTE: iota isn't used here for this enum needs to be stable + // long-term as it will be persisted to the database. + + // SingleFunderBit represents a channel wherein one party solely funds + // the entire capacity of the channel. + SingleFunderBit ChannelType = 0 + + // DualFunderBit represents a channel wherein both parties contribute + // funds towards the total capacity of the channel. The channel may be + // funded symmetrically or asymmetrically. + DualFunderBit ChannelType = 1 << 0 + + // SingleFunderTweaklessBit is similar to the basic SingleFunder channel + // type, but it omits the tweak for one's key in the commitment + // transaction of the remote party. + SingleFunderTweaklessBit ChannelType = 1 << 1 + + // NoFundingTxBit denotes if we have the funding transaction locally on + // disk. This bit may be on if the funding transaction was crafted by a + // wallet external to the primary daemon. + NoFundingTxBit ChannelType = 1 << 2 + + // AnchorOutputsBit indicates that the channel makes use of anchor + // outputs to bump the commitment transaction's effective feerate. This + // channel type also uses a delayed to_remote output script. + AnchorOutputsBit ChannelType = 1 << 3 + + // FrozenBit indicates that the channel is a frozen channel, meaning + // that only the responder can decide to cooperatively close the + // channel. + FrozenBit ChannelType = 1 << 4 + + // ZeroHtlcTxFeeBit indicates that the channel should use zero-fee + // second-level HTLC transactions. + ZeroHtlcTxFeeBit ChannelType = 1 << 5 + + // LeaseExpirationBit indicates that the channel has been leased for a + // period of time, constraining every output that pays to the channel + // initiator with an additional CLTV of the lease maturity. + LeaseExpirationBit ChannelType = 1 << 6 +) + +// IsSingleFunder returns true if the channel type if one of the known single +// funder variants. +func (c ChannelType) IsSingleFunder() bool { + return c&DualFunderBit == 0 +} + +// IsDualFunder returns true if the ChannelType has the DualFunderBit set. +func (c ChannelType) IsDualFunder() bool { + return c&DualFunderBit == DualFunderBit +} + +// IsTweakless returns true if the target channel uses a commitment that +// doesn't tweak the key for the remote party. +func (c ChannelType) IsTweakless() bool { + return c&SingleFunderTweaklessBit == SingleFunderTweaklessBit +} + +// HasFundingTx returns true if this channel type is one that has a funding +// transaction stored locally. +func (c ChannelType) HasFundingTx() bool { + return c&NoFundingTxBit == 0 +} + +// HasAnchors returns true if this channel type has anchor outputs on its +// commitment. +func (c ChannelType) HasAnchors() bool { + return c&AnchorOutputsBit == AnchorOutputsBit +} + +// ZeroHtlcTxFee returns true if this channel type uses second-level HTLC +// transactions signed with zero-fee. +func (c ChannelType) ZeroHtlcTxFee() bool { + return c&ZeroHtlcTxFeeBit == ZeroHtlcTxFeeBit +} + +// IsFrozen returns true if the channel is considered to be "frozen". A frozen +// channel means that only the responder can initiate a cooperative channel +// closure. +func (c ChannelType) IsFrozen() bool { + return c&FrozenBit == FrozenBit +} + +// HasLeaseExpiration returns true if the channel originated from a lease. +func (c ChannelType) HasLeaseExpiration() bool { + return c&LeaseExpirationBit == LeaseExpirationBit +} + +// ChannelStatus is a bit vector used to indicate whether an OpenChannel is in +// the default usable state, or a state where it shouldn't be used. +type ChannelStatus uint8 + +var ( + // ChanStatusDefault is the normal state of an open channel. + ChanStatusDefault ChannelStatus + + // ChanStatusBorked indicates that the channel has entered an + // irreconcilable state, triggered by a state desynchronization or + // channel breach. Channels in this state should never be added to the + // htlc switch. + ChanStatusBorked ChannelStatus = 1 + + // ChanStatusCommitBroadcasted indicates that a commitment for this + // channel has been broadcasted. + ChanStatusCommitBroadcasted ChannelStatus = 1 << 1 + + // ChanStatusLocalDataLoss indicates that we have lost channel state + // for this channel, and broadcasting our latest commitment might be + // considered a breach. + // + // TODO(halseh): actually enforce that we are not force closing such a + // channel. + ChanStatusLocalDataLoss ChannelStatus = 1 << 2 + + // ChanStatusRestored is a status flag that signals that the channel + // has been restored, and doesn't have all the fields a typical channel + // will have. + ChanStatusRestored ChannelStatus = 1 << 3 + + // ChanStatusCoopBroadcasted indicates that a cooperative close for + // this channel has been broadcasted. Older cooperatively closed + // channels will only have this status set. Newer ones will also have + // close initiator information stored using the local/remote initiator + // status. This status is set in conjunction with the initiator status + // so that we do not need to check multiple channel statues for + // cooperative closes. + ChanStatusCoopBroadcasted ChannelStatus = 1 << 4 + + // ChanStatusLocalCloseInitiator indicates that we initiated closing + // the channel. + ChanStatusLocalCloseInitiator ChannelStatus = 1 << 5 + + // ChanStatusRemoteCloseInitiator indicates that the remote node + // initiated closing the channel. + ChanStatusRemoteCloseInitiator ChannelStatus = 1 << 6 +) + +// chanStatusStrings maps a ChannelStatus to a human friendly string that +// describes that status. +var chanStatusStrings = map[ChannelStatus]string{ + ChanStatusDefault: "ChanStatusDefault", + ChanStatusBorked: "ChanStatusBorked", + ChanStatusCommitBroadcasted: "ChanStatusCommitBroadcasted", + ChanStatusLocalDataLoss: "ChanStatusLocalDataLoss", + ChanStatusRestored: "ChanStatusRestored", + ChanStatusCoopBroadcasted: "ChanStatusCoopBroadcasted", + ChanStatusLocalCloseInitiator: "ChanStatusLocalCloseInitiator", + ChanStatusRemoteCloseInitiator: "ChanStatusRemoteCloseInitiator", +} + +// orderedChanStatusFlags is an in-order list of all that channel status flags. +var orderedChanStatusFlags = []ChannelStatus{ + ChanStatusBorked, + ChanStatusCommitBroadcasted, + ChanStatusLocalDataLoss, + ChanStatusRestored, + ChanStatusCoopBroadcasted, + ChanStatusLocalCloseInitiator, + ChanStatusRemoteCloseInitiator, +} + +// String returns a human-readable representation of the ChannelStatus. +func (c ChannelStatus) String() string { + // If no flags are set, then this is the default case. + if c == ChanStatusDefault { + return chanStatusStrings[ChanStatusDefault] + } + + // Add individual bit flags. + statusStr := "" + for _, flag := range orderedChanStatusFlags { + if c&flag == flag { + statusStr += chanStatusStrings[flag] + "|" + c -= flag + } + } + + // Remove anything to the right of the final bar, including it as well. + statusStr = strings.TrimRight(statusStr, "|") + + // Add any remaining flags which aren't accounted for as hex. + if c != 0 { + statusStr += "|0x" + strconv.FormatUint(uint64(c), 16) + } + + // If this was purely an unknown flag, then remove the extra bar at the + // start of the string. + statusStr = strings.TrimLeft(statusStr, "|") + + return statusStr +} + +// OpenChannel embeds a mig.OpenChannel with the extra update-to-date fields. +// +// NOTE: doesn't have the Packager field as it's not used in current migration. +type OpenChannel struct { + mig.OpenChannel + + // ChanType denotes which type of channel this is. + ChanType ChannelType + + // chanStatus is the current status of this channel. If it is not in + // the state Default, it should not be used for forwarding payments. + chanStatus ChannelStatus + + // InitialLocalBalance is the balance we have during the channel + // opening. When we are not the initiator, this value represents the + // push amount. + InitialLocalBalance lnwire.MilliSatoshi + + // InitialRemoteBalance is the balance they have during the channel + // opening. + InitialRemoteBalance lnwire.MilliSatoshi + + // LocalShutdownScript is set to a pre-set script if the channel was + // opened by the local node with option_upfront_shutdown_script set. If + // the option was not set, the field is empty. + LocalShutdownScript lnwire.DeliveryAddress + + // RemoteShutdownScript is set to a pre-set script if the channel was + // opened by the remote node with option_upfront_shutdown_script set. + // If the option was not set, the field is empty. + RemoteShutdownScript lnwire.DeliveryAddress + + // ThawHeight is the height when a frozen channel once again becomes a + // normal channel. If this is zero, then there're no restrictions on + // this channel. If the value is lower than 500,000, then it's + // interpreted as a relative height, or an absolute height otherwise. + ThawHeight uint32 + + // LastWasRevoke is a boolean that determines if the last update we + // sent was a revocation (true) or a commitment signature (false). + LastWasRevoke bool + + // RevocationKeyLocator stores the KeyLocator information that we will + // need to derive the shachain root for this channel. This allows us to + // have private key isolation from lnd. + RevocationKeyLocator keychain.KeyLocator +} + +func (c *OpenChannel) hasChanStatus(status ChannelStatus) bool { + // Special case ChanStatusDefualt since it isn't actually flag, but a + // particular combination (or lack-there-of) of flags. + if status == ChanStatusDefault { + return c.chanStatus == ChanStatusDefault + } + + return c.chanStatus&status == status +} + +// FundingTxPresent returns true if expect the funding transcation to be found +// on disk or already populated within the passed open channel struct. +func (c *OpenChannel) FundingTxPresent() bool { + chanType := c.ChanType + + return chanType.IsSingleFunder() && chanType.HasFundingTx() && + c.IsInitiator && + !c.hasChanStatus(ChanStatusRestored) +} + +// fetchChanInfo deserializes the channel info based on the legacy boolean. +func fetchChanInfo(chanBucket kvdb.RBucket, c *OpenChannel, legacy bool) error { + infoBytes := chanBucket.Get(chanInfoKey) + if infoBytes == nil { + return ErrNoChanInfoFound + } + r := bytes.NewReader(infoBytes) + + var ( + chanType mig.ChannelType + chanStatus mig.ChannelStatus + ) + + if err := mig.ReadElements(r, + &chanType, &c.ChainHash, &c.FundingOutpoint, + &c.ShortChannelID, &c.IsPending, &c.IsInitiator, + &chanStatus, &c.FundingBroadcastHeight, + &c.NumConfsRequired, &c.ChannelFlags, + &c.IdentityPub, &c.Capacity, &c.TotalMSatSent, + &c.TotalMSatReceived, + ); err != nil { + return err + } + + c.ChanType = ChannelType(chanType) + c.chanStatus = ChannelStatus(chanStatus) + + // If this is not the legacy format, we need to read the extra two new + // fields. + if !legacy { + if err := mig.ReadElements(r, + &c.InitialLocalBalance, &c.InitialRemoteBalance, + ); err != nil { + return err + } + } + + // For single funder channels that we initiated and have the funding + // transaction to, read the funding txn. + if c.FundingTxPresent() { + if err := mig.ReadElement(r, &c.FundingTxn); err != nil { + return err + } + } + + if err := mig.ReadChanConfig(r, &c.LocalChanCfg); err != nil { + return err + } + if err := mig.ReadChanConfig(r, &c.RemoteChanCfg); err != nil { + return err + } + + // Retrieve the boolean stored under lastWasRevokeKey. + lastWasRevokeBytes := chanBucket.Get(lastWasRevokeKey) + if lastWasRevokeBytes == nil { + // If nothing has been stored under this key, we store false in + // the OpenChannel struct. + c.LastWasRevoke = false + } else { + // Otherwise, read the value into the LastWasRevoke field. + revokeReader := bytes.NewReader(lastWasRevokeBytes) + err := mig.ReadElements(revokeReader, &c.LastWasRevoke) + if err != nil { + return err + } + } + + keyLocRecord := MakeKeyLocRecord(keyLocType, &c.RevocationKeyLocator) + tlvStream, err := tlv.NewStream(keyLocRecord) + if err != nil { + return err + } + + if err := tlvStream.Decode(r); err != nil { + return err + } + + // Finally, read the optional shutdown scripts. + if err := GetOptionalUpfrontShutdownScript( + chanBucket, localUpfrontShutdownKey, &c.LocalShutdownScript, + ); err != nil { + return err + } + + return GetOptionalUpfrontShutdownScript( + chanBucket, remoteUpfrontShutdownKey, &c.RemoteShutdownScript, + ) +} + +// fetchChanInfo serializes the channel info based on the legacy boolean and +// saves it to disk. +func putChanInfo(chanBucket kvdb.RwBucket, c *OpenChannel, legacy bool) error { + var w bytes.Buffer + if err := mig.WriteElements(&w, + mig.ChannelType(c.ChanType), c.ChainHash, c.FundingOutpoint, + c.ShortChannelID, c.IsPending, c.IsInitiator, + mig.ChannelStatus(c.chanStatus), c.FundingBroadcastHeight, + c.NumConfsRequired, c.ChannelFlags, + c.IdentityPub, c.Capacity, c.TotalMSatSent, + c.TotalMSatReceived, + ); err != nil { + return err + } + + // If this is not legacy format, we need to write the extra two fields. + if !legacy { + if err := mig.WriteElements(&w, + c.InitialLocalBalance, c.InitialRemoteBalance, + ); err != nil { + return err + } + } + + // For single funder channels that we initiated, and we have the + // funding transaction, then write the funding txn. + if c.FundingTxPresent() { + if err := mig.WriteElement(&w, c.FundingTxn); err != nil { + return err + } + } + + if err := mig.WriteChanConfig(&w, &c.LocalChanCfg); err != nil { + return err + } + if err := mig.WriteChanConfig(&w, &c.RemoteChanCfg); err != nil { + return err + } + + // Write the RevocationKeyLocator as the first entry in a tlv stream. + keyLocRecord := MakeKeyLocRecord( + keyLocType, &c.RevocationKeyLocator, + ) + + tlvStream, err := tlv.NewStream(keyLocRecord) + if err != nil { + return err + } + + if err := tlvStream.Encode(&w); err != nil { + return err + } + + if err := chanBucket.Put(chanInfoKey, w.Bytes()); err != nil { + return err + } + + // Finally, add optional shutdown scripts for the local and remote peer + // if they are present. + if err := PutOptionalUpfrontShutdownScript( + chanBucket, localUpfrontShutdownKey, c.LocalShutdownScript, + ); err != nil { + return err + } + + return PutOptionalUpfrontShutdownScript( + chanBucket, remoteUpfrontShutdownKey, c.RemoteShutdownScript, + ) +} + +// EKeyLocator is an encoder for keychain.KeyLocator. +func EKeyLocator(w io.Writer, val interface{}, buf *[8]byte) error { + if v, ok := val.(*keychain.KeyLocator); ok { + err := tlv.EUint32T(w, uint32(v.Family), buf) + if err != nil { + return err + } + + return tlv.EUint32T(w, v.Index, buf) + } + return tlv.NewTypeForEncodingErr(val, "keychain.KeyLocator") +} + +// DKeyLocator is a decoder for keychain.KeyLocator. +func DKeyLocator(r io.Reader, val interface{}, buf *[8]byte, l uint64) error { + if v, ok := val.(*keychain.KeyLocator); ok { + var family uint32 + err := tlv.DUint32(r, &family, buf, 4) + if err != nil { + return err + } + v.Family = keychain.KeyFamily(family) + + return tlv.DUint32(r, &v.Index, buf, 4) + } + return tlv.NewTypeForDecodingErr(val, "keychain.KeyLocator", l, 8) +} + +// MakeKeyLocRecord creates a Record out of a KeyLocator using the passed +// Type and the EKeyLocator and DKeyLocator functions. The size will always be +// 8 as KeyFamily is uint32 and the Index is uint32. +func MakeKeyLocRecord(typ tlv.Type, keyLoc *keychain.KeyLocator) tlv.Record { + return tlv.MakeStaticRecord(typ, keyLoc, 8, EKeyLocator, DKeyLocator) +} + +// PutOptionalUpfrontShutdownScript adds a shutdown script under the key +// provided if it has a non-zero length. +func PutOptionalUpfrontShutdownScript(chanBucket kvdb.RwBucket, key []byte, + script []byte) error { + // If the script is empty, we do not need to add anything. + if len(script) == 0 { + return nil + } + + var w bytes.Buffer + if err := mig.WriteElement(&w, script); err != nil { + return err + } + + return chanBucket.Put(key, w.Bytes()) +} + +// GetOptionalUpfrontShutdownScript reads the shutdown script stored under the +// key provided if it is present. Upfront shutdown scripts are optional, so the +// function returns with no error if the key is not present. +func GetOptionalUpfrontShutdownScript(chanBucket kvdb.RBucket, key []byte, + script *lnwire.DeliveryAddress) error { + + // Return early if the bucket does not exit, a shutdown script was not + // set. + bs := chanBucket.Get(key) + if bs == nil { + return nil + } + + var tempScript []byte + r := bytes.NewReader(bs) + if err := mig.ReadElement(r, &tempScript); err != nil { + return err + } + *script = tempScript + + return nil +} + +// FetchChanCommitments fetches both the local and remote commitments. This +// function is exported so it can be used by later migrations. +func FetchChanCommitments(chanBucket kvdb.RBucket, channel *OpenChannel) error { + var err error + + // If this is a restored channel, then we don't have any commitments to + // read. + if channel.hasChanStatus(ChanStatusRestored) { + return nil + } + + channel.LocalCommitment, err = FetchChanCommitment(chanBucket, true) + if err != nil { + return err + } + channel.RemoteCommitment, err = FetchChanCommitment(chanBucket, false) + if err != nil { + return err + } + + return nil +} + +// FetchChanCommitment fetches a channel commitment. This function is exported +// so it can be used by later migrations. +func FetchChanCommitment(chanBucket kvdb.RBucket, + local bool) (mig.ChannelCommitment, error) { + + commitKey := chanCommitmentKey + if local { + commitKey = append(commitKey, byte(0x00)) + } else { + commitKey = append(commitKey, byte(0x01)) + } + + commitBytes := chanBucket.Get(commitKey) + if commitBytes == nil { + return mig.ChannelCommitment{}, ErrNoCommitmentsFound + } + + r := bytes.NewReader(commitBytes) + return mig.DeserializeChanCommit(r) +} + +func PutChanCommitment(chanBucket kvdb.RwBucket, c *mig.ChannelCommitment, + local bool) error { + + commitKey := chanCommitmentKey + if local { + commitKey = append(commitKey, byte(0x00)) + } else { + commitKey = append(commitKey, byte(0x01)) + } + + var b bytes.Buffer + if err := mig.SerializeChanCommit(&b, c); err != nil { + return err + } + + return chanBucket.Put(commitKey, b.Bytes()) +} + +func PutChanCommitments(chanBucket kvdb.RwBucket, channel *OpenChannel) error { + // If this is a restored channel, then we don't have any commitments to + // write. + if channel.hasChanStatus(ChanStatusRestored) { + return nil + } + + err := PutChanCommitment( + chanBucket, &channel.LocalCommitment, true, + ) + if err != nil { + return err + } + + return PutChanCommitment( + chanBucket, &channel.RemoteCommitment, false, + ) +} + +// balancesAtHeight returns the local and remote balances on our commitment +// transactions as of a given height. This function is not exported as it's +// deprecated. +// +// NOTE: these are our balances *after* subtracting the commitment fee and +// anchor outputs. +func (c *OpenChannel) balancesAtHeight(chanBucket kvdb.RBucket, + height uint64) (lnwire.MilliSatoshi, lnwire.MilliSatoshi, error) { + + // If our current commit is as the desired height, we can return our + // current balances. + if c.LocalCommitment.CommitHeight == height { + return c.LocalCommitment.LocalBalance, + c.LocalCommitment.RemoteBalance, nil + } + + // If our current remote commit is at the desired height, we can return + // the current balances. + if c.RemoteCommitment.CommitHeight == height { + return c.RemoteCommitment.LocalBalance, + c.RemoteCommitment.RemoteBalance, nil + } + + // If we are not currently on the height requested, we need to look up + // the previous height to obtain our balances at the given height. + commit, err := c.FindPreviousStateLegacy(chanBucket, height) + if err != nil { + return 0, 0, err + } + + return commit.LocalBalance, commit.RemoteBalance, nil +} + +// FindPreviousStateLegacy scans through the append-only log in an attempt to +// recover the previous channel state indicated by the update number. This +// method is intended to be used for obtaining the relevant data needed to +// claim all funds rightfully spendable in the case of an on-chain broadcast of +// the commitment transaction. +func (c *OpenChannel) FindPreviousStateLegacy(chanBucket kvdb.RBucket, + updateNum uint64) (*mig.ChannelCommitment, error) { + + c.RLock() + defer c.RUnlock() + + logBucket := chanBucket.NestedReadBucket(revocationLogBucketLegacy) + if logBucket == nil { + return nil, ErrNoPastDeltas + } + + commit, err := fetchChannelLogEntry(logBucket, updateNum) + if err != nil { + return nil, err + } + + return &commit, nil +} + +func fetchChannelLogEntry(log kvdb.RBucket, + updateNum uint64) (mig.ChannelCommitment, error) { + + logEntrykey := mig24.MakeLogKey(updateNum) + commitBytes := log.Get(logEntrykey[:]) + if commitBytes == nil { + return mig.ChannelCommitment{}, ErrLogEntryNotFound + } + + commitReader := bytes.NewReader(commitBytes) + return mig.DeserializeChanCommit(commitReader) +} diff --git a/channeldb/migration25/log.go b/channeldb/migration25/log.go new file mode 100644 index 0000000000..e308d2755a --- /dev/null +++ b/channeldb/migration25/log.go @@ -0,0 +1,14 @@ +package migration25 + +import ( + "github.com/btcsuite/btclog" +) + +// log is a logger that is initialized as disabled. This means the package will +// not perform any logging by default until a logger is set. +var log = btclog.Disabled + +// UseLogger uses a specified Logger to output package logging info. +func UseLogger(logger btclog.Logger) { + log = logger +} diff --git a/channeldb/migration25/migration.go b/channeldb/migration25/migration.go new file mode 100644 index 0000000000..db6e156e97 --- /dev/null +++ b/channeldb/migration25/migration.go @@ -0,0 +1,211 @@ +package migration25 + +import ( + "bytes" + "fmt" + + mig "github.com/lightningnetwork/lnd/channeldb/migration_01_to_11" + "github.com/lightningnetwork/lnd/kvdb" +) + +var ( + // openChanBucket stores all the currently open channels. This bucket + // has a second, nested bucket which is keyed by a node's ID. Within + // that node ID bucket, all attributes required to track, update, and + // close a channel are stored. + openChannelBucket = []byte("open-chan-bucket") + + // chanInfoKey can be accessed within the bucket for a channel + // (identified by its chanPoint). This key stores all the static + // information for a channel which is decided at the end of the + // funding flow. + chanInfoKey = []byte("chan-info-key") + + // ErrNoChanDBExists is returned when a channel bucket hasn't been + // created. + ErrNoChanDBExists = fmt.Errorf("channel db has not yet been created") + + // ErrNoActiveChannels is returned when there is no active (open) + // channels within the database. + ErrNoActiveChannels = fmt.Errorf("no active channels exist") + + // ErrChannelNotFound is returned when we attempt to locate a channel + // for a specific chain, but it is not found. + ErrChannelNotFound = fmt.Errorf("channel not found") +) + +// MigrateInitialBalances patches the two new fields, InitialLocalBalance and +// InitialRemoteBalance, for all the open channels. It does so by reading the +// revocation log at height 0 to learn the initial balances and then updates +// the channel's info. +// The channel info is saved in the nested bucket which is accessible via +// nodePub:chainHash:chanPoint. If any of the sub-buckets turns out to be nil, +// we will log the error and continue to process the rest. +func MigrateInitialBalances(tx kvdb.RwTx) error { + log.Infof("Migrating initial local and remote balances...") + + openChanBucket := tx.ReadWriteBucket(openChannelBucket) + + // If no bucket is found, we can exit early. + if openChanBucket == nil { + return nil + } + + // Read a list of open channels. + channels, err := findOpenChannels(openChanBucket) + if err != nil { + return err + } + + // Migrate the balances. + for _, c := range channels { + if err := migrateBalances(tx, c); err != nil { + return err + } + } + + return err +} + +// findOpenChannels finds all open channels. +func findOpenChannels(openChanBucket kvdb.RBucket) ([]*OpenChannel, error) { + channels := []*OpenChannel{} + + // readChannel is a helper closure that reads the channel info from the + // channel bucket. + readChannel := func(chainBucket kvdb.RBucket, cp []byte) error { + c := &OpenChannel{} + + // Read the sub-bucket level 3. + chanBucket := chainBucket.NestedReadBucket( + cp, + ) + if chanBucket == nil { + log.Errorf("unable to read bucket for chanPoint=%x", cp) + return nil + } + // Get the old channel info. + if err := fetchChanInfo(chanBucket, c, true); err != nil { + return fmt.Errorf("unable to fetch chan info: %v", err) + } + + // Fetch the channel commitments, which are useful for freshly + // open channels as they don't have any revocation logs and + // their current commitments reflect the initial balances. + if err := FetchChanCommitments(chanBucket, c); err != nil { + return fmt.Errorf("unable to fetch chan commits: %v", + err) + } + + channels = append(channels, c) + + return nil + } + + // Iterate the root bucket. + err := openChanBucket.ForEach(func(nodePub, v []byte) error { + // Ensure that this is a key the same size as a pubkey, and + // also that it leads directly to a bucket. + if len(nodePub) != 33 || v != nil { + return nil + } + + // Read the sub-bucket level 1. + nodeChanBucket := openChanBucket.NestedReadBucket(nodePub) + if nodeChanBucket == nil { + log.Errorf("no bucket for node %x", nodePub) + return nil + } + + // Iterate the bucket. + return nodeChanBucket.ForEach(func(chainHash, _ []byte) error { + // Read the sub-bucket level 2. + chainBucket := nodeChanBucket.NestedReadBucket( + chainHash, + ) + if chainBucket == nil { + log.Errorf("unable to read bucket for chain=%x", + chainHash) + return nil + } + + // Iterate the bucket. + return chainBucket.ForEach(func(cp, _ []byte) error { + return readChannel(chainBucket, cp) + }) + }) + }) + + if err != nil { + return nil, err + } + + return channels, nil +} + +// migrateBalances queries the revocation log at height 0 to find the initial +// balances and save them to the channel info. +func migrateBalances(tx kvdb.RwTx, c *OpenChannel) error { + // Get the bucket. + chanBucket, err := fetchChanBucket(tx, c) + if err != nil { + return err + } + // Get the initial balances. + localAmt, remoteAmt, err := c.balancesAtHeight(chanBucket, 0) + if err != nil { + return fmt.Errorf("unable to get initial balances: %v", err) + } + + c.InitialLocalBalance = localAmt + c.InitialRemoteBalance = remoteAmt + + // Update the channel info. + if err := putChanInfo(chanBucket, c, false); err != nil { + return fmt.Errorf("unable to put chan info: %v", err) + } + + return nil +} + +// fetchChanBucket is a helper function that returns the bucket where a +// channel's data resides in given: the public key for the node, the outpoint, +// and the chainhash that the channel resides on. +func fetchChanBucket(tx kvdb.RwTx, c *OpenChannel) (kvdb.RwBucket, error) { + // First fetch the top level bucket which stores all data related to + // current, active channels. + openChanBucket := tx.ReadWriteBucket(openChannelBucket) + if openChanBucket == nil { + return nil, ErrNoChanDBExists + } + + // Within this top level bucket, fetch the bucket dedicated to storing + // open channel data specific to the remote node. + nodePub := c.IdentityPub.SerializeCompressed() + nodeChanBucket := openChanBucket.NestedReadWriteBucket(nodePub) + if nodeChanBucket == nil { + return nil, ErrNoActiveChannels + } + + // We'll then recurse down an additional layer in order to fetch the + // bucket for this particular chain. + chainBucket := nodeChanBucket.NestedReadWriteBucket(c.ChainHash[:]) + if chainBucket == nil { + return nil, ErrNoActiveChannels + } + + // With the bucket for the node and chain fetched, we can now go down + // another level, for this channel itself. + var chanPointBuf bytes.Buffer + err := mig.WriteOutpoint(&chanPointBuf, &c.FundingOutpoint) + if err != nil { + return nil, err + } + + chanBucket := chainBucket.NestedReadWriteBucket(chanPointBuf.Bytes()) + if chanBucket == nil { + return nil, ErrChannelNotFound + } + + return chanBucket, nil +} diff --git a/channeldb/migration25/migration_test.go b/channeldb/migration25/migration_test.go new file mode 100644 index 0000000000..339815cab2 --- /dev/null +++ b/channeldb/migration25/migration_test.go @@ -0,0 +1,391 @@ +package migration25 + +import ( + "bytes" + "fmt" + "testing" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + + lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" + mig24 "github.com/lightningnetwork/lnd/channeldb/migration24" + mig "github.com/lightningnetwork/lnd/channeldb/migration_01_to_11" + "github.com/lightningnetwork/lnd/channeldb/migtest" + "github.com/lightningnetwork/lnd/kvdb" +) + +var ( + // Create dummy values to be stored in db. + dummyPrivKey, _ = btcec.NewPrivateKey() + dummyPubKey = dummyPrivKey.PubKey() + dummySig = []byte{1, 2, 3} + dummyTx = &wire.MsgTx{ + Version: 1, + TxIn: []*wire.TxIn{ + { + PreviousOutPoint: wire.OutPoint{ + Hash: chainhash.Hash{}, + Index: 0xffffffff, + }, + Sequence: 0xffffffff, + }, + }, + TxOut: []*wire.TxOut{ + { + Value: 5000000000, + }, + }, + LockTime: 5, + } + + dummyOp = wire.OutPoint{ + Hash: chainhash.Hash{}, + Index: 9, + } + dummyHTLC = mig.HTLC{ + Signature: dummySig, + RHash: [32]byte{}, + Amt: 100_000, + RefundTimeout: 731583, + OutputIndex: 1, + Incoming: true, + HtlcIndex: 1, + LogIndex: 1, + } + + // ourAmt and theirAmt are the initial balances found in the local + // channel commitment at height 0. + ourAmt = lnwire.MilliSatoshi(500_000) + theirAmt = lnwire.MilliSatoshi(1000_000) + + // ourAmtRevoke and theirAmtRevoke are the initial balances found in + // the revocation log at height 0. + // + // NOTE: they are made differently such that we can easily check the + // source when patching the balances. + ourAmtRevoke = lnwire.MilliSatoshi(501_000) + theirAmtRevoke = lnwire.MilliSatoshi(1001_000) + + // remoteCommit0 is the channel commitment at commit height 0. This is + // also the revocation log we will use to patch the initial balances. + remoteCommit0 = mig.ChannelCommitment{ + LocalBalance: ourAmtRevoke, + RemoteBalance: theirAmtRevoke, + CommitFee: btcutil.Amount(1), + FeePerKw: btcutil.Amount(1), + CommitTx: dummyTx, + CommitSig: dummySig, + Htlcs: []mig.HTLC{}, + } + + // localCommit0 is the channel commitment at commit height 0. This is + // the channel commitment we will use to patch the initial balances + // when there are no revocation logs. + localCommit0 = mig.ChannelCommitment{ + LocalBalance: ourAmt, + RemoteBalance: theirAmt, + CommitFee: btcutil.Amount(1), + FeePerKw: btcutil.Amount(1), + CommitTx: dummyTx, + CommitSig: dummySig, + Htlcs: []mig.HTLC{}, + } + + // remoteCommit1 and localCommit1 are the channel commitment at commit + // height 1. + remoteCommit1 = mig.ChannelCommitment{ + CommitHeight: 1, + LocalLogIndex: 1, + LocalHtlcIndex: 1, + RemoteLogIndex: 1, + RemoteHtlcIndex: 1, + LocalBalance: ourAmt - dummyHTLC.Amt, + RemoteBalance: theirAmt + dummyHTLC.Amt, + CommitFee: btcutil.Amount(1), + FeePerKw: btcutil.Amount(1), + CommitTx: dummyTx, + CommitSig: dummySig, + Htlcs: []mig.HTLC{dummyHTLC}, + } + localCommit1 = mig.ChannelCommitment{ + CommitHeight: 1, + LocalLogIndex: 1, + LocalHtlcIndex: 1, + RemoteLogIndex: 1, + RemoteHtlcIndex: 1, + LocalBalance: ourAmt - dummyHTLC.Amt, + RemoteBalance: theirAmt + dummyHTLC.Amt, + CommitFee: btcutil.Amount(1), + FeePerKw: btcutil.Amount(1), + CommitTx: dummyTx, + CommitSig: dummySig, + Htlcs: []mig.HTLC{dummyHTLC}, + } + + // openChannel0 is the OpenChannel at commit height 0. When this + // variable is used, we expect to patch the initial balances from its + // commitments. + openChannel0 = &OpenChannel{ + OpenChannel: mig.OpenChannel{ + IdentityPub: dummyPubKey, + FundingOutpoint: dummyOp, + LocalCommitment: localCommit0, + RemoteCommitment: remoteCommit0, + }, + } + + // openChannel1 is the OpenChannel at commit height 1. When this + // variable is used, we expect to patch the initial balances from the + // remote commitment at height 0. + openChannel1 = &OpenChannel{ + OpenChannel: mig.OpenChannel{ + IdentityPub: dummyPubKey, + FundingOutpoint: dummyOp, + LocalCommitment: localCommit1, + RemoteCommitment: remoteCommit1, + }, + } +) + +// TestMigrateInitialBalances checks that the proper initial balances are +// patched to the channel info. +func TestMigrateInitialBalances(t *testing.T) { + testCases := []struct { + name string + beforeMigrationFunc func(kvdb.RwTx) error + afterMigrationFunc func(kvdb.RwTx) error + shouldFail bool + }{ + { + // Test that we patch the initial balances using the + // revocation log. + name: "patch balance from revoke log", + beforeMigrationFunc: genBeforeMigration( + openChannel1, &remoteCommit0, + ), + afterMigrationFunc: genAfterMigration( + ourAmtRevoke, theirAmtRevoke, openChannel1, + ), + }, + { + // Test that we patch the initial balances using the + // channel's local commitment since at height 0, + // balances found in LocalCommitment reflect the + // initial balances. + name: "patch balance from local commit", + beforeMigrationFunc: genBeforeMigration( + openChannel0, nil, + ), + afterMigrationFunc: genAfterMigration( + ourAmt, theirAmt, openChannel0, + ), + }, + { + // Test that we patch the initial balances using the + // channel's local commitment even when there is a + // revocation log available. + name: "patch balance from local commit only", + beforeMigrationFunc: genBeforeMigration( + openChannel0, &remoteCommit0, + ), + afterMigrationFunc: genAfterMigration( + ourAmt, theirAmt, openChannel0, + ), + }, + { + // Test that when there is no revocation log the + // migration would fail. + name: "patch balance error on no revoke log", + beforeMigrationFunc: genBeforeMigration( + // Use nil to specify no revocation log will be + // created. + openChannel1, nil, + ), + afterMigrationFunc: genAfterMigration( + // Use nil to specify skipping the + // afterMigrationFunc. + 0, 0, nil, + ), + shouldFail: true, + }, + { + // Test that when the saved revocation log is not what + // we want the migration would fail. + name: "patch balance error on wrong revoke log", + beforeMigrationFunc: genBeforeMigration( + // Use the revocation log with the wrong + // height. + openChannel1, &remoteCommit1, + ), + afterMigrationFunc: genAfterMigration( + // Use nil to specify skipping the + // afterMigrationFunc. + 0, 0, nil, + ), + shouldFail: true, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + migtest.ApplyMigration( + t, + tc.beforeMigrationFunc, + tc.afterMigrationFunc, + MigrateInitialBalances, + tc.shouldFail, + ) + }) + } +} + +func genBeforeMigration(c *OpenChannel, + commit *mig.ChannelCommitment) func(kvdb.RwTx) error { + + return func(tx kvdb.RwTx) error { + if c.InitialLocalBalance != 0 { + return fmt.Errorf("non zero initial local amount") + } + + if c.InitialRemoteBalance != 0 { + return fmt.Errorf("non zero initial local amount") + } + + // Create the channel bucket. + chanBucket, err := createChanBucket(tx, c) + if err != nil { + return err + } + + // Save the channel info using legacy format. + if err := putChanInfo(chanBucket, c, true); err != nil { + return err + } + + // Save the channel commitments. + if err := PutChanCommitments(chanBucket, c); err != nil { + return err + } + + // If we have a remote commitment, save it as our revocation + // log. + if commit != nil { + err := putChannelLogEntryLegacy(chanBucket, commit) + if err != nil { + return err + } + } + + return nil + } +} + +func genAfterMigration(ourAmt, theirAmt lnwire.MilliSatoshi, + c *OpenChannel) func(kvdb.RwTx) error { + + return func(tx kvdb.RwTx) error { + // If the passed OpenChannel is nil, we will skip the + // afterMigrationFunc as it indicates an error is expected + // during the migration. + if c == nil { + return nil + } + + chanBucket, err := fetchChanBucket(tx, c) + if err != nil { + return err + } + + newChan := &OpenChannel{} + + // Fetch the channel info using the new format. + err = fetchChanInfo(chanBucket, newChan, false) + if err != nil { + return err + } + + // Check our initial amount is correct. + if newChan.InitialLocalBalance != ourAmt { + return fmt.Errorf("wrong local balance, got %d, "+ + "want %d", newChan.InitialLocalBalance, ourAmt) + } + + // Check their initial amount is correct. + if newChan.InitialRemoteBalance != theirAmt { + return fmt.Errorf("wrong remote balance, got %d, "+ + "want %d", newChan.InitialRemoteBalance, + theirAmt) + } + + // We also check the relevant channel info fields stay the + // same. + if !newChan.IdentityPub.IsEqual(c.IdentityPub) { + return fmt.Errorf("wrong IdentityPub") + } + if newChan.FundingOutpoint != c.FundingOutpoint { + return fmt.Errorf("wrong FundingOutpoint") + } + + return nil + } +} + +func createChanBucket(tx kvdb.RwTx, c *OpenChannel) (kvdb.RwBucket, error) { + // First fetch the top level bucket which stores all data related to + // current, active channels. + openChanBucket, err := tx.CreateTopLevelBucket(openChannelBucket) + if err != nil { + return nil, err + } + + // Within this top level bucket, fetch the bucket dedicated to storing + // open channel data specific to the remote node. + nodePub := c.IdentityPub.SerializeCompressed() + nodeChanBucket, err := openChanBucket.CreateBucketIfNotExists(nodePub) + if err != nil { + return nil, err + } + + // We'll then recurse down an additional layer in order to fetch the + // bucket for this particular chain. + chainBucket, err := nodeChanBucket.CreateBucketIfNotExists( + c.ChainHash[:], + ) + if err != nil { + return nil, err + } + + var chanPointBuf bytes.Buffer + err = mig.WriteOutpoint(&chanPointBuf, &c.FundingOutpoint) + if err != nil { + return nil, err + } + + // With the bucket for the node fetched, we can now go down another + // level, creating the bucket for this channel itself. + return chainBucket.CreateBucketIfNotExists(chanPointBuf.Bytes()) +} + +// putChannelLogEntryLegacy saves an old format revocation log to the bucket. +func putChannelLogEntryLegacy(chanBucket kvdb.RwBucket, + commit *mig.ChannelCommitment) error { + + logBucket, err := chanBucket.CreateBucketIfNotExists( + revocationLogBucketLegacy, + ) + if err != nil { + return err + } + + var b bytes.Buffer + if err := mig.SerializeChanCommit(&b, commit); err != nil { + return err + } + + logEntrykey := mig24.MakeLogKey(commit.CommitHeight) + return logBucket.Put(logEntrykey[:], b.Bytes()) +} diff --git a/channeldb/migration_01_to_11/channel.go b/channeldb/migration_01_to_11/channel.go index 2abdfd2873..47cbca3559 100644 --- a/channeldb/migration_01_to_11/channel.go +++ b/channeldb/migration_01_to_11/channel.go @@ -8,10 +8,10 @@ import ( "strings" "sync" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/shachain" @@ -616,13 +616,13 @@ func serializeChannelCloseSummary(w io.Writer, cs *ChannelCloseSummary) error { return err } - if err := writeChanConfig(w, &cs.LocalChanConfig); err != nil { + if err := WriteChanConfig(w, &cs.LocalChanConfig); err != nil { return err } // The RemoteNextRevocation field is optional, as it's possible for a // channel to be closed before we learn of the next unrevoked - // revocation point for the remote party. Write a boolen indicating + // revocation point for the remote party. Write a boolean indicating // whether this field is present or not. if err := WriteElements(w, cs.RemoteNextRevocation != nil); err != nil { return err @@ -680,7 +680,7 @@ func deserializeCloseChannelSummary(r io.Reader) (*ChannelCloseSummary, error) { return nil, err } - if err := readChanConfig(r, &c.LocalChanConfig); err != nil { + if err := ReadChanConfig(r, &c.LocalChanConfig); err != nil { return nil, err } @@ -731,7 +731,7 @@ func deserializeCloseChannelSummary(r io.Reader) (*ChannelCloseSummary, error) { return c, nil } -func writeChanConfig(b io.Writer, c *ChannelConfig) error { +func WriteChanConfig(b io.Writer, c *ChannelConfig) error { return WriteElements(b, c.DustLimit, c.MaxPendingAmount, c.ChanReserve, c.MinHTLC, c.MaxAcceptedHtlcs, c.CsvDelay, c.MultiSigKey, @@ -740,7 +740,7 @@ func writeChanConfig(b io.Writer, c *ChannelConfig) error { ) } -func readChanConfig(b io.Reader, c *ChannelConfig) error { +func ReadChanConfig(b io.Reader, c *ChannelConfig) error { return ReadElements(b, &c.DustLimit, &c.MaxPendingAmount, &c.ChanReserve, &c.MinHTLC, &c.MaxAcceptedHtlcs, &c.CsvDelay, @@ -749,3 +749,92 @@ func readChanConfig(b io.Reader, c *ChannelConfig) error { &c.HtlcBasePoint, ) } + +func DeserializeChanCommit(r io.Reader) (ChannelCommitment, error) { + var c ChannelCommitment + + err := ReadElements(r, + &c.CommitHeight, &c.LocalLogIndex, &c.LocalHtlcIndex, &c.RemoteLogIndex, + &c.RemoteHtlcIndex, &c.LocalBalance, &c.RemoteBalance, + &c.CommitFee, &c.FeePerKw, &c.CommitTx, &c.CommitSig, + ) + if err != nil { + return c, err + } + + c.Htlcs, err = DeserializeHtlcs(r) + if err != nil { + return c, err + } + + return c, nil +} + +// DeserializeHtlcs attempts to read out a slice of HTLC's from the passed +// io.Reader. The bytes within the passed reader MUST have been previously +// written to using the SerializeHtlcs function. +// +// NOTE: This API is NOT stable, the on-disk format will likely change in the +// future. +func DeserializeHtlcs(r io.Reader) ([]HTLC, error) { + var numHtlcs uint16 + if err := ReadElement(r, &numHtlcs); err != nil { + return nil, err + } + + var htlcs []HTLC + if numHtlcs == 0 { + return htlcs, nil + } + + htlcs = make([]HTLC, numHtlcs) + for i := uint16(0); i < numHtlcs; i++ { + if err := ReadElements(r, + &htlcs[i].Signature, &htlcs[i].RHash, &htlcs[i].Amt, + &htlcs[i].RefundTimeout, &htlcs[i].OutputIndex, + &htlcs[i].Incoming, &htlcs[i].OnionBlob, + &htlcs[i].HtlcIndex, &htlcs[i].LogIndex, + ); err != nil { + return htlcs, err + } + } + + return htlcs, nil +} + +func SerializeChanCommit(w io.Writer, c *ChannelCommitment) error { + if err := WriteElements(w, + c.CommitHeight, c.LocalLogIndex, c.LocalHtlcIndex, + c.RemoteLogIndex, c.RemoteHtlcIndex, c.LocalBalance, + c.RemoteBalance, c.CommitFee, c.FeePerKw, c.CommitTx, + c.CommitSig, + ); err != nil { + return err + } + + return SerializeHtlcs(w, c.Htlcs...) +} + +// SerializeHtlcs writes out the passed set of HTLC's into the passed writer +// using the current default on-disk serialization format. +// +// NOTE: This API is NOT stable, the on-disk format will likely change in the +// future. +func SerializeHtlcs(b io.Writer, htlcs ...HTLC) error { + numHtlcs := uint16(len(htlcs)) + if err := WriteElement(b, numHtlcs); err != nil { + return err + } + + for _, htlc := range htlcs { + if err := WriteElements(b, + htlc.Signature, htlc.RHash, htlc.Amt, htlc.RefundTimeout, + htlc.OutputIndex, htlc.Incoming, htlc.OnionBlob[:], + htlc.HtlcIndex, htlc.LogIndex, + ); err != nil { + return err + } + } + + return nil +} diff --git a/channeldb/migration_01_to_11/channel_test.go b/channeldb/migration_01_to_11/channel_test.go index 7e3ba6e0af..ff17b113fd 100644 --- a/channeldb/migration_01_to_11/channel_test.go +++ b/channeldb/migration_01_to_11/channel_test.go @@ -6,10 +6,10 @@ import ( "math/rand" "os" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" _ "github.com/btcsuite/btcwallet/walletdb/bdb" lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" "github.com/lightningnetwork/lnd/keychain" @@ -60,7 +60,7 @@ var ( }, LockTime: 5, } - privKey, pubKey = btcec.PrivKeyFromBytes(btcec.S256(), key[:]) + privKey, pubKey = btcec.PrivKeyFromBytes(key[:]) ) // makeTestDB creates a new instance of the ChannelDB for testing purposes. A diff --git a/channeldb/migration_01_to_11/codec.go b/channeldb/migration_01_to_11/codec.go index 6ee6f6089d..a87f6eda72 100644 --- a/channeldb/migration_01_to_11/codec.go +++ b/channeldb/migration_01_to_11/codec.go @@ -6,18 +6,18 @@ import ( "io" "net" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/shachain" ) -// writeOutpoint writes an outpoint to the passed writer using the minimal +// WriteOutpoint writes an outpoint to the passed writer using the minimal // amount of bytes possible. -func writeOutpoint(w io.Writer, o *wire.OutPoint) error { +func WriteOutpoint(w io.Writer, o *wire.OutPoint) error { if _, err := w.Write(o.Hash[:]); err != nil { return err } @@ -28,9 +28,9 @@ func writeOutpoint(w io.Writer, o *wire.OutPoint) error { return nil } -// readOutpoint reads an outpoint from the passed reader that was previously +// ReadOutpoint reads an outpoint from the passed reader that was previously // written using the writeOutpoint struct. -func readOutpoint(r io.Reader, o *wire.OutPoint) error { +func ReadOutpoint(r io.Reader, o *wire.OutPoint) error { if _, err := io.ReadFull(r, o.Hash[:]); err != nil { return err } @@ -88,7 +88,7 @@ func WriteElement(w io.Writer, element interface{}) error { } case wire.OutPoint: - return writeOutpoint(w, &e) + return WriteOutpoint(w, &e) case lnwire.ShortChannelID: if err := binary.Write(w, byteOrder, e.ToUint64()); err != nil { @@ -258,7 +258,7 @@ func ReadElement(r io.Reader, element interface{}) error { } case *wire.OutPoint: - return readOutpoint(r, e) + return ReadOutpoint(r, e) case *lnwire.ShortChannelID: var a uint64 @@ -324,7 +324,7 @@ func ReadElement(r io.Reader, element interface{}) error { return err } - priv, _ := btcec.PrivKeyFromBytes(btcec.S256(), b[:]) + priv, _ := btcec.PrivKeyFromBytes(b[:]) *e = priv case **btcec.PublicKey: @@ -333,7 +333,7 @@ func ReadElement(r io.Reader, element interface{}) error { return err } - pubKey, err := btcec.ParsePubKey(b[:], btcec.S256()) + pubKey, err := btcec.ParsePubKey(b[:]) if err != nil { return err } diff --git a/channeldb/migration_01_to_11/graph.go b/channeldb/migration_01_to_11/graph.go index 9a37015698..0f36a78edf 100644 --- a/channeldb/migration_01_to_11/graph.go +++ b/channeldb/migration_01_to_11/graph.go @@ -9,10 +9,10 @@ import ( "net" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" "github.com/lightningnetwork/lnd/kvdb" ) @@ -384,7 +384,7 @@ func (l *LightningNode) PubKey() (*btcec.PublicKey, error) { return l.pubKey, nil } - key, err := btcec.ParsePubKey(l.PubKeyBytes[:], btcec.S256()) + key, err := btcec.ParsePubKey(l.PubKeyBytes[:]) if err != nil { return nil, err } @@ -836,7 +836,7 @@ func deserializeChanEdgeInfo(r io.Reader) (ChannelEdgeInfo, error) { } edgeInfo.ChannelPoint = wire.OutPoint{} - if err := readOutpoint(r, &edgeInfo.ChannelPoint); err != nil { + if err := ReadOutpoint(r, &edgeInfo.ChannelPoint); err != nil { return ChannelEdgeInfo{}, err } if err := binary.Read(r, byteOrder, &edgeInfo.Capacity); err != nil { diff --git a/channeldb/migration_01_to_11/graph_test.go b/channeldb/migration_01_to_11/graph_test.go index dc42ba20e2..060c3aada0 100644 --- a/channeldb/migration_01_to_11/graph_test.go +++ b/channeldb/migration_01_to_11/graph_test.go @@ -1,13 +1,14 @@ package migration_01_to_11 import ( + "encoding/hex" "image/color" - "math/big" prand "math/rand" "net" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" ) @@ -18,12 +19,13 @@ var ( "[2001:db8:85a3:0:0:8a2e:370:7334]:80") testAddrs = []net.Addr{testAddr, anotherAddr} - testSig = &btcec.Signature{ - R: new(big.Int), - S: new(big.Int), - } - _, _ = testSig.R.SetString("63724406601629180062774974542967536251589935445068131219452686511677818569431", 10) - _, _ = testSig.S.SetString("18801056069249825825291287104931333862866033135609736119018462340006816851118", 10) + testRBytes, _ = hex.DecodeString("8ce2bc69281ce27da07e6683571319d18e949ddfa2965fb6caa1bf0314f882d7") + testSBytes, _ = hex.DecodeString("299105481d63e0f4bc2a88121167221b6700d72a0ead154c03be696a292d24ae") + testRScalar = new(btcec.ModNScalar) + testSScalar = new(btcec.ModNScalar) + _ = testRScalar.SetByteSlice(testRBytes) + _ = testSScalar.SetByteSlice(testSBytes) + testSig = ecdsa.NewSignature(testRScalar, testSScalar) testFeatures = lnwire.NewFeatureVector(nil, nil) ) @@ -48,7 +50,7 @@ func createLightningNode(db *DB, priv *btcec.PrivateKey) (*LightningNode, error) } func createTestVertex(db *DB) (*LightningNode, error) { - priv, err := btcec.NewPrivateKey(btcec.S256()) + priv, err := btcec.NewPrivateKey() if err != nil { return nil, err } diff --git a/channeldb/migration_01_to_11/legacy_serialization.go b/channeldb/migration_01_to_11/legacy_serialization.go index 5d731bff6f..f154f18043 100644 --- a/channeldb/migration_01_to_11/legacy_serialization.go +++ b/channeldb/migration_01_to_11/legacy_serialization.go @@ -34,7 +34,7 @@ func deserializeCloseChannelSummaryV6(r io.Reader) (*ChannelCloseSummary, error) return nil, err } - if err := readChanConfig(r, &c.LocalChanConfig); err != nil { + if err := ReadChanConfig(r, &c.LocalChanConfig); err != nil { return nil, err } diff --git a/channeldb/migration_01_to_11/migration_11_invoices_test.go b/channeldb/migration_01_to_11/migration_11_invoices_test.go index 402efa1fea..567150ec4c 100644 --- a/channeldb/migration_01_to_11/migration_11_invoices_test.go +++ b/channeldb/migration_01_to_11/migration_11_invoices_test.go @@ -6,7 +6,8 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" bitcoinCfg "github.com/btcsuite/btcd/chaincfg" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/zpay32" @@ -150,12 +151,10 @@ func signDigestCompact(hash []byte) ([]byte, error) { // Should the signature reference a compressed public key or not. isCompressedKey := true - privKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), testPrivKeyBytes) + privKey, _ := btcec.PrivKeyFromBytes(testPrivKeyBytes) - // btcec.SignCompact returns a pubkey-recoverable signature - sig, err := btcec.SignCompact( - btcec.S256(), privKey, hash, isCompressedKey, - ) + // ecdsa.SignCompact returns a pubkey-recoverable signature + sig, err := ecdsa.SignCompact(privKey, hash, isCompressedKey) if err != nil { return nil, fmt.Errorf("can't sign the hash: %v", err) } diff --git a/channeldb/migration_01_to_11/migrations.go b/channeldb/migration_01_to_11/migrations.go index 544faf1349..918a9317d7 100644 --- a/channeldb/migration_01_to_11/migrations.go +++ b/channeldb/migration_01_to_11/migrations.go @@ -6,7 +6,7 @@ import ( "encoding/binary" "fmt" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" "github.com/lightningnetwork/lnd/kvdb" ) @@ -839,7 +839,7 @@ func MigrateOutgoingPayments(tx kvdb.RwTx) error { // Since we don't have the session key for old payments, we // create a random one to be able to serialize the attempt // info. - priv, _ := btcec.NewPrivateKey(btcec.S256()) + priv, _ := btcec.NewPrivateKey() s := &PaymentAttemptInfo{ PaymentID: 0, // unknown. SessionKey: priv, // unknown. diff --git a/channeldb/migration_01_to_11/migrations_test.go b/channeldb/migration_01_to_11/migrations_test.go index 77887e8555..9c47bdcdf2 100644 --- a/channeldb/migration_01_to_11/migrations_test.go +++ b/channeldb/migration_01_to_11/migrations_test.go @@ -10,7 +10,7 @@ import ( "testing" "time" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" "github.com/davecgh/go-spew/spew" "github.com/go-errors/errors" lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" @@ -203,7 +203,7 @@ func TestMigrateOptionalChannelCloseSummaryFields(t *testing.T) { } var chanPointBuf bytes.Buffer - err = writeOutpoint(&chanPointBuf, &chanState.FundingOutpoint) + err = WriteOutpoint(&chanPointBuf, &chanState.FundingOutpoint) if err != nil { t.Fatalf("unable to write outpoint: %v", err) } @@ -303,7 +303,7 @@ func TestMigrateOptionalChannelCloseSummaryFields(t *testing.T) { t.Fatal(err) } - err = writeChanConfig(&buf, &cs.LocalChanConfig) + err = WriteChanConfig(&buf, &cs.LocalChanConfig) if err != nil { t.Fatal(err) } @@ -354,7 +354,7 @@ func TestMigrateOptionalChannelCloseSummaryFields(t *testing.T) { t.Fatal(err) } - err = writeChanConfig(&buf, &cs.LocalChanConfig) + err = WriteChanConfig(&buf, &cs.LocalChanConfig) if err != nil { t.Fatal(err) } @@ -440,8 +440,6 @@ func TestMigrateOptionalChannelCloseSummaryFields(t *testing.T) { dbChan := dbChannels[0] if !reflect.DeepEqual(dbChan, test.closeSummary) { - dbChan.RemotePub.Curve = nil - test.closeSummary.RemotePub.Curve = nil t.Fatalf("not equal: %v vs %v", spew.Sdump(dbChan), spew.Sdump(test.closeSummary)) @@ -626,7 +624,7 @@ func TestOutgoingPaymentsMigration(t *testing.T) { // Order of payments should be be preserved. old := oldPayments[i] - // Check the individial fields. + // Check the individual fields. if p.Info.Value != old.Terms.Value { t.Fatalf("value mismatch") } diff --git a/channeldb/migration_01_to_11/payments.go b/channeldb/migration_01_to_11/payments.go index 4338a30beb..7e9eebd644 100644 --- a/channeldb/migration_01_to_11/payments.go +++ b/channeldb/migration_01_to_11/payments.go @@ -9,7 +9,7 @@ import ( "sort" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/wire" lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" "github.com/lightningnetwork/lnd/kvdb" diff --git a/channeldb/migration_01_to_11/payments_test.go b/channeldb/migration_01_to_11/payments_test.go index 17f7a59a4b..ff2fbdf030 100644 --- a/channeldb/migration_01_to_11/payments_test.go +++ b/channeldb/migration_01_to_11/payments_test.go @@ -6,12 +6,12 @@ import ( "math/rand" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" ) var ( - priv, _ = btcec.NewPrivateKey(btcec.S256()) + priv, _ = btcec.NewPrivateKey() pub = priv.PubKey() ) diff --git a/channeldb/migration_01_to_11/route.go b/channeldb/migration_01_to_11/route.go index 2b43eaad2f..530cdba741 100644 --- a/channeldb/migration_01_to_11/route.go +++ b/channeldb/migration_01_to_11/route.go @@ -9,7 +9,7 @@ import ( "strconv" "strings" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" sphinx "github.com/lightningnetwork/lightning-onion" lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" "github.com/lightningnetwork/lnd/record" @@ -246,9 +246,7 @@ func (r *Route) ToSphinxPath() (*sphinx.PaymentPath, error) { // to an OnionHop with matching per-hop payload within the path as used // by the sphinx package. for i, hop := range r.Hops { - pub, err := btcec.ParsePubKey( - hop.PubKeyBytes[:], btcec.S256(), - ) + pub, err := btcec.ParsePubKey(hop.PubKeyBytes[:]) if err != nil { return nil, err } diff --git a/channeldb/migration_01_to_11/zpay32/decode.go b/channeldb/migration_01_to_11/zpay32/decode.go index 0803cc9b40..9f27543c05 100644 --- a/channeldb/migration_01_to_11/zpay32/decode.go +++ b/channeldb/migration_01_to_11/zpay32/decode.go @@ -8,11 +8,12 @@ import ( "strings" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/bech32" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcutil" - "github.com/btcsuite/btcutil/bech32" lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" ) @@ -112,8 +113,7 @@ func Decode(invoice string, net *chaincfg.Params) (*Invoice, error) { } else { headerByte := recoveryID + 27 + 4 compactSign := append([]byte{headerByte}, sig[:]...) - pubkey, _, err := btcec.RecoverCompact(btcec.S256(), - compactSign, hash) + pubkey, _, err := ecdsa.RecoverCompact(compactSign, hash) if err != nil { return nil, err } @@ -350,7 +350,7 @@ func parseDestination(data []byte) (*btcec.PublicKey, error) { return nil, err } - return btcec.ParsePubKey(base256Data, btcec.S256()) + return btcec.ParsePubKey(base256Data) } // parseExpiry converts the data (encoded in base32) into the expiry time. @@ -452,7 +452,7 @@ func parseRouteHint(data []byte) ([]HopHint, error) { for len(base256Data) > 0 { hopHint := HopHint{} - hopHint.NodeID, err = btcec.ParsePubKey(base256Data[:33], btcec.S256()) + hopHint.NodeID, err = btcec.ParsePubKey(base256Data[:33]) if err != nil { return nil, err } diff --git a/channeldb/migration_01_to_11/zpay32/hophint.go b/channeldb/migration_01_to_11/zpay32/hophint.go index 2502862811..0bafd3f35b 100644 --- a/channeldb/migration_01_to_11/zpay32/hophint.go +++ b/channeldb/migration_01_to_11/zpay32/hophint.go @@ -1,6 +1,6 @@ package zpay32 -import "github.com/btcsuite/btcd/btcec" +import "github.com/btcsuite/btcd/btcec/v2" const ( // DefaultFinalCLTVDelta is the default value to be used as the final diff --git a/channeldb/migration_01_to_11/zpay32/invoice.go b/channeldb/migration_01_to_11/zpay32/invoice.go index 83718c9ba6..cf24e4e10d 100644 --- a/channeldb/migration_01_to_11/zpay32/invoice.go +++ b/channeldb/migration_01_to_11/zpay32/invoice.go @@ -5,9 +5,9 @@ import ( "fmt" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcutil" lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" ) @@ -103,7 +103,7 @@ type MessageSigner struct { // SignCompact signs the passed hash with the node's privkey. The // returned signature should be 65 bytes, where the last 64 are the // compact signature, and the first one is a header byte. This is the - // format returned by btcec.SignCompact. + // format returned by ecdsa.SignCompact. SignCompact func(hash []byte) ([]byte, error) } @@ -266,7 +266,7 @@ func Features(features *lnwire.FeatureVector) func(*Invoice) { } // PaymentAddr is a functional option that allows callers of NewInvoice to set -// the desired payment address tht is advertised on the invoice. +// the desired payment address that is advertised on the invoice. func PaymentAddr(addr [32]byte) func(*Invoice) { return func(i *Invoice) { i.PaymentAddr = &addr diff --git a/channeldb/migtest/raw_db.go b/channeldb/migtest/raw_db.go index f6d6455ac8..f89f606540 100644 --- a/channeldb/migtest/raw_db.go +++ b/channeldb/migtest/raw_db.go @@ -72,7 +72,6 @@ func restoreDB(bucket kvdb.RwBucket, data map[string]interface{}) error { key := []byte(k) switch value := v.(type) { - // Key contains value. case string: err := bucket.Put(key, []byte(value)) @@ -114,7 +113,6 @@ func verifyDB(bucket kvdb.RBucket, data map[string]interface{}) error { key := []byte(k) switch value := v.(type) { - // Key contains value. case string: expectedValue := []byte(value) diff --git a/channeldb/mp_payment.go b/channeldb/mp_payment.go index 49a4b4df7c..0e29db95da 100644 --- a/channeldb/mp_payment.go +++ b/channeldb/mp_payment.go @@ -6,7 +6,7 @@ import ( "io" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwire" @@ -68,7 +68,7 @@ func NewHtlcAttemptInfo(attemptID uint64, sessionKey *btcec.PrivateKey, func (h *HTLCAttemptInfo) SessionKey() *btcec.PrivateKey { if h.cachedSessionKey == nil { h.cachedSessionKey, _ = btcec.PrivKeyFromBytes( - btcec.S256(), h.sessionKey[:], + h.sessionKey[:], ) } diff --git a/channeldb/nodes.go b/channeldb/nodes.go index ffc7414c50..7caee76fec 100644 --- a/channeldb/nodes.go +++ b/channeldb/nodes.go @@ -6,7 +6,7 @@ import ( "net" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/kvdb" ) @@ -298,7 +298,7 @@ func deserializeLinkNode(r io.Reader) (*LinkNode, error) { if _, err := io.ReadFull(r, pub[:]); err != nil { return nil, err } - node.IdentityPub, err = btcec.ParsePubKey(pub[:], btcec.S256()) + node.IdentityPub, err = btcec.ParsePubKey(pub[:]) if err != nil { return nil, err } diff --git a/channeldb/nodes_test.go b/channeldb/nodes_test.go index 8f60a79868..b10d147a69 100644 --- a/channeldb/nodes_test.go +++ b/channeldb/nodes_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/wire" ) @@ -23,8 +23,8 @@ func TestLinkNodeEncodeDecode(t *testing.T) { // First we'll create some initial data to use for populating our test // LinkNode instances. - _, pub1 := btcec.PrivKeyFromBytes(btcec.S256(), key[:]) - _, pub2 := btcec.PrivKeyFromBytes(btcec.S256(), rev[:]) + _, pub1 := btcec.PrivKeyFromBytes(key[:]) + _, pub2 := btcec.PrivKeyFromBytes(rev[:]) addr1, err := net.ResolveTCPAddr("tcp", "10.0.0.1:9000") if err != nil { t.Fatalf("unable to create test addr: %v", err) @@ -120,7 +120,7 @@ func TestDeleteLinkNode(t *testing.T) { cdb := fullDB.ChannelStateDB() - _, pubKey := btcec.PrivKeyFromBytes(btcec.S256(), key[:]) + _, pubKey := btcec.PrivKeyFromBytes(key[:]) addr := &net.TCPAddr{ IP: net.ParseIP("127.0.0.1"), Port: 1337, diff --git a/channeldb/options.go b/channeldb/options.go index bc53bb47e8..068a7fa015 100644 --- a/channeldb/options.go +++ b/channeldb/options.go @@ -50,6 +50,11 @@ type Options struct { // path finding. UseGraphCache bool + // NoMigration specifies that underlying backend was opened in read-only + // mode and migrations shouldn't be performed. This can be useful for + // applications that use the channeldb package as a library. + NoMigration bool + // clock is the time source used by the database. clock clock.Clock @@ -71,6 +76,7 @@ func DefaultOptions() Options { ChannelCacheSize: DefaultChannelCacheSize, PreAllocCacheNumNodes: DefaultPreAllocCacheNumNodes, UseGraphCache: true, + NoMigration: false, clock: clock.NewDefaultClock(), } } @@ -136,6 +142,14 @@ func OptionSetBatchCommitInterval(interval time.Duration) OptionModifier { } } +// OptionNoMigration allows the database to be opened in read only mode by +// disabling migrations. +func OptionNoMigration(b bool) OptionModifier { + return func(o *Options) { + o.NoMigration = b + } +} + // OptionClock sets a non-default clock dependency. func OptionClock(clock clock.Clock) OptionModifier { return func(o *Options) { @@ -143,7 +157,7 @@ func OptionClock(clock clock.Clock) OptionModifier { } } -// OptionDryRunMigration controls whether or not to intentially fail to commit a +// OptionDryRunMigration controls whether or not to intentionally fail to commit a // successful migration that occurs when opening the database. func OptionDryRunMigration(dryRun bool) OptionModifier { return func(o *Options) { diff --git a/channeldb/options_test.go b/channeldb/options_test.go index f6a873b9b4..e60c1cfcfc 100644 --- a/channeldb/options_test.go +++ b/channeldb/options_test.go @@ -10,7 +10,6 @@ import ( // TestDefaultOptions tests the default options are created as intended. func TestDefaultOptions(t *testing.T) { - opts := channeldb.DefaultOptions() require.True(t, opts.NoFreelistSync) diff --git a/channeldb/paginate.go b/channeldb/paginate.go index 48001f4896..496c236e05 100644 --- a/channeldb/paginate.go +++ b/channeldb/paginate.go @@ -69,7 +69,6 @@ func (p paginator) cursorStart() ([]byte, []byte) { // handle a few offset cases. if p.reversed { switch { - // This indicates the default case, where no offset was // specified. In that case we just start from the last // entry. diff --git a/channeldb/payment_control.go b/channeldb/payment_control.go index ada24e0c26..4ea0a95458 100644 --- a/channeldb/payment_control.go +++ b/channeldb/payment_control.go @@ -64,11 +64,11 @@ var ( "amount") // ErrNonMPPayment is returned if we try to register an MPP attempt for - // a payment that already has a non-MPP attempt regitered. + // a payment that already has a non-MPP attempt registered. ErrNonMPPayment = errors.New("payment has non-MPP attempts") // ErrMPPayment is returned if we try to register a non-MPP attempt for - // a payment that already has an MPP attempt regitered. + // a payment that already has an MPP attempt registered. ErrMPPayment = errors.New("payment has MPP attempts") // ErrMPPPaymentAddrMismatch is returned if we try to register an MPP @@ -106,7 +106,7 @@ func NewPaymentControl(db *DB) *PaymentControl { // InitPayment checks or records the given PaymentCreationInfo with the DB, // making sure it does not already exist as an in-flight payment. When this -// method returns successfully, the payment is guranteeed to be in the InFlight +// method returns successfully, the payment is guaranteed to be in the InFlight // state. func (p *PaymentControl) InitPayment(paymentHash lntypes.Hash, info *PaymentCreationInfo) error { @@ -324,7 +324,6 @@ func (p *PaymentControl) RegisterAttempt(paymentHash lntypes.Hash, hMpp := h.Route.FinalHop().MPP switch { - // We tried to register a non-MPP attempt for a MPP // payment. case mpp == nil && hMpp != nil: diff --git a/channeldb/payment_control_test.go b/channeldb/payment_control_test.go index bbd59bf0e3..b67be2e004 100644 --- a/channeldb/payment_control_test.go +++ b/channeldb/payment_control_test.go @@ -495,7 +495,7 @@ func TestPaymentControlDeleteNonInFligt(t *testing.T) { var s, i int for _, p := range dbPayments { - fmt.Println("fetch payment has status", p.Status) + t.Log("fetch payment has status", p.Status) switch p.Status { case StatusSucceeded: s++ @@ -551,7 +551,7 @@ func TestPaymentControlDeleteNonInFligt(t *testing.T) { require.Equal(t, 1, indexCount) } -// TestPaymentControlDeletePayments tests that DeletePayments correcly deletes +// TestPaymentControlDeletePayments tests that DeletePayments correctly deletes // information about completed payments from the database. func TestPaymentControlDeletePayments(t *testing.T) { t.Parallel() @@ -605,7 +605,7 @@ func TestPaymentControlDeletePayments(t *testing.T) { assertPayments(t, db, payments[2:]) } -// TestPaymentControlDeleteSinglePayment tests that DeletePayment correcly +// TestPaymentControlDeleteSinglePayment tests that DeletePayment correctly // deletes information about a completed payment from the database. func TestPaymentControlDeleteSinglePayment(t *testing.T) { t.Parallel() @@ -1254,7 +1254,6 @@ func createTestPayments(t *testing.T, p *PaymentControl, payments []*payment) { require.NoError(t, err, "unable to send htlc message") switch payments[i].status { - // Fail the attempt and the payment overall. case StatusFailed: htlcFailure := HTLCFailUnreadable diff --git a/channeldb/payments.go b/channeldb/payments.go index 496b7a5fd1..e1febf70d8 100644 --- a/channeldb/payments.go +++ b/channeldb/payments.go @@ -315,7 +315,6 @@ func fetchPayment(bucket kvdb.RBucket) (*MPPayment, error) { creationInfo, err := fetchCreationInfo(bucket) if err != nil { return nil, err - } var htlcs []HTLCAttempt @@ -358,7 +357,6 @@ func fetchPayment(bucket kvdb.RBucket) (*MPPayment, error) { var paymentStatus PaymentStatus switch { - // If any of the the HTLCs did succeed and there are no HTLCs in // flight, the payment succeeded. case !inflight && settled: @@ -383,7 +381,7 @@ func fetchPayment(bucket kvdb.RBucket) (*MPPayment, error) { }, nil } -// fetchHtlcAttempts retrives all htlc attempts made for the payment found in +// fetchHtlcAttempts retrieves all htlc attempts made for the payment found in // the given bucket. func fetchHtlcAttempts(bucket kvdb.RBucket) ([]HTLCAttempt, error) { htlcsMap := make(map[uint64]*HTLCAttempt) @@ -534,6 +532,10 @@ type PaymentsQuery struct { // fully completed. This means that pending payments, as well as failed // payments will show up if this field is set to true. IncludeIncomplete bool + + // CountTotal indicates that all payments currently present in the + // payment index (complete and incomplete) should be counted. + CountTotal bool } // PaymentsResponse contains the result of a query to the payments database. @@ -557,6 +559,11 @@ type PaymentsResponse struct { // in the event that the slice has too many events to fit into a single // response. The offset can be used to continue forward pagination. LastIndexOffset uint64 + + // TotalCount represents the total number of payments that are currently + // stored in the payment database. This will only be set if the + // CountTotal field in the query was set to true. + TotalCount uint64 } // QueryPayments is a query to the payments database which is restricted @@ -626,6 +633,35 @@ func (d *DB) QueryPayments(query PaymentsQuery) (PaymentsResponse, error) { return err } + // Counting the total number of payments is expensive, since we + // literally have to traverse the cursor linearly, which can + // take quite a while. So it's an optional query parameter. + if query.CountTotal { + var ( + totalPayments uint64 + err error + ) + countFn := func(_, _ []byte) error { + totalPayments++ + + return nil + } + + // In non-boltdb database backends, there's a faster + // ForAll query that allows for batch fetching items. + if fastBucket, ok := indexes.(kvdb.ExtendedRBucket); ok { + err = fastBucket.ForAll(countFn) + } else { + err = indexes.ForEach(countFn) + } + if err != nil { + return fmt.Errorf("error counting payments: %v", + err) + } + + resp.TotalCount = totalPayments + } + return nil }, func() { resp = PaymentsResponse{} @@ -732,7 +768,9 @@ func fetchPaymentWithSequenceNumber(tx kvdb.RTx, paymentHash lntypes.Hash, // DeletePayment deletes a payment from the DB given its payment hash. If // failedHtlcsOnly is set, only failed HTLC attempts of the payment will be // deleted. -func (d *DB) DeletePayment(paymentHash lntypes.Hash, failedHtlcsOnly bool) error { // nolint:interfacer +func (d *DB) DeletePayment(paymentHash lntypes.Hash, + failedHtlcsOnly bool) error { + return kvdb.Update(d, func(tx kvdb.RwTx) error { payments := tx.ReadWriteBucket(paymentsRootBucket) if payments == nil { @@ -1092,7 +1130,6 @@ func deserializeHTLCAttemptInfo(r io.Reader) (*HTLCAttemptInfo, error) { _, err = io.ReadFull(r, hash[:]) switch { - // Older payment attempts wouldn't have the hash set, in which case we // can just return. case err == io.EOF, err == io.ErrUnexpectedEOF: @@ -1142,6 +1179,10 @@ func serializeHop(w io.Writer, h *route.Hop) error { records = append(records, h.MPP.Record()) } + if h.Metadata != nil { + records = append(records, record.NewMetadataRecord(&h.Metadata)) + } + // Final sanity check to absolutely rule out custom records that are not // custom and write into the standard range. if err := h.CustomRecords.Validate(); err != nil { @@ -1256,6 +1297,13 @@ func deserializeHop(r io.Reader) (*route.Hop, error) { h.MPP = mpp } + metadataType := uint64(record.MetadataOnionType) + if metadata, ok := tlvMap[metadataType]; ok { + delete(tlvMap, metadataType) + + h.Metadata = metadata + } + h.CustomRecords = tlvMap return h, nil diff --git a/channeldb/payments_test.go b/channeldb/payments_test.go index 370a1d9c50..c0f6cfebb2 100644 --- a/channeldb/payments_test.go +++ b/channeldb/payments_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcwallet/walletdb" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/kvdb" @@ -19,7 +19,7 @@ import ( ) var ( - priv, _ = btcec.NewPrivateKey(btcec.S256()) + priv, _ = btcec.NewPrivateKey() pub = priv.PubKey() testHop1 = &route.Hop{ @@ -31,7 +31,8 @@ var ( 65536: []byte{}, 80001: []byte{}, }, - MPP: record.NewMPP(32, [32]byte{0x42}), + MPP: record.NewMPP(32, [32]byte{0x42}), + Metadata: []byte{1, 2, 3}, } testHop2 = &route.Hop{ @@ -487,6 +488,7 @@ func TestQueryPayments(t *testing.T) { } if tt.firstIndex != querySlice.FirstIndexOffset || tt.lastIndex != querySlice.LastIndexOffset { + t.Errorf("First or last index does not match "+ "expected index. Want (%d, %d), got (%d, %d).", tt.firstIndex, tt.lastIndex, @@ -614,9 +616,8 @@ func TestFetchPaymentWithSequenceNumber(t *testing.T) { test := test t.Run(test.name, func(t *testing.T) { - err := kvdb.Update(db, - func(tx walletdb.ReadWriteTx) error { - + err := kvdb.Update( + db, func(tx walletdb.ReadWriteTx) error { var seqNrBytes [8]byte byteOrder.PutUint64( seqNrBytes[:], test.sequenceNumber, @@ -626,7 +627,8 @@ func TestFetchPaymentWithSequenceNumber(t *testing.T) { tx, test.paymentHash, seqNrBytes[:], ) return err - }, func() {}) + }, func() {}, + ) require.Equal(t, test.expectedErr, err) }) } diff --git a/channeldb/reports.go b/channeldb/reports.go index c94c1933e8..c4e58d81e6 100644 --- a/channeldb/reports.go +++ b/channeldb/reports.go @@ -5,9 +5,9 @@ import ( "errors" "io" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/tlv" ) diff --git a/channeldb/revocation_log.go b/channeldb/revocation_log.go new file mode 100644 index 0000000000..64e22d069b --- /dev/null +++ b/channeldb/revocation_log.go @@ -0,0 +1,547 @@ +package channeldb + +import ( + "bytes" + "errors" + "io" + "math" + + "github.com/btcsuite/btcd/btcutil" + "github.com/lightningnetwork/lnd/kvdb" + "github.com/lightningnetwork/lnd/lntypes" + "github.com/lightningnetwork/lnd/tlv" +) + +// OutputIndexEmpty is used when the output index doesn't exist. +const OutputIndexEmpty = math.MaxUint16 + +var ( + // revocationLogBucketDeprecated is dedicated for storing the necessary + // delta state between channel updates required to re-construct a past + // state in order to punish a counterparty attempting a non-cooperative + // channel closure. This key should be accessed from within the + // sub-bucket of a target channel, identified by its channel point. + // + // Deprecated: This bucket is kept for read-only in case the user + // choose not to migrate the old data. + revocationLogBucketDeprecated = []byte("revocation-log-key") + + // revocationLogBucket is a sub-bucket under openChannelBucket. This + // sub-bucket is dedicated for storing the minimal info required to + // re-construct a past state in order to punish a counterparty + // attempting a non-cooperative channel closure. + revocationLogBucket = []byte("revocation-log") + + // ErrLogEntryNotFound is returned when we cannot find a log entry at + // the height requested in the revocation log. + ErrLogEntryNotFound = errors.New("log entry not found") + + // ErrOutputIndexTooBig is returned when the output index is greater + // than uint16. + ErrOutputIndexTooBig = errors.New("output index is over uint16") +) + +// HTLCEntry specifies the minimal info needed to be stored on disk for ALL the +// historical HTLCs, which is useful for constructing RevocationLog when a +// breach is detected. +// The actual size of each HTLCEntry varies based on its RHash and Amt(sat), +// summarized as follows, +// +// | RHash empty | Amt<=252 | Amt<=65,535 | Amt<=4,294,967,295 | otherwise | +// |:-----------:|:--------:|:-----------:|:------------------:|:---------:| +// | true | 19 | 21 | 23 | 26 | +// | false | 51 | 53 | 55 | 58 | +// +// So the size varies from 19 bytes to 58 bytes, where most likely to be 23 or +// 55 bytes. +// +// NOTE: all the fields saved to disk use the primitive go types so they can be +// made into tlv records without further conversion. +type HTLCEntry struct { + // RHash is the payment hash of the HTLC. + RHash [32]byte + + // RefundTimeout is the absolute timeout on the HTLC that the sender + // must wait before reclaiming the funds in limbo. + RefundTimeout uint32 + + // OutputIndex is the output index for this particular HTLC output + // within the commitment transaction. + // + // NOTE: we use uint16 instead of int32 here to save us 2 bytes, which + // gives us a max number of HTLCs of 65K. + OutputIndex uint16 + + // Incoming denotes whether we're the receiver or the sender of this + // HTLC. + // + // NOTE: this field is the memory representation of the field + // incomingUint. + Incoming bool + + // Amt is the amount of satoshis this HTLC escrows. + // + // NOTE: this field is the memory representation of the field amtUint. + Amt btcutil.Amount + + // amtTlv is the uint64 format of Amt. This field is created so we can + // easily make it into a tlv record and save it to disk. + // + // NOTE: we keep this field for accounting purpose only. If the disk + // space becomes an issue, we could delete this field to save us extra + // 8 bytes. + amtTlv uint64 + + // incomingTlv is the uint8 format of Incoming. This field is created + // so we can easily make it into a tlv record and save it to disk. + incomingTlv uint8 +} + +// RHashLen is used by MakeDynamicRecord to return the size of the RHash. +// +// NOTE: for zero hash, we return a length 0. +func (h *HTLCEntry) RHashLen() uint64 { + if h.RHash == lntypes.ZeroHash { + return 0 + } + return 32 +} + +// RHashEncoder is the customized encoder which skips encoding the empty hash. +func RHashEncoder(w io.Writer, val interface{}, buf *[8]byte) error { + v, ok := val.(*[32]byte) + if !ok { + return tlv.NewTypeForEncodingErr(val, "RHash") + } + + // If the value is an empty hash, we will skip encoding it. + if *v == lntypes.ZeroHash { + return nil + } + + return tlv.EBytes32(w, v, buf) +} + +// RHashDecoder is the customized decoder which skips decoding the empty hash. +func RHashDecoder(r io.Reader, val interface{}, buf *[8]byte, l uint64) error { + v, ok := val.(*[32]byte) + if !ok { + return tlv.NewTypeForEncodingErr(val, "RHash") + } + + // If the length is zero, we will skip encoding the empty hash. + if l == 0 { + return nil + } + + return tlv.DBytes32(r, v, buf, 32) +} + +// toTlvStream converts an HTLCEntry record into a tlv representation. +func (h *HTLCEntry) toTlvStream() (*tlv.Stream, error) { + const ( + // A set of tlv type definitions used to serialize htlc entries + // to the database. We define it here instead of the head of + // the file to avoid naming conflicts. + // + // NOTE: A migration should be added whenever this list + // changes. + rHashType tlv.Type = 0 + refundTimeoutType tlv.Type = 1 + outputIndexType tlv.Type = 2 + incomingType tlv.Type = 3 + amtType tlv.Type = 4 + ) + + return tlv.NewStream( + tlv.MakeDynamicRecord( + rHashType, &h.RHash, h.RHashLen, + RHashEncoder, RHashDecoder, + ), + tlv.MakePrimitiveRecord( + refundTimeoutType, &h.RefundTimeout, + ), + tlv.MakePrimitiveRecord( + outputIndexType, &h.OutputIndex, + ), + tlv.MakePrimitiveRecord(incomingType, &h.incomingTlv), + // We will save 3 bytes if the amount is less or equal to + // 4,294,967,295 msat, or roughly 0.043 bitcoin. + tlv.MakeBigSizeRecord(amtType, &h.amtTlv), + ) +} + +// RevocationLog stores the info needed to construct a breach retribution. Its +// fields can be viewed as a subset of a ChannelCommitment's. In the database, +// all historical versions of the RevocationLog are saved using the +// CommitHeight as the key. +// +// NOTE: all the fields use the primitive go types so they can be made into tlv +// records without further conversion. +type RevocationLog struct { + // OurOutputIndex specifies our output index in this commitment. In a + // remote commitment transaction, this is the to remote output index. + OurOutputIndex uint16 + + // TheirOutputIndex specifies their output index in this commitment. In + // a remote commitment transaction, this is the to local output index. + TheirOutputIndex uint16 + + // CommitTxHash is the hash of the latest version of the commitment + // state, broadcast able by us. + CommitTxHash [32]byte + + // HTLCEntries is the set of HTLCEntry's that are pending at this + // particular commitment height. + HTLCEntries []*HTLCEntry +} + +// toTlvStream converts an RevocationLog record into a tlv representation. +func (rl *RevocationLog) toTlvStream() (*tlv.Stream, error) { + const ( + // A set of tlv type definitions used to serialize the body of + // revocation logs to the database. We define it here instead + // of the head of the file to avoid naming conflicts. + // + // NOTE: A migration should be added whenever this list + // changes. + ourOutputIndexType tlv.Type = 0 + theirOutputIndexType tlv.Type = 1 + commitTxHashType tlv.Type = 2 + ) + + return tlv.NewStream( + tlv.MakePrimitiveRecord(ourOutputIndexType, &rl.OurOutputIndex), + tlv.MakePrimitiveRecord( + theirOutputIndexType, &rl.TheirOutputIndex, + ), + tlv.MakePrimitiveRecord(commitTxHashType, &rl.CommitTxHash), + ) +} + +// putRevocationLog uses the fields `CommitTx` and `Htlcs` from a +// ChannelCommitment to construct a revocation log entry and saves them to +// disk. It also saves our output index and their output index, which are +// useful when creating breach retribution. +func putRevocationLog(bucket kvdb.RwBucket, commit *ChannelCommitment, + ourOutputIndex, theirOutputIndex uint32) error { + + // Sanity check that the output indexes can be safely converted. + if ourOutputIndex > math.MaxUint16 { + return ErrOutputIndexTooBig + } + if theirOutputIndex > math.MaxUint16 { + return ErrOutputIndexTooBig + } + + rl := &RevocationLog{ + OurOutputIndex: uint16(ourOutputIndex), + TheirOutputIndex: uint16(theirOutputIndex), + CommitTxHash: commit.CommitTx.TxHash(), + HTLCEntries: make([]*HTLCEntry, 0, len(commit.Htlcs)), + } + + for _, htlc := range commit.Htlcs { + // Skip dust HTLCs. + if htlc.OutputIndex < 0 { + continue + } + + // Sanity check that the output indexes can be safely + // converted. + if htlc.OutputIndex > math.MaxUint16 { + return ErrOutputIndexTooBig + } + + entry := &HTLCEntry{ + RHash: htlc.RHash, + RefundTimeout: htlc.RefundTimeout, + Incoming: htlc.Incoming, + OutputIndex: uint16(htlc.OutputIndex), + Amt: htlc.Amt.ToSatoshis(), + } + rl.HTLCEntries = append(rl.HTLCEntries, entry) + } + + var b bytes.Buffer + err := serializeRevocationLog(&b, rl) + if err != nil { + return err + } + + logEntrykey := makeLogKey(commit.CommitHeight) + return bucket.Put(logEntrykey[:], b.Bytes()) +} + +// fetchRevocationLog queries the revocation log bucket to find an log entry. +// Return an error if not found. +func fetchRevocationLog(log kvdb.RBucket, + updateNum uint64) (RevocationLog, error) { + + logEntrykey := makeLogKey(updateNum) + commitBytes := log.Get(logEntrykey[:]) + if commitBytes == nil { + return RevocationLog{}, ErrLogEntryNotFound + } + + commitReader := bytes.NewReader(commitBytes) + + return deserializeRevocationLog(commitReader) +} + +// serializeRevocationLog serializes a RevocationLog record based on tlv +// format. +func serializeRevocationLog(w io.Writer, rl *RevocationLog) error { + // Create the tlv stream. + tlvStream, err := rl.toTlvStream() + if err != nil { + return err + } + + // Write the tlv stream. + if err := writeTlvStream(w, tlvStream); err != nil { + return err + } + + // Write the HTLCs. + return serializeHTLCEntries(w, rl.HTLCEntries) +} + +// serializeHTLCEntries serializes a list of HTLCEntry records based on tlv +// format. +func serializeHTLCEntries(w io.Writer, htlcs []*HTLCEntry) error { + for _, htlc := range htlcs { + // Patch the incomingTlv field. + if htlc.Incoming { + htlc.incomingTlv = 1 + } + + // Patch the amtTlv field. + htlc.amtTlv = uint64(htlc.Amt) + + // Create the tlv stream. + tlvStream, err := htlc.toTlvStream() + if err != nil { + return err + } + + // Write the tlv stream. + if err := writeTlvStream(w, tlvStream); err != nil { + return err + } + } + + return nil +} + +// deserializeRevocationLog deserializes a RevocationLog based on tlv format. +func deserializeRevocationLog(r io.Reader) (RevocationLog, error) { + var rl RevocationLog + + // Create the tlv stream. + tlvStream, err := rl.toTlvStream() + if err != nil { + return rl, err + } + + // Read the tlv stream. + if err := readTlvStream(r, tlvStream); err != nil { + return rl, err + } + + // Read the HTLC entries. + rl.HTLCEntries, err = deserializeHTLCEntries(r) + + return rl, err +} + +// deserializeHTLCEntries deserializes a list of HTLC entries based on tlv +// format. +func deserializeHTLCEntries(r io.Reader) ([]*HTLCEntry, error) { + var htlcs []*HTLCEntry + + for { + var htlc HTLCEntry + + // Create the tlv stream. + tlvStream, err := htlc.toTlvStream() + if err != nil { + return nil, err + } + + // Read the HTLC entry. + if err := readTlvStream(r, tlvStream); err != nil { + // We've reached the end when hitting an EOF. + if err == io.ErrUnexpectedEOF { + break + } + return nil, err + } + + // Patch the Incoming field. + if htlc.incomingTlv == 1 { + htlc.Incoming = true + } + + // Patch the Amt field. + htlc.Amt = btcutil.Amount(htlc.amtTlv) + + // Append the entry. + htlcs = append(htlcs, &htlc) + } + + return htlcs, nil +} + +// writeTlvStream is a helper function that encodes the tlv stream into the +// writer. +func writeTlvStream(w io.Writer, s *tlv.Stream) error { + var b bytes.Buffer + if err := s.Encode(&b); err != nil { + return err + } + // Write the stream's length as a varint. + err := tlv.WriteVarInt(w, uint64(b.Len()), &[8]byte{}) + if err != nil { + return err + } + + if _, err = w.Write(b.Bytes()); err != nil { + return err + } + + return nil +} + +// readTlvStream is a helper function that decodes the tlv stream from the +// reader. +func readTlvStream(r io.Reader, s *tlv.Stream) error { + var bodyLen uint64 + + // Read the stream's length. + bodyLen, err := tlv.ReadVarInt(r, &[8]byte{}) + switch { + // We'll convert any EOFs to ErrUnexpectedEOF, since this results in an + // invalid record. + case err == io.EOF: + return io.ErrUnexpectedEOF + + // Other unexpected errors. + case err != nil: + return err + } + + // TODO(yy): add overflow check. + lr := io.LimitReader(r, int64(bodyLen)) + return s.Decode(lr) +} + +// fetchOldRevocationLog finds the revocation log from the deprecated +// sub-bucket. +func fetchOldRevocationLog(log kvdb.RBucket, + updateNum uint64) (ChannelCommitment, error) { + + logEntrykey := makeLogKey(updateNum) + commitBytes := log.Get(logEntrykey[:]) + if commitBytes == nil { + return ChannelCommitment{}, ErrLogEntryNotFound + } + + commitReader := bytes.NewReader(commitBytes) + return deserializeChanCommit(commitReader) +} + +// fetchRevocationLogCompatible finds the revocation log from both the +// revocationLogBucket and revocationLogBucketDeprecated for compatibility +// concern. It returns three values, +// - RevocationLog, if this is non-nil, it means we've found the log in the +// new bucket. +// - ChannelCommitment, if this is non-nil, it means we've found the log in the +// old bucket. +// - error, this can happen if the log cannot be found in neither buckets. +func fetchRevocationLogCompatible(chanBucket kvdb.RBucket, + updateNum uint64) (*RevocationLog, *ChannelCommitment, error) { + + // Look into the new bucket first. + logBucket := chanBucket.NestedReadBucket(revocationLogBucket) + if logBucket != nil { + rl, err := fetchRevocationLog(logBucket, updateNum) + // We've found the record, no need to visit the old bucket. + if err == nil { + return &rl, nil, nil + } + + // Return the error if it doesn't say the log cannot be found. + if err != ErrLogEntryNotFound { + return nil, nil, err + } + } + + // Otherwise, look into the old bucket and try to find the log there. + oldBucket := chanBucket.NestedReadBucket(revocationLogBucketDeprecated) + if oldBucket != nil { + c, err := fetchOldRevocationLog(oldBucket, updateNum) + if err != nil { + return nil, nil, err + } + + // Found an old record and return it. + return nil, &c, nil + } + + // If both the buckets are nil, then the sub-buckets haven't been + // created yet. + if logBucket == nil && oldBucket == nil { + return nil, nil, ErrNoPastDeltas + } + + // Otherwise, we've tried to query the new bucket but the log cannot be + // found. + return nil, nil, ErrLogEntryNotFound +} + +// fetchLogBucket returns a read bucket by visiting both the new and the old +// bucket. +func fetchLogBucket(chanBucket kvdb.RBucket) (kvdb.RBucket, error) { + logBucket := chanBucket.NestedReadBucket(revocationLogBucket) + if logBucket == nil { + logBucket = chanBucket.NestedReadBucket( + revocationLogBucketDeprecated, + ) + if logBucket == nil { + return nil, ErrNoPastDeltas + } + } + + return logBucket, nil +} + +// deleteLogBucket deletes the both the new and old revocation log buckets. +func deleteLogBucket(chanBucket kvdb.RwBucket) error { + // Check if the bucket exists and delete it. + logBucket := chanBucket.NestedReadWriteBucket( + revocationLogBucket, + ) + if logBucket != nil { + err := chanBucket.DeleteNestedBucket(revocationLogBucket) + if err != nil { + return err + } + } + + // We also check whether the old revocation log bucket exists + // and delete it if so. + oldLogBucket := chanBucket.NestedReadWriteBucket( + revocationLogBucketDeprecated, + ) + if oldLogBucket != nil { + err := chanBucket.DeleteNestedBucket( + revocationLogBucketDeprecated, + ) + if err != nil { + return err + } + } + + return nil +} diff --git a/channeldb/revocation_log_test.go b/channeldb/revocation_log_test.go new file mode 100644 index 0000000000..1b17c9b28e --- /dev/null +++ b/channeldb/revocation_log_test.go @@ -0,0 +1,622 @@ +package channeldb + +import ( + "bytes" + "io" + "math" + "math/rand" + "testing" + + "github.com/btcsuite/btcd/btcutil" + "github.com/lightningnetwork/lnd/kvdb" + "github.com/lightningnetwork/lnd/lntest/channels" + "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/tlv" + "github.com/stretchr/testify/require" +) + +const ( + // testType is used for creating a testing tlv record type. We use 10 + // here so it's easier to be recognized by its hex value, 0xa. + testType tlv.Type = 10 +) + +var ( + // testValue is used for creating tlv record. + testValue = uint8(255) // 0xff + + // testValueBytes is the tlv encoded testValue. + testValueBytes = []byte{ + 0x3, // total length = 3 + 0xa, // type = 10 + 0x1, // length = 1 + 0xff, // value = 255 + } + + testHTLCEntry = HTLCEntry{ + RefundTimeout: 740_000, + OutputIndex: 10, + Incoming: true, + Amt: 1000_000, + amtTlv: 1000_000, + incomingTlv: 1, + } + testHTLCEntryBytes = []byte{ + // Body length 23. + 0x16, + // Rhash tlv. + 0x0, 0x0, + // RefundTimeout tlv. + 0x1, 0x4, 0x0, 0xb, 0x4a, 0xa0, + // OutputIndex tlv. + 0x2, 0x2, 0x0, 0xa, + // Incoming tlv. + 0x3, 0x1, 0x1, + // Amt tlv. + 0x4, 0x5, 0xfe, 0x0, 0xf, 0x42, 0x40, + } + + testChannelCommit = ChannelCommitment{ + CommitHeight: 999, + LocalBalance: lnwire.MilliSatoshi(9000), + RemoteBalance: lnwire.MilliSatoshi(3000), + CommitFee: btcutil.Amount(rand.Int63()), + FeePerKw: btcutil.Amount(5000), + CommitTx: channels.TestFundingTx, + CommitSig: bytes.Repeat([]byte{1}, 71), + Htlcs: []HTLC{{ + RefundTimeout: testHTLCEntry.RefundTimeout, + OutputIndex: int32(testHTLCEntry.OutputIndex), + Incoming: testHTLCEntry.Incoming, + Amt: lnwire.NewMSatFromSatoshis( + testHTLCEntry.Amt, + ), + }}, + } + + testRevocationLog = RevocationLog{ + OurOutputIndex: 0, + TheirOutputIndex: 1, + CommitTxHash: testChannelCommit.CommitTx.TxHash(), + HTLCEntries: []*HTLCEntry{&testHTLCEntry}, + } + testRevocationLogBytes = []byte{ + // Body length 42. + 0x2a, + // OurOutputIndex tlv. + 0x0, 0x2, 0x0, 0x0, + // TheirOutputIndex tlv. + 0x1, 0x2, 0x0, 0x1, + // CommitTxHash tlv. + 0x2, 0x20, + 0x28, 0x76, 0x2, 0x59, 0x1d, 0x9d, 0x64, 0x86, + 0x6e, 0x60, 0x29, 0x23, 0x1d, 0x5e, 0xc5, 0xe6, + 0xbd, 0xf7, 0xd3, 0x9b, 0x16, 0x7d, 0x0, 0xff, + 0xc8, 0x22, 0x51, 0xb1, 0x5b, 0xa0, 0xbf, 0xd, + } +) + +func TestWriteTLVStream(t *testing.T) { + t.Parallel() + + // Create a dummy tlv stream for testing. + ts, err := tlv.NewStream( + tlv.MakePrimitiveRecord(testType, &testValue), + ) + require.NoError(t, err) + + // Write the tlv stream. + buf := bytes.NewBuffer([]byte{}) + err = writeTlvStream(buf, ts) + require.NoError(t, err) + + // Check the bytes are written as expected. + require.Equal(t, testValueBytes, buf.Bytes()) +} + +func TestReadTLVStream(t *testing.T) { + t.Parallel() + + var valueRead uint8 + + // Create a dummy tlv stream for testing. + ts, err := tlv.NewStream( + tlv.MakePrimitiveRecord(testType, &valueRead), + ) + require.NoError(t, err) + + // Read the tlv stream. + buf := bytes.NewBuffer(testValueBytes) + err = readTlvStream(buf, ts) + require.NoError(t, err) + + // Check the bytes are read as expected. + require.Equal(t, testValue, valueRead) +} + +func TestReadTLVStreamErr(t *testing.T) { + t.Parallel() + + var valueRead uint8 + + // Create a dummy tlv stream for testing. + ts, err := tlv.NewStream( + tlv.MakePrimitiveRecord(testType, &valueRead), + ) + require.NoError(t, err) + + // Use empty bytes to cause an EOF. + b := []byte{} + + // Read the tlv stream. + buf := bytes.NewBuffer(b) + err = readTlvStream(buf, ts) + require.ErrorIs(t, err, io.ErrUnexpectedEOF) + + // Check the bytes are not read. + require.Zero(t, valueRead) +} + +func TestSerializeHTLCEntriesEmptyRHash(t *testing.T) { + t.Parallel() + + // Copy the testHTLCEntry. + entry := testHTLCEntry + + // Set the internal fields to empty values so we can test the bytes are + // padded. + entry.incomingTlv = 0 + entry.amtTlv = 0 + + // Write the tlv stream. + buf := bytes.NewBuffer([]byte{}) + err := serializeHTLCEntries(buf, []*HTLCEntry{&entry}) + require.NoError(t, err) + + // Check the bytes are read as expected. + require.Equal(t, testHTLCEntryBytes, buf.Bytes()) +} + +func TestSerializeHTLCEntries(t *testing.T) { + t.Parallel() + + // Copy the testHTLCEntry. + entry := testHTLCEntry + + // Create a fake rHash. + rHashBytes := bytes.Repeat([]byte{10}, 32) + copy(entry.RHash[:], rHashBytes) + + // Construct the serialized bytes. + // + // Exclude the first 3 bytes, which are total length, RHash type and + // RHash length(0). + partialBytes := testHTLCEntryBytes[3:] + + // Write the total length and RHash tlv. + expectedBytes := []byte{0x36, 0x0, 0x20} + expectedBytes = append(expectedBytes, rHashBytes...) + + // Append the rest. + expectedBytes = append(expectedBytes, partialBytes...) + + buf := bytes.NewBuffer([]byte{}) + err := serializeHTLCEntries(buf, []*HTLCEntry{&entry}) + require.NoError(t, err) + + // Check the bytes are read as expected. + require.Equal(t, expectedBytes, buf.Bytes()) +} + +func TestSerializeRevocationLog(t *testing.T) { + t.Parallel() + + // Copy the testRevocationLog. + rl := testRevocationLog + + // Write the tlv stream. + buf := bytes.NewBuffer([]byte{}) + err := serializeRevocationLog(buf, &rl) + require.NoError(t, err) + + // Check the expected bytes on the body of the revocation log. + bodyIndex := buf.Len() - len(testHTLCEntryBytes) + require.Equal(t, testRevocationLogBytes, buf.Bytes()[:bodyIndex]) +} + +func TestDerializeHTLCEntriesEmptyRHash(t *testing.T) { + t.Parallel() + + // Read the tlv stream. + buf := bytes.NewBuffer(testHTLCEntryBytes) + htlcs, err := deserializeHTLCEntries(buf) + require.NoError(t, err) + + // Check the bytes are read as expected. + require.Len(t, htlcs, 1) + require.Equal(t, &testHTLCEntry, htlcs[0]) +} + +func TestDerializeHTLCEntries(t *testing.T) { + t.Parallel() + + // Copy the testHTLCEntry. + entry := testHTLCEntry + + // Create a fake rHash. + rHashBytes := bytes.Repeat([]byte{10}, 32) + copy(entry.RHash[:], rHashBytes) + + // Construct the serialized bytes. + // + // Exclude the first 3 bytes, which are total length, RHash type and + // RHash length(0). + partialBytes := testHTLCEntryBytes[3:] + + // Write the total length and RHash tlv. + testBytes := append([]byte{0x36, 0x0, 0x20}, rHashBytes...) + + // Append the rest. + testBytes = append(testBytes, partialBytes...) + + // Read the tlv stream. + buf := bytes.NewBuffer(testBytes) + htlcs, err := deserializeHTLCEntries(buf) + require.NoError(t, err) + + // Check the bytes are read as expected. + require.Len(t, htlcs, 1) + require.Equal(t, &entry, htlcs[0]) +} + +func TestDerializeRevocationLog(t *testing.T) { + t.Parallel() + + // Construct the full bytes. + b := testRevocationLogBytes + b = append(b, testHTLCEntryBytes...) + + // Read the tlv stream. + buf := bytes.NewBuffer(b) + rl, err := deserializeRevocationLog(buf) + require.NoError(t, err) + + // Check the bytes are read as expected. + require.Len(t, rl.HTLCEntries, 1) + require.Equal(t, testRevocationLog, rl) +} + +func TestFetchLogBucket(t *testing.T) { + t.Parallel() + + fullDB, cleanUp, err := MakeTestDB() + require.NoError(t, err) + defer cleanUp() + + backend := fullDB.ChannelStateDB().backend + + // Test that when neither of the buckets exists, an error is returned. + err = kvdb.Update(backend, func(tx kvdb.RwTx) error { + chanBucket, err := tx.CreateTopLevelBucket(openChannelBucket) + require.NoError(t, err) + + // Check an error is returned when there's no sub bucket. + _, err = fetchLogBucket(chanBucket) + return err + }, func() {}) + require.ErrorIs(t, err, ErrNoPastDeltas) + + // Test a successful fetch. + err = kvdb.Update(backend, func(tx kvdb.RwTx) error { + chanBucket, err := tx.CreateTopLevelBucket(openChannelBucket) + require.NoError(t, err) + + _, err = chanBucket.CreateBucket(revocationLogBucket) + require.NoError(t, err) + + // Check an error is returned when there's no sub bucket. + _, err = fetchLogBucket(chanBucket) + return err + }, func() {}) + require.NoError(t, err) +} + +func TestDeleteLogBucket(t *testing.T) { + t.Parallel() + + fullDB, cleanUp, err := MakeTestDB() + require.NoError(t, err) + defer cleanUp() + + backend := fullDB.ChannelStateDB().backend + + err = kvdb.Update(backend, func(tx kvdb.RwTx) error { + // Create the buckets. + chanBucket, _, err := createTestRevocatoinLogBuckets(tx) + require.NoError(t, err) + + // Create the buckets again should give us an error. + _, _, err = createTestRevocatoinLogBuckets(tx) + require.ErrorIs(t, err, kvdb.ErrBucketExists) + + // Delete both buckets. + err = deleteLogBucket(chanBucket) + require.NoError(t, err) + + // Create the buckets again should give us NO error. + _, _, err = createTestRevocatoinLogBuckets(tx) + return err + }, func() {}) + require.NoError(t, err) +} + +func TestPutRevocationLog(t *testing.T) { + t.Parallel() + + // Create a test commit that has a large htlc output index. + testHtlc := HTLC{OutputIndex: math.MaxUint16 + 1} + testCommit := testChannelCommit + testCommit.Htlcs = []HTLC{testHtlc} + + // Create a test commit that has a dust HTLC. + testHtlcDust := HTLC{OutputIndex: -1} + testCommitDust := testChannelCommit + testCommitDust.Htlcs = append(testCommitDust.Htlcs, testHtlcDust) + + testCases := []struct { + name string + commit ChannelCommitment + ourIndex uint32 + theirIndex uint32 + expectedErr error + expectedLog RevocationLog + }{ + { + // Test a normal put operation. + name: "successful put", + commit: testChannelCommit, + ourIndex: 0, + theirIndex: 1, + expectedErr: nil, + expectedLog: testRevocationLog, + }, + { + // Test our index too big. + name: "our index too big", + commit: testChannelCommit, + ourIndex: math.MaxUint16 + 1, + theirIndex: 1, + expectedErr: ErrOutputIndexTooBig, + expectedLog: RevocationLog{}, + }, + { + // Test their index too big. + name: "their index too big", + commit: testChannelCommit, + ourIndex: 0, + theirIndex: math.MaxUint16 + 1, + expectedErr: ErrOutputIndexTooBig, + expectedLog: RevocationLog{}, + }, + { + // Test htlc output index too big. + name: "htlc index too big", + commit: testCommit, + ourIndex: 0, + theirIndex: 1, + expectedErr: ErrOutputIndexTooBig, + expectedLog: RevocationLog{}, + }, + { + // Test dust htlc is not saved. + name: "dust htlc not saved", + commit: testCommitDust, + ourIndex: 0, + theirIndex: 1, + expectedErr: nil, + expectedLog: testRevocationLog, + }, + } + + for _, tc := range testCases { + tc := tc + + fullDB, cleanUp, err := MakeTestDB() + require.NoError(t, err) + defer cleanUp() + + backend := fullDB.ChannelStateDB().backend + + // Construct the testing db transaction. + dbTx := func(tx kvdb.RwTx) (RevocationLog, error) { + // Create the buckets. + _, bucket, err := createTestRevocatoinLogBuckets(tx) + require.NoError(t, err) + + // Save the log. + err = putRevocationLog( + bucket, &tc.commit, tc.ourIndex, tc.theirIndex, + ) + if err != nil { + return RevocationLog{}, err + } + + // Read the saved log. + return fetchRevocationLog( + bucket, tc.commit.CommitHeight, + ) + } + + t.Run(tc.name, func(t *testing.T) { + var rl RevocationLog + err := kvdb.Update(backend, func(tx kvdb.RwTx) error { + record, err := dbTx(tx) + rl = record + return err + }, func() {}) + + require.Equal(t, tc.expectedErr, err) + require.Equal(t, tc.expectedLog, rl) + }) + } +} + +func TestFetchRevocationLogCompatible(t *testing.T) { + t.Parallel() + + knownHeight := testChannelCommit.CommitHeight + unknownHeight := knownHeight + 1 + logKey := makeLogKey(knownHeight) + + testCases := []struct { + name string + updateNum uint64 + expectedErr error + createRl bool + createCommit bool + expectRl bool + expectCommit bool + }{ + { + // Test we can fetch the new log. + name: "fetch new log", + updateNum: knownHeight, + expectedErr: nil, + createRl: true, + expectRl: true, + }, + { + // Test we can fetch the legacy log. + name: "fetch legacy log", + updateNum: knownHeight, + expectedErr: nil, + createCommit: true, + expectCommit: true, + }, + { + // Test we only fetch the new log when both logs exist. + name: "fetch new log only", + updateNum: knownHeight, + expectedErr: nil, + createRl: true, + createCommit: true, + expectRl: true, + }, + { + // Test no past deltas when the buckets do not exist. + name: "no buckets created", + updateNum: unknownHeight, + expectedErr: ErrNoPastDeltas, + }, + { + // Test no logs found when the height is unknown. + name: "no log found", + updateNum: unknownHeight, + expectedErr: ErrLogEntryNotFound, + createRl: true, + createCommit: true, + }, + } + + for _, tc := range testCases { + tc := tc + + fullDB, cleanUp, err := MakeTestDB() + require.NoError(t, err) + defer cleanUp() + + backend := fullDB.ChannelStateDB().backend + + var ( + rl *RevocationLog + commit *ChannelCommitment + ) + + // Setup the buckets and fill the test data if specified. + err = kvdb.Update(backend, func(tx kvdb.RwTx) error { + // Create the root bucket. + cb, err := tx.CreateTopLevelBucket(openChannelBucket) + require.NoError(t, err) + + // Create the revocation log if specified. + if tc.createRl { + lb, err := cb.CreateBucket(revocationLogBucket) + require.NoError(t, err) + + err = putRevocationLog( + lb, &testChannelCommit, 0, 1, + ) + require.NoError(t, err) + } + + // Create the channel commit if specified. + if tc.createCommit { + legacyBucket, err := cb.CreateBucket( + revocationLogBucketDeprecated, + ) + require.NoError(t, err) + + buf := bytes.NewBuffer([]byte{}) + err = serializeChanCommit( + buf, &testChannelCommit, + ) + require.NoError(t, err) + + err = legacyBucket.Put(logKey[:], buf.Bytes()) + require.NoError(t, err) + } + + return nil + }, func() {}) + + // Construct the testing db transaction. + dbTx := func(tx kvdb.RTx) error { + cb := tx.ReadBucket(openChannelBucket) + + rl, commit, err = fetchRevocationLogCompatible( + cb, tc.updateNum, + ) + return err + } + + t.Run(tc.name, func(t *testing.T) { + err := kvdb.View(backend, dbTx, func() {}) + require.Equal(t, tc.expectedErr, err) + + // Check the expected revocation log is returned. + if tc.expectRl { + require.NotNil(t, rl) + } else { + require.Nil(t, rl) + } + + // Check the expected channel commit is returned. + if tc.expectCommit { + require.NotNil(t, commit) + } else { + require.Nil(t, commit) + } + }) + } +} + +func createTestRevocatoinLogBuckets(tx kvdb.RwTx) (kvdb.RwBucket, + kvdb.RwBucket, error) { + + chanBucket, err := tx.CreateTopLevelBucket(openChannelBucket) + if err != nil { + return nil, nil, err + } + + logBucket, err := chanBucket.CreateBucket(revocationLogBucket) + if err != nil { + return nil, nil, err + } + + _, err = chanBucket.CreateBucket(revocationLogBucketDeprecated) + if err != nil { + return nil, nil, err + } + + return chanBucket, logBucket, nil +} diff --git a/channeldb/setup_test.go b/channeldb/setup_test.go new file mode 100644 index 0000000000..91d3bfb470 --- /dev/null +++ b/channeldb/setup_test.go @@ -0,0 +1,11 @@ +package channeldb + +import ( + "testing" + + "github.com/lightningnetwork/lnd/kvdb" +) + +func TestMain(m *testing.M) { + kvdb.RunTests(m) +} diff --git a/channeldb/waitingproof.go b/channeldb/waitingproof.go index 7bb53e1798..c6b2b9df52 100644 --- a/channeldb/waitingproof.go +++ b/channeldb/waitingproof.go @@ -1,13 +1,11 @@ package channeldb import ( + "bytes" "encoding/binary" "fmt" - "sync" - "io" - - "bytes" + "sync" "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/kvdb" diff --git a/channeldb/waitingproof_test.go b/channeldb/waitingproof_test.go index cb5b900f8f..4fd49a991e 100644 --- a/channeldb/waitingproof_test.go +++ b/channeldb/waitingproof_test.go @@ -1,9 +1,8 @@ package channeldb import ( - "testing" - "reflect" + "testing" "github.com/davecgh/go-spew/spew" "github.com/go-errors/errors" diff --git a/channelnotifier/channelnotifier.go b/channelnotifier/channelnotifier.go index 2cf6015c4d..7c6cc30955 100644 --- a/channelnotifier/channelnotifier.go +++ b/channelnotifier/channelnotifier.go @@ -87,7 +87,7 @@ func New(chanDB *channeldb.ChannelStateDB) *ChannelNotifier { func (c *ChannelNotifier) Start() error { var err error c.started.Do(func() { - log.Trace("ChannelNotifier starting") + log.Info("ChannelNotifier starting") err = c.ntfnServer.Start() }) return err diff --git a/chanrestore.go b/chanrestore.go index 909c2e397d..54825de567 100644 --- a/chanrestore.go +++ b/chanrestore.go @@ -5,7 +5,7 @@ import ( "math" "net" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/chanbackup" @@ -133,7 +133,6 @@ func (c *chanDBRestorer) openChannelShell(backup chanbackup.Single) ( var chanType channeldb.ChannelType switch backup.Version { - case chanbackup.DefaultSingleVersion: chanType = channeldb.SingleFunderBit diff --git a/cluster/etcd_elector.go b/cluster/etcd_elector.go index a33ff24cfa..71057c3606 100644 --- a/cluster/etcd_elector.go +++ b/cluster/etcd_elector.go @@ -23,7 +23,7 @@ const ( // Enforce that etcdLeaderElector implements the LeaderElector interface. var _ LeaderElector = (*etcdLeaderElector)(nil) -// etcdLeaderElector is an implemetation of LeaderElector using etcd as the +// etcdLeaderElector is an implementation of LeaderElector using etcd as the // election governor. type etcdLeaderElector struct { id string @@ -35,7 +35,7 @@ type etcdLeaderElector struct { // newEtcdLeaderElector constructs a new etcdLeaderElector. func newEtcdLeaderElector(ctx context.Context, id, electionPrefix string, - cfg *etcd.Config) (*etcdLeaderElector, error) { + leaderSessionTTL int, cfg *etcd.Config) (*etcdLeaderElector, error) { clientCfg := clientv3.Config{ Context: ctx, @@ -72,7 +72,9 @@ func newEtcdLeaderElector(ctx context.Context, id, electionPrefix string, cli.Lease = namespace.NewLease(cli.Lease, cfg.Namespace) log.Infof("Applied namespace to leader elector: %v", cfg.Namespace) - session, err := concurrency.NewSession(cli) + session, err := concurrency.NewSession( + cli, concurrency.WithTTL(leaderSessionTTL), + ) if err != nil { log.Errorf("Unable to start new leader election session: %v", err) diff --git a/cluster/etcd_elector_factory.go b/cluster/etcd_elector_factory.go index 75182d475a..9aeecbb227 100644 --- a/cluster/etcd_elector_factory.go +++ b/cluster/etcd_elector_factory.go @@ -15,9 +15,9 @@ import ( func makeEtcdElector(ctx context.Context, args ...interface{}) (LeaderElector, error) { - if len(args) != 3 { + if len(args) != 4 { return nil, fmt.Errorf("invalid number of arguments to "+ - "cluster.makeEtcdElector(): expected 3, got %v", + "cluster.makeEtcdElector(): expected 4, got %v", len(args)) } @@ -33,13 +33,21 @@ func makeEtcdElector(ctx context.Context, args ...interface{}) (LeaderElector, "cluster.makeEtcdElector(), expected: string") } - etcdCfg, ok := args[2].(*etcd.Config) + leaderSessionTTL, ok := args[2].(int) if !ok { return nil, fmt.Errorf("invalid argument (2) to " + + "cluster.makeEtcdElector(), expected: int") + } + + etcdCfg, ok := args[3].(*etcd.Config) + if !ok { + return nil, fmt.Errorf("invalid argument (3) to " + "cluster.makeEtcdElector(), expected: *etcd.Config") } - return newEtcdLeaderElector(ctx, id, electionPrefix, etcdCfg) + return newEtcdLeaderElector( + ctx, id, electionPrefix, leaderSessionTTL, etcdCfg, + ) } func init() { diff --git a/cluster/etcd_elector_test.go b/cluster/etcd_elector_test.go index 65587eddc1..858c3f8e09 100644 --- a/cluster/etcd_elector_test.go +++ b/cluster/etcd_elector_test.go @@ -57,15 +57,16 @@ func TestEtcdElector(t *testing.T) { election = "/election/" id1 = "e1" id2 = "e2" + ttl = 5 ) e1, err := newEtcdLeaderElector( - ctx, id1, election, etcdCfg, + ctx, id1, election, ttl, etcdCfg, ) require.NoError(t, err) e2, err := newEtcdLeaderElector( - ctx, id2, election, etcdCfg, + ctx, id2, election, ttl, etcdCfg, ) require.NoError(t, err) diff --git a/cluster/factory.go b/cluster/factory.go index 52ae360bf1..52b39091b1 100644 --- a/cluster/factory.go +++ b/cluster/factory.go @@ -23,7 +23,7 @@ func RegisterLeaderElectorFactory(id string, factory leaderElectorFactoryFunc) { leaderElectorFactories[id] = factory } -// MakeLeaderElector will constuct a LeaderElector identified by id with the +// MakeLeaderElector will construct a LeaderElector identified by id with the // passed arguments. func MakeLeaderElector(ctx context.Context, id string, args ...interface{}) ( LeaderElector, error) { diff --git a/cmd/lncli/arg_parse.go b/cmd/lncli/arg_parse.go index 3f621a9ab7..a97e0e2ede 100644 --- a/cmd/lncli/arg_parse.go +++ b/cmd/lncli/arg_parse.go @@ -21,8 +21,8 @@ var secondsPer = map[string]int64{ "y": 31557600, // 365.25 days } -// parseTime parses UNIX timestamps or short timeranges inspired by sytemd (when starting with "-"), -// e.g. "-1M" for one month (30.44 days) ago. +// parseTime parses UNIX timestamps or short timeranges inspired by systemd +// (when starting with "-"), e.g. "-1M" for one month (30.44 days) ago. func parseTime(s string, base time.Time) (uint64, error) { if reTimeRange.MatchString(s) { last := len(s) - 1 diff --git a/cmd/lncli/cmd_import_mission_control.go b/cmd/lncli/cmd_import_mission_control.go index 79847159f6..22ca217f77 100644 --- a/cmd/lncli/cmd_import_mission_control.go +++ b/cmd/lncli/cmd_import_mission_control.go @@ -24,6 +24,10 @@ var importMissionControlCommand = cli.Command{ Name: "failure", Usage: "whether the routing history entry was a failure", }, + cli.BoolFlag{ + Name: "force", + Usage: "whether to force the history entry import", + }, }, } @@ -86,6 +90,7 @@ func importMissionControl(ctx *cli.Context) error { Pairs: []*routerrpc.PairHistory{ importResult, }, + Force: ctx.IsSet("force"), } rpcCtx := context.Background() diff --git a/cmd/lncli/cmd_invoice.go b/cmd/lncli/cmd_invoice.go index 2e1336611d..cdb177780b 100644 --- a/cmd/lncli/cmd_invoice.go +++ b/cmd/lncli/cmd_invoice.go @@ -60,7 +60,7 @@ var addInvoiceCommand = cli.Command{ "specified an expiry of 3600 seconds (1 hour) " + "is implied.", }, - cli.BoolTFlag{ + cli.BoolFlag{ Name: "private", Usage: "encode routing hints in the invoice with " + "private channels in order to assist the " + diff --git a/cmd/lncli/cmd_mission_control.go b/cmd/lncli/cmd_mission_control.go index 92ce1269f4..e26481b235 100644 --- a/cmd/lncli/cmd_mission_control.go +++ b/cmd/lncli/cmd_mission_control.go @@ -4,7 +4,7 @@ import ( "fmt" "strconv" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" diff --git a/cmd/lncli/cmd_open_channel.go b/cmd/lncli/cmd_open_channel.go index e2d507c137..118bb93801 100644 --- a/cmd/lncli/cmd_open_channel.go +++ b/cmd/lncli/cmd_open_channel.go @@ -14,9 +14,9 @@ import ( "strconv" "strings" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnwallet/chanfunding" "github.com/urfave/cli" @@ -63,7 +63,7 @@ Signed base64 encoded PSBT or hex encoded raw wire TX (or path to text file): ` channelTypeAnchors = "anchors" ) -// TODO(roasbeef): change default number of confirmations +// TODO(roasbeef): change default number of confirmations. var openChannelCommand = cli.Command{ Name: "openchannel", Category: "Channels", @@ -289,6 +289,7 @@ func openChannel(ctx *cli.Context) error { _, err := client.ConnectPeer(ctxc, req) if err != nil && !strings.Contains(err.Error(), "already connected") { + return err } } diff --git a/cmd/lncli/cmd_payments.go b/cmd/lncli/cmd_payments.go index 82808e3fc7..350905ca31 100644 --- a/cmd/lncli/cmd_payments.go +++ b/cmd/lncli/cmd_payments.go @@ -14,14 +14,15 @@ import ( "strings" "time" - "github.com/btcsuite/btcutil" - "github.com/jedib0t/go-pretty/table" - "github.com/jedib0t/go-pretty/text" + "github.com/btcsuite/btcd/btcutil" + "github.com/jedib0t/go-pretty/v6/table" + "github.com/jedib0t/go-pretty/v6/text" "github.com/lightninglabs/protobuf-hex-display/jsonpb" "github.com/lightningnetwork/lnd/chainreg" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lntypes" + "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/record" "github.com/lightningnetwork/lnd/routing/route" @@ -95,10 +96,9 @@ var ( "payment", } - ampReuseFlag = cli.BoolFlag{ - Name: "amp-reuse", - Usage: "if set to true, then a random payment address will " + - "be generated to enable re-use of an AMP invoice", + timePrefFlag = cli.Float64Flag{ + Name: "time_pref", + Usage: "(optional) expresses time preference (range -1 to 1)", } ) @@ -145,7 +145,7 @@ func paymentFlags() []cli.Flag { }, dataFlag, inflightUpdatesFlag, maxPartsFlag, jsonFlag, maxShardSizeSatFlag, maxShardSizeMsatFlag, ampFlag, - ampReuseFlag, + timePrefFlag, } } @@ -207,7 +207,6 @@ var sendPaymentCommand = cli.Command{ // default. func retrieveFeeLimit(ctx *cli.Context, amt int64) (int64, error) { switch { - case ctx.IsSet("fee_limit") && ctx.IsSet("fee_limit_percent"): return 0, fmt.Errorf("either fee_limit or fee_limit_percent " + "can be set, but not both") @@ -224,8 +223,10 @@ func retrieveFeeLimit(ctx *cli.Context, amt int64) (int64, error) { return feeLimitRoundedUp, nil } - // If no fee limit is set, use the payment amount as a limit (100%). - return amt, nil + // If no fee limit is set, use a default value based on the amount. + amtMsat := lnwire.NewMSatFromSatoshis(btcutil.Amount(amt)) + limitMsat := lnwallet.DefaultRoutingFeeLimitForAmount(amtMsat) + return int64(limitMsat.ToSatoshis()), nil } func confirmPayReq(resp *lnrpc.PayReq, amt, feeLimit int64) error { @@ -252,15 +253,6 @@ func parsePayAddr(ctx *cli.Context) ([]byte, error) { case ctx.IsSet("pay_addr"): payAddr, err = hex.DecodeString(ctx.String("pay_addr")) - case ctx.IsSet(ampReuseFlag.Name): - var addrBytes [32]byte - if _, err := rand.Read(addrBytes[:]); err != nil { - return nil, fmt.Errorf("unable to generate pay "+ - "addr: %v", err) - } - - payAddr = addrBytes[:] - case ctx.Args().Present(): payAddr, err = hex.DecodeString(ctx.Args().First()) } @@ -291,8 +283,9 @@ func sendPayment(ctx *cli.Context) error { // details of the payment are encoded within the request. if ctx.IsSet("pay_req") { req := &routerrpc.SendPaymentRequest{ - PaymentRequest: ctx.String("pay_req"), - Amt: ctx.Int64("amt"), + PaymentRequest: ctx.String("pay_req"), + Amt: ctx.Int64("amt"), + DestCustomRecords: make(map[uint64][]byte), } // We'll attempt to parse a payment address as well, given that @@ -401,7 +394,6 @@ func sendPayment(ctx *cli.Context) error { if err != nil { return err } - args = args.Tail() req.FinalCltvDelta = int32(delta) } @@ -417,6 +409,7 @@ func sendPayment(ctx *cli.Context) error { func sendPaymentRequest(ctx *cli.Context, req *routerrpc.SendPaymentRequest) error { + ctxc := getContext() conn := getClientConn(ctx, false) @@ -536,6 +529,9 @@ func sendPaymentRequest(ctx *cli.Context, req.FeeLimitSat = feeLimit + // Set time pref. + req.TimePref = ctx.Float64(timePrefFlag.Name) + // Always print in-flight updates for the table output. printJSON := ctx.Bool(jsonFlag.Name) req.NoInflightUpdates = !ctx.Bool(inflightUpdatesFlag.Name) && printJSON @@ -571,7 +567,10 @@ var trackPaymentCommand = cli.Command{ specified by the hash argument. `, ArgsUsage: "hash", - Action: actionDecorator(trackPayment), + Flags: []cli.Flag{ + jsonFlag, + }, + Action: actionDecorator(trackPayment), } func trackPayment(ctx *cli.Context) error { @@ -712,6 +711,7 @@ func formatMsat(amt int64) string { // formatPayment formats the payment state as an ascii table. func formatPayment(ctxc context.Context, payment *lnrpc.Payment, aliases *aliasCache) string { + t := table.NewWriter() // Build table header. @@ -1028,6 +1028,7 @@ var queryRoutesCommand = cli.Command{ Usage: "(optional) the channel id of the channel " + "that must be taken to the first hop", }, + timePrefFlag, cltvLimitFlag, }, Action: actionDecorator(queryRoutes), @@ -1081,6 +1082,7 @@ func queryRoutes(ctx *cli.Context) error { UseMissionControl: ctx.Bool("use_mc"), CltvLimit: uint32(ctx.Uint64(cltvLimitFlag.Name)), OutgoingChanId: ctx.Uint64("outgoing_chanid"), + TimePref: ctx.Float64(timePrefFlag.Name), } route, err := client.QueryRoutes(ctxc, req) @@ -1128,13 +1130,22 @@ var listPaymentsCommand = cli.Command{ Name: "listpayments", Category: "Payments", Usage: "List all outgoing payments.", - Description: "This command enables the retrieval of payments stored " + - "in the database. Pagination is supported by the usage of " + - "index_offset in combination with the paginate_forwards flag. " + - "Reversed pagination is enabled by default to receive " + - "current payments first. Pagination can be resumed by using " + - "the returned last_index_offset (for forwards order), or " + - "first_index_offset (for reversed order) as the offset_index. ", + Description: ` + This command enables the retrieval of payments stored + in the database. + + Pagination is supported by the usage of index_offset in combination with + the paginate_forwards flag. + Reversed pagination is enabled by default to receive current payments + first. Pagination can be resumed by using the returned last_index_offset + (for forwards order), or first_index_offset (for reversed order) as the + offset_index. + + Because counting all payments in the payment database can take a long + time on systems with many payments, the count is not returned by + default. That feature can be turned on with the --count_total_payments + flag. + `, Flags: []cli.Flag{ cli.BoolFlag{ Name: "include_incomplete", @@ -1165,6 +1176,13 @@ var listPaymentsCommand = cli.Command{ "index_offset will be returned, allowing " + "forwards pagination", }, + cli.BoolFlag{ + Name: "count_total_payments", + Usage: "if set, all payments (complete or incomplete, " + + "independent of max_payments parameter) will " + + "be counted; can take a long time on systems " + + "with many payments", + }, }, Action: actionDecorator(listPayments), } @@ -1175,10 +1193,11 @@ func listPayments(ctx *cli.Context) error { defer cleanUp() req := &lnrpc.ListPaymentsRequest{ - IncludeIncomplete: ctx.Bool("include_incomplete"), - IndexOffset: uint64(ctx.Uint("index_offset")), - MaxPayments: uint64(ctx.Uint("max_payments")), - Reversed: !ctx.Bool("paginate_forwards"), + IncludeIncomplete: ctx.Bool("include_incomplete"), + IndexOffset: uint64(ctx.Uint("index_offset")), + MaxPayments: uint64(ctx.Uint("max_payments")), + Reversed: !ctx.Bool("paginate_forwards"), + CountTotalPayments: ctx.Bool("count_total_payments"), } payments, err := client.ListPayments(ctxc, req) @@ -1297,7 +1316,6 @@ func forwardingHistory(ctx *cli.Context) error { return fmt.Errorf("unable to decode max_events: %v", err) } maxEvents = uint32(m) - args = args.Tail() } req := &lnrpc.ForwardingHistoryRequest{ @@ -1532,7 +1550,7 @@ func deletePayments(ctx *cli.Context) error { return nil } -// ESC is the ASCII code for escape character +// ESC is the ASCII code for escape character. const ESC = 27 // clearCode defines a terminal escape code to clear the currently line and move diff --git a/cmd/lncli/cmd_profile.go b/cmd/lncli/cmd_profile.go index 1ff06e7cd6..e1983171a3 100644 --- a/cmd/lncli/cmd_profile.go +++ b/cmd/lncli/cmd_profile.go @@ -7,7 +7,7 @@ import ( "path" "strings" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/lncfg" "github.com/urfave/cli" "gopkg.in/macaroon.v2" diff --git a/cmd/lncli/cmd_state.go b/cmd/lncli/cmd_state.go index d561c8d08f..afca13e9d6 100644 --- a/cmd/lncli/cmd_state.go +++ b/cmd/lncli/cmd_state.go @@ -13,11 +13,13 @@ var getStateCommand = cli.Command{ Usage: "Get the current state of the wallet and RPC", Description: ` Get the current state of the wallet. The possible states are: + - WAITING_TO_START: node is waiting to become the leader in a cluster + and is not started yet. - NON_EXISTING: wallet has not yet been initialized. - LOCKED: wallet is locked. - - UNLOCKED: wallet has been unlocked successfully, but the full RPC is - not yet ready. - - RPC_READY: the daemon has started and the RPC is fully available. + - UNLOCKED: wallet was unlocked successfully, but RPC server isn't ready. + - RPC_ACTIVE: RPC server is active but not fully ready for calls. + - SERVER_ACTIVE: RPC server is available and ready to accept calls. `, Flags: []cli.Flag{}, Action: actionDecorator(getState), diff --git a/cmd/lncli/cmd_walletunlocker.go b/cmd/lncli/cmd_walletunlocker.go index 2bc5604825..5500e458e6 100644 --- a/cmd/lncli/cmd_walletunlocker.go +++ b/cmd/lncli/cmd_walletunlocker.go @@ -149,7 +149,6 @@ func create(ctx *cli.Context) error { warningLoop: for { - fmt.Println() fmt.Printf("WARNING: You are attempting to restore from a " + "static channel backup (SCB) file.\nThis action will CLOSE " + @@ -806,7 +805,7 @@ func askRecoveryWindow() (int32, error) { return defaultRecoveryWindow, nil } - lookAhead, err := strconv.Atoi(answer) + lookAhead, err := strconv.ParseInt(answer, 10, 32) if err != nil { fmt.Printf("Unable to parse recovery window: %v\n", err) continue diff --git a/cmd/lncli/commands.go b/cmd/lncli/commands.go index d368784613..251deec80b 100644 --- a/cmd/lncli/commands.go +++ b/cmd/lncli/commands.go @@ -39,6 +39,9 @@ const ( defaultUtxoMinConf = 1 ) +var errBadChanPoint = errors.New("expecting chan_point to be in format of: " + + "txid:index") + func getContext() context.Context { shutdownInterceptor, err := signal.Intercept() if err != nil { @@ -98,7 +101,10 @@ func actionDecorator(f func(*cli.Context) error) func(*cli.Context) error { // two commands. if s.Code() == codes.Unimplemented && (c.Command.Name == "create" || - c.Command.Name == "unlock") { + c.Command.Name == "unlock" || + c.Command.Name == "changepassword" || + c.Command.Name == "createwatchonly") { + return fmt.Errorf("Wallet is already unlocked") } @@ -136,7 +142,8 @@ var newAddressCommand = cli.Command{ Description: ` Generate a wallet new address. Address-types has to be one of: - p2wkh: Pay to witness key hash - - np2wkh: Pay to nested witness key hash`, + - np2wkh: Pay to nested witness key hash + - p2tr: Pay to taproot pubkey`, Action: actionDecorator(newAddress), } @@ -158,6 +165,8 @@ func newAddress(ctx *cli.Context) error { addrType = lnrpc.AddressType_WITNESS_PUBKEY_HASH case "np2wkh": addrType = lnrpc.AddressType_NESTED_PUBKEY_HASH + case "p2tr": + addrType = lnrpc.AddressType_TAPROOT_PUBKEY default: return fmt.Errorf("invalid address type %v, support address type "+ "are: p2wkh and np2wkh", stringAddrType) @@ -719,6 +728,12 @@ var closeChannelCommand = cli.Command{ Usage: "the output index for the funding output of the funding " + "transaction", }, + cli.StringFlag{ + Name: "chan_point", + Usage: "(optional) the channel point. If set, " + + "funding_txid and output_index flags and " + + "positional arguments will be ignored", + }, cli.BoolFlag{ Name: "force", Usage: "attempt an uncooperative closure", @@ -1183,10 +1198,19 @@ func abandonChannel(ctx *cli.Context) error { // line. Both named options as well as unnamed parameters are supported. func parseChannelPoint(ctx *cli.Context) (*lnrpc.ChannelPoint, error) { channelPoint := &lnrpc.ChannelPoint{} + var err error args := ctx.Args() switch { + case ctx.IsSet("chan_point"): + channelPoint, err = parseChanPoint(ctx.String("chan_point")) + if err != nil { + return nil, fmt.Errorf("unable to parse chan_point: "+ + "%v", err) + } + return channelPoint, nil + case ctx.IsSet("funding_txid"): channelPoint.FundingTxid = &lnrpc.ChannelPoint_FundingTxidStr{ FundingTxidStr: ctx.String("funding_txid"), @@ -1953,7 +1977,16 @@ var updateChannelPolicyCommand = cli.Command{ Usage: "the fee rate that will be charged " + "proportionally based on the value of each " + "forwarded HTLC, the lowest possible rate is 0 " + - "with a granularity of 0.000001 (millionths)", + "with a granularity of 0.000001 (millionths). Can not " + + "be set at the same time as fee_rate_ppm.", + }, + cli.Uint64Flag{ + Name: "fee_rate_ppm", + Usage: "the fee rate ppm (parts per million) that " + + "will be charged proportionally based on the value of each " + + "forwarded HTLC, the lowest possible rate is 0 " + + "with a granularity of 0.000001 (millionths). Can not " + + "be set at the same time as fee_rate.", }, cli.Int64Flag{ Name: "time_lock_delta", @@ -1984,9 +2017,8 @@ var updateChannelPolicyCommand = cli.Command{ func parseChanPoint(s string) (*lnrpc.ChannelPoint, error) { split := strings.Split(s, ":") - if len(split) != 2 { - return nil, fmt.Errorf("expecting chan_point to be in format of: " + - "txid:index") + if len(split) != 2 || len(split[0]) == 0 || len(split[1]) == 0 { + return nil, errBadChanPoint } index, err := strconv.ParseInt(split[1], 10, 32) @@ -2015,6 +2047,7 @@ func updateChannelPolicy(ctx *cli.Context) error { var ( baseFee int64 feeRate float64 + feeRatePpm uint64 timeLockDelta int64 err error ) @@ -2034,8 +2067,12 @@ func updateChannelPolicy(ctx *cli.Context) error { } switch { + case ctx.IsSet("fee_rate") && ctx.IsSet("fee_rate_ppm"): + return fmt.Errorf("fee_rate or fee_rate_ppm can not both be set") case ctx.IsSet("fee_rate"): feeRate = ctx.Float64("fee_rate") + case ctx.IsSet("fee_rate_ppm"): + feeRatePpm = ctx.Uint64("fee_rate_ppm") case args.Present(): feeRate, err = strconv.ParseFloat(args.First(), 64) if err != nil { @@ -2044,7 +2081,7 @@ func updateChannelPolicy(ctx *cli.Context) error { args = args.Tail() default: - return fmt.Errorf("fee_rate argument missing") + return fmt.Errorf("fee_rate or fee_rate_ppm argument missing") } switch { @@ -2077,13 +2114,12 @@ func updateChannelPolicy(ctx *cli.Context) error { if chanPointStr != "" { chanPoint, err = parseChanPoint(chanPointStr) if err != nil { - return fmt.Errorf("unable to parse chan point: %v", err) + return fmt.Errorf("unable to parse chan_point: %v", err) } } req := &lnrpc.PolicyUpdateRequest{ BaseFeeMsat: baseFee, - FeeRate: feeRate, TimeLockDelta: uint32(timeLockDelta), MaxHtlcMsat: ctx.Uint64("max_htlc_msat"), } @@ -2103,6 +2139,12 @@ func updateChannelPolicy(ctx *cli.Context) error { } } + if feeRate != 0 { + req.FeeRate = feeRate + } else if feeRatePpm != 0 { + req.FeeRatePpm = uint32(feeRatePpm) + } + resp, err := client.UpdateChannelPolicy(ctxc, req) if err != nil { return err @@ -2127,6 +2169,22 @@ func updateChannelPolicy(ctx *cli.Context) error { return nil } +var fishCompletionCommand = cli.Command{ + Name: "fish-completion", + Hidden: true, + Action: func(c *cli.Context) error { + completion, err := c.App.ToFishCompletion() + if err != nil { + return err + } + + // We don't want to suggest files, so we add this + // first line to the completions. + _, err = fmt.Printf("complete -c %q -f \n%s", c.App.Name, completion) + return err + }, +} + var exportChanBackupCommand = cli.Command{ Name: "exportchanbackup", Category: "Channels", @@ -2211,7 +2269,7 @@ func exportChanBackup(ctx *cli.Context) error { if chanPointStr != "" { chanPointRPC, err := parseChanPoint(chanPointStr) if err != nil { - return err + return fmt.Errorf("unable to parse chan_point: %v", err) } chanBackup, err := client.ExportChannelBackup( @@ -2318,8 +2376,9 @@ var verifyChanBackupCommand = cli.Command{ "from exportchanbackup", }, cli.StringFlag{ - Name: "multi_file", - Usage: "the path to a multi-channel back up file", + Name: "multi_file", + Usage: "the path to a multi-channel back up file", + TakesFile: true, }, }, Action: actionDecorator(verifyChanBackup), @@ -2400,8 +2459,9 @@ var restoreChanBackupCommand = cli.Command{ "from exportchanbackup", }, cli.StringFlag{ - Name: "multi_file", - Usage: "the path to a multi-channel back up file", + Name: "multi_file", + Usage: "the path to a multi-channel back up file", + TakesFile: true, }, }, Action: actionDecorator(restoreChanBackup), diff --git a/cmd/lncli/commands_test.go b/cmd/lncli/commands_test.go new file mode 100644 index 0000000000..48de92e785 --- /dev/null +++ b/cmd/lncli/commands_test.go @@ -0,0 +1,70 @@ +package main + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" +) + +// TestParseChanPoint tests parseChanPoint with various +// valid and invalid input values and verifies the output. +func TestParseChanPoint(t *testing.T) { + testCases := []struct { + channelPoinStr string + channelPointIsNil bool + outputIndex uint32 + err error + }{ + { + "24581424081379576b4a7580ace91db10925d996a2a8d45c8034" + + "3a5a467dc0bc:0", + false, + 0, + nil, + }, { + "24581424081379576b4a7580ace91db10925d996a2a8d45c8034" + + "3a5a467dc0bc:4", + false, + 4, + nil, + }, { + ":", + true, + 0, + errBadChanPoint, + }, { + ":0", + true, + 0, + errBadChanPoint, + }, { + "24581424081379576b4a7580ace91db10925d996a2a8d45c8034" + + "3a5a467dc0bc:", + true, + 0, + errBadChanPoint, + }, { + "24581424081379576b4a7580ace91db10925d996a2a8d45c8034" + + "3a5a467dc0bc:string", + true, + 0, + errors.New("unable to decode output index: strconv." + + "ParseInt: parsing \"string\": invalid syntax"), + }, { + "not_hex:0", + true, + 0, + errors.New("unable to parse hex string: encoding/hex:" + + " invalid byte: U+006E 'n'"), + }, + } + for _, tc := range testCases { + cp, err := parseChanPoint(tc.channelPoinStr) + require.Equal(t, tc.err, err) + require.Equal(t, tc.channelPointIsNil, cp == nil) + if !tc.channelPointIsNil { + require.Equal(t, tc.outputIndex, cp.OutputIndex) + } + } +} diff --git a/cmd/lncli/devrpc_active.go b/cmd/lncli/devrpc_active.go new file mode 100644 index 0000000000..925b2df18b --- /dev/null +++ b/cmd/lncli/devrpc_active.go @@ -0,0 +1,64 @@ +//go:build dev +// +build dev + +package main + +import ( + "bytes" + "fmt" + "io/ioutil" + + "github.com/lightninglabs/protobuf-hex-display/jsonpb" + "github.com/lightningnetwork/lnd/lncfg" + "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lnrpc/devrpc" + "github.com/urfave/cli" +) + +// devCommands will return the set of commands to enable for devrpc builds. +func devCommands() []cli.Command { + return []cli.Command{ + { + Name: "importgraph", + Category: "Development", + Description: "Imports graph from describegraph JSON", + Usage: "Import the network graph.", + ArgsUsage: "graph-json-file", + Action: actionDecorator(importGraph), + }, + } +} + +func getDevClient(ctx *cli.Context) (devrpc.DevClient, func()) { + conn := getClientConn(ctx, false) + cleanUp := func() { + conn.Close() + } + return devrpc.NewDevClient(conn), cleanUp +} + +func importGraph(ctx *cli.Context) error { + ctxc := getContext() + client, cleanUp := getDevClient(ctx) + defer cleanUp() + + jsonFile := lncfg.CleanAndExpandPath(ctx.Args().First()) + jsonBytes, err := ioutil.ReadFile(jsonFile) + if err != nil { + return fmt.Errorf("error reading JSON from file %v: %v", + jsonFile, err) + } + + jsonGraph := &lnrpc.ChannelGraph{} + err = jsonpb.Unmarshal(bytes.NewReader(jsonBytes), jsonGraph) + if err != nil { + return fmt.Errorf("error parsing JSON: %v", err) + } + res, err := client.ImportGraph(ctxc, jsonGraph) + if err != nil { + return err + } + + printRespJSON(res) + return nil +} diff --git a/cmd/lncli/devrpc_default.go b/cmd/lncli/devrpc_default.go new file mode 100644 index 0000000000..b9362cb421 --- /dev/null +++ b/cmd/lncli/devrpc_default.go @@ -0,0 +1,11 @@ +//go:build !dev +// +build !dev + +package main + +import "github.com/urfave/cli" + +// devCommands will return nil for non-devrpc builds. +func devCommands() []cli.Command { + return nil +} diff --git a/cmd/lncli/invoicesrpc_active.go b/cmd/lncli/invoicesrpc_active.go index b708e0f622..ee03d8679e 100644 --- a/cmd/lncli/invoicesrpc_active.go +++ b/cmd/lncli/invoicesrpc_active.go @@ -6,7 +6,6 @@ package main import ( "encoding/hex" "fmt" - "strconv" "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc" diff --git a/cmd/lncli/main.go b/cmd/lncli/main.go index b6263924cf..03b08033ec 100644 --- a/cmd/lncli/main.go +++ b/cmd/lncli/main.go @@ -1,24 +1,26 @@ // Copyright (c) 2013-2017 The btcsuite developers // Copyright (c) 2015-2016 The Decred developers -// Copyright (C) 2015-2017 The Lightning Network Developers +// Copyright (C) 2015-2022 The Lightning Network Developers package main import ( + "context" "crypto/tls" "fmt" + "net" "os" "path/filepath" "strings" "syscall" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/build" "github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/macaroons" + "github.com/lightningnetwork/lnd/tor" "github.com/urfave/cli" - "golang.org/x/term" "google.golang.org/grpc" "google.golang.org/grpc/credentials" @@ -39,7 +41,7 @@ var ( // maxMsgRecvSize is the largest message our client will receive. We // set this to 200MiB atm. - maxMsgRecvSize = grpc.MaxCallRecvMsgSize(1 * 1024 * 1024 * 200) + maxMsgRecvSize = grpc.MaxCallRecvMsgSize(lnrpc.MaxGrpcMsgSize) ) func fatal(err error) { @@ -174,10 +176,24 @@ func getClientConn(ctx *cli.Context, skipMacaroons bool) *grpc.ClientConn { opts = append(opts, grpc.WithPerRPCCredentials(cred)) } - // We need to use a custom dialer so we can also connect to unix sockets - // and not just TCP addresses. - genericDialer := lncfg.ClientAddressDialer(defaultRPCPort) - opts = append(opts, grpc.WithContextDialer(genericDialer)) + // If a socksproxy server is specified we use a tor dialer + // to connect to the grpc server. + if ctx.GlobalIsSet("socksproxy") { + socksProxy := ctx.GlobalString("socksproxy") + torDialer := func(_ context.Context, addr string) (net.Conn, error) { + return tor.Dial( + addr, socksProxy, false, false, + tor.DefaultConnTimeout, + ) + } + opts = append(opts, grpc.WithContextDialer(torDialer)) + } else { + // We need to use a custom dialer so we can also connect to + // unix sockets and not just TCP addresses. + genericDialer := lncfg.ClientAddressDialer(defaultRPCPort) + opts = append(opts, grpc.WithContextDialer(genericDialer)) + } + opts = append(opts, grpc.WithDefaultCallOptions(maxMsgRecvSize)) conn, err := grpc.Dial(profile.RPCServer, opts...) @@ -272,14 +288,22 @@ func main() { Usage: "The host:port of LN daemon.", }, cli.StringFlag{ - Name: "lnddir", - Value: defaultLndDir, - Usage: "The path to lnd's base directory.", + Name: "lnddir", + Value: defaultLndDir, + Usage: "The path to lnd's base directory.", + TakesFile: true, + }, + cli.StringFlag{ + Name: "socksproxy", + Usage: "The host:port of a SOCKS proxy through " + + "which all connections to the LN " + + "daemon will be established over.", }, cli.StringFlag{ - Name: "tlscertpath", - Value: defaultTLSCertPath, - Usage: "The path to lnd's TLS certificate.", + Name: "tlscertpath", + Value: defaultTLSCertPath, + Usage: "The path to lnd's TLS certificate.", + TakesFile: true, }, cli.StringFlag{ Name: "chain, c", @@ -297,8 +321,9 @@ func main() { Usage: "Disable macaroon authentication.", }, cli.StringFlag{ - Name: "macaroonpath", - Usage: "The path to macaroon file.", + Name: "macaroonpath", + Usage: "The path to macaroon file.", + TakesFile: true, }, cli.Int64Flag{ Name: "macaroontimeout", @@ -387,15 +412,19 @@ func main() { deletePaymentsCommand, sendCustomCommand, subscribeCustomCommand, + fishCompletionCommand, } // Add any extra commands determined by build flags. app.Commands = append(app.Commands, autopilotCommands()...) app.Commands = append(app.Commands, invoicesCommands()...) + app.Commands = append(app.Commands, neutrinoCommands()...) app.Commands = append(app.Commands, routerCommands()...) app.Commands = append(app.Commands, walletCommands()...) app.Commands = append(app.Commands, watchtowerCommands()...) app.Commands = append(app.Commands, wtclientCommands()...) + app.Commands = append(app.Commands, devCommands()...) + app.Commands = append(app.Commands, peersCommands()...) if err := app.Run(os.Args); err != nil { fatal(err) diff --git a/cmd/lncli/neutrino_active.go b/cmd/lncli/neutrino_active.go new file mode 100644 index 0000000000..9df1360c34 --- /dev/null +++ b/cmd/lncli/neutrino_active.go @@ -0,0 +1,284 @@ +//go:build neutrinorpc +// +build neutrinorpc + +package main + +import ( + "github.com/lightningnetwork/lnd/lnrpc/neutrinorpc" + "github.com/urfave/cli" +) + +func getNeutrinoKitClient(ctx *cli.Context) (neutrinorpc.NeutrinoKitClient, func()) { + conn := getClientConn(ctx, false) + + cleanUp := func() { + conn.Close() + } + + return neutrinorpc.NewNeutrinoKitClient(conn), cleanUp +} + +var getNeutrinoStatusCommand = cli.Command{ + Name: "status", + Usage: "Returns the status of the running neutrino instance.", + Category: "Neutrino", + Description: "Returns the status of the light client neutrino " + + "instance, along with height and hash of the best block, and " + + "a list of connected peers.", + Action: actionDecorator(getNeutrinoStatus), +} + +func getNeutrinoStatus(ctx *cli.Context) error { + ctxc := getContext() + client, cleanUp := getNeutrinoKitClient(ctx) + defer cleanUp() + + req := &neutrinorpc.StatusRequest{} + + resp, err := client.Status(ctxc, req) + if err != nil { + return err + } + + printRespJSON(resp) + + return nil +} + +var addPeerCommand = cli.Command{ + Name: "addpeer", + Usage: "Add a peer.", + Category: "Neutrino", + Description: "Adds a new peer that has already been connected to the " + + "server.", + ArgsUsage: "address", + Action: actionDecorator(addNeutrinoPeer), +} + +func addNeutrinoPeer(ctx *cli.Context) error { + ctxc := getContext() + + // Display the command's help message if we do not have the expected + // number of arguments/flags. + if ctx.NArg() != 1 || ctx.NumFlags() > 0 { + return cli.ShowCommandHelp(ctx, "addpeer") + } + + client, cleanUp := getNeutrinoKitClient(ctx) + defer cleanUp() + + req := &neutrinorpc.AddPeerRequest{ + PeerAddrs: ctx.Args().First(), + } + + // Add a peer to the neutrino server. + resp, err := client.AddPeer(ctxc, req) + if err != nil { + return err + } + + printRespJSON(resp) + + return nil +} + +var disconnectPeerCommand = cli.Command{ + Name: "disconnectpeer", + Usage: "Disconnect a peer.", + Category: "Neutrino", + Description: "Disconnects a peer by target address. Both outbound and" + + "inbound nodes will be searched for the target node. An error " + + "message will be returned if the peer was not found.", + ArgsUsage: "address", + Action: actionDecorator(disconnectNeutrinoPeer), +} + +func disconnectNeutrinoPeer(ctx *cli.Context) error { + ctxc := getContext() + + // Display the command's help message if we do not have the expected + // number of arguments/flags. + if ctx.NArg() != 1 || ctx.NumFlags() > 0 { + return cli.ShowCommandHelp(ctx, "disconnectpeer") + } + + client, cleanUp := getNeutrinoKitClient(ctx) + defer cleanUp() + + req := &neutrinorpc.DisconnectPeerRequest{ + PeerAddrs: ctx.Args().First(), + } + + // Disconnect a peer to the neutrino server. + resp, err := client.DisconnectPeer(ctxc, req) + if err != nil { + return err + } + + printRespJSON(resp) + + return nil +} + +var isBannedCommand = cli.Command{ + Name: "isbanned", + Usage: "Get ban status.", + Category: "Neutrino", + Description: "Returns true if the peer is banned, otherwise false.", + ArgsUsage: "address", + Action: actionDecorator(isBanned), +} + +func isBanned(ctx *cli.Context) error { + ctxc := getContext() + + // Display the command's help message if we do not have the expected + // number of arguments/flags. + if ctx.NArg() != 1 { + return cli.ShowCommandHelp(ctx, "isbanned") + } + client, cleanUp := getNeutrinoKitClient(ctx) + defer cleanUp() + + req := &neutrinorpc.IsBannedRequest{ + PeerAddrs: ctx.Args().First(), + } + + // Check if the peer is banned. + resp, err := client.IsBanned(ctxc, req) + if err != nil { + return err + } + + printRespJSON(resp) + + return nil +} + +var getBlockHeaderCommand = cli.Command{ + Name: "getblockheader", + Usage: "Get a block header.", + Category: "Neutrino", + Description: "Returns a block header with a particular block hash.", + ArgsUsage: "hash", + Action: actionDecorator(getBlockHeader), +} + +func getBlockHeader(ctx *cli.Context) error { + ctxc := getContext() + args := ctx.Args() + + // Display the command's help message if we do not have the expected + // number of arguments/flags. + if !args.Present() { + return cli.ShowCommandHelp(ctx, "getblockheader") + } + + client, cleanUp := getNeutrinoKitClient(ctx) + defer cleanUp() + + req := &neutrinorpc.GetBlockHeaderRequest{ + Hash: ctx.Args().First(), + } + + resp, err := client.GetBlockHeader(ctxc, req) + if err != nil { + return err + } + + printRespJSON(resp) + + return nil +} + +var getBlockCommand = cli.Command{ + Name: "getblock", + Usage: "Get a block.", + Category: "Neutrino", + Description: "Returns a block with a particular block hash.", + ArgsUsage: "hash", + Action: actionDecorator(getBlock), +} + +func getBlock(ctx *cli.Context) error { + ctxc := getContext() + args := ctx.Args() + + // Display the command's help message if we do not have the expected + // number of arguments/flags. + if !args.Present() { + return cli.ShowCommandHelp(ctx, "getblock") + } + + client, cleanUp := getNeutrinoKitClient(ctx) + defer cleanUp() + + req := &neutrinorpc.GetBlockRequest{ + Hash: args.First(), + } + + resp, err := client.GetBlock(ctxc, req) + if err != nil { + return err + } + + printRespJSON(resp) + + return nil +} + +var getCFilterCommand = cli.Command{ + Name: "getcfilter", + Usage: "Get a compact filter.", + Category: "Neutrino", + Description: "Returns a compact filter of a particular block.", + ArgsUsage: "hash", + Action: actionDecorator(getCFilter), +} + +func getCFilter(ctx *cli.Context) error { + ctxc := getContext() + args := ctx.Args() + + // Display the command's help message if we do not have the expected + // number of arguments/flags. + if !args.Present() { + return cli.ShowCommandHelp(ctx, "getcfilter") + } + + client, cleanUp := getNeutrinoKitClient(ctx) + defer cleanUp() + + req := &neutrinorpc.GetCFilterRequest{Hash: args.First()} + + resp, err := client.GetCFilter(ctxc, req) + if err != nil { + return err + } + + printRespJSON(resp) + + return nil +} + +// neutrinoCommands will return the set of commands to enable for neutrinorpc +// builds. +func neutrinoCommands() []cli.Command { + return []cli.Command{ + { + Name: "neutrino", + Category: "Neutrino", + Usage: "Interact with a running neutrino instance.", + Description: "", + Subcommands: []cli.Command{ + getNeutrinoStatusCommand, + addPeerCommand, + disconnectPeerCommand, + isBannedCommand, + getBlockCommand, + getBlockHeaderCommand, + getCFilterCommand, + }, + }, + } +} diff --git a/cmd/lncli/neutrino_default.go b/cmd/lncli/neutrino_default.go new file mode 100644 index 0000000000..4ca9a4b7f4 --- /dev/null +++ b/cmd/lncli/neutrino_default.go @@ -0,0 +1,10 @@ +// +build !neutrinorpc + +package main + +import "github.com/urfave/cli" + +// neutrinoCommands will return nil for non-neutrinorpc builds. +func neutrinoCommands() []cli.Command { + return nil +} diff --git a/cmd/lncli/peersrpc_active.go b/cmd/lncli/peersrpc_active.go new file mode 100644 index 0000000000..09ef17d583 --- /dev/null +++ b/cmd/lncli/peersrpc_active.go @@ -0,0 +1,162 @@ +//go:build peersrpc +// +build peersrpc + +package main + +import ( + "fmt" + + "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lnrpc/peersrpc" + "github.com/urfave/cli" +) + +// peersCommands will return the set of commands to enable for peersrpc +// builds. +func peersCommands() []cli.Command { + return []cli.Command{ + { + Name: "peers", + Category: "Peers", + Usage: "Interacts with the other nodes of the " + + "newtwork", + Subcommands: []cli.Command{ + updateNodeAnnouncementCommand, + }, + }, + } +} + +func getPeersClient(ctx *cli.Context) (peersrpc.PeersClient, func()) { + conn := getClientConn(ctx, false) + cleanUp := func() { + conn.Close() + } + return peersrpc.NewPeersClient(conn), cleanUp +} + +var updateNodeAnnouncementCommand = cli.Command{ + Name: "updatenodeannouncement", + Category: "Peers", + Usage: "update and brodcast a new node announcement", + Description: ` + Update the node's information and broadcast a new node announcement. + + Add or remove addresses where your node can be reached at, change the + alias/color of the node or enable/disable supported feature bits without + restarting the node. A node announcement with the new information will + be created and brodcasted to the network.`, + ArgsUsage: "[--address_add=] [--address_remove=] [--alias=] " + + "[--color=] [--feature_bit_add=] [--feature_bit_remove=]", + Flags: []cli.Flag{ + cli.StringSliceFlag{ + Name: "address_add", + Usage: "a new address that should be added to the " + + "set of URIs of this node. Can be set " + + "multiple times in the same command", + }, + cli.StringSliceFlag{ + Name: "address_remove", + Usage: "an address that needs to be removed from the " + + "set of URIs of this node. Can be set " + + "multiple times in the same command", + }, + cli.StringFlag{ + Name: "alias", + Usage: "the new alias for this node, e.g. \"bob\"", + }, + cli.StringFlag{ + Name: "color", + Usage: "the new color for this node, e.g. #c42a81", + }, + cli.Int64SliceFlag{ + Name: "feature_bit_add", + Usage: "a feature bit index that needs to be enabled. " + + "Can be set multiple times in the same command", + }, + cli.Int64SliceFlag{ + Name: "feature_bit_remove", + Usage: "a feature bit that needs to be disabled" + + "Can be set multiple times in the same command", + }, + }, + Action: actionDecorator(updateNodeAnnouncement), +} + +func updateNodeAnnouncement(ctx *cli.Context) error { + ctxc := getContext() + client, cleanUp := getPeersClient(ctx) + defer cleanUp() + + change := false + + req := &peersrpc.NodeAnnouncementUpdateRequest{} + + if ctx.IsSet("address_add") { + change = true + for _, addr := range ctx.StringSlice("address_add") { + action := &peersrpc.UpdateAddressAction{ + Action: peersrpc.UpdateAction_ADD, + Address: addr, + } + req.AddressUpdates = append(req.AddressUpdates, action) + } + } + + if ctx.IsSet("address_remove") { + change = true + for _, addr := range ctx.StringSlice("address_remove") { + action := &peersrpc.UpdateAddressAction{ + Action: peersrpc.UpdateAction_REMOVE, + Address: addr, + } + req.AddressUpdates = append(req.AddressUpdates, action) + } + } + + if ctx.IsSet("alias") { + change = true + req.Alias = ctx.String("alias") + } + + if ctx.IsSet("color") { + change = true + req.Color = ctx.String("color") + } + + if ctx.IsSet("feature_bit_add") { + change = true + for _, feature := range ctx.IntSlice("feature_bit_add") { + action := &peersrpc.UpdateFeatureAction{ + Action: peersrpc.UpdateAction_ADD, + FeatureBit: lnrpc.FeatureBit(feature), + } + req.FeatureUpdates = append(req.FeatureUpdates, action) + } + } + + if ctx.IsSet("feature_bit_remove") { + change = true + for _, feature := range ctx.IntSlice("feature_bit_remove") { + action := &peersrpc.UpdateFeatureAction{ + Action: peersrpc.UpdateAction_REMOVE, + FeatureBit: lnrpc.FeatureBit(feature), + } + req.FeatureUpdates = append(req.FeatureUpdates, action) + } + } + + if !change { + return fmt.Errorf("no changes for the node information " + + "detected") + } + + resp, err := client.UpdateNodeAnnouncement(ctxc, req) + if err != nil { + return err + } + + printRespJSON(resp) + + return nil +} diff --git a/cmd/lncli/peersrpc_default.go b/cmd/lncli/peersrpc_default.go new file mode 100644 index 0000000000..24cb2b8134 --- /dev/null +++ b/cmd/lncli/peersrpc_default.go @@ -0,0 +1,11 @@ +//go:build !peersrpc +// +build !peersrpc + +package main + +import "github.com/urfave/cli" + +// peersCommands will return nil for non-peersrpc builds. +func peersCommands() []cli.Command { + return nil +} diff --git a/cmd/lncli/profile.go b/cmd/lncli/profile.go index 5f684b3eaf..2f2819df64 100644 --- a/cmd/lncli/profile.go +++ b/cmd/lncli/profile.go @@ -29,7 +29,7 @@ type profileEntry struct { LndDir string `json:"lnddir"` Chain string `json:"chain"` Network string `json:"network"` - NoMacaroons bool `json:"no-macaroons,omitempty"` + NoMacaroons bool `json:"no-macaroons,omitempty"` // nolint:tagliatelle TLSCert string `json:"tlscert"` Macaroons *macaroonJar `json:"macaroons"` } diff --git a/cmd/lncli/walletrpc_active.go b/cmd/lncli/walletrpc_active.go index 460635f0ef..169f539690 100644 --- a/cmd/lncli/walletrpc_active.go +++ b/cmd/lncli/walletrpc_active.go @@ -62,6 +62,7 @@ func walletCommands() []cli.Command { labelTxCommand, publishTxCommand, releaseOutputCommand, + leaseOutputCommand, listLeasesCommand, psbtCommand, accountsCommand, @@ -847,6 +848,81 @@ func finalizePsbt(ctx *cli.Context) error { return nil } +var leaseOutputCommand = cli.Command{ + Name: "leaseoutput", + Usage: "Lease an output.", + Description: ` + The leaseoutput command locks an output, making it unavailable + for coin selection. + + An app lock ID and expiration duration must be specified when locking + the output. + `, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "outpoint", + Usage: "the output to lock", + }, + cli.StringFlag{ + Name: "lockid", + Usage: "the hex-encoded app lock ID", + }, + cli.Uint64Flag{ + Name: "expiry", + Usage: "expiration duration in seconds", + }, + }, + Action: actionDecorator(leaseOutput), +} + +func leaseOutput(ctx *cli.Context) error { + ctxc := getContext() + + // Display the command's help message if we do not have the expected + // number of arguments/flags. + if ctx.NArg() != 0 || ctx.NumFlags() == 0 { + return cli.ShowCommandHelp(ctx, "leaseoutput") + } + + outpointStr := ctx.String("outpoint") + outpoint, err := NewProtoOutPoint(outpointStr) + if err != nil { + return fmt.Errorf("error parsing outpoint: %v", err) + } + + lockIDStr := ctx.String("lockid") + if lockIDStr == "" { + return errors.New("lockid not specified") + } + lockID, err := hex.DecodeString(lockIDStr) + if err != nil { + return fmt.Errorf("error parsing lockid: %v", err) + } + + expiry := ctx.Uint64("expiry") + if expiry == 0 { + return errors.New("expiry not specified or invalid") + } + + req := &walletrpc.LeaseOutputRequest{ + Outpoint: outpoint, + Id: lockID, + ExpirationSeconds: expiry, + } + + walletClient, cleanUp := getWalletClient(ctx) + defer cleanUp() + + response, err := walletClient.LeaseOutput(ctxc, req) + if err != nil { + return err + } + + printRespJSON(response) + + return nil +} + var releaseOutputCommand = cli.Command{ Name: "releaseoutput", Usage: "Release an output previously locked by lnd.", @@ -855,15 +931,19 @@ var releaseOutputCommand = cli.Command{ The releaseoutput command unlocks an output, allowing it to be available for coin selection if it remains unspent. - The internal lnd app lock ID is used when releasing the output. - Therefore only UTXOs locked by the fundpsbt command can currently be - released with this command. + If no lock ID is specified, the internal lnd app lock ID is used when + releasing the output. With the internal ID, only UTXOs locked by the + fundpsbt command can be released. `, Flags: []cli.Flag{ cli.StringFlag{ Name: "outpoint", Usage: "the output to unlock", }, + cli.StringFlag{ + Name: "lockid", + Usage: "the hex-encoded app lock ID", + }, }, Action: actionDecorator(releaseOutput), } @@ -894,9 +974,20 @@ func releaseOutput(ctx *cli.Context) error { if err != nil { return fmt.Errorf("error parsing outpoint: %v", err) } + + lockID := walletrpc.LndInternalLockID[:] + lockIDStr := ctx.String("lockid") + if lockIDStr != "" { + var err error + lockID, err = hex.DecodeString(lockIDStr) + if err != nil { + return fmt.Errorf("error parsing lockid: %v", err) + } + } + req := &walletrpc.ReleaseOutputRequest{ Outpoint: outpoint, - Id: walletrpc.LndInternalLockID[:], + Id: lockID, } walletClient, cleanUp := getWalletClient(ctx) diff --git a/config.go b/config.go index 158e152be8..1af48ef58b 100644 --- a/config.go +++ b/config.go @@ -1,6 +1,6 @@ // Copyright (c) 2013-2017 The btcsuite developers // Copyright (c) 2015-2016 The Decred developers -// Copyright (C) 2015-2020 The Lightning Network Developers +// Copyright (C) 2015-2022 The Lightning Network Developers package lnd @@ -19,8 +19,8 @@ import ( "strings" "time" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcutil" flags "github.com/jessevdk/go-flags" "github.com/lightninglabs/neutrino" "github.com/lightningnetwork/lnd/autopilot" @@ -35,6 +35,7 @@ import ( "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lnrpc/peersrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lnrpc/signrpc" "github.com/lightningnetwork/lnd/lnwallet" @@ -82,6 +83,10 @@ const ( defaultTorV2PrivateKeyFilename = "v2_onion_private_key" defaultTorV3PrivateKeyFilename = "v3_onion_private_key" + // defaultZMQReadDeadline is the default read deadline to be used for + // both the block and tx ZMQ subscriptions. + defaultZMQReadDeadline = 5 * time.Second + // DefaultAutogenValidity is the default validity of a self-signed // certificate. The value corresponds to 14 months // (14 months * 30 days * 24 hours). @@ -148,6 +153,16 @@ const ( defaultTCBackoff = time.Minute defaultTCAttempts = 0 + // Set defaults for a health check which ensures that the remote signer + // RPC connection is alive. Although this check is off by default (only + // active when remote signing is turned on), we still set the other + // default values so that the health check can be easily enabled with + // sane defaults. + defaultRSInterval = time.Minute + defaultRSTimeout = time.Second * 1 + defaultRSBackoff = time.Second * 30 + defaultRSAttempts = 1 + // defaultRemoteMaxHtlcs specifies the default limit for maximum // concurrent HTLCs the remote party may add to commitment transactions. // This value can be overridden with --default-remote-max-htlcs. @@ -158,10 +173,24 @@ const ( // TODO(halseth): find a more scientific choice of value. defaultMaxLocalCSVDelay = 10000 - // defaultChannelCommitInterval is the default maximum time between receiving a - // channel state update and signing a new commitment. + // defaultChannelCommitInterval is the default maximum time between + // receiving a channel state update and signing a new commitment. defaultChannelCommitInterval = 50 * time.Millisecond + // maxChannelCommitInterval is the maximum time the commit interval can + // be configured to. + maxChannelCommitInterval = time.Hour + + // defaultPendingCommitInterval specifies the default timeout value + // while waiting for the remote party to revoke a locally initiated + // commitment state. + defaultPendingCommitInterval = 1 * time.Minute + + // maxPendingCommitInterval specifies the max allowed duration when + // waiting for the remote party to revoke a locally initiated + // commitment state. + maxPendingCommitInterval = 5 * time.Minute + // defaultChannelCommitBatchSize is the default maximum number of // channel state updates that is accumulated before signing a new // commitment. @@ -234,6 +263,7 @@ type Config struct { TLSAutoRefresh bool `long:"tlsautorefresh" description:"Re-generate TLS certificate and key if the IPs or domains are changed"` TLSDisableAutofill bool `long:"tlsdisableautofill" description:"Do not include the interface IPs or the system hostname in TLS certificate, use first --tlsextradomain as Common Name instead, if set"` TLSCertDuration time.Duration `long:"tlscertduration" description:"The duration for which the auto-generated TLS certificate will be valid for"` + TLSEncryptKey bool `long:"tlsencryptkey" description:"Automatically encrypts the TLS private key and generates ephemeral TLS key pairs when the wallet is locked or not initialized"` NoMacaroons bool `long:"no-macaroons" description:"Disable macaroon authentication, can only be used if server is not listening on a public interface."` AdminMacPath string `long:"adminmacaroonpath" description:"Path to write the admin macaroon for lnd's RPC and REST services if it doesn't exist"` @@ -246,7 +276,7 @@ type Config struct { LetsEncryptDir string `long:"letsencryptdir" description:"The directory to store Let's Encrypt certificates within"` LetsEncryptListen string `long:"letsencryptlisten" description:"The IP:port on which lnd will listen for Let's Encrypt challenges. Let's Encrypt will always try to contact on port 80. Often non-root processes are not allowed to bind to ports lower than 1024. This configuration option allows a different port to be used, but must be used in combination with port forwarding from port 80. This configuration can also be used to specify another IP address to listen on, for example an IPv6 address."` - LetsEncryptDomain string `long:"letsencryptdomain" description:"Request a Let's Encrypt certificate for this domain. Note that the certicate is only requested and stored when the first rpc connection comes in."` + LetsEncryptDomain string `long:"letsencryptdomain" description:"Request a Let's Encrypt certificate for this domain. Note that the certificate is only requested and stored when the first rpc connection comes in."` // We'll parse these 'raw' string arguments into real net.Addrs in the // loadConfig function. We need to expose the 'raw' strings so the @@ -256,7 +286,7 @@ type Config struct { RawRESTListeners []string `long:"restlisten" description:"Add an interface/port/socket to listen for REST connections"` RawListeners []string `long:"listen" description:"Add an interface/port to listen for peer connections"` RawExternalIPs []string `long:"externalip" description:"Add an ip:port to the list of local addresses we claim to listen on to peers. If a port is not specified, the default (9735) will be used regardless of other parameters"` - ExternalHosts []string `long:"externalhosts" description:"A set of hosts that should be periodically resolved to announce IPs for"` + ExternalHosts []string `long:"externalhosts" description:"Add a hostname:port that should be periodically resolved to announce IPs for. If a port is not specified, the default (9735) will be used."` RPCListeners []net.Addr RESTListeners []net.Addr RestCORS []string `long:"restcors" description:"Add an ip:port/hostname to allow cross origin access from. To allow all origins, set as \"*\"."` @@ -268,6 +298,7 @@ type Config struct { WSPingInterval time.Duration `long:"ws-ping-interval" description:"The ping interval for REST based WebSocket connections, set to 0 to disable sending ping messages from the server side"` WSPongWait time.Duration `long:"ws-pong-wait" description:"The time we wait for a pong response message on REST based WebSocket connections before the connection is closed as inactive"` NAT bool `long:"nat" description:"Toggle NAT traversal support (using either UPnP or NAT-PMP) to automatically advertise your external IP address to the network -- NOTE this does not support devices behind multiple NATs"` + AddPeers []string `long:"addpeer" description:"Specify peers to connect to first"` MinBackoff time.Duration `long:"minbackoff" description:"Shortest backoff when reconnecting to persistent peers. Valid time units are {s, m, h}."` MaxBackoff time.Duration `long:"maxbackoff" description:"Longest backoff when reconnecting to persistent peers. Valid time units are {s, m, h}."` ConnectionTimeout time.Duration `long:"connectiontimeout" description:"The timeout value for network connections. Valid time units are {ms, s, m, h}."` @@ -316,7 +347,7 @@ type Config struct { PaymentsExpirationGracePeriod time.Duration `long:"payments-expiration-grace-period" description:"A period to wait before force closing channels with outgoing htlcs that have timed-out and are a result of this node initiated payments."` TrickleDelay int `long:"trickledelay" description:"Time in milliseconds between each release of announcements to the network"` - ChanEnableTimeout time.Duration `long:"chan-enable-timeout" description:"The duration that a peer connection must be stable before attempting to send a channel update to reenable or cancel a pending disables of the peer's channels on the network."` + ChanEnableTimeout time.Duration `long:"chan-enable-timeout" description:"The duration that a peer connection must be stable before attempting to send a channel update to re-enable or cancel a pending disables of the peer's channels on the network."` ChanDisableTimeout time.Duration `long:"chan-disable-timeout" description:"The duration that must elapse after first detecting that an already active channel is actually inactive and sending channel update disabling it to the network. The pending disable can be canceled if the peer reconnects and becomes stable for chan-enable-timeout before the disable update is sent."` ChanStatusSampleInterval time.Duration `long:"chan-status-sample-interval" description:"The polling interval between attempts to detect if an active channel has become inactive due to its peer going offline."` HeightHintCacheQueryDisable bool `long:"height-hint-cache-query-disable" description:"Disable queries from the height-hint cache to try to recover channels stuck in the pending close state. Disabling height hint queries may cause longer chain rescans, resulting in a performance hit. Unset this after channels are unstuck so you can get better performance again."` @@ -326,8 +357,11 @@ type Config struct { MaxChanSize int64 `long:"maxchansize" description:"The largest channel size (in satoshis) that we should accept. Incoming channels larger than this will be rejected"` CoopCloseTargetConfs uint32 `long:"coop-close-target-confs" description:"The target number of blocks that a cooperative channel close transaction should confirm in. This is used to estimate the fee to use as the lower bound during fee negotiation for the channel closure."` - ChannelCommitInterval time.Duration `long:"channel-commit-interval" description:"The maximum time that is allowed to pass between receiving a channel state update and signing the next commitment. Setting this to a longer duration allows for more efficient channel operations at the cost of latency."` - ChannelCommitBatchSize uint32 `long:"channel-commit-batch-size" description:"The maximum number of channel state updates that is accumulated before signing a new commitment."` + ChannelCommitInterval time.Duration `long:"channel-commit-interval" description:"The maximum time that is allowed to pass between receiving a channel state update and signing the next commitment. Setting this to a longer duration allows for more efficient channel operations at the cost of latency."` + + PendingCommitInterval time.Duration `long:"pending-commit-interval" description:"The maximum time that is allowed to pass while waiting for the remote party to revoke a locally initiated commitment state. Setting this to a longer duration if a slow response is expected from the remote party or large number of payments are attempted at the same time."` + + ChannelCommitBatchSize uint32 `long:"channel-commit-batch-size" description:"The maximum number of channel state updates that is accumulated before signing a new commitment."` DefaultRemoteMaxHtlcs uint16 `long:"default-remote-max-htlcs" description:"The default max_htlc applied when opening or accepting channels. This value limits the number of concurrent HTLCs that the remote party can add to the commitment. The maximum possible value is 483."` @@ -340,6 +374,10 @@ type Config struct { RejectHTLC bool `long:"rejecthtlc" description:"If true, lnd will not forward any HTLCs that are meant as onward payments. This option will still allow lnd to send HTLCs and receive HTLCs but lnd won't be used as a hop."` + // RequireInterceptor determines whether the HTLC interceptor is + // registered regardless of whether the RPC is called or not. + RequireInterceptor bool `long:"requireinterceptor" description:"Whether to always intercept HTLCs, even if no stream is attached"` + StaggerInitialReconnect bool `long:"stagger-initial-reconnect" description:"If true, will apply a randomized staggering between 0s and 30s when reconnecting to persistent peers on startup. The first 10 reconnections will be attempted instantly, regardless of the flag's value"` MaxOutgoingCltvExpiry uint32 `long:"max-cltv-expiry" description:"The maximum number of blocks funds could be locked up for when forwarding payments."` @@ -450,6 +488,7 @@ func DefaultConfig() Config { RPCHost: defaultRPCHost, EstimateMode: defaultBitcoindEstimateMode, PrunedNodeMaxPeers: defaultPrunedNodeMaxPeers, + ZMQReadDeadline: defaultZMQReadDeadline, }, Litecoin: &lncfg.Chain{ MinHTLCIn: chainreg.DefaultLitecoinMinHTLCInMSat, @@ -485,6 +524,7 @@ func DefaultConfig() Config { SubRPCServers: &subRPCServerConfigs{ SignRPC: &signrpc.Config{}, RouterRPC: routerrpc.DefaultConfig(), + PeersRPC: &peersrpc.Config{}, }, Autopilot: &lncfg.AutoPilot{ MaxChannels: 5, @@ -558,6 +598,12 @@ func DefaultConfig() Config { Attempts: defaultTCAttempts, Backoff: defaultTCBackoff, }, + RemoteSigner: &lncfg.CheckConfig{ + Interval: defaultRSInterval, + Timeout: defaultRSTimeout, + Attempts: defaultRSAttempts, + Backoff: defaultRSBackoff, + }, }, Gossip: &lncfg.Gossip{ MaxChannelUpdateBurst: discovery.DefaultMaxChannelUpdateBurst, @@ -577,9 +623,12 @@ func DefaultConfig() Config { registeredChains: chainreg.NewChainRegistry(), ActiveNetParams: chainreg.BitcoinTestNetParams, ChannelCommitInterval: defaultChannelCommitInterval, + PendingCommitInterval: defaultPendingCommitInterval, ChannelCommitBatchSize: defaultChannelCommitBatchSize, CoinSelectionStrategy: defaultCoinSelectionStrategy, - RemoteSigner: &lncfg.RemoteSigner{}, + RemoteSigner: &lncfg.RemoteSigner{ + Timeout: lncfg.DefaultRemoteSignerRPCTimeout, + }, } } @@ -615,11 +664,22 @@ func LoadConfig(interceptor signal.Interceptor) (*Config, error) { // file within it. configFileDir := CleanAndExpandPath(preCfg.LndDir) configFilePath := CleanAndExpandPath(preCfg.ConfigFile) - if configFileDir != DefaultLndDir { - if configFilePath == DefaultConfigFile { - configFilePath = filepath.Join( - configFileDir, lncfg.DefaultConfigFilename, - ) + switch { + // User specified --lnddir but no --configfile. Update the config file + // path to the lnd config directory, but don't require it to exist. + case configFileDir != DefaultLndDir && + configFilePath == DefaultConfigFile: + + configFilePath = filepath.Join( + configFileDir, lncfg.DefaultConfigFilename, + ) + + // User did specify an explicit --configfile, so we check that it does + // exist under that path to avoid surprises. + case configFilePath != DefaultConfigFile: + if !fileExists(configFilePath) { + return nil, fmt.Errorf("specified config file does "+ + "not exist in %s", configFilePath) } } @@ -647,15 +707,34 @@ func LoadConfig(interceptor signal.Interceptor) (*Config, error) { } // Make sure everything we just loaded makes sense. - cleanCfg, err := ValidateConfig(cfg, usageMessage, interceptor, - fileParser, flagParser) + cleanCfg, err := ValidateConfig( + cfg, interceptor, fileParser, flagParser, + ) + if usageErr, ok := err.(*usageError); ok { + // The logging system might not yet be initialized, so we also + // write to stderr to make sure the error appears somewhere. + _, _ = fmt.Fprintln(os.Stderr, usageMessage) + ltndLog.Warnf("Incorrect usage: %v", usageMessage) + + // The log subsystem might not yet be initialized. But we still + // try to log the error there since some packaging solutions + // might only look at the log and not stdout/stderr. + ltndLog.Warnf("Error validating config: %v", usageErr.err) + + return nil, usageErr.err + } if err != nil { + // The log subsystem might not yet be initialized. But we still + // try to log the error there since some packaging solutions + // might only look at the log and not stdout/stderr. + ltndLog.Warnf("Error validating config: %v", err) + return nil, err } // Warn about missing config file only after all other configuration is - // done. This prevents the warning on help messages and invalid - // options. Note this should go directly before the return. + // done. This prevents the warning on help messages and invalid options. + // Note this should go directly before the return. if configFileError != nil { ltndLog.Warnf("%v", configFileError) } @@ -663,12 +742,24 @@ func LoadConfig(interceptor signal.Interceptor) (*Config, error) { return cleanCfg, nil } +// usageError is an error type that signals a problem with the supplied flags. +type usageError struct { + err error +} + +// Error returns the error string. +// +// NOTE: This is part of the error interface. +func (u *usageError) Error() string { + return u.err.Error() +} + // ValidateConfig check the given configuration to be sane. This makes sure no // illegal values or combination of values are set. All file system paths are // normalized. The cleaned up config is returned on success. -func ValidateConfig(cfg Config, usageMessage string, - interceptor signal.Interceptor, fileParser, +func ValidateConfig(cfg Config, interceptor signal.Interceptor, fileParser, flagParser *flags.Parser) (*Config, error) { + // If the provided lnd directory is not the default, we'll modify the // path to all of the files and directories that will live within it. lndDir := CleanAndExpandPath(cfg.LndDir) @@ -685,12 +776,16 @@ func ValidateConfig(cfg Config, usageMessage string, // user has not requested a different location, we'll move the // location to be relative to the specified lnd directory. if cfg.Watchtower.TowerDir == defaultTowerDir { - cfg.Watchtower.TowerDir = - filepath.Join(cfg.DataDir, defaultTowerSubDirname) + cfg.Watchtower.TowerDir = filepath.Join( + cfg.DataDir, defaultTowerSubDirname, + ) } } - funcName := "loadConfig" + funcName := "ValidateConfig" + mkErr := func(format string, args ...interface{}) error { + return fmt.Errorf(funcName+": "+format, args...) + } makeDirectory := func(dir string) error { err := os.MkdirAll(dir, 0700) if err != nil { @@ -705,10 +800,8 @@ func ValidateConfig(cfg Config, usageMessage string, } } - str := "%s: Failed to create lnd directory: %v" - err := fmt.Errorf(str, funcName, err) - _, _ = fmt.Fprintln(os.Stderr, err) - return err + str := "Failed to create lnd directory '%s': %v" + return mkErr(str, dir, err) } return nil @@ -716,21 +809,17 @@ func ValidateConfig(cfg Config, usageMessage string, // IsSet returns true if an option has been set in either the config // file or by a flag. - isSet := func(field string) bool { + isSet := func(field string) (bool, error) { fieldName, ok := reflect.TypeOf(Config{}).FieldByName(field) if !ok { - str := "%s: could not find field %s" - err := fmt.Errorf(str, funcName, field) - _, _ = fmt.Fprintln(os.Stderr, err) - return false + str := "could not find field %s" + return false, mkErr(str, field) } long, ok := fieldName.Tag.Lookup("long") if !ok { - str := "%s: field %s does not have a long tag" - err := fmt.Errorf(str, funcName, field) - _, _ = fmt.Fprintln(os.Stderr, err) - return false + str := "field %s does not have a long tag" + return false, mkErr(str, field) } // The user has the option to set the flag in either the config @@ -751,9 +840,10 @@ func ValidateConfig(cfg Config, usageMessage string, ) return (fileOption != nil && fileOption.IsSet()) || - (fileOptionNested != nil && fileOptionNested.IsSet()) || - (flagOption != nil && flagOption.IsSet()) || - (flagOptionNested != nil && flagOptionNested.IsSet()) + (fileOptionNested != nil && fileOptionNested.IsSet()) || + (flagOption != nil && flagOption.IsSet()) || + (flagOptionNested != nil && flagOptionNested.IsSet()), + nil } // As soon as we're done parsing configuration options, ensure all paths @@ -779,61 +869,31 @@ func ValidateConfig(cfg Config, usageMessage string, cfg.WalletUnlockPasswordFile, ) - // Create the lnd directory and all other sub directories if they don't - // already exist. This makes sure that directory trees are also created - // for files that point to outside of the lnddir. - dirs := []string{ - lndDir, cfg.DataDir, - cfg.LetsEncryptDir, cfg.Watchtower.TowerDir, - filepath.Dir(cfg.TLSCertPath), filepath.Dir(cfg.TLSKeyPath), - filepath.Dir(cfg.AdminMacPath), filepath.Dir(cfg.ReadMacPath), - filepath.Dir(cfg.InvoiceMacPath), - filepath.Dir(cfg.Tor.PrivateKeyPath), - filepath.Dir(cfg.Tor.WatchtowerKeyPath), - } - for _, dir := range dirs { - if err := makeDirectory(dir); err != nil { - return nil, err - } - } - // Ensure that the user didn't attempt to specify negative values for // any of the autopilot params. if cfg.Autopilot.MaxChannels < 0 { - str := "%s: autopilot.maxchannels must be non-negative" - err := fmt.Errorf(str, funcName) - _, _ = fmt.Fprintln(os.Stderr, err) - return nil, err + str := "autopilot.maxchannels must be non-negative" + return nil, mkErr(str) } if cfg.Autopilot.Allocation < 0 { - str := "%s: autopilot.allocation must be non-negative" - err := fmt.Errorf(str, funcName) - _, _ = fmt.Fprintln(os.Stderr, err) - return nil, err + str := "autopilot.allocation must be non-negative" + return nil, mkErr(str) } if cfg.Autopilot.MinChannelSize < 0 { - str := "%s: autopilot.minchansize must be non-negative" - err := fmt.Errorf(str, funcName) - _, _ = fmt.Fprintln(os.Stderr, err) - return nil, err + str := "autopilot.minchansize must be non-negative" + return nil, mkErr(str) } if cfg.Autopilot.MaxChannelSize < 0 { - str := "%s: autopilot.maxchansize must be non-negative" - err := fmt.Errorf(str, funcName) - _, _ = fmt.Fprintln(os.Stderr, err) - return nil, err + str := "autopilot.maxchansize must be non-negative" + return nil, mkErr(str) } if cfg.Autopilot.MinConfs < 0 { - str := "%s: autopilot.minconfs must be non-negative" - err := fmt.Errorf(str, funcName) - _, _ = fmt.Fprintln(os.Stderr, err) - return nil, err + str := "autopilot.minconfs must be non-negative" + return nil, mkErr(str) } if cfg.Autopilot.ConfTarget < 1 { - str := "%s: autopilot.conftarget must be positive" - err := fmt.Errorf(str, funcName) - _, _ = fmt.Fprintln(os.Stderr, err) - return nil, err + str := "autopilot.conftarget must be positive" + return nil, mkErr(str) } // Ensure that the specified values for the min and max channel size @@ -846,7 +906,7 @@ func ValidateConfig(cfg Config, usageMessage string, } if _, err := validateAtplCfg(cfg.Autopilot); err != nil { - return nil, err + return nil, mkErr("error validating autopilot: %v", err) } // Ensure that --maxchansize is properly handled when set by user. @@ -865,32 +925,33 @@ func ValidateConfig(cfg Config, usageMessage string, // Ensure that the user specified values for the min and max channel // size make sense. if cfg.MaxChanSize < cfg.MinChanSize { - return nil, fmt.Errorf("invalid channel size parameters: "+ - "max channel size %v, must be no less than min chan size %v", - cfg.MaxChanSize, cfg.MinChanSize, + return nil, mkErr("invalid channel size parameters: "+ + "max channel size %v, must be no less than min chan "+ + "size %v", cfg.MaxChanSize, cfg.MinChanSize, ) } - // Don't allow superflous --maxchansize greater than + // Don't allow superfluous --maxchansize greater than // BOLT 02 soft-limit for non-wumbo channel - if !cfg.ProtocolOptions.Wumbo() && cfg.MaxChanSize > int64(MaxFundingAmount) { - return nil, fmt.Errorf("invalid channel size parameters: "+ - "maximum channel size %v is greater than maximum non-wumbo"+ - " channel size %v", - cfg.MaxChanSize, MaxFundingAmount, + if !cfg.ProtocolOptions.Wumbo() && + cfg.MaxChanSize > int64(MaxFundingAmount) { + + return nil, mkErr("invalid channel size parameters: "+ + "maximum channel size %v is greater than maximum "+ + "non-wumbo channel size %v", cfg.MaxChanSize, + MaxFundingAmount, ) } // Ensure a valid max channel fee allocation was set. if cfg.MaxChannelFeeAllocation <= 0 || cfg.MaxChannelFeeAllocation > 1 { - return nil, fmt.Errorf("invalid max channel fee allocation: "+ - "%v, must be within (0, 1]", - cfg.MaxChannelFeeAllocation) + return nil, mkErr("invalid max channel fee allocation: %v, "+ + "must be within (0, 1]", cfg.MaxChannelFeeAllocation) } if cfg.MaxCommitFeeRateAnchors < 1 { - return nil, fmt.Errorf("invalid max commit fee rate anchors: "+ - "%v, must be at least 1 sat/vbyte", + return nil, mkErr("invalid max commit fee rate anchors: %v, "+ + "must be at least 1 sat/vByte", cfg.MaxCommitFeeRateAnchors) } @@ -912,7 +973,7 @@ func ValidateConfig(cfg Config, usageMessage string, cfg.net.ResolveTCPAddr, ) if err != nil { - return nil, err + return nil, mkErr("error parsing tor dns: %v", err) } cfg.Tor.DNS = dns.String() } @@ -922,25 +983,24 @@ func ValidateConfig(cfg Config, usageMessage string, cfg.net.ResolveTCPAddr, ) if err != nil { - return nil, err + return nil, mkErr("error parsing tor control address: %v", err) } cfg.Tor.Control = control.String() // Ensure that tor socks host:port is not equal to tor control // host:port. This would lead to lnd not starting up properly. if cfg.Tor.SOCKS == cfg.Tor.Control { - str := "%s: tor.socks and tor.control can not use " + - "the same host:port" - return nil, fmt.Errorf(str, funcName) + str := "tor.socks and tor.control can not us the same host:port" + return nil, mkErr(str) } switch { case cfg.Tor.V2 && cfg.Tor.V3: - return nil, errors.New("either tor.v2 or tor.v3 can be set, " + + return nil, mkErr("either tor.v2 or tor.v3 can be set, " + "but not both") case cfg.DisableListen && (cfg.Tor.V2 || cfg.Tor.V3): - return nil, errors.New("listening must be enabled when " + - "enabling inbound connections over Tor") + return nil, mkErr("listening must be enabled when enabling " + + "inbound connections over Tor") } if cfg.Tor.PrivateKeyPath == "" { @@ -960,11 +1020,13 @@ func ValidateConfig(cfg Config, usageMessage string, switch { case cfg.Tor.V2: cfg.Tor.WatchtowerKeyPath = filepath.Join( - cfg.Watchtower.TowerDir, defaultTorV2PrivateKeyFilename, + cfg.Watchtower.TowerDir, + defaultTorV2PrivateKeyFilename, ) case cfg.Tor.V3: cfg.Tor.WatchtowerKeyPath = filepath.Join( - cfg.Watchtower.TowerDir, defaultTorV3PrivateKeyFilename, + cfg.Watchtower.TowerDir, + defaultTorV3PrivateKeyFilename, ) } } @@ -984,11 +1046,11 @@ func ValidateConfig(cfg Config, usageMessage string, } if cfg.DisableListen && cfg.NAT { - return nil, errors.New("NAT traversal cannot be used when " + + return nil, mkErr("NAT traversal cannot be used when " + "listening is disabled") } if cfg.NAT && len(cfg.ExternalHosts) != 0 { - return nil, errors.New("NAT support and externalhosts are " + + return nil, mkErr("NAT support and externalhosts are " + "mutually exclusive, only one should be selected") } @@ -996,20 +1058,22 @@ func ValidateConfig(cfg Config, usageMessage string, switch { // At this moment, multiple active chains are not supported. case cfg.Litecoin.Active && cfg.Bitcoin.Active: - str := "%s: Currently both Bitcoin and Litecoin cannot be " + + str := "Currently both Bitcoin and Litecoin cannot be " + "active together" - return nil, fmt.Errorf(str, funcName) + return nil, mkErr(str) // Either Bitcoin must be active, or Litecoin must be active. // Otherwise, we don't know which chain we're on. case !cfg.Bitcoin.Active && !cfg.Litecoin.Active: - return nil, fmt.Errorf("%s: either bitcoin.active or "+ - "litecoin.active must be set to 1 (true)", funcName) + return nil, mkErr("either bitcoin.active or " + + "litecoin.active must be set to 1 (true)") case cfg.Litecoin.Active: - err := cfg.Litecoin.Validate(minTimeLockDelta, funding.MinLtcRemoteDelay) + err := cfg.Litecoin.Validate( + minTimeLockDelta, funding.MinLtcRemoteDelay, + ) if err != nil { - return nil, err + return nil, mkErr("error validating litecoin: %v", err) } // Multiple networks can't be selected simultaneously. Count @@ -1034,25 +1098,22 @@ func ValidateConfig(cfg Config, usageMessage string, ltcParams = chainreg.LitecoinSimNetParams } if cfg.Litecoin.SigNet { - return nil, fmt.Errorf("%s: litecoin.signet is not "+ - "supported", funcName) + return nil, mkErr("litecoin.signet is not supported") } if numNets > 1 { - str := "%s: The mainnet, testnet, and simnet params " + + str := "The mainnet, testnet, and simnet params " + "can't be used together -- choose one of the " + "three" - err := fmt.Errorf(str, funcName) - return nil, err + return nil, mkErr(str) } // The target network must be provided, otherwise, we won't // know how to initialize the daemon. if numNets == 0 { - str := "%s: either --litecoin.mainnet, or " + + str := "either --litecoin.mainnet, or " + "litecoin.testnet must be specified" - err := fmt.Errorf(str, funcName) - return nil, err + return nil, mkErr(str) } // The litecoin chain is the current active chain. However @@ -1063,36 +1124,39 @@ func ValidateConfig(cfg Config, usageMessage string, switch cfg.Litecoin.Node { case "ltcd": - err := parseRPCParams(cfg.Litecoin, cfg.LtcdMode, - chainreg.LitecoinChain, funcName, cfg.ActiveNetParams) + err := parseRPCParams( + cfg.Litecoin, cfg.LtcdMode, + chainreg.LitecoinChain, cfg.ActiveNetParams, + ) if err != nil { - err := fmt.Errorf("unable to load RPC "+ + return nil, mkErr("unable to load RPC "+ "credentials for ltcd: %v", err) - return nil, err } case "litecoind": if cfg.Litecoin.SimNet { - return nil, fmt.Errorf("%s: litecoind does not "+ - "support simnet", funcName) + return nil, mkErr("litecoind does not " + + "support simnet") } - err := parseRPCParams(cfg.Litecoin, cfg.LitecoindMode, - chainreg.LitecoinChain, funcName, cfg.ActiveNetParams) + err := parseRPCParams( + cfg.Litecoin, cfg.LitecoindMode, + chainreg.LitecoinChain, cfg.ActiveNetParams, + ) if err != nil { - err := fmt.Errorf("unable to load RPC "+ + return nil, mkErr("unable to load RPC "+ "credentials for litecoind: %v", err) - return nil, err } default: - str := "%s: only ltcd and litecoind mode supported for " + + str := "only ltcd and litecoind mode supported for " + "litecoin at this time" - return nil, fmt.Errorf(str, funcName) + return nil, mkErr(str) } - cfg.Litecoin.ChainDir = filepath.Join(cfg.DataDir, - defaultChainSubDirname, - chainreg.LitecoinChain.String()) + cfg.Litecoin.ChainDir = filepath.Join( + cfg.DataDir, defaultChainSubDirname, + chainreg.LitecoinChain.String(), + ) - // Finally we'll register the litecoin chain as our current + // Finally, we'll register the litecoin chain as our current // primary chain. cfg.registeredChains.RegisterPrimaryChain(chainreg.LitecoinChain) MaxFundingAmount = funding.MaxLtcFundingAmount @@ -1133,9 +1197,9 @@ func ValidateConfig(cfg Config, usageMessage string, cfg.Bitcoin.SigNetChallenge, ) if err != nil { - return nil, fmt.Errorf("%s: Invalid "+ + return nil, mkErr("Invalid "+ "signet challenge, hex decode "+ - "failed: %v", funcName, err) + "failed: %v", err) } sigNetChallenge = challenge } @@ -1158,66 +1222,70 @@ func ValidateConfig(cfg Config, usageMessage string, cfg.ActiveNetParams.Params = &chainParams } if numNets > 1 { - str := "%s: The mainnet, testnet, regtest, and " + - "simnet params can't be used together -- " + - "choose one of the four" - err := fmt.Errorf(str, funcName) - return nil, err + str := "The mainnet, testnet, regtest, and simnet " + + "params can't be used together -- choose one " + + "of the four" + return nil, mkErr(str) } // The target network must be provided, otherwise, we won't // know how to initialize the daemon. if numNets == 0 { - str := "%s: either --bitcoin.mainnet, or " + - "bitcoin.testnet, bitcoin.simnet, or bitcoin.regtest " + + str := "either --bitcoin.mainnet, or bitcoin.testnet," + + "bitcoin.simnet, or bitcoin.regtest " + "must be specified" - err := fmt.Errorf(str, funcName) - return nil, err + return nil, mkErr(str) } - err := cfg.Bitcoin.Validate(minTimeLockDelta, funding.MinBtcRemoteDelay) + err := cfg.Bitcoin.Validate( + minTimeLockDelta, funding.MinBtcRemoteDelay, + ) if err != nil { - return nil, err + return nil, mkErr("error validating bitcoin params: %v", + err) } switch cfg.Bitcoin.Node { case "btcd": err := parseRPCParams( - cfg.Bitcoin, cfg.BtcdMode, chainreg.BitcoinChain, funcName, - cfg.ActiveNetParams, + cfg.Bitcoin, cfg.BtcdMode, + chainreg.BitcoinChain, cfg.ActiveNetParams, ) if err != nil { - err := fmt.Errorf("unable to load RPC "+ + return nil, mkErr("unable to load RPC "+ "credentials for btcd: %v", err) - return nil, err } case "bitcoind": if cfg.Bitcoin.SimNet { - return nil, fmt.Errorf("%s: bitcoind does not "+ - "support simnet", funcName) + return nil, mkErr("bitcoind does not " + + "support simnet") } err := parseRPCParams( - cfg.Bitcoin, cfg.BitcoindMode, chainreg.BitcoinChain, funcName, - cfg.ActiveNetParams, + cfg.Bitcoin, cfg.BitcoindMode, + chainreg.BitcoinChain, cfg.ActiveNetParams, ) if err != nil { - err := fmt.Errorf("unable to load RPC "+ + return nil, mkErr("unable to load RPC "+ "credentials for bitcoind: %v", err) - return nil, err } case "neutrino": // No need to get RPC parameters. + case "nochainbackend": + // Nothing to configure, we're running without any chain + // backend whatsoever (pure signing mode). + default: - str := "%s: only btcd, bitcoind, and neutrino mode " + + str := "only btcd, bitcoind, and neutrino mode " + "supported for bitcoin at this time" - return nil, fmt.Errorf(str, funcName) + return nil, mkErr(str) } - cfg.Bitcoin.ChainDir = filepath.Join(cfg.DataDir, - defaultChainSubDirname, - chainreg.BitcoinChain.String()) + cfg.Bitcoin.ChainDir = filepath.Join( + cfg.DataDir, defaultChainSubDirname, + chainreg.BitcoinChain.String(), + ) // Finally we'll register the bitcoin chain as our current // primary chain. @@ -1227,28 +1295,20 @@ func ValidateConfig(cfg Config, usageMessage string, // Ensure that the user didn't attempt to specify negative values for // any of the autopilot params. if cfg.Autopilot.MaxChannels < 0 { - str := "%s: autopilot.maxchannels must be non-negative" - err := fmt.Errorf(str, funcName) - _, _ = fmt.Fprintln(os.Stderr, err) - return nil, err + str := "autopilot.maxchannels must be non-negative" + return nil, mkErr(str) } if cfg.Autopilot.Allocation < 0 { - str := "%s: autopilot.allocation must be non-negative" - err := fmt.Errorf(str, funcName) - _, _ = fmt.Fprintln(os.Stderr, err) - return nil, err + str := "autopilot.allocation must be non-negative" + return nil, mkErr(str) } if cfg.Autopilot.MinChannelSize < 0 { - str := "%s: autopilot.minchansize must be non-negative" - err := fmt.Errorf(str, funcName) - _, _ = fmt.Fprintln(os.Stderr, err) - return nil, err + str := "autopilot.minchansize must be non-negative" + return nil, mkErr(str) } if cfg.Autopilot.MaxChannelSize < 0 { - str := "%s: autopilot.maxchansize must be non-negative" - err := fmt.Errorf(str, funcName) - _, _ = fmt.Fprintln(os.Stderr, err) - return nil, err + str := "autopilot.maxchansize must be non-negative" + return nil, mkErr(str) } // Ensure that the specified values for the min and max channel size @@ -1270,19 +1330,13 @@ func ValidateConfig(cfg Config, usageMessage string, // Determine if the port is valid. profilePort, err := strconv.Atoi(hostPort) if err != nil || profilePort < 1024 || profilePort > 65535 { - err = fmt.Errorf(str, funcName) - _, _ = fmt.Fprintln(os.Stderr, err) - _, _ = fmt.Fprintln(os.Stderr, usageMessage) - return nil, err + return nil, &usageError{mkErr(str)} } } else { // Try to parse Profile as a port. profilePort, err := strconv.Atoi(cfg.Profile) if err != nil || profilePort < 1024 || profilePort > 65535 { - err = fmt.Errorf(str, funcName) - _, _ = fmt.Fprintln(os.Stderr, err) - _, _ = fmt.Fprintln(os.Stderr, usageMessage) - return nil, err + return nil, &usageError{mkErr(str)} } // Since the user just set a port, we will serve debugging @@ -1299,12 +1353,6 @@ func ValidateConfig(cfg Config, usageMessage string, lncfg.NormalizeNetwork(cfg.ActiveNetParams.Name), ) - // We need to make sure the default network directory exists for when we - // try to create our default macaroons there. - if err := makeDirectory(cfg.networkDir); err != nil { - return nil, err - } - // If a custom macaroon directory wasn't specified and the data // directory has changed from the default path, then we'll also update // the path for the macaroons to be generated. @@ -1324,6 +1372,24 @@ func ValidateConfig(cfg Config, usageMessage string, ) } + // Create the lnd directory and all other sub-directories if they don't + // already exist. This makes sure that directory trees are also created + // for files that point to outside the lnddir. + dirs := []string{ + lndDir, cfg.DataDir, cfg.networkDir, + cfg.LetsEncryptDir, cfg.Watchtower.TowerDir, + filepath.Dir(cfg.TLSCertPath), filepath.Dir(cfg.TLSKeyPath), + filepath.Dir(cfg.AdminMacPath), filepath.Dir(cfg.ReadMacPath), + filepath.Dir(cfg.InvoiceMacPath), + filepath.Dir(cfg.Tor.PrivateKeyPath), + filepath.Dir(cfg.Tor.WatchtowerKeyPath), + } + for _, dir := range dirs { + if err := makeDirectory(dir); err != nil { + return nil, err + } + } + // Similarly, if a custom back up file path wasn't specified, then // we'll update the file location to match our set network directory. if cfg.BackupFilePath == "" { @@ -1334,14 +1400,15 @@ func ValidateConfig(cfg Config, usageMessage string, // Append the network type to the log directory so it is "namespaced" // per network in the same fashion as the data directory. - cfg.LogDir = filepath.Join(cfg.LogDir, - cfg.registeredChains.PrimaryChain().String(), - lncfg.NormalizeNetwork(cfg.ActiveNetParams.Name)) + cfg.LogDir = filepath.Join( + cfg.LogDir, cfg.registeredChains.PrimaryChain().String(), + lncfg.NormalizeNetwork(cfg.ActiveNetParams.Name), + ) // A log writer must be passed in, otherwise we can't function and would // run into a panic later on. if cfg.LogWriter == nil { - return nil, fmt.Errorf("log writer missing in config") + return nil, mkErr("log writer missing in config") } // Special show command to list supported subsystems and exit. @@ -1358,19 +1425,15 @@ func ValidateConfig(cfg Config, usageMessage string, cfg.MaxLogFileSize, cfg.MaxLogFiles, ) if err != nil { - str := "%s: log rotation setup failed: %v" - err = fmt.Errorf(str, funcName, err.Error()) - _, _ = fmt.Fprintln(os.Stderr, err) - return nil, err + str := "log rotation setup failed: %v" + return nil, mkErr(str, err) } // Parse, validate, and set debug log level(s). err = build.ParseAndSetDebugLevels(cfg.DebugLevel, cfg.LogWriter) if err != nil { - err = fmt.Errorf("%s: %v", funcName, err.Error()) - _, _ = fmt.Fprintln(os.Stderr, err) - _, _ = fmt.Fprintln(os.Stderr, usageMessage) - return nil, err + str := "error parsing debug level: %v" + return nil, &usageError{mkErr(str, err)} } // At least one RPCListener is required. So listen on localhost per @@ -1406,7 +1469,7 @@ func ValidateConfig(cfg Config, usageMessage string, cfg.net.ResolveTCPAddr, ) if err != nil { - return nil, err + return nil, mkErr("error normalizing RPC listen addrs: %v", err) } // Add default port to all REST listener addresses if needed and remove @@ -1416,25 +1479,25 @@ func ValidateConfig(cfg Config, usageMessage string, cfg.net.ResolveTCPAddr, ) if err != nil { - return nil, err + return nil, mkErr("error normalizing REST listen addrs: %v", err) } switch { // The no seed backup and auto unlock are mutually exclusive. case cfg.NoSeedBackup && cfg.WalletUnlockPasswordFile != "": - return nil, fmt.Errorf("cannot set noseedbackup and " + + return nil, mkErr("cannot set noseedbackup and " + "wallet-unlock-password-file at the same time") // The "allow-create" flag cannot be set without the auto unlock file. case cfg.WalletUnlockAllowCreate && cfg.WalletUnlockPasswordFile == "": - return nil, fmt.Errorf("cannot set wallet-unlock-allow-create " + + return nil, mkErr("cannot set wallet-unlock-allow-create " + "without wallet-unlock-password-file") // If a password file was specified, we need it to exist. case cfg.WalletUnlockPasswordFile != "" && !lnrpc.FileExists(cfg.WalletUnlockPasswordFile): - return nil, fmt.Errorf("wallet unlock password file %s does "+ + return nil, mkErr("wallet unlock password file %s does "+ "not exist", cfg.WalletUnlockPasswordFile) } @@ -1446,7 +1509,8 @@ func ValidateConfig(cfg Config, usageMessage string, cfg.RPCListeners, !cfg.NoMacaroons, true, ) if err != nil { - return nil, err + return nil, mkErr("error enforcing safe authentication on "+ + "RPC ports: %v", err) } if cfg.DisableRest { @@ -1457,7 +1521,8 @@ func ValidateConfig(cfg Config, usageMessage string, cfg.RESTListeners, !cfg.NoMacaroons, !cfg.DisableRestTLS, ) if err != nil { - return nil, err + return nil, mkErr("error enforcing safe "+ + "authentication on REST ports: %v", err) } } @@ -1475,7 +1540,8 @@ func ValidateConfig(cfg Config, usageMessage string, cfg.net.ResolveTCPAddr, ) if err != nil { - return nil, err + return nil, mkErr("error normalizing p2p listen "+ + "addrs: %v", err) } // Add default port to all external IP addresses if needed and remove @@ -1493,10 +1559,9 @@ func ValidateConfig(cfg Config, usageMessage string, // that. for _, p2pListener := range cfg.Listeners { if lncfg.IsUnix(p2pListener) { - err := fmt.Errorf("unix socket addresses cannot be "+ - "used for the p2p connection listener: %s", - p2pListener) - return nil, err + return nil, mkErr("unix socket addresses "+ + "cannot be used for the p2p "+ + "connection listener: %s", p2pListener) } } } @@ -1504,15 +1569,18 @@ func ValidateConfig(cfg Config, usageMessage string, // Ensure that the specified minimum backoff is below or equal to the // maximum backoff. if cfg.MinBackoff > cfg.MaxBackoff { - return nil, fmt.Errorf("maxbackoff must be greater than " + - "minbackoff") + return nil, mkErr("maxbackoff must be greater than minbackoff") } // Newer versions of lnd added a new sub-config for bolt-specific - // parameters. However we want to also allow existing users to use the + // parameters. However, we want to also allow existing users to use the // value on the top-level config. If the outer config value is set, // then we'll use that directly. - if isSet("SyncFreelist") { + flagSet, err := isSet("SyncFreelist") + if err != nil { + return nil, mkErr("error parsing freelist sync flag: %v", err) + } + if flagSet { cfg.DB.Bolt.NoFreelistSync = !cfg.SyncFreelist } @@ -1520,13 +1588,29 @@ func ValidateConfig(cfg Config, usageMessage string, // than the protocol maximum. maxRemoteHtlcs := uint16(input.MaxHTLCNumber / 2) if cfg.DefaultRemoteMaxHtlcs > maxRemoteHtlcs { - return nil, fmt.Errorf("default-remote-max-htlcs (%v) must be "+ + return nil, mkErr("default-remote-max-htlcs (%v) must be "+ "less than %v", cfg.DefaultRemoteMaxHtlcs, maxRemoteHtlcs) } + // Clamp the ChannelCommitInterval so that commitment updates can still + // happen in a reasonable timeframe. + if cfg.ChannelCommitInterval > maxChannelCommitInterval { + return nil, mkErr("channel-commit-interval (%v) must be less "+ + "than %v", cfg.ChannelCommitInterval, + maxChannelCommitInterval) + } + + // Limit PendingCommitInterval so we don't wait too long for the remote + // party to send back a revoke. + if cfg.PendingCommitInterval > maxPendingCommitInterval { + return nil, mkErr("pending-commit-interval (%v) must be less "+ + "than %v", cfg.PendingCommitInterval, + maxPendingCommitInterval) + } + if err := cfg.Gossip.Parse(); err != nil { - return nil, err + return nil, mkErr("error parsing gossip syncer: %v", err) } // Log a warning if our expiry delta is not greater than our incoming @@ -1550,6 +1634,7 @@ func ValidateConfig(cfg Config, usageMessage string, cfg.Cluster, cfg.HealthChecks, cfg.RPCMiddleware, + cfg.RemoteSigner, ) if err != nil { return nil, err @@ -1558,13 +1643,13 @@ func ValidateConfig(cfg Config, usageMessage string, // Finally, ensure that the user's color is correctly formatted, // otherwise the server will not be able to start after the unlocking // the wallet. - _, err = parseHexColor(cfg.Color) + _, err = lncfg.ParseHexColor(cfg.Color) if err != nil { - return nil, fmt.Errorf("unable to parse node color: %v", err) + return nil, mkErr("unable to parse node color: %v", err) } // All good, return the sanitized result. - return &cfg, err + return &cfg, nil } // graphDatabaseDir returns the default directory where the local bolt graph db @@ -1585,7 +1670,10 @@ func (c *Config) ImplementationConfig( // watch-only source of chain and address data. But we don't need any // private key material in that btcwallet base wallet. if c.RemoteSigner.Enable { - rpcImpl := NewRPCSignerWalletImpl(c, ltndLog, interceptor) + rpcImpl := NewRPCSignerWalletImpl( + c, ltndLog, interceptor, + c.RemoteSigner.MigrateWatchOnly, + ) return &ImplementationCfg{ GrpcRegistrar: rpcImpl, RestRegistrar: rpcImpl, @@ -1636,13 +1724,12 @@ func CleanAndExpandPath(path string) string { } func parseRPCParams(cConfig *lncfg.Chain, nodeConfig interface{}, - net chainreg.ChainCode, funcName string, - netParams chainreg.BitcoinNetParams) error { // nolint:unparam + net chainreg.ChainCode, netParams chainreg.BitcoinNetParams) error { // First, we'll check our node config to make sure the RPC parameters // were set correctly. We'll also determine the path to the conf file // depending on the backend node. - var daemonName, confDir, confFile string + var daemonName, confDir, confFile, confFileBase string switch conf := nodeConfig.(type) { case *lncfg.Btcd: // If both RPCUser and RPCPass are set, we assume those @@ -1656,11 +1743,11 @@ func parseRPCParams(cConfig *lncfg.Chain, nodeConfig interface{}, case chainreg.BitcoinChain: daemonName = "btcd" confDir = conf.Dir - confFile = "btcd" + confFileBase = "btcd" case chainreg.LitecoinChain: daemonName = "ltcd" confDir = conf.Dir - confFile = "ltcd" + confFileBase = "ltcd" } // If only ONE of RPCUser or RPCPass is set, we assume the @@ -1691,11 +1778,19 @@ func parseRPCParams(cConfig *lncfg.Chain, nodeConfig interface{}, } } - // If all of RPCUser, RPCPass, ZMQBlockHost, and ZMQTxHost are - // set, we assume those parameters are good to use. - if conf.RPCUser != "" && conf.RPCPass != "" && - conf.ZMQPubRawBlock != "" && conf.ZMQPubRawTx != "" { - return nil + if conf.RPCUser != "" && conf.RPCPass != "" { + // If all of RPCUser, RPCPass, ZMQBlockHost, and + // ZMQTxHost are set, we assume those parameters are + // good to use. + if conf.ZMQPubRawBlock != "" && conf.ZMQPubRawTx != "" { + return nil + } + + // If RPCUser and RPCPass are set and RPCPolling is + // enabled, we assume the parameters are good to use. + if conf.RPCPolling { + return nil + } } // Get the daemon name for displaying proper errors. @@ -1703,11 +1798,13 @@ func parseRPCParams(cConfig *lncfg.Chain, nodeConfig interface{}, case chainreg.BitcoinChain: daemonName = "bitcoind" confDir = conf.Dir - confFile = "bitcoin" + confFile = conf.ConfigPath + confFileBase = "bitcoin" case chainreg.LitecoinChain: daemonName = "litecoind" confDir = conf.Dir - confFile = "litecoin" + confFile = conf.ConfigPath + confFileBase = "litecoin" } // If not all of the parameters are set, we'll assume the user @@ -1726,32 +1823,34 @@ func parseRPCParams(cConfig *lncfg.Chain, nodeConfig interface{}, // the RPC credentials from the configuration. So if lnd wasn't // specified the parameters, then we won't be able to start. if cConfig.SimNet { - str := "%v: rpcuser and rpcpass must be set to your btcd " + - "node's RPC parameters for simnet mode" - return fmt.Errorf(str, funcName) + return fmt.Errorf("rpcuser and rpcpass must be set to your " + + "btcd node's RPC parameters for simnet mode") } fmt.Println("Attempting automatic RPC configuration to " + daemonName) - confFile = filepath.Join(confDir, fmt.Sprintf("%v.conf", confFile)) + if confFile == "" { + confFile = filepath.Join(confDir, fmt.Sprintf("%v.conf", + confFileBase)) + } switch cConfig.Node { case "btcd", "ltcd": nConf := nodeConfig.(*lncfg.Btcd) rpcUser, rpcPass, err := extractBtcdRPCParams(confFile) if err != nil { - return fmt.Errorf("unable to extract RPC credentials:"+ - " %v, cannot start w/o RPC connection", - err) + return fmt.Errorf("unable to extract RPC credentials: "+ + "%v, cannot start w/o RPC connection", err) } nConf.RPCUser, nConf.RPCPass = rpcUser, rpcPass + case "bitcoind", "litecoind": nConf := nodeConfig.(*lncfg.Bitcoind) rpcUser, rpcPass, zmqBlockHost, zmqTxHost, err := - extractBitcoindRPCParams(netParams.Params.Name, confFile) + extractBitcoindRPCParams(netParams.Params.Name, + nConf.Dir, confFile, nConf.RPCCookie) if err != nil { - return fmt.Errorf("unable to extract RPC credentials:"+ - " %v, cannot start w/o RPC connection", - err) + return fmt.Errorf("unable to extract RPC credentials: "+ + "%v, cannot start w/o RPC connection", err) } nConf.RPCUser, nConf.RPCPass = rpcUser, rpcPass nConf.ZMQPubRawBlock, nConf.ZMQPubRawTx = zmqBlockHost, zmqTxHost @@ -1808,12 +1907,11 @@ func extractBtcdRPCParams(btcdConfigPath string) (string, string, error) { } // extractBitcoindRPCParams attempts to extract the RPC credentials for an -// existing bitcoind node instance. The passed path is expected to be the -// location of bitcoind's bitcoin.conf on the target system. The routine looks -// for a cookie first, optionally following the datadir configuration option in -// the bitcoin.conf. If it doesn't find one, it looks for rpcuser/rpcpassword. -func extractBitcoindRPCParams(networkName string, - bitcoindConfigPath string) (string, string, string, string, error) { +// existing bitcoind node instance. The routine looks for a cookie first, +// optionally following the datadir configuration option in the bitcoin.conf. If +// it doesn't find one, it looks for rpcuser/rpcpassword. +func extractBitcoindRPCParams(networkName, bitcoindDataDir, bitcoindConfigPath, + rpcCookiePath string) (string, string, string, string, error) { // First, we'll open up the bitcoind configuration file found at the // target destination. @@ -1861,6 +1959,9 @@ func extractBitcoindRPCParams(networkName string, // Next, we'll try to find an auth cookie. We need to detect the chain // by seeing if one is specified in the configuration file. dataDir := filepath.Dir(bitcoindConfigPath) + if bitcoindDataDir != "" { + dataDir = bitcoindDataDir + } dataDirRE, err := regexp.Compile(`(?m)^\s*datadir\s*=\s*([^\s]+)`) if err != nil { return "", "", "", "", err @@ -1870,7 +1971,7 @@ func extractBitcoindRPCParams(networkName string, dataDir = string(dataDirSubmatches[1]) } - chainDir := "" + var chainDir string switch networkName { case "mainnet": chainDir = "" @@ -1880,7 +1981,11 @@ func extractBitcoindRPCParams(networkName string, return "", "", "", "", fmt.Errorf("unexpected networkname %v", networkName) } - cookie, err := ioutil.ReadFile(filepath.Join(dataDir, chainDir, ".cookie")) + cookiePath := filepath.Join(dataDir, chainDir, ".cookie") + if rpcCookiePath != "" { + cookiePath = rpcCookiePath + } + cookie, err := ioutil.ReadFile(cookiePath) if err == nil { splitCookie := strings.Split(string(cookie), ":") if len(splitCookie) == 2 { diff --git a/config_builder.go b/config_builder.go index a2ae613eb5..71ab15611b 100644 --- a/config_builder.go +++ b/config_builder.go @@ -35,7 +35,6 @@ import ( "github.com/lightningnetwork/lnd/macaroons" "github.com/lightningnetwork/lnd/rpcperms" "github.com/lightningnetwork/lnd/signal" - "github.com/lightningnetwork/lnd/tor" "github.com/lightningnetwork/lnd/walletunlocker" "github.com/lightningnetwork/lnd/watchtower" "github.com/lightningnetwork/lnd/watchtower/wtclient" @@ -144,8 +143,9 @@ type DefaultWalletImpl struct { logger btclog.Logger interceptor signal.Interceptor - watchOnly bool - pwService *walletunlocker.UnlockerService + watchOnly bool + migrateWatchOnly bool + pwService *walletunlocker.UnlockerService } // NewDefaultWalletImpl creates a new default wallet implementation. @@ -422,28 +422,29 @@ func (d *DefaultWalletImpl) BuildWalletConfig(ctx context.Context, return nil, nil, nil, err } + // Send an admin macaroon to all our listeners that requested + // one by setting a non-nil macaroon channel. + adminMacBytes, err := bakeMacaroon( + ctx, macaroonService, adminPermissions(), + ) + if err != nil { + return nil, nil, nil, err + } + for _, lis := range grpcListeners { + if lis.MacChan != nil { + lis.MacChan <- adminMacBytes + } + } + // In case we actually needed to unlock the wallet, we now need // to create an instance of the admin macaroon and send it to // the unlocker so it can forward it to the user. In no seed // backup mode, there's nobody listening on the channel and we'd // block here forever. if !d.cfg.NoSeedBackup { - adminMacBytes, err := bakeMacaroon( - ctx, macaroonService, adminPermissions(), - ) - if err != nil { - return nil, nil, nil, err - } - // The channel is buffered by one element so writing // should not block here. walletInitParams.MacResponseChan <- adminMacBytes - - for _, lis := range grpcListeners { - if lis.MacChan != nil { - lis.MacChan <- adminMacBytes - } - } } // If the user requested a stateless initialization, no macaroon @@ -559,16 +560,17 @@ func (d *DefaultWalletImpl) BuildWalletConfig(ctx context.Context, } walletConfig := &btcwallet.Config{ - PrivatePass: privateWalletPw, - PublicPass: publicWalletPw, - Birthday: walletInitParams.Birthday, - RecoveryWindow: walletInitParams.RecoveryWindow, - NetParams: d.cfg.ActiveNetParams.Params, - CoinType: d.cfg.ActiveNetParams.CoinType, - Wallet: walletInitParams.Wallet, - LoaderOptions: []btcwallet.LoaderOption{dbs.WalletDB}, - ChainSource: partialChainControl.ChainSource, - WatchOnly: d.watchOnly, + PrivatePass: privateWalletPw, + PublicPass: publicWalletPw, + Birthday: walletInitParams.Birthday, + RecoveryWindow: walletInitParams.RecoveryWindow, + NetParams: d.cfg.ActiveNetParams.Params, + CoinType: d.cfg.ActiveNetParams.CoinType, + Wallet: walletInitParams.Wallet, + LoaderOptions: []btcwallet.LoaderOption{dbs.WalletDB}, + ChainSource: partialChainControl.ChainSource, + WatchOnly: d.watchOnly, + MigrateWatchOnly: d.migrateWatchOnly, } // Parse coin selection strategy. @@ -600,7 +602,7 @@ func (d *DefaultWalletImpl) BuildChainControl( *walletConfig, partialChainControl.Cfg.BlockCache, ) if err != nil { - fmt.Printf("unable to create wallet controller: %v\n", err) + err := fmt.Errorf("unable to create wallet controller: %v", err) d.logger.Error(err) return nil, nil, err } @@ -649,15 +651,17 @@ type RPCSignerWalletImpl struct { // NewRPCSignerWalletImpl creates a new instance of the remote signing wallet // implementation. func NewRPCSignerWalletImpl(cfg *Config, logger btclog.Logger, - interceptor signal.Interceptor) *RPCSignerWalletImpl { + interceptor signal.Interceptor, + migrateWatchOnly bool) *RPCSignerWalletImpl { return &RPCSignerWalletImpl{ DefaultWalletImpl: &DefaultWalletImpl{ - cfg: cfg, - logger: logger, - interceptor: interceptor, - watchOnly: true, - pwService: createWalletUnlockerService(cfg), + cfg: cfg, + logger: logger, + interceptor: interceptor, + watchOnly: true, + migrateWatchOnly: migrateWatchOnly, + pwService: createWalletUnlockerService(cfg), }, } } @@ -675,7 +679,7 @@ func (d *RPCSignerWalletImpl) BuildChainControl( *walletConfig, partialChainControl.Cfg.BlockCache, ) if err != nil { - fmt.Printf("unable to create wallet controller: %v\n", err) + err := fmt.Errorf("unable to create wallet controller: %v", err) d.logger.Error(err) return nil, nil, err } @@ -686,11 +690,11 @@ func (d *RPCSignerWalletImpl) BuildChainControl( rpcKeyRing, err := rpcwallet.NewRPCKeyRing( baseKeyRing, walletController, - d.DefaultWalletImpl.cfg.RemoteSigner, - rpcwallet.DefaultRPCTimeout, + d.DefaultWalletImpl.cfg.RemoteSigner, walletConfig.NetParams, ) if err != nil { - fmt.Printf("unable to create RPC remote signing wallet %v", err) + err := fmt.Errorf("unable to create RPC remote signing wallet "+ + "%v", err) d.logger.Error(err) return nil, nil, err } @@ -947,7 +951,6 @@ func waitForWalletPassword(cfg *Config, // for creation or unlocking, as a new wallet db will be created if // none exists when creating the chain control. select { - // The wallet is being created for the first time, we'll check to see // if the user provided any entropy for seed creation. If so, then // we'll create the wallet early to load the seed. @@ -1108,7 +1111,14 @@ func importWatchOnlyAccounts(wallet *wallet.Wallet, for _, scope := range scopes { addrSchema := waddrmgr.ScopeAddrMap[waddrmgr.KeyScopeBIP0084] - if scope.Scope.Purpose == waddrmgr.KeyScopeBIP0049Plus.Purpose { + + // We want witness pubkey hash by default, except for BIP49 + // where we want mixed and BIP86 where we want taproot address + // formats. + switch scope.Scope.Purpose { + case waddrmgr.KeyScopeBIP0049Plus.Purpose, + waddrmgr.KeyScopeBIP0086.Purpose: + addrSchema = waddrmgr.ScopeAddrMap[scope.Scope] } @@ -1190,43 +1200,12 @@ func initNeutrinoBackend(cfg *Config, chainDir string, AddPeers: cfg.NeutrinoMode.AddPeers, ConnectPeers: cfg.NeutrinoMode.ConnectPeers, Dialer: func(addr net.Addr) (net.Conn, error) { - dialAddr := addr - if tor.IsOnionFakeIP(addr) { - // Because the Neutrino address manager only - // knows IP addresses, we need to turn any fake - // tcp6 address that actually encodes an Onion - // v2 address back into the hostname - // representation before we can pass it to the - // dialer. - var err error - dialAddr, err = tor.FakeIPToOnionHost(addr) - if err != nil { - return nil, err - } - } - return cfg.net.Dial( - dialAddr.Network(), dialAddr.String(), + addr.Network(), addr.String(), cfg.ConnectionTimeout, ) }, NameResolver: func(host string) ([]net.IP, error) { - if tor.IsOnionHost(host) { - // Neutrino internally uses btcd's address - // manager which only operates on an IP level - // and does not understand onion hosts. We need - // to turn an onion host into a fake - // representation of an IP address to make it - // possible to connect to a block filter backend - // that serves on an Onion v2 hidden service. - fakeIP, err := tor.OnionHostToFakeIP(host) - if err != nil { - return nil, err - } - - return []net.IP{fakeIP}, nil - } - addrs, err := cfg.net.LookupHost(host) if err != nil { return nil, err diff --git a/contractcourt/anchor_resolver.go b/contractcourt/anchor_resolver.go index fe2adc8b14..80ac456316 100644 --- a/contractcourt/anchor_resolver.go +++ b/contractcourt/anchor_resolver.go @@ -5,9 +5,9 @@ import ( "io" "sync" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/sweep" @@ -125,7 +125,6 @@ func (c *anchorResolver) Resolve() (ContractResolver, error) { select { case sweepRes := <-resultChan: switch sweepRes.Err { - // Anchor was swept successfully. case nil: sweepTxID := sweepRes.Tx.TxHash() diff --git a/contractcourt/breach_resolver.go b/contractcourt/breach_resolver.go new file mode 100644 index 0000000000..922ec609ab --- /dev/null +++ b/contractcourt/breach_resolver.go @@ -0,0 +1,121 @@ +package contractcourt + +import ( + "encoding/binary" + "io" + + "github.com/lightningnetwork/lnd/channeldb" +) + +// breachResolver is a resolver that will handle breached closes. In the +// future, this will likely take over the duties the current breacharbiter has. +type breachResolver struct { + // resolved reflects if the contract has been fully resolved or not. + resolved bool + + // subscribed denotes whether or not the breach resolver has subscribed + // to the breacharbiter for breach resolution. + subscribed bool + + // replyChan is closed when the breach arbiter has completed serving + // justice. + replyChan chan struct{} + + contractResolverKit +} + +// newBreachResolver instantiates a new breach resolver. +func newBreachResolver(resCfg ResolverConfig) *breachResolver { + r := &breachResolver{ + contractResolverKit: *newContractResolverKit(resCfg), + replyChan: make(chan struct{}), + } + + r.initLogger(r) + + return r +} + +// ResolverKey returns the unique identifier for this resolver. +func (b *breachResolver) ResolverKey() []byte { + key := newResolverID(b.ChanPoint) + return key[:] +} + +// Resolve queries the breacharbiter to see if the justice transaction has been +// broadcast. +func (b *breachResolver) Resolve() (ContractResolver, error) { + if !b.subscribed { + complete, err := b.SubscribeBreachComplete( + &b.ChanPoint, b.replyChan, + ) + if err != nil { + return nil, err + } + + // If the breach resolution process is already complete, then + // we can cleanup and checkpoint the resolved state. + if complete { + b.resolved = true + return nil, b.Checkpoint(b) + } + + // Prevent duplicate subscriptions. + b.subscribed = true + } + + select { + case <-b.replyChan: + // The replyChan has been closed, signalling that the breach + // has been fully resolved. Checkpoint the resolved state and + // exit. + b.resolved = true + return nil, b.Checkpoint(b) + case <-b.quit: + } + + return nil, errResolverShuttingDown +} + +// Stop signals the breachResolver to stop. +func (b *breachResolver) Stop() { + close(b.quit) +} + +// IsResolved returns true if the breachResolver is fully resolved and cleanup +// can occur. +func (b *breachResolver) IsResolved() bool { + return b.resolved +} + +// SupplementState adds additional state to the breachResolver. +func (b *breachResolver) SupplementState(_ *channeldb.OpenChannel) { +} + +// Encode encodes the breachResolver to the passed writer. +func (b *breachResolver) Encode(w io.Writer) error { + return binary.Write(w, endian, b.resolved) +} + +// newBreachResolverFromReader attempts to decode an encoded breachResolver +// from the passed Reader instance, returning an active breachResolver. +func newBreachResolverFromReader(r io.Reader, resCfg ResolverConfig) ( + *breachResolver, error) { + + b := &breachResolver{ + contractResolverKit: *newContractResolverKit(resCfg), + replyChan: make(chan struct{}), + } + + if err := binary.Read(r, endian, &b.resolved); err != nil { + return nil, err + } + + b.initLogger(b) + + return b, nil +} + +// A compile time assertion to ensure breachResolver meets the ContractResolver +// interface. +var _ ContractResolver = (*breachResolver)(nil) diff --git a/contractcourt/breacharbiter.go b/contractcourt/breacharbiter.go index 3253e00094..b5f7583cc6 100644 --- a/contractcourt/breacharbiter.go +++ b/contractcourt/breacharbiter.go @@ -9,12 +9,11 @@ import ( "sync" "github.com/btcsuite/btcd/blockchain" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/davecgh/go-spew/spew" - "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/input" @@ -48,11 +47,6 @@ var ( // procedure, we can recover and continue from the persisted state. retributionBucket = []byte("retribution") - // justiceTxnBucket holds the finalized justice transactions for all - // breached contracts. Entries are added to the justice txn bucket just - // before broadcasting the sweep txn. - justiceTxnBucket = []byte("justice-txn") - // errBrarShuttingDown is an error returned if the breacharbiter has // been signalled to exit. errBrarShuttingDown = errors.New("breacharbiter shutting down") @@ -185,6 +179,8 @@ type BreachArbiter struct { cfg *BreachConfig + subscriptions map[wire.OutPoint]chan struct{} + quit chan struct{} wg sync.WaitGroup sync.Mutex @@ -194,8 +190,9 @@ type BreachArbiter struct { // its dependent objects. func NewBreachArbiter(cfg *BreachConfig) *BreachArbiter { return &BreachArbiter{ - cfg: cfg, - quit: make(chan struct{}), + cfg: cfg, + subscriptions: make(map[wire.OutPoint]chan struct{}), + quit: make(chan struct{}), } } @@ -204,14 +201,13 @@ func NewBreachArbiter(cfg *BreachConfig) *BreachArbiter { func (b *BreachArbiter) Start() error { var err error b.started.Do(func() { + brarLog.Info("Breach arbiter starting") err = b.start() }) return err } func (b *BreachArbiter) start() error { - brarLog.Tracef("Starting breach arbiter") - // Load all retributions currently persisted in the retribution store. var breachRetInfos map[wire.OutPoint]retributionInfo if err := b.cfg.Store.ForAll(func(ret *retributionInfo) error { @@ -322,6 +318,47 @@ func (b *BreachArbiter) IsBreached(chanPoint *wire.OutPoint) (bool, error) { return b.cfg.Store.IsBreached(chanPoint) } +// SubscribeBreachComplete is used by outside subsystems to be notified of a +// successful breach resolution. +func (b *BreachArbiter) SubscribeBreachComplete(chanPoint *wire.OutPoint, + c chan struct{}) (bool, error) { + + breached, err := b.cfg.Store.IsBreached(chanPoint) + if err != nil { + // If an error occurs, no subscription will be registered. + return false, err + } + + if !breached { + // If chanPoint no longer exists in the Store, then the breach + // was cleaned up successfully. Any subscription that occurs + // happens after the breach information was persisted to the + // underlying store. + return true, nil + } + + // Otherwise since the channel point is not resolved, add a + // subscription. There can only be one subscription per channel point. + b.Lock() + defer b.Unlock() + b.subscriptions[*chanPoint] = c + + return false, nil +} + +// notifyBreachComplete is used by the BreachArbiter to notify outside +// subsystems that the breach resolution process is complete. +func (b *BreachArbiter) notifyBreachComplete(chanPoint *wire.OutPoint) { + b.Lock() + defer b.Unlock() + if c, ok := b.subscriptions[*chanPoint]; ok { + close(c) + } + + // Remove the subscription. + delete(b.subscriptions, *chanPoint) +} + // contractObserver is the primary goroutine for the BreachArbiter. This // goroutine is responsible for handling breach events coming from the // contractcourt on the ContractBreaches channel. If a channel breach is @@ -489,7 +526,7 @@ func (b *BreachArbiter) waitForSpendEvent(breachInfo *retributionInfo, // spends it to the second level, and mutates the breach output into one that // is able to properly sweep that second level output. We'll use this function // when we go to sweep a breached commitment transaction, but the cheating -// party has already attempted to take it to the second level +// party has already attempted to take it to the second level. func convertToSecondLevelRevoke(bo *breachedOutput, breachInfo *retributionInfo, spendDetails *chainntnfs.SpendDetail) { @@ -582,7 +619,6 @@ func updateBreachInfo(breachInfo *retributionInfo, spends []spend) ( // count the total and revoked funds swept depending on the // input type. switch breachedOutput.witnessType { - // If the output being revoked is the remote commitment // output or an offered HTLC output, it's amount // contributes to the value of funds being revoked from @@ -651,7 +687,7 @@ func (b *BreachArbiter) exactRetribution(confChan *chainntnfs.ConfirmationEvent, // We may have to wait for some of the HTLC outputs to be spent to the // second level before broadcasting the justice tx. We'll store the - // SpendEvents between each attempt to not re-register uneccessarily. + // SpendEvents between each attempt to not re-register unnecessarily. spendNtfns := make(map[wire.OutPoint]*chainntnfs.SpendEvent) // Compute both the total value of funds being swept and the @@ -715,7 +751,6 @@ justiceTxBroadcast: Loop: for { - select { case spends := <-spendChan: // Update the breach info with the new spends. @@ -857,6 +892,14 @@ func (b *BreachArbiter) cleanupBreach(chanPoint *wire.OutPoint) error { err) } + // This is after the Remove call so that the chan passed in via + // SubscribeBreachComplete is always notified, no matter when it is + // called. Otherwise, if notifyBreachComplete was before Remove, a + // very rare edge case could occur in which SubscribeBreachComplete + // is called after notifyBreachComplete and before Remove, meaning the + // caller would never be notified. + b.notifyBreachComplete(chanPoint) + return nil } @@ -1048,11 +1091,15 @@ func (bo *breachedOutput) SignDesc() *input.SignDescriptor { // sign descriptor. The method then returns the witness computed by invoking // this function on the first and subsequent calls. func (bo *breachedOutput) CraftInputScript(signer input.Signer, txn *wire.MsgTx, - hashCache *txscript.TxSigHashes, txinIdx int) (*input.Script, error) { + hashCache *txscript.TxSigHashes, + prevOutputFetcher txscript.PrevOutputFetcher, + txinIdx int) (*input.Script, error) { // First, we ensure that the witness generation function has been // initialized for this breached output. - bo.witnessFunc = bo.witnessType.WitnessGenerator(signer, bo.SignDesc()) + signDesc := bo.SignDesc() + signDesc.PrevOutputFetcher = prevOutputFetcher + bo.witnessFunc = bo.witnessType.WitnessGenerator(signer, signDesc) // Now that we have ensured that the witness generation function has // been initialized, we can proceed to execute it and generate the @@ -1196,7 +1243,7 @@ func newRetributionInfo(chanPoint *wire.OutPoint, } return &retributionInfo{ - commitHash: breachInfo.BreachTransaction.TxHash(), + commitHash: breachInfo.BreachTxHash, chainHash: breachInfo.ChainHash, chanPoint: *chanPoint, breachedOutputs: breachedOutputs, @@ -1282,6 +1329,7 @@ func (b *BreachArbiter) createJusticeTx( // createSweepTx creates a tx that sweeps the passed inputs back to our wallet. func (b *BreachArbiter) createSweepTx(inputs []input.Input) (*wire.MsgTx, error) { + if len(inputs) == 0 { return nil, nil } @@ -1348,8 +1396,8 @@ func (b *BreachArbiter) sweepSpendableOutputsTxn(txWeight int64, // Compute the total amount contained in the inputs. var totalAmt btcutil.Amount - for _, input := range inputs { - totalAmt += btcutil.Amount(input.SignDesc().Output.Value) + for _, inp := range inputs { + totalAmt += btcutil.Amount(inp.SignDesc().Output.Value) } // We'll actually attempt to target inclusion within the next two @@ -1375,10 +1423,10 @@ func (b *BreachArbiter) sweepSpendableOutputsTxn(txWeight int64, // Next, we add all of the spendable outputs as inputs to the // transaction. - for _, input := range inputs { + for _, inp := range inputs { txn.AddTxIn(&wire.TxIn{ - PreviousOutPoint: *input.OutPoint(), - Sequence: input.BlocksToMaturity(), + PreviousOutPoint: *inp.OutPoint(), + Sequence: inp.BlocksToMaturity(), }) } @@ -1391,7 +1439,11 @@ func (b *BreachArbiter) sweepSpendableOutputsTxn(txWeight int64, // Create a sighash cache to improve the performance of hashing and // signing SigHashAll inputs. - hashCache := txscript.NewTxSigHashes(txn) + prevOutputFetcher, err := input.MultiPrevOutFetcher(inputs) + if err != nil { + return nil, err + } + hashCache := txscript.NewTxSigHashes(txn, prevOutputFetcher) // Create a closure that encapsulates the process of initializing a // particular output's witness generation function, computing the @@ -1403,7 +1455,7 @@ func (b *BreachArbiter) sweepSpendableOutputsTxn(txWeight int64, // transaction using the SpendableOutput's witness generation // function. inputScript, err := so.CraftInputScript( - b.cfg.Signer, txn, hashCache, idx, + b.cfg.Signer, txn, hashCache, prevOutputFetcher, idx, ) if err != nil { return err @@ -1418,8 +1470,8 @@ func (b *BreachArbiter) sweepSpendableOutputsTxn(txWeight int64, // Finally, generate a witness for each output and attach it to the // transaction. - for i, input := range inputs { - if err := addWitness(i, input); err != nil { + for i, inp := range inputs { + if err := addWitness(i, inp); err != nil { return nil, err } } @@ -1521,18 +1573,7 @@ func (rs *RetributionStore) Remove(chanPoint *wire.OutPoint) error { // Remove the persisted retribution info and finalized justice // transaction. - if err := retBucket.Delete(chanBytes); err != nil { - return err - } - - // If we have not finalized this channel breach, we can exit - // early. - justiceBkt := tx.ReadWriteBucket(justiceTxnBucket) - if justiceBkt == nil { - return nil - } - - return justiceBkt.Delete(chanBytes) + return retBucket.Delete(chanBytes) }, func() {}) } @@ -1599,7 +1640,7 @@ func (ret *retributionInfo) Encode(w io.Writer) error { return nil } -// Dencode deserializes a retribution from the passed byte stream. +// Decode deserializes a retribution from the passed byte stream. func (ret *retributionInfo) Decode(r io.Reader) error { var scratch [32]byte diff --git a/contractcourt/breacharbiter_test.go b/contractcourt/breacharbiter_test.go index b402285be7..63161d6e5f 100644 --- a/contractcourt/breacharbiter_test.go +++ b/contractcourt/breacharbiter_test.go @@ -19,11 +19,11 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" @@ -432,7 +432,7 @@ func initBreachedOutputs() error { bo := &breachedOutputs[i] // Parse the sign descriptor's pubkey. - pubkey, err := btcec.ParsePubKey(breachKeys[i], btcec.S256()) + pubkey, err := btcec.ParsePubKey(breachKeys[i]) if err != nil { return fmt.Errorf("unable to parse pubkey: %v", breachKeys[i]) @@ -1051,7 +1051,7 @@ func TestBreachHandoffSuccess(t *testing.T) { processACK <- brarErr }, BreachRetribution: &lnwallet.BreachRetribution{ - BreachTransaction: bobClose.CloseTx, + BreachTxHash: bobClose.CloseTx.TxHash(), LocalOutputSignDesc: &input.SignDescriptor{ Output: &wire.TxOut{ PkScript: breachKeys[0], @@ -1085,7 +1085,7 @@ func TestBreachHandoffSuccess(t *testing.T) { processACK <- brarErr }, BreachRetribution: &lnwallet.BreachRetribution{ - BreachTransaction: bobClose.CloseTx, + BreachTxHash: bobClose.CloseTx.TxHash(), LocalOutputSignDesc: &input.SignDescriptor{ Output: &wire.TxOut{ PkScript: breachKeys[0], @@ -1137,7 +1137,7 @@ func TestBreachHandoffFail(t *testing.T) { processACK <- brarErr }, BreachRetribution: &lnwallet.BreachRetribution{ - BreachTransaction: bobClose.CloseTx, + BreachTxHash: bobClose.CloseTx.TxHash(), LocalOutputSignDesc: &input.SignDescriptor{ Output: &wire.TxOut{ PkScript: breachKeys[0], @@ -1179,7 +1179,7 @@ func TestBreachHandoffFail(t *testing.T) { processACK <- brarErr }, BreachRetribution: &lnwallet.BreachRetribution{ - BreachTransaction: bobClose.CloseTx, + BreachTxHash: bobClose.CloseTx.TxHash(), LocalOutputSignDesc: &input.SignDescriptor{ Output: &wire.TxOut{ PkScript: breachKeys[0], @@ -1216,7 +1216,7 @@ func TestBreachCreateJusticeTx(t *testing.T) { // to the justice tx, not that we create a valid spend, so we just set // some params making the script generation succeed. aliceKeyPriv, _ := btcec.PrivKeyFromBytes( - btcec.S256(), channels.AlicesPrivKey, + channels.AlicesPrivKey, ) alicePubKey := aliceKeyPriv.PubKey() @@ -1263,7 +1263,7 @@ func TestBreachCreateJusticeTx(t *testing.T) { // The spendCommitOuts tx should be spending the 4 typed of commit outs // (note that in practice there will be at most two commit outputs per - // commmit, but we test all 4 types here). + // commit, but we test all 4 types here). require.Len(t, justiceTxs.spendCommitOuts.TxIn, 4) // Finally check that the spendHTLCs tx are spending the two revoked @@ -1279,7 +1279,7 @@ type breachTest struct { // spend2ndLevel requests that second level htlcs be spent *again*, as // if by a remote party or watchtower. The outpoint of the second level - // htlc is in effect "readded" to the set of inputs. + // htlc is in effect "re-added" to the set of inputs. spend2ndLevel bool // sweepHtlc tests that the HTLC output is swept using the revocation @@ -1377,7 +1377,7 @@ func getSpendTransactions(signer input.Signer, chanPoint *wire.OutPoint, // sign and add the witness to the HTLC sweep. retInfo := newRetributionInfo(chanPoint, retribution) - hashCache := txscript.NewTxSigHashes(htlcSweep) + hashCache := input.NewTxSigHashesV0Only(htlcSweep) for i := range retInfo.breachedOutputs { inp := &retInfo.breachedOutputs[i] @@ -1386,8 +1386,11 @@ func getSpendTransactions(signer input.Signer, chanPoint *wire.OutPoint, case input.HtlcAcceptedRevoke: fallthrough case input.HtlcOfferedRevoke: + cannedFetcher := txscript.NewCannedPrevOutputFetcher( + nil, 0, + ) inputScript, err := inp.CraftInputScript( - signer, htlcSweep, hashCache, 0, + signer, htlcSweep, hashCache, cannedFetcher, 0, ) if err != nil { return nil, err @@ -1618,7 +1621,7 @@ func testBreachSpends(t *testing.T, test breachTest) { // Notify the breach arbiter about the breach. retribution, err := lnwallet.NewBreachRetribution( - alice.State(), height, 1, + alice.State(), height, 1, forceCloseTx, ) if err != nil { t.Fatalf("unable to create breach retribution: %v", err) @@ -1834,7 +1837,7 @@ func TestBreachDelayedJusticeConfirmation(t *testing.T) { // Notify the breach arbiter about the breach. retribution, err := lnwallet.NewBreachRetribution( - alice.State(), height, uint32(blockHeight), + alice.State(), height, uint32(blockHeight), forceCloseTx, ) if err != nil { t.Fatalf("unable to create breach retribution: %v", err) @@ -1949,7 +1952,6 @@ func TestBreachDelayedJusticeConfirmation(t *testing.T) { maxIndex = uint32(len(forceCloseTx.TxOut)) - 1 ) for i := 0; i < 2; i++ { - var tx *wire.MsgTx select { case tx = <-publTx: @@ -1989,7 +1991,6 @@ func TestBreachDelayedJusticeConfirmation(t *testing.T) { } select { - // The published tx should spend the same inputs as our second split. case tx := <-publTx: require.Len(t, tx.TxIn, len(splits[1].TxIn)) @@ -2166,8 +2167,7 @@ func createTestArbiter(t *testing.T, contractBreaches chan *ContractBreachEvent, return NewRetributionStore(db) }) - aliceKeyPriv, _ := btcec.PrivKeyFromBytes(btcec.S256(), - channels.AlicesPrivKey) + aliceKeyPriv, _ := btcec.PrivKeyFromBytes(channels.AlicesPrivKey) signer := &mock.SingleSigner{Privkey: aliceKeyPriv} // Assemble our test arbiter. @@ -2201,10 +2201,12 @@ func createTestArbiter(t *testing.T, contractBreaches chan *ContractBreachEvent, // initiator. func createInitChannels(revocationWindow int) (*lnwallet.LightningChannel, *lnwallet.LightningChannel, func(), error) { - aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(btcec.S256(), - channels.AlicesPrivKey) - bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(btcec.S256(), - channels.BobsPrivKey) + aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes( + channels.AlicesPrivKey, + ) + bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes( + channels.BobsPrivKey, + ) channelCapacity, err := btcutil.NewAmount(10) if err != nil { diff --git a/contractcourt/briefcase.go b/contractcourt/briefcase.go index e0fd6f980f..2c8f6e34e4 100644 --- a/contractcourt/briefcase.go +++ b/contractcourt/briefcase.go @@ -6,7 +6,7 @@ import ( "fmt" "io" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" @@ -36,16 +36,21 @@ type ContractResolutions struct { // output. If the channel type doesn't include anchors, the value of // this field will be nil. AnchorResolution *lnwallet.AnchorResolution + + // BreachResolution contains the data required to manage the lifecycle + // of a breach in the ChannelArbitrator. + BreachResolution *BreachResolution } // IsEmpty returns true if the set of resolutions is "empty". A resolution is -// empty if: our commitment output has been trimmed, and we don't have any -// incoming or outgoing HTLC's active. +// empty if: our commitment output has been trimmed, we don't have any +// incoming or outgoing HTLC's active, there is no anchor output to sweep, or +// there are no breached outputs to resolve. func (c *ContractResolutions) IsEmpty() bool { return c.CommitResolution == nil && len(c.HtlcResolutions.IncomingHTLCs) == 0 && len(c.HtlcResolutions.OutgoingHTLCs) == 0 && - c.AnchorResolution == nil + c.AnchorResolution == nil && c.BreachResolution == nil } // ArbitratorLog is the primary source of persistent storage for the @@ -142,7 +147,7 @@ const ( // | | | // | | |-> StateCommitmentBroadcasted: chain/user trigger // | | | - // | | |-> StateContractClosed: local/remote close trigger + // | | |-> StateContractClosed: local/remote/breach close trigger // | | | | // | | | |-> StateWaitingFullResolution: contract resolutions not empty // | | | | | @@ -152,9 +157,9 @@ const ( // | | | | // | | | |-> StateFullyResolved: contract resolutions empty // | | | - // | | |-> StateFullyResolved: coop/breach close trigger + // | | |-> StateFullyResolved: coop/breach(legacy) close trigger // | | - // | |-> StateContractClosed: local/remote close trigger + // | |-> StateContractClosed: local/remote/breach close trigger // | | | // | | |-> StateWaitingFullResolution: contract resolutions not empty // | | | | @@ -164,11 +169,11 @@ const ( // | | | // | | |-> StateFullyResolved: contract resolutions empty // | | - // | |-> StateFullyResolved: coop/breach close trigger + // | |-> StateFullyResolved: coop/breach(legacy) close trigger // | - // |-> StateContractClosed: local/remote close trigger + // |-> StateContractClosed: local/remote/breach close trigger // | | - // | |-> StateWaitingFullResolution: contract resolutions empty + // | |-> StateWaitingFullResolution: contract resolutions not empty // | | | // | | |-> StateWaitingFullResolution: contract resolutions not empty // | | | @@ -176,7 +181,7 @@ const ( // | | // | |-> StateFullyResolved: contract resolutions empty // | - // |-> StateFullyResolved: coop/breach close trigger + // |-> StateFullyResolved: coop/breach(legacy) close trigger. // StateDefault is the default state. In this state, no major actions // need to be executed. @@ -269,6 +274,10 @@ const ( // sweeping out direct commitment output form the remote party's // commitment transaction. resolverUnilateralSweep resolverType = 4 + + // resolverBreach is the type of resolver that manages a contract + // breach on-chain. + resolverBreach resolverType = 5 ) // resolverIDLen is the size of the resolver ID key. This is 36 bytes as we get @@ -341,6 +350,11 @@ var ( // store the anchor resolution, if any. anchorResolutionKey = []byte("anchor-resolution") + // breachResolutionKey is the key under the logScope that we'll use to + // store the breach resolution, if any. This is used rather than the + // resolutionsKey. + breachResolutionKey = []byte("breach-resolution") + // actionsBucketKey is the key under the logScope that we'll use to // store all chain actions once they're determined. actionsBucketKey = []byte("chain-actions") @@ -364,7 +378,7 @@ var ( // chain resolutions. errNoResolutions = fmt.Errorf("no contract resolutions exist") - // errNoActions is retuned when the log doesn't contain any stored + // errNoActions is returned when the log doesn't contain any stored // chain actions. errNoActions = fmt.Errorf("no chain actions exist") @@ -464,6 +478,8 @@ func (b *boltArbitratorLog) writeResolver(contractBucket kvdb.RwBucket, rType = resolverIncomingContest case *commitSweepResolver: rType = resolverUnilateralSweep + case *breachResolver: + rType = resolverBreach } if _, err := buf.Write([]byte{byte(rType)}); err != nil { return err @@ -593,6 +609,11 @@ func (b *boltArbitratorLog) FetchUnresolvedContracts() ([]ContractResolver, erro resReader, resolverCfg, ) + case resolverBreach: + res, err = newBreachResolverFromReader( + resReader, resolverCfg, + ) + default: return fmt.Errorf("unknown resolver type: %v", resType) } @@ -785,6 +806,20 @@ func (b *boltArbitratorLog) LogContractResolutions(c *ContractResolutions) error } } + // Write out the breach resolution if present. + if c.BreachResolution != nil { + var b bytes.Buffer + err := encodeBreachResolution(&b, c.BreachResolution) + if err != nil { + return err + } + + err = scopeBucket.Put(breachResolutionKey, b.Bytes()) + if err != nil { + return err + } + } + return nil }) } @@ -904,6 +939,18 @@ func (b *boltArbitratorLog) FetchContractResolutions() (*ContractResolutions, er } } + breachResBytes := scopeBucket.Get(breachResolutionKey) + if breachResBytes != nil { + c.BreachResolution = &BreachResolution{} + resReader := bytes.NewReader(breachResBytes) + err := decodeBreachResolution( + resReader, c.BreachResolution, + ) + if err != nil { + return err + } + } + return nil }, func() { c = &ContractResolutions{} @@ -1098,7 +1145,7 @@ func (b *boltArbitratorLog) checkpointContract(c ContractResolver, }, func() {}) } -// encodeSignDetails encodes the gived SignDetails struct to the writer. +// encodeSignDetails encodes the given SignDetails struct to the writer. // SignDetails is allowed to be nil, in which we will encode that it is not // present. func encodeSignDetails(w io.Writer, s *input.SignDetails) error { @@ -1162,7 +1209,7 @@ func decodeSignDetails(r io.Reader) (*input.SignDetails, error) { if err != nil { return nil, err } - sig, err := btcec.ParseDERSignature(rawSig, btcec.S256()) + sig, err := ecdsa.ParseDERSignature(rawSig) if err != nil { return nil, err } @@ -1372,6 +1419,21 @@ func decodeAnchorResolution(r io.Reader, return input.ReadSignDescriptor(r, &a.AnchorSignDescriptor) } +func encodeBreachResolution(w io.Writer, b *BreachResolution) error { + if _, err := w.Write(b.FundingOutPoint.Hash[:]); err != nil { + return err + } + return binary.Write(w, endian, b.FundingOutPoint.Index) +} + +func decodeBreachResolution(r io.Reader, b *BreachResolution) error { + _, err := io.ReadFull(r, b.FundingOutPoint.Hash[:]) + if err != nil { + return err + } + return binary.Read(r, endian, &b.FundingOutPoint.Index) +} + func encodeHtlcSetKey(w io.Writer, h *HtlcSetKey) error { err := binary.Write(w, endian, h.IsRemote) if err != nil { diff --git a/contractcourt/briefcase_test.go b/contractcourt/briefcase_test.go index 1bba095141..b6f1ec2957 100644 --- a/contractcourt/briefcase_test.go +++ b/contractcourt/briefcase_test.go @@ -3,14 +3,14 @@ package contractcourt import ( "crypto/rand" "io/ioutil" + prand "math/rand" "os" "reflect" "testing" "time" - prand "math/rand" - - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" @@ -136,7 +136,7 @@ var ( LockTime: 123, } - testSig, _ = btcec.ParseDERSignature(channels.TestSigBytes, btcec.S256()) + testSig, _ = ecdsa.ParseDERSignature(channels.TestSigBytes) testSignDetails = &input.SignDetails{ SignDesc: testSignDesc, @@ -180,6 +180,7 @@ func newTestBoltArbLog(chainhash chainhash.Hash, testArbCfg := ChannelArbitratorConfig{ PutResolverReport: func(_ kvdb.RwTx, _ *channeldb.ResolverReport) error { + return nil }, } @@ -665,13 +666,6 @@ func TestContractResolutionsStorage(t *testing.T) { } if !reflect.DeepEqual(&res, diskRes) { - for _, h := range res.HtlcResolutions.IncomingHTLCs { - h.SweepSignDesc.KeyDesc.PubKey.Curve = nil - } - for _, h := range diskRes.HtlcResolutions.IncomingHTLCs { - h.SweepSignDesc.KeyDesc.PubKey.Curve = nil - } - t.Fatalf("resolution mismatch: expected %v\n, got %v", spew.Sdump(&res), spew.Sdump(diskRes)) } @@ -857,7 +851,7 @@ func TestCommitSetStorage(t *testing.T) { } func init() { - testSignDesc.KeyDesc.PubKey, _ = btcec.ParsePubKey(key1, btcec.S256()) + testSignDesc.KeyDesc.PubKey, _ = btcec.ParsePubKey(key1) prand.Seed(time.Now().Unix()) } diff --git a/contractcourt/chain_arbitrator.go b/contractcourt/chain_arbitrator.go index 182c3205d3..fc5c88e331 100644 --- a/contractcourt/chain_arbitrator.go +++ b/contractcourt/chain_arbitrator.go @@ -7,9 +7,9 @@ import ( "sync/atomic" "time" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/btcsuite/btcwallet/walletdb" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" @@ -100,14 +100,12 @@ type ChainArbitratorConfig struct { MarkLinkInactive func(wire.OutPoint) error // ContractBreach is a function closure that the ChainArbitrator will - // use to notify the breachArbiter about a contract breach. A callback - // should be passed that when called will mark the channel pending - // close in the databae. It should only return a non-nil error when the - // breachArbiter has preserved the necessary breach info for this - // channel point, and the callback has succeeded, meaning it is safe to - // stop watching the channel. - ContractBreach func(wire.OutPoint, *lnwallet.BreachRetribution, - func() error) error + // use to notify the breachArbiter about a contract breach. It should + // only return a non-nil error when the breachArbiter has preserved + // the necessary breach info for this channel point. Once the breach + // resolution is persisted in the channel arbitrator, it will be safe + // to mark the channel closed. + ContractBreach func(wire.OutPoint, *lnwallet.BreachRetribution) error // IsOurAddress is a function that returns true if the passed address // is known to the underlying wallet. Otherwise, false should be @@ -183,6 +181,12 @@ type ChainArbitratorConfig struct { // Clock is the clock implementation that ChannelArbitrator uses. // It is useful for testing. Clock clock.Clock + + // SubscribeBreachComplete is used by the breachResolver to register a + // subscription that notifies when the breach resolution process is + // complete. + SubscribeBreachComplete func(op *wire.OutPoint, c chan struct{}) ( + bool, error) } // ChainArbitrator is a sub-system that oversees the on-chain resolution of all @@ -425,7 +429,6 @@ func (c *ChainArbitrator) getArbChannel( // This is only to be done once all contracts which were live on the channel // before hitting the chain have been resolved. func (c *ChainArbitrator) ResolveContract(chanPoint wire.OutPoint) error { - log.Infof("Marking ChannelPoint(%v) fully resolved", chanPoint) // First, we'll we'll mark the channel as fully closed from the PoV of @@ -484,7 +487,7 @@ func (c *ChainArbitrator) Start() error { return nil } - log.Tracef("Starting ChainArbitrator") + log.Info("ChainArbitrator starting") // First, we'll fetch all the channels that are still open, in order to // collect them within our set of active contracts. @@ -506,19 +509,17 @@ func (c *ChainArbitrator) Start() error { // First, we'll create an active chainWatcher for this channel // to ensure that we detect any relevant on chain events. + breachClosure := func(ret *lnwallet.BreachRetribution) error { + return c.cfg.ContractBreach(chanPoint, ret) + } + chainWatcher, err := newChainWatcher( chainWatcherConfig{ - chanState: channel, - notifier: c.cfg.Notifier, - signer: c.cfg.Signer, - isOurAddr: c.cfg.IsOurAddress, - contractBreach: func(retInfo *lnwallet.BreachRetribution, - markClosed func() error) error { - - return c.cfg.ContractBreach( - chanPoint, retInfo, markClosed, - ) - }, + chanState: channel, + notifier: c.cfg.Notifier, + signer: c.cfg.Signer, + isOurAddr: c.cfg.IsOurAddress, + contractBreach: breachClosure, extractStateNumHint: lnwallet.GetStateNumHint, }, ) @@ -846,8 +847,8 @@ func (c *ChainArbitrator) publishClosingTxs( // rebroadcast is a helper method which will republish the unilateral or // cooperative close transaction or a channel in a particular state. // -// NOTE: There is no risk to caling this method if the channel isn't in either -// CommimentBroadcasted or CoopBroadcasted, but the logs will be misleading. +// NOTE: There is no risk to calling this method if the channel isn't in either +// CommitmentBroadcasted or CoopBroadcasted, but the logs will be misleading. func (c *ChainArbitrator) rebroadcast(channel *channeldb.OpenChannel, state channeldb.ChannelStatus) error { @@ -872,7 +873,6 @@ func (c *ChainArbitrator) rebroadcast(channel *channeldb.OpenChannel, } switch { - // This can happen for channels that had their closing tx published // before we started storing it to disk. case err == channeldb.ErrNoCloseTx: @@ -964,16 +964,9 @@ type ContractUpdate struct { Htlcs []channeldb.HTLC } -// ContractSignals wraps the two signals that affect the state of a channel -// being watched by an arbitrator. The two signals we care about are: the -// channel has a new set of HTLC's, and the remote party has just broadcast -// their version of the commitment transaction. +// ContractSignals is used by outside subsystems to notify a channel arbitrator +// of its ShortChannelID. type ContractSignals struct { - // HtlcUpdates is a channel that the link will use to update the - // designated channel arbitrator when the set of HTLCs on any valid - // commitment changes. - HtlcUpdates chan *ContractUpdate - // ShortChanID is the up to date short channel ID for a contract. This // can change either if when the contract was added it didn't yet have // a stable identifier, or in the case of a reorg. @@ -1001,6 +994,26 @@ func (c *ChainArbitrator) UpdateContractSignals(chanPoint wire.OutPoint, return nil } +// NotifyContractUpdate lets a channel arbitrator know that a new +// ContractUpdate is available. This calls the ChannelArbitrator's internal +// method NotifyContractUpdate which waits for a response on a done chan before +// returning. This method will return an error if the ChannelArbitrator is not +// in the activeChannels map. However, this only happens if the arbitrator is +// resolved and the related link would already be shut down. +func (c *ChainArbitrator) NotifyContractUpdate(chanPoint wire.OutPoint, + update *ContractUpdate) error { + + c.Lock() + arbitrator, ok := c.activeChannels[chanPoint] + c.Unlock() + if !ok { + return fmt.Errorf("can't find arbitrator for %v", chanPoint) + } + + arbitrator.notifyContractUpdate(update) + return nil +} + // GetChannelArbitrator safely returns the channel arbitrator for a given // channel outpoint. func (c *ChainArbitrator) GetChannelArbitrator(chanPoint wire.OutPoint) ( @@ -1116,11 +1129,11 @@ func (c *ChainArbitrator) WatchNewChannel(newChan *channeldb.OpenChannel) error notifier: c.cfg.Notifier, signer: c.cfg.Signer, isOurAddr: c.cfg.IsOurAddress, - contractBreach: func(retInfo *lnwallet.BreachRetribution, - markClosed func() error) error { + contractBreach: func( + retInfo *lnwallet.BreachRetribution) error { return c.cfg.ContractBreach( - chanPoint, retInfo, markClosed, + chanPoint, retInfo, ) }, extractStateNumHint: lnwallet.GetStateNumHint, diff --git a/contractcourt/chain_watcher.go b/contractcourt/chain_watcher.go index 973a0ca73f..6ba4c2795b 100644 --- a/contractcourt/chain_watcher.go +++ b/contractcourt/chain_watcher.go @@ -7,11 +7,12 @@ import ( "sync/atomic" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" @@ -57,6 +58,29 @@ type RemoteUnilateralCloseInfo struct { CommitSet CommitSet } +// BreachResolution wraps the outpoint of the breached channel. +type BreachResolution struct { + FundingOutPoint wire.OutPoint +} + +// BreachCloseInfo wraps the BreachResolution with a CommitSet for the latest, +// non-breached state, with the AnchorResolution for the breached state. +type BreachCloseInfo struct { + *BreachResolution + *lnwallet.AnchorResolution + + // CommitHash is the hash of the commitment transaction. + CommitHash chainhash.Hash + + // CommitSet is the set of known valid commitments at the time the + // breach occurred on-chain. + CommitSet CommitSet + + // CloseSummary gives the recipient of the BreachCloseInfo information + // to mark the channel closed in the database. + CloseSummary channeldb.ChannelCloseSummary +} + // CommitSet is a collection of the set of known valid commitments at a given // instant. If ConfCommitKey is set, then the commitment identified by the // HtlcSetKey has hit the chain. This struct will be used to examine all live @@ -124,7 +148,7 @@ type ChainEventSubscription struct { // ContractBreach is a channel that will be sent upon if we detect a // contract breach. The struct sent across the channel contains all the // material required to bring the cheating channel peer to justice. - ContractBreach chan *lnwallet.BreachRetribution + ContractBreach chan *BreachCloseInfo // Cancel cancels the subscription to the event stream for a particular // channel. This method should be called once the caller no longer needs to @@ -150,13 +174,10 @@ type chainWatcherConfig struct { signer input.Signer // contractBreach is a method that will be called by the watcher if it - // detects that a contract breach transaction has been confirmed. A - // callback should be passed that when called will mark the channel - // pending close in the database. It will only return a non-nil error - // when the breachArbiter has preserved the necessary breach info for - // this channel point, and the callback has succeeded, meaning it is - // safe to stop watching the channel. - contractBreach func(*lnwallet.BreachRetribution, func() error) error + // detects that a contract breach transaction has been confirmed. It + // will only return a non-nil error when the breachArbiter has + // preserved the necessary breach info for this channel point. + contractBreach func(*lnwallet.BreachRetribution) error // isOurAddr is a function that returns true if the passed address is // known to us. @@ -311,7 +332,7 @@ func (c *chainWatcher) SubscribeChannelEvents() *ChainEventSubscription { RemoteUnilateralClosure: make(chan *RemoteUnilateralCloseInfo, 1), LocalUnilateralClosure: make(chan *LocalUnilateralCloseInfo, 1), CooperativeClosure: make(chan *CooperativeCloseInfo, 1), - ContractBreach: make(chan *lnwallet.BreachRetribution, 1), + ContractBreach: make(chan *BreachCloseInfo, 1), Cancel: func() { c.Lock() delete(c.clientSubscriptions, clientID) @@ -708,7 +729,6 @@ func (c *chainWatcher) handleKnownRemoteState( commitTxBroadcast := commitSpend.SpendingTx commitHash := commitTxBroadcast.TxHash() - spendHeight := uint32(commitSpend.SpendingHeight) switch { // If the spending transaction matches the current latest state, then @@ -759,14 +779,25 @@ func (c *chainWatcher) handleKnownRemoteState( return true, nil } + // This is neither a remote force close or a "future" commitment, we + // now check whether it's a remote breach and properly handle it. + return c.handlePossibleBreach(commitSpend, broadcastStateNum, chainSet) +} + +// handlePossibleBreach checks whether the remote has breached and dispatches a +// breach resolution to claim funds. +func (c *chainWatcher) handlePossibleBreach(commitSpend *chainntnfs.SpendDetail, + broadcastStateNum uint64, chainSet *chainSet) (bool, error) { + // We check if we have a revoked state at this state num that matches // the spend transaction. + spendHeight := uint32(commitSpend.SpendingHeight) retribution, err := lnwallet.NewBreachRetribution( c.cfg.chanState, broadcastStateNum, spendHeight, + commitSpend.SpendingTx, ) switch { - // If we had no log entry at this height, this was not a revoked state. case err == channeldb.ErrLogEntryNotFound: return false, nil @@ -781,16 +812,32 @@ func (c *chainWatcher) handleKnownRemoteState( // We found a revoked state at this height, but it could still be our // own broadcasted state we are looking at. Therefore check that the // commit matches before assuming it was a breach. - if retribution.BreachTransaction.TxHash() != commitHash { + commitHash := commitSpend.SpendingTx.TxHash() + if retribution.BreachTxHash != commitHash { return false, nil } + // Create an AnchorResolution for the breached state. + anchorRes, err := lnwallet.NewAnchorResolution( + c.cfg.chanState, commitSpend.SpendingTx, + ) + if err != nil { + return false, fmt.Errorf("unable to create anchor "+ + "resolution: %v", err) + } + + // We'll set the ConfCommitKey here as the remote htlc set. This is + // only used to ensure a nil-pointer-dereference doesn't occur and is + // not used otherwise. The HTLC's may not exist for the + // RemotePendingHtlcSet. + chainSet.commitSet.ConfCommitKey = &RemoteHtlcSet + // THEY'RE ATTEMPTING TO VIOLATE THE CONTRACT LAID OUT WITHIN THE // PAYMENT CHANNEL. Therefore we close the signal indicating a revoked // broadcast to allow subscribers to swiftly dispatch justice!!! err = c.dispatchContractBreach( - commitSpend, &chainSet.remoteCommit, - broadcastStateNum, retribution, + commitSpend, chainSet, broadcastStateNum, retribution, + anchorRes, ) if err != nil { return false, fmt.Errorf("unable to handle channel "+ @@ -833,7 +880,6 @@ func (c *chainWatcher) handleUnknownRemoteState( "sweep our funds...", commitPoint.SerializeCompressed(), c.cfg.chanState.FundingOutpoint) - } else { log.Infof("ChannelPoint(%v) is tweakless, "+ "moving to sweep directly on chain", @@ -1083,8 +1129,9 @@ func (c *chainWatcher) dispatchRemoteForceClose( // materials required to bring the cheater to justice, then notify all // registered subscribers of this event. func (c *chainWatcher) dispatchContractBreach(spendEvent *chainntnfs.SpendDetail, - remoteCommit *channeldb.ChannelCommitment, broadcastStateNum uint64, - retribution *lnwallet.BreachRetribution) error { + chainSet *chainSet, broadcastStateNum uint64, + retribution *lnwallet.BreachRetribution, + anchorRes *lnwallet.AnchorResolution) error { log.Warnf("Remote peer has breached the channel contract for "+ "ChannelPoint(%v). Revoked state #%v was broadcast!!!", @@ -1096,27 +1143,8 @@ func (c *chainWatcher) dispatchContractBreach(spendEvent *chainntnfs.SpendDetail spendHeight := uint32(spendEvent.SpendingHeight) - // Nil the curve before printing. - if retribution.RemoteOutputSignDesc != nil && - retribution.RemoteOutputSignDesc.DoubleTweak != nil { - retribution.RemoteOutputSignDesc.DoubleTweak.Curve = nil - } - if retribution.RemoteOutputSignDesc != nil && - retribution.RemoteOutputSignDesc.KeyDesc.PubKey != nil { - retribution.RemoteOutputSignDesc.KeyDesc.PubKey.Curve = nil - } - if retribution.LocalOutputSignDesc != nil && - retribution.LocalOutputSignDesc.DoubleTweak != nil { - retribution.LocalOutputSignDesc.DoubleTweak.Curve = nil - } - if retribution.LocalOutputSignDesc != nil && - retribution.LocalOutputSignDesc.KeyDesc.PubKey != nil { - retribution.LocalOutputSignDesc.KeyDesc.PubKey.Curve = nil - } - log.Debugf("Punishment breach retribution created: %v", newLogClosure(func() string { - retribution.KeyRing.CommitPoint.Curve = nil retribution.KeyRing.LocalHtlcKey = nil retribution.KeyRing.RemoteHtlcKey = nil retribution.KeyRing.ToLocalKey = nil @@ -1125,7 +1153,7 @@ func (c *chainWatcher) dispatchContractBreach(spendEvent *chainntnfs.SpendDetail return spew.Sdump(retribution) })) - settledBalance := remoteCommit.LocalBalance.ToSatoshis() + settledBalance := chainSet.remoteCommit.LocalBalance.ToSatoshis() closeSummary := channeldb.ChannelCloseSummary{ ChanPoint: c.cfg.chanState.FundingOutpoint, ChainHash: c.cfg.chanState.ChainHash, @@ -1151,38 +1179,35 @@ func (c *chainWatcher) dispatchContractBreach(spendEvent *chainntnfs.SpendDetail closeSummary.LastChanSyncMsg = chanSync } - // We create a function closure that will mark the channel as pending - // close in the database. We pass it to the contracBreach method such - // that it can ensure safe handoff of the breach before we close the - // channel. - markClosed := func() error { - // At this point, we've successfully received an ack for the - // breach close, and we can mark the channel as pending force - // closed. - if err := c.cfg.chanState.CloseChannel( - &closeSummary, channeldb.ChanStatusRemoteCloseInitiator, - ); err != nil { - return err - } - - log.Infof("Breached channel=%v marked pending-closed", - c.cfg.chanState.FundingOutpoint) - return nil - } - - // Hand the retribution info over to the breach arbiter. - if err := c.cfg.contractBreach(retribution, markClosed); err != nil { + // Hand the retribution info over to the breach arbiter. This function + // will wait for a response from the breach arbiter and then proceed to + // send a BreachCloseInfo to the channel arbitrator. The channel arb + // will then mark the channel as closed after resolutions and the + // commit set are logged in the arbitrator log. + if err := c.cfg.contractBreach(retribution); err != nil { log.Errorf("unable to hand breached contract off to "+ "breachArbiter: %v", err) return err } + breachRes := &BreachResolution{ + FundingOutPoint: c.cfg.chanState.FundingOutpoint, + } + + breachInfo := &BreachCloseInfo{ + CommitHash: spendEvent.SpendingTx.TxHash(), + BreachResolution: breachRes, + AnchorResolution: anchorRes, + CommitSet: chainSet.commitSet, + CloseSummary: closeSummary, + } + // With the event processed and channel closed, we'll now notify all // subscribers of the event. c.Lock() for _, sub := range c.clientSubscriptions { select { - case sub.ContractBreach <- retribution: + case sub.ContractBreach <- breachInfo: case <-c.quit: c.Unlock() return fmt.Errorf("quitting") diff --git a/contractcourt/channel_arbitrator.go b/contractcourt/channel_arbitrator.go index e85e74b34e..5abdf80355 100644 --- a/contractcourt/channel_arbitrator.go +++ b/contractcourt/channel_arbitrator.go @@ -9,11 +9,12 @@ import ( "sync/atomic" "time" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/htlcswitch/hop" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/labels" @@ -69,7 +70,9 @@ type WitnessSubscription struct { type WitnessBeacon interface { // SubscribeUpdates returns a channel that will be sent upon *each* time // a new preimage is discovered. - SubscribeUpdates() *WitnessSubscription + SubscribeUpdates(chanID lnwire.ShortChannelID, htlc *channeldb.HTLC, + payload *hop.Payload, + nextHopOnionBlob []byte) (*WitnessSubscription, error) // LookupPreImage attempts to lookup a preimage in the global cache. // True is returned for the second argument if the preimage is found. @@ -326,6 +329,13 @@ type ChannelArbitrator struct { // currently valid commitment transactions. activeHTLCs map[HtlcSetKey]htlcSet + // unmergedSet is used to update the activeHTLCs map in two callsites: + // checkLocalChainActions and sweepAnchors. It contains the latest + // updates from the link. It is not deleted from, its entries may be + // replaced on subsequent calls to notifyContractUpdate. + unmergedSet map[HtlcSetKey]htlcSet + unmergedMtx sync.RWMutex + // cfg contains all the functionality that the ChannelArbitrator requires // to do its duty. cfg ChannelArbitratorConfig @@ -339,11 +349,6 @@ type ChannelArbitrator struct { // we're watching over will be sent. signalUpdates chan *signalUpdateMsg - // htlcUpdates is a channel that is sent upon with new updates from the - // active channel. Each time a new commitment state is accepted, the - // set of HTLC's on the new state should be sent across this channel. - htlcUpdates <-chan *ContractUpdate - // activeResolvers is a slice of any active resolvers. This is used to // be able to signal them for shutdown in the case that we shutdown. activeResolvers []ContractResolver @@ -374,14 +379,27 @@ type ChannelArbitrator struct { func NewChannelArbitrator(cfg ChannelArbitratorConfig, htlcSets map[HtlcSetKey]htlcSet, log ArbitratorLog) *ChannelArbitrator { + // Create a new map for unmerged HTLC's as we will overwrite the values + // and want to avoid modifying activeHTLCs directly. This soft copying + // is done to ensure that activeHTLCs isn't reset as an empty map later + // on. + unmerged := make(map[HtlcSetKey]htlcSet) + unmerged[LocalHtlcSet] = htlcSets[LocalHtlcSet] + unmerged[RemoteHtlcSet] = htlcSets[RemoteHtlcSet] + + // If the pending set exists, write that as well. + if _, ok := htlcSets[RemotePendingHtlcSet]; ok { + unmerged[RemotePendingHtlcSet] = htlcSets[RemotePendingHtlcSet] + } + return &ChannelArbitrator{ log: log, blocks: make(chan int32, arbitratorBlockBufferSize), signalUpdates: make(chan *signalUpdateMsg), - htlcUpdates: make(<-chan *ContractUpdate), resolutionSignal: make(chan struct{}), forceCloseReqs: make(chan *forceCloseReq), activeHTLCs: htlcSets, + unmergedSet: unmerged, cfg: cfg, quit: make(chan struct{}), } @@ -612,8 +630,19 @@ func (c *ChannelArbitrator) relaunchResolvers(commitSet *CommitSet, // We'll also fetch the historical state of this channel, as it should // have been marked as closed by now, and supplement it to each resolver // such that we can properly resolve our pending contracts. - chanState, err := c.cfg.FetchHistoricalChannel() - if err != nil { + var chanState *channeldb.OpenChannel + chanState, err = c.cfg.FetchHistoricalChannel() + switch { + // If we don't find this channel, then it may be the case that it + // was closed before we started to retain the final state + // information for open channels. + case err == channeldb.ErrNoHistoricalBucket: + fallthrough + case err == channeldb.ErrChannelNotFound: + log.Warnf("ChannelArbitrator(%v): unable to fetch historical "+ + "state", c.cfg.ChanPoint) + + case err != nil: return err } @@ -621,7 +650,9 @@ func (c *ChannelArbitrator) relaunchResolvers(commitSet *CommitSet, "resolvers", c.cfg.ChanPoint, len(unresolvedContracts)) for _, resolver := range unresolvedContracts { - resolver.SupplementState(chanState) + if chanState != nil { + resolver.SupplementState(chanState) + } htlcResolver, ok := resolver.(htlcContractResolver) if !ok { @@ -734,8 +765,8 @@ const ( coopCloseTrigger // breachCloseTrigger is a transition trigger driven by a remote breach - // being confirmed. In this case the channel arbitrator won't have to - // do anything, so we'll just clean up and exit gracefully. + // being confirmed. In this case the channel arbitrator will wait for + // the breacharbiter to finish and then clean up gracefully. breachCloseTrigger ) @@ -797,6 +828,10 @@ func (c *ChannelArbitrator) stateStep( if confCommitSet != nil { htlcs = confCommitSet.toActiveHTLCSets() } else { + // Update the set of activeHTLCs so + // checkLocalChainActions has an up-to-date view of the + // commitments. + c.updateActiveHTLCs() htlcs = c.activeHTLCs } chainActions, err := c.checkLocalChainActions( @@ -839,9 +874,8 @@ func (c *ChannelArbitrator) stateStep( // If the trigger is a cooperative close being confirmed, then // we can go straight to StateFullyResolved, as there won't be - // any contracts to resolve. The same is true in the case of a - // breach. - case coopCloseTrigger, breachCloseTrigger: + // any contracts to resolve. + case coopCloseTrigger: nextState = StateFullyResolved // Otherwise, if this state advance was triggered by a @@ -855,6 +889,14 @@ func (c *ChannelArbitrator) stateStep( fallthrough case remoteCloseTrigger: nextState = StateContractClosed + + case breachCloseTrigger: + nextContractState, err := c.checkLegacyBreach() + if nextContractState == StateError { + return nextContractState, nil, err + } + + nextState = nextContractState } // If we're in this state, then we've decided to broadcast the @@ -877,7 +919,23 @@ func (c *ChannelArbitrator) stateStep( c.cfg.ChanPoint, trigger, StateContractClosed) return StateContractClosed, closeTx, nil - case coopCloseTrigger, breachCloseTrigger: + case breachCloseTrigger: + nextContractState, err := c.checkLegacyBreach() + if nextContractState == StateError { + log.Infof("ChannelArbitrator(%v): unable to "+ + "advance breach close resolution: %v", + c.cfg.ChanPoint, nextContractState) + return StateError, closeTx, err + } + + log.Infof("ChannelArbitrator(%v): detected %s close "+ + "after closing channel, fast-forwarding to %s"+ + " to resolve contract", c.cfg.ChanPoint, + trigger, nextContractState) + + return nextContractState, closeTx, nil + + case coopCloseTrigger: log.Infof("ChannelArbitrator(%v): detected %s "+ "close after closing channel, fast-forwarding "+ "to %s to resolve contract", @@ -981,10 +1039,18 @@ func (c *ChannelArbitrator) stateStep( case localCloseTrigger, remoteCloseTrigger: nextState = StateContractClosed - // If a coop close or breach was confirmed, jump straight to - // the fully resolved state. - case coopCloseTrigger, breachCloseTrigger: + // If a coop close was confirmed, jump straight to the fully + // resolved state. + case coopCloseTrigger: nextState = StateFullyResolved + + case breachCloseTrigger: + nextContractState, err := c.checkLegacyBreach() + if nextContractState == StateError { + return nextContractState, closeTx, err + } + + nextState = nextContractState } log.Infof("ChannelArbitrator(%v): trigger %v moving from "+ @@ -1039,8 +1105,6 @@ func (c *ChannelArbitrator) stateStep( if len(pktsToSend) != 0 { err := c.cfg.DeliverResolutionMsg(pktsToSend...) if err != nil { - // TODO(roasbeef): make sure packet sends are - // idempotent log.Errorf("unable to send pkts: %v", err) return StateError, closeTx, err } @@ -1164,6 +1228,10 @@ func (c *ChannelArbitrator) sweepAnchors(anchors *lnwallet.AnchorResolutions, return nil } + // Update the set of activeHTLCs so that the sweeping routine has an + // up-to-date view of the set of commitments. + c.updateActiveHTLCs() + // Sweep anchors based on different HTLC sets. Notice the HTLC sets may // differ across commitments, thus their deadline values could vary. for htlcSet, htlcs := range c.activeHTLCs { @@ -1512,7 +1580,7 @@ func (c *ChannelArbitrator) checkCommitChainActions(height uint32, ) if toChain { - log.Debugf("ChannelArbitrator(%v): go to chain for "+ + log.Infof("ChannelArbitrator(%v): go to chain for "+ "outgoing htlc %x: timeout=%v, "+ "blocks_until_expiry=%v, broadcast_delta=%v", c.cfg.ChanPoint, htlc.RHash[:], @@ -1527,7 +1595,7 @@ func (c *ChannelArbitrator) checkCommitChainActions(height uint32, for _, htlc := range htlcs.incomingHTLCs { // We'll need to go on-chain to pull an incoming HTLC iff we // know the pre-image and it's close to timing out. We need to - // ensure that we claim the funds that our rightfully ours + // ensure that we claim the funds that are rightfully ours // on-chain. preimageAvailable, err := c.isPreimageAvailable(htlc.RHash) if err != nil { @@ -1543,7 +1611,7 @@ func (c *ChannelArbitrator) checkCommitChainActions(height uint32, ) if toChain { - log.Debugf("ChannelArbitrator(%v): go to chain for "+ + log.Infof("ChannelArbitrator(%v): go to chain for "+ "incoming htlc %x: timeout=%v, "+ "blocks_until_expiry=%v, broadcast_delta=%v", c.cfg.ChanPoint, htlc.RHash[:], @@ -1768,7 +1836,7 @@ func (c *ChannelArbitrator) checkRemoteDanglingActions( continue } - log.Tracef("ChannelArbitrator(%v): immediately failing "+ + log.Infof("ChannelArbitrator(%v): immediately failing "+ "htlc=%x from remote commitment", c.cfg.ChanPoint, htlc.RHash[:]) @@ -1804,7 +1872,7 @@ func (c *ChannelArbitrator) checkRemoteChainActions( return nil, err } - // With this actions computed, we'll now check the diff of the HTLCs on + // With these actions computed, we'll now check the diff of the HTLCs on // the commitments, and cancel back any that are on the pending but not // the non-pending. remoteDiffActions := c.checkRemoteDiffActions( @@ -1852,7 +1920,7 @@ func (c *ChannelArbitrator) checkRemoteDiffActions(height uint32, actionMap[HtlcFailNowAction], htlc, ) - log.Tracef("ChannelArbitrator(%v): immediately failing "+ + log.Infof("ChannelArbitrator(%v): immediately failing "+ "htlc=%x from remote commitment", c.cfg.ChanPoint, htlc.RHash[:]) } @@ -1875,7 +1943,7 @@ func (c *ChannelArbitrator) constructChainActions(confCommitSet *CommitSet, return c.log.FetchChainActions() } - // Otherwise we have the full commitment set written to disk, and can + // Otherwise, we have the full commitment set written to disk, and can // proceed as normal. htlcSets := confCommitSet.toActiveHTLCSets() switch *confCommitSet.ConfCommitKey { @@ -1906,7 +1974,7 @@ func (c *ChannelArbitrator) constructChainActions(confCommitSet *CommitSet, return nil, fmt.Errorf("unable to locate chain actions") } -// prepContractResolutions is called either int he case that we decide we need +// prepContractResolutions is called either in the case that we decide we need // to go to chain, or the remote party goes to chain. Given a set of actions we // need to take for each HTLC, this method will return a set of contract // resolvers that will resolve the contracts on-chain if needed, and also a set @@ -1930,8 +1998,19 @@ func (c *ChannelArbitrator) prepContractResolutions( // We'll also fetch the historical state of this channel, as it should // have been marked as closed by now, and supplement it to each resolver // such that we can properly resolve our pending contracts. - chanState, err := c.cfg.FetchHistoricalChannel() - if err != nil { + var chanState *channeldb.OpenChannel + chanState, err = c.cfg.FetchHistoricalChannel() + switch { + // If we don't find this channel, then it may be the case that it + // was closed before we started to retain the final state + // information for open channels. + case err == channeldb.ErrNoHistoricalBucket: + fallthrough + case err == channeldb.ErrChannelNotFound: + log.Warnf("ChannelArbitrator(%v): unable to fetch historical "+ + "state", c.cfg.ChanPoint) + + case err != nil: return nil, nil, err } @@ -1972,10 +2051,62 @@ func (c *ChannelArbitrator) prepContractResolutions( commitHash := contractResolutions.CommitHash failureMsg := &lnwire.FailPermanentChannelFailure{} + var htlcResolvers []ContractResolver + + // We instantiate an anchor resolver if the commitment tx has an + // anchor. + if contractResolutions.AnchorResolution != nil { + anchorResolver := newAnchorResolver( + contractResolutions.AnchorResolution.AnchorSignDescriptor, + contractResolutions.AnchorResolution.CommitAnchor, + height, c.cfg.ChanPoint, resolverCfg, + ) + htlcResolvers = append(htlcResolvers, anchorResolver) + } + + // If this is a breach close, we'll create a breach resolver, determine + // the htlc's to fail back, and exit. This is done because the other + // steps taken for non-breach-closes do not matter for breach-closes. + if contractResolutions.BreachResolution != nil { + breachResolver := newBreachResolver(resolverCfg) + htlcResolvers = append(htlcResolvers, breachResolver) + + // We'll use the CommitSet, we'll fail back all outgoing HTLC's + // that exist on either of the remote commitments. The map is + // used to deduplicate any shared htlc's. + remoteOutgoing := make(map[uint64]channeldb.HTLC) + for htlcSetKey, htlcs := range confCommitSet.HtlcSets { + if !htlcSetKey.IsRemote { + continue + } + + for _, htlc := range htlcs { + if htlc.Incoming { + continue + } + + remoteOutgoing[htlc.HtlcIndex] = htlc + } + } + + // Now we'll loop over the map and create ResolutionMsgs for + // each of them. + for _, htlc := range remoteOutgoing { + failMsg := ResolutionMsg{ + SourceChan: c.cfg.ShortChanID, + HtlcIndex: htlc.HtlcIndex, + Failure: failureMsg, + } + + msgsToSend = append(msgsToSend, failMsg) + } + + return htlcResolvers, msgsToSend, nil + } + // For each HTLC, we'll either act immediately, meaning we'll instantly // fail the HTLC, or we'll act only once the transaction has been // confirmed, in which case we'll need an HTLC resolver. - var htlcResolvers []ContractResolver for htlcAction, htlcs := range htlcActions { switch htlcAction { @@ -2040,7 +2171,9 @@ func (c *ChannelArbitrator) prepContractResolutions( resolver := newTimeoutResolver( resolution, height, htlc, resolverCfg, ) - resolver.SupplementState(chanState) + if chanState != nil { + resolver.SupplementState(chanState) + } htlcResolvers = append(htlcResolvers, resolver) } @@ -2097,7 +2230,9 @@ func (c *ChannelArbitrator) prepContractResolutions( resolver := newOutgoingContestResolver( resolution, height, htlc, resolverCfg, ) - resolver.SupplementState(chanState) + if chanState != nil { + resolver.SupplementState(chanState) + } htlcResolvers = append(htlcResolvers, resolver) } } @@ -2111,21 +2246,12 @@ func (c *ChannelArbitrator) prepContractResolutions( *contractResolutions.CommitResolution, height, c.cfg.ChanPoint, resolverCfg, ) - resolver.SupplementState(chanState) + if chanState != nil { + resolver.SupplementState(chanState) + } htlcResolvers = append(htlcResolvers, resolver) } - // We instantiate an anchor resolver if the commitmentment tx has an - // anchor. - if contractResolutions.AnchorResolution != nil { - anchorResolver := newAnchorResolver( - contractResolutions.AnchorResolution.AnchorSignDescriptor, - contractResolutions.AnchorResolution.CommitAnchor, - height, c.cfg.ChanPoint, resolverCfg, - ) - htlcResolvers = append(htlcResolvers, anchorResolver) - } - return htlcResolvers, msgsToSend, nil } @@ -2285,6 +2411,42 @@ func (c *ChannelArbitrator) UpdateContractSignals(newSignals *ContractSignals) { } } +// notifyContractUpdate updates the ChannelArbitrator's unmerged mappings such +// that it can later be merged with activeHTLCs when calling +// checkLocalChainActions or sweepAnchors. These are the only two places that +// activeHTLCs is used. +func (c *ChannelArbitrator) notifyContractUpdate(upd *ContractUpdate) { + c.unmergedMtx.Lock() + defer c.unmergedMtx.Unlock() + + // Update the mapping. + c.unmergedSet[upd.HtlcKey] = newHtlcSet(upd.Htlcs) + + log.Tracef("ChannelArbitrator(%v): fresh set of htlcs=%v", + c.cfg.ChanPoint, + newLogClosure(func() string { + return spew.Sdump(upd) + }), + ) +} + +// updateActiveHTLCs merges the unmerged set of HTLCs from the link with +// activeHTLCs. +func (c *ChannelArbitrator) updateActiveHTLCs() { + c.unmergedMtx.RLock() + defer c.unmergedMtx.RUnlock() + + // Update the mapping. + c.activeHTLCs[LocalHtlcSet] = c.unmergedSet[LocalHtlcSet] + c.activeHTLCs[RemoteHtlcSet] = c.unmergedSet[RemoteHtlcSet] + + // If the pending set exists, update that as well. + if _, ok := c.unmergedSet[RemotePendingHtlcSet]; ok { + pendingSet := c.unmergedSet[RemotePendingHtlcSet] + c.activeHTLCs[RemotePendingHtlcSet] = pendingSet + } +} + // channelAttendant is the primary goroutine that acts at the judicial // arbitrator between our channel state, the remote channel peer, and the // blockchain (Our judge). This goroutine will ensure that we faithfully execute @@ -2346,34 +2508,14 @@ func (c *ChannelArbitrator) channelAttendant(bestHeight int32) { log.Tracef("ChannelArbitrator(%v) got new signal "+ "update!", c.cfg.ChanPoint) - // First, we'll update our set of signals. - c.htlcUpdates = signalUpdate.newSignals.HtlcUpdates + // We'll update the ShortChannelID. c.cfg.ShortChanID = signalUpdate.newSignals.ShortChanID - // Now that the signals have been updated, we'll now + // Now that the signal has been updated, we'll now // close the done channel to signal to the caller we've - // registered the new contracts. + // registered the new ShortChannelID. close(signalUpdate.doneChan) - // A new set of HTLC's has been added or removed from the - // commitment transaction. So we'll update our activeHTLCs map - // accordingly. - case htlcUpdate := <-c.htlcUpdates: - // We'll wipe out our old set of HTLC's for each - // htlcSetKey type included in this update in order to - // only monitor the HTLCs that are still active on this - // target commitment. - c.activeHTLCs[htlcUpdate.HtlcKey] = newHtlcSet( - htlcUpdate.Htlcs, - ) - - log.Tracef("ChannelArbitrator(%v): fresh set of htlcs=%v", - c.cfg.ChanPoint, - newLogClosure(func() string { - return spew.Sdump(htlcUpdate) - }), - ) - // We've cooperatively closed the channel, so we're no longer // needed. We'll mark the channel as resolved and exit. case closeInfo := <-c.cfg.ChainEvents.CooperativeClosure: @@ -2544,14 +2686,59 @@ func (c *ChannelArbitrator) channelAttendant(bestHeight int32) { // the ChainWatcher and BreachArbiter, we don't have to do // anything in particular, so just advance our state and // gracefully exit. - case <-c.cfg.ChainEvents.ContractBreach: + case breachInfo := <-c.cfg.ChainEvents.ContractBreach: log.Infof("ChannelArbitrator(%v): remote party has "+ "breached channel!", c.cfg.ChanPoint) + // In the breach case, we'll only have anchor and + // breach resolutions. + contractRes := &ContractResolutions{ + CommitHash: breachInfo.CommitHash, + BreachResolution: breachInfo.BreachResolution, + AnchorResolution: breachInfo.AnchorResolution, + } + + // We'll transition to the ContractClosed state and log + // the set of resolutions such that they can be turned + // into resolvers later on. We'll also insert the + // CommitSet of the latest set of commitments. + err := c.log.LogContractResolutions(contractRes) + if err != nil { + log.Errorf("Unable to write resolutions: %v", + err) + return + } + err = c.log.InsertConfirmedCommitSet( + &breachInfo.CommitSet, + ) + if err != nil { + log.Errorf("Unable to write commit set: %v", + err) + return + } + + // The channel is finally marked pending closed here as + // the breacharbiter and channel arbitrator have + // persisted the relevant states. + closeSummary := &breachInfo.CloseSummary + err = c.cfg.MarkChannelClosed( + closeSummary, + channeldb.ChanStatusRemoteCloseInitiator, + ) + if err != nil { + log.Errorf("Unable to mark channel closed: %v", + err) + return + } + + log.Infof("Breached channel=%v marked pending-closed", + breachInfo.BreachResolution.FundingOutPoint) + // We'll advance our state machine until it reaches a // terminal state. - _, _, err := c.advanceState( - uint32(bestHeight), breachCloseTrigger, nil, + _, _, err = c.advanceState( + uint32(bestHeight), breachCloseTrigger, + &breachInfo.CommitSet, ) if err != nil { log.Errorf("Unable to advance state: %v", err) @@ -2631,3 +2818,25 @@ func (c *ChannelArbitrator) channelAttendant(bestHeight int32) { } } } + +// checkLegacyBreach returns StateFullyResolved if the channel was closed with +// a breach transaction before the channel arbitrator launched its own breach +// resolver. StateContractClosed is returned if this is a modern breach close +// with a breach resolver. StateError is returned if the log lookup failed. +func (c *ChannelArbitrator) checkLegacyBreach() (ArbitratorState, error) { + // A previous version of the channel arbitrator would make the breach + // close skip to StateFullyResolved. If there are no contract + // resolutions in the bolt arbitrator log, then this is an older breach + // close. Otherwise, if there are resolutions, the state should advance + // to StateContractClosed. + _, err := c.log.FetchContractResolutions() + if err == errNoResolutions { + // This is an older breach close still in the database. + return StateFullyResolved, nil + } else if err != nil { + return StateError, err + } + + // This is a modern breach close with resolvers. + return StateContractClosed, nil +} diff --git a/contractcourt/channel_arbitrator_test.go b/contractcourt/channel_arbitrator_test.go index 46742a384a..0c7dc70783 100644 --- a/contractcourt/channel_arbitrator_test.go +++ b/contractcourt/channel_arbitrator_test.go @@ -12,9 +12,9 @@ import ( "testing" "time" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/clock" @@ -205,6 +205,9 @@ type chanArbTestCtx struct { log ArbitratorLog sweeper *mockSweeper + + breachSubscribed chan struct{} + breachResolutionChan chan struct{} } func (c *chanArbTestCtx) CleanUp() { @@ -303,13 +306,17 @@ func withMarkClosed(markClosed func(*channeldb.ChannelCloseSummary, func createTestChannelArbitrator(t *testing.T, log ArbitratorLog, opts ...testChanArbOption) (*chanArbTestCtx, error) { + chanArbCtx := &chanArbTestCtx{ + breachSubscribed: make(chan struct{}), + } + chanPoint := wire.OutPoint{} shortChanID := lnwire.ShortChannelID{} chanEvents := &ChainEventSubscription{ RemoteUnilateralClosure: make(chan *RemoteUnilateralCloseInfo, 1), LocalUnilateralClosure: make(chan *LocalUnilateralCloseInfo, 1), CooperativeClosure: make(chan *CooperativeCloseInfo, 1), - ContractBreach: make(chan *lnwallet.BreachRetribution, 1), + ContractBreach: make(chan *BreachCloseInfo, 1), } resolutionChan := make(chan []ResolutionMsg, 1) @@ -346,6 +353,13 @@ func createTestChannelArbitrator(t *testing.T, log ArbitratorLog, return true }, + SubscribeBreachComplete: func(op *wire.OutPoint, + c chan struct{}) (bool, error) { + + chanArbCtx.breachResolutionChan = c + chanArbCtx.breachSubscribed <- struct{}{} + return false, nil + }, Clock: clock.NewDefaultClock(), Sweeper: mockSweeper, } @@ -369,6 +383,7 @@ func createTestChannelArbitrator(t *testing.T, log ArbitratorLog, }, MarkChannelClosed: func(*channeldb.ChannelCloseSummary, ...channeldb.ChannelStatus) error { + return nil }, IsPendingClose: false, @@ -425,16 +440,16 @@ func createTestChannelArbitrator(t *testing.T, log ArbitratorLog, chanArb := NewChannelArbitrator(*arbCfg, htlcSets, log) - return &chanArbTestCtx{ - t: t, - chanArb: chanArb, - cleanUp: cleanUp, - resolvedChan: resolvedChan, - resolutions: resolutionChan, - log: log, - incubationRequests: incubateChan, - sweeper: mockSweeper, - }, nil + chanArbCtx.t = t + chanArbCtx.chanArb = chanArb + chanArbCtx.cleanUp = cleanUp + chanArbCtx.resolvedChan = resolvedChan + chanArbCtx.resolutions = resolutionChan + chanArbCtx.log = log + chanArbCtx.incubationRequests = incubateChan + chanArbCtx.sweeper = mockSweeper + + return chanArbCtx, nil } // TestChannelArbitratorCooperativeClose tests that the ChannelArbitertor @@ -661,11 +676,13 @@ func TestChannelArbitratorLocalForceClose(t *testing.T) { // TestChannelArbitratorBreachClose tests that the ChannelArbitrator goes // through the expected states in case we notice a breach in the chain, and -// gracefully exits. +// is able to properly progress the breachResolver and anchorResolver to a +// successful resolution. func TestChannelArbitratorBreachClose(t *testing.T) { log := &mockArbitratorLog{ state: StateDefault, newStates: make(chan ArbitratorState, 5), + resolvers: make(map[ContractResolver]struct{}), } chanArbCtx, err := createTestChannelArbitrator(t, log) @@ -673,6 +690,8 @@ func TestChannelArbitratorBreachClose(t *testing.T) { t.Fatalf("unable to create ChannelArbitrator: %v", err) } chanArb := chanArbCtx.chanArb + chanArb.cfg.PreimageDB = newMockWitnessBeacon() + chanArb.cfg.Registry = &mockRegistry{} if err := chanArb.Start(nil); err != nil { t.Fatalf("unable to start ChannelArbitrator: %v", err) @@ -686,13 +705,99 @@ func TestChannelArbitratorBreachClose(t *testing.T) { // It should start out in the default state. chanArbCtx.AssertState(StateDefault) + // We create two HTLCs, one incoming and one outgoing. We will later + // assert that we only receive a ResolutionMsg for the outgoing HTLC. + outgoingIdx := uint64(2) + + rHash1 := [lntypes.PreimageSize]byte{1, 2, 3} + htlc1 := channeldb.HTLC{ + RHash: rHash1, + OutputIndex: 2, + Incoming: false, + HtlcIndex: outgoingIdx, + LogIndex: 2, + } + + rHash2 := [lntypes.PreimageSize]byte{2, 2, 2} + htlc2 := channeldb.HTLC{ + RHash: rHash2, + OutputIndex: 3, + Incoming: true, + HtlcIndex: 3, + LogIndex: 3, + } + + anchorRes := &lnwallet.AnchorResolution{ + AnchorSignDescriptor: input.SignDescriptor{ + Output: &wire.TxOut{Value: 1}, + }, + } + + // Create the BreachCloseInfo that the chain_watcher would normally + // send to the channel_arbitrator. + breachInfo := &BreachCloseInfo{ + BreachResolution: &BreachResolution{ + FundingOutPoint: wire.OutPoint{}, + }, + AnchorResolution: anchorRes, + CommitSet: CommitSet{ + ConfCommitKey: &RemoteHtlcSet, + HtlcSets: map[HtlcSetKey][]channeldb.HTLC{ + RemoteHtlcSet: {htlc1, htlc2}, + }, + }, + CommitHash: chainhash.Hash{}, + } + // Send a breach close event. - chanArb.cfg.ChainEvents.ContractBreach <- &lnwallet.BreachRetribution{} + chanArb.cfg.ChainEvents.ContractBreach <- breachInfo - // It should transition StateDefault -> StateFullyResolved. - chanArbCtx.AssertStateTransitions( - StateFullyResolved, - ) + // It should transition StateDefault -> StateContractClosed. + chanArbCtx.AssertStateTransitions(StateContractClosed) + + // We should receive one ResolutionMsg as there was only one outgoing + // HTLC at the time of the breach. + select { + case res := <-chanArbCtx.resolutions: + require.Equal(t, 1, len(res)) + require.Equal(t, outgoingIdx, res[0].HtlcIndex) + case <-time.After(5 * time.Second): + t.Fatal("expected to receive a resolution msg") + } + + // We should now transition from StateContractClosed to + // StateWaitingFullResolution. + chanArbCtx.AssertStateTransitions(StateWaitingFullResolution) + + // One of the resolvers should be an anchor resolver and the other + // should be a breach resolver. + require.Equal(t, 2, len(chanArb.activeResolvers)) + + var anchorExists, breachExists bool + for _, resolver := range chanArb.activeResolvers { + switch resolver.(type) { + case *anchorResolver: + anchorExists = true + case *breachResolver: + breachExists = true + default: + t.Fatalf("did not expect resolver %T", resolver) + } + } + require.True(t, anchorExists && breachExists) + + // The anchor resolver is expected to re-offer the anchor input to the + // sweeper. + <-chanArbCtx.sweeper.sweptInputs + + // Wait for SubscribeBreachComplete to be called. + <-chanArbCtx.breachSubscribed + + // We'll now close the breach channel so that the state transitions to + // StateFullyResolved. + close(chanArbCtx.breachResolutionChan) + + chanArbCtx.AssertStateTransitions(StateFullyResolved) // It should also mark the channel as resolved. select { @@ -724,11 +829,7 @@ func TestChannelArbitratorLocalForceClosePendingHtlc(t *testing.T) { } defer chanArb.Stop() - // Create htlcUpdates channel. - htlcUpdates := make(chan *ContractUpdate) - signals := &ContractSignals{ - HtlcUpdates: htlcUpdates, ShortChanID: lnwire.ShortChannelID{}, } chanArb.UpdateContractSignals(signals) @@ -759,10 +860,11 @@ func TestChannelArbitratorLocalForceClosePendingHtlc(t *testing.T) { htlc, outgoingDustHtlc, incomingDustHtlc, } - htlcUpdates <- &ContractUpdate{ + newUpdate := &ContractUpdate{ HtlcKey: LocalHtlcSet, Htlcs: htlcSet, } + chanArb.notifyContractUpdate(newUpdate) errChan := make(chan error, 1) respChan := make(chan *wire.MsgTx, 1) @@ -1318,12 +1420,14 @@ func TestChannelArbitratorPersistence(t *testing.T) { // TestChannelArbitratorForceCloseBreachedChannel tests that the channel // arbitrator is able to handle a channel in the process of being force closed // is breached by the remote node. In these cases we expect the -// ChannelArbitrator to gracefully exit, as the breach is handled by other -// subsystems. +// ChannelArbitrator to properly execute the breachResolver flow and then +// gracefully exit once the breachResolver receives the signal from what would +// normally be the breacharbiter. func TestChannelArbitratorForceCloseBreachedChannel(t *testing.T) { log := &mockArbitratorLog{ state: StateDefault, newStates: make(chan ArbitratorState, 5), + resolvers: make(map[ContractResolver]struct{}), } chanArbCtx, err := createTestChannelArbitrator(t, log) @@ -1389,6 +1493,20 @@ func TestChannelArbitratorForceCloseBreachedChannel(t *testing.T) { t.Fatalf("no response received") } + // Before restarting, we'll need to modify the arbitrator log to have + // a set of contract resolutions and a commit set. + log.resolutions = &ContractResolutions{ + BreachResolution: &BreachResolution{ + FundingOutPoint: wire.OutPoint{}, + }, + } + log.commitSet = &CommitSet{ + ConfCommitKey: &RemoteHtlcSet, + HtlcSets: map[HtlcSetKey][]channeldb.HTLC{ + RemoteHtlcSet: {}, + }, + } + // We mimic that the channel is breached while the channel arbitrator // is down. This means that on restart it will be started with a // pending close channel, of type BreachClose. @@ -1402,7 +1520,18 @@ func TestChannelArbitratorForceCloseBreachedChannel(t *testing.T) { } defer chanArbCtx.CleanUp() - // Finally it should advance to StateFullyResolved. + // We should transition to StateContractClosed. + chanArbCtx.AssertStateTransitions( + StateContractClosed, StateWaitingFullResolution, + ) + + // Wait for SubscribeBreachComplete to be called. + <-chanArbCtx.breachSubscribed + + // We'll close the breachResolutionChan to cleanup the breachResolver + // and make the state transition to StateFullyResolved. + close(chanArbCtx.breachResolutionChan) + chanArbCtx.AssertStateTransitions(StateFullyResolved) // It should also mark the channel as resolved. @@ -1417,7 +1546,6 @@ func TestChannelArbitratorForceCloseBreachedChannel(t *testing.T) { // TestChannelArbitratorCommitFailure tests that the channel arbitrator is able // to recover from a failed CommitState call at restart. func TestChannelArbitratorCommitFailure(t *testing.T) { - testCases := []struct { // closeType is the type of channel close we want ot test. @@ -1505,6 +1633,7 @@ func TestChannelArbitratorCommitFailure(t *testing.T) { chanArb.cfg.MarkChannelClosed = func( *channeldb.ChannelCloseSummary, ...channeldb.ChannelStatus) error { + close(closed) return nil } @@ -1717,9 +1846,7 @@ func TestChannelArbitratorDanglingCommitForceClose(t *testing.T) { // Now that our channel arb has started, we'll set up // its contract signals channel so we can send it // various HTLC updates for this test. - htlcUpdates := make(chan *ContractUpdate) signals := &ContractSignals{ - HtlcUpdates: htlcUpdates, ShortChanID: lnwire.ShortChannelID{}, } chanArb.UpdateContractSignals(signals) @@ -1740,10 +1867,11 @@ func TestChannelArbitratorDanglingCommitForceClose(t *testing.T) { HtlcIndex: htlcIndex, RefundTimeout: htlcExpiry, } - htlcUpdates <- &ContractUpdate{ + newUpdate := &ContractUpdate{ HtlcKey: htlcKey, Htlcs: []channeldb.HTLC{danglingHTLC}, } + chanArb.notifyContractUpdate(newUpdate) // At this point, we now have a split commitment state // from the PoV of the channel arb. There's now an HTLC @@ -1912,9 +2040,7 @@ func TestChannelArbitratorPendingExpiredHTLC(t *testing.T) { // Now that our channel arb has started, we'll set up // its contract signals channel so we can send it // various HTLC updates for this test. - htlcUpdates := make(chan *ContractUpdate) signals := &ContractSignals{ - HtlcUpdates: htlcUpdates, ShortChanID: lnwire.ShortChannelID{}, } chanArb.UpdateContractSignals(signals) @@ -1929,10 +2055,11 @@ func TestChannelArbitratorPendingExpiredHTLC(t *testing.T) { HtlcIndex: htlcIndex, RefundTimeout: htlcExpiry, } - htlcUpdates <- &ContractUpdate{ + newUpdate := &ContractUpdate{ HtlcKey: RemoteHtlcSet, Htlcs: []channeldb.HTLC{pendingHTLC}, } + chanArb.notifyContractUpdate(newUpdate) // We will advance the uptime to 10 seconds which should be still within // the grace period and should not trigger going to chain. @@ -2039,6 +2166,7 @@ func TestRemoteCloseInitiator(t *testing.T) { // about setting of channel status. mockMarkClosed := func(_ *channeldb.ChannelCloseSummary, statuses ...channeldb.ChannelStatus) error { + for _, status := range statuses { err := alice.State().ApplyChanStatus(status) if err != nil { @@ -2090,6 +2218,7 @@ func TestRemoteCloseInitiator(t *testing.T) { if !alice.State().HasChanStatus( channeldb.ChanStatusRemoteCloseInitiator, ) { + t.Fatalf("expected remote close initiator, "+ "got: %v", alice.State().ChanStatus()) } @@ -2219,7 +2348,6 @@ func TestFindCommitmentDeadline(t *testing.T) { require.Equal(t, tc.deadline, deadline) }) } - } // TestSweepAnchors checks the sweep transactions are created using the @@ -2277,6 +2405,14 @@ func TestSweepAnchors(t *testing.T) { htlcDust.HtlcIndex: htlcDust, }, } + chanArb.unmergedSet[LocalHtlcSet] = htlcSet{ + incomingHTLCs: map[uint64]channeldb.HTLC{ + htlcWithPreimage.HtlcIndex: htlcWithPreimage, + }, + outgoingHTLCs: map[uint64]channeldb.HTLC{ + htlcDust.HtlcIndex: htlcDust, + }, + } // Setup our remote HTLC set such that no valid HTLCs can be used, thus // we default to anchorSweepConfTarget. @@ -2289,6 +2425,14 @@ func TestSweepAnchors(t *testing.T) { htlcDust.HtlcIndex: htlcDust, }, } + chanArb.unmergedSet[RemoteHtlcSet] = htlcSet{ + incomingHTLCs: map[uint64]channeldb.HTLC{ + htlcSmallExipry.HtlcIndex: htlcSmallExipry, + }, + outgoingHTLCs: map[uint64]channeldb.HTLC{ + htlcDust.HtlcIndex: htlcDust, + }, + } // Setup out pending remote HTLC set such that we will use the HTLC's // CLTV from the outgoing HTLC set. @@ -2301,6 +2445,14 @@ func TestSweepAnchors(t *testing.T) { htlcSmallExipry.HtlcIndex: htlcSmallExipry, }, } + chanArb.unmergedSet[RemotePendingHtlcSet] = htlcSet{ + incomingHTLCs: map[uint64]channeldb.HTLC{ + htlcDust.HtlcIndex: htlcDust, + }, + outgoingHTLCs: map[uint64]channeldb.HTLC{ + htlcSmallExipry.HtlcIndex: htlcSmallExipry, + }, + } // Create AnchorResolutions. anchors := &lnwallet.AnchorResolutions{ @@ -2342,7 +2494,6 @@ func TestSweepAnchors(t *testing.T) { t, expectedRemoteDeadline, deadlines[2], "remote deadline not matched", ) - } // TestChannelArbitratorAnchors asserts that the commitment tx anchor is swept. @@ -2399,11 +2550,7 @@ func TestChannelArbitratorAnchors(t *testing.T) { } }() - // Create htlcUpdates channel. - htlcUpdates := make(chan *ContractUpdate) - signals := &ContractSignals{ - HtlcUpdates: htlcUpdates, ShortChanID: lnwire.ShortChannelID{}, } chanArb.UpdateContractSignals(signals) @@ -2427,7 +2574,7 @@ func TestChannelArbitratorAnchors(t *testing.T) { // We now send two HTLC updates, one for local HTLC set and the other // for remote HTLC set. - htlcUpdates <- &ContractUpdate{ + newUpdate := &ContractUpdate{ HtlcKey: LocalHtlcSet, // This will make the deadline of the local anchor resolution // to be htlcWithPreimage's CLTV minus heightHint since the @@ -2435,13 +2582,16 @@ func TestChannelArbitratorAnchors(t *testing.T) { // preimage available. Htlcs: []channeldb.HTLC{htlc, htlcWithPreimage}, } - htlcUpdates <- &ContractUpdate{ + chanArb.notifyContractUpdate(newUpdate) + + newUpdate = &ContractUpdate{ HtlcKey: RemoteHtlcSet, // This will make the deadline of the remote anchor resolution // to be htlcWithPreimage's CLTV minus heightHint because the // incoming HTLC (toRemoteHTLCs) has a lower CLTV. Htlcs: []channeldb.HTLC{htlc, htlcWithPreimage}, } + chanArb.notifyContractUpdate(newUpdate) errChan := make(chan error, 1) respChan := make(chan *wire.MsgTx, 1) @@ -2571,7 +2721,6 @@ func TestChannelArbitratorAnchors(t *testing.T) { htlcWithPreimage.RefundTimeout-heightHint, chanArbCtx.sweeper.deadlines[1], ) - } // putResolverReportInChannel returns a put report function which will pipe @@ -2607,6 +2756,7 @@ type mockChannel struct { func (m *mockChannel) NewAnchorResolutions() (*lnwallet.AnchorResolutions, error) { + if m.anchorResolutions != nil { return m.anchorResolutions, nil } diff --git a/contractcourt/commit_sweep_resolver.go b/contractcourt/commit_sweep_resolver.go index 6b365735f5..ea78bbbac2 100644 --- a/contractcourt/commit_sweep_resolver.go +++ b/contractcourt/commit_sweep_resolver.go @@ -7,10 +7,10 @@ import ( "math" "sync" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/input" @@ -262,7 +262,6 @@ func (c *commitSweepResolver) Resolve() (ContractResolver, error) { // commitment this is. var witnessType input.WitnessType switch { - // Delayed output to us on our local commitment for a channel lease in // which we are the initiator. case isLocalCommitTx && c.hasCLTV(): @@ -487,8 +486,8 @@ func (c *commitSweepResolver) report() *ContractReport { c.reportLock.Lock() defer c.reportLock.Unlock() - copy := c.currentReport - return © + cpy := c.currentReport + return &cpy } // initReport initializes the pending channels report for this resolver. diff --git a/contractcourt/commit_sweep_resolver_test.go b/contractcourt/commit_sweep_resolver_test.go index 1321c03280..17c02dd110 100644 --- a/contractcourt/commit_sweep_resolver_test.go +++ b/contractcourt/commit_sweep_resolver_test.go @@ -4,8 +4,8 @@ import ( "testing" "time" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/input" @@ -347,7 +347,6 @@ func testCommitSweepResolverDelay(t *testing.T, sweepErr error) { t.Fatalf("unexpected resolver report. want=%v got=%v", expectedReport, report) } - } // TestCommitSweepResolverDelay tests resolution of a direct commitment output diff --git a/contractcourt/htlc_incoming_contest_resolver.go b/contractcourt/htlc_incoming_contest_resolver.go index ca4e05ddf5..2e1ad77565 100644 --- a/contractcourt/htlc_incoming_contest_resolver.go +++ b/contractcourt/htlc_incoming_contest_resolver.go @@ -7,12 +7,13 @@ import ( "fmt" "io" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/htlcswitch/hop" "github.com/lightningnetwork/lnd/invoices" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwallet" + "github.com/lightningnetwork/lnd/lnwire" ) // htlcIncomingContestResolver is a ContractResolver that's able to resolve an @@ -70,7 +71,7 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) { // First try to parse the payload. If that fails, we can stop resolution // now. - payload, err := h.decodePayload() + payload, nextHopOnionBlob, err := h.decodePayload() if err != nil { log.Debugf("ChannelArbitrator(%v): cannot decode payload of "+ "htlc %v", h.ChanPoint, h.HtlcPoint()) @@ -152,7 +153,7 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) { // Update htlcResolution with the matching preimage. h.htlcResolution.Preimage = preimage - log.Infof("%T(%v): extracted preimage=%v from beacon!", h, + log.Infof("%T(%v): applied preimage=%v", h, h.htlcResolution.ClaimOutpoint, preimage) // If this is our commitment transaction, then we'll need to @@ -180,7 +181,6 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) { // Take action based on the type of resolution we have // received. switch resolution := e.(type) { - // If the htlc resolution was a settle, apply the // preimage and return a success resolver. case *invoices.HtlcSettleResolution: @@ -278,7 +278,13 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) { // NOTE: This is done BEFORE opportunistically querying the db, // to ensure the preimage can't be delivered between querying // and registering for the preimage subscription. - preimageSubscription := h.PreimageDB.SubscribeUpdates() + preimageSubscription, err := h.PreimageDB.SubscribeUpdates( + h.htlcSuccessResolver.ShortChanID, &h.htlc, + payload, nextHopOnionBlob, + ) + if err != nil { + return nil, err + } defer preimageSubscription.CancelSubscription() // With the epochs and preimage subscriptions initialized, we'll @@ -441,17 +447,31 @@ func (h *htlcIncomingContestResolver) SupplementState(_ *channeldb.OpenChannel) } // decodePayload (re)decodes the hop payload of a received htlc. -func (h *htlcIncomingContestResolver) decodePayload() (*hop.Payload, error) { +func (h *htlcIncomingContestResolver) decodePayload() (*hop.Payload, + []byte, error) { onionReader := bytes.NewReader(h.htlc.OnionBlob) iterator, err := h.OnionProcessor.ReconstructHopIterator( onionReader, h.htlc.RHash[:], ) if err != nil { - return nil, err + return nil, nil, err + } + + payload, err := iterator.HopPayload() + if err != nil { + return nil, nil, err + } + + // Transform onion blob for the next hop. + var onionBlob [lnwire.OnionPacketSize]byte + buf := bytes.NewBuffer(onionBlob[0:0]) + err = iterator.EncodeNextHop(buf) + if err != nil { + return nil, nil, err } - return iterator.HopPayload() + return payload, onionBlob[:], nil } // A compile time assertion to ensure htlcIncomingContestResolver meets the diff --git a/contractcourt/htlc_incoming_resolver_test.go b/contractcourt/htlc_incoming_resolver_test.go index baef5e5088..2f76a5321b 100644 --- a/contractcourt/htlc_incoming_resolver_test.go +++ b/contractcourt/htlc_incoming_resolver_test.go @@ -140,6 +140,7 @@ func TestHtlcIncomingResolverExitSettle(t *testing.T) { if !bytes.Equal( ctx.onionProcessor.offeredOnionBlob, testOnionBlob, ) { + t.Fatal("unexpected onion blob") } } @@ -275,6 +276,10 @@ func (h *mockHopIterator) HopPayload() (*hop.Payload, error) { }), nil } +func (h *mockHopIterator) EncodeNextHop(w io.Writer) error { + return nil +} + type mockOnionProcessor struct { isExit bool offeredOnionBlob []byte diff --git a/contractcourt/htlc_outgoing_contest_resolver.go b/contractcourt/htlc_outgoing_contest_resolver.go index 14398e1a76..2d066b667a 100644 --- a/contractcourt/htlc_outgoing_contest_resolver.go +++ b/contractcourt/htlc_outgoing_contest_resolver.go @@ -4,7 +4,7 @@ import ( "fmt" "io" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/lnwallet" ) diff --git a/contractcourt/htlc_success_resolver.go b/contractcourt/htlc_success_resolver.go index c81fffcc8b..b642b2a555 100644 --- a/contractcourt/htlc_success_resolver.go +++ b/contractcourt/htlc_success_resolver.go @@ -5,9 +5,9 @@ import ( "io" "sync" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" @@ -515,8 +515,8 @@ func (h *htlcSuccessResolver) report() *ContractReport { h.reportLock.Lock() defer h.reportLock.Unlock() - copy := h.currentReport - return © + cpy := h.currentReport + return &cpy } func (h *htlcSuccessResolver) initReport() { diff --git a/contractcourt/htlc_success_resolver_test.go b/contractcourt/htlc_success_resolver_test.go index 9c7a560975..2e11d7da28 100644 --- a/contractcourt/htlc_success_resolver_test.go +++ b/contractcourt/htlc_success_resolver_test.go @@ -6,9 +6,9 @@ import ( "reflect" "testing" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" @@ -62,6 +62,7 @@ func newHtlcResolverTestContext(t *testing.T, Sweeper: newMockSweeper(), IncubateOutputs: func(wire.OutPoint, *lnwallet.OutgoingHtlcResolution, *lnwallet.IncomingHtlcResolution, uint32) error { + return nil }, DeliverResolutionMsg: func(msgs ...ResolutionMsg) error { @@ -86,6 +87,7 @@ func newHtlcResolverTestContext(t *testing.T, // this so set nolint directive. checkpointFunc := func(c ContractResolver, // nolint r ...*channeldb.ResolverReport) error { + return testCtx.checkpoint(c, r...) } @@ -253,6 +255,7 @@ func TestHtlcSuccessSecondStageResolution(t *testing.T) { // to resolve our htlc. preCheckpoint: func(ctx *htlcResolverTestContext, _ bool) error { + ctx.notifier.SpendChan <- &chainntnfs.SpendDetail{ SpendingTx: sweepTx, SpenderTxHash: &sweepHash, @@ -389,7 +392,6 @@ func TestHtlcSuccessSecondStageResolutionSweeper(t *testing.T) { SpendingHeight: 10, } return nil - }, // incubating=true is used to signal that the // second-level transaction was confirmed. @@ -555,7 +557,6 @@ func runFromCheckpoint(t *testing.T, ctx *htlcResolverTestContext, t.Fatalf("expected checkpoint to be have "+ "incubating=%v, had %v", cp.incubating, incubating) - } // Check we go the expected reports. @@ -595,7 +596,6 @@ func runFromCheckpoint(t *testing.T, ctx *htlcResolverTestContext, if err := cp.preCheckpoint(ctx, resumed); err != nil { t.Fatalf("failure at stage %d: %v", i, err) } - } resumed = false diff --git a/contractcourt/htlc_timeout_resolver.go b/contractcourt/htlc_timeout_resolver.go index b20f10ca9e..d84beb5c07 100644 --- a/contractcourt/htlc_timeout_resolver.go +++ b/contractcourt/htlc_timeout_resolver.go @@ -7,8 +7,8 @@ import ( "math" "sync" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" @@ -329,7 +329,6 @@ func (h *htlcTimeoutResolver) Resolve() (ContractResolver, error) { // transaction. func (h *htlcTimeoutResolver) spendHtlcOutput() (*chainntnfs.SpendDetail, error) { switch { - // If we have non-nil SignDetails, this means that have a 2nd level // HTLC transaction that is signed using sighash SINGLE|ANYONECANPAY // (the case for anchor type channels). In this case we can re-sign it @@ -441,7 +440,6 @@ func (h *htlcTimeoutResolver) handleCommitSpend( ) switch { - // If the sweeper is handling the second level transaction, wait for // the CSV and possible CLTV lock to expire, before sweeping the output // on the second-level. @@ -529,7 +527,7 @@ func (h *htlcTimeoutResolver) handleCommitSpend( case h.htlcResolution.SignedTimeoutTx != nil: log.Infof("%T(%v): waiting for nursery/sweeper to spend CSV "+ "delayed output", h, claimOutpoint) - sweep, err := waitForSpend( + sweepTx, err := waitForSpend( &claimOutpoint, h.htlcResolution.SweepSignDesc.Output.PkScript, h.broadcastHeight, h.Notifier, h.quit, @@ -539,7 +537,7 @@ func (h *htlcTimeoutResolver) handleCommitSpend( } // Update the spend txid to the hash of the sweep transaction. - spendTxID = sweep.SpenderTxHash + spendTxID = sweepTx.SpenderTxHash // Once our sweep of the timeout tx has confirmed, we add a // resolution for our timeoutTx tx first stage transaction. @@ -603,8 +601,8 @@ func (h *htlcTimeoutResolver) report() *ContractReport { h.reportLock.Lock() defer h.reportLock.Unlock() - copy := h.currentReport - return © + cpy := h.currentReport + return &cpy } func (h *htlcTimeoutResolver) initReport() { diff --git a/contractcourt/htlc_timeout_resolver_test.go b/contractcourt/htlc_timeout_resolver_test.go index 680b82b668..cfc0933401 100644 --- a/contractcourt/htlc_timeout_resolver_test.go +++ b/contractcourt/htlc_timeout_resolver_test.go @@ -8,17 +8,19 @@ import ( "testing" "time" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/htlcswitch/hop" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lntest/mock" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwallet" + "github.com/lightningnetwork/lnd/lnwire" "github.com/stretchr/testify/require" ) @@ -36,11 +38,15 @@ func newMockWitnessBeacon() *mockWitnessBeacon { } } -func (m *mockWitnessBeacon) SubscribeUpdates() *WitnessSubscription { +func (m *mockWitnessBeacon) SubscribeUpdates( + chanID lnwire.ShortChannelID, htlc *channeldb.HTLC, + payload *hop.Payload, + nextHopOnionBlob []byte) (*WitnessSubscription, error) { + return &WitnessSubscription{ WitnessUpdates: m.preImageUpdates, CancelSubscription: func() {}, - } + }, nil } func (m *mockWitnessBeacon) LookupPreimage(payhash lntypes.Hash) (lntypes.Preimage, bool) { @@ -138,6 +144,7 @@ func TestHtlcTimeoutResolver(t *testing.T) { if reflect.DeepEqual( templateTx.TxIn[0].Witness, witness, ) { + return templateTx, nil } templateTx.TxIn[0].Witness = witness @@ -169,6 +176,7 @@ func TestHtlcTimeoutResolver(t *testing.T) { if reflect.DeepEqual( templateTx.TxIn[0].Witness, witness, ) { + return templateTx, nil } @@ -206,6 +214,7 @@ func TestHtlcTimeoutResolver(t *testing.T) { if reflect.DeepEqual( templateTx.TxIn[0].Witness, witness, ) { + return templateTx, nil } @@ -239,6 +248,7 @@ func TestHtlcTimeoutResolver(t *testing.T) { if reflect.DeepEqual( templateTx.TxIn[0].Witness, witness, ) { + return templateTx, nil } @@ -750,7 +760,7 @@ func TestHtlcTimeoutSingleStageRemoteSpend(t *testing.T) { witnessBeacon := ctx.resolver.(*htlcTimeoutResolver).PreimageDB.(*mockWitnessBeacon) - // The remote spends the output direcly with + // The remote spends the output directly with // the preimage. ctx.notifier.SpendChan <- &chainntnfs.SpendDetail{ SpendingTx: spendTx, @@ -857,6 +867,7 @@ func TestHtlcTimeoutSecondStageRemoteSpend(t *testing.T) { // success transcation. preCheckpoint: func(ctx *htlcResolverTestContext, _ bool) error { + ctx.notifier.SpendChan <- &chainntnfs.SpendDetail{ SpendingTx: remoteSuccessTx, SpenderTxHash: &successTxid, @@ -978,7 +989,7 @@ func TestHtlcTimeoutSecondStageSweeper(t *testing.T) { } // twoStageResolution is a resolution for a htlc on the local - // party's commitment, where the timout tx can be re-signed. + // party's commitment, where the timeout tx can be re-signed. twoStageResolution := lnwallet.OutgoingHtlcResolution{ ClaimOutpoint: htlcOutpoint, SignedTimeoutTx: timeoutTx, @@ -1041,7 +1052,7 @@ func TestHtlcTimeoutSecondStageSweeper(t *testing.T) { preCheckpoint: func(ctx *htlcResolverTestContext, resumed bool) error { - // If we are resuming from a checkpoing, we + // If we are resuming from a checkpoint, we // expect the resolver to re-subscribe to a // spend, hence we must resend it. if resumed { @@ -1070,7 +1081,7 @@ func TestHtlcTimeoutSecondStageSweeper(t *testing.T) { Height: 13, } - // The timout tx output should now be given to + // The timeout tx output should now be given to // the sweeper. resolver := ctx.resolver.(*htlcTimeoutResolver) inp := <-resolver.Sweeper.(*mockSweeper).sweptInputs @@ -1161,7 +1172,7 @@ func TestHtlcTimeoutSecondStageSweeperRemoteSpend(t *testing.T) { spendTxHash := spendTx.TxHash() // twoStageResolution is a resolution for a htlc on the local - // party's commitment, where the timout tx can be re-signed. + // party's commitment, where the timeout tx can be re-signed. twoStageResolution := lnwallet.OutgoingHtlcResolution{ ClaimOutpoint: htlcOutpoint, SignedTimeoutTx: timeoutTx, @@ -1214,11 +1225,11 @@ func TestHtlcTimeoutSecondStageSweeperRemoteSpend(t *testing.T) { preCheckpoint: func(ctx *htlcResolverTestContext, resumed bool) error { - // If we are resuming from a checkpoing, we + // If we are resuming from a checkpoint, we // expect the resolver to re-subscribe to a // spend, hence we must resend it. if resumed { - fmt.Println("resumed") + t.Logf("resumed") ctx.notifier.SpendChan <- &chainntnfs.SpendDetail{ SpendingTx: spendTx, SpenderTxHash: &spendTxHash, diff --git a/contractcourt/nursery_store.go b/contractcourt/nursery_store.go index 755dcf8df7..94cb9b6428 100644 --- a/contractcourt/nursery_store.go +++ b/contractcourt/nursery_store.go @@ -291,7 +291,6 @@ func (ns *NurseryStore) Incubate(kids []kidOutput, babies []babyOutput) error { // will be stored as it waits out the kidOutput's CSV delay. func (ns *NurseryStore) CribToKinder(bby *babyOutput) error { return kvdb.Update(ns.db, func(tx kvdb.RwTx) error { - // First, retrieve or create the channel bucket corresponding to // the baby output's origin channel point. chanPoint := bby.OriginChanPoint() @@ -472,7 +471,6 @@ func (ns *NurseryStore) PreschoolToKinder(kid *kidOutput, // pruned from the height index as outputs are removed. func (ns *NurseryStore) GraduateKinder(height uint32, kid *kidOutput) error { return kvdb.Update(ns.db, func(tx kvdb.RwTx) error { - hghtBucket := ns.getHeightBucket(tx, height) if hghtBucket == nil { // Nothing to delete, bucket has already been removed. @@ -597,7 +595,6 @@ func (ns *NurseryStore) FetchClass( func (ns *NurseryStore) FetchPreschools() ([]kidOutput, error) { // nolint:golint var kids []kidOutput if err := kvdb.View(ns.db, func(tx kvdb.RTx) error { - // Retrieve the existing chain bucket for this nursery store. chainBucket := tx.ReadBucket(ns.pfxChainKey) if chainBucket == nil { @@ -1058,18 +1055,18 @@ func (ns *NurseryStore) createHeightBucket(tx kvdb.RwTx, // store, using the provided block height. If the bucket does not exist, or any // bucket along its path does not exist, a nil value is returned. func (ns *NurseryStore) getHeightBucketPath(tx kvdb.RTx, - height uint32) (kvdb.RBucket, kvdb.RBucket, kvdb.RBucket) { + height uint32) (kvdb.RBucket, kvdb.RBucket) { // Retrieve the existing chain bucket for this nursery store. chainBucket := tx.ReadBucket(ns.pfxChainKey) if chainBucket == nil { - return nil, nil, nil + return nil, nil } // Retrieve the existing channel index. hghtIndex := chainBucket.NestedReadBucket(heightIndexKey) if hghtIndex == nil { - return nil, nil, nil + return nil, nil } // Serialize the provided block height and return the bucket matching @@ -1077,25 +1074,25 @@ func (ns *NurseryStore) getHeightBucketPath(tx kvdb.RTx, var heightBytes [4]byte byteOrder.PutUint32(heightBytes[:], height) - return chainBucket, hghtIndex, hghtIndex.NestedReadBucket(heightBytes[:]) + return chainBucket, hghtIndex.NestedReadBucket(heightBytes[:]) } // getHeightBucketPathWrite retrieves an existing height bucket from the nursery // store, using the provided block height. If the bucket does not exist, or any // bucket along its path does not exist, a nil value is returned. func (ns *NurseryStore) getHeightBucketPathWrite(tx kvdb.RwTx, - height uint32) (kvdb.RwBucket, kvdb.RwBucket, kvdb.RwBucket) { + height uint32) (kvdb.RwBucket, kvdb.RwBucket) { // Retrieve the existing chain bucket for this nursery store. chainBucket := tx.ReadWriteBucket(ns.pfxChainKey) if chainBucket == nil { - return nil, nil, nil + return nil, nil } // Retrieve the existing channel index. hghtIndex := chainBucket.NestedReadWriteBucket(heightIndexKey) if hghtIndex == nil { - return nil, nil, nil + return nil, nil } // Serialize the provided block height and return the bucket matching @@ -1103,7 +1100,7 @@ func (ns *NurseryStore) getHeightBucketPathWrite(tx kvdb.RwTx, var heightBytes [4]byte byteOrder.PutUint32(heightBytes[:], height) - return chainBucket, hghtIndex, hghtIndex.NestedReadWriteBucket( + return hghtIndex, hghtIndex.NestedReadWriteBucket( heightBytes[:], ) } @@ -1113,7 +1110,8 @@ func (ns *NurseryStore) getHeightBucketPathWrite(tx kvdb.RwTx, // along its path does not exist, a nil value is returned. func (ns *NurseryStore) getHeightBucket(tx kvdb.RTx, height uint32) kvdb.RBucket { - _, _, hghtBucket := ns.getHeightBucketPath(tx, height) + + _, hghtBucket := ns.getHeightBucketPath(tx, height) return hghtBucket } @@ -1124,7 +1122,7 @@ func (ns *NurseryStore) getHeightBucket(tx kvdb.RTx, func (ns *NurseryStore) getHeightBucketWrite(tx kvdb.RwTx, height uint32) kvdb.RwBucket { - _, _, hghtBucket := ns.getHeightBucketPathWrite(tx, height) + _, hghtBucket := ns.getHeightBucketPathWrite(tx, height) return hghtBucket } @@ -1190,7 +1188,7 @@ func (ns *NurseryStore) forEachHeightPrefix(tx kvdb.RTx, prefix []byte, // Start by retrieving the height bucket corresponding to the provided // block height. - chainBucket, _, hghtBucket := ns.getHeightBucketPath(tx, height) + chainBucket, hghtBucket := ns.getHeightBucketPath(tx, height) if hghtBucket == nil { return nil } @@ -1345,7 +1343,7 @@ func (ns *NurseryStore) removeOutputFromHeight(tx kvdb.RwTx, height uint32, // this invocation successfully pruned the height bucket. func (ns *NurseryStore) pruneHeight(tx kvdb.RwTx, height uint32) (bool, error) { // Fetch the existing height index and height bucket. - _, hghtIndex, hghtBucket := ns.getHeightBucketPathWrite(tx, height) + hghtIndex, hghtBucket := ns.getHeightBucketPathWrite(tx, height) if hghtBucket == nil { return false, nil } diff --git a/contractcourt/setup_test.go b/contractcourt/setup_test.go new file mode 100644 index 0000000000..f9925ce040 --- /dev/null +++ b/contractcourt/setup_test.go @@ -0,0 +1,11 @@ +package contractcourt + +import ( + "testing" + + "github.com/lightningnetwork/lnd/kvdb" +) + +func TestMain(m *testing.M) { + kvdb.RunTests(m) +} diff --git a/contractcourt/utxonursery.go b/contractcourt/utxonursery.go index c6592bc3fe..02a09dea0c 100644 --- a/contractcourt/utxonursery.go +++ b/contractcourt/utxonursery.go @@ -8,8 +8,8 @@ import ( "sync" "sync/atomic" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" @@ -241,7 +241,7 @@ func (u *UtxoNursery) Start() error { return nil } - utxnLog.Tracef("Starting UTXO nursery") + utxnLog.Info("UTXO nursery starting") // Retrieve the currently best known block. This is needed to have the // state machine catch up with the blocks we missed when we were down. @@ -884,7 +884,8 @@ func (u *UtxoNursery) sweepCribOutput(classHeight uint32, baby *babyOutput) erro // notification for an htlc timeout transaction. If successful, a goroutine // will be spawned that will transition the provided baby output into the // kindergarten state within the nursery store. -func (u *UtxoNursery) registerTimeoutConf(baby *babyOutput, heightHint uint32) error { +func (u *UtxoNursery) registerTimeoutConf(baby *babyOutput, + heightHint uint32) error { birthTxID := baby.timeoutTx.TxHash() @@ -1052,7 +1053,7 @@ type ContractMaturityReport struct { } // HtlcMaturityReport provides a summary of a single htlc output, and is -// embedded as party of the overarching ContractMaturityReport +// embedded as party of the overarching ContractMaturityReport. type HtlcMaturityReport struct { // Outpoint is the final output that will be swept back to the wallet. Outpoint wire.OutPoint diff --git a/contractcourt/utxonursery_test.go b/contractcourt/utxonursery_test.go index 60a92b8689..bb8d78af90 100644 --- a/contractcourt/utxonursery_test.go +++ b/contractcourt/utxonursery_test.go @@ -14,11 +14,11 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lntest/mock" @@ -328,7 +328,7 @@ func init() { // Finish initializing our test vectors by parsing the desired public keys and // properly populating the sign descriptors of all baby and kid outputs. for i := range signDescriptors { - pk, err := btcec.ParsePubKey(keys[i], btcec.S256()) + pk, err := btcec.ParsePubKey(keys[i]) if err != nil { panic(fmt.Sprintf("unable to parse pub key during init: %v", err)) } diff --git a/contrib/init/README.md b/contrib/init/README.md new file mode 100644 index 0000000000..7e88151b40 --- /dev/null +++ b/contrib/init/README.md @@ -0,0 +1,27 @@ +# Init + +Sample configuration files for: + +``` +systemd: lnd.service +``` + +## systemd + +Add the example `lnd.service` file to `/etc/systemd/system/` and modify it according to your system and user configuration. Use the following commands to interact with the service: + +```bash +# Enable lnd to automatically start on system boot +systemctl enable lnd + +# Start lnd +systemctl start lnd + +# Restart lnd +systemctl restart lnd + +# Stop lnd +systemctl stop lnd +``` + +Systemd will attempt to restart lnd automatically if it crashes or otherwise stops unexpectedly. diff --git a/contrib/init/lnd.service b/contrib/init/lnd.service new file mode 100644 index 0000000000..0e9cfb1ab7 --- /dev/null +++ b/contrib/init/lnd.service @@ -0,0 +1,49 @@ +# A sample systemd service file for lnd running with a bitcoind service. + +[Unit] +Description=Lightning Network Daemon + +# Make sure lnd starts after bitcoind is ready +Requires=bitcoind.service +After=bitcoind.service + +[Service] +ExecStart=/usr/local/bin/lnd +ExecStop=/usr/local/bin/lncli stop + +# Replace these with the user:group that will run lnd +User=bitcoin +Group=bitcoin + +# Try restarting lnd if it stops due to a failure +Restart=on-failure +RestartSec=60 + +# Type=notify is required for lnd to notify systemd when it is ready +Type=notify + +# An extended timeout period is needed to allow for database compaction +# and other time intensive operations during startup. We also extend the +# stop timeout to ensure graceful shutdowns of lnd. +TimeoutStartSec=1200 +TimeoutStopSec=3600 + +# Hardening Measures +#################### + +# Mount /usr, /boot/ and /etc read-only for the process. +ProtectSystem=full + +# Disallow the process and all of its children to gain +# new privileges through execve(). +NoNewPrivileges=true + +# Use a new /dev namespace only populated with API pseudo devices +# such as /dev/null, /dev/zero and /dev/random. +PrivateDevices=true + +# Deny the creation of writable and executable memory mappings. +MemoryDenyWriteExecute=true + +[Install] +WantedBy=multi-user.target diff --git a/dev.Dockerfile b/dev.Dockerfile index 77dd035417..df77303e72 100644 --- a/dev.Dockerfile +++ b/dev.Dockerfile @@ -4,7 +4,7 @@ # /make/builder.Dockerfile # /.github/workflows/main.yml # /.github/workflows/release.yml -FROM golang:1.17.1-alpine as builder +FROM golang:1.17.3-alpine as builder LABEL maintainer="Olaoluwa Osuntokun " @@ -22,7 +22,7 @@ COPY . /go/src/github.com/lightningnetwork/lnd RUN cd /go/src/github.com/lightningnetwork/lnd \ && make \ -&& make install tags="signrpc walletrpc chainrpc invoicesrpc" +&& make install tags="signrpc walletrpc chainrpc invoicesrpc peersrpc" # Start a new, final image to reduce size. FROM alpine as final diff --git a/discovery/bootstrapper.go b/discovery/bootstrapper.go index 6aced5987d..a30ffddc32 100644 --- a/discovery/bootstrapper.go +++ b/discovery/bootstrapper.go @@ -12,8 +12,8 @@ import ( "strings" "time" - "github.com/btcsuite/btcd/btcec" - "github.com/btcsuite/btcutil/bech32" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil/bech32" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/autopilot" "github.com/lightningnetwork/lnd/lnwire" @@ -158,6 +158,7 @@ func (c *ChannelGraphBootstrapper) SampleNodeAddrs(numAddrs uint32, // We'll merge the ignore map with our currently selected map in order // to ensure we don't return any duplicate nodes. for n := range ignore { + log.Tracef("Ignored node %x for bootstrapping", n) c.tried[n] = struct{}{} } @@ -206,7 +207,7 @@ func (c *ChannelGraphBootstrapper) SampleNodeAddrs(numAddrs uint32, } nodePub, err := btcec.ParsePubKey( - nodePubKeyBytes[:], btcec.S256(), + nodePubKeyBytes[:], ) if err != nil { return err @@ -307,12 +308,13 @@ var _ NetworkPeerBootstrapper = (*ChannelGraphBootstrapper)(nil) func NewDNSSeedBootstrapper( seeds [][2]string, net tor.Net, timeout time.Duration) NetworkPeerBootstrapper { + return &DNSSeedBootstrapper{dnsSeeds: seeds, net: net, timeout: timeout} } // fallBackSRVLookup attempts to manually query for SRV records we need to // properly bootstrap. We do this by querying the special record at the "soa." -// sub-domain of supporting DNS servers. The retuned IP address will be the IP +// sub-domain of supporting DNS servers. The returned IP address will be the IP // address of the authoritative DNS server. Once we have this IP address, we'll // connect manually over TCP to request the SRV record. This is necessary as // the records we return are currently too large for a class of resolvers, @@ -360,7 +362,7 @@ func (d *DNSSeedBootstrapper) fallBackSRVLookup(soaShim string, "received: %v", resp.Rcode) } - // Retrieve the RR(s) of the Answer section, and covert to the format + // Retrieve the RR(s) of the Answer section, and convert to the format // that net.LookupSRV would normally return. var rrs []*net.SRV for _, rr := range resp.Answer { @@ -486,9 +488,7 @@ search: if err != nil { return nil, err } - nodeKey, err := btcec.ParsePubKey( - nodeBytes, btcec.S256(), - ) + nodeKey, err := btcec.ParsePubKey(nodeBytes) if err != nil { return nil, err } diff --git a/discovery/gossiper.go b/discovery/gossiper.go index 075982af86..18681843f1 100644 --- a/discovery/gossiper.go +++ b/discovery/gossiper.go @@ -7,11 +7,13 @@ import ( "sync" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/davecgh/go-spew/spew" + "github.com/lightninglabs/neutrino/cache" + "github.com/lightninglabs/neutrino/cache/lru" "github.com/lightningnetwork/lnd/batch" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" @@ -38,6 +40,15 @@ const ( // determine how often we should allow a new update for a specific // channel and direction. DefaultChannelUpdateInterval = time.Minute + + // maxPrematureUpdates tracks the max amount of premature channel + // updates that we'll hold onto. + maxPrematureUpdates = 100 + + // maxRejectedUpdates tracks the max amount of rejected channel updates + // we'll maintain. This is the global size across all peers. We'll + // allocate ~3 MB max to the cache. + maxRejectedUpdates = 10_000 ) var ( @@ -268,6 +279,54 @@ type Config struct { ChannelUpdateInterval time.Duration } +// cachedNetworkMsg is a wrapper around a network message that can be used with +// *lru.Cache. +type cachedNetworkMsg struct { + msgs []*networkMsg +} + +// Size returns the "size" of an entry. We return the number of items as we +// just want to limit the total amount of entries rather than do accurate size +// accounting. +func (c *cachedNetworkMsg) Size() (uint64, error) { + return uint64(len(c.msgs)), nil +} + +// rejectCacheKey is the cache key that we'll use to track announcements we've +// recently rejected. +type rejectCacheKey struct { + pubkey [33]byte + chanID uint64 +} + +// newRejectCacheKey returns a new cache key for the reject cache. +func newRejectCacheKey(cid uint64, pub [33]byte) rejectCacheKey { + k := rejectCacheKey{ + chanID: cid, + pubkey: pub, + } + + return k +} + +// sourceToPub returns a serialized-compressed public key for use in the reject +// cache. +func sourceToPub(pk *btcec.PublicKey) [33]byte { + var pub [33]byte + copy(pub[:], pk.SerializeCompressed()) + return pub +} + +// cachedReject is the empty value used to track the value for rejects. +type cachedReject struct { +} + +// Size returns the "size" of an entry. We return 1 as we just want to limit +// the total size. +func (c *cachedReject) Size() (uint64, error) { + return 1, nil +} + // AuthenticatedGossiper is a subsystem which is responsible for receiving // announcements, validating them and applying the changes to router, syncing // lightning network with newly connected nodes, broadcasting announcements @@ -302,14 +361,20 @@ type AuthenticatedGossiper struct { // that wasn't associated with any channel we know about. We store // them temporarily, such that we can reprocess them when a // ChannelAnnouncement for the channel is received. - prematureChannelUpdates map[uint64][]*networkMsg - pChanUpdMtx sync.Mutex + prematureChannelUpdates *lru.Cache // networkMsgs is a channel that carries new network broadcasted // message from outside the gossiper service to be processed by the // networkHandler. networkMsgs chan *networkMsg + // futureMsgs is a list of premature network messages that have a block + // height specified in the future. We will save them and resend it to + // the chan networkMsgs once the block height has reached. The cached + // map format is, + // {blockHeight: [msg1, msg2, ...], ...} + futureMsgs *lru.Cache + // chanPolicyUpdates is a channel that requests to update the // forwarding policy of a set of channels is sent over. chanPolicyUpdates chan *chanPolicyUpdateRequest @@ -327,8 +392,7 @@ type AuthenticatedGossiper struct { // consistent between when the DB is first read until it's written. channelMtx *multimutex.Mutex - rejectMtx sync.RWMutex - recentRejects map[uint64]struct{} + recentRejects *lru.Cache // syncMgr is a subsystem responsible for managing the gossip syncers // for peers currently connected. When a new peer is connected, the @@ -366,11 +430,12 @@ func New(cfg Config, selfKeyDesc *keychain.KeyDescriptor) *AuthenticatedGossiper selfKeyLoc: selfKeyDesc.KeyLocator, cfg: &cfg, networkMsgs: make(chan *networkMsg), + futureMsgs: lru.NewCache(maxPrematureUpdates), quit: make(chan struct{}), chanPolicyUpdates: make(chan *chanPolicyUpdateRequest), - prematureChannelUpdates: make(map[uint64][]*networkMsg), + prematureChannelUpdates: lru.NewCache(maxPrematureUpdates), channelMtx: multimutex.NewMutex(), - recentRejects: make(map[uint64]struct{}), + recentRejects: lru.NewCache(maxRejectedUpdates), chanUpdateRateLimiter: make(map[uint64][2]*rate.Limiter), } @@ -433,14 +498,13 @@ func (d *AuthenticatedGossiper) PropagateChanPolicyUpdate( func (d *AuthenticatedGossiper) Start() error { var err error d.started.Do(func() { + log.Info("Authenticated Gossiper starting") err = d.start() }) return err } func (d *AuthenticatedGossiper) start() error { - log.Info("Authenticated Gossiper is starting") - // First we register for new notifications of newly discovered blocks. // We do this immediately so we'll later be able to consume any/all // blocks which were discovered. @@ -465,12 +529,82 @@ func (d *AuthenticatedGossiper) start() error { d.syncMgr.Start() - d.wg.Add(1) + // Start receiving blocks in its dedicated goroutine. + d.wg.Add(2) + go d.syncBlockHeight() go d.networkHandler() return nil } +// syncBlockHeight syncs the best block height for the gossiper by reading +// blockEpochs. +// +// NOTE: must be run as a goroutine. +func (d *AuthenticatedGossiper) syncBlockHeight() { + defer d.wg.Done() + + for { + select { + // A new block has arrived, so we can re-process the previously + // premature announcements. + case newBlock, ok := <-d.blockEpochs.Epochs: + // If the channel has been closed, then this indicates + // the daemon is shutting down, so we exit ourselves. + if !ok { + return + } + + // Once a new block arrives, we update our running + // track of the height of the chain tip. + d.Lock() + blockHeight := uint32(newBlock.Height) + d.bestHeight = blockHeight + d.Unlock() + + log.Debugf("New block: height=%d, hash=%s", blockHeight, + newBlock.Hash) + + // Resend future messages, if any. + d.resendFutureMessages(blockHeight) + + case <-d.quit: + return + } + } +} + +// resendFutureMessages takes a block height, resends all the future messages +// found at that height and deletes those messages found in the gossiper's +// futureMsgs. +func (d *AuthenticatedGossiper) resendFutureMessages(height uint32) { + result, err := d.futureMsgs.Get(height) + + // Return early if no messages found. + if err == cache.ErrElementNotFound { + return + } + + // The error must nil, we will log an error and exit. + if err != nil { + log.Errorf("Reading future messages got error: %v", err) + return + } + + msgs := result.(*cachedNetworkMsg).msgs + + log.Debugf("Resending %d network messages at height %d", + len(msgs), height) + + for _, msg := range msgs { + select { + case d.networkMsgs <- msg: + case <-d.quit: + msg.err <- ErrGossiperShuttingDown + } + } +} + // Stop signals any active goroutines for a graceful closure. func (d *AuthenticatedGossiper) Stop() error { d.stopped.Do(func() { @@ -708,6 +842,8 @@ func (d *deDupedAnnouncements) reset() { // and the set of senders is updated to reflect which node sent us this // message. func (d *deDupedAnnouncements) addMsg(message networkMsg) { + log.Tracef("Adding network message: %v to batch", message.msg.MsgType()) + // Depending on the message type (channel announcement, channel update, // or node announcement), the message is added to the corresponding map // in deDupedAnnouncements. Because each identifying key can have at @@ -757,6 +893,10 @@ func (d *deDupedAnnouncements) addMsg(message networkMsg) { // If we already had this message with a strictly newer // timestamp, then we'll just discard the message we got. if oldTimestamp > msg.Timestamp { + log.Debugf("Ignored outdated network message: "+ + "peer=%v, source=%x, msg=%s, ", message.peer, + message.source.SerializeCompressed(), + msg.MsgType()) return } @@ -976,6 +1116,9 @@ func (d *AuthenticatedGossiper) networkHandler() { // sub-systems below us, then craft, sign, and broadcast a new // ChannelUpdate for the set of affected clients. case policyUpdate := <-d.chanPolicyUpdates: + log.Tracef("Received channel %d policy update requests", + len(policyUpdate.edgesToUpdate)) + // First, we'll now create new fully signed updates for // the affected channels and also update the underlying // graph with the new state. @@ -995,6 +1138,13 @@ func (d *AuthenticatedGossiper) networkHandler() { announcements.AddMsgs(newChanUpdates...) case announcement := <-d.networkMsgs: + log.Tracef("Received network message: "+ + "peer=%v, source=%x, msg=%s, is_remote=%v", + announcement.peer, + announcement.source.SerializeCompressed(), + announcement.msg.MsgType(), + announcement.isRemote) + // We should only broadcast this message forward if it // originated from us or it wasn't received as part of // our initial historical sync. @@ -1008,6 +1158,11 @@ func (d *AuthenticatedGossiper) networkHandler() { emittedAnnouncements, _ := d.processNetworkAnnouncement( announcement, ) + log.Debugf("Processed network message %s, "+ + "returned len(announcements)=%v", + announcement.msg.MsgType(), + len(emittedAnnouncements)) + if emittedAnnouncements != nil { announcements.AddMsgs( emittedAnnouncements..., @@ -1018,7 +1173,11 @@ func (d *AuthenticatedGossiper) networkHandler() { // If this message was recently rejected, then we won't // attempt to re-process it. - if d.isRecentlyRejectedMsg(announcement.msg) { + if announcement.isRemote && d.isRecentlyRejectedMsg( + announcement.msg, + sourceToPub(announcement.source), + ) { + announcement.err <- fmt.Errorf("recently " + "rejected") continue @@ -1041,8 +1200,12 @@ func (d *AuthenticatedGossiper) networkHandler() { announcement.msg, ) if err != nil { - if err != routing.ErrVBarrierShuttingDown && - err != routing.ErrParentValidationFailed { + if !routing.IsError( + err, + routing.ErrVBarrierShuttingDown, + routing.ErrParentValidationFailed, + ) { + log.Warnf("unexpected error "+ "during validation "+ "barrier shutdown: %v", @@ -1060,6 +1223,13 @@ func (d *AuthenticatedGossiper) networkHandler() { announcement, ) + log.Tracef("Processed network message %s, "+ + "returned len(announcements)=%v, "+ + "allowDependents=%v", + announcement.msg.MsgType(), + len(emittedAnnouncements), + allowDependents) + // If this message had any dependencies, then // we can now signal them to continue. validationBarrier.SignalDependants( @@ -1084,25 +1254,6 @@ func (d *AuthenticatedGossiper) networkHandler() { }() - // A new block has arrived, so we can re-process the previously - // premature announcements. - case newBlock, ok := <-d.blockEpochs.Epochs: - // If the channel has been closed, then this indicates - // the daemon is shutting down, so we exit ourselves. - if !ok { - return - } - - // Once a new block arrives, we update our running - // track of the height of the chain tip. - d.Lock() - blockHeight := uint32(newBlock.Height) - d.bestHeight = blockHeight - d.Unlock() - - log.Debugf("New block: height=%d, hash=%s", blockHeight, - newBlock.Hash) - // The trickle timer has ticked, which indicates we should // flush to the network the pending batch of new announcements // we've received since the last trickle tick. @@ -1184,22 +1335,23 @@ func (d *AuthenticatedGossiper) PruneSyncState(peer route.Vertex) { // isRecentlyRejectedMsg returns true if we recently rejected a message, and // false otherwise, This avoids expensive reprocessing of the message. -func (d *AuthenticatedGossiper) isRecentlyRejectedMsg(msg lnwire.Message) bool { - d.rejectMtx.RLock() - defer d.rejectMtx.RUnlock() +func (d *AuthenticatedGossiper) isRecentlyRejectedMsg(msg lnwire.Message, + peerPub [33]byte) bool { + var scid uint64 switch m := msg.(type) { case *lnwire.ChannelUpdate: - _, ok := d.recentRejects[m.ShortChannelID.ToUint64()] - return ok + scid = m.ShortChannelID.ToUint64() case *lnwire.ChannelAnnouncement: - _, ok := d.recentRejects[m.ShortChannelID.ToUint64()] - return ok + scid = m.ShortChannelID.ToUint64() default: return false } + + _, err := d.recentRejects.Get(newRejectCacheKey(scid, peerPub)) + return err != cache.ErrElementNotFound } // retransmitStaleAnns examines all outgoing channels that the source node is @@ -1527,6 +1679,64 @@ func (d *AuthenticatedGossiper) addNode(msg *lnwire.NodeAnnouncement, return d.cfg.Router.AddNode(node, op...) } +// isPremature decides whether a given network message has a block height+delta +// value specified in the future. If so, the message will be added to the +// future message map and be processed when the block height as reached. +// +// NOTE: must be used inside a lock. +func (d *AuthenticatedGossiper) isPremature(chanID lnwire.ShortChannelID, + delta uint32, msg *networkMsg) bool { + // TODO(roasbeef) make height delta 6 + // * or configurable + + msgHeight := chanID.BlockHeight + delta + + // The message height is smaller or equal to our best known height, + // thus the message is mature. + if msgHeight <= d.bestHeight { + return false + } + + // Add the premature message to our future messages which will + // be resent once the block height has reached. + // + // Init an empty cached message and overwrite it if there are cached + // messages found. + cachedMsgs := &cachedNetworkMsg{ + msgs: make([]*networkMsg, 0), + } + + result, err := d.futureMsgs.Get(msgHeight) + // No error returned means we have old messages cached. + if err == nil { + cachedMsgs = result.(*cachedNetworkMsg) + } + + // Copy the networkMsgs since the old message's err chan will + // be consumed. + copied := &networkMsg{ + peer: msg.peer, + source: msg.source, + msg: msg.msg, + optionalMsgFields: msg.optionalMsgFields, + isRemote: msg.isRemote, + err: make(chan error, 1), + } + + // Add the network message. + cachedMsgs.msgs = append(cachedMsgs.msgs, copied) + _, err = d.futureMsgs.Put(msgHeight, cachedMsgs) + if err != nil { + log.Errorf("Adding future message got error: %v", err) + } + + log.Debugf("Network message: %v added to future messages for "+ + "msgHeight=%d, bestHeight=%d", msg.msg.MsgType(), + msgHeight, d.bestHeight) + + return true +} + // processNetworkAnnouncement processes a new network relate authenticated // channel or node announcement or announcements proofs. If the announcement // didn't affect the internal state due to either being out of date, invalid, @@ -1537,11 +1747,9 @@ func (d *AuthenticatedGossiper) addNode(msg *lnwire.NodeAnnouncement, func (d *AuthenticatedGossiper) processNetworkAnnouncement( nMsg *networkMsg) ([]networkMsg, bool) { - isPremature := func(chanID lnwire.ShortChannelID, delta uint32) bool { - // TODO(roasbeef) make height delta 6 - // * or configurable - return chanID.BlockHeight+delta > d.bestHeight - } + log.Debugf("Processing network message: peer=%v, source=%x, msg=%s, "+ + "is_remote=%v", nMsg.peer, nMsg.source.SerializeCompressed(), + nMsg.msg.MsgType(), nMsg.isRemote) // If this is a remote update, we set the scheduler option to lazily // add it to the graph. @@ -1550,1153 +1758,1209 @@ func (d *AuthenticatedGossiper) processNetworkAnnouncement( schedulerOp = append(schedulerOp, batch.LazyAdd()) } - var announcements []networkMsg - switch msg := nMsg.msg.(type) { - // A new node announcement has arrived which either presents new // information about a node in one of the channels we know about, or a // updating previously advertised information. case *lnwire.NodeAnnouncement: - timestamp := time.Unix(int64(msg.Timestamp), 0) - - // We'll quickly ask the router if it already has a - // newer update for this node so we can skip validating - // signatures if not required. - if d.cfg.Router.IsStaleNode(msg.NodeID, timestamp) { - nMsg.err <- nil - return nil, true - } - - if err := d.addNode(msg, schedulerOp...); err != nil { - if routing.IsError(err, routing.ErrOutdated, - routing.ErrIgnored) { - - log.Debug(err) - } else if err != routing.ErrVBarrierShuttingDown { - log.Error(err) - } - - nMsg.err <- err - return nil, false - } - - // In order to ensure we don't leak unadvertised nodes, we'll - // make a quick check to ensure this node intends to publicly - // advertise itself to the network. - isPublic, err := d.cfg.Router.IsPublicNode(msg.NodeID) - if err != nil { - log.Errorf("Unable to determine if node %x is "+ - "advertised: %v", msg.NodeID, err) - nMsg.err <- err - return nil, false - } - - // If it does, we'll add their announcement to our batch so that - // it can be broadcast to the rest of our peers. - if isPublic { - announcements = append(announcements, networkMsg{ - peer: nMsg.peer, - source: nMsg.source, - msg: msg, - }) - } else { - log.Tracef("Skipping broadcasting node announcement "+ - "for %x due to being unadvertised", msg.NodeID) - } - - nMsg.err <- nil - // TODO(roasbeef): get rid of the above - return announcements, true + return d.handleNodeAnnouncement(nMsg, msg, schedulerOp) // A new channel announcement has arrived, this indicates the // *creation* of a new channel within the network. This only advertises // the existence of a channel and not yet the routing policies in // either direction of the channel. case *lnwire.ChannelAnnouncement: - // We'll ignore any channel announcements that target any chain - // other than the set of chains we know of. - if !bytes.Equal(msg.ChainHash[:], d.cfg.ChainHash[:]) { - err := fmt.Errorf("ignoring ChannelAnnouncement from "+ - "chain=%v, gossiper on chain=%v", msg.ChainHash, - d.cfg.ChainHash) - log.Errorf(err.Error()) - - d.rejectMtx.Lock() - d.recentRejects[msg.ShortChannelID.ToUint64()] = struct{}{} - d.rejectMtx.Unlock() - - nMsg.err <- err - return nil, false - } + return d.handleChanAnnouncement(nMsg, msg, schedulerOp) - // If the advertised inclusionary block is beyond our knowledge - // of the chain tip, then we'll ignore for it now. - d.Lock() - if nMsg.isRemote && isPremature(msg.ShortChannelID, 0) { - log.Infof("Announcement for chan_id=(%v), is "+ - "premature: advertises height %v, only "+ - "height %v is known", - msg.ShortChannelID.ToUint64(), - msg.ShortChannelID.BlockHeight, - d.bestHeight) - d.Unlock() - nMsg.err <- nil - return nil, false - } - d.Unlock() + // A new authenticated channel edge update has arrived. This indicates + // that the directional information for an already known channel has + // been updated. + case *lnwire.ChannelUpdate: + return d.handleChanUpdate(nMsg, msg, schedulerOp) - // At this point, we'll now ask the router if this is a - // zombie/known edge. If so we can skip all the processing - // below. - if d.cfg.Router.IsKnownEdge(msg.ShortChannelID) { - nMsg.err <- nil - return nil, true - } + // A new signature announcement has been received. This indicates + // willingness of nodes involved in the funding of a channel to + // announce this new channel to the rest of the world. + case *lnwire.AnnounceSignatures: + return d.handleAnnSig(nMsg, msg) - // If this is a remote channel announcement, then we'll validate - // all the signatures within the proof as it should be well - // formed. - var proof *channeldb.ChannelAuthProof - if nMsg.isRemote { - if err := routing.ValidateChannelAnn(msg); err != nil { - err := fmt.Errorf("unable to validate "+ - "announcement: %v", err) - d.rejectMtx.Lock() - d.recentRejects[msg.ShortChannelID.ToUint64()] = struct{}{} - d.rejectMtx.Unlock() - - log.Error(err) - nMsg.err <- err - return nil, false - } + default: + err := errors.New("wrong type of the announcement") + nMsg.err <- err + return nil, false + } +} - // If the proof checks out, then we'll save the proof - // itself to the database so we can fetch it later when - // gossiping with other nodes. - proof = &channeldb.ChannelAuthProof{ - NodeSig1Bytes: msg.NodeSig1.ToSignatureBytes(), - NodeSig2Bytes: msg.NodeSig2.ToSignatureBytes(), - BitcoinSig1Bytes: msg.BitcoinSig1.ToSignatureBytes(), - BitcoinSig2Bytes: msg.BitcoinSig2.ToSignatureBytes(), - } - } +// processZombieUpdate determines whether the provided channel update should +// resurrect a given zombie edge. +func (d *AuthenticatedGossiper) processZombieUpdate( + chanInfo *channeldb.ChannelEdgeInfo, msg *lnwire.ChannelUpdate) error { - // With the proof validate (if necessary), we can now store it - // within the database for our path finding and syncing needs. - var featureBuf bytes.Buffer - if err := msg.Features.Encode(&featureBuf); err != nil { - log.Errorf("unable to encode features: %v", err) - nMsg.err <- err - return nil, false - } + // The least-significant bit in the flag on the channel update tells us + // which edge is being updated. + isNode1 := msg.ChannelFlags&lnwire.ChanUpdateDirection == 0 - edge := &channeldb.ChannelEdgeInfo{ - ChannelID: msg.ShortChannelID.ToUint64(), - ChainHash: msg.ChainHash, - NodeKey1Bytes: msg.NodeID1, - NodeKey2Bytes: msg.NodeID2, - BitcoinKey1Bytes: msg.BitcoinKey1, - BitcoinKey2Bytes: msg.BitcoinKey2, - AuthProof: proof, - Features: featureBuf.Bytes(), - ExtraOpaqueData: msg.ExtraOpaqueData, - } + // Since we've deemed the update as not stale above, before marking it + // live, we'll make sure it has been signed by the correct party. If we + // have both pubkeys, either party can resurect the channel. If we've + // already marked this with the stricter, single-sided resurrection we + // will only have the pubkey of the node with the oldest timestamp. + var pubKey *btcec.PublicKey + switch { + case isNode1 && chanInfo.NodeKey1Bytes != emptyPubkey: + pubKey, _ = chanInfo.NodeKey1() + case !isNode1 && chanInfo.NodeKey2Bytes != emptyPubkey: + pubKey, _ = chanInfo.NodeKey2() + } + if pubKey == nil { + return fmt.Errorf("incorrect pubkey to resurrect zombie "+ + "with chan_id=%v", msg.ShortChannelID) + } - // If there were any optional message fields provided, we'll - // include them in its serialized disk representation now. - if nMsg.optionalMsgFields != nil { - if nMsg.optionalMsgFields.capacity != nil { - edge.Capacity = *nMsg.optionalMsgFields.capacity - } - if nMsg.optionalMsgFields.channelPoint != nil { - edge.ChannelPoint = *nMsg.optionalMsgFields.channelPoint - } - } + err := routing.VerifyChannelUpdateSignature(msg, pubKey) + if err != nil { + return fmt.Errorf("unable to verify channel "+ + "update signature: %v", err) + } - // We will add the edge to the channel router. If the nodes - // present in this channel are not present in the database, a - // partial node will be added to represent each node while we - // wait for a node announcement. - // - // Before we add the edge to the database, we obtain - // the mutex for this channel ID. We do this to ensure - // no other goroutine has read the database and is now - // making decisions based on this DB state, before it - // writes to the DB. - d.channelMtx.Lock(msg.ShortChannelID.ToUint64()) - defer d.channelMtx.Unlock(msg.ShortChannelID.ToUint64()) - if err := d.cfg.Router.AddEdge(edge, schedulerOp...); err != nil { - // If the edge was rejected due to already being known, - // then it may be that case that this new message has a - // fresh channel proof, so we'll check. - if routing.IsError(err, routing.ErrIgnored) { - // Attempt to process the rejected message to - // see if we get any new announcements. - anns, rErr := d.processRejectedEdge(msg, proof) - if rErr != nil { - d.rejectMtx.Lock() - d.recentRejects[msg.ShortChannelID.ToUint64()] = struct{}{} - d.rejectMtx.Unlock() - nMsg.err <- rErr - return nil, false - } + // With the signature valid, we'll proceed to mark the + // edge as live and wait for the channel announcement to + // come through again. + err = d.cfg.Router.MarkEdgeLive(msg.ShortChannelID) + if err != nil { + return fmt.Errorf("unable to remove edge with "+ + "chan_id=%v from zombie index: %v", + msg.ShortChannelID, err) + } - // If while processing this rejected edge, we - // realized there's a set of announcements we - // could extract, then we'll return those - // directly. - if len(anns) != 0 { - nMsg.err <- nil - return anns, true - } + log.Debugf("Removed edge with chan_id=%v from zombie "+ + "index", msg.ShortChannelID) - // Otherwise, this is just a regular rejected - // edge. - log.Debugf("Router rejected channel "+ - "edge: %v", err) - } else { - log.Tracef("Router rejected channel "+ - "edge: %v", err) - - d.rejectMtx.Lock() - d.recentRejects[msg.ShortChannelID.ToUint64()] = struct{}{} - d.rejectMtx.Unlock() - } + return nil +} - nMsg.err <- err - return nil, false - } +// fetchNodeAnn fetches the latest signed node announcement from our point of +// view for the node with the given public key. +func (d *AuthenticatedGossiper) fetchNodeAnn( + pubKey [33]byte) (*lnwire.NodeAnnouncement, error) { - // If we earlier received any ChannelUpdates for this channel, - // we can now process them, as the channel is added to the - // graph. - shortChanID := msg.ShortChannelID.ToUint64() - var channelUpdates []*networkMsg + node, err := d.cfg.Router.FetchLightningNode(pubKey) + if err != nil { + return nil, err + } - d.pChanUpdMtx.Lock() - channelUpdates = append(channelUpdates, d.prematureChannelUpdates[shortChanID]...) + return node.NodeAnnouncement(true) +} - // Now delete the premature ChannelUpdates, since we added them - // all to the queue of network messages. - delete(d.prematureChannelUpdates, shortChanID) - d.pChanUpdMtx.Unlock() +// isMsgStale determines whether a message retrieved from the backing +// MessageStore is seen as stale by the current graph. +func (d *AuthenticatedGossiper) isMsgStale(msg lnwire.Message) bool { + switch msg := msg.(type) { + case *lnwire.AnnounceSignatures: + chanInfo, _, _, err := d.cfg.Router.GetChannelByID( + msg.ShortChannelID, + ) - // Launch a new goroutine to handle each ChannelUpdate, this to - // ensure we don't block here, as we can handle only one - // announcement at a time. - for _, cu := range channelUpdates { - d.wg.Add(1) - go func(nMsg *networkMsg) { - defer d.wg.Done() + // If the channel cannot be found, it is most likely a leftover + // message for a channel that was closed, so we can consider it + // stale. + if err == channeldb.ErrEdgeNotFound { + return true + } + if err != nil { + log.Debugf("Unable to retrieve channel=%v from graph: "+ + "%v", err) + return false + } - switch msg := nMsg.msg.(type) { + // If the proof exists in the graph, then we have successfully + // received the remote proof and assembled the full proof, so we + // can safely delete the local proof from the database. + return chanInfo.AuthProof != nil - // Reprocess the message, making sure we return - // an error to the original caller in case the - // gossiper shuts down. - case *lnwire.ChannelUpdate: - log.Debugf("Reprocessing"+ - " ChannelUpdate for "+ - "shortChanID=%v", - msg.ShortChannelID.ToUint64()) + case *lnwire.ChannelUpdate: + _, p1, p2, err := d.cfg.Router.GetChannelByID(msg.ShortChannelID) - select { - case d.networkMsgs <- nMsg: - case <-d.quit: - nMsg.err <- ErrGossiperShuttingDown - } - - // We don't expect any other message type than - // ChannelUpdate to be in this map. - default: - log.Errorf("Unsupported message type "+ - "found among ChannelUpdates: "+ - "%T", msg) - } - }(cu) + // If the channel cannot be found, it is most likely a leftover + // message for a channel that was closed, so we can consider it + // stale. + if err == channeldb.ErrEdgeNotFound { + return true } - - // Channel announcement was successfully proceeded and know it - // might be broadcast to other connected nodes if it was - // announcement with proof (remote). - if proof != nil { - announcements = append(announcements, networkMsg{ - peer: nMsg.peer, - source: nMsg.source, - msg: msg, - }) + if err != nil { + log.Debugf("Unable to retrieve channel=%v from graph: "+ + "%v", msg.ShortChannelID, err) + return false } - nMsg.err <- nil - return announcements, true - - // A new authenticated channel edge update has arrived. This indicates - // that the directional information for an already known channel has - // been updated. - case *lnwire.ChannelUpdate: - // We'll ignore any channel announcements that target any chain - // other than the set of chains we know of. - if !bytes.Equal(msg.ChainHash[:], d.cfg.ChainHash[:]) { - err := fmt.Errorf("ignoring ChannelUpdate from "+ - "chain=%v, gossiper on chain=%v", msg.ChainHash, - d.cfg.ChainHash) - log.Errorf(err.Error()) - - d.rejectMtx.Lock() - d.recentRejects[msg.ShortChannelID.ToUint64()] = struct{}{} - d.rejectMtx.Unlock() - - nMsg.err <- err - return nil, false + // Otherwise, we'll retrieve the correct policy that we + // currently have stored within our graph to check if this + // message is stale by comparing its timestamp. + var p *channeldb.ChannelEdgePolicy + if msg.ChannelFlags&lnwire.ChanUpdateDirection == 0 { + p = p1 + } else { + p = p2 } - blockHeight := msg.ShortChannelID.BlockHeight - shortChanID := msg.ShortChannelID.ToUint64() - - // If the advertised inclusionary block is beyond our knowledge - // of the chain tip, then we'll put the announcement in limbo - // to be fully verified once we advance forward in the chain. - d.Lock() - if nMsg.isRemote && isPremature(msg.ShortChannelID, 0) { - log.Infof("Update announcement for "+ - "short_chan_id(%v), is premature: advertises "+ - "height %v, only height %v is known", - shortChanID, blockHeight, - d.bestHeight) - d.Unlock() - nMsg.err <- nil - return nil, false + // If the policy is still unknown, then we can consider this + // policy fresh. + if p == nil { + return false } - d.Unlock() - // Before we perform any of the expensive checks below, we'll - // check whether this update is stale or is for a zombie - // channel in order to quickly reject it. timestamp := time.Unix(int64(msg.Timestamp), 0) - if d.cfg.Router.IsStaleEdgePolicy( - msg.ShortChannelID, timestamp, msg.ChannelFlags, - ) { - nMsg.err <- nil - return nil, true - } + return p.LastUpdate.After(timestamp) - // Get the node pub key as far as we don't have it in channel - // update announcement message. We'll need this to properly - // verify message signature. - // - // We make sure to obtain the mutex for this channel ID - // before we access the database. This ensures the state - // we read from the database has not changed between this - // point and when we call UpdateEdge() later. - d.channelMtx.Lock(msg.ShortChannelID.ToUint64()) - defer d.channelMtx.Unlock(msg.ShortChannelID.ToUint64()) - chanInfo, edge1, edge2, err := d.cfg.Router.GetChannelByID(msg.ShortChannelID) - switch err { - // No error, break. - case nil: - break - - case channeldb.ErrZombieEdge: - err = d.processZombieUpdate(chanInfo, msg) - if err != nil { - log.Debug(err) - nMsg.err <- err - return nil, false - } + default: + // We'll make sure to not mark any unsupported messages as stale + // to ensure they are not removed. + return false + } +} - // We'll fallthrough to ensure we stash the update until - // we receive its corresponding ChannelAnnouncement. - // This is needed to ensure the edge exists in the graph - // before applying the update. - fallthrough - case channeldb.ErrGraphNotFound: - fallthrough - case channeldb.ErrGraphNoEdgesFound: - fallthrough - case channeldb.ErrEdgeNotFound: - // If the edge corresponding to this ChannelUpdate was - // not found in the graph, this might be a channel in - // the process of being opened, and we haven't processed - // our own ChannelAnnouncement yet, hence it is not - // found in the graph. This usually gets resolved after - // the channel proofs are exchanged and the channel is - // broadcasted to the rest of the network, but in case - // this is a private channel this won't ever happen. - // This can also happen in the case of a zombie channel - // with a fresh update for which we don't have a - // ChannelAnnouncement for since we reject them. Because - // of this, we temporarily add it to a map, and - // reprocess it after our own ChannelAnnouncement has - // been processed. - d.pChanUpdMtx.Lock() - d.prematureChannelUpdates[shortChanID] = append( - d.prematureChannelUpdates[shortChanID], nMsg, - ) - d.pChanUpdMtx.Unlock() +// updateChannel creates a new fully signed update for the channel, and updates +// the underlying graph with the new state. +func (d *AuthenticatedGossiper) updateChannel(info *channeldb.ChannelEdgeInfo, + edge *channeldb.ChannelEdgePolicy) (*lnwire.ChannelAnnouncement, + *lnwire.ChannelUpdate, error) { - log.Debugf("Got ChannelUpdate for edge not found in "+ - "graph(shortChanID=%v), saving for "+ - "reprocessing later", shortChanID) + // Parse the unsigned edge into a channel update. + chanUpdate := netann.UnsignedChannelUpdateFromEdge(info, edge) - // NOTE: We don't return anything on the error channel - // for this message, as we expect that will be done when - // this ChannelUpdate is later reprocessed. - return nil, false + // We'll generate a new signature over a digest of the channel + // announcement itself and update the timestamp to ensure it propagate. + err := netann.SignChannelUpdate( + d.cfg.AnnSigner, d.selfKeyLoc, chanUpdate, + netann.ChanUpdSetTimestamp, + ) + if err != nil { + return nil, nil, err + } - default: - err := fmt.Errorf("unable to validate channel update "+ - "short_chan_id=%v: %v", shortChanID, err) - log.Error(err) - nMsg.err <- err + // Next, we'll set the new signature in place, and update the reference + // in the backing slice. + edge.LastUpdate = time.Unix(int64(chanUpdate.Timestamp), 0) + edge.SigBytes = chanUpdate.Signature.ToSignatureBytes() - d.rejectMtx.Lock() - d.recentRejects[msg.ShortChannelID.ToUint64()] = struct{}{} - d.rejectMtx.Unlock() - return nil, false - } + // To ensure that our signature is valid, we'll verify it ourself + // before committing it to the slice returned. + err = routing.ValidateChannelUpdateAnn(d.selfKey, info.Capacity, chanUpdate) + if err != nil { + return nil, nil, fmt.Errorf("generated invalid channel "+ + "update sig: %v", err) + } - // The least-significant bit in the flag on the channel update - // announcement tells us "which" side of the channels directed - // edge is being updated. - var ( - pubKey *btcec.PublicKey - edgeToUpdate *channeldb.ChannelEdgePolicy + // Finally, we'll write the new edge policy to disk. + if err := d.cfg.Router.UpdateEdge(edge); err != nil { + return nil, nil, err + } + + // We'll also create the original channel announcement so the two can + // be broadcast along side each other (if necessary), but only if we + // have a full channel announcement for this channel. + var chanAnn *lnwire.ChannelAnnouncement + if info.AuthProof != nil { + chanID := lnwire.NewShortChanIDFromInt(info.ChannelID) + chanAnn = &lnwire.ChannelAnnouncement{ + ShortChannelID: chanID, + NodeID1: info.NodeKey1Bytes, + NodeID2: info.NodeKey2Bytes, + ChainHash: info.ChainHash, + BitcoinKey1: info.BitcoinKey1Bytes, + Features: lnwire.NewRawFeatureVector(), + BitcoinKey2: info.BitcoinKey2Bytes, + ExtraOpaqueData: edge.ExtraOpaqueData, + } + chanAnn.NodeSig1, err = lnwire.NewSigFromRawSignature( + info.AuthProof.NodeSig1Bytes, ) - direction := msg.ChannelFlags & lnwire.ChanUpdateDirection - switch direction { - case 0: - pubKey, _ = chanInfo.NodeKey1() - edgeToUpdate = edge1 - case 1: - pubKey, _ = chanInfo.NodeKey2() - edgeToUpdate = edge2 + if err != nil { + return nil, nil, err } - - // If we have a previous version of the edge being updated, - // we'll want to rate limit its updates to prevent spam - // throughout the network. - if nMsg.isRemote && edgeToUpdate != nil { - // If it's a keep-alive update, we'll only propagate one - // if it's been a day since the previous. This follows - // our own heuristic of sending keep-alive updates after - // the same duration (see retransmitStaleAnns). - timeSinceLastUpdate := timestamp.Sub(edgeToUpdate.LastUpdate) - if IsKeepAliveUpdate(msg, edgeToUpdate) { - if timeSinceLastUpdate < d.cfg.RebroadcastInterval { - log.Debugf("Ignoring keep alive update "+ - "not within %v period for "+ - "channel %v", - d.cfg.RebroadcastInterval, - shortChanID) - nMsg.err <- nil - return nil, false - } - } else { - // If it's not, we'll allow an update per minute - // with a maximum burst of 10. If we haven't - // seen an update for this channel before, we'll - // need to initialize a rate limiter for each - // direction. - d.Lock() - rateLimiters, ok := d.chanUpdateRateLimiter[shortChanID] - if !ok { - r := rate.Every(d.cfg.ChannelUpdateInterval) - b := d.cfg.MaxChannelUpdateBurst - rateLimiters = [2]*rate.Limiter{ - rate.NewLimiter(r, b), - rate.NewLimiter(r, b), - } - d.chanUpdateRateLimiter[shortChanID] = rateLimiters - } - d.Unlock() - - if !rateLimiters[direction].Allow() { - log.Debugf("Rate limiting update for "+ - "channel %v from direction %x", - shortChanID, - pubKey.SerializeCompressed()) - nMsg.err <- nil - return nil, false - } - } + chanAnn.NodeSig2, err = lnwire.NewSigFromRawSignature( + info.AuthProof.NodeSig2Bytes, + ) + if err != nil { + return nil, nil, err } - - // Validate the channel announcement with the expected public key and - // channel capacity. In the case of an invalid channel update, we'll - // return an error to the caller and exit early. - err = routing.ValidateChannelUpdateAnn(pubKey, chanInfo.Capacity, msg) + chanAnn.BitcoinSig1, err = lnwire.NewSigFromRawSignature( + info.AuthProof.BitcoinSig1Bytes, + ) if err != nil { - rErr := fmt.Errorf("unable to validate channel "+ - "update announcement for short_chan_id=%v: %v", - spew.Sdump(msg.ShortChannelID), err) - - log.Error(rErr) - nMsg.err <- rErr - return nil, false + return nil, nil, err } - - update := &channeldb.ChannelEdgePolicy{ - SigBytes: msg.Signature.ToSignatureBytes(), - ChannelID: shortChanID, - LastUpdate: timestamp, - MessageFlags: msg.MessageFlags, - ChannelFlags: msg.ChannelFlags, - TimeLockDelta: msg.TimeLockDelta, - MinHTLC: msg.HtlcMinimumMsat, - MaxHTLC: msg.HtlcMaximumMsat, - FeeBaseMSat: lnwire.MilliSatoshi(msg.BaseFee), - FeeProportionalMillionths: lnwire.MilliSatoshi(msg.FeeRate), - ExtraOpaqueData: msg.ExtraOpaqueData, + chanAnn.BitcoinSig2, err = lnwire.NewSigFromRawSignature( + info.AuthProof.BitcoinSig2Bytes, + ) + if err != nil { + return nil, nil, err } + } - if err := d.cfg.Router.UpdateEdge(update, schedulerOp...); err != nil { - if routing.IsError(err, routing.ErrOutdated, - routing.ErrIgnored) { - log.Debug(err) - } else if err != routing.ErrVBarrierShuttingDown { - d.rejectMtx.Lock() - d.recentRejects[msg.ShortChannelID.ToUint64()] = struct{}{} - d.rejectMtx.Unlock() - log.Error(err) - } + return chanAnn, chanUpdate, err +} - nMsg.err <- err - return nil, false - } +// SyncManager returns the gossiper's SyncManager instance. +func (d *AuthenticatedGossiper) SyncManager() *SyncManager { + return d.syncMgr +} - // If this is a local ChannelUpdate without an AuthProof, it - // means it is an update to a channel that is not (yet) - // supposed to be announced to the greater network. However, - // our channel counter party will need to be given the update, - // so we'll try sending the update directly to the remote peer. - if !nMsg.isRemote && chanInfo.AuthProof == nil { - // Get our peer's public key. - remotePubKey := remotePubFromChanInfo( - chanInfo, msg.ChannelFlags, - ) +// IsKeepAliveUpdate determines whether this channel update is considered a +// keep-alive update based on the previous channel update processed for the same +// direction. +func IsKeepAliveUpdate(update *lnwire.ChannelUpdate, + prev *channeldb.ChannelEdgePolicy) bool { - // Now, we'll attempt to send the channel update message - // reliably to the remote peer in the background, so - // that we don't block if the peer happens to be offline - // at the moment. - err := d.reliableSender.sendMessage(msg, remotePubKey) - if err != nil { - err := fmt.Errorf("unable to reliably send %v "+ - "for channel=%v to peer=%x: %v", - msg.MsgType(), msg.ShortChannelID, - remotePubKey, err) - nMsg.err <- err - return nil, false - } - } + // Both updates should be from the same direction. + if update.ChannelFlags&lnwire.ChanUpdateDirection != + prev.ChannelFlags&lnwire.ChanUpdateDirection { - // Channel update announcement was successfully processed and - // now it can be broadcast to the rest of the network. However, - // we'll only broadcast the channel update announcement if it - // has an attached authentication proof. - if chanInfo.AuthProof != nil { - announcements = append(announcements, networkMsg{ - peer: nMsg.peer, - source: nMsg.source, - msg: msg, - }) - } + return false + } - nMsg.err <- nil - return announcements, true + // The timestamp should always increase for a keep-alive update. + timestamp := time.Unix(int64(update.Timestamp), 0) + if !timestamp.After(prev.LastUpdate) { + return false + } - // A new signature announcement has been received. This indicates - // willingness of nodes involved in the funding of a channel to - // announce this new channel to the rest of the world. - case *lnwire.AnnounceSignatures: - needBlockHeight := msg.ShortChannelID.BlockHeight + - d.cfg.ProofMatureDelta - shortChanID := msg.ShortChannelID.ToUint64() + // None of the remaining fields should change for a keep-alive update. + if update.ChannelFlags.IsDisabled() != prev.ChannelFlags.IsDisabled() { + return false + } + if lnwire.MilliSatoshi(update.BaseFee) != prev.FeeBaseMSat { + return false + } + if lnwire.MilliSatoshi(update.FeeRate) != prev.FeeProportionalMillionths { + return false + } + if update.TimeLockDelta != prev.TimeLockDelta { + return false + } + if update.HtlcMinimumMsat != prev.MinHTLC { + return false + } + if update.MessageFlags.HasMaxHtlc() && !prev.MessageFlags.HasMaxHtlc() { + return false + } + if update.HtlcMaximumMsat != prev.MaxHTLC { + return false + } + if !bytes.Equal(update.ExtraOpaqueData, prev.ExtraOpaqueData) { + return false + } + return true +} - prefix := "local" - if nMsg.isRemote { - prefix = "remote" - } +// latestHeight returns the gossiper's latest height known of the chain. +func (d *AuthenticatedGossiper) latestHeight() uint32 { + d.Lock() + defer d.Unlock() + return d.bestHeight +} - log.Infof("Received new %v channel announcement for %v", prefix, - msg.ShortChannelID) - - // By the specification, channel announcement proofs should be - // sent after some number of confirmations after channel was - // registered in bitcoin blockchain. Therefore, we check if the - // proof is premature. - d.Lock() - if isPremature(msg.ShortChannelID, d.cfg.ProofMatureDelta) { - log.Infof("Premature proof announcement, current "+ - "block height lower than needed: %v < %v", - d.bestHeight, needBlockHeight) - d.Unlock() - nMsg.err <- nil - return nil, false - } - d.Unlock() +// handleNodeAnnouncement processes a new node announcement. +func (d *AuthenticatedGossiper) handleNodeAnnouncement(nMsg *networkMsg, + nodeAnn *lnwire.NodeAnnouncement, + ops []batch.SchedulerOption) ([]networkMsg, bool) { - // Ensure that we know of a channel with the target channel ID - // before proceeding further. - // - // We must acquire the mutex for this channel ID before getting - // the channel from the database, to ensure what we read does - // not change before we call AddProof() later. - d.channelMtx.Lock(msg.ShortChannelID.ToUint64()) - defer d.channelMtx.Unlock(msg.ShortChannelID.ToUint64()) - - chanInfo, e1, e2, err := d.cfg.Router.GetChannelByID( - msg.ShortChannelID) - if err != nil { - // TODO(andrew.shvv) this is dangerous because remote - // node might rewrite the waiting proof. - proof := channeldb.NewWaitingProof(nMsg.isRemote, msg) - err := d.cfg.WaitingProofStore.Add(proof) - if err != nil { - err := fmt.Errorf("unable to store "+ - "the proof for short_chan_id=%v: %v", - shortChanID, err) - log.Error(err) - nMsg.err <- err - return nil, false - } + timestamp := time.Unix(int64(nodeAnn.Timestamp), 0) - log.Infof("Orphan %v proof announcement with "+ - "short_chan_id=%v, adding "+ - "to waiting batch", prefix, shortChanID) - nMsg.err <- nil - return nil, false - } + // We'll quickly ask the router if it already has a newer update for + // this node so we can skip validating signatures if not required. + if d.cfg.Router.IsStaleNode(nodeAnn.NodeID, timestamp) { + log.Debugf("Skipped processing stale node: %x", nodeAnn.NodeID) + nMsg.err <- nil + return nil, true + } - nodeID := nMsg.source.SerializeCompressed() - isFirstNode := bytes.Equal(nodeID, chanInfo.NodeKey1Bytes[:]) - isSecondNode := bytes.Equal(nodeID, chanInfo.NodeKey2Bytes[:]) + if err := d.addNode(nodeAnn, ops...); err != nil { + log.Debugf("Adding node: %x got error: %v", nodeAnn.NodeID, + err) + + if !routing.IsError( + err, + routing.ErrOutdated, + routing.ErrIgnored, + routing.ErrVBarrierShuttingDown, + ) { - // Ensure that channel that was retrieved belongs to the peer - // which sent the proof announcement. - if !(isFirstNode || isSecondNode) { - err := fmt.Errorf("channel that was received not "+ - "belongs to the peer which sent the proof, "+ - "short_chan_id=%v", shortChanID) log.Error(err) - nMsg.err <- err - return nil, false } - // If proof was sent by a local sub-system, then we'll - // send the announcement signature to the remote node - // so they can also reconstruct the full channel - // announcement. - if !nMsg.isRemote { - var remotePubKey [33]byte - if isFirstNode { - remotePubKey = chanInfo.NodeKey2Bytes - } else { - remotePubKey = chanInfo.NodeKey1Bytes - } - // Since the remote peer might not be online - // we'll call a method that will attempt to - // deliver the proof when it comes online. - err := d.reliableSender.sendMessage(msg, remotePubKey) - if err != nil { - err := fmt.Errorf("unable to reliably send %v "+ - "for channel=%v to peer=%x: %v", - msg.MsgType(), msg.ShortChannelID, - remotePubKey, err) - nMsg.err <- err - return nil, false - } - } + nMsg.err <- err + return nil, false + } - // Check if we already have the full proof for this channel. - if chanInfo.AuthProof != nil { - // If we already have the fully assembled proof, then - // the peer sending us their proof has probably not - // received our local proof yet. So be kind and send - // them the full proof. - if nMsg.isRemote { - peerID := nMsg.source.SerializeCompressed() - log.Debugf("Got AnnounceSignatures for " + - "channel with full proof.") - - d.wg.Add(1) - go func() { - defer d.wg.Done() - log.Debugf("Received half proof for "+ - "channel %v with existing "+ - "full proof. Sending full "+ - "proof to peer=%x", - msg.ChannelID, - peerID) - - chanAnn, _, _, err := netann.CreateChanAnnouncement( - chanInfo.AuthProof, chanInfo, - e1, e2, - ) - if err != nil { - log.Errorf("unable to gen "+ - "ann: %v", err) - return - } - err = nMsg.peer.SendMessage( - false, chanAnn, - ) - if err != nil { - log.Errorf("Failed sending "+ - "full proof to "+ - "peer=%x: %v", - peerID, err) - return - } - log.Debugf("Full proof sent to peer=%x"+ - " for chanID=%v", peerID, - msg.ChannelID) - }() - } + // In order to ensure we don't leak unadvertised nodes, we'll make a + // quick check to ensure this node intends to publicly advertise itself + // to the network. + isPublic, err := d.cfg.Router.IsPublicNode(nodeAnn.NodeID) + if err != nil { + log.Errorf("Unable to determine if node %x is advertised: %v", + nodeAnn.NodeID, err) + nMsg.err <- err + return nil, false + } - log.Debugf("Already have proof for channel "+ - "with chanID=%v", msg.ChannelID) - nMsg.err <- nil - return nil, true - } + var announcements []networkMsg + + // If it does, we'll add their announcement to our batch so that it can + // be broadcast to the rest of our peers. + if isPublic { + announcements = append(announcements, networkMsg{ + peer: nMsg.peer, + source: nMsg.source, + msg: nodeAnn, + }) + } else { + log.Tracef("Skipping broadcasting node announcement for %x "+ + "due to being unadvertised", nodeAnn.NodeID) + } + + nMsg.err <- nil + // TODO(roasbeef): get rid of the above + return announcements, true +} - // Check that we received the opposite proof. If so, then we're - // now able to construct the full proof, and create the channel - // announcement. If we didn't receive the opposite half of the - // proof than we should store it this one, and wait for - // opposite to be received. - proof := channeldb.NewWaitingProof(nMsg.isRemote, msg) - oppositeProof, err := d.cfg.WaitingProofStore.Get( - proof.OppositeKey(), +// handleChanAnnouncement processes a new channel announcement. +func (d *AuthenticatedGossiper) handleChanAnnouncement(nMsg *networkMsg, + ann *lnwire.ChannelAnnouncement, + ops []batch.SchedulerOption) ([]networkMsg, bool) { + + // We'll ignore any channel announcements that target any chain other + // than the set of chains we know of. + if !bytes.Equal(ann.ChainHash[:], d.cfg.ChainHash[:]) { + err := fmt.Errorf("ignoring ChannelAnnouncement from chain=%v"+ + ", gossiper on chain=%v", ann.ChainHash, + d.cfg.ChainHash) + log.Errorf(err.Error()) + + key := newRejectCacheKey( + ann.ShortChannelID.ToUint64(), + sourceToPub(nMsg.source), ) - if err != nil && err != channeldb.ErrWaitingProofNotFound { - err := fmt.Errorf("unable to get "+ - "the opposite proof for short_chan_id=%v: %v", - shortChanID, err) - log.Error(err) - nMsg.err <- err - return nil, false - } + _, _ = d.recentRejects.Put(key, &cachedReject{}) - if err == channeldb.ErrWaitingProofNotFound { - err := d.cfg.WaitingProofStore.Add(proof) - if err != nil { - err := fmt.Errorf("unable to store "+ - "the proof for short_chan_id=%v: %v", - shortChanID, err) - log.Error(err) - nMsg.err <- err - return nil, false - } + nMsg.err <- err + return nil, false + } - log.Infof("1/2 of channel ann proof received for "+ - "short_chan_id=%v, waiting for other half", - shortChanID) + // If the advertised inclusionary block is beyond our knowledge of the + // chain tip, then we'll ignore it for now. + d.Lock() + if nMsg.isRemote && d.isPremature(ann.ShortChannelID, 0, nMsg) { + log.Warnf("Announcement for chan_id=(%v), is premature: "+ + "advertises height %v, only height %v is known", + ann.ShortChannelID.ToUint64(), + ann.ShortChannelID.BlockHeight, d.bestHeight) + d.Unlock() + nMsg.err <- nil + return nil, false + } + d.Unlock() - nMsg.err <- nil - return nil, false - } + // At this point, we'll now ask the router if this is a zombie/known + // edge. If so we can skip all the processing below. + if d.cfg.Router.IsKnownEdge(ann.ShortChannelID) { + nMsg.err <- nil + return nil, true + } - // We now have both halves of the channel announcement proof, - // then we'll reconstruct the initial announcement so we can - // validate it shortly below. - var dbProof channeldb.ChannelAuthProof - if isFirstNode { - dbProof.NodeSig1Bytes = msg.NodeSignature.ToSignatureBytes() - dbProof.NodeSig2Bytes = oppositeProof.NodeSignature.ToSignatureBytes() - dbProof.BitcoinSig1Bytes = msg.BitcoinSignature.ToSignatureBytes() - dbProof.BitcoinSig2Bytes = oppositeProof.BitcoinSignature.ToSignatureBytes() - } else { - dbProof.NodeSig1Bytes = oppositeProof.NodeSignature.ToSignatureBytes() - dbProof.NodeSig2Bytes = msg.NodeSignature.ToSignatureBytes() - dbProof.BitcoinSig1Bytes = oppositeProof.BitcoinSignature.ToSignatureBytes() - dbProof.BitcoinSig2Bytes = msg.BitcoinSignature.ToSignatureBytes() - } - chanAnn, e1Ann, e2Ann, err := netann.CreateChanAnnouncement( - &dbProof, chanInfo, e1, e2, - ) - if err != nil { - log.Error(err) - nMsg.err <- err - return nil, false - } + // If this is a remote channel announcement, then we'll validate all + // the signatures within the proof as it should be well formed. + var proof *channeldb.ChannelAuthProof + if nMsg.isRemote { + if err := routing.ValidateChannelAnn(ann); err != nil { + err := fmt.Errorf("unable to validate announcement: "+ + "%v", err) - // With all the necessary components assembled validate the - // full channel announcement proof. - if err := routing.ValidateChannelAnn(chanAnn); err != nil { - err := fmt.Errorf("channel announcement proof "+ - "for short_chan_id=%v isn't valid: %v", - shortChanID, err) + key := newRejectCacheKey( + ann.ShortChannelID.ToUint64(), + sourceToPub(nMsg.source), + ) + _, _ = d.recentRejects.Put(key, &cachedReject{}) log.Error(err) nMsg.err <- err return nil, false } - // If the channel was returned by the router it means that - // existence of funding point and inclusion of nodes bitcoin - // keys in it already checked by the router. In this stage we - // should check that node keys are attest to the bitcoin keys - // by validating the signatures of announcement. If proof is - // valid then we'll populate the channel edge with it, so we - // can announce it on peer connect. - err = d.cfg.Router.AddProof(msg.ShortChannelID, &dbProof) - if err != nil { - err := fmt.Errorf("unable add proof to the "+ - "channel chanID=%v: %v", msg.ChannelID, err) - log.Error(err) - nMsg.err <- err - return nil, false + // If the proof checks out, then we'll save the proof itself to + // the database so we can fetch it later when gossiping with + // other nodes. + proof = &channeldb.ChannelAuthProof{ + NodeSig1Bytes: ann.NodeSig1.ToSignatureBytes(), + NodeSig2Bytes: ann.NodeSig2.ToSignatureBytes(), + BitcoinSig1Bytes: ann.BitcoinSig1.ToSignatureBytes(), + BitcoinSig2Bytes: ann.BitcoinSig2.ToSignatureBytes(), } + } - err = d.cfg.WaitingProofStore.Remove(proof.OppositeKey()) - if err != nil { - err := fmt.Errorf("unable remove opposite proof "+ - "for the channel with chanID=%v: %v", - msg.ChannelID, err) - log.Error(err) - nMsg.err <- err - return nil, false - } + // With the proof validated (if necessary), we can now store it within + // the database for our path finding and syncing needs. + var featureBuf bytes.Buffer + if err := ann.Features.Encode(&featureBuf); err != nil { + log.Errorf("unable to encode features: %v", err) + nMsg.err <- err + return nil, false + } - // Proof was successfully created and now can announce the - // channel to the remain network. - log.Infof("Fully valid channel proof for short_chan_id=%v "+ - "constructed, adding to next ann batch", - shortChanID) + edge := &channeldb.ChannelEdgeInfo{ + ChannelID: ann.ShortChannelID.ToUint64(), + ChainHash: ann.ChainHash, + NodeKey1Bytes: ann.NodeID1, + NodeKey2Bytes: ann.NodeID2, + BitcoinKey1Bytes: ann.BitcoinKey1, + BitcoinKey2Bytes: ann.BitcoinKey2, + AuthProof: proof, + Features: featureBuf.Bytes(), + ExtraOpaqueData: ann.ExtraOpaqueData, + } - // Assemble the necessary announcements to add to the next - // broadcasting batch. - announcements = append(announcements, networkMsg{ - peer: nMsg.peer, - source: nMsg.source, - msg: chanAnn, - }) - if src, err := chanInfo.NodeKey1(); err == nil && e1Ann != nil { - announcements = append(announcements, networkMsg{ - peer: nMsg.peer, - source: src, - msg: e1Ann, - }) + // If there were any optional message fields provided, we'll include + // them in its serialized disk representation now. + if nMsg.optionalMsgFields != nil { + if nMsg.optionalMsgFields.capacity != nil { + edge.Capacity = *nMsg.optionalMsgFields.capacity } - if src, err := chanInfo.NodeKey2(); err == nil && e2Ann != nil { - announcements = append(announcements, networkMsg{ - peer: nMsg.peer, - source: src, - msg: e2Ann, - }) + if nMsg.optionalMsgFields.channelPoint != nil { + cp := *nMsg.optionalMsgFields.channelPoint + edge.ChannelPoint = cp } + } - // We'll also send along the node announcements for each channel - // participant if we know of them. To ensure our node - // announcement propagates to our channel counterparty, we'll - // set the source for each announcement to the node it belongs - // to, otherwise we won't send it since the source gets skipped. - // This isn't necessary for channel updates and announcement - // signatures since we send those directly to our channel - // counterparty through the gossiper's reliable sender. - node1Ann, err := d.fetchNodeAnn(chanInfo.NodeKey1Bytes) - if err != nil { - log.Debugf("Unable to fetch node announcement for "+ - "%x: %v", chanInfo.NodeKey1Bytes, err) - } else { - if nodeKey1, err := chanInfo.NodeKey1(); err == nil { - announcements = append(announcements, networkMsg{ - peer: nMsg.peer, - source: nodeKey1, - msg: node1Ann, - }) + // We will add the edge to the channel router. If the nodes present in + // this channel are not present in the database, a partial node will be + // added to represent each node while we wait for a node announcement. + // + // Before we add the edge to the database, we obtain the mutex for this + // channel ID. We do this to ensure no other goroutine has read the + // database and is now making decisions based on this DB state, before + // it writes to the DB. + d.channelMtx.Lock(ann.ShortChannelID.ToUint64()) + err := d.cfg.Router.AddEdge(edge, ops...) + if err != nil { + defer d.channelMtx.Unlock(ann.ShortChannelID.ToUint64()) + + // If the edge was rejected due to already being known, then it + // may be the case that this new message has a fresh channel + // proof, so we'll check. + if routing.IsError(err, routing.ErrIgnored) { + // Attempt to process the rejected message to see if we + // get any new announcements. + anns, rErr := d.processRejectedEdge(ann, proof) + if rErr != nil { + key := newRejectCacheKey( + ann.ShortChannelID.ToUint64(), + sourceToPub(nMsg.source), + ) + cr := &cachedReject{} + _, _ = d.recentRejects.Put(key, cr) + + nMsg.err <- rErr + return nil, false } - } - node2Ann, err := d.fetchNodeAnn(chanInfo.NodeKey2Bytes) - if err != nil { - log.Debugf("Unable to fetch node announcement for "+ - "%x: %v", chanInfo.NodeKey2Bytes, err) - } else { - if nodeKey2, err := chanInfo.NodeKey2(); err == nil { - announcements = append(announcements, networkMsg{ - peer: nMsg.peer, - source: nodeKey2, - msg: node2Ann, - }) + + // If while processing this rejected edge, we realized + // there's a set of announcements we could extract, + // then we'll return those directly. + if len(anns) != 0 { + nMsg.err <- nil + return anns, true } - } - nMsg.err <- nil - return announcements, true + // Otherwise, this is just a regular rejected edge. + log.Debugf("Router rejected channel edge: %v", err) + } else { + log.Debugf("Router rejected channel edge: %v", err) + + key := newRejectCacheKey( + ann.ShortChannelID.ToUint64(), + sourceToPub(nMsg.source), + ) + _, _ = d.recentRejects.Put(key, &cachedReject{}) + } - default: - err := errors.New("wrong type of the announcement") nMsg.err <- err return nil, false } -} - -// processZombieUpdate determines whether the provided channel update should -// resurrect a given zombie edge. -func (d *AuthenticatedGossiper) processZombieUpdate( - chanInfo *channeldb.ChannelEdgeInfo, msg *lnwire.ChannelUpdate) error { - // The least-significant bit in the flag on the channel update tells us - // which edge is being updated. - isNode1 := msg.ChannelFlags&lnwire.ChanUpdateDirection == 0 + // If err is nil, release the lock immediately. + d.channelMtx.Unlock(ann.ShortChannelID.ToUint64()) + + // If we earlier received any ChannelUpdates for this channel, we can + // now process them, as the channel is added to the graph. + shortChanID := ann.ShortChannelID.ToUint64() + var channelUpdates []*networkMsg + + earlyChanUpdates, err := d.prematureChannelUpdates.Get(shortChanID) + if err == nil { + // There was actually an entry in the map, so we'll accumulate + // it. We don't worry about deletion, since it'll eventually + // fall out anyway. + chanMsgs := earlyChanUpdates.(*cachedNetworkMsg) + channelUpdates = append(channelUpdates, chanMsgs.msgs...) + } + + // Launch a new goroutine to handle each ChannelUpdate, this is to + // ensure we don't block here, as we can handle only one announcement + // at a time. + for _, cu := range channelUpdates { + d.wg.Add(1) + go func(updMsg *networkMsg) { + defer d.wg.Done() + + switch msg := updMsg.msg.(type) { + // Reprocess the message, making sure we return an + // error to the original caller in case the gossiper + // shuts down. + case *lnwire.ChannelUpdate: + log.Debugf("Reprocessing ChannelUpdate for "+ + "shortChanID=%v", + msg.ShortChannelID.ToUint64()) + + select { + case d.networkMsgs <- updMsg: + case <-d.quit: + updMsg.err <- ErrGossiperShuttingDown + } - // Since we've deemed the update as not stale above, before marking it - // live, we'll make sure it has been signed by the correct party. If we - // have both pubkeys, either party can resurect the channel. If we've - // already marked this with the stricter, single-sided resurrection we - // will only have the pubkey of the node with the oldest timestamp. - var pubKey *btcec.PublicKey - switch { - case isNode1 && chanInfo.NodeKey1Bytes != emptyPubkey: - pubKey, _ = chanInfo.NodeKey1() - case !isNode1 && chanInfo.NodeKey2Bytes != emptyPubkey: - pubKey, _ = chanInfo.NodeKey2() - } - if pubKey == nil { - return fmt.Errorf("incorrect pubkey to resurrect zombie "+ - "with chan_id=%v", msg.ShortChannelID) + // We don't expect any other message type than + // ChannelUpdate to be in this cache. + default: + log.Errorf("Unsupported message type found "+ + "among ChannelUpdates: %T", msg) + } + }(cu) } - err := routing.VerifyChannelUpdateSignature(msg, pubKey) - if err != nil { - return fmt.Errorf("unable to verify channel "+ - "update signature: %v", err) - } + // Channel announcement was successfully processed and now it might be + // broadcast to other connected nodes if it was an announcement with + // proof (remote). + var announcements []networkMsg - // With the signature valid, we'll proceed to mark the - // edge as live and wait for the channel announcement to - // come through again. - err = d.cfg.Router.MarkEdgeLive(msg.ShortChannelID) - if err != nil { - return fmt.Errorf("unable to remove edge with "+ - "chan_id=%v from zombie index: %v", - msg.ShortChannelID, err) + if proof != nil { + announcements = append(announcements, networkMsg{ + peer: nMsg.peer, + source: nMsg.source, + msg: ann, + }) } - log.Debugf("Removed edge with chan_id=%v from zombie "+ - "index", msg.ShortChannelID) - - return nil + nMsg.err <- nil + return announcements, true } -// fetchNodeAnn fetches the latest signed node announcement from our point of -// view for the node with the given public key. -func (d *AuthenticatedGossiper) fetchNodeAnn( - pubKey [33]byte) (*lnwire.NodeAnnouncement, error) { +// handleChanUpdate processes a new channel update. +func (d *AuthenticatedGossiper) handleChanUpdate(nMsg *networkMsg, + upd *lnwire.ChannelUpdate, + ops []batch.SchedulerOption) ([]networkMsg, bool) { + + // We'll ignore any channel updates that target any chain other than + // the set of chains we know of. + if !bytes.Equal(upd.ChainHash[:], d.cfg.ChainHash[:]) { + err := fmt.Errorf("ignoring ChannelUpdate from chain=%v, "+ + "gossiper on chain=%v", upd.ChainHash, d.cfg.ChainHash) + log.Errorf(err.Error()) + + key := newRejectCacheKey( + upd.ShortChannelID.ToUint64(), + sourceToPub(nMsg.source), + ) + _, _ = d.recentRejects.Put(key, &cachedReject{}) - node, err := d.cfg.Router.FetchLightningNode(pubKey) - if err != nil { - return nil, err + nMsg.err <- err + return nil, false } - return node.NodeAnnouncement(true) -} + blockHeight := upd.ShortChannelID.BlockHeight + shortChanID := upd.ShortChannelID.ToUint64() -// isMsgStale determines whether a message retrieved from the backing -// MessageStore is seen as stale by the current graph. -func (d *AuthenticatedGossiper) isMsgStale(msg lnwire.Message) bool { - switch msg := msg.(type) { - case *lnwire.AnnounceSignatures: - chanInfo, _, _, err := d.cfg.Router.GetChannelByID( - msg.ShortChannelID, + // If the advertised inclusionary block is beyond our knowledge of the + // chain tip, then we'll put the announcement in limbo to be fully + // verified once we advance forward in the chain. + d.Lock() + if nMsg.isRemote && d.isPremature(upd.ShortChannelID, 0, nMsg) { + log.Warnf("Update announcement for short_chan_id(%v), is "+ + "premature: advertises height %v, only height %v is "+ + "known", shortChanID, blockHeight, d.bestHeight) + d.Unlock() + nMsg.err <- nil + return nil, false + } + d.Unlock() + + // Before we perform any of the expensive checks below, we'll check + // whether this update is stale or is for a zombie channel in order to + // quickly reject it. + timestamp := time.Unix(int64(upd.Timestamp), 0) + if d.cfg.Router.IsStaleEdgePolicy( + upd.ShortChannelID, timestamp, upd.ChannelFlags, + ) { + + log.Debugf("Ignored stale edge policy: peer=%v, source=%x, "+ + "msg=%s, is_remote=%v", nMsg.peer, + nMsg.source.SerializeCompressed(), nMsg.msg.MsgType(), + nMsg.isRemote, ) - // If the channel cannot be found, it is most likely a leftover - // message for a channel that was closed, so we can consider it - // stale. - if err == channeldb.ErrEdgeNotFound { - return true - } + nMsg.err <- nil + return nil, true + } + + // Get the node pub key as far since we don't have it in the channel + // update announcement message. We'll need this to properly verify the + // message's signature. + // + // We make sure to obtain the mutex for this channel ID before we + // access the database. This ensures the state we read from the + // database has not changed between this point and when we call + // UpdateEdge() later. + d.channelMtx.Lock(upd.ShortChannelID.ToUint64()) + defer d.channelMtx.Unlock(upd.ShortChannelID.ToUint64()) + chanInfo, e1, e2, err := d.cfg.Router.GetChannelByID( + upd.ShortChannelID, + ) + switch err { + // No error, break. + case nil: + break + + case channeldb.ErrZombieEdge: + err = d.processZombieUpdate(chanInfo, upd) if err != nil { - log.Debugf("Unable to retrieve channel=%v from graph: "+ - "%v", err) - return false + log.Debug(err) + nMsg.err <- err + return nil, false } - // If the proof exists in the graph, then we have successfully - // received the remote proof and assembled the full proof, so we - // can safely delete the local proof from the database. - return chanInfo.AuthProof != nil - - case *lnwire.ChannelUpdate: - _, p1, p2, err := d.cfg.Router.GetChannelByID(msg.ShortChannelID) + // We'll fallthrough to ensure we stash the update until we + // receive its corresponding ChannelAnnouncement. This is + // needed to ensure the edge exists in the graph before + // applying the update. + fallthrough + case channeldb.ErrGraphNotFound: + fallthrough + case channeldb.ErrGraphNoEdgesFound: + fallthrough + case channeldb.ErrEdgeNotFound: + // If the edge corresponding to this ChannelUpdate was not + // found in the graph, this might be a channel in the process + // of being opened, and we haven't processed our own + // ChannelAnnouncement yet, hence it is not not found in the + // graph. This usually gets resolved after the channel proofs + // are exchanged and the channel is broadcasted to the rest of + // the network, but in case this is a private channel this + // won't ever happen. This can also happen in the case of a + // zombie channel with a fresh update for which we don't have a + // ChannelAnnouncement for since we reject them. Because of + // this, we temporarily add it to a map, and reprocess it after + // our own ChannelAnnouncement has been processed. + earlyMsgs, err := d.prematureChannelUpdates.Get(shortChanID) + switch { + // Nothing in the cache yet, we can just directly insert this + // element. + case err == cache.ErrElementNotFound: + _, _ = d.prematureChannelUpdates.Put( + shortChanID, &cachedNetworkMsg{ + msgs: []*networkMsg{nMsg}, + }) - // If the channel cannot be found, it is most likely a leftover - // message for a channel that was closed, so we can consider it - // stale. - if err == channeldb.ErrEdgeNotFound { - return true + // There's already something in the cache, so we'll combine the + // set of messages into a single value. + default: + msgs := earlyMsgs.(*cachedNetworkMsg).msgs + msgs = append(msgs, nMsg) + _, _ = d.prematureChannelUpdates.Put( + shortChanID, &cachedNetworkMsg{ + msgs: msgs, + }) } - if err != nil { - log.Debugf("Unable to retrieve channel=%v from graph: "+ - "%v", msg.ShortChannelID, err) - return false + + log.Debugf("Got ChannelUpdate for edge not found in graph"+ + "(shortChanID=%v), saving for reprocessing later", + shortChanID) + + // NOTE: We don't return anything on the error channel for this + // message, as we expect that will be done when this + // ChannelUpdate is later reprocessed. + return nil, false + + default: + err := fmt.Errorf("unable to validate channel update "+ + "short_chan_id=%v: %v", shortChanID, err) + log.Error(err) + nMsg.err <- err + + key := newRejectCacheKey( + upd.ShortChannelID.ToUint64(), + sourceToPub(nMsg.source), + ) + _, _ = d.recentRejects.Put(key, &cachedReject{}) + + return nil, false + } + + // The least-significant bit in the flag on the channel update + // announcement tells us "which" side of the channels directed edge is + // being updated. + var ( + pubKey *btcec.PublicKey + edgeToUpdate *channeldb.ChannelEdgePolicy + ) + direction := upd.ChannelFlags & lnwire.ChanUpdateDirection + switch direction { + case 0: + pubKey, _ = chanInfo.NodeKey1() + edgeToUpdate = e1 + case 1: + pubKey, _ = chanInfo.NodeKey2() + edgeToUpdate = e2 + } + + // Validate the channel announcement with the expected public key and + // channel capacity. In the case of an invalid channel update, we'll + // return an error to the caller and exit early. + err = routing.ValidateChannelUpdateAnn(pubKey, chanInfo.Capacity, upd) + if err != nil { + rErr := fmt.Errorf("unable to validate channel update "+ + "announcement for short_chan_id=%v: %v", + spew.Sdump(upd.ShortChannelID), err) + + log.Error(rErr) + nMsg.err <- rErr + return nil, false + } + + // If we have a previous version of the edge being updated, we'll want + // to rate limit its updates to prevent spam throughout the network. + if nMsg.isRemote && edgeToUpdate != nil { + // If it's a keep-alive update, we'll only propagate one if + // it's been a day since the previous. This follows our own + // heuristic of sending keep-alive updates after the same + // duration (see retransmitStaleAnns). + timeSinceLastUpdate := timestamp.Sub(edgeToUpdate.LastUpdate) + if IsKeepAliveUpdate(upd, edgeToUpdate) { + if timeSinceLastUpdate < d.cfg.RebroadcastInterval { + log.Debugf("Ignoring keep alive update not "+ + "within %v period for channel %v", + d.cfg.RebroadcastInterval, shortChanID) + nMsg.err <- nil + return nil, false + } + } else { + // If it's not, we'll allow an update per minute with a + // maximum burst of 10. If we haven't seen an update + // for this channel before, we'll need to initialize a + // rate limiter for each direction. + d.Lock() + rls, ok := d.chanUpdateRateLimiter[shortChanID] + if !ok { + r := rate.Every(d.cfg.ChannelUpdateInterval) + b := d.cfg.MaxChannelUpdateBurst + rls = [2]*rate.Limiter{ + rate.NewLimiter(r, b), + rate.NewLimiter(r, b), + } + d.chanUpdateRateLimiter[shortChanID] = rls + } + d.Unlock() + + if !rls[direction].Allow() { + log.Debugf("Rate limiting update for channel "+ + "%v from direction %x", shortChanID, + pubKey.SerializeCompressed()) + nMsg.err <- nil + return nil, false + } } + } + + update := &channeldb.ChannelEdgePolicy{ + SigBytes: upd.Signature.ToSignatureBytes(), + ChannelID: shortChanID, + LastUpdate: timestamp, + MessageFlags: upd.MessageFlags, + ChannelFlags: upd.ChannelFlags, + TimeLockDelta: upd.TimeLockDelta, + MinHTLC: upd.HtlcMinimumMsat, + MaxHTLC: upd.HtlcMaximumMsat, + FeeBaseMSat: lnwire.MilliSatoshi(upd.BaseFee), + FeeProportionalMillionths: lnwire.MilliSatoshi(upd.FeeRate), + ExtraOpaqueData: upd.ExtraOpaqueData, + } + + if err := d.cfg.Router.UpdateEdge(update, ops...); err != nil { + if routing.IsError( + err, routing.ErrOutdated, + routing.ErrIgnored, + routing.ErrVBarrierShuttingDown, + ) { - // Otherwise, we'll retrieve the correct policy that we - // currently have stored within our graph to check if this - // message is stale by comparing its timestamp. - var p *channeldb.ChannelEdgePolicy - if msg.ChannelFlags&lnwire.ChanUpdateDirection == 0 { - p = p1 + log.Debug(err) } else { - p = p2 - } + key := newRejectCacheKey( + upd.ShortChannelID.ToUint64(), + sourceToPub(nMsg.source), + ) + _, _ = d.recentRejects.Put(key, &cachedReject{}) - // If the policy is still unknown, then we can consider this - // policy fresh. - if p == nil { - return false + log.Error(err) } - timestamp := time.Unix(int64(msg.Timestamp), 0) - return p.LastUpdate.After(timestamp) + nMsg.err <- err + return nil, false + } - default: - // We'll make sure to not mark any unsupported messages as stale - // to ensure they are not removed. - return false + // If this is a local ChannelUpdate without an AuthProof, it means it + // is an update to a channel that is not (yet) supposed to be announced + // to the greater network. However, our channel counter party will need + // to be given the update, so we'll try sending the update directly to + // the remote peer. + if !nMsg.isRemote && chanInfo.AuthProof == nil { + // Get our peer's public key. + remotePubKey := remotePubFromChanInfo( + chanInfo, upd.ChannelFlags, + ) + + log.Debugf("The message %v has no AuthProof, sending the "+ + "update to remote peer %x", upd.MsgType(), + remotePubKey) + + // Now we'll attempt to send the channel update message + // reliably to the remote peer in the background, so that we + // don't block if the peer happens to be offline at the moment. + err := d.reliableSender.sendMessage(upd, remotePubKey) + if err != nil { + err := fmt.Errorf("unable to reliably send %v for "+ + "channel=%v to peer=%x: %v", upd.MsgType(), + upd.ShortChannelID, remotePubKey, err) + nMsg.err <- err + return nil, false + } + } + + // Channel update announcement was successfully processed and now it + // can be broadcast to the rest of the network. However, we'll only + // broadcast the channel update announcement if it has an attached + // authentication proof. + var announcements []networkMsg + if chanInfo.AuthProof != nil { + announcements = append(announcements, networkMsg{ + peer: nMsg.peer, + source: nMsg.source, + msg: upd, + }) } + + nMsg.err <- nil + return announcements, true } -// updateChannel creates a new fully signed update for the channel, and updates -// the underlying graph with the new state. -func (d *AuthenticatedGossiper) updateChannel(info *channeldb.ChannelEdgeInfo, - edge *channeldb.ChannelEdgePolicy) (*lnwire.ChannelAnnouncement, - *lnwire.ChannelUpdate, error) { +// handleAnnSig processes a new announcement signatures message. +func (d *AuthenticatedGossiper) handleAnnSig(nMsg *networkMsg, + ann *lnwire.AnnounceSignatures) ([]networkMsg, bool) { - // Parse the unsigned edge into a channel update. - chanUpdate := netann.UnsignedChannelUpdateFromEdge(info, edge) + needBlockHeight := ann.ShortChannelID.BlockHeight + + d.cfg.ProofMatureDelta + shortChanID := ann.ShortChannelID.ToUint64() - // We'll generate a new signature over a digest of the channel - // announcement itself and update the timestamp to ensure it propagate. - err := netann.SignChannelUpdate( - d.cfg.AnnSigner, d.selfKeyLoc, chanUpdate, - netann.ChanUpdSetTimestamp, + prefix := "local" + if nMsg.isRemote { + prefix = "remote" + } + + log.Infof("Received new %v channel announcement for %v", prefix, + ann.ShortChannelID) + + // By the specification, channel announcement proofs should be sent + // after some number of confirmations after channel was registered in + // bitcoin blockchain. Therefore, we check if the proof is mature. + d.Lock() + premature := d.isPremature( + ann.ShortChannelID, d.cfg.ProofMatureDelta, nMsg, ) - if err != nil { - return nil, nil, err + if premature { + log.Warnf("Premature proof announcement, current block height"+ + "lower than needed: %v < %v", d.bestHeight, + needBlockHeight) + d.Unlock() + nMsg.err <- nil + return nil, false } + d.Unlock() - // Next, we'll set the new signature in place, and update the reference - // in the backing slice. - edge.LastUpdate = time.Unix(int64(chanUpdate.Timestamp), 0) - edge.SigBytes = chanUpdate.Signature.ToSignatureBytes() + // Ensure that we know of a channel with the target channel ID before + // proceeding further. + // + // We must acquire the mutex for this channel ID before getting the + // channel from the database, to ensure what we read does not change + // before we call AddProof() later. + d.channelMtx.Lock(ann.ShortChannelID.ToUint64()) + defer d.channelMtx.Unlock(ann.ShortChannelID.ToUint64()) + + chanInfo, e1, e2, err := d.cfg.Router.GetChannelByID( + ann.ShortChannelID, + ) - // To ensure that our signature is valid, we'll verify it ourself - // before committing it to the slice returned. - err = routing.ValidateChannelUpdateAnn(d.selfKey, info.Capacity, chanUpdate) if err != nil { - return nil, nil, fmt.Errorf("generated invalid channel "+ - "update sig: %v", err) + proof := channeldb.NewWaitingProof(nMsg.isRemote, ann) + err := d.cfg.WaitingProofStore.Add(proof) + if err != nil { + err := fmt.Errorf("unable to store the proof for "+ + "short_chan_id=%v: %v", shortChanID, err) + log.Error(err) + nMsg.err <- err + return nil, false + } + + log.Infof("Orphan %v proof announcement with short_chan_id=%v"+ + ", adding to waiting batch", prefix, shortChanID) + nMsg.err <- nil + return nil, false } - // Finally, we'll write the new edge policy to disk. - if err := d.cfg.Router.UpdateEdge(edge); err != nil { - return nil, nil, err + nodeID := nMsg.source.SerializeCompressed() + isFirstNode := bytes.Equal(nodeID, chanInfo.NodeKey1Bytes[:]) + isSecondNode := bytes.Equal(nodeID, chanInfo.NodeKey2Bytes[:]) + + // Ensure that channel that was retrieved belongs to the peer which + // sent the proof announcement. + if !(isFirstNode || isSecondNode) { + err := fmt.Errorf("channel that was received doesn't belong "+ + "to the peer which sent the proof, short_chan_id=%v", + shortChanID) + log.Error(err) + nMsg.err <- err + return nil, false } - // We'll also create the original channel announcement so the two can - // be broadcast along side each other (if necessary), but only if we - // have a full channel announcement for this channel. - var chanAnn *lnwire.ChannelAnnouncement - if info.AuthProof != nil { - chanID := lnwire.NewShortChanIDFromInt(info.ChannelID) - chanAnn = &lnwire.ChannelAnnouncement{ - ShortChannelID: chanID, - NodeID1: info.NodeKey1Bytes, - NodeID2: info.NodeKey2Bytes, - ChainHash: info.ChainHash, - BitcoinKey1: info.BitcoinKey1Bytes, - Features: lnwire.NewRawFeatureVector(), - BitcoinKey2: info.BitcoinKey2Bytes, - ExtraOpaqueData: edge.ExtraOpaqueData, - } - chanAnn.NodeSig1, err = lnwire.NewSigFromRawSignature( - info.AuthProof.NodeSig1Bytes, - ) - if err != nil { - return nil, nil, err - } - chanAnn.NodeSig2, err = lnwire.NewSigFromRawSignature( - info.AuthProof.NodeSig2Bytes, - ) - if err != nil { - return nil, nil, err - } - chanAnn.BitcoinSig1, err = lnwire.NewSigFromRawSignature( - info.AuthProof.BitcoinSig1Bytes, - ) - if err != nil { - return nil, nil, err + // If proof was sent by a local sub-system, then we'll send the + // announcement signature to the remote node so they can also + // reconstruct the full channel announcement. + if !nMsg.isRemote { + var remotePubKey [33]byte + if isFirstNode { + remotePubKey = chanInfo.NodeKey2Bytes + } else { + remotePubKey = chanInfo.NodeKey1Bytes } - chanAnn.BitcoinSig2, err = lnwire.NewSigFromRawSignature( - info.AuthProof.BitcoinSig2Bytes, - ) + // Since the remote peer might not be online we'll call a + // method that will attempt to deliver the proof when it comes + // online. + err := d.reliableSender.sendMessage(ann, remotePubKey) if err != nil { - return nil, nil, err + err := fmt.Errorf("unable to reliably send %v for "+ + "channel=%v to peer=%x: %v", ann.MsgType(), + ann.ShortChannelID, remotePubKey, err) + nMsg.err <- err + return nil, false } } - return chanAnn, chanUpdate, err -} + // Check if we already have the full proof for this channel. + if chanInfo.AuthProof != nil { + // If we already have the fully assembled proof, then the peer + // sending us their proof has probably not received our local + // proof yet. So be kind and send them the full proof. + if nMsg.isRemote { + peerID := nMsg.source.SerializeCompressed() + log.Debugf("Got AnnounceSignatures for channel with " + + "full proof.") -// SyncManager returns the gossiper's SyncManager instance. -func (d *AuthenticatedGossiper) SyncManager() *SyncManager { - return d.syncMgr -} + d.wg.Add(1) + go func() { + defer d.wg.Done() + log.Debugf("Received half proof for channel "+ + "%v with existing full proof. Sending"+ + " full proof to peer=%x", + ann.ChannelID, peerID) -// IsKeepAliveUpdate determines whether this channel update is considered a -// keep-alive update based on the previous channel update processed for the same -// direction. -func IsKeepAliveUpdate(update *lnwire.ChannelUpdate, - prev *channeldb.ChannelEdgePolicy) bool { + ca, _, _, err := netann.CreateChanAnnouncement( + chanInfo.AuthProof, chanInfo, e1, e2, + ) + if err != nil { + log.Errorf("unable to gen ann: %v", + err) + return + } - // Both updates should be from the same direction. - if update.ChannelFlags&lnwire.ChanUpdateDirection != - prev.ChannelFlags&lnwire.ChanUpdateDirection { - return false + err = nMsg.peer.SendMessage(false, ca) + if err != nil { + log.Errorf("Failed sending full proof"+ + " to peer=%x: %v", peerID, err) + return + } + + log.Debugf("Full proof sent to peer=%x for "+ + "chanID=%v", peerID, ann.ChannelID) + }() + } + + log.Debugf("Already have proof for channel with chanID=%v", + ann.ChannelID) + nMsg.err <- nil + return nil, true + } + + // Check that we received the opposite proof. If so, then we're now + // able to construct the full proof, and create the channel + // announcement. If we didn't receive the opposite half of the proof + // then we should store this one, and wait for the opposite to be + // received. + proof := channeldb.NewWaitingProof(nMsg.isRemote, ann) + oppProof, err := d.cfg.WaitingProofStore.Get(proof.OppositeKey()) + if err != nil && err != channeldb.ErrWaitingProofNotFound { + err := fmt.Errorf("unable to get the opposite proof for "+ + "short_chan_id=%v: %v", shortChanID, err) + log.Error(err) + nMsg.err <- err + return nil, false } - // The timestamp should always increase for a keep-alive update. - timestamp := time.Unix(int64(update.Timestamp), 0) - if !timestamp.After(prev.LastUpdate) { - return false + if err == channeldb.ErrWaitingProofNotFound { + err := d.cfg.WaitingProofStore.Add(proof) + if err != nil { + err := fmt.Errorf("unable to store the proof for "+ + "short_chan_id=%v: %v", shortChanID, err) + log.Error(err) + nMsg.err <- err + return nil, false + } + + log.Infof("1/2 of channel ann proof received for "+ + "short_chan_id=%v, waiting for other half", + shortChanID) + + nMsg.err <- nil + return nil, false } - // None of the remaining fields should change for a keep-alive update. - if update.ChannelFlags.IsDisabled() != prev.ChannelFlags.IsDisabled() { - return false + // We now have both halves of the channel announcement proof, then + // we'll reconstruct the initial announcement so we can validate it + // shortly below. + var dbProof channeldb.ChannelAuthProof + if isFirstNode { + dbProof.NodeSig1Bytes = ann.NodeSignature.ToSignatureBytes() + dbProof.NodeSig2Bytes = oppProof.NodeSignature.ToSignatureBytes() + dbProof.BitcoinSig1Bytes = ann.BitcoinSignature.ToSignatureBytes() + dbProof.BitcoinSig2Bytes = oppProof.BitcoinSignature.ToSignatureBytes() + } else { + dbProof.NodeSig1Bytes = oppProof.NodeSignature.ToSignatureBytes() + dbProof.NodeSig2Bytes = ann.NodeSignature.ToSignatureBytes() + dbProof.BitcoinSig1Bytes = oppProof.BitcoinSignature.ToSignatureBytes() + dbProof.BitcoinSig2Bytes = ann.BitcoinSignature.ToSignatureBytes() } - if lnwire.MilliSatoshi(update.BaseFee) != prev.FeeBaseMSat { - return false + + chanAnn, e1Ann, e2Ann, err := netann.CreateChanAnnouncement( + &dbProof, chanInfo, e1, e2, + ) + if err != nil { + log.Error(err) + nMsg.err <- err + return nil, false } - if lnwire.MilliSatoshi(update.FeeRate) != prev.FeeProportionalMillionths { - return false + + // With all the necessary components assembled validate the full + // channel announcement proof. + if err := routing.ValidateChannelAnn(chanAnn); err != nil { + err := fmt.Errorf("channel announcement proof for "+ + "short_chan_id=%v isn't valid: %v", shortChanID, err) + + log.Error(err) + nMsg.err <- err + return nil, false } - if update.TimeLockDelta != prev.TimeLockDelta { - return false + + // If the channel was returned by the router it means that existence of + // funding point and inclusion of nodes bitcoin keys in it already + // checked by the router. In this stage we should check that node keys + // attest to the bitcoin keys by validating the signatures of + // announcement. If proof is valid then we'll populate the channel edge + // with it, so we can announce it on peer connect. + err = d.cfg.Router.AddProof(ann.ShortChannelID, &dbProof) + if err != nil { + err := fmt.Errorf("unable add proof to the channel chanID=%v:"+ + " %v", ann.ChannelID, err) + log.Error(err) + nMsg.err <- err + return nil, false } - if update.HtlcMinimumMsat != prev.MinHTLC { - return false + + err = d.cfg.WaitingProofStore.Remove(proof.OppositeKey()) + if err != nil { + err := fmt.Errorf("unable to remove opposite proof for the "+ + "channel with chanID=%v: %v", ann.ChannelID, err) + log.Error(err) + nMsg.err <- err + return nil, false } - if update.MessageFlags.HasMaxHtlc() && !prev.MessageFlags.HasMaxHtlc() { - return false + + // Proof was successfully created and now can announce the channel to + // the remain network. + log.Infof("Fully valid channel proof for short_chan_id=%v constructed"+ + ", adding to next ann batch", shortChanID) + + // Assemble the necessary announcements to add to the next broadcasting + // batch. + var announcements []networkMsg + announcements = append(announcements, networkMsg{ + peer: nMsg.peer, + source: nMsg.source, + msg: chanAnn, + }) + if src, err := chanInfo.NodeKey1(); err == nil && e1Ann != nil { + announcements = append(announcements, networkMsg{ + peer: nMsg.peer, + source: src, + msg: e1Ann, + }) } - if update.HtlcMaximumMsat != prev.MaxHTLC { - return false + if src, err := chanInfo.NodeKey2(); err == nil && e2Ann != nil { + announcements = append(announcements, networkMsg{ + peer: nMsg.peer, + source: src, + msg: e2Ann, + }) } - if !bytes.Equal(update.ExtraOpaqueData, prev.ExtraOpaqueData) { - return false + + // We'll also send along the node announcements for each channel + // participant if we know of them. To ensure our node announcement + // propagates to our channel counterparty, we'll set the source for + // each announcement to the node it belongs to, otherwise we won't send + // it since the source gets skipped. This isn't necessary for channel + // updates and announcement signatures since we send those directly to + // our channel counterparty through the gossiper's reliable sender. + node1Ann, err := d.fetchNodeAnn(chanInfo.NodeKey1Bytes) + if err != nil { + log.Debugf("Unable to fetch node announcement for %x: %v", + chanInfo.NodeKey1Bytes, err) + } else { + if nodeKey1, err := chanInfo.NodeKey1(); err == nil { + announcements = append(announcements, networkMsg{ + peer: nMsg.peer, + source: nodeKey1, + msg: node1Ann, + }) + } } - return true -} -// latestHeight returns the gossiper's latest height known of the chain. -func (d *AuthenticatedGossiper) latestHeight() uint32 { - d.Lock() - defer d.Unlock() - return d.bestHeight + node2Ann, err := d.fetchNodeAnn(chanInfo.NodeKey2Bytes) + if err != nil { + log.Debugf("Unable to fetch node announcement for %x: %v", + chanInfo.NodeKey2Bytes, err) + } else { + if nodeKey2, err := chanInfo.NodeKey2(); err == nil { + announcements = append(announcements, networkMsg{ + peer: nMsg.peer, + source: nodeKey2, + msg: node2Ann, + }) + } + } + + nMsg.err <- nil + return announcements, true } diff --git a/discovery/gossiper_test.go b/discovery/gossiper_test.go index ea76d62560..cffe80536e 100644 --- a/discovery/gossiper_test.go +++ b/discovery/gossiper_test.go @@ -5,7 +5,6 @@ import ( "encoding/hex" "fmt" "io/ioutil" - "math/big" prand "math/rand" "net" "os" @@ -16,10 +15,10 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/davecgh/go-spew/spew" "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/batch" @@ -43,30 +42,24 @@ var ( Port: 9000} testAddrs = []net.Addr{testAddr} testFeatures = lnwire.NewRawFeatureVector() - testSig = &btcec.Signature{ - R: new(big.Int), - S: new(big.Int), - } - _, _ = testSig.R.SetString("63724406601629180062774974542967536251589935445068131219452686511677818569431", 10) - _, _ = testSig.S.SetString("18801056069249825825291287104931333862866033135609736119018462340006816851118", 10) - testKeyLoc = keychain.KeyLocator{Family: keychain.KeyFamilyNodeKey} + testKeyLoc = keychain.KeyLocator{Family: keychain.KeyFamilyNodeKey} - selfKeyPriv, _ = btcec.NewPrivateKey(btcec.S256()) + selfKeyPriv, _ = btcec.NewPrivateKey() selfKeyDesc = &keychain.KeyDescriptor{ PubKey: selfKeyPriv.PubKey(), KeyLocator: testKeyLoc, } - bitcoinKeyPriv1, _ = btcec.NewPrivateKey(btcec.S256()) + bitcoinKeyPriv1, _ = btcec.NewPrivateKey() bitcoinKeyPub1 = bitcoinKeyPriv1.PubKey() - remoteKeyPriv1, _ = btcec.NewPrivateKey(btcec.S256()) + remoteKeyPriv1, _ = btcec.NewPrivateKey() remoteKeyPub1 = remoteKeyPriv1.PubKey() - bitcoinKeyPriv2, _ = btcec.NewPrivateKey(btcec.S256()) + bitcoinKeyPriv2, _ = btcec.NewPrivateKey() bitcoinKeyPub2 = bitcoinKeyPriv2.PubKey() - remoteKeyPriv2, _ = btcec.NewPrivateKey(btcec.S256()) + remoteKeyPriv2, _ = btcec.NewPrivateKey() trickleDelay = time.Millisecond * 100 retransmitDelay = time.Hour * 1 @@ -758,7 +751,7 @@ func createTestCtx(startHeight uint32) (*testCtx, func(), error) { NotifyWhenOnline: func(target [33]byte, peerChan chan<- lnpeer.Peer) { - pk, _ := btcec.ParsePubKey(target[:], btcec.S256()) + pk, _ := btcec.ParsePubKey(target[:]) peerChan <- &mockPeer{pk, nil, nil} }, NotifyWhenOffline: func(_ [33]byte) <-chan struct{} { @@ -973,7 +966,7 @@ func TestSignatureAnnouncementLocalFirst(t *testing.T) { ctx.gossiper.reliableSender.cfg.NotifyWhenOnline = func(target [33]byte, peerChan chan<- lnpeer.Peer) { - pk, _ := btcec.ParsePubKey(target[:], btcec.S256()) + pk, _ := btcec.ParsePubKey(target[:]) select { case peerChan <- &mockPeer{pk, sentMsgs, ctx.gossiper.quit}: @@ -986,7 +979,7 @@ func TestSignatureAnnouncementLocalFirst(t *testing.T) { t.Fatalf("can't generate announcements: %v", err) } - remoteKey, err := btcec.ParsePubKey(batch.nodeAnn2.NodeID[:], btcec.S256()) + remoteKey, err := btcec.ParsePubKey(batch.nodeAnn2.NodeID[:]) if err != nil { t.Fatalf("unable to parse pubkey: %v", err) } @@ -1166,7 +1159,7 @@ func TestOrphanSignatureAnnouncement(t *testing.T) { ctx.gossiper.reliableSender.cfg.NotifyWhenOnline = func(target [33]byte, peerChan chan<- lnpeer.Peer) { - pk, _ := btcec.ParsePubKey(target[:], btcec.S256()) + pk, _ := btcec.ParsePubKey(target[:]) select { case peerChan <- &mockPeer{pk, sentMsgs, ctx.gossiper.quit}: @@ -1179,7 +1172,7 @@ func TestOrphanSignatureAnnouncement(t *testing.T) { t.Fatalf("can't generate announcements: %v", err) } - remoteKey, err := btcec.ParsePubKey(batch.nodeAnn2.NodeID[:], btcec.S256()) + remoteKey, err := btcec.ParsePubKey(batch.nodeAnn2.NodeID[:]) if err != nil { t.Fatalf("unable to parse pubkey: %v", err) } @@ -1369,7 +1362,7 @@ func TestSignatureAnnouncementRetryAtStartup(t *testing.T) { t.Fatalf("can't generate announcements: %v", err) } - remoteKey, err := btcec.ParsePubKey(batch.nodeAnn2.NodeID[:], btcec.S256()) + remoteKey, err := btcec.ParsePubKey(batch.nodeAnn2.NodeID[:]) if err != nil { t.Fatalf("unable to parse pubkey: %v", err) } @@ -1586,7 +1579,7 @@ func TestSignatureAnnouncementFullProofWhenRemoteProof(t *testing.T) { t.Fatalf("can't generate announcements: %v", err) } - remoteKey, err := btcec.ParsePubKey(batch.nodeAnn2.NodeID[:], btcec.S256()) + remoteKey, err := btcec.ParsePubKey(batch.nodeAnn2.NodeID[:]) if err != nil { t.Fatalf("unable to parse pubkey: %v", err) } @@ -2016,7 +2009,7 @@ func TestDeDuplicatedAnnouncements(t *testing.T) { // indexes as due to the randomized order of map iteration they may be // in either place. if !reflect.DeepEqual(batch[2].msg, na) && !reflect.DeepEqual(batch[3].msg, na) { - t.Fatal("first node announcement not in last part of batch: "+ + t.Fatalf("first node announcement not in last part of batch: "+ "got %v, expected %v", batch[2].msg, na) } @@ -2329,8 +2322,8 @@ func TestProcessZombieEdgeNowLive(t *testing.T) { } // We'll also add the edge to our zombie index, provide a blank pubkey - // for the first node as we're simulating the sitaution where the first - // ndoe is updating but the second node isn't. In this case we only + // for the first node as we're simulating the situation where the first + // node is updating but the second node isn't. In this case we only // want to allow a new update from the second node to allow the entire // edge to be resurrected. chanID := batch.chanAnn.ShortChannelID @@ -2350,7 +2343,7 @@ func TestProcessZombieEdgeNowLive(t *testing.T) { } processAnnouncement(batch.chanUpdAnn1, true, true) - // At this point, the channel should still be consiered a zombie. + // At this point, the channel should still be considered a zombie. _, _, _, err = ctx.router.GetChannelByID(chanID) if err != channeldb.ErrZombieEdge { t.Fatalf("channel should still be a zombie") @@ -2437,7 +2430,7 @@ func TestReceiveRemoteChannelUpdateFirst(t *testing.T) { t.Fatalf("can't generate announcements: %v", err) } - remoteKey, err := btcec.ParsePubKey(batch.nodeAnn2.NodeID[:], btcec.S256()) + remoteKey, err := btcec.ParsePubKey(batch.nodeAnn2.NodeID[:]) if err != nil { t.Fatalf("unable to parse pubkey: %v", err) } @@ -2448,7 +2441,7 @@ func TestReceiveRemoteChannelUpdateFirst(t *testing.T) { remotePeer := &mockPeer{remoteKey, sentMsgs, ctx.gossiper.quit} // Override NotifyWhenOnline to return the remote peer which we expect - // meesages to be sent to. + // messages to be sent to. ctx.gossiper.reliableSender.cfg.NotifyWhenOnline = func(peer [33]byte, peerChan chan<- lnpeer.Peer) { @@ -2645,7 +2638,7 @@ func TestExtraDataChannelAnnouncementValidation(t *testing.T) { // We'll now create an announcement that contains an extra set of bytes // that we don't know of ourselves, but should still include in the // final signature check. - extraBytes := []byte("gotta validate this stil!") + extraBytes := []byte("gotta validate this still!") ca, err := createRemoteChannelAnnouncement(0, extraBytes) if err != nil { t.Fatalf("can't create channel announcement: %v", err) @@ -2822,13 +2815,13 @@ func TestRetransmit(t *testing.T) { t.Fatalf("can't generate announcements: %v", err) } - remoteKey, err := btcec.ParsePubKey(batch.nodeAnn2.NodeID[:], btcec.S256()) + remoteKey, err := btcec.ParsePubKey(batch.nodeAnn2.NodeID[:]) if err != nil { t.Fatalf("unable to parse pubkey: %v", err) } remotePeer := &mockPeer{remoteKey, nil, nil} - // Process a local channel annoucement, channel update and node + // Process a local channel announcement, channel update and node // announcement. No messages should be broadcasted yet, since no proof // has been exchanged. assertProcessAnnouncement( @@ -2893,6 +2886,7 @@ func TestRetransmit(t *testing.T) { if chanAnn != chanAnns || chanUpd != chanUpds || nodeAnn != nodeAnns { + t.Fatalf("unexpected number of announcements: "+ "chanAnn=%d, chanUpd=%d, nodeAnn=%d", chanAnn, chanUpd, nodeAnn) @@ -2934,8 +2928,7 @@ func TestNodeAnnouncementNoChannels(t *testing.T) { t.Fatalf("can't generate announcements: %v", err) } - remoteKey, err := btcec.ParsePubKey(batch.nodeAnn2.NodeID[:], - btcec.S256()) + remoteKey, err := btcec.ParsePubKey(batch.nodeAnn2.NodeID[:]) if err != nil { t.Fatalf("unable to parse pubkey: %v", err) } @@ -2961,7 +2954,7 @@ func TestNodeAnnouncementNoChannels(t *testing.T) { } // Now add the node's channel to the graph by processing the channel - // announement and channel update. + // announcement and channel update. select { case err = <-ctx.gossiper.ProcessRemoteAnnouncement(batch.chanAnn, remotePeer): @@ -3002,7 +2995,7 @@ func TestNodeAnnouncementNoChannels(t *testing.T) { } } - // Processing the same node announement again should be ignored, as it + // Processing the same node announcement again should be ignored, as it // is stale. select { case err = <-ctx.gossiper.ProcessRemoteAnnouncement(batch.nodeAnn2, @@ -3129,7 +3122,7 @@ func TestSendChannelUpdateReliably(t *testing.T) { // We'll also create two keys, one for ourselves and another for the // remote party. - remoteKey, err := btcec.ParsePubKey(batch.nodeAnn2.NodeID[:], btcec.S256()) + remoteKey, err := btcec.ParsePubKey(batch.nodeAnn2.NodeID[:]) if err != nil { t.Fatalf("unable to parse pubkey: %v", err) } @@ -4078,7 +4071,7 @@ func TestIgnoreOwnAnnouncement(t *testing.T) { t.Fatalf("can't generate announcements: %v", err) } - remoteKey, err := btcec.ParsePubKey(batch.nodeAnn2.NodeID[:], btcec.S256()) + remoteKey, err := btcec.ParsePubKey(batch.nodeAnn2.NodeID[:]) if err != nil { t.Fatalf("unable to parse pubkey: %v", err) } @@ -4245,7 +4238,7 @@ func TestRejectCacheChannelAnn(t *testing.T) { t.Fatalf("can't generate announcements: %v", err) } - remoteKey, err := btcec.ParsePubKey(batch.nodeAnn2.NodeID[:], btcec.S256()) + remoteKey, err := btcec.ParsePubKey(batch.nodeAnn2.NodeID[:]) if err != nil { t.Fatalf("unable to parse pubkey: %v", err) } diff --git a/discovery/message_store_test.go b/discovery/message_store_test.go index e70210db16..7542f82c6b 100644 --- a/discovery/message_store_test.go +++ b/discovery/message_store_test.go @@ -8,7 +8,7 @@ import ( "reflect" "testing" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/kvdb" @@ -43,7 +43,7 @@ func createTestMessageStore(t *testing.T) (*MessageStore, func()) { } func randPubKey(t *testing.T) *btcec.PublicKey { - priv, err := btcec.NewPrivateKey(btcec.S256()) + priv, err := btcec.NewPrivateKey() if err != nil { t.Fatalf("unable to create private key: %v", err) } diff --git a/discovery/mock_test.go b/discovery/mock_test.go index 714f6b4ac0..0040ab8b66 100644 --- a/discovery/mock_test.go +++ b/discovery/mock_test.go @@ -5,7 +5,7 @@ import ( "net" "sync" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/lnpeer" diff --git a/discovery/reliable_sender.go b/discovery/reliable_sender.go index 882eeebee1..b22a15ae1e 100644 --- a/discovery/reliable_sender.go +++ b/discovery/reliable_sender.go @@ -131,10 +131,10 @@ spawnHandler: // spawnPeerMsgHandler spawns a peerHandler for the given peer if there isn't // one already active. The boolean returned signals whether there was already // one active or not. -func (s *reliableSender) spawnPeerHandler(peerPubKey [33]byte) (peerManager, bool) { - s.activePeersMtx.Lock() - defer s.activePeersMtx.Unlock() +func (s *reliableSender) spawnPeerHandler( + peerPubKey [33]byte) (peerManager, bool) { + s.activePeersMtx.Lock() msgHandler, ok := s.activePeers[peerPubKey] if !ok { msgHandler = peerManager{ @@ -142,7 +142,12 @@ func (s *reliableSender) spawnPeerHandler(peerPubKey [33]byte) (peerManager, boo done: make(chan struct{}), } s.activePeers[peerPubKey] = msgHandler + } + s.activePeersMtx.Unlock() + // If this is a newly initiated peerManager, we will create a + // peerHandler. + if !ok { s.wg.Add(1) go s.peerHandler(msgHandler, peerPubKey) } diff --git a/discovery/reliable_sender_test.go b/discovery/reliable_sender_test.go index 66d1767ff8..d1e69b11fb 100644 --- a/discovery/reliable_sender_test.go +++ b/discovery/reliable_sender_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/lnpeer" "github.com/lightningnetwork/lnd/lntest/wait" @@ -20,7 +20,7 @@ func newTestReliableSender(t *testing.T) *reliableSender { cfg := &reliableSenderCfg{ NotifyWhenOnline: func(pubKey [33]byte, peerChan chan<- lnpeer.Peer) { - pk, err := btcec.ParsePubKey(pubKey[:], btcec.S256()) + pk, err := btcec.ParsePubKey(pubKey[:]) if err != nil { t.Fatalf("unable to parse pubkey: %v", err) } diff --git a/discovery/sync_manager.go b/discovery/sync_manager.go index 68c277c6a8..ab0f0af83a 100644 --- a/discovery/sync_manager.go +++ b/discovery/sync_manager.go @@ -220,7 +220,7 @@ func (m *SyncManager) syncerHandler() { initialHistoricalSyncer *GossipSyncer // initialHistoricalSyncSignal is a signal that will fire once - // the intiial historical sync has been completed. This is + // the initial historical sync has been completed. This is // crucial to ensure that another historical sync isn't // attempted just because the initialHistoricalSyncer was // disconnected. @@ -232,7 +232,7 @@ func (m *SyncManager) syncerHandler() { initialHistoricalSyncSignal = s.ResetSyncedSignal() // Restart the timer for our new historical sync peer. This will - // ensure that all initial syncers recevie an equivalent + // ensure that all initial syncers receive an equivalent // duration before attempting the next sync. Without doing so we // might attempt two historical sync back to back if a peer // disconnects just before the ticker fires. @@ -362,7 +362,7 @@ func (m *SyncManager) syncerHandler() { // Otherwise, our initialHistoricalSyncer corresponds to // the peer being disconnected, so we'll have to find a // replacement. - log.Debug("Finding replacement for intitial " + + log.Debug("Finding replacement for initial " + "historical sync") s := m.forceHistoricalSync() @@ -398,6 +398,13 @@ func (m *SyncManager) syncerHandler() { continue } + // We may not even have enough inactive syncers to be + // transitted. In that case, we will transit all the + // inactive syncers. + if len(m.inactiveSyncers) < numActiveLeft { + numActiveLeft = len(m.inactiveSyncers) + } + log.Debugf("Attempting to transition %v passive "+ "GossipSyncers to active", numActiveLeft) @@ -492,6 +499,10 @@ func (m *SyncManager) createGossipSyncer(peer lnpeer.Peer) *GossipSyncer { // handle any sync transitions. s.setSyncState(chansSynced) s.setSyncType(PassiveSync) + + log.Debugf("Created new GossipSyncer[state=%s type=%s] for peer=%v", + s.syncState(), s.SyncType(), peer) + return s } diff --git a/discovery/sync_manager_test.go b/discovery/sync_manager_test.go index f2113ff5b4..f6861f0c64 100644 --- a/discovery/sync_manager_test.go +++ b/discovery/sync_manager_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/lntest/wait" "github.com/lightningnetwork/lnd/lnwire" @@ -76,7 +76,6 @@ func TestSyncManagerNumActiveSyncers(t *testing.T) { pinnedSyncers[vertex] = struct{}{} pinnedPubkeys[vertex] = pubkey - } syncMgr := newPinnedTestSyncManager(numActiveSyncers, pinnedSyncers) diff --git a/discovery/syncer.go b/discovery/syncer.go index de6821e317..11424a61ea 100644 --- a/discovery/syncer.go +++ b/discovery/syncer.go @@ -842,6 +842,7 @@ func (g *GossipSyncer) processChanRangeReply(msg *lnwire.ReplyChannelRange) erro // behind a few blocks from us. if replyLastHeight < queryLastHeight && g.numChanRangeRepliesRcvd < maxReplies { + return nil } } @@ -1377,6 +1378,7 @@ func (g *GossipSyncer) ProcessQueryMsg(msg lnwire.Message, peerQuit <-chan struc syncState := g.syncState() if syncState != waitingQueryRangeReply && syncState != waitingQueryChanReply { + return fmt.Errorf("received unexpected query reply "+ "message %T", msg) } diff --git a/discovery/syncer_test.go b/discovery/syncer_test.go index 40e759f91d..01afaf3911 100644 --- a/discovery/syncer_test.go +++ b/discovery/syncer_test.go @@ -951,7 +951,7 @@ func TestGossipSyncerReplyChanRangeQuery(t *testing.T) { // TestGossipSyncerReplyChanRangeQuery tests a variety of // QueryChannelRange messages to ensure the underlying queries are -// executed with the correct block range +// executed with the correct block range. func TestGossipSyncerReplyChanRangeQueryBlockRange(t *testing.T) { t.Parallel() @@ -1027,7 +1027,6 @@ func TestGossipSyncerReplyChanRangeQueryBlockRange(t *testing.T) { resultsCh <- capFilterReqs return } - } }() @@ -2126,7 +2125,7 @@ func TestGossipSyncerSyncTransitions(t *testing.T) { select { case msgs := <-msgChan: if len(msgs) != 1 { - t.Fatal("expected to send a single message at "+ + t.Fatalf("expected to send a single message at "+ "a time, got %d", len(msgs)) } msgSent = msgs[0] diff --git a/docker/btcd/Dockerfile b/docker/btcd/Dockerfile index a87d081a84..3cbc91f144 100644 --- a/docker/btcd/Dockerfile +++ b/docker/btcd/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.17.1-alpine as builder +FROM golang:1.17.3-alpine as builder LABEL maintainer="Olaoluwa Osuntokun " @@ -13,7 +13,7 @@ ARG BTCD_VERSION=v0.20.1-beta # Grab and install the latest version of of btcd and all related dependencies. RUN git clone https://github.com/btcsuite/btcd.git . \ && git checkout $BTCD_VERSION \ - && GO111MODULE=on go install -v . ./cmd/... + && go install -v . ./cmd/... # Start a new image FROM alpine as final diff --git a/docker/ltcd/Dockerfile b/docker/ltcd/Dockerfile index 944fb87f68..6b78cff446 100644 --- a/docker/ltcd/Dockerfile +++ b/docker/ltcd/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.17.1-alpine as builder +FROM golang:1.17.3-alpine as builder LABEL maintainer="Olaoluwa Osuntokun " @@ -7,8 +7,8 @@ LABEL maintainer="Olaoluwa Osuntokun " WORKDIR $GOPATH/src/github.com/ltcsuite/ltcd RUN apk add --no-cache --update alpine-sdk git RUN git clone https://github.com/ltcsuite/ltcd ./ -RUN GO111MODULE=on go install -v . ./cmd/... -RUN GO111MODULE=on go install . ./cmd/ltcctl ./cmd/gencerts +RUN go install -v . ./cmd/... +RUN go install . ./cmd/ltcctl ./cmd/gencerts # Start a new image FROM alpine as final diff --git a/docs/DOCKER.md b/docs/DOCKER.md index 74142fcaa6..fc4b1e48b2 100644 --- a/docs/DOCKER.md +++ b/docs/DOCKER.md @@ -29,7 +29,7 @@ Docker container, adding the appropriate command-line options as parameters. You first need to build the `lnd` docker image: ```shell -⛰ docker build --tag=myrepository/lnd --build-arg checkout=v0.11.1-beta . +⛰ docker build --tag=myrepository/lnd --build-arg checkout=v0.14.1-beta . ``` It is recommended that you checkout the latest released tag. @@ -49,10 +49,13 @@ images of `lnd` available in the You can just pull those images by specifying a release tag: ```shell -⛰ docker pull lightninglabs/lnd:v0.12.0-beta +⛰ docker pull lightninglabs/lnd:v0.14.1-beta ⛰ docker run lightninglabs/lnd [command-line options] ``` +Note that **`daily-*` tags are unstable and not for production use**. +They are only suitable for development and pre-release testing. + ### Verifying docker images To verify the `lnd` and `lncli` binaries inside the docker images against the @@ -61,10 +64,10 @@ script in the image that can be called (before starting the container for example): ```shell -⛰ docker run --rm --entrypoint="" lightninglabs/lnd:v0.12.1-beta /verify-install.sh v0.12.1-beta +⛰ docker run --rm --entrypoint="" lightninglabs/lnd:v0.14.1-beta /verify-install.sh v0.14.1-beta ⛰ OK=$? ⛰ if [ "$OK" -ne "0" ]; then echo "Verification failed!"; exit 1; done -⛰ docker run lightninglabs/lnd [command-line options] +⛰ docker run lightninglabs/lnd:v0.14.1-beta [command-line options] ``` ## Volumes @@ -115,10 +118,10 @@ To test the Docker production image locally, run the following from the project ⛰ docker build . -t myrepository/lnd:master ``` -To choose a specific [branch](https://github.com/lightningnetwork/lnd/branches) or [tag](https://hub.docker.com/r/lightninglabs/lnd/tags?page=1&ordering=last_updated) instead, use the "checkout" build-arg. For example, to build the latest tagged commit: +To choose a specific [branch](https://github.com/lightningnetwork/lnd/branches) or [tag](https://hub.docker.com/r/lightninglabs/lnd/tags?page=1&ordering=last_updated) instead, use the `checkout` build-arg. For example, to build the latest tagged commit: ```shell -⛰ docker build . --build-arg checkout=v0.13.0-beta -t myrepository/lnd:v0.13.0-beta +⛰ docker build . --build-arg checkout=v0.14.1-beta -t myrepository/lnd:v0.14.1-beta ``` To build the image using the most current tag: @@ -127,6 +130,12 @@ To build the image using the most current tag: ⛰ docker build . --build-arg checkout=$(git describe --tags `git rev-list --tags --max-count=1`) -t myrepository/lnd:latest-tag ``` +You can also specify a different repo than the default (`https://github.com/lightningnetwork/lnd`) using the `git_url` build-arg: + +```shell +⛰ docker build . --build-arg git_url=https://git.example.com/lnd.git --build-arg checkout=mybranch -t myrepository/lnd:v0.13.0-beta +``` + Once the image has been built and tagged locally, start the container: ```shell diff --git a/docs/INSTALL.md b/docs/INSTALL.md index 4c732ac9d7..d14c4be2b0 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -1,25 +1,27 @@ # Table of Contents -* [Installation](#installation) - * [Installing a binary release](#installing-a-binary-release) - * [Building a tagged version with Docker](#building-a-tagged-version-with-docker) - * [Building a development version from source](#building-a-development-version-from-source) - * [Preliminaries](#preliminaries-for-installing-from-source) - * [Installing lnd](#installing-lnd-from-source) -* [Available Backend Operating Modes](#available-backend-operating-modes) - * [btcd Options](#btcd-options) - * [Neutrino Options](#neutrino-options) - * [Bitcoind Options](#bitcoind-options) - * [Using btcd](#using-btcd) - * [Installing btcd](#installing-btcd) - * [Starting btcd](#starting-btcd) - * [Running lnd using the btcd backend](#running-lnd-using-the-btcd-backend) - * [Using Neutrino](#using-neutrino) - * [Using bitcoind or litecoind](#using-bitcoind-or-litecoind) -* [Creating a Wallet](#creating-a-wallet) -* [Macaroons](#macaroons) -* [Network Reachability](#network-reachability) -* [Simnet vs. Testnet Development](#simnet-vs-testnet-development) -* [Creating an lnd.conf (Optional)](#creating-an-lndconf-optional) +- [Table of Contents](#table-of-contents) +- [Installation](#installation) + - [Installing a binary release](#installing-a-binary-release) + - [Building a tagged version with Docker](#building-a-tagged-version-with-docker) + - [Building a development version from source](#building-a-development-version-from-source) + - [Installing Go](#installing-go) + - [Go modules](#go-modules) + - [Installing lnd from source](#installing-lnd-from-source) +- [Available Backend Operating Modes](#available-backend-operating-modes) + - [btcd Options](#btcd-options) + - [Neutrino Options](#neutrino-options) + - [Bitcoind Options](#bitcoind-options) + - [Using btcd](#using-btcd) + - [Installing btcd](#installing-btcd) + - [Starting btcd](#starting-btcd) + - [Running lnd using the btcd backend](#running-lnd-using-the-btcd-backend) + - [Using Neutrino](#using-neutrino) + - [Using bitcoind or litecoind](#using-bitcoind-or-litecoind) +- [Creating a wallet](#creating-a-wallet) +- [Macaroons](#macaroons) +- [Network Reachability](#network-reachability) +- [Simnet vs. Testnet Development](#simnet-vs-testnet-development) +- [Creating an lnd.conf (Optional)](#creating-an-lndconf-optional) # Installation @@ -86,84 +88,110 @@ recommended for mainnet. The `master` branch can at times be unstable and running your node off of it can prevent it to go back to a previous, stable version if there are database migrations present. -### Preliminaries for installing from source - In order to work with [`lnd`](https://github.com/lightningnetwork/lnd), the - following build dependencies are required: - - * **Go:** `lnd` is written in Go. To install, run one of the following commands: - - - **Note**: The minimum version of Go supported is Go 1.16. We recommend that - users use the latest version of Go, which at the time of writing is - [`1.17.1`](https://blog.golang.org/go1.17.1). - - - On Linux: - - (x86-64) - ``` - wget https://dl.google.com/go/go1.17.1.linux-amd64.tar.gz - sha256sum go1.17.1.linux-amd64.tar.gz | awk -F " " '{ print $1 }' - ``` - - The final output of the command above should be - `dab7d9c34361dc21ec237d584590d72500652e7c909bf082758fb63064fca0ef`. If it - isn't, then the target REPO HAS BEEN MODIFIED, and you shouldn't install - this version of Go. If it matches, then proceed to install Go: - ``` - sudo tar -C /usr/local -xzf go1.17.1.linux-amd64.tar.gz - export PATH=$PATH:/usr/local/go/bin - ``` - - (ARMv6) - ``` - wget https://dl.google.com/go/go1.17.1.linux-armv6l.tar.gz - sha256sum go1.17.1.linux-armv6l.tar.gz | awk -F " " '{ print $1 }' - ``` - - The final output of the command above should be - `ed3e4dbc9b80353f6482c441d65b51808290e94ff1d15d56da5f4a7be7353758`. If it - isn't, then the target REPO HAS BEEN MODIFIED, and you shouldn't install - this version of Go. If it matches, then proceed to install Go: - ``` - tar -C /usr/local -xzf go1.17.1.linux-armv6l.tar.gz - export PATH=$PATH:/usr/local/go/bin - ``` - - On Mac OS X: - ``` - brew install go@1.17.1 - ``` - - On FreeBSD: - ``` - pkg install go - ``` - - Alternatively, one can download the pre-compiled binaries hosted on the - [Golang download page](https://golang.org/dl/). If one seeks to install - from source, then more detailed installation instructions can be found - [here](https://golang.org/doc/install). - - At this point, you should set your `$GOPATH` environment variable, which - represents the path to your workspace. By default, `$GOPATH` is set to - `~/go`. You will also need to add `$GOPATH/bin` to your `PATH`. This ensures - that your shell will be able to detect the binaries you install. - - ```shell - ⛰ export GOPATH=~/gocode - ⛰ export PATH=$PATH:$GOPATH/bin - ``` - - We recommend placing the above in your .bashrc or in a setup script so that - you can avoid typing this every time you open a new terminal window. - - * **Go modules:** This project uses [Go modules](https://github.com/golang/go/wiki/Modules) - to manage dependencies as well as to provide *reproducible builds*. - - Usage of Go modules (with Go 1.13) means that you no longer need to clone - `lnd` into your `$GOPATH` for development purposes. Instead, your `lnd` - repo can now live anywhere! +In order to work with [`lnd`](https://github.com/lightningnetwork/lnd), the +following build dependencies are required: + +### Installing Go + +`lnd` is written in Go, with a minimum version of 1.18. To install, run one of +the following commands for your OS: + +
+ Linux (x86-64) + + ``` + wget https://dl.google.com/go/go1.18.linux-amd64.tar.gz + sha256sum go1.18.linux-amd64.tar.gz | awk -F " " '{ print $1 }' + ``` + + The final output of the command above should be + `e85278e98f57cdb150fe8409e6e5df5343ecb13cebf03a5d5ff12bd55a80264f`. If it + isn't, then the target REPO HAS BEEN MODIFIED, and you shouldn't install + this version of Go. If it matches, then proceed to install Go: + ``` + sudo tar -C /usr/local -xzf go1.18.linux-amd64.tar.gz + export PATH=$PATH:/usr/local/go/bin + ``` +
+ +
+ Linux (ARMv6) + + ``` + wget https://dl.google.com/go/go1.18.linux-armv6l.tar.gz + sha256sum go1.18.linux-armv6l.tar.gz | awk -F " " '{ print $1 }' + ``` + + The final output of the command above should be + `a80fa43d1f4575fb030adbfbaa94acd860c6847820764eecb06c63b7c103612b`. If it + isn't, then the target REPO HAS BEEN MODIFIED, and you shouldn't install + this version of Go. If it matches, then proceed to install Go: + ``` + tar -C /usr/local -xzf go1.17.1.linux-armv6l.tar.gz + export PATH=$PATH:/usr/local/go/bin + ``` + +
+ +
+ macOS + + First, install [Homebrew](https://brew.sh) if you don‘t already have it. + + Then + + ``` + brew install go + ``` + +
+ +
+ FreeBSD + + ``` + pkg install go + ``` + + Alternatively, one can download the pre-compiled binaries hosted on the + [Golang download page](https://golang.org/dl/). If one seeks to install + from source, then more detailed installation instructions can be found + [here](https://golang.org/doc/install). +
+ +***Important*** + +At this point, you should set your `$GOPATH` environment variable, which +represents the path to your workspace. By default, `$GOPATH` is set to +`~/go`. You will also need to add `$GOPATH/bin` to your `PATH`. This ensures +that your shell will be able to detect the binaries you install. + +```shell +⛰ export GOPATH=~/go +⛰ export PATH=$PATH:$GOPATH/bin +``` + +--- + +We recommend placing the above in your `.bashrc`, `.zshrc` or in a setup script +so that you can avoid typing this every time you open a new terminal window. + +### Go modules + +This project uses [Go modules](https://github.com/golang/go/wiki/Modules) +to manage dependencies as well as to provide *reproducible builds*. + +Usage of Go modules (with Go 1.13) means that you no longer need to clone +`lnd` into your `$GOPATH` for development purposes. Instead, your `lnd` +repo can now live anywhere! + +--- +Note: For mobile development, having the source code in `$GOPATH` is still +required due to a current limitation in +[Go mobile](https://pkg.go.dev/golang.org/x/mobile). Take a look at the +documentation for [building mobile libraries](../mobile) to learn more. + +--- ### Installing lnd from source @@ -188,7 +216,6 @@ a small modification to the above command: ⛰ make install ``` - **NOTE**: Our instructions still use the `$GOPATH` directory from prior versions of Go, but with Go 1.13, it's now possible for `lnd` to live _anywhere_ on your file system. @@ -208,7 +235,34 @@ On FreeBSD, use gmake instead of make. Alternatively, if one doesn't wish to use `make`, then the `go` commands can be used directly: ```shell -⛰ GO111MODULE=on go install -v ./... +⛰ go install -v ./... +``` + +**Tags** + +Release binaries and installations from source using `make release-install` +will have the following tags: + +- [autopilotrpc](/lnrpc/autopilotrpc/autopilot.proto) +- [signrpc](/lnrpc/signrpc/signer.proto) +- [walletrpc](/lnrpc/walletrpc/walletkit.proto) +- [chainrpc](/lnrpc/chainrpc/chainnotifier.proto) +- [invoicesrpc](/lnrpc/invoicesrpc/invoices.proto) +- [neutrinorpc](/lnrpc/neutrinorpc/neutrino.proto) +- [routerrpc](/lnrpc/routerrpc/router.proto) +- [watchtowerrpc](/lnrpc/watchtowerrpc/watchtower.proto) +- [monitoring](/monitoring) (for Prometheus integration) +- [peersrpc](/lnrpc/peersrpc/peers.proto) +- [kvdb_postrgres](/docs/postgres.md) +- [kvdb_etcd](/docs/etcd.md) + +The `dev` tag is used for development builds, and is not included in the +release builds & installation. + +You can specify a custom set of tags when installing from source using the `tags=""` parameter. For example: + +```shell +make install tags="signrpc walletrpc routerrpc invoicesrpc" ``` **Updating** @@ -228,7 +282,7 @@ used directly: ```shell ⛰ cd $GOPATH/src/github.com/lightningnetwork/lnd ⛰ git pull -⛰ GO111MODULE=on go install -v ./... +⛰ go install -v ./... ``` **Tests** @@ -241,6 +295,11 @@ To check that `lnd` was installed properly run the following command: This command requires `bitcoind` (almost any version should do) to be available in the system's `$PATH` variable. Otherwise some of the tests will fail. +**Command-line completion for `lncli`** + +_Bash_: See `contrib/lncli.bash-completion` +_Fish_: Run: `lncli fish-completion > $HOME/.config/fish/completions/lncli.fish` + # Available Backend Operating Modes In order to run, `lnd` requires, that the user specify a chain backend. At the @@ -376,7 +435,7 @@ in `--bitcoin.simnet` if needed), and also your own `btcd` node if available: ## Using bitcoind or litecoind The configuration for bitcoind and litecoind are nearly identical, the -following steps can be mirrored with loss of generality to enable a litecoind +following steps can be mirrored without loss of generality to enable a litecoind backend. Setup will be described in regards to `bitcoind`, but note that `lnd` uses a distinct `litecoin.node=litecoind` argument and analogous subconfigurations prefixed by `litecoind`. Note that adding `--txindex` is @@ -453,7 +512,7 @@ below): `lnd` plus any application that consumes the RPC could cause `lnd` to miss crucial updates from the backend. - The default fee estimate mode in `bitcoind` is CONSERVATIVE. You can set - `bitcoind.estimatemode=ECONOMICAL` to change it into ECONOMICAL. Futhermore, + `bitcoind.estimatemode=ECONOMICAL` to change it into ECONOMICAL. Furthermore, if you start `bitcoind` in `regtest`, this configuration won't take any effect. @@ -479,8 +538,9 @@ bearer credentials allowing for delegation, attenuation, and other cool features. You can learn more about them in Alex Akselrod's [writeup on Github](https://github.com/lightningnetwork/lnd/issues/20). -Running `lnd` for the first time will by default generate the `admin.macaroon`, -`read_only.macaroon`, and `macaroons.db` files that are used to authenticate +Running `lncli create` to create a wallet, will by default generate +the `admin.macaroon`, `read_only.macaroon`, and `macaroons.db` +files that are used to authenticate into `lnd`. They will be stored in the network directory (default: `lnddir/data/chain/bitcoin/mainnet`) so that it's possible to use a distinct password for mainnet, testnet, simnet, etc. Note that if you specified an diff --git a/docs/MAKEFILE.md b/docs/MAKEFILE.md index 8878a84219..cf47a948a8 100644 --- a/docs/MAKEFILE.md +++ b/docs/MAKEFILE.md @@ -179,7 +179,7 @@ Arguments: `unit-cover` ------------ -Runs the unit test suite with test coverage, compiling the statisitics in +Runs the unit test suite with test coverage, compiling the statistics in `profile.cov`. Arguments: @@ -206,4 +206,3 @@ Related: [`unit`](#unit) [btcd]: https://github.com/btcsuite/btcd (github.com/btcsuite/btcd") [gometalinter]: https://gopkg.in/alecthomas/gometalinter.v1 (gopkg.in/alecthomas/gometalinter.v1) -[goveralls]: https://github.com/mattn/goveralls (github.com/mattn/goveralls) diff --git a/docs/code_contribution_guidelines.md b/docs/code_contribution_guidelines.md index a2032a705a..23565164da 100644 --- a/docs/code_contribution_guidelines.md +++ b/docs/code_contribution_guidelines.md @@ -8,15 +8,16 @@ 1. [Code Documentation and Commenting](#code-documentation-and-commenting) 1. [Model Git Commit Messages](#model-git-commit-messages) 1. [Ideal Git Commit Structure](#ideal-git-commit-structure) - 1. [Code Spacing](#code-spacing) - 1. [Protobuf Compilation](#protobuf-compilation) - 1. [Additional Style Constraints On Top of gofmt](#additional-style-constraints-on-top-of-gofmt) - 1. [Pointing to Remote Dependant Branches in Go Modules](#pointing-to-remote-dependant-branches-in-go-modules) + 1. [Sign Your Git Commits](#sign-your-git-commits) + 1. [Code Spacing and formatting](#code-spacing-and-formatting) + 1. [Pointing to Remote Dependent Branches in Go Modules](#pointing-to-remote-dependent-branches-in-go-modules) 1. [Use of Log Levels](#use-of-log-levels) + 1. [Use of Golang submodules](#use-of-golang-submodules) 5. [Code Approval Process](#code-approval-process) 1. [Code Review](#code-review) 1. [Rework Code (if needed)](#rework-code-if-needed) 1. [Acceptance](#acceptance) + 1. [Review Bot](#review-bot) 6. [Contribution Standards](#contribution-standards) 1. [Contribution Checklist](#contribution-checklist) 1. [Licensing of Contributions](#licensing-of-contributions) @@ -83,7 +84,7 @@ security and performance implications. foundation to build on will make the code much more comprehensible. - [Lightning Network Whitepaper](https://lightning.network/lightning-network-paper.pdf) - This is the white paper that kicked off the Layer 2 revolution. Having a good grasp of the concepts of Lightning will make the core logic within the daemon much more comprehensible: Bitcoin Script, off-chain blockchain protocols, payment channels, bi-directional payment channels, relative and absolute time-locks, commitment state revocations, and Segregated Witness. - The original LN was written for a rather narrow audience, the paper may be a bit unapproachable to many. Thanks to the Bitcoin community, there exist many easily accessible supplemental resources which can help one see how all the pieces fit together from double-spend protection all the way up to commitment state transitions and Hash Time Locked Contracts (HTLCs): - - [Lightning Network Summary](https://lightning.network/lightning-network-summary.pdf) + - [Lightning Network Summary](https://lightning.network/lightning-network-summary.pdf) - [Understanding the Lightning Network 3-Part series](https://bitcoinmagazine.com/articles/understanding-the-lightning-network-part-building-a-bidirectional-payment-channel-1464710791) - [Deployable Lightning](https://github.com/ElementsProject/lightning/blob/master/doc/deployable-lightning.pdf) @@ -154,7 +155,7 @@ A quick summary of test practices follows: or RPC's will need to be accompanied by integration tests which use the [`networkHarness`framework](https://github.com/lightningnetwork/lnd/blob/master/lntest/harness.go) contained within `lnd`. For example integration tests, see - [`lnd_test.go`](https://github.com/lightningnetwork/lnd/blob/master/lnd_test.go#L181). + [`lnd_test.go`](https://github.com/lightningnetwork/lnd/blob/master/lntest/itest/lnd_test.go). - The itest log files are automatically scanned for `[ERR]` lines. There shouldn't be any of those in the logs, see [Use of Log Levels](#use-of-log-levels). @@ -319,8 +320,97 @@ Examples of common patterns w.r.t commit structures within the project: * If a PR only fixes a trivial issue, such as updating documentation on a small scale, fix typos, or any changes that do not modify the code, the commit message should end with `[skip ci]` to skip the CI checks. + +## Sign your git commits -## Code Spacing +When contributing to `lnd` it is recommended to sign your git commits. This is +easy to do and will help in assuring the integrity of the tree. See [mailing +list entry](https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2014-May/005877.html) +for more information. + +### How to sign your commits? + +Provide the `-S` flag (or `--gpg-sign`) to git commit when you commit +your changes, for example + +```shell +⛰ git commit -m "Commit message" -S +``` + +Optionally you can provide a key id after the `-S` option to sign with a +specific key. + +To instruct `git` to auto-sign every commit, add the following lines to your +`~/.gitconfig` file: + +```text +[commit] + gpgsign = true +``` + +### What if I forgot? + +You can retroactively sign your previous commit using `--amend`, for example + +```shell +⛰ git commit -S --amend +``` + +If you need to go further back, you can use the interactive rebase +command with 'edit'. Replace `HEAD~3` with the base commit from which +you want to start. + +```shell +⛰ git rebase -i HEAD~3 +``` + +Replace 'pick' by 'edit' for the commit that you want to sign and the +rebasing will stop after that commit. Then you can amend the commit as +above. Afterwards, do + +```shell +⛰ git rebase --continue +``` + +As this will rewrite history, you cannot do this when your commit is +already merged. In that case, too bad, better luck next time. + +If you rewrite history for another reason - for example when squashing +commits - make sure that you re-sign as the signatures will be lost. + +Multiple commits can also be re-signed with `git rebase`. For example, signing +the last three commits can be done with: + +```shell +⛰ git rebase --exec 'git commit --amend --no-edit -n -S' -i HEAD~3 +``` + +### How to check if commits are signed? + +Use `git log` with `--show-signature`, + +```shell +⛰ git log --show-signature +``` + +You can also pass the `--show-signature` option to `git show` to check a single +commit. + +## Code Spacing and formatting + +### Why this emphasis on formatting? + +Code in general (and Open Source code specifically) is _read_ by developers many +more times during its lifecycle than it is modified. With this fact in mind, the +Golang language was designed for readability (among other goals). +While the enforced formatting of `go fmt` and some best practices already +eliminates many discussions, the resulting code can still look and feel very +differently between different developers. + +We aim to enforce a few additional rules to unify the look and feel of all code +in `lnd` to help improve the overall readability. + +### Spacing Blocks of code within `lnd` should be segmented into logical stanzas of operation. Such spacing makes the code easier to follow at a skim, and reduces @@ -373,103 +463,160 @@ statements and select statements. **WRONG** ```go - switch { - case a: - - case b: - - case c: - - case d: - - default: - - } + switch { + case a: + + case b: + + case c: + + case d: + + default: + + } ``` **RIGHT** ```go - switch { - // Brief comment detailing instances of this case (repeat below). - case a: - + switch { + // Brief comment detailing instances of this case (repeat below). + case a: + - case b: - + case b: + - case c: - + case c: + - case d: - + case d: + - default: - - } + default: + + } ``` -If one is forced to wrap lines of function arguments that exceed the 80 -character limit, then a new line should be inserted before the first stanza in -the comment body. +### Additional Style Constraints -**WRONG** +Before a PR is submitted, the proposer should ensure that the file passes the +set of linting scripts run by `make lint`. These include `gofmt`. In addition +to `gofmt` we've opted to enforce the following style guidelines. + +**80 character line length**: ALL columns (on a best effort basis) should be +wrapped to 80 line columns. +Editors should be set to treat a **tab as 8 spaces**. + + **WRONG** ```go - func foo(a, b, c, - d, e) error { - var a int - } +myKey := "0214cd678a565041d00e6cf8d62ef8add33b4af4786fb2beb87b366a2e151fcee7" ``` + **RIGHT** ```go - func foo(a, b, c, - d, e) error { +myKey := "0214cd678a565041d00e6cf8d62ef8add33b4af4786fb2beb87b366a2e1" + + "51fcee7" +``` + +**Wrapping long function calls**: When wrapping a line that contains a function +call as the unwrapped line exceeds the column limit, the close paren should be +placed on its own line. Additionally, all arguments should begin in a new line +after the open paren. - var a int - } +**WRONG** +```go +value, err := bar(a, + a, b, c) ``` -## Protobuf Compilation +**RIGHT** +```go +value, err := bar( + a, a, b, c, +) +``` -The `lnd` project uses `protobuf`, and its extension [`gRPC`](www.grpc.io) in -several areas and as the primary RPC interface. In order to ensure uniformity -of all protos checked, in we require that all contributors pin against the -_exact same_ version of `protoc`. As of the writing of this article, the `lnd` -project uses [v3.4.0](https://github.com/google/protobuf/releases/tag/v3.4.0) -of `protoc`. +**Wrapping long function definitions**: If one is forced to wrap lines of +function arguments that exceed the 80 character limit, then indentation must be +kept on the following lines. Also, lines should not end with an open +parenthesis if the function definition isn't finished yet. -For detailed instructions on how to compile modifications to `lnd`'s `protobuf` -definitions, check out the [lnrpc README](https://github.com/lightningnetwork/lnd/blob/master/lnrpc/README.md). +**WRONG** +```go +func foo(a, b, c, +) (d, error) { -## Additional Style Constraints On Top of `gofmt` +func bar(a, b, c) ( + d, error, +) { -Before a PR is submitted, the proposer should ensure that the file passes the -set of linting scripts run by `make lint`. These include `gofmt`. In addition -to `gofmt` we've opted to enforce the following style guidelines. +func baz(a, b, c) ( + d, error) { + ``` +**RIGHT** +```go +func foo(a, b, + c) (d, error) { - * ALL columns (on a best effort basis) should be wrapped to 80 line columns. - Editors should be set to treat a tab as 8 spaces. - * When wrapping a line that contains a function call as the unwrapped line - exceeds the column limit, the close paren should be placed on its own - line. Additionally, all arguments should begin in a new line after the - open paren. - - **WRONG** - ```go - value, err := bar(a, - a, b, c) - ``` - - **RIGHT** - ```go - value, err := bar( - a, a, b, c, - ) - ``` +func baz(a, b, c) (d, + error) { +``` + +If a function declaration spans multiple lines the body should start with an +empty line to help visually distinguishing the two elements. + +**WRONG** +```go +func foo(a, b, c, + d, e) error { + var a int +} +``` +**RIGHT** +```go +func foo(a, b, c, + d, e) error { + + var a int +} +``` **Note that the above guidelines don't apply to log or error messages.** For log and error messages, committers should attempt to minimize the number of -lines utilized, while still adhering to the 80-character column limit. +lines utilized, while still adhering to the 80-character column limit. For +example: + +**WRONG** +```go +return fmt.Errorf( + "this is a long error message with a couple (%d) place holders", + len(things), +) + +log.Debugf( + "Something happened here that we need to log: %v", + longVariableNameHere, +) +``` -## Pointing to Remote Dependant Branches in Go Modules +**RIGHT** +```go +return fmt.Errorf("this is a long error message with a couple (%d) place "+ + "holders", len(things)) + +log.Debugf("Something happened here that we need to log: %v", + longVariableNameHere) +``` + +This helps to visually distinguish those formatting statements (where nothing +of consequence happens except for formatting an error message or writing +to a log) from actual method or function calls. This compact formatting should +be used for calls to formatting functions like `fmt.Errorf`, +`log.(Trace|Debug|Info|Warn|Error)f` and `fmt.Printf`. +But not for statements that are important for the flow or logic of the code, +like `require.NoErrorf()`. + +## Pointing to Remote Dependent Branches in Go Modules It's common that a developer may need to make a change in a dependent project of `lnd` such as `btcd`, `neutrino`, `btcwallet`, etc. In order to test changes @@ -494,6 +641,24 @@ Only use `error` for internal errors that are never expected to happen during normal operation. No event triggered by external sources (rpc, chain backend, etc) should lead to an `error` log. +## Use of Golang submodules + +Changes to packages that are their own submodules (e.g. they contain a `go.mod` +and `go.sum` file, for example `tor/go.mod`) require a specific process. +We want to avoid the use of local replace directives in the root `go.mod`, +therefore changes to a submodule are a bit involved. + +The main process for updating and then using code in a submodule is as follows: + - Create a PR for the changes to the submodule itself (e.g. edit something in + the `tor` package) + - Wait for the PR to be merged and a new tag (for example `tor/v1.0.x`) to be + pushed. + - Create a second PR that bumps the updated submodule in the root `go.mod` and + uses the new functionality in the main module. + +Of course the two PRs can be opened at the same time and be built on top of each +other. But the merge and tag push order should always be maintained. + # Code Approval Process This section describes the code approval process that is used for code @@ -571,32 +736,46 @@ these signatures intact, we prefer using merge commits. PR proposers can use Rejoice as you will now be listed as a [contributor](https://github.com/lightningnetwork/lnd/graphs/contributors)! +## Review Bot + +In order to keep the review flow going, Lightning Labs uses a bot to remind +PR reviewers about their outstanding reviews or to remind authors to address +recent reviews. Here are some important things to know about the bot and some +controls for adjusting its behaviour: + +####🤖 Expected Behaviour: +- The bot will not do anything if your PR is in draft mode. +- It will ping a pending reviewer if they have not reviewed or commented on the +PR in x days since the last update or the last time the bot pinged them. +(default x = 3) +- It will ping the author of the PR if they have not addressed a review on a PR +after x days since last review or the last time the bot pinged them. It will +also ping them to remind them to re-request review if needed. (default x = 3) + +####🤖 Controls: +To control the bot, you need to add a comment on the PR starting with +`!lightninglabs-deploy` followed by the command. There are 2 control types: +mute/unmute & cadence. Only the latest comment for each control type will be +used. This also means you dont need to keep adding new control comments, just +edit the latest comment for that control type. + +- `!lightninglabs-deploy mute` will mute the bot on the PR completely. +- `!lightninglabs-deploy mute 72h30m` will mute the bot for the given duration. +- `!lightninglabs-deploy mute 2022-Feb-02` will mute the bot until the given +date (must be in this format!). +- `!lightninglabs-deploy mute #4` will mute the bot until the given PR of the +same repo has been merged. +- `!lightninglabs-deploy unmute` will unmute the bot (or just delete the comment +that was muting it) +- `!lightninglabs-deploy cadence 60h` change the cadence of the bot from the +default of 3 days to the given duration. +- it will auto-mute if the PR is in Draft mode + # Contribution Standards ## Contribution Checklist -- [  ] All changes are Go version 1.12 compliant -- [  ] The code being submitted is commented according to - [Code Documentation and Commenting](#code-documentation-and-commenting) -- [  ] For new code: Code is accompanied by tests which exercise both - the positive and negative (error paths) conditions (if applicable) -- [  ] For bug fixes: Code is accompanied by new tests which trigger - the bug being fixed to prevent regressions -- [  ] Any new logging statements use an appropriate subsystem and - logging level -- [  ] Code has been formatted with `go fmt` -- [  ] For code and documentation: lines are wrapped at 80 characters - (the tab character should be counted as 8 characters, not 4, as some IDEs do - per default) -- [  ] Running `make check` does not fail any tests -- [  ] Running `go vet` does not report any issues -- [  ] Running `make lint` does not report any **new** issues that - did not already exist -- [  ] All commits build properly and pass tests. Only in exceptional - cases it can be justifiable to violate this condition. In that case, the - reason should be stated in the commit message. -- [  ] Commits have a logical structure according to - [Ideal Git Commit Structure](#ideal-git-commit-structure). +See [template](https://github.com/lightningnetwork/lnd/blob/master/.github/pull_request_template.md). ## Licensing of Contributions **** diff --git a/docs/configuring_tor.md b/docs/configuring_tor.md index 1c3c03fe01..a21099c98d 100644 --- a/docs/configuring_tor.md +++ b/docs/configuring_tor.md @@ -182,3 +182,8 @@ base directory. This will allow `lnd` to recreate the same hidden service upon restart. If you wish to generate a new onion service, you can simply delete this file. The path to this private key file can also be modified with the `--tor.privatekeypath` argument. + +You can optionally encrypt the Tor private key by using the `--tor.encryptkey` +flag. This will still write to the same private key files. However instead of +writing the plaintext private key, `lnd` encrypts the private key using the +wallet's seed and writes the encrypted blob to the file. \ No newline at end of file diff --git a/docs/etcd.md b/docs/etcd.md index e15b80594d..60a9dbe0c9 100644 --- a/docs/etcd.md +++ b/docs/etcd.md @@ -2,7 +2,7 @@ With the recent introduction of the `kvdb` interface LND can support multiple database backends allowing experimentation with the storage model as well as -improving robustness trough eg. replicating essential data. +improving robustness through eg. replicating essential data. Building on `kvdb` in v0.11.0 we're adding experimental [etcd](https://etcd.io) support to LND. As this is an unstable feature heavily in development, it still @@ -39,10 +39,8 @@ To start your local etcd instance for testing run: ``` The large `max-txn-ops` and `max-request-bytes` values are currently required in -case of running LND with the full graph in etcd. Upcoming versions will split -the database to local and replicated parts and only essential parts will remain -in the replicated database, removing the requirement for these additional -settings. These parameters have been tested to work with testnet LND. +case of running LND with the full graph in etcd. These parameters have been +tested to work with testnet LND. ## Configuring LND to run on etcd @@ -71,7 +69,7 @@ db.etcd.keyfile=/home/user/etcd/bin/default.etcd/fixtures/client/key.pem db.etcd.insecure_skip_verify=true ``` -Optionally users can specifiy `db.etcd.user` and `db.etcd.pass` for db user +Optionally users can specify `db.etcd.user` and `db.etcd.pass` for db user authentication. If the database is shared, it is possible to separate our data from other users by setting `db.etcd.namespace` to an (already existing) etcd namespace. In order to test without TLS, users are able to set `db.etcd.disabletls` diff --git a/docs/grpc/java.md b/docs/grpc/java.md index d3c291e6c2..933ff1ac1e 100644 --- a/docs/grpc/java.md +++ b/docs/grpc/java.md @@ -19,10 +19,6 @@ with lnd in Java. We'll be using Maven as our build tool. ├── java │ └── Main.java ├── proto - ├── google - │ └── api - │ ├── annotations.proto - │ └── http.proto └── lnrpc └── lightning.proto @@ -30,13 +26,11 @@ with lnd in Java. We'll be using Maven as our build tool. Note the ***proto*** folder, where all the proto files are kept. - [lightning.proto](https://github.com/lightningnetwork/lnd/blob/master/lnrpc/lightning.proto) - - [annotations.proto](https://github.com/grpc-ecosystem/grpc-gateway/blob/master/third_party/googleapis/google/api/annotations.proto) - - [http.proto](https://github.com/grpc-ecosystem/grpc-gateway/blob/master/third_party/googleapis/google/api/http.proto) #### pom.xml ```xml - 1.8.0 + 1.36.0 ``` The following dependencies are required. @@ -60,7 +54,7 @@ The following dependencies are required. io.netty netty-tcnative-boringssl-static - 2.0.7.Final + 2.0.28.Final commons-codec @@ -76,16 +70,16 @@ In the build section, we'll need to configure the following things : kr.motd.maven os-maven-plugin - 1.5.0.Final + 1.6.2.Final org.xolstice.maven.plugins protobuf-maven-plugin - 0.5.0 + 0.6.1 - com.google.protobuf:protoc:3.4.0:exe:${os.detected.classifier} + com.google.protobuf:protoc:3.12.0:exe:${os.detected.classifier} grpc-java io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier} @@ -129,36 +123,30 @@ import java.nio.file.Paths; import java.util.concurrent.Executor; public class Main { - static class MacaroonCallCredential implements CallCredentials { + static class MacaroonCallCredential extends CallCredentials { private final String macaroon; MacaroonCallCredential(String macaroon) { this.macaroon = macaroon; } - public void thisUsesUnstableApi() {} - - public void applyRequestMetadata( - MethodDescriptor < ? , ? > methodDescriptor, - Attributes attributes, - Executor executor, - final MetadataApplier metadataApplier - ) { - String authority = attributes.get(ATTR_AUTHORITY); - System.out.println(authority); - executor.execute(new Runnable() { - public void run() { - try { - Metadata headers = new Metadata(); - Metadata.Key < String > macaroonKey = Metadata.Key.of("macaroon", Metadata.ASCII_STRING_MARSHALLER); - headers.put(macaroonKey, macaroon); - metadataApplier.apply(headers); - } catch (Throwable e) { - metadataApplier.fail(Status.UNAUTHENTICATED.withCause(e)); - } + @Override + public void applyRequestMetadata(RequestInfo requestInfo, Executor executor, MetadataApplier metadataApplier) { + executor.execute(() -> { + try { + Metadata headers = new Metadata(); + Metadata.Key macaroonKey = Metadata.Key.of("macaroon", Metadata.ASCII_STRING_MARSHALLER); + headers.put(macaroonKey, macaroon); + metadataApplier.apply(headers); + } catch (Throwable e) { + metadataApplier.fail(Status.UNAUTHENTICATED.withCause(e)); } }); } + + @Override + public void thisUsesUnstableApi() { + } } private static final String CERT_PATH = "/Users/user/Library/Application Support/Lnd/tls.cert"; @@ -199,19 +187,19 @@ Execute the following command in the directory where the **pom.xml** file is loc [INFO] ------------------------------------------------------------------------ [INFO] os.detected.name: osx [INFO] os.detected.arch: x86_64 -[INFO] os.detected.version: 10.13 +[INFO] os.detected.version: 10.15 [INFO] os.detected.version.major: 10 -[INFO] os.detected.version.minor: 13 +[INFO] os.detected.version.minor: 15 [INFO] os.detected.classifier: osx-x86_64 [INFO] [INFO] ------------------------------------------------------------------------ [INFO] Building lightning-client 0.0.1-SNAPSHOT [INFO] ------------------------------------------------------------------------ [INFO] -[INFO] --- protobuf-maven-plugin:0.5.0:compile (default) @ lightning-client --- +[INFO] --- protobuf-maven-plugin:0.6.1:compile (default) @ lightning-client --- [INFO] Compiling 3 proto file(s) to /Users/user/Documents/Projects/lightningclient/target/generated-sources/protobuf/java [INFO] -[INFO] --- protobuf-maven-plugin:0.5.0:compile-custom (default) @ lightning-client --- +[INFO] --- protobuf-maven-plugin:0.6.1:compile-custom (default) @ lightning-client --- [INFO] Compiling 3 proto file(s) to /Users/user/Documents/Projects/lightningclient/target/generated-sources/protobuf/grpc-java [INFO] [INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ lightning-client --- diff --git a/docs/grpc/javascript.md b/docs/grpc/javascript.md index a36647ce3c..efe99b78b3 100644 --- a/docs/grpc/javascript.md +++ b/docs/grpc/javascript.md @@ -31,8 +31,8 @@ const grpc = require('@grpc/grpc-js'); const protoLoader = require('@grpc/proto-loader'); const fs = require("fs"); -// Due to updated ECDSA generated tls.cert we need to let gprc know that -// we need to use that cipher suite otherwise there will be a handhsake +// Due to updated ECDSA generated tls.cert we need to let gRPC know that +// we need to use that cipher suite otherwise there will be a handshake // error when we communicate with the lnd rpc server. process.env.GRPC_SSL_CIPHER_SUITES = 'HIGH+ECDSA' diff --git a/docs/key_import.md b/docs/key_import.md index 8d6f5d49ca..fd9e62e941 100644 --- a/docs/key_import.md +++ b/docs/key_import.md @@ -117,7 +117,8 @@ default until a key has been imported into them. * `external_key_count`: The number of external addresses generated. * `internal_key_count`: The number of change addresses generated. * `watch_only`: Whether the wallet has private key information for the account. - This is always true for `lnd`'s default wallet accounts. + `lnd`'s default wallet accounts always have private key information, so this + value is `false`. # Key Import diff --git a/docs/leader_election.md b/docs/leader_election.md index 2ca77e1522..91ca04db2f 100644 --- a/docs/leader_election.md +++ b/docs/leader_election.md @@ -1,4 +1,4 @@ -# Increasing LND reliablity by clustering +# Increasing LND reliability by clustering Normally LND nodes use the embedded bbolt database to store all important states. This method of running has been proven to work well in a variety of environments, @@ -9,7 +9,7 @@ do updates and be more resilient to datacenter failures. It is now possible to store all essential state in a replicated etcd DB and to run multiple LND nodes on different machines where only one of them (the leader) is able to read and mutate the database. In such setup if the leader node fails -or decomissioned, a follower node will be elected as the new leader and will +or decommissioned, a follower node will be elected as the new leader and will quickly come online to minimize downtime. The leader election feature currently relies on etcd to work both for the election @@ -94,19 +94,18 @@ readinessProbe: periodSeconds: 1 ``` -## Replication of non-critical data +## What data is written to the replicated remote database? -All critical data is written to the replicated database, including LND's wallet -data which contains the key material and node identity. Some less critical data -however is currently not written to that same database for performance reasons -and is instead still kept in local `bbolt` files. +Beginning with LND 0.14.0 when using a remote database (etcd or PostgreSQL) all +LND data will be written to the replicated database, including the wallet data +which contains the key material and node identity, the graph, the channel state, +the macaroon and the watchtower client databases. This means that when using +leader election there's no need to copy anything between instances of the LND +cluster. -For example the graph data is kept locally to improve path finding. Other examples -are the macaroon database or watchtower client database. To make sure a node can -become active and take over quickly if the leader fails, it is therefore still -recommended to have the LND data directory on a shared volume that all active and -passive nodes can access. Otherwise the node that is taking over might first need -to sync its graph. +## Is leader election supported for Postgres? -As we evolve our cluster support we'll provide more solutions to make replication -and clustering even more seamless. +No, leader election is not supported by Postgres itself since it doesn't have a +mechanism to reliably **determine a leading node**. It is, however, possible to +use Postgres **as the LND database backend** while using an etcd cluster purely +for the leader election functionality. diff --git a/docs/musig2.md b/docs/musig2.md new file mode 100644 index 0000000000..6a445bbef9 --- /dev/null +++ b/docs/musig2.md @@ -0,0 +1,117 @@ +# MuSig2 + +With `lnd v0.15.0-beta` a new, experimental MuSig2 API was added to the +`signrpc` subserver RPC. With MuSig2 it is possible to combine the public keys +of multiple signing participants into a single, combined key. The signers can +then later come together and cooperatively produce a single signature that is +valid for that combined public key. MuSig2 therefore is an interactive `n-of-n` +signature scheme that produces a final/complete Schnorr signature out of `n` +partial signatures. + +**NOTE**: At the time the MuSig2 code in `btcd`/`lnd` was written, there was no +"official" MuSig2 support merged to either `bitcoind` or `secp256k1`. Therefore, +some smaller details in the signing protocol could change in the future that +might not be backward compatible. So this API must be seen as highly +experimental and backward compatibility can't be guaranteed at this point. + +## References + * [MuSig2 paper](https://eprint.iacr.org/2020/1261.pdf) + * [Draft MuSig + BIP](https://github.com/jonasnick/bips/blob/musig2/bip-musig2.mediawiki) + * [MuSig2 implementation discussion in + `bitcoind`](https://github.com/bitcoin/bitcoin/issues/23326) + +## A note on security + +The MuSig2 signing protocol is secure from leaking private key information of +the signers **as long as the same secret nonce is never used multiple times**. +If the same nonce is used for signing multiple times then the private key can +leak. To avoid this security risk, the `signrpc.MuSig2Sign` RPC can only be +called a single time for the same session ID. This has the implication that if a +signing session fails or is aborted (for example because a participant isn't +responsive or the message changes after some participants have already started +signing), a completely new signing session needs to be initialized, which +internally creates fresh nonces. + +## Examples + +An API is sometimes easiest explained by showing concrete usage examples. Here +we take a look at the MuSig2 integration tests in `lnd`, since they both serve +to test the RPCs and to showcase the different use cases. + +### 3-of-3 Taproot key spend path (BIP-0086) + +See `testTaprootMuSig2KeySpendBip86` in +[`lntest/itest/lnd_taproot_test.go`](../lntest/itest/lnd_taproot_test.go) to see +the full code. + +This example uses combines the public keys of 3 participants into a shared +MuSig2 public key that is then tweaked with the +[BIP-0086](https://github.com/bitcoin/bips/blob/master/bip-0086.mediawiki#address-derivation) +TapTweak to be turned into a Taproot public key that can be used directly as the +`pkScript` of a p2tr output on chain. + +The most notable parameter for this to work is the `TaprootTweak` parameter in +the `MuSig2CreateSession` RPC call: + +```go + taprootTweak := &signrpc.TaprootTweakDesc{ + KeySpendOnly: true, + } + + sessResp1, err := node.SignerClient.MuSig2CreateSession( + ctx, &signrpc.MuSig2SessionRequest{ + KeyLoc: keyDesc1.KeyLoc, + AllSignerPubkeys: allPubKeys, + TaprootTweak: taprootTweak, + }, + ) +``` + +### 3-of-3 Taproot key spend path with root hash commitment + +See `testTaprootMuSig2KeySpendRootHash` in +[`lntest/itest/lnd_taproot_test.go`](../lntest/itest/lnd_taproot_test.go) to see +the full code. + +This is very similar to the above example but with the main difference that the +p2tr output on chain not only commits to a key spend path but also a script +path. The MuSig2 combined key becomes the Taproot internal key and the TapTweak +commits to both the internal key and the Taproot script merkle root hash. + +The most notable parameter for this to work is the `TaprootTweak` parameter in +the `MuSig2CreateSession` RPC call: + +```go + taprootTweak := &signrpc.TaprootTweakDesc{ + ScriptRoot: rootHash[:], + } + + sessResp1, err := node.SignerClient.MuSig2CreateSession( + ctx, &signrpc.MuSig2SessionRequest{ + KeyLoc: keyDesc1.KeyLoc, + AllSignerPubkeys: allPubKeys, + TaprootTweak: taprootTweak, + }, + ) +``` + +### 3-of-3 `OP_CHECKSIG` in Taproot script spend path + +See `testTaprootMuSig2CombinedLeafKeySpend` in +[`lntest/itest/lnd_taproot_test.go`](../lntest/itest/lnd_taproot_test.go) to see +the full code. + +This example is definitely the most involved one. To be able to use a MuSig2 +combined key and then spend it through a Taproot script spend with an +`OP_CHECKSIG` script, the following steps need to be performed: + +1. Derive signing keys on signers, combine them through `MuSig2CombineKeys`. +2. Create a Taproot script tree with a script leaf ` OP_CHECKSIG`. +3. Create the Taproot key by committing to the script tree root hash. +4. When spending the output, the message being signed needs to be the sighash of + a Taproot script spend that also commits to the leaf hash. +5. The final witness stack needs to contain the combined signature, the leaf + script and the control block (which contains the internal public key and the + inclusion proof if there were any other script leaves). + diff --git a/docs/postgres.md b/docs/postgres.md index 1b46e7a747..1a51f6ae0c 100644 --- a/docs/postgres.md +++ b/docs/postgres.md @@ -6,10 +6,12 @@ describes how it can be configured. ## Building LND with postgres support -To build LND with postgres support, include the following build tag: +Since `lnd v0.14.1-beta` the necessary build tags to enable postgres support are +already enabled by default. The default release binaries or docker images can +be used. To build from source, simply run: ```shell -⛰ make tags="kvdb_postgres" +⛰ make install ``` ## Configuring Postgres for LND @@ -29,3 +31,17 @@ LND is configured for Postgres through the following configuration options: database, user and password. * `db.postgres.timeout=...` to set the connection timeout. If not set, no timeout applies. + +Example as follows: +``` +[db] +db.backend=postgres +db.postgres.dsn=postgresql://dbuser:dbpass@127.0.0.1:5432/dbname +db.postgres.timeout=0 +``` +Connection timeout is disabled, to account for situations where the database +might be slow for unexpected reasons. + +## Important note about replication + +In case a replication architecture is planned, streaming replication should be avoided, as the master does not verify the replica is indeed identical, but it will only forward the edits queue, and let the slave catch up autonomously; synchronous mode, albeit slower, is paramount for `lnd` data integrity across the copies, as it will finalize writes only after the slave confirmed successful replication. diff --git a/docs/recovery.md b/docs/recovery.md index 4c2dfe03be..7531003324 100644 --- a/docs/recovery.md +++ b/docs/recovery.md @@ -38,11 +38,15 @@ result, it cannot be used in isolation. ### 24-word Cipher Seeds -When a new `lnd` node is created, it's given a 24-word seed phrase, called an -[`cipher seed`](https://github.com/lightningnetwork/lnd/tree/master/aezeed). -The two seed formats look similar, but the only commonality they share are -using the same default English dictionary. A valid seed phrase obtained over -the CLI `lncli create` command looks something like: +When a new `lnd` node is created, it is given a 24-word seed phrase, called an +[`aezeed cipher seed`](https://github.com/lightningnetwork/lnd/tree/master/aezeed). +The [BIP39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) and +[`aezeed cipher seed`](https://github.com/lightningnetwork/lnd/tree/master/aezeed) +formats look similar, but the only commonality they share is that they use the +same default [English](https://raw.githubusercontent.com/bitcoin/bips/master/bip-0039/english.txt) +wordlist. +A valid seed phrase obtained over the CLI `lncli create` command looks something +like: ```text !!!YOU MUST WRITE DOWN THIS SEED TO BE ABLE TO RESTORE THE WALLET!!! @@ -282,7 +286,7 @@ the same format. #### Streaming Updates via `SubscribeChannelBackups` -Using the gRPC interace directly, [a new call: +Using the gRPC interface directly, [a new call: `SubscribeChannelBackups`](https://api.lightning.community/#subscribechannelbackups). This call allows users to receive a new notification each time the underlying SCB state changes. This can be used to implement more complex backup diff --git a/docs/release-notes/release-notes-0.14.0.md b/docs/release-notes/release-notes-0.14.0.md index eb013ee947..3b6bd0fce2 100644 --- a/docs/release-notes/release-notes-0.14.0.md +++ b/docs/release-notes/release-notes-0.14.0.md @@ -104,12 +104,12 @@ operations. ### Explicit Channel Negotiation -[A new protocol extension has been added known as explicit channel negotiation] -(https://github.com/lightningnetwork/lnd/pull/5669). This allows a channel -initiator to signal their desired channel type to use with the remote peer. If -the remote peer supports said channel type and agrees, the previous implicit -negotiation based on the shared set of feature bits is bypassed, and the -proposed channel type is used. [Feature bits 44/45 are used to +[A new protocol extension has been added known as explicit channel +negotiation](https://github.com/lightningnetwork/lnd/pull/5669). This allows a +channel initiator to signal their desired channel type to use with the remote +peer. If the remote peer supports said channel type and agrees, the previous +implicit negotiation based on the shared set of feature bits is bypassed, and +the proposed channel type is used. [Feature bits 44/45 are used to signal](https://github.com/lightningnetwork/lnd/pull/5874) this new feature. @@ -128,7 +128,7 @@ details. ### Re-Usable Static AMP Invoices [AMP invoices are now fully re-usable, meaning it's possible for an `lnd` node -today a static AMP invoice multiple times](https://github.com/lightningnetwork/lnd/pull/5803). +to use a static AMP invoice multiple times](https://github.com/lightningnetwork/lnd/pull/5803). An AMP invoice can be created by adding the `--amp` flag to `lncli addinvoice`. From there repeated payments can be made to the invoice using `lncli payinvoice`. On the receiver side, notifications will still come in as normal, @@ -281,6 +281,8 @@ messages directly. There is no routing/path finding involved. ## Build System +* [The illumos operating system has been dropped from the set of release binaries. We now also build with postgres and etcd in the main release binaries](https://github.com/lightningnetwork/lnd/pull/5985). + * [A new pre-submit check has been added](https://github.com/lightningnetwork/lnd/pull/5520) to ensure that all PRs ([aside from merge @@ -323,6 +325,11 @@ messages directly. There is no routing/path finding involved. * [Upgrade the sub packages to 1.16](https://github.com/lightningnetwork/lnd/pull/5813) +* [CI has been upgraded to build against bitcoind 22.0](https://github.com/lightningnetwork/lnd/pull/5928) + +* [Update to the latest neutrino version](https://github.com/lightningnetwork/lnd/pull/5933) + + ## Documentation * [Outdated warning about unsupported pruning was replaced with clarification that LND **does** @@ -363,8 +370,12 @@ messages directly. There is no routing/path finding involved. add MacChan field for passing back lnd's admin macaroon back to the program calling lnd, when needed. +* [The `--amp-reuse` CLI flag has been removed as the latest flavor of AMP now natively supports static invoices](https://github.com/lightningnetwork/lnd/pull/5991) + * Using `go get` to install go executables is now deprecated. Migrate to `go install` our lnrpc proto dockerfile [Migrate `go get` to `go install`](https://github.com/lightningnetwork/lnd/pull/5879) +* [The premature update map has been revamped using an LRU cache](https://github.com/lightningnetwork/lnd/pull/5902) + ## Code Health ### Code cleanup, refactor, typo fixes @@ -460,6 +471,14 @@ messages directly. There is no routing/path finding involved. * [Fixed flake that occurred with the switch dust forwarding test under the data race unit build.](https://github.com/lightningnetwork/lnd/pull/5828) +* [Run channeldb tests on postgres](https://github.com/lightningnetwork/lnd/pull/5550) + +* [Fixed two flakes in itests that were caused by things progressing too + fast](https://github.com/lightningnetwork/lnd/pull/5905). + +* [Fixes two issues around configuration parsing and error + logging](https://github.com/lightningnetwork/lnd/pull/5948). + ## Database * [Ensure single writer for legacy @@ -498,16 +517,21 @@ messages directly. There is no routing/path finding involved. * [`lnd` will now no longer (in a steady state) need to open a new database transaction each time a private key needs to be derived for signing or ECDH - operations]https://github.com/lightningnetwork/lnd/pull/5629). This results + operations](https://github.com/lightningnetwork/lnd/pull/5629). This results in a massive performance improvement across several routine operations at the * [When decrypting incoming encrypted brontide messages on the wire, we'll now properly re-use the buffer that was allocated for the ciphertext to store the - plaintext]https://github.com/lightningnetwork/lnd/pull/5622). When combined + plaintext](https://github.com/lightningnetwork/lnd/pull/5622). When combined with the buffer pool, this ensures that we no longer need to allocate a new buffer each time we decrypt an incoming message, as we recycle these buffers in the peer. +* [The `DescribeGraph` and `GetNetworkInfo` calls have been + optimized](https://github.com/lightningnetwork/lnd/pull/5873) by caching the + response periodically, or using the new channel graph cache directly. This + should significantly cut down on the garbage these two calls generate. + ## Log system * [Save compressed log files from logrorate during @@ -584,6 +608,34 @@ messages directly. There is no routing/path finding involved. re-notified](https://github.com/lightningnetwork/lnd/pull/5901), which could lead to higher-level HTLC mismanagement issues. +* [Do not error log when an invoice that has been canceled and GC'd is expired]( + https://github.com/lightningnetwork/lnd/pull/5913) + +* [Don't print bucket names with special characters when compacting]( + https://github.com/lightningnetwork/lnd/pull/5878) + +* [Fix pathfinding crash when inbound policy is unknown]( + https://github.com/lightningnetwork/lnd/pull/5922) + +* [Stagger connection attempts to multi-address peers to ensure that the peer + doesn't close the first successful connection in favour of the next if + the first one was successful]( + https://github.com/lightningnetwork/lnd/pull/5925) + +* [Fixed an issue with external listeners and the `--noseedbackup` development + flag](https://github.com/lightningnetwork/lnd/pull/5930). + +* [Fixed a bug that caused the RPC middleware request ID not to be the same + for intercept messages belonging to the same intercepted gRPC request/response + pair](https://github.com/lightningnetwork/lnd/pull/5950). + +* [Fix deadlock when using the graph cache]( + https://github.com/lightningnetwork/lnd/pull/5941) + +* [Fixes a bug that would cause pruned nodes to stall out](https://github.com/lightningnetwork/lnd/pull/5970) + +* [Add Postgres connection limit](https://github.com/lightningnetwork/lnd/pull/5992) + ## Documentation The [code contribution guidelines have been updated to mention the new @@ -591,23 +643,46 @@ requirements surrounding updating the release notes for each new change](https://github.com/lightningnetwork/lnd/pull/5613). # Contributors (Alphabetical Order) +* Abubakar Nur Khalil +* Adrian-Stefan Mares * Alex Bosworth * Alyssa Hertig -* Andras Banki-Horvath -* de6df1re +* András Bánki-Horváth +* Bjarne Magnussen +* Carla Kirk-Cohen +* Carsten Otto +* Conner Fromknecht * Elle Mouton * ErikEk * Eugene Siegel * Hampus Sjöberg * Harsha Goli * Jesse de Wit +* Johan T. Halseth +* Johnny Holton * Joost Jager * Jordi Montes -* Martin Habovstiak +* Juan Pablo Civile +* Kishin Kato +* Leonhard Weese +* Martin Habovštiak +* Michael Rhee * Naveen Srinivasan +* Olaoluwa Osuntokun * Oliver Gugger * Priyansh Rastogi +* Roei Erez +* Simon Males +* Stevie Zollo +* Torkel Rogstad * Wilmer Paulino -* xanoni * Yong Yu * Zero-1729 +* benthecarman +* de6df1re +* github2k20 +* mateuszmp +* nathanael +* offerm +* positiveblue +* xanoni diff --git a/docs/release-notes/release-notes-0.14.1.md b/docs/release-notes/release-notes-0.14.1.md new file mode 100644 index 0000000000..6d6ec56f64 --- /dev/null +++ b/docs/release-notes/release-notes-0.14.1.md @@ -0,0 +1,34 @@ +# Release Notes + +## Bug Fixes + +* [Fixed an inaccurate log message during a compaction + failure](https://github.com/lightningnetwork/lnd/pull/5961). + +* [Fixed a bug in the Tor controller that would cause the health check to fail + if there was more than one hidden service + configured](https://github.com/lightningnetwork/lnd/pull/6016). + +* [A bug has been fixed in channeldb that uses the return value without checking + the returned error first](https://github.com/lightningnetwork/lnd/pull/6012). + +* [Fixes a bug that would cause lnd to be unable to start if anchors was + disabled](https://github.com/lightningnetwork/lnd/pull/6007). + +* [Fixed a bug that would cause nodes with older channels to be unable to start + up](https://github.com/lightningnetwork/lnd/pull/6003). + +* [Addresses an issue with explicit channel type negotiation that caused + incompatibilities when opening channels with the latest versions of + c-lightning and eclair](https://github.com/lightningnetwork/lnd/pull/6026). + +* [Ensure that if a user specifies explicit channel funding on the API level, + then it can't be + downgraded](https://github.com/lightningnetwork/lnd/pull/6027). + +# Contributors (Alphabetical Order) + +* Jamie Turley +* nayuta-ueno +* Olaoluwa Osuntokun +* Oliver Gugger diff --git a/docs/release-notes/release-notes-0.14.2.md b/docs/release-notes/release-notes-0.14.2.md new file mode 100644 index 0000000000..f82fbe68ec --- /dev/null +++ b/docs/release-notes/release-notes-0.14.2.md @@ -0,0 +1,147 @@ +# Release Notes + +## Remote signing + +The [remote signing](../remote-signing.md) setup was simplified in that the +signing node now [does not need to be hooked up to its own chain +backend](https://github.com/lightningnetwork/lnd/pull/6006). A new mock chain +backend can be specified with `--bitcoin.node=nochainbackend`. That way a wallet +will be created and all signing RPCs work but the node will not look at any +chain data. It can therefore be fully offline except for a single incoming gRPC +connection from the watch-only node. + +## Wallet + +* A bug that prevented opening anchor-based channels from external wallets when + the internal wallet was empty even though the transaction contained a + sufficiently large output belonging to the internal wallet + [was fixed](https://github.com/lightningnetwork/lnd/pull/5539). + In other words, freshly-installed LND can now be initialized with multiple + channels from an external (e.g. hardware) wallet *in a single transaction*. + +* A bug that allowed fees to be up to 100% of the payment amount was fixed by + [introducing a more sane default + value](https://github.com/lightningnetwork/lnd/pull/6226) of 5% routing fees + (except for small amounts <= [1k + satoshis](https://github.com/lightningnetwork/lnd/pull/6234) where the 100% + routing fees are kept to accommodate for the base fee in channels). To avoid + falling back to a default value, users should always set their own fee limits + by using the `--fee_limit` or `--fee_limit_percent` flags on the `lncli + payinvoice`, `lncli sendpayment` and `lncli queryroutes` commands. Users of + the gRPC or REST API should set the `fee_limit` field on the corresponding + calls (`SendPayment`, `SendPaymentSync`, `QueryRoutes`). + +## Database + +* [Speed up graph cache loading on startup with +Postgres](https://github.com/lightningnetwork/lnd/pull/6111) + +## Build System + +* [Clean up Makefile by using go + install](https://github.com/lightningnetwork/lnd/pull/6035). + +* [Make etcd max message size + configurable](https://github.com/lightningnetwork/lnd/pull/6049). + +* [Export bitcoind port and other values for itests, useful for + using itest harness outside of + lnd](https://github.com/lightningnetwork/lnd/pull/6050). + +* [Export `lntest` base node config so it can be re-used in LiT integration + tests](https://github.com/lightningnetwork/lnd/pull/6139). + +## Bug fixes + +* [A new resolver for breach closes was introduced that handles sweeping + anchor outputs and failing back HTLCs.](https://github.com/lightningnetwork/lnd/pull/6158) + +* [Return the nearest known fee rate when a given conf target cannot be found + from Web API fee estimator.](https://github.com/lightningnetwork/lnd/pull/6062) + +* [We now _always_ set a channel type if the other party signals the feature + bit](https://github.com/lightningnetwork/lnd/pull/6075). + +* [Add `--json` flag to + `trackpayment`](https://github.com/lightningnetwork/lnd/pull/6060). + +* [Clarify invalid config timeout + constraints](https://github.com/lightningnetwork/lnd/pull/6073). + +* [Fix memory corruption in Mission Control + Store](https://github.com/lightningnetwork/lnd/pull/6068) + +* [Ensure that the min relay fee is always clamped by our fee + floor](https://github.com/lightningnetwork/lnd/pull/6076) + +* [Clarify log message about not running within + systemd](https://github.com/lightningnetwork/lnd/pull/6096) + +* [Fix Postgres context cancellation](https://github.com/lightningnetwork/lnd/pull/6108) + +* A conflict was found in connecting peers, where the peer bootstrapping + process and persistent connection could compete connection for a peer that + led to an already made connection being lost. [This is now fixed so that + bootstrapping will always ignore the peers chosen by the persistent + connection.](https://github.com/lightningnetwork/lnd/pull/6082) + +* [Fix Postgres itests max connections](https://github.com/lightningnetwork/lnd/pull/6116) + +* [Fix duplicate db connection close](https://github.com/lightningnetwork/lnd/pull/6140) + +* [Fix a memory leak introduced by the new ping-header p2p enhancement](https://github.com/lightningnetwork/lnd/pull/6144) + +* [Fix an issue that would prevent very old nodes from starting up due to lack of a historical channel bucket](https://github.com/lightningnetwork/lnd/pull/6159) + +* [Fixes a bug that would cause incorrect rounding when translating a decimal fee rate to ppm](https://github.com/lightningnetwork/lnd/pull/6200) + + +## RPC Server + +* [ChanStatusFlags is now + exposed](https://github.com/lightningnetwork/lnd/pull/5971) inside + WaitingCloseResp from calling `PendingChannels`. + +* [Fix missing label on streamed + transactions](https://github.com/lightningnetwork/lnd/pull/5854). + +* [The `fee_rate_ppm` parameter/argument was added to + update channel policy](https://github.com/lightningnetwork/lnd/pull/5711) + to prevent truncation error with tiny fee rates. + +* [Closing txid is now + exposed](https://github.com/lightningnetwork/lnd/pull/6146) inside + WaitingCloseResp from calling `PendingChannels`. + +* [CustomCaveatCondition is now properly + set](https://github.com/lightningnetwork/lnd/pull/6185) on + `RPCMiddlewareRequest` messages. + +* [Adds a new FeeRatePpm to the UpdateChanPolicy call to allow fee rate expression in the native protocol unit](https://github.com/lightningnetwork/lnd/pull/6200) + + +## Routing + +* [Enable forced update of MC pair + history](https://github.com/lightningnetwork/lnd/pull/6180) by adding the `force` + flag to the `XImportMissionControl` RPC call. + +## Documentation + +* [General improvements to the mobile documentation](https://github.com/lightningnetwork/lnd/pull/6181). + +# Contributors (Alphabetical Order) + +* Andras Banki-Horvath +* Andreas Schjønhaug +* Bjarne Magnussen +* Daniel McNally +* Elle Mouton +* Erik Ek +* Harsha Goli +* Joost Jager +* Martin Habovštiak +* Naveen Srinivasan +* Oliver Gugger +* Olaoluwa Osuntokun +* Yong Yu diff --git a/docs/release-notes/release-notes-0.14.3.md b/docs/release-notes/release-notes-0.14.3.md new file mode 100644 index 0000000000..07de969c47 --- /dev/null +++ b/docs/release-notes/release-notes-0.14.3.md @@ -0,0 +1,20 @@ +# Release Notes + +## Bug fixes + +* The REST proxy (`grpc-gateway` library) had a fallback that redirected `POST` + requests to another endpoint _with the same URI_ if no endpoint for `POST` was + registered. [This default behavior was turned + off](https://github.com/lightningnetwork/lnd/pull/6359), enabling strict + HTTP method matching. + +* The [`SignOutputRaw` RPC now works properly in remote signing + mode](https://github.com/lightningnetwork/lnd/pull/6341), even when + only a public key or only a key locator is specified. This allows a Loop or + Pool node to be connected to a remote signing node pair. + A bug in the remote signer health check was also fixed that lead to too many + open connections. + +# Contributors (Alphabetical Order) + +* Oliver Gugger diff --git a/docs/release-notes/release-notes-0.15.0.md b/docs/release-notes/release-notes-0.15.0.md new file mode 100644 index 0000000000..9ac888b035 --- /dev/null +++ b/docs/release-notes/release-notes-0.15.0.md @@ -0,0 +1,401 @@ +# Release Notes + +## Payments + +Support according to the +[spec](https://github.com/lightningnetwork/lightning-rfc/pull/912) has been +added for [payment metadata in +invoices](https://github.com/lightningnetwork/lnd/pull/5810). If metadata is +present in the invoice, it is encoded as a tlv record for the receiver. + +This functionality unlocks future features such as [stateless +invoices](https://lists.linuxfoundation.org/pipermail/lightning-dev/2021-September/003236.html). + +## Security + +* [Misconfigured ZMQ + setup now logs a warning](https://github.com/lightningnetwork/lnd/pull/5710). + +## Taproot + +The internal on-chain wallet of `lnd` is now able to create and spend from +[Taproot (SegWit v1) +addresses](https://github.com/lightningnetwork/lnd/pull/6263). Using +`lncli newaddress p2tr` will create a new BIP-0086 keyspend only address and +then watch it on chain. Taproot script spends are also supported through the +`signrpc.SignOutputRaw` RPC (`/v2/signer/signraw` in REST). + +The `walletrpc.SignPsbt` RPC now also supports [Taproot PSBT +signing](https://github.com/lightningnetwork/lnd/pull/6450) to fully support +remote signing with Taproot outputs. + +## MuSig2 + +The [`signrpc.Signer` RPC service now supports EXPERIMENTAL MuSig2 +signing](https://github.com/lightningnetwork/lnd/pull/6361). + +More information can be found in the [MuSig2 documentation](../musig2.md). +Note that the MuSig2 BIP is not final yet and therefore the MuSig2 API must be +considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming +releases. Backward compatibility is not guaranteed! + +* [Add option to encrypt Tor private key on disk](https://github.com/lightningnetwork/lnd/pull/4458). + +* [Add option to encrypt TLS private key on disk](https://github.com/lightningnetwork/lnd/pull/4476). + +## `lncli` + +* Add [auto-generated command-line completions](https://github.com/lightningnetwork/lnd/pull/4177) + for Fish shell. + +* Add [chan_point flag](https://github.com/lightningnetwork/lnd/pull/6152) + to closechannel command. + +* Add [private status](https://github.com/lightningnetwork/lnd/pull/6167) + to pendingchannels response. + +* [Update description for `state` command](https://github.com/lightningnetwork/lnd/pull/6237). + +* Add [update node announcement](https://github.com/lightningnetwork/lnd/pull/5587) + for updating and propagating node information. + +* Add [--socksproxy](https://github.com/lightningnetwork/lnd/pull/6422) + to allow for RPC calls via Tor. + +* [Hop hints are now opt in when using `lncli + addinvoice`]https://github.com/lightningnetwork/lnd/pull/6523). Users now + need to explicitly specify the `--private` flag. + +## Neutrino + +[Neutrino now suports BIP +155](https://github.com/lightningnetwork/lnd/pull/6468), allowing it to connect +to Bitcoin nodes that advertise a Tor v3 onion service address. + +[A new neutrino sub-server](https://github.com/lightningnetwork/lnd/pull/5652) +capable of status checks, adding, disconnecting and listing peers, fetching +compact filters and block/block headers. + +## Btcwallet + +* [Add option to configure the block and transaction subscription + notifications from bitcoind to be obtained through polling of the RPC + interface instead of using ZMQ](https://github.com/lightningnetwork/lnd/pull/6345) + +## Bug Fixes + +* [Pipelining an UpdateFulfillHTLC message now only happens when the related UpdateAddHTLC is locked-in.](https://github.com/lightningnetwork/lnd/pull/6246) + +* [Fixed an inactive invoice subscription not removed from invoice + registry](https://github.com/lightningnetwork/lnd/pull/6053). When an invoice + subscription is created and canceled immediately, it could be left uncleaned + due to the cancel signal is processed before the creation. It is now properly + handled by moving creation before deletion. + +* When the block height+delta specified by a network message is greater than + the gossiper's best height, it will be considered as premature and ignored. + [These premature messages are now saved into a cache and processed once the + height has reached.](https://github.com/lightningnetwork/lnd/pull/6054) + +* [Fixed failure to limit our number of hop hints in private invoices](https://github.com/lightningnetwork/lnd/pull/6236). + When a private invoice is created, and the node had > 20 (our hop hint limit) + private channels with inbound > invoice amount, hop hint selection would add + too many hop hints. When a node had many channels meeting this criteria, it + could result in an "invoice too large" error when creating invoices. Hints + are now properly limited to our maximum of 20. + +* [Fixed an edge case where the lnd might be stuck at starting due to channel + arbitrator relying on htlcswitch to be started + first](https://github.com/lightningnetwork/lnd/pull/6214). + +* [Fixed crash in MuSig2Combine](https://github.com/lightningnetwork/lnd/pull/6502) + +* [Added signature length + validation](https://github.com/lightningnetwork/lnd/pull/6314) when calling + `NewSigFromRawSignature`. + +* [Fixed deadlock in invoice + registry](https://github.com/lightningnetwork/lnd/pull/6332). + +* [Fixed an issue that would cause wallet UTXO state to be incorrect if a 3rd + party sweeps our anchor + output](https://github.com/lightningnetwork/lnd/pull/6274). + +* [Fixed node shutdown in forward interceptor itests](https://github.com/lightningnetwork/lnd/pull/6362). + +* [Fixed a bug that would cause lnd to be unable to parse certain PSBT blobs](https://github.com/lightningnetwork/lnd/pull/6383). + +* [Use normal TCP resolution, instead of Tor DNS resolution, for addresses + using the all-interfaces IP](https://github.com/lightningnetwork/lnd/pull/6376). + +* [Fixed a bug in the `btcwallet` that caused an error to be shown for + `lncli walletbalance` in existing wallets after upgrading to + Taproot](https://github.com/lightningnetwork/lnd/pull/6379). + +* [Fixed a data race in the websocket proxy + code](https://github.com/lightningnetwork/lnd/pull/6380). + +* [Fixed race condition resulting in MPP payments sometimes getting stuck + in-flight](https://github.com/lightningnetwork/lnd/pull/6352). + +* [Fixed a panic in the Taproot signing part of the `SignOutputRaw` RPC that + occurred when not all UTXO information was + specified](https://github.com/lightningnetwork/lnd/pull/6407). + +* [Fixed P2TR addresses not correctly being detected as + used](https://github.com/lightningnetwork/lnd/pull/6389). + +* [Fixed incorrect PSBT de-serialization for transactions with no + inputs](https://github.com/lightningnetwork/lnd/pull/6428). + +* [Fixed a spec-compliance issue where lnd was not allowing cooperative +close to continue after a peer disconnect](https://github.com/lightningnetwork/lnd/pull/6419). + +* [A subsystem hand-off between the contractcourt and htlcswitch has been fixed by adding a persistence layer. This avoids a rare edge case +from occurring that would result in an erroneous force close.](https://github.com/lightningnetwork/lnd/pull/6250) + +* [Ignore addresses with unknown types in NodeAnnouncements]( + https://github.com/lightningnetwork/lnd/pull/6435) + +* [Taproot wallet inputs can also be used to fund + channels](https://github.com/lightningnetwork/lnd/pull/6521) + +## Routing + +* [Add a new `time_pref` parameter to the QueryRoutes and SendPayment APIs](https://github.com/lightningnetwork/lnd/pull/6024) that + allows the caller to control the trade-off between payment speed and cost in + pathfinding. + +## Misc + +* [An example systemd service file](https://github.com/lightningnetwork/lnd/pull/6033) + for running lnd alongside a bitcoind service is now provided in + `contrib/init/lnd.service`. + +* [Allow disabling migrations if the database backend passed to `channeldb` was + opened in read-only mode](https://github.com/lightningnetwork/lnd/pull/6084). + +* [Disable compiler optimizations](https://github.com/lightningnetwork/lnd/pull/6105) + when building `lnd-debug` and `lncli-debug`. It helps when stepping through the code + with a debugger like Delve. + +* A new command `lncli leaseoutput` was [added](https://github.com/lightningnetwork/lnd/pull/5964). + +* [Consolidated many smaller docs/typo/trivial fixes from PRs that were stuck + in review because of unmet contribution guideline + requirements](https://github.com/lightningnetwork/lnd/pull/6080). + +* [A nightly build of the `lnd` docker image is now created + automatically](https://github.com/lightningnetwork/lnd/pull/6160). + +* Add default values to [walletrpc.ListUnspent RPC call](https://github.com/lightningnetwork/lnd/pull/6190). + +* [Add `.vs/` folder to `.gitignore`](https://github.com/lightningnetwork/lnd/pull/6178). + +* [Chain backend healthchecks disabled for --nochainbackend mode](https://github.com/lightningnetwork/lnd/pull/6184) + +* [The `tlv` package was refactored into its own Golang + submodule](https://github.com/lightningnetwork/lnd/pull/6283). + +* The `tor` package was refactored into its own Golang submodule and a new + process for changing and tagging submodules was introduced in a series of + 3 PRs ([#6350](https://github.com/lightningnetwork/lnd/pull/6350), + [#6355](https://github.com/lightningnetwork/lnd/pull/6350) and + [#6356](https://github.com/lightningnetwork/lnd/pull/6356)). + +* [Source repository can now be specified for Docker image builds](https://github.com/lightningnetwork/lnd/pull/6300) + +* [The new `btcsuite/btcd/btcec/v2` and the moved `btcsuite/btcd/btcutil` + modules were integrated into `lnd` as a preparation for basic Taproot + support](https://github.com/lightningnetwork/lnd/pull/6285). + +* [Make etcd leader election session + TTL](https://github.com/lightningnetwork/lnd/pull/6342) configurable. + +* [Fix race condition in the htlc interceptor unit + test](https://github.com/lightningnetwork/lnd/pull/6353). + +* [A new config option, `pending-commit-interval` is + added](https://github.com/lightningnetwork/lnd/pull/6186). This value + specifies the maximum duration it allows for a remote peer to respond to a + locally initiated commitment update. + +* [`macos` and `apple` Makefile tasks have been added.](https://github.com/lightningnetwork/lnd/pull/6373) + + The `macos` task uses `gomobile` to build an `XCFramework` that can be used to + embed lnd to macOS apps, similar to how the `ios` task builds for iOS. + + The `apple` task uses `gomobile` to build an `XCFramework` that can be used to + embed lnd to both iOS and macOS apps. + +* [The CI and build infrastructure for the project has transitioned to using Go 1.18](https://github.com/lightningnetwork/lnd/pull/6340). + +* [Announce the keysend feature bit in NodeAnnouncement if `--accept-keysend` + is set](https://github.com/lightningnetwork/lnd/pull/6414). + +* [Fix a flaky unit test in the `chainview` + package](https://github.com/lightningnetwork/lnd/pull/6354). + +* [Adds a new config option for adding a specific peer at startup](https://github.com/lightningnetwork/lnd/pull/5157). + +* [Add a new method in `tlv` to encode an uint64/uint32 field using `BigSize` + format.](https://github.com/lightningnetwork/lnd/pull/6421) + +* [Add new `bitcoind.config` and `bitcoind.rpccookie` + options](https://github.com/lightningnetwork/lnd/pull/6064) to allow + specifying non-default paths for the configuration and RPC cookie files. + +* [Clarify comment](https://github.com/lightningnetwork/lnd/pull/6481) on + `chainnotifier.RegisterConfirmationsNtfn`. + +* [The experimental MuSig2 API now also has a `MuSig2Cleanup` RPC that allows + the in-memory state to be cleaned up early if a session isn't expected to + succeed anymore](https://github.com/lightningnetwork/lnd/pull/6495). + +## RPC Server + +* [Add value to the field + `remote_balance`](https://github.com/lightningnetwork/lnd/pull/5931) in + `pending_force_closing_channels` under `pendingchannels` whereas before was + empty(zero). +* The graph's [diameter is calculated](https://github.com/lightningnetwork/lnd/pull/6066) + and added to the `getnetworkinfo` output. + +* [Add dev only RPC subserver and the devrpc.ImportGraph + call](https://github.com/lightningnetwork/lnd/pull/6149) + +* [Extend](https://github.com/lightningnetwork/lnd/pull/6177) the HTLC + interceptor API to provide more control over failure messages. With this + change, it allows encrypted failure messages to be returned to the sender. + Additionally it is possible to signal a malformed htlc. + +* Add an [always on](https://github.com/lightningnetwork/lnd/pull/6232) mode to + the HTLC interceptor API. This enables interception applications where every + packet must be intercepted. + +* Add [destination output information](https://github.com/lightningnetwork/lnd/pull/5476) + to the transaction structure returned from the RPC `GetTransactions` and when + subscribed with `SubscribeTransactions`. + +* [Support for making routes with the legacy onion payload format via `SendToRoute` has been removed.](https://github.com/lightningnetwork/lnd/pull/6385) + +* Close a gap in the HTLC interceptor API by [intercepting htlcs in the on-chain + resolution flow](https://github.com/lightningnetwork/lnd/pull/6219) too. + +* [Expose](https://github.com/lightningnetwork/lnd/pull/6454) always on mode of + the HTLC interceptor API through GetInfo. + +* [The `lnrpc.ListPayments` RPC now has an optional `count_total_payments` + parameter that will cause the `total_num_payments` response field to be set + to the total number of payments (complete and incomplete) that are currently + in the payment database](https://github.com/lightningnetwork/lnd/pull/6463). + +* [Remove `confirmation_height` field from PendingChannel proto](https://github.com/lightningnetwork/lnd/pull/6472) + +## Database + +* [Add ForAll implementation for etcd to speed up + graph cache at startup](https://github.com/lightningnetwork/lnd/pull/6136) + +* [Improve validation of a PSBT packet when handling a request to finalize it.](https://github.com/lightningnetwork/lnd/pull/6217) + +* [Add new Peers subserver](https://github.com/lightningnetwork/lnd/pull/5587) with a new endpoint for updating the `NodeAnnouncement` data without having to restart the node. + +* Add [htlc expiry protection](https://github.com/lightningnetwork/lnd/pull/6212) +to the htlc interceptor API. + +* In order to safely advance commitment state, `lnd` saves the past states and + constructs a justice transaction in case of a remote breach. The states can + grow very large on disk given a busy operating channel, [which is now changed + with a space deduction over (at least) 96 percents.](https://github.com/lightningnetwork/lnd/pull/6347) + +* [Mobile builds now expose main sub-servers by default](https://github.com/lightningnetwork/lnd/pull/6464). + All API methods have prefixed the generated methods with the subserver name. + This is required to support subservers with name conflicts. + +## Documentation + +* Improved instructions on [how to build lnd for mobile](https://github.com/lightningnetwork/lnd/pull/6085). +* [Log force-close related messages on "info" level](https://github.com/lightningnetwork/lnd/pull/6124). + +* [Add list of build tags](https://github.com/lightningnetwork/lnd/pull/6486) + to the install instructions. + +## Monitoring + +A new [flag (`--prometheus.perfhistograms`) has been added to enable export of +gRPC performance metrics (latency to process `GetInfo`, etc)](https://github.com/lightningnetwork/lnd/pull/6224). + +## Code Health + +### Code cleanup, refactor, typo fixes + +* [Refactored itest to better manage contexts inside integration tests](https://github.com/lightningnetwork/lnd/pull/5756). + +* [Fix itest not picking up local config file or creating directories in home + dir of the user](https://github.com/lightningnetwork/lnd/pull/6202). + +* [A refactor of `SelectHopHints`](https://github.com/lightningnetwork/lnd/pull/6182) + allows code external to lnd to call the function, where previously it would + require access to lnd's internals. + +* [rpc-check fails if it finds any changes](https://github.com/lightningnetwork/lnd/pull/6207/) + including new and deleted files. + +* [The `golangci-lint` package was updated and new linters were + enabled](https://github.com/lightningnetwork/lnd/pull/6244). + +* The linting process now runs [inside a docker + container](https://github.com/lightningnetwork/lnd/pull/6248) to fix + versioning issues between projects. + +* The [`whitespace` linter](https://github.com/lightningnetwork/lnd/pull/6270) + was enabled to make sure multi-line `if` conditions and function/method + declarations are followed by an empty line to improve readability. + **Note to developers**: please make sure you delete the old version of + `golangci-lint` in your `$GOPATH/bin` directory. `make lint` does not + automatically replace it with the new version if the binary already exists! + +* [`ChannelLink` in the `htlcswitch` now performs a 1-way handoff via a temporary map with its `ChannelArbitrator`.](https://github.com/lightningnetwork/lnd/pull/6322) + +* [The channel-commit-interval is now clamped to a reasonable timeframe of 1h.](https://github.com/lightningnetwork/lnd/pull/6220) + +* [A function in the gossiper `processNetworkAnnouncements` has been refactored for readability and for future deduplication efforts.](https://github.com/lightningnetwork/lnd/pull/6278) + +# Contributors (Alphabetical Order) + +* 3nprob +* Alyssa Hertig +* Andras Banki-Horvath +* Andreas Schjønhaug +* asvdf +* bitromortac +* Bjarne Magnussen +* BTCparadigm +* Carl Dong +* Carla Kirk-Cohen +* Carsten Otto +* Dan Bolser +* Daniel McNally +* Elle Mouton +* ErikEk +* Eugene Siegel +* Evan Kaloudis +* Graham Krizek +* Hampus Sjöberg +* henta +* hieblmi +* Joost Jager +* Jordi Montes +* LightningHelper +* Liviu +* mateuszmp +* Naveen Srinivasan +* Olaoluwa Osuntokun +* randymcmillan +* Rong Ou +* Thebora Kompanioni +* Tommy Volk +* Torkel Rogstad +* Vsevolod Kaganovych +* Yong Yu diff --git a/docs/remote-signing.md b/docs/remote-signing.md index 9e4561526b..6bcef1b572 100644 --- a/docs/remote-signing.md +++ b/docs/remote-signing.md @@ -9,7 +9,7 @@ the **private** keys. The advantage of such a setup is that the `lnd` instance containing the private keys (the "signer") can be completely offline except for a single inbound gRPC -connection and the outbound connection to `bitcoind` (or `btcd` or `neutrino`). +connection. The signer instance can run on a different machine with more tightly locked down network security, optimally only allowing the single gRPC connection from the outside. @@ -31,39 +31,13 @@ xxx xx v | | +----------------+ gRPC | +----------------+ | | watch-only lnd +--------------+-->| full seed lnd | | - +-------+--------+ | +------+---------+ | - | | | | - +-------v--------+ | +------v---------+ | - | bitcoind/btcd | | | bitcoind/btcd | | - +----------------+ | +----------------+ | - | | - +----------------------------------+ -``` + +-------+--------+ | +----------------+ | + | | | + +-------v--------+ +----------------------------------+ + | bitcoind/btcd | + +----------------+ -NOTE: Offline in this context means that `lnd` itself does not need to be -reachable from the internet and itself doesn't connect out. If the `bitcoind` -or other chain backend is indeed running within this same firewall/offline zone -then that component will need at least _outbound_ internet access. But it is -also possible to use a chain backend that is running outside this zone. - -## Restrictions / limitations - -The current implementation of the remote signing mode comes with a few -restrictions and limitations: - -- Both `lnd` instances need a connection to a chain backend: - - The type of chain backend (`bitcoind`, `btcd` or `neutrino`) **must** - match. - - Both instances can point to the same chain backend node, though limits - apply with the number of `lnd` instances that can use the same `bitcoind` - node over ZMQ. - See ["running multiple lnd nodes" in the safety guide](safety.md#running-multiple-lnd-nodes) - . - - Using a pruned chain backend is not recommended as that increases the - chance of the two wallets falling out of sync with each other. -- The wallet of the "signer" instance **must not be used** for anything. - Especially generating new addresses manually on the "signer" will lead to the - two wallets falling out of sync! +``` ## Example setup @@ -80,8 +54,8 @@ opened to this node from the host on which the node "watch-only" is running. Recommended entries in `lnd.conf`: ```text -# No special configuration required other than basic "hardening" parameters to -# make sure no connections to the internet are opened. +# We apply some basic "hardening" parameters to make sure no connections to the +# internet are opened. [Application Options] # Don't listen on the p2p port. @@ -93,6 +67,19 @@ nobootstrap=true # Just an example, this is the port that needs to be opened in the firewall and # reachable from the node "watch-only". rpclisten=10019 + +# The signer node will not look at the chain at all, it only needs to sign +# things with the keys contained in its wallet. So we don't need to hook it up +# to any chain backend. +[bitcoin] +# We still need to signal that we're using the Bitcoin chain. +bitcoin.active=true + +# And we're making sure mainnet parameters are used. +bitcoin.mainnet=true + +# But we aren't using a "real" chain backed but a mocked one. +bitcoin.node=nochainbackend ``` After successfully starting up "signer", the following command can be run to @@ -151,6 +138,21 @@ Input an optional address look-ahead used to scan for used keys (default 2500): Alternatively a script can be used for initializing the watch-only wallet through the RPC interface as is described in the next section. +## Migrating an existing setup to remote signing + +It is possible to migrate a node that is currently a standalone, normal node +with all private keys in its wallet to a setup that uses remote signing (with +a watch-only and a remote signer node). + +To migrate an existing node, follow these steps: +1. Create a new "signer" node using the same seed as the existing node, + following the steps [mentioned above](#the-signer-node). +2. In the configuration of the existing node, add the configuration entries as + [shown above](#the-watch-only-node). But instead of creating a new wallet + (since one already exists), instruct `lnd` to migrate the existing wallet to + a watch-only one (by purging all private key material from it) by adding the + `remotesigner.migrate-wallet-to-watch-only=true` configuration entry. + ## Example initialization script This section shows an example script that initializes the watch-only wallet of diff --git a/docs/rest/websockets.md b/docs/rest/websockets.md index f2c85769a5..f231075f16 100644 --- a/docs/rest/websockets.md +++ b/docs/rest/websockets.md @@ -53,7 +53,7 @@ ws.onmessage = function (event) { console.log(JSON.parse(event.data).result); } ws.onerror = function (event) { - // An error occured, let's log it to the console. + // An error occurred, let's log it to the console. console.log(event); } ``` diff --git a/feature/default_sets.go b/feature/default_sets.go index d345c90df9..ae8187efb0 100644 --- a/feature/default_sets.go +++ b/feature/default_sets.go @@ -64,6 +64,9 @@ var defaultSetDesc = setDesc{ SetInit: {}, // I SetNodeAnn: {}, // N }, + lnwire.KeysendOptional: { + SetNodeAnn: {}, // N + }, lnwire.ScriptEnforcedLeaseOptional: { SetInit: {}, // I SetNodeAnn: {}, // N diff --git a/feature/deps.go b/feature/deps.go index 92b0c03d59..36a1bde746 100644 --- a/feature/deps.go +++ b/feature/deps.go @@ -69,6 +69,9 @@ var deps = depDesc{ lnwire.ExplicitChannelTypeOptional: {}, lnwire.AnchorsZeroFeeHtlcTxOptional: {}, }, + lnwire.KeysendOptional: { + lnwire.TLVOnionPayloadOptional: {}, + }, } // ValidateDeps asserts that a feature vector sets all features and their diff --git a/feature/deps_test.go b/feature/deps_test.go index f1d950df89..e1108fbe70 100644 --- a/feature/deps_test.go +++ b/feature/deps_test.go @@ -162,6 +162,5 @@ func testValidateDeps(t *testing.T, test depTest) { if !reflect.DeepEqual(err, test.expErr) { t.Fatalf("validation mismatch, want: %v, got: %v", test.expErr, err) - } } diff --git a/feature/manager.go b/feature/manager.go index 50a54d6251..5048da332f 100644 --- a/feature/manager.go +++ b/feature/manager.go @@ -27,13 +27,17 @@ type Config struct { // NoScriptEnforcementLease unsets any bits signaling support for script // enforced leases. NoScriptEnforcementLease bool + + // NoKeysend unsets any bits signaling support for accepting keysend + // payments. + NoKeysend bool } // Manager is responsible for generating feature vectors for different requested // feature sets. type Manager struct { // fsets is a static map of feature set to raw feature vectors. Requests - // are fulfilled by cloning these interal feature vectors. + // are fulfilled by cloning these internal feature vectors. fsets map[Set]*lnwire.RawFeatureVector } @@ -83,6 +87,8 @@ func newManager(cfg Config, desc setDesc) (*Manager, error) { raw.Unset(lnwire.MPPRequired) raw.Unset(lnwire.AMPOptional) raw.Unset(lnwire.AMPRequired) + raw.Unset(lnwire.KeysendOptional) + raw.Unset(lnwire.KeysendRequired) } if cfg.NoStaticRemoteKey { raw.Unset(lnwire.StaticRemoteKeyOptional) @@ -91,6 +97,21 @@ func newManager(cfg Config, desc setDesc) (*Manager, error) { if cfg.NoAnchors { raw.Unset(lnwire.AnchorsZeroFeeHtlcTxOptional) raw.Unset(lnwire.AnchorsZeroFeeHtlcTxRequired) + + // If anchors are disabled, then we also need to + // disable all other features that depend on it as + // well, as otherwise we may create an invalid feature + // bit set. + for bit, depFeatures := range deps { + for depFeature := range depFeatures { + switch { + case depFeature == lnwire.AnchorsZeroFeeHtlcTxRequired: + fallthrough + case depFeature == lnwire.AnchorsZeroFeeHtlcTxOptional: + raw.Unset(bit) + } + } + } } if cfg.NoWumbo { raw.Unset(lnwire.WumboChannelsOptional) @@ -100,6 +121,10 @@ func newManager(cfg Config, desc setDesc) (*Manager, error) { raw.Unset(lnwire.ScriptEnforcedLeaseOptional) raw.Unset(lnwire.ScriptEnforcedLeaseRequired) } + if cfg.NoKeysend { + raw.Unset(lnwire.KeysendOptional) + raw.Unset(lnwire.KeysendRequired) + } // Ensure that all of our feature sets properly set any // dependent features. @@ -126,6 +151,11 @@ func (m *Manager) GetRaw(set Set) *lnwire.RawFeatureVector { return lnwire.NewRawFeatureVector() } +// SetRaw sets a new raw feature vector for the given set. +func (m *Manager) SetRaw(set Set, raw *lnwire.RawFeatureVector) { + m.fsets[set] = raw +} + // Get returns a feature vector for the passed set. If no set is known, an empty // feature vector is returned. func (m *Manager) Get(set Set) *lnwire.FeatureVector { diff --git a/feature/manager_internal_test.go b/feature/manager_internal_test.go index 9c97bd052b..2778a86d95 100644 --- a/feature/manager_internal_test.go +++ b/feature/manager_internal_test.go @@ -52,6 +52,12 @@ var managerTests = []managerTest{ NoStaticRemoteKey: true, }, }, + { + name: "anchors should disable anything dependent on it", + cfg: Config{ + NoAnchors: true, + }, + }, } // TestManager asserts basic initialazation and operation of a feature manager, @@ -104,6 +110,10 @@ func testManager(t *testing.T, test managerTest) { if test.cfg.NoStaticRemoteKey { assertUnset(lnwire.StaticRemoteKeyOptional) } + if test.cfg.NoAnchors { + assertUnset(lnwire.ScriptEnforcedLeaseRequired) + assertUnset(lnwire.ScriptEnforcedLeaseOptional) + } assertUnset(unknownFeature) } diff --git a/funding/batch.go b/funding/batch.go index ec4eb744af..d809f439ce 100644 --- a/funding/batch.go +++ b/funding/batch.go @@ -8,11 +8,11 @@ import ( "errors" "fmt" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/psbt" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" - "github.com/btcsuite/btcutil/psbt" "github.com/lightningnetwork/lnd/labels" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" diff --git a/funding/batch_test.go b/funding/batch_test.go index 7b01ea95be..9ca522b030 100644 --- a/funding/batch_test.go +++ b/funding/batch_test.go @@ -8,11 +8,11 @@ import ( "fmt" "testing" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/psbt" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" - "github.com/btcsuite/btcutil/psbt" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" "github.com/lightningnetwork/lnd/lnwallet/chainfee" @@ -101,7 +101,7 @@ func newTestHarness(t *testing.T, failUpdate1, failUpdate2, func (h *testHarness) parseRequest( in *lnrpc.OpenChannelRequest) (*InitFundingMsg, error) { - pubKey, err := btcec.ParsePubKey(in.NodePubkey, btcec.S256()) + pubKey, err := btcec.ParsePubKey(in.NodePubkey) if err != nil { return nil, err } diff --git a/funding/commitment_type_negotiation.go b/funding/commitment_type_negotiation.go index 091c6fcbb6..a4ec131ed9 100644 --- a/funding/commitment_type_negotiation.go +++ b/funding/commitment_type_negotiation.go @@ -26,18 +26,31 @@ var ( // will be attempted if the set of both local and remote features support it. // Otherwise, implicit negotiation will be attempted. func negotiateCommitmentType(channelType *lnwire.ChannelType, - local, remote *lnwire.FeatureVector) (lnwallet.CommitmentType, error) { + local, remote *lnwire.FeatureVector, mustBeExplicit bool, +) (bool, *lnwire.ChannelType, lnwallet.CommitmentType, error) { if channelType != nil { - if !hasFeatures(local, remote, lnwire.ExplicitChannelTypeOptional) { - return 0, errUnsupportedExplicitNegotiation + // If the peer does know explicit negotiation, let's attempt + // that now. + if hasFeatures(local, remote, lnwire.ExplicitChannelTypeOptional) { + chanType, err := explicitNegotiateCommitmentType( + *channelType, local, remote, + ) + return true, channelType, chanType, err + } + + // If we're the funder, and we are attempting to use an + // explicit channel type, but the remote party doesn't signal + // the bit, then we actually want to exit here, to ensure the + // user doesn't end up with an unexpected channel type via + // implicit negotiation. + if mustBeExplicit { + return false, nil, 0, errUnsupportedExplicitNegotiation } - return explicitNegotiateCommitmentType( - *channelType, local, remote, - ) } - return implicitNegotiateCommitmentType(local, remote), nil + chanType, commitType := implicitNegotiateCommitmentType(local, remote) + return false, chanType, commitType, nil } // explicitNegotiateCommitmentType attempts to explicitly negotiate for a @@ -63,6 +76,7 @@ func explicitNegotiateCommitmentType(channelType lnwire.ChannelType, lnwire.AnchorsZeroFeeHtlcTxOptional, lnwire.StaticRemoteKeyOptional, ) { + return 0, errUnsupportedChannelType } return lnwallet.CommitmentTypeScriptEnforcedLease, nil @@ -77,6 +91,7 @@ func explicitNegotiateCommitmentType(channelType lnwire.ChannelType, lnwire.AnchorsZeroFeeHtlcTxOptional, lnwire.StaticRemoteKeyOptional, ) { + return 0, errUnsupportedChannelType } return lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx, nil @@ -99,14 +114,19 @@ func explicitNegotiateCommitmentType(channelType lnwire.ChannelType, // implicitNegotiateCommitmentType negotiates the commitment type of a channel // implicitly by choosing the latest type supported by the local and remote -// fetures. +// features. func implicitNegotiateCommitmentType(local, - remote *lnwire.FeatureVector) lnwallet.CommitmentType { + remote *lnwire.FeatureVector) (*lnwire.ChannelType, lnwallet.CommitmentType) { // If both peers are signalling support for anchor commitments with // zero-fee HTLC transactions, we'll use this type. if hasFeatures(local, remote, lnwire.AnchorsZeroFeeHtlcTxOptional) { - return lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx + chanType := lnwire.ChannelType(*lnwire.NewRawFeatureVector( + lnwire.AnchorsZeroFeeHtlcTxRequired, + lnwire.StaticRemoteKeyRequired, + )) + + return &chanType, lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx } // Since we don't want to support the "legacy" anchor type, we will fall @@ -116,11 +136,16 @@ func implicitNegotiateCommitmentType(local, // If both nodes are signaling the proper feature bit for tweakless // commitments, we'll use that. if hasFeatures(local, remote, lnwire.StaticRemoteKeyOptional) { - return lnwallet.CommitmentTypeTweakless + chanType := lnwire.ChannelType(*lnwire.NewRawFeatureVector( + lnwire.StaticRemoteKeyRequired, + )) + + return &chanType, lnwallet.CommitmentTypeTweakless } // Otherwise we'll fall back to the legacy type. - return lnwallet.CommitmentTypeLegacy + chanType := lnwire.ChannelType(*lnwire.NewRawFeatureVector()) + return &chanType, lnwallet.CommitmentTypeLegacy } // hasFeatures determines whether a set of features is supported by both the set diff --git a/funding/commitment_type_negotiation_test.go b/funding/commitment_type_negotiation_test.go index c9ac25a7a8..689b37cb15 100644 --- a/funding/commitment_type_negotiation_test.go +++ b/funding/commitment_type_negotiation_test.go @@ -14,12 +14,14 @@ func TestCommitmentTypeNegotiation(t *testing.T) { t.Parallel() testCases := []struct { - name string - channelFeatures *lnwire.RawFeatureVector - localFeatures *lnwire.RawFeatureVector - remoteFeatures *lnwire.RawFeatureVector - expectsRes lnwallet.CommitmentType - expectsErr error + name string + mustBeExplicit bool + channelFeatures *lnwire.RawFeatureVector + localFeatures *lnwire.RawFeatureVector + remoteFeatures *lnwire.RawFeatureVector + expectsCommitType lnwallet.CommitmentType + expectsChanType lnwire.ChannelType + expectsErr error }{ { name: "explicit missing remote negotiation feature", @@ -36,6 +38,30 @@ func TestCommitmentTypeNegotiation(t *testing.T) { lnwire.StaticRemoteKeyOptional, lnwire.AnchorsZeroFeeHtlcTxOptional, ), + expectsCommitType: lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx, + expectsChanType: lnwire.ChannelType(*lnwire.NewRawFeatureVector( + lnwire.StaticRemoteKeyRequired, + lnwire.AnchorsZeroFeeHtlcTxRequired, + )), + expectsErr: nil, + }, + { + name: "local funder wants explicit, remote doesn't " + + "support so fall back", + mustBeExplicit: true, + channelFeatures: lnwire.NewRawFeatureVector( + lnwire.StaticRemoteKeyRequired, + lnwire.AnchorsZeroFeeHtlcTxRequired, + ), + localFeatures: lnwire.NewRawFeatureVector( + lnwire.StaticRemoteKeyOptional, + lnwire.AnchorsZeroFeeHtlcTxOptional, + lnwire.ExplicitChannelTypeOptional, + ), + remoteFeatures: lnwire.NewRawFeatureVector( + lnwire.StaticRemoteKeyOptional, + lnwire.AnchorsZeroFeeHtlcTxOptional, + ), expectsErr: errUnsupportedExplicitNegotiation, }, { @@ -71,7 +97,11 @@ func TestCommitmentTypeNegotiation(t *testing.T) { lnwire.AnchorsZeroFeeHtlcTxOptional, lnwire.ExplicitChannelTypeOptional, ), - expectsRes: lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx, + expectsCommitType: lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx, + expectsChanType: lnwire.ChannelType(*lnwire.NewRawFeatureVector( + lnwire.StaticRemoteKeyRequired, + lnwire.AnchorsZeroFeeHtlcTxRequired, + )), expectsErr: nil, }, { @@ -89,7 +119,10 @@ func TestCommitmentTypeNegotiation(t *testing.T) { lnwire.AnchorsZeroFeeHtlcTxOptional, lnwire.ExplicitChannelTypeOptional, ), - expectsRes: lnwallet.CommitmentTypeTweakless, + expectsCommitType: lnwallet.CommitmentTypeTweakless, + expectsChanType: lnwire.ChannelType(*lnwire.NewRawFeatureVector( + lnwire.StaticRemoteKeyRequired, + )), expectsErr: nil, }, { @@ -105,9 +138,13 @@ func TestCommitmentTypeNegotiation(t *testing.T) { lnwire.AnchorsZeroFeeHtlcTxOptional, lnwire.ExplicitChannelTypeOptional, ), - expectsRes: lnwallet.CommitmentTypeLegacy, - expectsErr: nil, + expectsCommitType: lnwallet.CommitmentTypeLegacy, + expectsChanType: lnwire.ChannelType(*lnwire.NewRawFeatureVector()), + expectsErr: nil, }, + // Both sides signal the explicit chan type bit, so we expect + // that we return the corresponding chan type feature bits, + // even though we didn't set an explicit channel type. { name: "implicit anchors", channelFeatures: nil, @@ -121,7 +158,11 @@ func TestCommitmentTypeNegotiation(t *testing.T) { lnwire.AnchorsZeroFeeHtlcTxOptional, lnwire.ExplicitChannelTypeOptional, ), - expectsRes: lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx, + expectsCommitType: lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx, + expectsChanType: lnwire.ChannelType(*lnwire.NewRawFeatureVector( + lnwire.StaticRemoteKeyRequired, + lnwire.AnchorsZeroFeeHtlcTxRequired, + )), expectsErr: nil, }, { @@ -134,7 +175,10 @@ func TestCommitmentTypeNegotiation(t *testing.T) { remoteFeatures: lnwire.NewRawFeatureVector( lnwire.StaticRemoteKeyOptional, ), - expectsRes: lnwallet.CommitmentTypeTweakless, + expectsCommitType: lnwallet.CommitmentTypeTweakless, + expectsChanType: lnwire.ChannelType(*lnwire.NewRawFeatureVector( + lnwire.StaticRemoteKeyRequired, + )), expectsErr: nil, }, { @@ -145,8 +189,9 @@ func TestCommitmentTypeNegotiation(t *testing.T) { lnwire.StaticRemoteKeyOptional, lnwire.AnchorsZeroFeeHtlcTxOptional, ), - expectsRes: lnwallet.CommitmentTypeLegacy, - expectsErr: nil, + expectsCommitType: lnwallet.CommitmentTypeLegacy, + expectsChanType: lnwire.ChannelType(*lnwire.NewRawFeatureVector()), + expectsErr: nil, }, } @@ -167,13 +212,15 @@ func TestCommitmentTypeNegotiation(t *testing.T) { *testCase.channelFeatures, ) } - localType, err := negotiateCommitmentType( + _, localChanType, localCommitType, err := negotiateCommitmentType( channelType, localFeatures, remoteFeatures, + testCase.mustBeExplicit, ) require.Equal(t, testCase.expectsErr, err) - remoteType, err := negotiateCommitmentType( + _, remoteChanType, remoteCommitType, err := negotiateCommitmentType( channelType, remoteFeatures, localFeatures, + testCase.mustBeExplicit, ) require.Equal(t, testCase.expectsErr, err) @@ -181,8 +228,23 @@ func TestCommitmentTypeNegotiation(t *testing.T) { return } - require.Equal(t, testCase.expectsRes, localType) - require.Equal(t, testCase.expectsRes, remoteType) + require.Equal( + t, testCase.expectsCommitType, localCommitType, + testCase.name, + ) + require.Equal( + t, testCase.expectsCommitType, remoteCommitType, + testCase.name, + ) + + require.Equal( + t, testCase.expectsChanType, *localChanType, + testCase.name, + ) + require.Equal( + t, testCase.expectsChanType, *remoteChanType, + testCase.name, + ) }) if !ok { return diff --git a/funding/manager.go b/funding/manager.go index b4fad55707..648448a021 100644 --- a/funding/manager.go +++ b/funding/manager.go @@ -8,11 +8,12 @@ import ( "sync" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/davecgh/go-spew/spew" "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/chainntnfs" @@ -84,7 +85,7 @@ const ( // MaxBtcFundingAmountWumbo is a soft-limit on the maximum size of wumbo // channels. This limit is 10 BTC and is the only thing standing between - // you and limitless channel size (apart from 21 million cap) + // you and limitless channel size (apart from 21 million cap). MaxBtcFundingAmountWumbo = btcutil.Amount(1000000000) // MaxLtcFundingAmount is a soft-limit of the maximum channel size @@ -92,7 +93,7 @@ const ( // Protocol. MaxLtcFundingAmount = MaxBtcFundingAmount * chainreg.BtcToLtcConversionRate - // TODO(roasbeef): tune + // TODO(roasbeef): tune. msgBufferSize = 50 // maxWaitNumBlocksFundingConf is the maximum number of blocks to wait @@ -327,7 +328,7 @@ type Config struct { // TODO(roasbeef): should instead pass on this responsibility to a // distinct sub-system? SignMessage func(keyLoc keychain.KeyLocator, - msg []byte, doubleHash bool) (*btcec.Signature, error) + msg []byte, doubleHash bool) (*ecdsa.Signature, error) // CurrentNodeAnnouncement should return the latest, fully signed node // announcement from the backing Lightning Network node. @@ -553,6 +554,19 @@ const ( addedToRouterGraph ) +func (c channelOpeningState) String() string { + switch c { + case markedOpen: + return "markedOpen" + case fundingLockedSent: + return "fundingLocked" + case addedToRouterGraph: + return "addedToRouterGraph" + default: + return "unknown" + } +} + // NewFundingManager creates and initializes a new instance of the // fundingManager. func NewFundingManager(cfg Config) (*Manager, error) { @@ -575,14 +589,13 @@ func NewFundingManager(cfg Config) (*Manager, error) { func (f *Manager) Start() error { var err error f.started.Do(func() { + log.Info("Funding manager starting") err = f.start() }) return err } func (f *Manager) start() error { - log.Tracef("Funding manager running") - // Upon restart, the Funding Manager will check the database to load any // channels that were waiting for their funding transactions to be // confirmed on the blockchain at the time when the daemon last went @@ -710,7 +723,6 @@ func (f *Manager) nextPendingChanID() [32]byte { // passed node. This will ensure any outputs which have been pre committed, // (and thus locked from coin selection), are properly freed. func (f *Manager) CancelPeerReservations(nodePub [33]byte) { - log.Debugf("Cancelling all reservations for peer %x", nodePub[:]) f.resMtx.Lock() @@ -926,7 +938,6 @@ func (f *Manager) stateStep(channel *channeldb.OpenChannel, chanID, shortChanID, channelState) switch channelState { - // The funding transaction was confirmed, but we did not successfully // send the fundingLocked message to the peer, so let's do that now. case markedOpen: @@ -1099,7 +1110,6 @@ func (f *Manager) advancePendingChannelState( }() return timeoutErr - } else if err != nil { return fmt.Errorf("error waiting for funding "+ "confirmation for ChannelPoint(%v): %v", @@ -1136,7 +1146,7 @@ func (f *Manager) ProcessFundingMsg(msg lnwire.Message, peer lnpeer.Peer) { // the funding workflow. // // TODO(roasbeef): add error chan to all, let channelManager handle -// error+propagate +// error+propagate. func (f *Manager) handleFundingOpen(peer lnpeer.Peer, msg *lnwire.OpenChannel) { @@ -1225,7 +1235,7 @@ func (f *Manager) handleFundingOpen(peer lnpeer.Peer, if amt < f.cfg.MinChanSize { f.failFundingFlow( peer, msg.PendingChannelID, - lnwallet.ErrChanTooSmall(amt, btcutil.Amount(f.cfg.MinChanSize)), + lnwallet.ErrChanTooSmall(amt, f.cfg.MinChanSize), ) return } @@ -1274,8 +1284,9 @@ func (f *Manager) handleFundingOpen(peer lnpeer.Peer, // the remote peer are signaling the proper feature bit if we're using // implicit negotiation, and simply the channel type sent over if we're // using explicit negotiation. - commitType, err := negotiateCommitmentType( + wasExplicit, _, commitType, err := negotiateCommitmentType( msg.ChannelType, peer.LocalFeatures(), peer.RemoteFeatures(), + false, ) if err != nil { // TODO(roasbeef): should be using soft errors @@ -1284,6 +1295,13 @@ func (f *Manager) handleFundingOpen(peer lnpeer.Peer, return } + // Only echo back a channel type in AcceptChannel if we actually used + // explicit negotiation above. + var chanTypeFeatureBits *lnwire.ChannelType + if wasExplicit { + chanTypeFeatureBits = msg.ChannelType + } + chainHash := chainhash.Hash(msg.ChainHash) req := &lnwallet.InitFundingReserveMsg{ ChainHash: &chainHash, @@ -1520,7 +1538,7 @@ func (f *Manager) handleFundingOpen(peer lnpeer.Peer, HtlcPoint: ourContribution.HtlcBasePoint.PubKey, FirstCommitmentPoint: ourContribution.FirstCommitmentPoint, UpfrontShutdownScript: ourContribution.UpfrontShutdown, - ChannelType: msg.ChannelType, + ChannelType: chanTypeFeatureBits, LeaseExpiry: msg.LeaseExpiry, } @@ -1587,9 +1605,36 @@ func (f *Manager) handleFundingAccept(peer lnpeer.Peer, } } } else if msg.ChannelType != nil { - err := errors.New("received unexpected channel type") - f.failFundingFlow(peer, msg.PendingChannelID, err) - return + // The spec isn't too clear about whether it's okay to set the + // channel type in the accept_channel response if we didn't + // explicitly set it in the open_channel message. For now, let's + // just log the problem instead of failing the funding flow. + _, implicitChannelType := implicitNegotiateCommitmentType( + peer.LocalFeatures(), peer.RemoteFeatures(), + ) + + // We pass in false here as the funder since at this point, we + // didn't set a chan type ourselves, so falling back to + // implicit funding is acceptable. + _, _, negotiatedChannelType, err := negotiateCommitmentType( + msg.ChannelType, peer.LocalFeatures(), + peer.RemoteFeatures(), false, + ) + if err != nil { + err := errors.New("received unexpected channel type") + f.failFundingFlow(peer, msg.PendingChannelID, err) + return + } + + // Even though we don't expect a channel type to be set when we + // didn't send one in the first place, we check that it's the + // same type we'd have arrived through implicit negotiation. If + // it's another type, we fail the flow. + if implicitChannelType != negotiatedChannelType { + err := errors.New("negotiated unexpected channel type") + f.failFundingFlow(peer, msg.PendingChannelID, err) + return + } } // The required number of confirmations should not be greater than the @@ -2334,6 +2379,7 @@ func (f *Manager) waitForFundingConfirmation( // NOTE: This MUST be run as a goroutine. func (f *Manager) waitForTimeout(completeChan *channeldb.OpenChannel, cancelChan <-chan struct{}, timeoutChan chan<- error) { + defer f.wg.Done() epochClient, err := f.cfg.Notifier.RegisterBlockEpochNtfn(nil) @@ -2357,7 +2403,7 @@ func (f *Manager) waitForTimeout(completeChan *channeldb.OpenChannel, } // Close the timeout channel and exit if the block is - // aboce the max height. + // above the max height. if uint32(epoch.Height) >= maxHeight { log.Warnf("Waited for %v blocks without "+ "seeing funding transaction confirmed,"+ @@ -2738,7 +2784,7 @@ func (f *Manager) annAfterSixConfs(completeChan *channeldb.OpenChannel, } log.Debugf("Channel with ChannelPoint(%v), short_chan_id=%v "+ - "announced", &fundingPoint, shortChanID) + "sent to gossiper", &fundingPoint, shortChanID) } return nil @@ -3223,9 +3269,9 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) { // Before we init the channel, we'll also check to see what commitment // format we can use with this peer. This is dependent on *both* us and // the remote peer are signaling the proper feature bit. - commitType, err := negotiateCommitmentType( + _, chanType, commitType, err := negotiateCommitmentType( msg.ChannelType, msg.Peer.LocalFeatures(), - msg.Peer.RemoteFeatures(), + msg.Peer.RemoteFeatures(), true, ) if err != nil { log.Errorf("channel type negotiation failed: %v", err) @@ -3247,6 +3293,7 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) { // maximum. if commitType.HasAnchors() && commitFeePerKw > f.cfg.MaxAnchorsCommitFeeRate { + commitFeePerKw = f.cfg.MaxAnchorsCommitFeeRate } @@ -3380,7 +3427,7 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) { FirstCommitmentPoint: ourContribution.FirstCommitmentPoint, ChannelFlags: channelFlags, UpfrontShutdownScript: shutdown, - ChannelType: msg.ChannelType, + ChannelType: chanType, LeaseExpiry: leaseExpiry, } if err := msg.Peer.SendMessage(true, &fundingOpen); err != nil { @@ -3466,7 +3513,8 @@ func (f *Manager) pruneZombieReservations() { for pendingChanID, resCtx := range zombieReservations { err := fmt.Errorf("reservation timed out waiting for peer "+ - "(peer_id:%x, chan_id:%x)", resCtx.peer.IdentityKey(), + "(peer_id:%x, chan_id:%x)", + resCtx.peer.IdentityKey().SerializeCompressed(), pendingChanID[:]) log.Warnf(err.Error()) f.failFundingFlow(resCtx.peer, pendingChanID, err) @@ -3581,11 +3629,10 @@ func (f *Manager) IsPendingChannel(pendingChanID [32]byte, } func copyPubKey(pub *btcec.PublicKey) *btcec.PublicKey { - return &btcec.PublicKey{ - Curve: btcec.S256(), - X: pub.X, - Y: pub.Y, - } + var tmp btcec.JacobianPoint + pub.AsJacobian(&tmp) + tmp.ToAffine() + return btcec.NewPublicKey(&tmp.X, &tmp.Y) } // saveChannelOpeningState saves the channelOpeningState for the provided diff --git a/funding/manager_test.go b/funding/manager_test.go index bfd47329fc..459ea8cf21 100644 --- a/funding/manager_test.go +++ b/funding/manager_test.go @@ -5,10 +5,10 @@ package funding import ( "bytes" + "encoding/hex" "errors" "fmt" "io/ioutil" - "math/big" "net" "os" "path/filepath" @@ -17,11 +17,12 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/chainreg" "github.com/lightningnetwork/lnd/chanacceptor" @@ -76,8 +77,9 @@ var ( 0x6a, 0x49, 0x18, 0x83, 0x31, 0x98, 0x47, 0x53, } - alicePrivKey, alicePubKey = btcec.PrivKeyFromBytes(btcec.S256(), - alicePrivKeyBytes[:]) + alicePrivKey, alicePubKey = btcec.PrivKeyFromBytes( + alicePrivKeyBytes[:], + ) aliceTCPAddr, _ = net.ResolveTCPAddr("tcp", "10.0.0.2:9001") @@ -93,8 +95,7 @@ var ( 0x1e, 0xb, 0x4c, 0xfd, 0x9e, 0xc5, 0x8c, 0xe9, } - bobPrivKey, bobPubKey = btcec.PrivKeyFromBytes(btcec.S256(), - bobPrivKeyBytes[:]) + bobPrivKey, bobPubKey = btcec.PrivKeyFromBytes(bobPrivKeyBytes[:]) bobTCPAddr, _ = net.ResolveTCPAddr("tcp", "10.0.0.2:9000") @@ -103,12 +104,13 @@ var ( Address: bobTCPAddr, } - testSig = &btcec.Signature{ - R: new(big.Int), - S: new(big.Int), - } - _, _ = testSig.R.SetString("63724406601629180062774974542967536251589935445068131219452686511677818569431", 10) - _, _ = testSig.S.SetString("18801056069249825825291287104931333862866033135609736119018462340006816851118", 10) + testRBytes, _ = hex.DecodeString("8ce2bc69281ce27da07e6683571319d18e949ddfa2965fb6caa1bf0314f882d7") + testSBytes, _ = hex.DecodeString("299105481d63e0f4bc2a88121167221b6700d72a0ead154c03be696a292d24ae") + testRScalar = new(btcec.ModNScalar) + testSScalar = new(btcec.ModNScalar) + _ = testRScalar.SetByteSlice(testRBytes) + _ = testSScalar.SetByteSlice(testSBytes) + testSig = ecdsa.NewSignature(testRScalar, testSScalar) testKeyLoc = keychain.KeyLocator{Family: keychain.KeyFamilyNodeKey} @@ -362,7 +364,7 @@ func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey, Notifier: chainNotifier, FeeEstimator: estimator, SignMessage: func(_ keychain.KeyLocator, - _ []byte, _ bool) (*btcec.Signature, error) { + _ []byte, _ bool) (*ecdsa.Signature, error) { return testSig, nil }, @@ -510,7 +512,7 @@ func recreateAliceFundingManager(t *testing.T, alice *testNode) { Notifier: oldCfg.Notifier, FeeEstimator: oldCfg.FeeEstimator, SignMessage: func(_ keychain.KeyLocator, - _ []byte, _ bool) (*btcec.Signature, error) { + _ []byte, _ bool) (*ecdsa.Signature, error) { return testSig, nil }, @@ -996,6 +998,7 @@ func assertAddedToRouterGraph(t *testing.T, alice, bob *testNode, func assertChannelAnnouncements(t *testing.T, alice, bob *testNode, capacity btcutil.Amount, customMinHtlc []lnwire.MilliSatoshi, customMaxHtlc []lnwire.MilliSatoshi) { + t.Helper() // After the FundingLocked message is sent, Alice and Bob will each @@ -2584,7 +2587,7 @@ func TestFundingManagerPrivateRestart(t *testing.T) { } // TestFundingManagerCustomChannelParameters checks that custom requirements we -// specify during the channel funding flow is preserved correcly on both sides. +// specify during the channel funding flow is preserved correctly on both sides. func TestFundingManagerCustomChannelParameters(t *testing.T) { t.Parallel() @@ -2780,7 +2783,7 @@ func TestFundingManagerCustomChannelParameters(t *testing.T) { } // The max value in flight Alice can have should be maxValueAcceptChannel, - // which is the default value and the maxium Bob can offer should be + // which is the default value and the maximum Bob can offer should be // maxValueInFlight. if err := assertMaxHtlc(resCtx, maxValueAcceptChannel, maxValueInFlight); err != nil { @@ -3362,7 +3365,6 @@ func TestGetUpfrontShutdownScript(t *testing.T) { t.Fatalf("expected address: %x, got: %x", test.expectedScript, addr) } - }) } } diff --git a/fuzz/brontide/fuzz_utils.go b/fuzz/brontide/fuzz_utils.go index ab846efa57..e7a1e86b20 100644 --- a/fuzz/brontide/fuzz_utils.go +++ b/fuzz/brontide/fuzz_utils.go @@ -7,7 +7,7 @@ import ( "encoding/hex" "fmt" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/brontide" "github.com/lightningnetwork/lnd/keychain" @@ -37,7 +37,7 @@ var ( return nil, err } - priv, _ := btcec.PrivKeyFromBytes(btcec.S256(), eBytes) + priv, _ := btcec.PrivKeyFromBytes(eBytes) return priv, nil }) @@ -50,7 +50,7 @@ var ( return nil, err } - priv, _ := btcec.PrivKeyFromBytes(btcec.S256(), eBytes) + priv, _ := btcec.PrivKeyFromBytes(eBytes) return priv, nil }) ) @@ -99,12 +99,6 @@ func handshake(initiator, responder *brontide.Machine) error { // nilAndPanic first nils the initiator and responder's Curve fields and then // panics. func nilAndPanic(initiator, responder *brontide.Machine, err error) { - if initiator != nil { - initiator.SetCurveToNil() - } - if responder != nil { - responder.SetCurveToNil() - } panic(fmt.Errorf("error: %v, initiator: %v, responder: %v", err, spew.Sdump(initiator), spew.Sdump(responder))) } @@ -112,8 +106,8 @@ func nilAndPanic(initiator, responder *brontide.Machine, err error) { // getBrontideMachines returns two brontide machines that use random keys // everywhere. func getBrontideMachines() (*brontide.Machine, *brontide.Machine) { - initPriv, _ := btcec.NewPrivateKey(btcec.S256()) - respPriv, _ := btcec.NewPrivateKey(btcec.S256()) + initPriv, _ := btcec.NewPrivateKey() + respPriv, _ := btcec.NewPrivateKey() respPub := (*btcec.PublicKey)(&respPriv.PublicKey) initPrivECDH := &keychain.PrivKeyECDH{PrivKey: initPriv} @@ -128,8 +122,8 @@ func getBrontideMachines() (*brontide.Machine, *brontide.Machine) { // getStaticBrontideMachines returns two brontide machines that use static keys // everywhere. func getStaticBrontideMachines() (*brontide.Machine, *brontide.Machine) { - initPriv, _ := btcec.PrivKeyFromBytes(btcec.S256(), initBytes) - respPriv, respPub := btcec.PrivKeyFromBytes(btcec.S256(), respBytes) + initPriv, _ := btcec.PrivKeyFromBytes(initBytes) + respPriv, respPub := btcec.PrivKeyFromBytes(respBytes) initPrivECDH := &keychain.PrivKeyECDH{PrivKey: initPriv} respPrivECDH := &keychain.PrivKeyECDH{PrivKey: respPriv} diff --git a/fuzz/zpay32/encode.go b/fuzz/zpay32/encode.go index 63c995b102..9c3a08dbc2 100644 --- a/fuzz/zpay32/encode.go +++ b/fuzz/zpay32/encode.go @@ -7,7 +7,8 @@ import ( "encoding/hex" "fmt" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/zpay32" @@ -27,16 +28,14 @@ func Fuzz_encode(data []byte) int { // Initialize the static key we will be using for this fuzz test. testPrivKeyBytes, _ := hex.DecodeString("e126f68f7eafcc8b74f54d269fe206be715000f94dac067d1c04a8ca3b2db734") - testPrivKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), testPrivKeyBytes) + testPrivKey, _ := btcec.PrivKeyFromBytes(testPrivKeyBytes) // Then, initialize the testMessageSigner so we can encode out // invoices with this private key. testMessageSigner := zpay32.MessageSigner{ SignCompact: func(msg []byte) ([]byte, error) { hash := chainhash.HashB(msg) - sig, err := btcec.SignCompact( - btcec.S256(), testPrivKey, hash, true, - ) + sig, err := ecdsa.SignCompact(testPrivKey, hash, true) if err != nil { return nil, fmt.Errorf("can't sign the "+ "message: %v", err) diff --git a/go.mod b/go.mod index 2e1f3dc103..623d70b6ea 100644 --- a/go.mod +++ b/go.mod @@ -1,23 +1,23 @@ module github.com/lightningnetwork/lnd require ( - git.schwanenlied.me/yawning/bsaes.git v0.0.0-20180720073208-c0276d75487e // indirect - github.com/NebulousLabs/fastrand v0.0.0-20181203155948-6fb6489aac4e // indirect github.com/NebulousLabs/go-upnp v0.0.0-20180202185039-29b680b06c82 - github.com/Yawning/aez v0.0.0-20180114000226-4dad034d9db2 - github.com/btcsuite/btcd v0.22.0-beta.0.20211005184431-e3449998be39 + github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 + github.com/btcsuite/btcd v0.22.0-beta.0.20220413172512-bf64c8bdbbbf + github.com/btcsuite/btcd/btcec/v2 v2.2.0 + github.com/btcsuite/btcd/btcutil v1.1.1 + github.com/btcsuite/btcd/btcutil/psbt v1.1.4 + github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f - github.com/btcsuite/btcutil v1.0.3-0.20210527170813-e2ba6805a890 - github.com/btcsuite/btcutil/psbt v1.0.3-0.20210527170813-e2ba6805a890 - github.com/btcsuite/btcwallet v0.12.1-0.20211022222026-9043c19d8725 - github.com/btcsuite/btcwallet/wallet/txauthor v1.1.0 - github.com/btcsuite/btcwallet/wallet/txrules v1.1.0 - github.com/btcsuite/btcwallet/walletdb v1.3.6-0.20210803004036-eebed51155ec - github.com/btcsuite/btcwallet/wtxmgr v1.3.1-0.20210822222949-9b5a201c344c + github.com/btcsuite/btcwallet v0.15.0 + github.com/btcsuite/btcwallet/wallet/txauthor v1.2.3 + github.com/btcsuite/btcwallet/wallet/txrules v1.2.0 + github.com/btcsuite/btcwallet/walletdb v1.4.0 + github.com/btcsuite/btcwallet/wtxmgr v1.5.0 github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f github.com/davecgh/go-spew v1.1.1 + github.com/dvyukov/go-fuzz v0.0.0-20210602112143-b1f3d6f4ef4e github.com/go-errors/errors v1.0.1 - github.com/go-openapi/strfmt v0.19.5 // indirect github.com/golang/protobuf v1.5.2 github.com/gorilla/websocket v1.4.2 github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 @@ -26,66 +26,136 @@ require ( github.com/jackc/pgx/v4 v4.13.0 github.com/jackpal/gateway v1.0.5 github.com/jackpal/go-nat-pmp v0.0.0-20170405195558-28a68d0c24ad - github.com/jedib0t/go-pretty v4.3.0+incompatible + github.com/jedib0t/go-pretty/v6 v6.2.7 github.com/jessevdk/go-flags v1.4.0 github.com/jrick/logrotate v1.0.0 - github.com/juju/clock v0.0.0-20190205081909-9c5c9712527c // indirect - github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d // indirect - github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 // indirect - github.com/juju/retry v0.0.0-20180821225755-9058e192b216 // indirect - github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2 // indirect - github.com/juju/utils v0.0.0-20180820210520-bf9cc5bdd62d // indirect - github.com/juju/version v0.0.0-20180108022336-b64dbd566305 // indirect github.com/kkdai/bstream v1.0.0 - github.com/lightninglabs/neutrino v0.12.3 + github.com/lightninglabs/neutrino v0.14.1 github.com/lightninglabs/protobuf-hex-display v1.4.3-hex-display - github.com/lightningnetwork/lightning-onion v1.0.2-0.20210520211913-522b799e65b1 - github.com/lightningnetwork/lnd/cert v1.1.0 + github.com/lightningnetwork/lightning-onion v1.0.2-0.20220211021909-bb84a1ccb0c5 + github.com/lightningnetwork/lnd/cert v1.1.1 github.com/lightningnetwork/lnd/clock v1.1.0 - github.com/lightningnetwork/lnd/healthcheck v1.2.0 - github.com/lightningnetwork/lnd/kvdb v1.2.0 + github.com/lightningnetwork/lnd/healthcheck v1.2.2 + github.com/lightningnetwork/lnd/kvdb v1.3.1 github.com/lightningnetwork/lnd/queue v1.1.0 github.com/lightningnetwork/lnd/ticker v1.1.0 + github.com/lightningnetwork/lnd/tlv v1.0.3 + github.com/lightningnetwork/lnd/tor v1.0.0 github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 - github.com/mattn/go-runewidth v0.0.9 // indirect github.com/miekg/dns v1.1.43 github.com/prometheus/client_golang v1.11.0 - github.com/stretchr/testify v1.7.0 + github.com/stretchr/testify v1.7.1 github.com/tv42/zbase32 v0.0.0-20160707012821-501572607d02 - github.com/urfave/cli v1.20.0 + github.com/urfave/cli v1.22.4 go.etcd.io/etcd/client/pkg/v3 v3.5.0 go.etcd.io/etcd/client/v3 v3.5.0 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 - golang.org/x/net v0.0.0-20210913180222-943fd674d43e + golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - golang.org/x/sys v0.0.0-20210915083310-ed5796bab164 // indirect golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba google.golang.org/grpc v1.38.0 google.golang.org/protobuf v1.26.0 - gopkg.in/errgo.v1 v1.0.1 // indirect gopkg.in/macaroon-bakery.v2 v2.0.1 gopkg.in/macaroon.v2 v2.0.0 - gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 // indirect ) -replace github.com/lightningnetwork/lnd/ticker => ./ticker - -replace github.com/lightningnetwork/lnd/queue => ./queue - -replace github.com/lightningnetwork/lnd/cert => ./cert - -replace github.com/lightningnetwork/lnd/clock => ./clock - -replace github.com/lightningnetwork/lnd/healthcheck => ./healthcheck - -replace github.com/lightningnetwork/lnd/kvdb => ./kvdb - -// This replace is for addressing the CVE https://github.com/advisories/GHSA-f6mq-5m25-4r72 -// This is a indirect dependency that cannot be upgraded directly. -replace go.mongodb.org/mongo-driver => go.mongodb.org/mongo-driver v1.5.1 - -replace git.schwanenlied.me/yawning/bsaes.git => github.com/Yawning/bsaes v0.0.0-20180720073208-c0276d75487e +require ( + github.com/NebulousLabs/fastrand v0.0.0-20181203155948-6fb6489aac4e // indirect + github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect + github.com/aead/siphash v1.0.1 // indirect + github.com/andybalholm/brotli v1.0.3 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/btcsuite/btcwallet/wallet/txsizes v1.1.0 // indirect + github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect + github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 // indirect + github.com/btcsuite/winsvc v1.0.0 // indirect + github.com/cespare/xxhash/v2 v2.1.1 // indirect + github.com/coreos/go-semver v0.3.0 // indirect + github.com/coreos/go-systemd/v22 v22.3.2 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect + github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect + github.com/decred/dcrd/lru v1.0.0 // indirect + github.com/dsnet/compress v0.0.1 // indirect + github.com/dustin/go-humanize v1.0.0 // indirect + github.com/fergusstrange/embedded-postgres v1.10.0 // indirect + github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/google/btree v1.0.1 // indirect + github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect + github.com/jackc/chunkreader/v2 v2.0.1 // indirect + github.com/jackc/pgconn v1.10.0 // indirect + github.com/jackc/pgio v1.0.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgproto3/v2 v2.1.1 // indirect + github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect + github.com/jackc/pgtype v1.8.1 // indirect + github.com/jackc/puddle v1.1.3 // indirect + github.com/jonboulle/clockwork v0.2.2 // indirect + github.com/json-iterator/go v1.1.11 // indirect + github.com/juju/loggo v0.0.0-20210728185423-eebad3a902c4 // indirect + github.com/juju/testing v0.0.0-20220203020004-a0ff61f03494 // indirect + github.com/klauspost/compress v1.13.6 // indirect + github.com/klauspost/pgzip v1.2.5 // indirect + github.com/lib/pq v1.10.3 // indirect + github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf // indirect + github.com/mattn/go-runewidth v0.0.13 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect + github.com/mholt/archiver/v3 v3.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.1 // indirect + github.com/nwaples/rardecode v1.1.2 // indirect + github.com/pierrec/lz4/v4 v4.1.8 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_model v0.2.0 // indirect + github.com/prometheus/common v0.26.0 // indirect + github.com/prometheus/procfs v0.6.0 // indirect + github.com/rivo/uniseg v0.2.0 // indirect + github.com/rogpeppe/fastuuid v1.2.0 // indirect + github.com/russross/blackfriday/v2 v2.0.1 // indirect + github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect + github.com/sirupsen/logrus v1.7.0 // indirect + github.com/soheilhy/cmux v0.1.5 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/stretchr/objx v0.2.0 // indirect + github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect + github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect + github.com/ulikunitz/xz v0.5.10 // indirect + github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect + github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect + gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect + go.etcd.io/bbolt v1.3.6 // indirect + go.etcd.io/etcd/api/v3 v3.5.0 // indirect + go.etcd.io/etcd/client/v2 v2.305.0 // indirect + go.etcd.io/etcd/pkg/v3 v3.5.0 // indirect + go.etcd.io/etcd/raft/v3 v3.5.0 // indirect + go.etcd.io/etcd/server/v3 v3.5.0 // indirect + go.opentelemetry.io/contrib v0.20.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0 // indirect + go.opentelemetry.io/otel v0.20.0 // indirect + go.opentelemetry.io/otel/exporters/otlp v0.20.0 // indirect + go.opentelemetry.io/otel/metric v0.20.0 // indirect + go.opentelemetry.io/otel/sdk v0.20.0 // indirect + go.opentelemetry.io/otel/sdk/export/metric v0.20.0 // indirect + go.opentelemetry.io/otel/sdk/metric v0.20.0 // indirect + go.opentelemetry.io/otel/trace v0.20.0 // indirect + go.opentelemetry.io/proto/otlp v0.7.0 // indirect + go.uber.org/atomic v1.7.0 // indirect + go.uber.org/multierr v1.6.0 // indirect + go.uber.org/zap v1.17.0 // indirect + golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect + golang.org/x/text v0.3.7 // indirect + golang.org/x/tools v0.1.8 // indirect + google.golang.org/genproto v0.0.0-20210617175327-b9e0b3197ced // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect + gopkg.in/errgo.v1 v1.0.1 // indirect + gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect + sigs.k8s.io/yaml v1.2.0 // indirect +) // This replace is for https://github.com/advisories/GHSA-w73w-5m7g-f7qc replace github.com/dgrijalva/jwt-go => github.com/golang-jwt/jwt v3.2.1+incompatible @@ -99,6 +169,6 @@ replace github.com/gogo/protobuf => github.com/gogo/protobuf v1.3.2 // If you change this please also update .github/pull_request_template.md and // docs/INSTALL.md. -go 1.16 +go 1.17 retract v0.0.2 diff --git a/go.sum b/go.sum index 4eea7ffaab..d1989dbd22 100644 --- a/go.sum +++ b/go.sum @@ -43,10 +43,8 @@ github.com/NebulousLabs/fastrand v0.0.0-20181203155948-6fb6489aac4e/go.mod h1:Bd github.com/NebulousLabs/go-upnp v0.0.0-20180202185039-29b680b06c82 h1:MG93+PZYs9PyEsj/n5/haQu2gK0h4tUtSy9ejtMwWa0= github.com/NebulousLabs/go-upnp v0.0.0-20180202185039-29b680b06c82/go.mod h1:GbuBk21JqF+driLX3XtJYNZjGa45YDoa9IqCTzNSfEc= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/Yawning/aez v0.0.0-20180114000226-4dad034d9db2 h1:2be4ykKKov3M1yISM2E8gnGXZ/N2SsPawfnGiXxaYEU= -github.com/Yawning/aez v0.0.0-20180114000226-4dad034d9db2/go.mod h1:9pIqrY6SXNL8vjRQE5Hd/OL5GyK/9MrGUWs87z/eFfk= -github.com/Yawning/bsaes v0.0.0-20180720073208-c0276d75487e h1:n88VxLC80RPVHbFG/kq7ItMizCVRPCyLj63UMqxLkOw= -github.com/Yawning/bsaes v0.0.0-20180720073208-c0276d75487e/go.mod h1:3JAJz+vEO82SkYEkAa2lRPkQC7lslUY24HX3929i2Ec= +github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 h1:cDVUiFo+npB0ZASqnw4q90ylaVAbnYyx0JYqK4YcGok= +github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344/go.mod h1:9pIqrY6SXNL8vjRQE5Hd/OL5GyK/9MrGUWs87z/eFfk= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= github.com/aead/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg= @@ -63,9 +61,6 @@ github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kd github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= github.com/benbjohnson/clock v1.0.3 h1:vkLuvpK4fmtSCuo60+yC63p7y0BmQ8gm5ZXGuBCJyXg= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -74,49 +69,53 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= -github.com/btcsuite/btcd v0.0.0-20190629003639-c26ffa870fd8/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= -github.com/btcsuite/btcd v0.21.0-beta.0.20201208033208-6bd4c64a54fa/go.mod h1:Sv4JPQ3/M+teHz9Bo5jBpkNcP0x6r7rdihlNL/7tTAs= -github.com/btcsuite/btcd v0.22.0-beta.0.20210803133449-f5a1fb9965e4/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA= -github.com/btcsuite/btcd v0.22.0-beta.0.20211005184431-e3449998be39 h1:o6qacOzpKubr16y0RrE2fBauRZN1rDZ1YsE26ixCgQ0= -github.com/btcsuite/btcd v0.22.0-beta.0.20211005184431-e3449998be39/go.mod h1:3PH+KbvLFfzBTCevQenPiDedjGQGt6aa70dVjJDWGTA= +github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= +github.com/btcsuite/btcd v0.22.0-beta.0.20220204213055-eaf0459ff879/go.mod h1:osu7EoKiL36UThEgzYPqdRaxeo0NU8VoXqgcnwpey0g= +github.com/btcsuite/btcd v0.22.0-beta.0.20220207191057-4dc4ff7963b4/go.mod h1:7alexyj/lHlOtr2PJK7L/+HDJZpcGDn/pAU98r7DY08= +github.com/btcsuite/btcd v0.22.0-beta.0.20220316175102-8d5c75c28923/go.mod h1:taIcYprAW2g6Z9S0gGUxyR+zDwimyDMK5ePOX+iJ2ds= +github.com/btcsuite/btcd v0.22.0-beta.0.20220413172512-bf64c8bdbbbf h1:8lTakMpAAOKTe60bNhiEjUDtcZAlrst2MF49ekuA9yI= +github.com/btcsuite/btcd v0.22.0-beta.0.20220413172512-bf64c8bdbbbf/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY= +github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= +github.com/btcsuite/btcd/btcec/v2 v2.1.1/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= +github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= +github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k= +github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU= +github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= +github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= +github.com/btcsuite/btcd/btcutil v1.1.1 h1:hDcDaXiP0uEzR8Biqo2weECKqEw0uHDZ9ixIWevVQqY= +github.com/btcsuite/btcd/btcutil v1.1.1/go.mod h1:nbKlBMNm9FGsdvKvu0essceubPiAcI57pYBNnsLAa34= +github.com/btcsuite/btcd/btcutil/psbt v1.1.1/go.mod h1:KsGzRAzAdEimzgERpK9Xm+RhuCMvc4j2ctK0BEQ8JV0= +github.com/btcsuite/btcd/btcutil/psbt v1.1.4 h1:Edx4AfBn+YPam2KP5AobDitulGp4r1Oibm8oruzkMdI= +github.com/btcsuite/btcd/btcutil/psbt v1.1.4/go.mod h1:9AyU6EQVJ9Iw9zPyNT1lcdHd6cnEZdno5wLu5FY74os= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= -github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= -github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o= -github.com/btcsuite/btcutil v1.0.3-0.20210527170813-e2ba6805a890 h1:9aGy5p7oXRUB4MCTmWm0+jzuh79GpjPIfv1leA5POD4= -github.com/btcsuite/btcutil v1.0.3-0.20210527170813-e2ba6805a890/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o= -github.com/btcsuite/btcutil/psbt v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:LVveMu4VaNSkIRTZu2+ut0HDBRuYjqGocxDMNS1KuGQ= -github.com/btcsuite/btcutil/psbt v1.0.3-0.20210527170813-e2ba6805a890 h1:0xUNvvwJ7RjzBs4nCF+YrK28S5P/b4uHkpPxY1ovGY4= -github.com/btcsuite/btcutil/psbt v1.0.3-0.20210527170813-e2ba6805a890/go.mod h1:LVveMu4VaNSkIRTZu2+ut0HDBRuYjqGocxDMNS1KuGQ= -github.com/btcsuite/btcwallet v0.12.1-0.20211022222026-9043c19d8725 h1:YsJ15EzrUG0OHghRriFuJHctw3HP3RRhe8bwR20JuDE= -github.com/btcsuite/btcwallet v0.12.1-0.20211022222026-9043c19d8725/go.mod h1:iLN1lG1MW0eREm+SikmPO8AZPz5NglBTEK/ErqkjGpo= -github.com/btcsuite/btcwallet/wallet/txauthor v1.0.0/go.mod h1:VufDts7bd/zs3GV13f/lXc/0lXrPnvxD/NvmpG/FEKU= -github.com/btcsuite/btcwallet/wallet/txauthor v1.1.0 h1:8pO0pvPX1rFRfRiol4oV6kX7dY5y4chPwhfVwUfvwtk= -github.com/btcsuite/btcwallet/wallet/txauthor v1.1.0/go.mod h1:ktYuJyumYtwG+QQ832Q+kqvxWJRAei3Nqs5qhSn4nww= -github.com/btcsuite/btcwallet/wallet/txrules v1.0.0/go.mod h1:UwQE78yCerZ313EXZwEiu3jNAtfXj2n2+c8RWiE/WNA= -github.com/btcsuite/btcwallet/wallet/txrules v1.1.0 h1:Vg8G8zhNVjaCdwJg2QOmLoWn4RTP7K0J9xlwY8CJnLY= -github.com/btcsuite/btcwallet/wallet/txrules v1.1.0/go.mod h1:Zn9UTqpiTH+HOd5BLzSBzULzlOPmcoeyQIA0cp0WbQQ= -github.com/btcsuite/btcwallet/wallet/txsizes v1.0.0/go.mod h1:pauEU8UuMFiThe5PB3EO+gO5kx87Me5NvdQDsTuq6cs= +github.com/btcsuite/btcwallet v0.15.0 h1:FdgC7JySVQJIcU+3W+kswDPv8rtzArGNQLOn2g3TiLg= +github.com/btcsuite/btcwallet v0.15.0/go.mod h1:EE9BactCCWhCFoVfxCJrSFINrYqLx/Tq6quxRlPTpzM= +github.com/btcsuite/btcwallet/wallet/txauthor v1.2.1/go.mod h1:/74bubxX5Js48d76nf/TsNabpYp/gndUuJw4chzCmhU= +github.com/btcsuite/btcwallet/wallet/txauthor v1.2.3 h1:M2yr5UlULvpqtxUqpMxTME/pA92Z9cpqeyvAFk9lAg0= +github.com/btcsuite/btcwallet/wallet/txauthor v1.2.3/go.mod h1:T2xSiKGpUkSLCh68aF+FMXmKK9mFqNdHl9VaqOr+JjU= +github.com/btcsuite/btcwallet/wallet/txrules v1.2.0 h1:BtEN5Empw62/RVnZ0VcJaVtVlBijnLlJY+dwjAye2Bg= +github.com/btcsuite/btcwallet/wallet/txrules v1.2.0/go.mod h1:AtkqiL7ccKWxuLYtZm8Bu8G6q82w4yIZdgq6riy60z0= github.com/btcsuite/btcwallet/wallet/txsizes v1.1.0 h1:wZnOolEAeNOHzHTnznw/wQv+j35ftCIokNrnOTOU5o8= github.com/btcsuite/btcwallet/wallet/txsizes v1.1.0/go.mod h1:pauEU8UuMFiThe5PB3EO+gO5kx87Me5NvdQDsTuq6cs= -github.com/btcsuite/btcwallet/walletdb v1.3.4/go.mod h1:oJDxAEUHVtnmIIBaa22wSBPTVcs6hUp5NKWmI8xDwwU= github.com/btcsuite/btcwallet/walletdb v1.3.5/go.mod h1:oJDxAEUHVtnmIIBaa22wSBPTVcs6hUp5NKWmI8xDwwU= -github.com/btcsuite/btcwallet/walletdb v1.3.6-0.20210803004036-eebed51155ec h1:zcAU3Ij8SmqaE+ITtS76fua2Niq7DRNp46sJRhi8PiI= github.com/btcsuite/btcwallet/walletdb v1.3.6-0.20210803004036-eebed51155ec/go.mod h1:oJDxAEUHVtnmIIBaa22wSBPTVcs6hUp5NKWmI8xDwwU= -github.com/btcsuite/btcwallet/wtxmgr v1.3.0/go.mod h1:awQsh1n/0ZrEQ+JZgWvHeo153ubzEisf/FyNtwI0dDk= -github.com/btcsuite/btcwallet/wtxmgr v1.3.1-0.20210822222949-9b5a201c344c h1:owWPexGfK4eSK4/Zy+XK2lET5qsnW7FRAc8OCOdD0Fg= -github.com/btcsuite/btcwallet/wtxmgr v1.3.1-0.20210822222949-9b5a201c344c/go.mod h1:UM38ixX8VwJ9qey4umf//0H3ndn5kSImFZ46V54Nd5Q= +github.com/btcsuite/btcwallet/walletdb v1.4.0 h1:/C5JRF+dTuE2CNMCO/or5N8epsrhmSM4710uBQoYPTQ= +github.com/btcsuite/btcwallet/walletdb v1.4.0/go.mod h1:oJDxAEUHVtnmIIBaa22wSBPTVcs6hUp5NKWmI8xDwwU= +github.com/btcsuite/btcwallet/wtxmgr v1.5.0 h1:WO0KyN4l6H3JWnlFxfGR7r3gDnlGT7W2cL8vl6av4SU= +github.com/btcsuite/btcwallet/wtxmgr v1.5.0/go.mod h1:TQVDhFxseiGtZwEPvLgtfyxuNUDsIdaJdshvWzR0HJ4= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= github.com/btcsuite/golangcrypto v0.0.0-20150304025918-53f62d9b43e8/go.mod h1:tYvUd8KLhm/oXvUeSEs2VlLghFjQt9+ZaF9ghH0JNjc= github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= -github.com/btcsuite/goleveldb v1.0.0 h1:Tvd0BfvqX9o823q1j2UZ/epQo09eJh6dTcRp79ilIN4= github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= -github.com/btcsuite/snappy-go v1.0.0 h1:ZxaA6lo2EpxGddsA8JwWOcxlzRybb444sgmeJQMJGQE= github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3SkEwmHoWBmX1DNXhXZqlTpq6s4tyJGc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= @@ -154,6 +153,8 @@ github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7 github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -162,6 +163,10 @@ github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= github.com/decred/dcrd/lru v1.0.0 h1:Kbsb1SFDsIlaupWPwsPp+dkxiBY1frcS07PCPgotKz8= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= @@ -170,6 +175,8 @@ github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5Jflh github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dvyukov/go-fuzz v0.0.0-20210602112143-b1f3d6f4ef4e h1:qTP1telKJHlToHlwPQNmVg4yfMDMHe4Z3SYmzkrvA2M= +github.com/dvyukov/go-fuzz v0.0.0-20210602112143-b1f3d6f4ef4e/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -184,6 +191,8 @@ github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoD github.com/frankban/quicktest v1.2.2 h1:xfmOhhoH5fGPgbEAlhLpJH9p0z/0Qizio9osmvn9IUY= github.com/frankban/quicktest v1.2.2/go.mod h1:Qh/WofXFeiAFII1aEBu529AtJo6Zg2VHscnEsbBnJ20= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JYMGs= github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -198,37 +207,7 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-openapi/errors v0.19.2 h1:a2kIyV3w+OS3S97zxUndRVD46+FhGOUBDFY7nmu4CsY= -github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= -github.com/go-openapi/strfmt v0.19.5 h1:0utjKrw+BAh8s57XE9Xz8DUBsVvPmRUB6styvl9wWIM= -github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= -github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= -github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= -github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= -github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= -github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= -github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= -github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= -github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= -github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= -github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= -github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= -github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= -github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= -github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= -github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= -github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= -github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= -github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= -github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= -github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= -github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= -github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= -github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= -github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= @@ -284,7 +263,6 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= @@ -300,8 +278,6 @@ github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= @@ -338,7 +314,6 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= @@ -394,14 +369,11 @@ github.com/jackpal/gateway v1.0.5 h1:qzXWUJfuMdlLMtt0a3Dgt+xkWQiA5itDEITVJtuSwMc github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA= github.com/jackpal/go-nat-pmp v0.0.0-20170405195558-28a68d0c24ad h1:heFfj7z0pGsNCekUlsFhO2jstxO4b5iQ665LjwM5mDc= github.com/jackpal/go-nat-pmp v0.0.0-20170405195558-28a68d0c24ad/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= -github.com/jedib0t/go-pretty v4.3.0+incompatible h1:CGs8AVhEKg/n9YbUenWmNStRW2PHJzaeDodcfvRAbIo= -github.com/jedib0t/go-pretty v4.3.0+incompatible/go.mod h1:XemHduiw8R651AF9Pt4FwCTKeG3oo7hrHJAoznj9nag= +github.com/jedib0t/go-pretty/v6 v6.2.7 h1:4823Lult/tJ0VI1PgW3aSKw59pMWQ6Kzv9b3Bj6MwY0= +github.com/jedib0t/go-pretty/v6 v6.2.7/go.mod h1:FMkOpgGD3EZ91cW8g/96RfxoV7bdeJyzXPYgz1L1ln0= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= @@ -415,24 +387,50 @@ github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/juju/ansiterm v0.0.0-20160907234532-b99631de12cf/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= +github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= github.com/juju/clock v0.0.0-20190205081909-9c5c9712527c h1:3UvYABOQRhJAApj9MdCN+Ydv841ETSoy6xLzdmmr/9A= github.com/juju/clock v0.0.0-20190205081909-9c5c9712527c/go.mod h1:nD0vlnrUjcjJhqN5WuCWZyzfd5AHZAC9/ajvbSx69xA= -github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d h1:hJXjZMxj0SWlMoQkzeZDLi2cmeiWKa7y1B8Rg+qaoEc= -github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= -github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 h1:UUHMLvzt/31azWTN/ifGWef4WUqvXk0iRqdhdy/2uzI= -github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= +github.com/juju/cmd v0.0.0-20171107070456-e74f39857ca0/go.mod h1:yWJQHl73rdSX4DHVKGqkAip+huBslxRwS8m9CrOLq18= +github.com/juju/collections v0.0.0-20200605021417-0d0ec82b7271 h1:4R626WTwa7pRYQFiIRLVPepMhm05eZMEx+wIurRnMLc= +github.com/juju/collections v0.0.0-20200605021417-0d0ec82b7271/go.mod h1:5XgO71dV1JClcOJE+4dzdn4HrI5LiyKd7PlVG6eZYhY= +github.com/juju/errors v0.0.0-20150916125642-1b5e39b83d18/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= +github.com/juju/errors v0.0.0-20200330140219-3fe23663418f/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= +github.com/juju/errors v0.0.0-20220203013757-bd733f3c86b9 h1:EJHbsNpQyupmMeWTq7inn+5L/WZ7JfzCVPJ+DP9McCQ= +github.com/juju/errors v0.0.0-20220203013757-bd733f3c86b9/go.mod h1:TRm7EVGA3mQOqSVcBySRY7a9Y1/gyVhh/WTCnc5sD4U= +github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= +github.com/juju/httpprof v0.0.0-20141217160036-14bf14c30767/go.mod h1:+MaLYz4PumRkkyHYeXJ2G5g5cIW0sli2bOfpmbaMV/g= +github.com/juju/loggo v0.0.0-20170605014607-8232ab8918d9/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= +github.com/juju/loggo v0.0.0-20200526014432-9ce3a2e09b5e/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= +github.com/juju/loggo v0.0.0-20210728185423-eebad3a902c4 h1:NO5tuyw++EGLnz56Q8KMyDZRwJwWO8jQnj285J3FOmY= +github.com/juju/loggo v0.0.0-20210728185423-eebad3a902c4/go.mod h1:NIXFioti1SmKAlKNuUwbMenNdef59IF52+ZzuOmHYkg= +github.com/juju/mgo/v2 v2.0.0-20210302023703-70d5d206e208 h1:/WiCm+Vpj87e4QWuWwPD/bNE9kDrWCLvPBHOQNcG2+A= +github.com/juju/mgo/v2 v2.0.0-20210302023703-70d5d206e208/go.mod h1:0OChplkvPTZ174D2FYZXg4IB9hbEwyHkD+zT+/eK+Fg= +github.com/juju/mutex v0.0.0-20171110020013-1fe2a4bf0a3a/go.mod h1:Y3oOzHH8CQ0Ppt0oCKJ2JFO81/EsWenH5AEqigLH+yY= +github.com/juju/retry v0.0.0-20151029024821-62c620325291/go.mod h1:OohPQGsr4pnxwD5YljhQ+TZnuVRYpa5irjugL1Yuif4= github.com/juju/retry v0.0.0-20180821225755-9058e192b216 h1:/eQL7EJQKFHByJe3DeE8Z36yqManj9UY5zppDoQi4FU= github.com/juju/retry v0.0.0-20180821225755-9058e192b216/go.mod h1:OohPQGsr4pnxwD5YljhQ+TZnuVRYpa5irjugL1Yuif4= -github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2 h1:Pp8RxiF4rSoXP9SED26WCfNB28/dwTDpPXS8XMJR8rc= +github.com/juju/testing v0.0.0-20180402130637-44801989f0f7/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA= github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA= -github.com/juju/utils v0.0.0-20180820210520-bf9cc5bdd62d h1:irPlN9z5VCe6BTsqVsxheCZH99OFSmqSVyTigW4mEoY= -github.com/juju/utils v0.0.0-20180820210520-bf9cc5bdd62d/go.mod h1:6/KLg8Wz/y2KVGWEpkK9vMNGkOnu4k/cqs8Z1fKjTOk= -github.com/juju/version v0.0.0-20180108022336-b64dbd566305 h1:lQxPJ1URr2fjsKnJRt/BxiIxjLt9IKGvS+0injMHbag= +github.com/juju/testing v0.0.0-20210302031854-2c7ee8570c07/go.mod h1:7lxZW0B50+xdGFkvhAb8bwAGt6IU87JB1H9w4t8MNVM= +github.com/juju/testing v0.0.0-20220202055744-1ad0816210a6/go.mod h1:QgWc2UdIPJ8t3rnvv95tFNOsQDfpXYEZDbP281o3b2c= +github.com/juju/testing v0.0.0-20220203020004-a0ff61f03494 h1:XEDzpuZb8Ma7vLja3+5hzUqVTvAqm5Y+ygvnDs5iTMM= +github.com/juju/testing v0.0.0-20220203020004-a0ff61f03494/go.mod h1:rUquetT0ALL48LHZhyRGvjjBH8xZaZ8dFClulKK5wK4= +github.com/juju/utils v0.0.0-20180424094159-2000ea4ff043/go.mod h1:6/KLg8Wz/y2KVGWEpkK9vMNGkOnu4k/cqs8Z1fKjTOk= +github.com/juju/utils v0.0.0-20200116185830-d40c2fe10647 h1:wQpkHVbIIpz1PCcLYku9KFWsJ7aEMQXHBBmLy3tRBTk= +github.com/juju/utils v0.0.0-20200116185830-d40c2fe10647/go.mod h1:6/KLg8Wz/y2KVGWEpkK9vMNGkOnu4k/cqs8Z1fKjTOk= +github.com/juju/utils/v2 v2.0.0-20200923005554-4646bfea2ef1/go.mod h1:fdlDtQlzundleLLz/ggoYinEt/LmnrpNKcNTABQATNI= +github.com/juju/utils/v3 v3.0.0-20220130232349-cd7ecef0e94a h1:5ZWDCeCF0RaITrZGemzmDFIhjR/MVSvBUqgSyaeTMbE= +github.com/juju/utils/v3 v3.0.0-20220130232349-cd7ecef0e94a/go.mod h1:LzwbbEN7buYjySp4nqnti6c6olSqRXUk6RkbSUUP1n8= +github.com/juju/version v0.0.0-20161031051906-1f41e27e54f2/go.mod h1:kE8gK5X0CImdr7qpSKl3xB2PmpySSmfj7zVbkZFs81U= github.com/juju/version v0.0.0-20180108022336-b64dbd566305/go.mod h1:kE8gK5X0CImdr7qpSKl3xB2PmpySSmfj7zVbkZFs81U= +github.com/juju/version v0.0.0-20191219164919-81c1be00b9a6 h1:nrqc9b4YKpKV4lPI3GPPFbo5FUuxkWxgZE2Z8O4lgaw= +github.com/juju/version v0.0.0-20191219164919-81c1be00b9a6/go.mod h1:kE8gK5X0CImdr7qpSKl3xB2PmpySSmfj7zVbkZFs81U= +github.com/juju/version/v2 v2.0.0-20211007103408-2e8da085dc23 h1:wtEPbidt1VyHlb8RSztU6ySQj29FLsOQiI9XiJhXDM4= +github.com/juju/version/v2 v2.0.0-20211007103408-2e8da085dc23/go.mod h1:Ljlbryh9sYaUSGXucslAEDf0A2XUSGvDbHJgW8ps6nc= +github.com/julienschmidt/httprouter v1.1.1-0.20151013225520-77a895ad01eb/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= -github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= @@ -440,7 +438,6 @@ github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec/go.mod h1:J+Gs4SYgM6 github.com/kkdai/bstream v1.0.0 h1:Se5gHwgp2VT2uHfDrkbbgbgEvV9cimLELwrPJctSjg8= github.com/kkdai/bstream v1.0.0/go.mod h1:FDnDOHt5Yx4p3FaHcioFT0QjDOtgUpvjeZqAs+NVZZA= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= @@ -452,8 +449,9 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -468,28 +466,54 @@ github.com/lib/pq v1.10.3 h1:v9QZf2Sn6AmjXtQeFpdoq/eaNtYP6IN+7lcrygsIAtg= github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf h1:HZKvJUHlcXI/f/O0Avg7t8sqkPo78HFzjmeYFl6DPnc= github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf/go.mod h1:vxmQPeIQxPf6Jf9rM8R+B4rKBqLA2AjttNxkFBL2Plk= -github.com/lightninglabs/neutrino v0.12.1/go.mod h1:GlKninWpRBbL7b8G0oQ36/8downfnFwKsr0hbRA6E/E= -github.com/lightninglabs/neutrino v0.12.3 h1:9M4VTcfu9Ay4Qy8UgB4OFZ51MKKg1BAlYf2v66ZUMxc= -github.com/lightninglabs/neutrino v0.12.3/go.mod h1:GlKninWpRBbL7b8G0oQ36/8downfnFwKsr0hbRA6E/E= +github.com/lightninglabs/neutrino v0.13.2/go.mod h1:Cv/v8oHiPhuGiGvGgO+rIMhwCwEdsQFu6as840i2afw= +github.com/lightninglabs/neutrino v0.14.1 h1:ALFckeS3CPmWZmX75vxZaWvz2TUebuASH+CR4cqVo18= +github.com/lightninglabs/neutrino v0.14.1/go.mod h1:SV9ccrw2m6t6UvJX8xB//W0Dv+LEwMTbjg4V/Fb5KwU= github.com/lightninglabs/protobuf-hex-display v1.4.3-hex-display h1:RZJ8H4ueU/aQ9pFtx5wqsuD3B/DezrewJeVwDKKYY8E= github.com/lightninglabs/protobuf-hex-display v1.4.3-hex-display/go.mod h1:2oKOBU042GKFHrdbgGiKax4xVrFiZu51lhacUZQ9MnE= -github.com/lightningnetwork/lightning-onion v1.0.2-0.20210520211913-522b799e65b1 h1:h1BsjPzWea790mAXISoiT/qr0JRcixTCDNLmjsDThSw= -github.com/lightningnetwork/lightning-onion v1.0.2-0.20210520211913-522b799e65b1/go.mod h1:rigfi6Af/KqsF7Za0hOgcyq2PNH4AN70AaMRxcJkff4= +github.com/lightningnetwork/lightning-onion v1.0.2-0.20220211021909-bb84a1ccb0c5 h1:TkKwqFcQTGYoI+VEqyxA8rxpCin8qDaYX0AfVRinT3k= +github.com/lightningnetwork/lightning-onion v1.0.2-0.20220211021909-bb84a1ccb0c5/go.mod h1:7dDx73ApjEZA0kcknI799m2O5kkpfg4/gr7N092ojNo= +github.com/lightningnetwork/lnd/cert v1.1.1 h1:Nsav0RlIDRbOnzz2Yu69SQlK939IKya3Q2S0mDviIN8= +github.com/lightningnetwork/lnd/cert v1.1.1/go.mod h1:1P46svkkd73oSoeI4zjkVKgZNwGq8bkGuPR8z+5vQUs= +github.com/lightningnetwork/lnd/clock v1.0.1/go.mod h1:KnQudQ6w0IAMZi1SgvecLZQZ43ra2vpDNj7H/aasemg= +github.com/lightningnetwork/lnd/clock v1.1.0 h1:/yfVAwtPmdx45aQBoXQImeY7sOIEr7IXlImRMBOZ7GQ= +github.com/lightningnetwork/lnd/clock v1.1.0/go.mod h1:KnQudQ6w0IAMZi1SgvecLZQZ43ra2vpDNj7H/aasemg= +github.com/lightningnetwork/lnd/healthcheck v1.0.0/go.mod h1:u92p1JGFJNMSkMvztKEwmt1P3TRnLeJBXZ3M85xkU1E= +github.com/lightningnetwork/lnd/healthcheck v1.2.2 h1:im+qcpgSuteqRCGeorT9yqVXuLrS6A7/acYzGgarMS4= +github.com/lightningnetwork/lnd/healthcheck v1.2.2/go.mod h1:IWY0GChlarRbXFkFDdE4WY5POYJabe/7/H1iCZt4ZKs= +github.com/lightningnetwork/lnd/kvdb v1.3.1 h1:gEz3zudNNRrCLEvqRaktYoKwsUblyHX+MKjR0aI3QnM= +github.com/lightningnetwork/lnd/kvdb v1.3.1/go.mod h1:x+IpsuDynubjokUofavLXroeGfS/WrqUXXTK6vN/gp4= +github.com/lightningnetwork/lnd/queue v1.0.1/go.mod h1:vaQwexir73flPW43Mrm7JOgJHmcEFBWWSl9HlyASoms= +github.com/lightningnetwork/lnd/queue v1.1.0 h1:YpCJjlIvVxN/R7ww2aNiY8ex7U2fucZDLJ67tI3HFx8= +github.com/lightningnetwork/lnd/queue v1.1.0/go.mod h1:YTkTVZCxz8tAYreH27EO3s8572ODumWrNdYW2E/YKxg= +github.com/lightningnetwork/lnd/ticker v1.0.0/go.mod h1:iaLXJiVgI1sPANIF2qYYUJXjoksPNvGNYowB8aRbpX0= +github.com/lightningnetwork/lnd/ticker v1.1.0 h1:ShoBiRP3pIxZHaETndfQ5kEe+S4NdAY1hiX7YbZ4QE4= +github.com/lightningnetwork/lnd/ticker v1.1.0/go.mod h1:ubqbSVCn6RlE0LazXuBr7/Zi6QT0uQo++OgIRBxQUrk= +github.com/lightningnetwork/lnd/tlv v1.0.2/go.mod h1:fICAfsqk1IOsC1J7G9IdsWX1EqWRMqEDCNxZJSKr9C4= +github.com/lightningnetwork/lnd/tlv v1.0.3 h1:0xBZcPuXagP6f7TY/RnLNR4igE21ov6qUdTr5NyvhhI= +github.com/lightningnetwork/lnd/tlv v1.0.3/go.mod h1:dzR/aZetBri+ZY/fHbwV06fNn/3UID6htQzbHfREFdo= +github.com/lightningnetwork/lnd/tor v1.0.0 h1:wvEc7I+Y7IOtPglVP3cVBbYhiVhc7uTd7cMF9gQRzwA= +github.com/lightningnetwork/lnd/tor v1.0.0/go.mod h1:RDtaAdwfAm+ONuPYwUhNIH1RAvKPv+75lHPOegUcz64= github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 h1:sjOGyegMIhvgfq5oaue6Td+hxZuf3tDC8lAPrFldqFw= github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796/go.mod h1:3p7ZTf9V1sNPI5H8P3NkTFF4LuwMdPl2DodF60qAKqY= github.com/ltcsuite/ltcutil v0.0.0-20181217130922-17f3b04680b6/go.mod h1:8Vg/LTOO0KYa/vlHWJ6XZAevPQThGH5sufO0Hrou/lA= +github.com/lunixbochs/vtclean v0.0.0-20160125035106-4fbf7632a2c6/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= -github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= +github.com/masterzen/azure-sdk-for-go v3.2.0-beta.0.20161014135628-ee4f0065d00c+incompatible/go.mod h1:mf8fjOu33zCqxUjuiU3I8S1lJMyEAlH+0F2+M5xl3hE= +github.com/masterzen/simplexml v0.0.0-20160608183007-4572e39b1ab9/go.mod h1:kCEbxUJlNDEBNbdQMkPSp6yaKcRXVI6f4ddk8Riv4bc= +github.com/masterzen/winrm v0.0.0-20161014151040-7a535cd943fc/go.mod h1:CfZSN7zwz5gJiFhZJz49Uzk7mEBHIceWmbFmYx7Hf7E= +github.com/masterzen/xmlpath v0.0.0-20140218185901-13f4951698ad/go.mod h1:A0zPC53iKKKcXYxr4ROjpQRQ5FgJXtelNdSmHHuq/tY= +github.com/mattn/go-colorable v0.0.6/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.0-20160806122752-66b8e73f3f5c/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mholt/archiver/v3 v3.5.0 h1:nE8gZIrw66cu4osS/U7UW7YDuGMHssxKutU8IfWxwWE= @@ -504,7 +528,6 @@ github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eI github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= @@ -512,23 +535,28 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U= github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/nwaples/rardecode v1.1.2 h1:Cj0yZY6T1Zx1R7AhTbyGSALm44/Mmq+BAPc4B/p/d3M= github.com/nwaples/rardecode v1.1.2/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= +github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= github.com/pierrec/lz4/v4 v4.0.3/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pierrec/lz4/v4 v4.1.8 h1:ieHkV+i2BRzngO4Wd/3HGowuZStgq6QkPsD1eolNAO4= github.com/pierrec/lz4/v4 v4.1.8/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= @@ -536,6 +564,7 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= @@ -563,15 +592,16 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= @@ -579,9 +609,9 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= @@ -595,7 +625,6 @@ github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -611,11 +640,12 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= -github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 h1:uruHq4dN7GR16kFc5fp3d1RIYzJW5onx8Ybykw2YQFA= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= @@ -623,22 +653,21 @@ github.com/tv42/zbase32 v0.0.0-20160707012821-501572607d02 h1:tcJ6OjwOMvExLlzrAV github.com/tv42/zbase32 v0.0.0-20160707012821-501572607d02/go.mod h1:tHlrkM198S068ZqfrO6S8HsoJq2bF3ETfTL+kt4tInY= github.com/ulikunitz/xz v0.5.8 h1:ERv8V6GKqVi23rgu5cj9pVfVzJbOqAY2Ntl88O6c2nQ= github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= -github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= -github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= -github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= +github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA= +github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo= +gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec/go.mod h1:BZ1RAoRPbCxum9Grlv5aeksu2H8BiKehBYooU2LFiOQ= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.5-0.20200615073812-232d8fc87f50/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= @@ -657,8 +686,6 @@ go.etcd.io/etcd/raft/v3 v3.5.0 h1:kw2TmO3yFTgE+F0mdKkG7xMxkit2duBDa2Hu6D/HMlw= go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc= go.etcd.io/etcd/server/v3 v3.5.0 h1:jk8D/lwGEDlQU9kZXUFMSANkE22Sg5+mW27ip8xcF9E= go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4= -go.mongodb.org/mongo-driver v1.5.1 h1:9nOVLGDfOaZ9R0tBumx/BcuqkbFpyTCU2r/Po7A2azI= -go.mongodb.org/mongo-driver v1.5.1/go.mod h1:gRXCHX4Jo7J0IJ1oDQyUxF7jfy19UfxniMS4xxMmUqw= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -706,20 +733,18 @@ go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180214000028-650f4a345ab4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= -golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= @@ -736,6 +761,7 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20220426173459-3bcf042a4bf5/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -760,6 +786,9 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= +golang.org/x/net v0.0.0-20180406214816-61147c48b25b/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -796,13 +825,15 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210913180222-943fd674d43e h1:+b/22bPvDYt4NPDcy4xAGCmON713ONAWFeY3Z7I3tR8= -golang.org/x/net v0.0.0-20210913180222-943fd674d43e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f h1:OfiFi4JbukWwe3lzw+xunroH1mnC1e2Gy5cxNJApiSY= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -814,7 +845,6 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -823,6 +853,7 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180816055513-1c9583448a9c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -830,24 +861,24 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -863,10 +894,12 @@ golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -879,8 +912,9 @@ golang.org/x/sys v0.0.0-20210426080607-c94f62235c83/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210915083310-ed5796bab164 h1:7ZDGnxgHAMw7thfC5bEos0RDAccZKxioiWBhfIe+tvw= -golang.org/x/sys v0.0.0-20210915083310-ed5796bab164/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -891,8 +925,9 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -906,14 +941,10 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= @@ -954,8 +985,10 @@ golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.3 h1:L69ShwSZEyCsLKoAxDKeMvLDZkumEe8gXUZAjab0tX8= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.8-0.20211029000441-d6a9af8af023/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +golang.org/x/tools v0.1.8 h1:P1HhGGuLW4aAclzjtmJdf0mJOjVUZUzOTqkAkWL+l6w= +golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1055,27 +1088,32 @@ google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/l google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20160105164936-4f90aeace3a2/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v1 v1.0.0-20161222125816-442357a80af5/go.mod h1:u0ALmqvLRxLI95fkdCEWrE6mhWYZW1aMOJHp5YXLHTg= gopkg.in/errgo.v1 v1.0.1 h1:oQFRXzZ7CkBGdm1XZm/EbQYaYNNEElNBOd09M6cqNso= gopkg.in/errgo.v1 v1.0.1/go.mod h1:3NjfXwocQRYAPTq4/fzX+CwUhPRcR/azYRhj8G+LqMo= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/httprequest.v1 v1.1.1/go.mod h1:/CkavNL+g3qLOrpFHVrEx4NKepeqR4XTZWNj4sGGjz0= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/macaroon-bakery.v2 v2.0.1 h1:0N1TlEdfLP4HXNCg7MQUMp5XwvOoxk+oe9Owr2cpvsc= gopkg.in/macaroon-bakery.v2 v2.0.1/go.mod h1:B4/T17l+ZWGwxFSZQmlBwp25x+og7OkhETfr3S9MbIA= gopkg.in/macaroon.v2 v2.0.0 h1:LVWycAfeJBUjCIqfR9gqlo7I8vmiXRr51YEOZ1suop8= gopkg.in/macaroon.v2 v2.0.0/go.mod h1:+I6LnTMkm/uV5ew/0nsulNjL16SK4+C8yDmRUzHR17I= -gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 h1:VpOs+IwYnYBaFnrNAeB8UUWtL3vEUnzSCL1nVjPhqrw= +gopkg.in/mgo.v2 v2.0.0-20160818015218-f2b6f6c918c4/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637/go.mod h1:BHsqpu/nsuzkT5BpiH1EMZPLyqSMM8JbIavyFACoFNk= +gopkg.in/yaml.v2 v2.0.0-20170712054546-1be3d31502d6/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -1097,6 +1135,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +launchpad.net/gocheck v0.0.0-20140225173054-000000000087/go.mod h1:hj7XX3B/0A+80Vse0e+BUHsHMTEhd0O4cpUHr/e/BUM= +launchpad.net/xmlpath v0.0.0-20130614043138-000000000004/go.mod h1:vqyExLOM3qBx7mvYRkoxjSCF945s0mbe7YynlKYXtsA= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/healthcheck/go.mod b/healthcheck/go.mod index 0b7573a5bf..c0caa8177c 100644 --- a/healthcheck/go.mod +++ b/healthcheck/go.mod @@ -4,7 +4,8 @@ go 1.16 require ( github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f - github.com/lightningnetwork/lnd/ticker v1.0.0 + github.com/lightningnetwork/lnd/ticker v1.1.0 + github.com/lightningnetwork/lnd/tor v1.0.0 github.com/stretchr/testify v1.7.0 - golang.org/x/sys v0.0.0-20210426080607-c94f62235c83 + golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e ) diff --git a/healthcheck/go.sum b/healthcheck/go.sum index a724e6b7a0..cb5f1ad9a3 100644 --- a/healthcheck/go.sum +++ b/healthcheck/go.sum @@ -1,17 +1,114 @@ +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= +github.com/btcsuite/btcd v0.22.0-beta.0.20220207191057-4dc4ff7963b4 h1:CEGr/598C/0LZQUoioaT6sdGGcJgu4+ck0PDeJ/QkKs= +github.com/btcsuite/btcd v0.22.0-beta.0.20220207191057-4dc4ff7963b4/go.mod h1:7alexyj/lHlOtr2PJK7L/+HDJZpcGDn/pAU98r7DY08= +github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= +github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= +github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/lightningnetwork/lnd/ticker v1.0.0 h1:S1b60TEGoTtCe2A0yeB+ecoj/kkS4qpwh6l+AkQEZwU= -github.com/lightningnetwork/lnd/ticker v1.0.0/go.mod h1:iaLXJiVgI1sPANIF2qYYUJXjoksPNvGNYowB8aRbpX0= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/lightningnetwork/lnd/ticker v1.1.0 h1:ShoBiRP3pIxZHaETndfQ5kEe+S4NdAY1hiX7YbZ4QE4= +github.com/lightningnetwork/lnd/ticker v1.1.0/go.mod h1:ubqbSVCn6RlE0LazXuBr7/Zi6QT0uQo++OgIRBxQUrk= +github.com/lightningnetwork/lnd/tor v1.0.0 h1:wvEc7I+Y7IOtPglVP3cVBbYhiVhc7uTd7cMF9gQRzwA= +github.com/lightningnetwork/lnd/tor v1.0.0/go.mod h1:RDtaAdwfAm+ONuPYwUhNIH1RAvKPv+75lHPOegUcz64= +github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg= +github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -golang.org/x/sys v0.0.0-20210426080607-c94f62235c83 h1:kHSDPqCtsHZOg0nVylfTo20DDhE9gG4Y0jn7hKQ0QAM= -golang.org/x/sys v0.0.0-20210426080607-c94f62235c83/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f h1:OfiFi4JbukWwe3lzw+xunroH1mnC1e2Gy5cxNJApiSY= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/healthcheck/tor_connection.go b/healthcheck/tor_connection.go index 13f90a8c8b..a6befd99e5 100644 --- a/healthcheck/tor_connection.go +++ b/healthcheck/tor_connection.go @@ -48,7 +48,7 @@ func CheckTorServiceStatus(tc *tor.Controller, return restartTorController(tc, createService) // If this is not a connection layer error, such as - // ErrServiceNotCreated or ErrServiceIDUnmatch, there's little we can + // ErrServiceNotCreated or ErrServiceIDMismatch, there's little we can // do but to report the error to the user. default: return err diff --git a/htlcswitch/circuit_map.go b/htlcswitch/circuit_map.go index d5bb5f376d..a62067727f 100644 --- a/htlcswitch/circuit_map.go +++ b/htlcswitch/circuit_map.go @@ -213,6 +213,10 @@ type CircuitMapConfig struct { // ExtractErrorEncrypter derives the shared secret used to encrypt // errors from the obfuscator's ephemeral public key. ExtractErrorEncrypter hop.ErrorEncrypterExtracter + + // CheckResolutionMsg checks whether a given resolution message exists + // for the passed CircuitKey. + CheckResolutionMsg func(outKey *CircuitKey) error } // NewCircuitMap creates a new instance of the circuitMap. @@ -400,7 +404,19 @@ func (cm *circuitMap) cleanClosedChannels() error { // Check if the outgoing channel ID can be found in the // closed channel ID map. Notice that we need to store // the outgoing key because it's used for db query. + // + // NOTE: We skip this if a resolution message can be + // found under the outKey. This means that there is an + // existing resolution message(s) that need to get to + // the incoming links. if isClosedChannel(outKey.ChanID) { + // Check the resolution message store. A return + // value of nil means we need to skip deleting + // these circuits. + if cm.cfg.CheckResolutionMsg(&outKey) == nil { + return nil + } + keystoneKeySet[outKey] = struct{}{} // Also update circuitKeySet to mark the @@ -411,7 +427,6 @@ func (cm *circuitMap) cleanClosedChannels() error { return nil }) return err - }, func() { // Reset the sets. circuitKeySet = make(map[CircuitKey]struct{}) @@ -438,7 +453,7 @@ func (cm *circuitMap) cleanClosedChannels() error { return ErrCorruptedCircuitMap } - // Delete the ciruit. + // Delete the circuit. for inKey := range circuitKeySet { if err := circuitBkt.Delete(inKey.Bytes()); err != nil { return err diff --git a/htlcswitch/circuit_map_test.go b/htlcswitch/circuit_map_test.go index b2faa61ca5..af9af3fd7c 100644 --- a/htlcswitch/circuit_map_test.go +++ b/htlcswitch/circuit_map_test.go @@ -6,8 +6,8 @@ import ( "io" "testing" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/kvdb" @@ -66,6 +66,11 @@ func TestCircuitMapCleanClosedChannels(t *testing.T) { chanParams []closeChannelParams deleted []htlcswitch.Keystone untouched []htlcswitch.Keystone + + // If resMsg is true, then closed channels will not delete + // circuits if the channel was the keystone / outgoing key in + // the open circuit. + resMsg bool }{ { name: "no deletion if there are no closed channels", @@ -120,7 +125,7 @@ func TestCircuitMapCleanClosedChannels(t *testing.T) { {InKey: inKey20, OutKey: outKey20}, }, deleted: []htlcswitch.Keystone{ - {InKey: inKey00}, {InKey: inKey11}, + {InKey: inKey10}, {InKey: inKey11}, }, untouched: []htlcswitch.Keystone{ {InKey: inKey20, OutKey: outKey20}, @@ -214,13 +219,33 @@ func TestCircuitMapCleanClosedChannels(t *testing.T) { {InKey: inKey22, OutKey: outKey20}, }, }, + { + name: "don't delete circuits for outgoing", + chanParams: []closeChannelParams{ + // Creates a close channel with chanID1. + {chanID: chanID1, isPending: false}, + }, + keystones: []htlcswitch.Keystone{ + // Creates a circuit and a keystone + {InKey: inKey10, OutKey: outKey10}, + // Creates a circuit and a keystone + {InKey: inKey11, OutKey: outKey20}, + // Creates a circuit and a keystone + {InKey: inKey00, OutKey: outKey11}, + }, + deleted: []htlcswitch.Keystone{ + {InKey: inKey10, OutKey: outKey10}, + {InKey: inKey11, OutKey: outKey20}, + }, + resMsg: true, + }, } for _, tt := range testParams { test := tt t.Run(test.name, func(t *testing.T) { - cfg, circuitMap := newCircuitMap(t) + cfg, circuitMap := newCircuitMap(t, test.resMsg) // create test circuits for _, ks := range test.keystones { @@ -265,10 +290,8 @@ func TestCircuitMapCleanClosedChannels(t *testing.T) { for _, ks := range test.untouched { assertKeystoneNotDeleted(t, circuitMap, ks) } - }) } - } // createTestCircuit creates a circuit for testing with its incoming key being diff --git a/htlcswitch/circuit_test.go b/htlcswitch/circuit_test.go index fed07958ba..d9e9f4542e 100644 --- a/htlcswitch/circuit_test.go +++ b/htlcswitch/circuit_test.go @@ -2,13 +2,14 @@ package htlcswitch_test import ( "bytes" + "fmt" "io/ioutil" "reflect" "testing" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" bitcoinCfg "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcutil" sphinx "github.com/lightningnetwork/lightning-onion" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/htlcswitch" @@ -38,13 +39,13 @@ var ( func init() { // Generate a fresh key for our sphinx router. var err error - sphinxPrivKey, err = btcec.NewPrivateKey(btcec.S256()) + sphinxPrivKey, err = btcec.NewPrivateKey() if err != nil { panic(err) } // And another, whose public key will serve as the test ephemeral key. - testEphemeralPriv, err := btcec.NewPrivateKey(btcec.S256()) + testEphemeralPriv, err := btcec.NewPrivateKey() if err != nil { panic(err) } @@ -97,8 +98,9 @@ func newOnionProcessor(t *testing.T) *hop.OnionProcessor { } // newCircuitMap creates a new htlcswitch.CircuitMap using a temp db and a -// fresh sphinx router. -func newCircuitMap(t *testing.T) (*htlcswitch.CircuitMapConfig, +// fresh sphinx router. When resMsg is set to true, CheckResolutionMsg will +// always return nil. Otherwise it will always return an error. +func newCircuitMap(t *testing.T, resMsg bool) (*htlcswitch.CircuitMapConfig, htlcswitch.CircuitMap) { onionProcessor := newOnionProcessor(t) @@ -111,6 +113,18 @@ func newCircuitMap(t *testing.T) (*htlcswitch.CircuitMapConfig, ExtractErrorEncrypter: onionProcessor.ExtractErrorEncrypter, } + if resMsg { + checkRes := func(out *htlcswitch.CircuitKey) error { + return nil + } + circuitMapCfg.CheckResolutionMsg = checkRes + } else { + checkRes := func(out *htlcswitch.CircuitKey) error { + return fmt.Errorf("not found") + } + circuitMapCfg.CheckResolutionMsg = checkRes + } + circuitMap, err := htlcswitch.NewCircuitMap(circuitMapCfg) if err != nil { t.Fatalf("unable to create persistent circuit map: %v", err) @@ -124,7 +138,7 @@ func newCircuitMap(t *testing.T) (*htlcswitch.CircuitMapConfig, func TestCircuitMapInit(t *testing.T) { t.Parallel() - cfg, _ := newCircuitMap(t) + cfg, _ := newCircuitMap(t, false) restartCircuitMap(t, cfg) } @@ -231,7 +245,7 @@ func TestCircuitMapPersistence(t *testing.T) { err error ) - cfg, circuitMap := newCircuitMap(t) + cfg, circuitMap := newCircuitMap(t, false) circuit := circuitMap.LookupCircuit(htlcswitch.CircuitKey{ ChanID: chan1, @@ -490,7 +504,7 @@ func TestCircuitMapPersistence(t *testing.T) { // Removing already-removed circuit should return an error. err = circuitMap.DeleteCircuits(circuit1.Incoming) if err != nil { - t.Fatal("Unexpected failure when deleting already "+ + t.Fatalf("Unexpected failure when deleting already "+ "deleted circuit: %v", err) } @@ -649,6 +663,7 @@ func restartCircuitMap(t *testing.T, cfg *htlcswitch.CircuitMapConfig) ( FetchAllOpenChannels: db.ChannelStateDB().FetchAllOpenChannels, FetchClosedChannels: db.ChannelStateDB().FetchClosedChannels, ExtractErrorEncrypter: cfg.ExtractErrorEncrypter, + CheckResolutionMsg: cfg.CheckResolutionMsg, } cm2, err := htlcswitch.NewCircuitMap(cfg2) if err != nil { @@ -671,7 +686,7 @@ func TestCircuitMapCommitCircuits(t *testing.T) { err error ) - cfg, circuitMap := newCircuitMap(t) + cfg, circuitMap := newCircuitMap(t, false) circuit := &htlcswitch.PaymentCircuit{ Incoming: htlcswitch.CircuitKey{ @@ -767,7 +782,7 @@ func TestCircuitMapOpenCircuits(t *testing.T) { err error ) - cfg, circuitMap := newCircuitMap(t) + cfg, circuitMap := newCircuitMap(t, false) circuit := &htlcswitch.PaymentCircuit{ Incoming: htlcswitch.CircuitKey{ @@ -973,7 +988,7 @@ func TestCircuitMapTrimOpenCircuits(t *testing.T) { err error ) - cfg, circuitMap := newCircuitMap(t) + cfg, circuitMap := newCircuitMap(t, false) const nCircuits = 10 const firstTrimIndex = 7 @@ -1122,7 +1137,7 @@ func TestCircuitMapCloseOpenCircuits(t *testing.T) { err error ) - cfg, circuitMap := newCircuitMap(t) + cfg, circuitMap := newCircuitMap(t, false) circuit := &htlcswitch.PaymentCircuit{ Incoming: htlcswitch.CircuitKey{ @@ -1215,7 +1230,7 @@ func TestCircuitMapCloseUnopenedCircuit(t *testing.T) { err error ) - cfg, circuitMap := newCircuitMap(t) + cfg, circuitMap := newCircuitMap(t, false) circuit := &htlcswitch.PaymentCircuit{ Incoming: htlcswitch.CircuitKey{ @@ -1272,7 +1287,7 @@ func TestCircuitMapDeleteUnopenedCircuit(t *testing.T) { err error ) - cfg, circuitMap := newCircuitMap(t) + cfg, circuitMap := newCircuitMap(t, false) circuit := &htlcswitch.PaymentCircuit{ Incoming: htlcswitch.CircuitKey{ @@ -1331,7 +1346,7 @@ func TestCircuitMapDeleteOpenCircuit(t *testing.T) { err error ) - cfg, circuitMap := newCircuitMap(t) + cfg, circuitMap := newCircuitMap(t, false) circuit := &htlcswitch.PaymentCircuit{ Incoming: htlcswitch.CircuitKey{ diff --git a/htlcswitch/decayedlog.go b/htlcswitch/decayedlog.go index 5a39f37c37..44e4d1c518 100644 --- a/htlcswitch/decayedlog.go +++ b/htlcswitch/decayedlog.go @@ -158,9 +158,6 @@ func (d *DecayedLog) Stop() error { d.wg.Wait() - // Close boltdb. - d.db.Close() - return nil } diff --git a/htlcswitch/decayedlog_test.go b/htlcswitch/decayedlog_test.go index 42e636536b..e7b7e6e581 100644 --- a/htlcswitch/decayedlog_test.go +++ b/htlcswitch/decayedlog_test.go @@ -31,14 +31,14 @@ func tempDecayedLogPath(t *testing.T) string { // startup sets up the DecayedLog and possibly the garbage collector. func startup(dbPath string, notifier bool) (sphinx.ReplayLog, - *mock.ChainNotifier, *sphinx.HashPrefix, error) { + *mock.ChainNotifier, *sphinx.HashPrefix, func(), error) { cfg := &kvdb.BoltConfig{ DBTimeout: time.Second, } backend, err := NewBoltBackendCreator(dbPath, "sphinxreplay.db")(cfg) if err != nil { - return nil, nil, nil, fmt.Errorf("unable to create temporary "+ + return nil, nil, nil, nil, fmt.Errorf("unable to create temporary "+ "decayed log db: %v", err) } @@ -63,7 +63,7 @@ func startup(dbPath string, notifier bool) (sphinx.ReplayLog, // Open the channeldb (start the garbage collector) err = log.Start() if err != nil { - return nil, nil, nil, err + return nil, nil, nil, nil, err } // Create a HashPrefix identifier for a packet. Instead of actually @@ -72,10 +72,15 @@ func startup(dbPath string, notifier bool) (sphinx.ReplayLog, var hashedSecret sphinx.HashPrefix _, err = rand.Read(hashedSecret[:]) if err != nil { - return nil, nil, nil, err + return nil, nil, nil, nil, err } - return log, chainNotifier, &hashedSecret, nil + stop := func() { + _ = log.Stop() + backend.Close() + } + + return log, chainNotifier, &hashedSecret, stop, nil } // shutdown deletes the temporary directory that the test database uses @@ -93,7 +98,7 @@ func TestDecayedLogGarbageCollector(t *testing.T) { dbPath := tempDecayedLogPath(t) - d, notifier, hashedSecret, err := startup(dbPath, true) + d, notifier, hashedSecret, _, err := startup(dbPath, true) if err != nil { t.Fatalf("Unable to start up DecayedLog: %v", err) } @@ -154,7 +159,7 @@ func TestDecayedLogPersistentGarbageCollector(t *testing.T) { dbPath := tempDecayedLogPath(t) - d, _, hashedSecret, err := startup(dbPath, true) + d, _, hashedSecret, stop, err := startup(dbPath, true) if err != nil { t.Fatalf("Unable to start up DecayedLog: %v", err) } @@ -172,9 +177,9 @@ func TestDecayedLogPersistentGarbageCollector(t *testing.T) { } // Shut down DecayedLog and the garbage collector along with it. - d.Stop() + stop() - d2, notifier2, _, err := startup(dbPath, true) + d2, notifier2, _, _, err := startup(dbPath, true) if err != nil { t.Fatalf("Unable to restart DecayedLog: %v", err) } @@ -210,7 +215,7 @@ func TestDecayedLogInsertionAndDeletion(t *testing.T) { dbPath := tempDecayedLogPath(t) - d, _, hashedSecret, err := startup(dbPath, false) + d, _, hashedSecret, _, err := startup(dbPath, false) if err != nil { t.Fatalf("Unable to start up DecayedLog: %v", err) } @@ -248,7 +253,7 @@ func TestDecayedLogStartAndStop(t *testing.T) { dbPath := tempDecayedLogPath(t) - d, _, hashedSecret, err := startup(dbPath, false) + d, _, hashedSecret, stop, err := startup(dbPath, false) if err != nil { t.Fatalf("Unable to start up DecayedLog: %v", err) } @@ -261,9 +266,9 @@ func TestDecayedLogStartAndStop(t *testing.T) { } // Shutdown the DecayedLog's channeldb - d.Stop() + stop() - d2, _, hashedSecret2, err := startup(dbPath, false) + d2, _, hashedSecret2, stop, err := startup(dbPath, false) if err != nil { t.Fatalf("Unable to restart DecayedLog: %v", err) } @@ -288,9 +293,9 @@ func TestDecayedLogStartAndStop(t *testing.T) { } // Shutdown the DecayedLog's channeldb - d2.Stop() + stop() - d3, _, hashedSecret3, err := startup(dbPath, false) + d3, _, hashedSecret3, _, err := startup(dbPath, false) if err != nil { t.Fatalf("Unable to restart DecayedLog: %v", err) } @@ -314,7 +319,7 @@ func TestDecayedLogStorageAndRetrieval(t *testing.T) { dbPath := tempDecayedLogPath(t) - d, _, hashedSecret, err := startup(dbPath, false) + d, _, hashedSecret, _, err := startup(dbPath, false) if err != nil { t.Fatalf("Unable to start up DecayedLog: %v", err) } diff --git a/htlcswitch/hop/error_encryptor.go b/htlcswitch/hop/error_encryptor.go index 4c25333c04..4017d8e7eb 100644 --- a/htlcswitch/hop/error_encryptor.go +++ b/htlcswitch/hop/error_encryptor.go @@ -5,7 +5,7 @@ import ( "fmt" "io" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" sphinx "github.com/lightningnetwork/lightning-onion" "github.com/lightningnetwork/lnd/lnwire" ) @@ -165,7 +165,7 @@ func (s *SphinxErrorEncrypter) Decode(r io.Reader) error { } var err error - s.EphemeralKey, err = btcec.ParsePubKey(ephemeral[:], btcec.S256()) + s.EphemeralKey, err = btcec.ParsePubKey(ephemeral[:]) if err != nil { return err } diff --git a/htlcswitch/hop/iterator.go b/htlcswitch/hop/iterator.go index 20963bd7b9..c1073b1da9 100644 --- a/htlcswitch/hop/iterator.go +++ b/htlcswitch/hop/iterator.go @@ -6,7 +6,7 @@ import ( "io" "sync" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" sphinx "github.com/lightningnetwork/lightning-onion" "github.com/lightningnetwork/lnd/lnwire" ) @@ -132,6 +132,7 @@ func NewOnionProcessor(router *sphinx.Router) *OnionProcessor { // Start spins up the onion processor's sphinx router. func (p *OnionProcessor) Start() error { + log.Info("Onion processor starting") return p.router.Start() } diff --git a/htlcswitch/hop/payload.go b/htlcswitch/hop/payload.go index e233537f41..be7be5eebb 100644 --- a/htlcswitch/hop/payload.go +++ b/htlcswitch/hop/payload.go @@ -93,6 +93,10 @@ type Payload struct { // customRecords are user-defined records in the custom type range that // were included in the payload. customRecords record.CustomSet + + // metadata is additional data that is sent along with the payment to + // the payee. + metadata []byte } // NewLegacyPayload builds a Payload from the amount, cltv, and next hop @@ -115,11 +119,12 @@ func NewLegacyPayload(f *sphinx.HopData) *Payload { // should correspond to the bytes encapsulated in a TLV onion payload. func NewPayloadFromReader(r io.Reader) (*Payload, error) { var ( - cid uint64 - amt uint64 - cltv uint32 - mpp = &record.MPP{} - amp = &record.AMP{} + cid uint64 + amt uint64 + cltv uint32 + mpp = &record.MPP{} + amp = &record.AMP{} + metadata []byte ) tlvStream, err := tlv.NewStream( @@ -128,6 +133,7 @@ func NewPayloadFromReader(r io.Reader) (*Payload, error) { record.NewNextHopIDRecord(&cid), mpp.Record(), amp.Record(), + record.NewMetadataRecord(&metadata), ) if err != nil { return nil, err @@ -168,6 +174,12 @@ func NewPayloadFromReader(r io.Reader) (*Payload, error) { amp = nil } + // If no metadata field was parsed, set the metadata field on the + // resulting payload to nil. + if _, ok := parsedTypes[record.MetadataOnionType]; !ok { + metadata = nil + } + // Filter out the custom records. customRecords := NewCustomRecords(parsedTypes) @@ -180,6 +192,7 @@ func NewPayloadFromReader(r io.Reader) (*Payload, error) { }, MPP: mpp, AMP: amp, + metadata: metadata, customRecords: customRecords, }, nil } @@ -284,6 +297,12 @@ func (h *Payload) CustomRecords() record.CustomSet { return h.customRecords } +// Metadata returns the additional data that is sent along with the +// payment to the payee. +func (h *Payload) Metadata() []byte { + return h.metadata +} + // getMinRequiredViolation checks for unrecognized required (even) fields in the // standard range and returns the lowest required type. Always returning the // lowest required type allows a failure message to be deterministic. diff --git a/htlcswitch/hop/payload_test.go b/htlcswitch/hop/payload_test.go index c7abc9fad1..130b363dd6 100644 --- a/htlcswitch/hop/payload_test.go +++ b/htlcswitch/hop/payload_test.go @@ -11,15 +11,16 @@ import ( "github.com/stretchr/testify/require" ) -const testUnknownRequiredType = 0x10 +const testUnknownRequiredType = 0x80 type decodePayloadTest struct { - name string - payload []byte - expErr error - expCustomRecords map[uint64][]byte - shouldHaveMPP bool - shouldHaveAMP bool + name string + payload []byte + expErr error + expCustomRecords map[uint64][]byte + shouldHaveMPP bool + shouldHaveAMP bool + shouldHaveMetadata bool } var decodePayloadTests = []decodePayloadTest{ @@ -258,6 +259,18 @@ var decodePayloadTests = []decodePayloadTest{ }, shouldHaveAMP: true, }, + { + name: "final hop with metadata", + payload: []byte{ + // amount + 0x02, 0x00, + // cltv + 0x04, 0x00, + // metadata + 0x10, 0x03, 0x01, 0x02, 0x03, + }, + shouldHaveMetadata: true, + }, } // TestDecodeHopPayloadRecordValidation asserts that parsing the payloads in the @@ -293,6 +306,7 @@ func testDecodeHopPayloadValidation(t *testing.T, test decodePayloadTest) { 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, } + testMetadata = []byte{1, 2, 3} testChildIndex = uint32(9) ) @@ -331,6 +345,15 @@ func testDecodeHopPayloadValidation(t *testing.T, test decodePayloadTest) { t.Fatalf("unexpected AMP payload") } + if test.shouldHaveMetadata { + if p.Metadata() == nil { + t.Fatalf("payload should have metadata") + } + require.Equal(t, testMetadata, p.Metadata()) + } else if p.Metadata() != nil { + t.Fatalf("unexpected metadata") + } + // Convert expected nil map to empty map, because we always expect an // initiated map from the payload. expCustomRecords := make(record.CustomSet) diff --git a/htlcswitch/htlcnotifier.go b/htlcswitch/htlcnotifier.go index fd067e2b48..00bba71132 100644 --- a/htlcswitch/htlcnotifier.go +++ b/htlcswitch/htlcnotifier.go @@ -81,7 +81,7 @@ func NewHtlcNotifier(now func() time.Time) *HtlcNotifier { func (h *HtlcNotifier) Start() error { var err error h.started.Do(func() { - log.Trace("HtlcNotifier starting") + log.Info("HtlcNotifier starting") err = h.ntfnServer.Start() }) return err diff --git a/htlcswitch/interceptable_switch.go b/htlcswitch/interceptable_switch.go index 1e3405345d..be5ba80fd5 100644 --- a/htlcswitch/interceptable_switch.go +++ b/htlcswitch/interceptable_switch.go @@ -1,6 +1,7 @@ package htlcswitch import ( + "crypto/sha256" "fmt" "sync" @@ -15,6 +16,10 @@ var ( // ErrFwdNotExists is an error returned when the caller tries to resolve // a forward that doesn't exist anymore. ErrFwdNotExists = errors.New("forward does not exist") + + // ErrUnsupportedFailureCode when processing of an unsupported failure + // code is attempted. + ErrUnsupportedFailureCode = errors.New("unsupported failure code") ) // InterceptableSwitch is an implementation of ForwardingSwitch interface. @@ -25,90 +30,436 @@ var ( // Settle - routes UpdateFulfillHTLC to the originating link. // Fail - routes UpdateFailHTLC to the originating link. type InterceptableSwitch struct { - sync.RWMutex - // htlcSwitch is the underline switch htlcSwitch *Switch - // fwdInterceptor is the callback that is called for each forward of - // an incoming htlc. It should return true if it is interested in handling - // it. - fwdInterceptor ForwardInterceptor + // intercepted is where we stream all intercepted packets coming from + // the switch. + intercepted chan *interceptedPackets + + // resolutionChan is where we stream all responses coming from the + // interceptor client. + resolutionChan chan *fwdResolution + + onchainIntercepted chan InterceptedForward + + // interceptorRegistration is a channel that we use to synchronize + // client connect and disconnect. + interceptorRegistration chan ForwardInterceptor + + // requireInterceptor indicates whether processing should block if no + // interceptor is connected. + requireInterceptor bool + + // interceptor is the handler for intercepted packets. + interceptor ForwardInterceptor + + // holdForwards keeps track of outstanding intercepted forwards. + holdForwards map[channeldb.CircuitKey]InterceptedForward + + // cltvRejectDelta defines the number of blocks before the expiry of the + // htlc where we no longer intercept it and instead cancel it back. + cltvRejectDelta uint32 + + wg sync.WaitGroup + quit chan struct{} +} + +type interceptedPackets struct { + packets []*htlcPacket + linkQuit chan struct{} + isReplay bool +} + +// FwdAction defines the various resolution types. +type FwdAction int + +const ( + // FwdActionResume forwards the intercepted packet to the switch. + FwdActionResume FwdAction = iota + + // FwdActionSettle settles the intercepted packet with a preimage. + FwdActionSettle + + // FwdActionFail fails the intercepted packet back to the sender. + FwdActionFail +) + +// FwdResolution defines the action to be taken on an intercepted packet. +type FwdResolution struct { + // Key is the incoming circuit key of the htlc. + Key channeldb.CircuitKey + + // Action is the action to take on the intercepted htlc. + Action FwdAction + + // Preimage is the preimage that is to be used for settling if Action is + // FwdActionSettle. + Preimage lntypes.Preimage + + // FailureMessage is the encrypted failure message that is to be passed + // back to the sender if action is FwdActionFail. + FailureMessage []byte + + // FailureCode is the failure code that is to be passed back to the + // sender if action is FwdActionFail. + FailureCode lnwire.FailCode +} + +type fwdResolution struct { + resolution *FwdResolution + errChan chan error } // NewInterceptableSwitch returns an instance of InterceptableSwitch. -func NewInterceptableSwitch(s *Switch) *InterceptableSwitch { - return &InterceptableSwitch{htlcSwitch: s} +func NewInterceptableSwitch(s *Switch, cltvRejectDelta uint32, + requireInterceptor bool) *InterceptableSwitch { + + return &InterceptableSwitch{ + htlcSwitch: s, + intercepted: make(chan *interceptedPackets), + onchainIntercepted: make(chan InterceptedForward), + interceptorRegistration: make(chan ForwardInterceptor), + holdForwards: make(map[channeldb.CircuitKey]InterceptedForward), + resolutionChan: make(chan *fwdResolution), + requireInterceptor: requireInterceptor, + cltvRejectDelta: cltvRejectDelta, + + quit: make(chan struct{}), + } } -// SetInterceptor sets the ForwardInterceptor to be used. +// SetInterceptor sets the ForwardInterceptor to be used. A nil argument +// unregisters the current interceptor. func (s *InterceptableSwitch) SetInterceptor( interceptor ForwardInterceptor) { - s.Lock() - defer s.Unlock() - s.fwdInterceptor = interceptor + // Synchronize setting the handler with the main loop to prevent race + // conditions. + select { + case s.interceptorRegistration <- interceptor: + + case <-s.quit: + } +} + +func (s *InterceptableSwitch) Start() error { + s.wg.Add(1) + go func() { + defer s.wg.Done() + + s.run() + }() + + return nil +} + +func (s *InterceptableSwitch) Stop() error { + close(s.quit) + s.wg.Wait() + + return nil +} + +func (s *InterceptableSwitch) run() { + for { + select { + // An interceptor registration or de-registration came in. + case interceptor := <-s.interceptorRegistration: + s.setInterceptor(interceptor) + + case packets := <-s.intercepted: + var notIntercepted []*htlcPacket + for _, p := range packets.packets { + if !s.interceptForward(p, packets.isReplay) { + notIntercepted = append( + notIntercepted, p, + ) + } + } + err := s.htlcSwitch.ForwardPackets( + packets.linkQuit, notIntercepted..., + ) + if err != nil { + log.Errorf("Cannot forward packets: %v", err) + } + + case fwd := <-s.onchainIntercepted: + // For on-chain interceptions, we don't know if it has + // already been offered before. This information is in + // the forwarding package which isn't easily accessible + // from contractcourt. It is likely though that it was + // already intercepted in the off-chain flow. And even + // if not, it is safe to signal replay so that we won't + // unexpectedly skip over this htlc. + s.forward(fwd, true) + + case res := <-s.resolutionChan: + res.errChan <- s.resolve(res.resolution) + + case <-s.quit: + return + } + } +} +func (s *InterceptableSwitch) sendForward(fwd InterceptedForward) { + err := s.interceptor(fwd.Packet()) + if err != nil { + // Only log the error. If we couldn't send the packet, we assume + // that the interceptor will reconnect so that we can retry. + log.Debugf("Interceptor cannot handle forward: %v", err) + } +} + +func (s *InterceptableSwitch) setInterceptor(interceptor ForwardInterceptor) { + s.interceptor = interceptor + + // Replay all currently held htlcs. When an interceptor is not required, + // there may be none because they've been cleared after the previous + // disconnect. + if interceptor != nil { + log.Debugf("Interceptor connected") + + for _, fwd := range s.holdForwards { + s.sendForward(fwd) + } + + return + } + + // The interceptor disconnects. If an interceptor is required, keep the + // held htlcs. + if s.requireInterceptor { + log.Infof("Interceptor disconnected, retaining held packets") + + return + } + + // Interceptor is not required. Release held forwards. + log.Infof("Interceptor disconnected, resolving held packets") + + for _, fwd := range s.holdForwards { + if err := fwd.Resume(); err != nil { + log.Errorf("Failed to resume hold forward %v", err) + } + } + s.holdForwards = make(map[channeldb.CircuitKey]InterceptedForward) +} + +func (s *InterceptableSwitch) resolve(res *FwdResolution) error { + intercepted, ok := s.holdForwards[res.Key] + if !ok { + return fmt.Errorf("fwd %v not found", res.Key) + } + delete(s.holdForwards, res.Key) + + switch res.Action { + case FwdActionResume: + return intercepted.Resume() + + case FwdActionSettle: + return intercepted.Settle(res.Preimage) + + case FwdActionFail: + if len(res.FailureMessage) > 0 { + return intercepted.Fail(res.FailureMessage) + } + + return intercepted.FailWithCode(res.FailureCode) + + default: + return fmt.Errorf("unrecognized action %v", res.Action) + } +} + +// Resolve resolves an intercepted packet. +func (s *InterceptableSwitch) Resolve(res *FwdResolution) error { + internalRes := &fwdResolution{ + resolution: res, + errChan: make(chan error, 1), + } + + select { + case s.resolutionChan <- internalRes: + + case <-s.quit: + return errors.New("switch shutting down") + } + + select { + case err := <-internalRes.errChan: + return err + + case <-s.quit: + return errors.New("switch shutting down") + } } -// ForwardPackets attempts to forward the batch of htlcs through the -// switch, any failed packets will be returned to the provided -// ChannelLink. The link's quit signal should be provided to allow +// ForwardPackets attempts to forward the batch of htlcs to a connected +// interceptor. If the interceptor signals the resume action, the htlcs are +// forwarded to the switch. The link's quit signal should be provided to allow // cancellation of forwarding during link shutdown. -func (s *InterceptableSwitch) ForwardPackets(linkQuit chan struct{}, +func (s *InterceptableSwitch) ForwardPackets(linkQuit chan struct{}, isReplay bool, packets ...*htlcPacket) error { - var interceptor ForwardInterceptor - s.Lock() - interceptor = s.fwdInterceptor - s.Unlock() + // Synchronize with the main event loop. This should be light in the + // case where there is no interceptor. + select { + case s.intercepted <- &interceptedPackets{ + packets: packets, + linkQuit: linkQuit, + isReplay: isReplay, + }: + + case <-linkQuit: + log.Debugf("Forward cancelled because link quit") - // Optimize for the case we don't have an interceptor. - if interceptor == nil { - return s.htlcSwitch.ForwardPackets(linkQuit, packets...) + case <-s.quit: + return errors.New("interceptable switch quit") } - var notIntercepted []*htlcPacket - for _, p := range packets { - if !s.interceptForward(p, interceptor, linkQuit) { - notIntercepted = append(notIntercepted, p) - } + return nil +} + +// ForwardPacket forwards a single htlc to the external interceptor. +func (s *InterceptableSwitch) ForwardPacket( + fwd InterceptedForward) error { + + select { + case s.onchainIntercepted <- fwd: + + case <-s.quit: + return errors.New("interceptable switch quit") } - return s.htlcSwitch.ForwardPackets(linkQuit, notIntercepted...) + + return nil } -// interceptForward checks if there is any external interceptor interested in -// this packet. Currently only htlc type of UpdateAddHTLC that are forwarded -// are being checked for interception. It can be extended in the future given -// the right use case. +// interceptForward forwards the packet to the external interceptor after +// checking the interception criteria. func (s *InterceptableSwitch) interceptForward(packet *htlcPacket, - interceptor ForwardInterceptor, linkQuit chan struct{}) bool { + isReplay bool) bool { switch htlc := packet.htlc.(type) { case *lnwire.UpdateAddHTLC: - // We are not interested in intercepting initated payments. + // We are not interested in intercepting initiated payments. if packet.incomingChanID == hop.Source { return false } intercepted := &interceptedForward{ - linkQuit: linkQuit, htlc: htlc, packet: packet, htlcSwitch: s.htlcSwitch, } - // If this htlc was intercepted, don't handle the forward. - return interceptor(intercepted) + // Handle forwards that are too close to expiry. + handled, err := s.handleExpired(intercepted) + if err != nil { + log.Errorf("Error handling intercepted htlc "+ + "that expires too soon: circuit=%v, "+ + "incoming_timeout=%v, err=%v", + packet.inKey(), packet.incomingTimeout, err) + + // Return false so that the packet is offered as normal + // to the switch. This isn't ideal because interception + // may be configured as always-on and is skipped now. + // Returning true isn't great either, because the htlc + // will remain stuck and potentially force-close the + // channel. But in the end, we should never get here, so + // the actual return value doesn't matter that much. + return false + } + if handled { + return true + } + + return s.forward(intercepted, isReplay) + default: return false } } +// forward records the intercepted htlc and forwards it to the interceptor. +func (s *InterceptableSwitch) forward( + fwd InterceptedForward, isReplay bool) bool { + + inKey := fwd.Packet().IncomingCircuit + + // Ignore already held htlcs. + if _, ok := s.holdForwards[inKey]; ok { + return true + } + + // If there is no interceptor currently registered, configuration and packet + // replay status determine how the packet is handled. + if s.interceptor == nil { + // Process normally if an interceptor is not required. + if !s.requireInterceptor { + return false + } + + // We are in interceptor-required mode. If this is a new packet, it is + // still safe to fail back. The interceptor has never seen this packet + // yet. This limits the backlog of htlcs when the interceptor is down. + if !isReplay { + err := fwd.FailWithCode( + lnwire.CodeTemporaryChannelFailure, + ) + if err != nil { + log.Errorf("Cannot fail packet: %v", err) + } + + return true + } + + // This packet is a replay. It is not safe to fail back, because the + // interceptor may still signal otherwise upon reconnect. Keep the + // packet in the queue until then. + s.holdForwards[inKey] = fwd + + return true + } + + // There is an interceptor registered. We can forward the packet right now. + // Hold it in the queue too to track what is outstanding. + s.holdForwards[inKey] = fwd + s.sendForward(fwd) + + return true +} + +// handleExpired checks that the htlc isn't too close to the channel +// force-close broadcast height. If it is, it is cancelled back. +func (s *InterceptableSwitch) handleExpired(fwd *interceptedForward) ( + bool, error) { + + height := s.htlcSwitch.BestHeight() + if fwd.packet.incomingTimeout >= height+s.cltvRejectDelta { + return false, nil + } + + log.Debugf("Interception rejected because htlc "+ + "expires too soon: circuit=%v, "+ + "height=%v, incoming_timeout=%v", + fwd.packet.inKey(), height, + fwd.packet.incomingTimeout) + + err := fwd.FailWithCode( + lnwire.CodeExpiryTooSoon, + ) + if err != nil { + return false, err + } + + return true, nil +} + // interceptedForward implements the InterceptedForward interface. // It is passed from the switch to external interceptors that are interested // in holding forwards and resolve them manually. type interceptedForward struct { - linkQuit chan struct{} htlc *lnwire.UpdateAddHTLC packet *htlcPacket htlcSwitch *Switch @@ -134,24 +485,78 @@ func (f *interceptedForward) Packet() InterceptedPacket { // Resume resumes the default behavior as if the packet was not intercepted. func (f *interceptedForward) Resume() error { - return f.htlcSwitch.ForwardPackets(f.linkQuit, f.packet) + // Forward to the switch. A link quit channel isn't needed, because we + // are on a different thread now. + return f.htlcSwitch.ForwardPackets(nil, f.packet) } -// Fail forward a failed packet to the switch. -func (f *interceptedForward) Fail() error { - update, err := f.htlcSwitch.cfg.FetchLastChannelUpdate( - f.packet.incomingChanID, - ) - if err != nil { - return err +// Fail notifies the intention to Fail an existing hold forward with an +// encrypted failure reason. +func (f *interceptedForward) Fail(reason []byte) error { + obfuscatedReason := f.packet.obfuscator.IntermediateEncrypt(reason) + + return f.resolve(&lnwire.UpdateFailHTLC{ + Reason: obfuscatedReason, + }) +} + +// FailWithCode notifies the intention to fail an existing hold forward with the +// specified failure code. +func (f *interceptedForward) FailWithCode(code lnwire.FailCode) error { + shaOnionBlob := func() [32]byte { + return sha256.Sum256(f.htlc.OnionBlob[:]) } - reason, err := f.packet.obfuscator.EncryptFirstHop( - lnwire.NewTemporaryChannelFailure(update), - ) + // Create a local failure. + var failureMsg lnwire.FailureMessage + + switch code { + case lnwire.CodeInvalidOnionVersion: + failureMsg = &lnwire.FailInvalidOnionVersion{ + OnionSHA256: shaOnionBlob(), + } + + case lnwire.CodeInvalidOnionHmac: + failureMsg = &lnwire.FailInvalidOnionHmac{ + OnionSHA256: shaOnionBlob(), + } + + case lnwire.CodeInvalidOnionKey: + failureMsg = &lnwire.FailInvalidOnionKey{ + OnionSHA256: shaOnionBlob(), + } + + case lnwire.CodeTemporaryChannelFailure: + update, err := f.htlcSwitch.cfg.FetchLastChannelUpdate( + f.packet.incomingChanID, + ) + if err != nil { + return err + } + + failureMsg = lnwire.NewTemporaryChannelFailure(update) + + case lnwire.CodeExpiryTooSoon: + update, err := f.htlcSwitch.cfg.FetchLastChannelUpdate( + f.packet.incomingChanID, + ) + if err != nil { + return err + } + + failureMsg = lnwire.NewExpiryTooSoon(*update) + + default: + return ErrUnsupportedFailureCode + } + + // Encrypt the failure for the first hop. This node will be the origin + // of the failure. + reason, err := f.packet.obfuscator.EncryptFirstHop(failureMsg) if err != nil { return fmt.Errorf("failed to encrypt failure reason %v", err) } + return f.resolve(&lnwire.UpdateFailHTLC{ Reason: reason, }) diff --git a/htlcswitch/interfaces.go b/htlcswitch/interfaces.go index dc3a91ef9d..1b80adab3e 100644 --- a/htlcswitch/interfaces.go +++ b/htlcswitch/interfaces.go @@ -234,6 +234,9 @@ type TowerClient interface { type InterceptableHtlcForwarder interface { // SetInterceptor sets a ForwardInterceptor. SetInterceptor(interceptor ForwardInterceptor) + + // Resolve resolves an intercepted packet. + Resolve(res *FwdResolution) error } // ForwardInterceptor is a function that is invoked from the switch for every @@ -242,7 +245,7 @@ type InterceptableHtlcForwarder interface { // to resolve it manually later in case it is held. // The return value indicates if this handler will take control of this forward // and resolve it later or let the switch execute its default behavior. -type ForwardInterceptor func(InterceptedForward) bool +type ForwardInterceptor func(InterceptedPacket) error // InterceptedPacket contains the relevant information for the interceptor about // an htlc. @@ -297,8 +300,13 @@ type InterceptedForward interface { // forward with a given preimage. Settle(lntypes.Preimage) error - // Fails notifies the intention to fail an existing hold forward - Fail() error + // Fail notifies the intention to fail an existing hold forward with an + // encrypted failure reason. + Fail(reason []byte) error + + // FailWithCode notifies the intention to fail an existing hold forward + // with the specified failure code. + FailWithCode(code lnwire.FailCode) error } // htlcNotifier is an interface which represents the input side of the diff --git a/htlcswitch/link.go b/htlcswitch/link.go index f347a7f26f..6c553e31c8 100644 --- a/htlcswitch/link.go +++ b/htlcswitch/link.go @@ -9,9 +9,9 @@ import ( "sync/atomic" "time" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btclog" - "github.com/btcsuite/btcutil" "github.com/davecgh/go-spew/spew" "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/build" @@ -141,7 +141,7 @@ type ChannelLinkConfig struct { // switch. The function returns and error in case it fails to send one or // more packets. The link's quit signal should be provided to allow // cancellation of forwarding during link shutdown. - ForwardPackets func(chan struct{}, ...*htlcPacket) error + ForwardPackets func(chan struct{}, bool, ...*htlcPacket) error // DecodeHopIterators facilitates batched decoding of HTLC Sphinx onion // blobs, which are then used to inform how to forward an HTLC. @@ -187,11 +187,14 @@ type ChannelLinkConfig struct { LinkFailureError) // UpdateContractSignals is a function closure that we'll use to update - // outside sub-systems with the latest signals for our inner Lightning - // channel. These signals will notify the caller when the channel has - // been closed, or when the set of active HTLC's is updated. + // outside sub-systems with this channel's latest ShortChannelID. UpdateContractSignals func(*contractcourt.ContractSignals) error + // NotifyContractUpdate is a function closure that we'll use to update + // the contractcourt and more specifically the ChannelArbitrator of the + // latest channel state. + NotifyContractUpdate func(*contractcourt.ContractUpdate) error + // ChainEvents is an active subscription to the chain watcher for this // channel to be notified of any on-chain activity related to this // channel. @@ -372,10 +375,6 @@ type channelLink struct { // sent across. localUpdateAdd chan *localUpdateAddMsg - // htlcUpdates is a channel that we'll use to update outside - // sub-systems with the latest set of active HTLC's on our channel. - htlcUpdates chan *contractcourt.ContractUpdate - // shutdownRequest is a channel that the channelLink will listen on to // service shutdown requests from ShutdownIfChannelClean calls. shutdownRequest chan *shutdownReq @@ -421,11 +420,9 @@ func NewChannelLink(cfg ChannelLinkConfig, logPrefix := fmt.Sprintf("ChannelLink(%v):", channel.ChannelPoint()) return &channelLink{ - cfg: cfg, - channel: channel, - shortChanID: channel.ShortChanID(), - // TODO(roasbeef): just do reserve here? - htlcUpdates: make(chan *contractcourt.ContractUpdate), + cfg: cfg, + channel: channel, + shortChanID: channel.ShortChanID(), shutdownRequest: make(chan *shutdownReq), hodlMap: make(map[channeldb.CircuitKey]hodlHtlc), hodlQueue: queue.NewConcurrentQueue(10), @@ -496,7 +493,6 @@ func (l *channelLink) Start() error { // TODO(roasbeef): split goroutines within channel arb to avoid go func() { signals := &contractcourt.ContractSignals{ - HtlcUpdates: l.htlcUpdates, ShortChanID: l.channel.ShortChanID(), } @@ -923,9 +919,9 @@ func (l *channelLink) loadAndRemove() error { // This goroutine reads messages from the upstream (remote) peer, and also from // downstream channel managed by the channel link. In the event that an htlc // needs to be forwarded, then send-only forward handler is used which sends -// htlc packets to the switch. Additionally, the this goroutine handles acting -// upon all timeouts for any active HTLCs, manages the channel's revocation -// window, and also the htlc trickle queue+timer for this active channels. +// htlc packets to the switch. Additionally, this goroutine handles acting upon +// all timeouts for any active HTLCs, manages the channel's revocation window, +// and also the htlc trickle queue+timer for this active channels. // // NOTE: This MUST be run as a goroutine. func (l *channelLink) htlcManager() { @@ -1088,8 +1084,13 @@ func (l *channelLink) htlcManager() { // batch is empty. if l.channel.PendingLocalUpdateCount() > 0 { l.cfg.BatchTicker.Resume() + l.log.Tracef("BatchTicker resumed, "+ + "PendingLocalUpdateCount=%d", + l.channel.PendingLocalUpdateCount()) } else { l.cfg.BatchTicker.Pause() + l.log.Trace("BatchTicker paused due to zero " + + "PendingLocalUpdateCount") } select { @@ -1130,6 +1131,7 @@ func (l *channelLink) htlcManager() { if !shouldAdjustCommitFee( newCommitFee, commitFee, minRelayFee, ) { + continue } @@ -1675,6 +1677,28 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) { case *lnwire.UpdateFulfillHTLC: pre := msg.PaymentPreimage idx := msg.ID + + // Before we pipeline the settle, we'll check the set of active + // htlc's to see if the related UpdateAddHTLC has been fully + // locked-in. + var lockedin bool + htlcs := l.channel.ActiveHtlcs() + for _, add := range htlcs { + // The HTLC will be outgoing and match idx. + if !add.Incoming && add.HtlcIndex == idx { + lockedin = true + break + } + } + + if !lockedin { + l.fail( + LinkFailureError{code: ErrInvalidUpdate}, + "unable to handle upstream settle", + ) + return + } + if err := l.channel.ReceiveHTLCSettle(pre, idx); err != nil { l.fail( LinkFailureError{ @@ -1701,7 +1725,7 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) { l.uncommittedPreimages = append(l.uncommittedPreimages, pre) // Pipeline this settle, send it to the switch. - go l.forwardBatch(settlePacket) + go l.forwardBatch(false, settlePacket) case *lnwire.UpdateFailMalformedHTLC: // Convert the failure type encoded within the HTLC fail @@ -1836,21 +1860,29 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) { l.cfg.Peer.SendMessage(false, nextRevocation) // Since we just revoked our commitment, we may have a new set - // of HTLC's on our commitment, so we'll send them over our - // HTLC update channel so any callers can be notified. - select { - case l.htlcUpdates <- &contractcourt.ContractUpdate{ + // of HTLC's on our commitment, so we'll send them using our + // function closure NotifyContractUpdate. + newUpdate := &contractcourt.ContractUpdate{ HtlcKey: contractcourt.LocalHtlcSet, Htlcs: currentHtlcs, - }: + } + err = l.cfg.NotifyContractUpdate(newUpdate) + if err != nil { + l.log.Errorf("unable to notify contract update: %v", + err) + return + } + + select { case <-l.quit: return + default: } // If both commitment chains are fully synced from our PoV, // then we don't need to reply with a signature as both sides // already have a commitment with the latest accepted. - if !l.channel.OweCommitment(true) { + if !l.channel.OweCommitment() { return } @@ -1865,9 +1897,16 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) { // We've received a revocation from the remote chain, if valid, // this moves the remote chain forward, and expands our // revocation window. - fwdPkg, adds, settleFails, remoteHTLCs, err := l.channel.ReceiveRevocation( - msg, - ) + // + // Before advancing our remote chain, we will record the + // current commit tx, which is used by the TowerClient to + // create backups. + oldCommitTx := l.channel.State().RemoteCommitment.CommitTx + + // We now process the message and advance our remote commit + // chain. + fwdPkg, adds, settleFails, remoteHTLCs, err := l.channel. + ReceiveRevocation(msg) if err != nil { // TODO(halseth): force close? l.fail(LinkFailureError{code: ErrInvalidRevocation}, @@ -1878,20 +1917,31 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) { // The remote party now has a new primary commitment, so we'll // update the contract court to be aware of this new set (the // prior old remote pending). - select { - case l.htlcUpdates <- &contractcourt.ContractUpdate{ + newUpdate := &contractcourt.ContractUpdate{ HtlcKey: contractcourt.RemoteHtlcSet, Htlcs: remoteHTLCs, - }: + } + err = l.cfg.NotifyContractUpdate(newUpdate) + if err != nil { + l.log.Errorf("unable to notify contract update: %v", + err) + return + } + + select { case <-l.quit: return + default: } // If we have a tower client for this channel type, we'll + // create a backup for the current state. if l.cfg.TowerClient != nil { state := l.channel.State() breachInfo, err := lnwallet.NewBreachRetribution( state, state.RemoteCommitment.CommitHeight-1, 0, + // OldCommitTx is the breaching tx at height-1. + oldCommitTx, ) if err != nil { l.fail(LinkFailureError{code: ErrInternalError}, @@ -1927,7 +1977,7 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) { // processRemoteAdds. Also in case there are no local updates, // but there are still remote updates that are not in the remote // commit tx yet, send out an update. - if l.channel.OweCommitment(true) { + if l.channel.OweCommitment() { if !l.updateCommitTxOrFail() { return } @@ -2067,6 +2117,7 @@ func (l *channelLink) updateCommitTx() error { theirCommitSig, htlcSigs, pendingHTLCs, err := l.channel.SignNextCommitment() if err == lnwallet.ErrNoWindow { l.cfg.PendingCommitTicker.Resume() + l.log.Trace("PendingCommitTicker resumed") l.log.Tracef("revocation window exhausted, unable to send: "+ "%v, pend_updates=%v, dangling_closes%v", @@ -2088,17 +2139,25 @@ func (l *channelLink) updateCommitTx() error { } l.cfg.PendingCommitTicker.Pause() + l.log.Trace("PendingCommitTicker paused after ackDownStreamPackets") // The remote party now has a new pending commitment, so we'll update // the contract court to be aware of this new set (the prior old remote // pending). - select { - case l.htlcUpdates <- &contractcourt.ContractUpdate{ + newUpdate := &contractcourt.ContractUpdate{ HtlcKey: contractcourt.RemotePendingHtlcSet, Htlcs: pendingHTLCs, - }: + } + err = l.cfg.NotifyContractUpdate(newUpdate) + if err != nil { + l.log.Errorf("unable to notify contract update: %v", err) + return err + } + + select { case <-l.quit: return ErrLinkShuttingDown + default: } commitSig := &lnwire.CommitSig{ @@ -2166,7 +2225,6 @@ func (l *channelLink) UpdateShortChanID() (lnwire.ShortChannelID, error) { go func() { err := l.cfg.UpdateContractSignals(&contractcourt.ContractSignals{ - HtlcUpdates: l.htlcUpdates, ShortChanID: sid, }) if err != nil { @@ -2571,7 +2629,6 @@ func (l *channelLink) ShutdownIfChannelClean() error { // updateChannelFee updates the commitment fee-per-kw on this channel by // committing to an update_fee message. func (l *channelLink) updateChannelFee(feePerKw chainfee.SatPerKWeight) error { - l.log.Infof("updating commit fee to %v sat/kw", feePerKw) // We skip sending the UpdateFee message if the channel is not @@ -2704,7 +2761,7 @@ func (l *channelLink) processRemoteSettleFails(fwdPkg *channeldb.FwdPkg, // Only spawn the task forward packets we have a non-zero number. if len(switchPackets) > 0 { - go l.forwardBatch(switchPackets...) + go l.forwardBatch(false, switchPackets...) } } @@ -3003,14 +3060,17 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg, return } - l.log.Debugf("forwarding %d packets to switch", len(switchPackets)) + replay := fwdPkg.State != channeldb.FwdStateLockedIn + + l.log.Debugf("forwarding %d packets to switch: replay=%v", + len(switchPackets), replay) // NOTE: This call is made synchronous so that we ensure all circuits // are committed in the exact order that they are processed in the link. // Failing to do this could cause reorderings/gaps in the range of // opened circuits, which violates assumptions made by the circuit // trimming. - l.forwardBatch(switchPackets...) + l.forwardBatch(replay, switchPackets...) } // processExitHop handles an htlc for which this link is the exit hop. It @@ -3144,7 +3204,7 @@ func (l *channelLink) settleHTLC(preimage lntypes.Preimage, // forwardBatch forwards the given htlcPackets to the switch, and waits on the // err chan for the individual responses. This method is intended to be spawned // as a goroutine so the responses can be handled in the background. -func (l *channelLink) forwardBatch(packets ...*htlcPacket) { +func (l *channelLink) forwardBatch(replay bool, packets ...*htlcPacket) { // Don't forward packets for which we already have a response in our // mailbox. This could happen if a packet fails and is buffered in the // mailbox, and the incoming link flaps. @@ -3157,7 +3217,8 @@ func (l *channelLink) forwardBatch(packets ...*htlcPacket) { filteredPkts = append(filteredPkts, pkt) } - if err := l.cfg.ForwardPackets(l.quit, filteredPkts...); err != nil { + err := l.cfg.ForwardPackets(l.quit, replay, filteredPkts...) + if err != nil { log.Errorf("Unhandled error while reforwarding htlc "+ "settle/fail over htlcswitch: %v", err) } diff --git a/htlcswitch/link_test.go b/htlcswitch/link_test.go index c880e6683b..5320336ec0 100644 --- a/htlcswitch/link_test.go +++ b/htlcswitch/link_test.go @@ -14,10 +14,10 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/davecgh/go-spew/spew" "github.com/go-errors/errors" sphinx "github.com/lightningnetwork/lightning-onion" @@ -70,25 +70,6 @@ func (c *concurrentTester) Fatalf(format string, args ...interface{}) { // setting the 'Curve" parameter to nil. Doing this avoids printing out each of // the field elements in the curve parameters for secp256k1. func messageToString(msg lnwire.Message) string { - switch m := msg.(type) { - case *lnwire.RevokeAndAck: - m.NextRevocationKey.Curve = nil - case *lnwire.AcceptChannel: - m.FundingKey.Curve = nil - m.RevocationPoint.Curve = nil - m.PaymentPoint.Curve = nil - m.DelayedPaymentPoint.Curve = nil - m.FirstCommitmentPoint.Curve = nil - case *lnwire.OpenChannel: - m.FundingKey.Curve = nil - m.RevocationPoint.Curve = nil - m.PaymentPoint.Curve = nil - m.DelayedPaymentPoint.Curve = nil - m.FirstCommitmentPoint.Curve = nil - case *lnwire.FundingLocked: - m.NextPerCommitmentPoint.Curve = nil - } - return spew.Sdump(msg) } @@ -1944,16 +1925,29 @@ func newSingleLinkTestHarness(chanAmt, chanReserve btcutil.Amount) ( return nil, nil, nil, nil, nil, nil, err } + notifyUpdateChan := make(chan *contractcourt.ContractUpdate) + doneChan := make(chan struct{}) + notifyContractUpdate := func(u *contractcourt.ContractUpdate) error { + select { + case notifyUpdateChan <- u: + case <-doneChan: + } + + return nil + } + // Instantiate with a long interval, so that we can precisely control // the firing via force feeding. bticker := ticker.NewForce(time.Hour) aliceCfg := ChannelLinkConfig{ - FwrdingPolicy: globalPolicy, - Peer: alicePeer, - Switch: aliceSwitch, - BestHeight: aliceSwitch.BestHeight, - Circuits: aliceSwitch.CircuitModifier(), - ForwardPackets: aliceSwitch.ForwardPackets, + FwrdingPolicy: globalPolicy, + Peer: alicePeer, + Switch: aliceSwitch, + BestHeight: aliceSwitch.BestHeight, + Circuits: aliceSwitch.CircuitModifier(), + ForwardPackets: func(linkQuit chan struct{}, _ bool, packets ...*htlcPacket) error { + return aliceSwitch.ForwardPackets(linkQuit, packets...) + }, DecodeHopIterators: decoder.DecodeHopIterators, ExtractErrorEncrypter: func(*btcec.PublicKey) ( hop.ErrorEncrypter, lnwire.FailCode) { @@ -1967,12 +1961,13 @@ func newSingleLinkTestHarness(chanAmt, chanReserve btcutil.Amount) ( UpdateContractSignals: func(*contractcourt.ContractSignals) error { return nil }, - Registry: invoiceRegistry, - FeeEstimator: newMockFeeEstimator(), - ChainEvents: &contractcourt.ChainEventSubscription{}, - BatchTicker: bticker, - FwdPkgGCTicker: ticker.NewForce(15 * time.Second), - PendingCommitTicker: ticker.New(time.Minute), + NotifyContractUpdate: notifyContractUpdate, + Registry: invoiceRegistry, + FeeEstimator: newMockFeeEstimator(), + ChainEvents: &contractcourt.ChainEventSubscription{}, + BatchTicker: bticker, + FwdPkgGCTicker: ticker.NewForce(15 * time.Second), + PendingCommitTicker: ticker.New(time.Minute), // Make the BatchSize and Min/MaxFeeUpdateTimeout large enough // to not trigger commit updates automatically during tests. BatchSize: 10000, @@ -1993,8 +1988,9 @@ func newSingleLinkTestHarness(chanAmt, chanReserve btcutil.Amount) ( go func() { for { select { - case <-aliceLink.(*channelLink).htlcUpdates: + case <-notifyUpdateChan: case <-aliceLink.(*channelLink).quit: + close(doneChan) return } } @@ -2170,7 +2166,7 @@ func updateState(batchTick chan time.Time, link *channelLink, // sleep in this test and the one below func TestChannelLinkBandwidthConsistency(t *testing.T) { if !build.IsDevBuild() { - t.Fatalf("htlcswitch tests must be run with '-tags debug") + t.Fatalf("htlcswitch tests must be run with '-tags dev") } t.Parallel() @@ -2904,7 +2900,7 @@ func TestChannelLinkTrimCircuitsPending(t *testing.T) { // circuits if the ADDs corresponding to open circuits are never committed. func TestChannelLinkTrimCircuitsNoCommit(t *testing.T) { if !build.IsDevBuild() { - t.Fatalf("htlcswitch tests must be run with '-tags debug") + t.Fatalf("htlcswitch tests must be run with '-tags dev") } t.Parallel() @@ -3507,7 +3503,7 @@ func TestChannelRetransmission(t *testing.T) { {"alice", "bob", &lnwire.RevokeAndAck{}, false}, // Proceed the payment farther by sending the - // fulfilment message and trigger the state + // fulfillment message and trigger the state // update. {"bob", "alice", &lnwire.UpdateFulfillHTLC{}, false}, {"bob", "alice", &lnwire.CommitSig{}, false}, @@ -3548,7 +3544,7 @@ func TestChannelRetransmission(t *testing.T) { {"bob", "alice", &lnwire.CommitSig{}, false}, // Proceed the payment farther by sending the - // fulfilment message and trigger the state + // fulfillment message and trigger the state // update. {"alice", "bob", &lnwire.RevokeAndAck{}, false}, {"bob", "alice", &lnwire.UpdateFulfillHTLC{}, false}, @@ -4482,16 +4478,29 @@ func (h *persistentLinkHarness) restartLink( } } + notifyUpdateChan := make(chan *contractcourt.ContractUpdate) + doneChan := make(chan struct{}) + notifyContractUpdate := func(u *contractcourt.ContractUpdate) error { + select { + case notifyUpdateChan <- u: + case <-doneChan: + } + + return nil + } + // Instantiate with a long interval, so that we can precisely control // the firing via force feeding. bticker := ticker.NewForce(time.Hour) aliceCfg := ChannelLinkConfig{ - FwrdingPolicy: globalPolicy, - Peer: alicePeer, - Switch: aliceSwitch, - BestHeight: aliceSwitch.BestHeight, - Circuits: aliceSwitch.CircuitModifier(), - ForwardPackets: aliceSwitch.ForwardPackets, + FwrdingPolicy: globalPolicy, + Peer: alicePeer, + Switch: aliceSwitch, + BestHeight: aliceSwitch.BestHeight, + Circuits: aliceSwitch.CircuitModifier(), + ForwardPackets: func(linkQuit chan struct{}, _ bool, packets ...*htlcPacket) error { + return aliceSwitch.ForwardPackets(linkQuit, packets...) + }, DecodeHopIterators: decoder.DecodeHopIterators, ExtractErrorEncrypter: func(*btcec.PublicKey) ( hop.ErrorEncrypter, lnwire.FailCode) { @@ -4505,12 +4514,13 @@ func (h *persistentLinkHarness) restartLink( UpdateContractSignals: func(*contractcourt.ContractSignals) error { return nil }, - Registry: h.coreLink.cfg.Registry, - FeeEstimator: newMockFeeEstimator(), - ChainEvents: &contractcourt.ChainEventSubscription{}, - BatchTicker: bticker, - FwdPkgGCTicker: ticker.New(5 * time.Second), - PendingCommitTicker: ticker.New(time.Minute), + NotifyContractUpdate: notifyContractUpdate, + Registry: h.coreLink.cfg.Registry, + FeeEstimator: newMockFeeEstimator(), + ChainEvents: &contractcourt.ChainEventSubscription{}, + BatchTicker: bticker, + FwdPkgGCTicker: ticker.New(5 * time.Second), + PendingCommitTicker: ticker.New(time.Minute), // Make the BatchSize and Min/MaxFeeUpdateTimeout large enough // to not trigger commit updates automatically during tests. BatchSize: 10000, @@ -4534,8 +4544,9 @@ func (h *persistentLinkHarness) restartLink( go func() { for { select { - case <-aliceLink.(*channelLink).htlcUpdates: + case <-notifyUpdateChan: case <-aliceLink.(*channelLink).quit: + close(doneChan) return } } @@ -5474,8 +5485,8 @@ func TestChannelLinkFail(t *testing.T) { false, }, { - // Test that we force close the channel if we receive - // an invalid Settle message. + // Test that we don't force close the channel if we + // receive an invalid Settle message. func(c *channelLink) { }, func(t *testing.T, c *channelLink, _ *lnwallet.LightningChannel) { @@ -5487,7 +5498,7 @@ func TestChannelLinkFail(t *testing.T) { } c.HandleChannelUpdate(htlcSettle) }, - true, + false, false, }, { @@ -5752,7 +5763,6 @@ func TestForwardingAsymmetricTimeLockPolicies(t *testing.T) { // TestCheckHtlcForward tests that a link is properly enforcing the HTLC // forwarding policy. func TestCheckHtlcForward(t *testing.T) { - fetchLastChannelUpdate := func(lnwire.ShortChannelID) ( *lnwire.ChannelUpdate, error) { @@ -5980,7 +5990,7 @@ func newHodlInvoiceTestCtx(t *testing.T) (*hodlInvoiceTestCtx, error) { t.Fatal("timeout") case h := <-receiver.registry.settleChan: if hash != h { - t.Fatal("unexpect invoice settled") + t.Fatal("unexpected invoice settled") } } @@ -6659,6 +6669,179 @@ func TestShutdownIfChannelClean(t *testing.T) { } } +// TestPipelineSettle tests that a link should only pipeline a settle if the +// related add is fully locked-in meaning it is on both sides' commitment txns. +func TestPipelineSettle(t *testing.T) { + t.Parallel() + + const chanAmt = btcutil.SatoshiPerBitcoin * 5 + const chanReserve = btcutil.SatoshiPerBitcoin * 1 + aliceLink, bobChannel, _, start, cleanUp, restore, err := + newSingleLinkTestHarness(chanAmt, chanReserve) + require.NoError(t, err) + defer cleanUp() + + alice := newPersistentLinkHarness( + t, aliceLink, nil, restore, + ) + + linkErrors := make(chan LinkFailureError, 1) + + // Modify OnChannelFailure so we are notified when the link is failed. + alice.coreLink.cfg.OnChannelFailure = func(_ lnwire.ChannelID, + _ lnwire.ShortChannelID, linkErr LinkFailureError) { + + linkErrors <- linkErr + } + + // Modify ForwardPackets so we are notified if a settle packet is + // erroneously forwarded. If the forwardChan is closed before the last + // step, then the test will fail. + forwardChan := make(chan struct{}) + fwdPkts := func(c chan struct{}, _ bool, hp ...*htlcPacket) error { + close(forwardChan) + return nil + } + alice.coreLink.cfg.ForwardPackets = fwdPkts + + // Put Alice in ExitSettle mode, so we can simulate a multi-hop route + // without actually doing so. This allows us to test the locked-in add + // logic without having the add being removed by Alice sending a + // settle. + alice.coreLink.cfg.HodlMask = hodl.Mask(hodl.ExitSettle) + + err = start() + require.NoError(t, err) + + ctx := linkTestContext{ + t: t, + aliceLink: alice.link, + bobChannel: bobChannel, + aliceMsgs: alice.msgs, + } + + // First lock in an HTLC from Bob to Alice. + htlc1, invoice1 := generateHtlcAndInvoice(t, 0) + preimage1 := invoice1.Terms.PaymentPreimage + + // Add the invoice to Alice's registry so she expects it. + aliceReg := alice.coreLink.cfg.Registry.(*mockInvoiceRegistry) + err = aliceReg.AddInvoice(*invoice1, htlc1.PaymentHash) + require.NoError(t, err) + + // <---add----- + ctx.sendHtlcBobToAlice(htlc1) + // <---sig----- + ctx.sendCommitSigBobToAlice(1) + // ----rev----> + ctx.receiveRevAndAckAliceToBob() + // ----sig----> + ctx.receiveCommitSigAliceToBob(1) + // <---rev----- + ctx.sendRevAndAckBobToAlice() + + // Bob will send the preimage for the HTLC he just sent. This will test + // the check that the HTLC is locked-in. The channel should not be + // force closed if everything is working correctly. + settle1 := &lnwire.UpdateFulfillHTLC{ + ID: 0, + PaymentPreimage: *preimage1, + } + ctx.aliceLink.HandleChannelUpdate(settle1) + + // ForceClose should be false. + select { + case linkErr := <-linkErrors: + require.False(t, linkErr.ForceClose) + case <-forwardChan: + t.Fatal("packet was erroneously forwarded") + } + + // Restart Alice's link with the hodl.ExitSettle and hodl.Commit flags. + alice.restart(false, false, hodl.ExitSettle, hodl.Commit) + ctx.aliceLink = alice.link + ctx.aliceMsgs = alice.msgs + + alice.coreLink.cfg.OnChannelFailure = func(_ lnwire.ChannelID, + _ lnwire.ShortChannelID, linkErr LinkFailureError) { + + linkErrors <- linkErr + } + alice.coreLink.cfg.ForwardPackets = fwdPkts + + // Alice will now send an HTLC to Bob, but won't sign a commitment for + // it. This HTLC will have the same payment hash as the one above. + htlc2 := htlc1 + + // ----add---> + ctx.sendHtlcAliceToBob(0, htlc2) + ctx.receiveHtlcAliceToBob() + + // Now Bob will send a settle backwards before the HTLC is locked in + // and the link should be failed again. + settle2 := &lnwire.UpdateFulfillHTLC{ + ID: 0, + PaymentPreimage: *preimage1, + } + ctx.aliceLink.HandleChannelUpdate(settle2) + + // ForceClose should be false. + select { + case linkErr := <-linkErrors: + require.False(t, linkErr.ForceClose) + case <-forwardChan: + t.Fatal("packet was erroneously forwarded") + } + + // Restart Alice's link without the hodl.Commit flag. + alice.restart(false, false, hodl.ExitSettle) + ctx.aliceLink = alice.link + ctx.aliceMsgs = alice.msgs + + alice.coreLink.cfg.OnChannelFailure = func(_ lnwire.ChannelID, + _ lnwire.ShortChannelID, linkErr LinkFailureError) { + + linkErrors <- linkErr + } + alice.coreLink.cfg.ForwardPackets = fwdPkts + + // Alice's mailbox should give the link the HTLC to send again. + select { + case msg := <-ctx.aliceMsgs: + _, ok := msg.(*lnwire.UpdateAddHTLC) + require.True(t, ok) + case <-time.After(5 * time.Second): + t.Fatal("did not receive htlc from alice") + } + + // Trigger the BatchTicker. + select { + case alice.batchTicker <- time.Now(): + case <-time.After(5 * time.Second): + t.Fatalf("could not force commit sig") + } + + // ----sig---> + ctx.receiveCommitSigAliceToBob(2) + // <---rev---- + ctx.sendRevAndAckBobToAlice() + // <---sig---- + ctx.sendCommitSigBobToAlice(2) + // ----rev---> + ctx.receiveRevAndAckAliceToBob() + + // Bob should now be able to send the settle to Alice without making + // the link fail. + ctx.aliceLink.HandleChannelUpdate(settle2) + + select { + case <-linkErrors: + t.Fatal("should not have received a link error") + case <-forwardChan: + // success + } +} + // assertFailureCode asserts that an error is of type ClearTextError and that // the failure code is as expected. func assertFailureCode(t *testing.T, err error, code lnwire.FailCode) { diff --git a/htlcswitch/mailbox.go b/htlcswitch/mailbox.go index 6bec19f398..a8e6ed31e8 100644 --- a/htlcswitch/mailbox.go +++ b/htlcswitch/mailbox.go @@ -25,7 +25,7 @@ var ( // MailBox is an interface which represents a concurrent-safe, in-order // delivery queue for messages from the network and also from the main switch. -// This struct servers as a buffer between incoming messages, and messages to +// This struct serves as a buffer between incoming messages, and messages to // the handled by the link. Each of the mutating methods within this interface // should be implemented in a non-blocking manner. type MailBox interface { @@ -91,7 +91,7 @@ type mailBoxConfig struct { // belongs to. shortChanID lnwire.ShortChannelID - // fetchUpdate retreives the most recent channel update for the channel + // fetchUpdate retrieves the most recent channel update for the channel // this mailbox belongs to. fetchUpdate func(lnwire.ShortChannelID) (*lnwire.ChannelUpdate, error) @@ -590,7 +590,6 @@ func (m *memoryMailBox) AddMessage(msg lnwire.Message) error { func (m *memoryMailBox) AddPacket(pkt *htlcPacket) error { m.pktCond.L.Lock() switch htlc := pkt.htlc.(type) { - // Split off Settle/Fail packets into the repPkts queue. case *lnwire.UpdateFulfillHTLC, *lnwire.UpdateFailHTLC: if _, ok := m.repIndex[pkt.inKey()]; ok { @@ -674,6 +673,7 @@ func (m *memoryMailBox) DustPackets() (lnwire.MilliSatoshi, if m.isDust( m.feeRate, false, true, addPkt.amount.ToSatoshis(), ) { + localDustSum += addPkt.amount } @@ -681,6 +681,7 @@ func (m *memoryMailBox) DustPackets() (lnwire.MilliSatoshi, if m.isDust( m.feeRate, false, false, addPkt.amount.ToSatoshis(), ) { + remoteDustSum += addPkt.amount } } @@ -803,7 +804,7 @@ type mailOrchestrator struct { // chan_id -> short_chan_id // short_chan_id -> mailbox // so that Deliver can lookup mailbox directly once live, - // but still queriable by channel_id. + // but still queryable by channel_id. // unclaimedPackets maps a live short chan id to queue of packets if no // mailbox has been created. @@ -816,7 +817,7 @@ type mailOrchConfig struct { // properly exit during shutdown. forwardPackets func(chan struct{}, ...*htlcPacket) error - // fetchUpdate retreives the most recent channel update for the channel + // fetchUpdate retrieves the most recent channel update for the channel // this mailbox belongs to. fetchUpdate func(lnwire.ShortChannelID) (*lnwire.ChannelUpdate, error) diff --git a/htlcswitch/mailbox_test.go b/htlcswitch/mailbox_test.go index 86c8faa6d4..a65eb9983e 100644 --- a/htlcswitch/mailbox_test.go +++ b/htlcswitch/mailbox_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/clock" @@ -208,6 +208,7 @@ func newMailboxContext(t *testing.T, startTime time.Time, ctx.mailbox = newMemoryMailBox(&mailBoxConfig{ fetchUpdate: func(sid lnwire.ShortChannelID) ( *lnwire.ChannelUpdate, error) { + return &lnwire.ChannelUpdate{ ShortChannelID: sid, }, nil @@ -671,12 +672,14 @@ func TestMailOrchestrator(t *testing.T) { mo := newMailOrchestrator(&mailOrchConfig{ fetchUpdate: func(sid lnwire.ShortChannelID) ( *lnwire.ChannelUpdate, error) { + return &lnwire.ChannelUpdate{ ShortChannelID: sid, }, nil }, forwardPackets: func(_ chan struct{}, pkts ...*htlcPacket) error { + return nil }, clock: clock.NewTestClock(time.Now()), diff --git a/htlcswitch/mock.go b/htlcswitch/mock.go index d9ca346bde..2ae5e41d81 100644 --- a/htlcswitch/mock.go +++ b/htlcswitch/mock.go @@ -14,9 +14,9 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/go-errors/errors" sphinx "github.com/lightningnetwork/lightning-onion" "github.com/lightningnetwork/lnd/chainntnfs" @@ -65,8 +65,12 @@ func (m *mockPreimageCache) AddPreimages(preimages ...lntypes.Preimage) error { return nil } -func (m *mockPreimageCache) SubscribeUpdates() *contractcourt.WitnessSubscription { - return nil +func (m *mockPreimageCache) SubscribeUpdates( + chanID lnwire.ShortChannelID, htlc *channeldb.HTLC, + payload *hop.Payload, + nextHopOnionBlob []byte) (*contractcourt.WitnessSubscription, error) { + + return nil, nil } type mockFeeEstimator struct { @@ -185,7 +189,7 @@ func initSwitchWithDB(startingHeight uint32, db *channeldb.DB) (*Switch, error) events: make(map[time.Time]channeldb.ForwardingEvent), }, FetchLastChannelUpdate: func(lnwire.ShortChannelID) (*lnwire.ChannelUpdate, error) { - return nil, nil + return &lnwire.ChannelUpdate{}, nil }, Notifier: &mock.ChainNotifier{ SpendChan: make(chan *chainntnfs.SpendDetail), @@ -610,7 +614,7 @@ func (s *mockServer) PubKey() [33]byte { } func (s *mockServer) IdentityKey() *btcec.PublicKey { - pubkey, _ := btcec.ParsePubKey(s.id[:], btcec.S256()) + pubkey, _ := btcec.ParsePubKey(s.id[:]) return pubkey } @@ -690,9 +694,11 @@ func (f *mockChannelLink) completeCircuit(pkt *htlcPacket) error { f.htlcID++ case *lnwire.UpdateFulfillHTLC, *lnwire.UpdateFailHTLC: - err := f.htlcSwitch.teardownCircuit(pkt) - if err != nil { - return err + if pkt.circuit != nil { + err := f.htlcSwitch.teardownCircuit(pkt) + if err != nil { + return err + } } } @@ -986,17 +992,18 @@ var _ htlcNotifier = (*mockHTLCNotifier)(nil) type mockHTLCNotifier struct{} func (h *mockHTLCNotifier) NotifyForwardingEvent(key HtlcKey, info HtlcInfo, - eventType HtlcEventType) { + eventType HtlcEventType) { // nolint:whitespace } func (h *mockHTLCNotifier) NotifyLinkFailEvent(key HtlcKey, info HtlcInfo, - eventType HtlcEventType, linkErr *LinkError, incoming bool) { + eventType HtlcEventType, linkErr *LinkError, + incoming bool) { // nolint:whitespace } func (h *mockHTLCNotifier) NotifyForwardingFailEvent(key HtlcKey, - eventType HtlcEventType) { + eventType HtlcEventType) { // nolint:whitespace } func (h *mockHTLCNotifier) NotifySettleEvent(key HtlcKey, - preimage lntypes.Preimage, eventType HtlcEventType) { + preimage lntypes.Preimage, eventType HtlcEventType) { // nolint:whitespace } diff --git a/htlcswitch/packet.go b/htlcswitch/packet.go index 78c3af8e23..f971ad90b1 100644 --- a/htlcswitch/packet.go +++ b/htlcswitch/packet.go @@ -89,7 +89,7 @@ type htlcPacket struct { incomingTimeout uint32 // outgoingTimeout is the timeout of the proposed outgoing HTLC. This - // will be extraced from the hop payload recevived by the incoming + // will be extracted from the hop payload received by the incoming // link. outgoingTimeout uint32 diff --git a/htlcswitch/payment_result_test.go b/htlcswitch/payment_result_test.go index aa7cbc173e..828c212316 100644 --- a/htlcswitch/payment_result_test.go +++ b/htlcswitch/payment_result_test.go @@ -215,6 +215,5 @@ func TestNetworkResultStore(t *testing.T) { if i >= 2 && err != ErrPaymentIDNotFound { t.Fatalf("expected ErrPaymentIDNotFound, got %v", err) } - } } diff --git a/htlcswitch/resolution_store.go b/htlcswitch/resolution_store.go new file mode 100644 index 0000000000..20ac4e5d8a --- /dev/null +++ b/htlcswitch/resolution_store.go @@ -0,0 +1,202 @@ +package htlcswitch + +import ( + "bytes" + "io" + + "github.com/go-errors/errors" + "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/contractcourt" + "github.com/lightningnetwork/lnd/kvdb" + "github.com/lightningnetwork/lnd/lnwire" +) + +var ( + // resBucketKey is used for the root level bucket that stores the + // CircuitKey -> ResolutionMsg mapping. + resBucketKey = []byte("resolution-store-bucket-key") + + // errResMsgNotFound is used to let callers know that the resolution + // message was not found for the given CircuitKey. This is used in the + // checkResolutionMsg function. + errResMsgNotFound = errors.New("resolution message not found") +) + +// resolutionStore contains ResolutionMsgs received from the contractcourt. The +// Switch deletes these from the store when the underlying circuit has been +// removed via DeleteCircuits. If the circuit hasn't been deleted, the Switch +// will dispatch the ResolutionMsg to a link if this was a multi-hop HTLC or to +// itself if the Switch initiated the payment. +type resolutionStore struct { + backend kvdb.Backend +} + +func newResolutionStore(db kvdb.Backend) *resolutionStore { + return &resolutionStore{ + backend: db, + } +} + +// addResolutionMsg persists a ResolutionMsg to the resolutionStore. +func (r *resolutionStore) addResolutionMsg( + resMsg *contractcourt.ResolutionMsg) error { + + // The outKey will be the database key. + outKey := &CircuitKey{ + ChanID: resMsg.SourceChan, + HtlcID: resMsg.HtlcIndex, + } + + var resBuf bytes.Buffer + if err := serializeResolutionMsg(&resBuf, resMsg); err != nil { + return err + } + + err := kvdb.Update(r.backend, func(tx kvdb.RwTx) error { + resBucket, err := tx.CreateTopLevelBucket(resBucketKey) + if err != nil { + return err + } + + return resBucket.Put(outKey.Bytes(), resBuf.Bytes()) + }, func() {}) + if err != nil { + return err + } + + return nil +} + +// checkResolutionMsg returns nil if the resolution message is found in the +// store. It returns an error if no resolution message was found for the +// passed outKey or if a database error occurred. +func (r *resolutionStore) checkResolutionMsg(outKey *CircuitKey) error { + err := kvdb.View(r.backend, func(tx kvdb.RTx) error { + resBucket := tx.ReadBucket(resBucketKey) + if resBucket == nil { + // Return an error if the bucket doesn't exist. + return errResMsgNotFound + } + + msg := resBucket.Get(outKey.Bytes()) + if msg == nil { + // Return the not found error since no message exists + // for this CircuitKey. + return errResMsgNotFound + } + + // Return nil to indicate that the message was found. + return nil + }, func() {}) + if err != nil { + return err + } + + return nil +} + +// fetchAllResolutionMsg returns a slice of all stored ResolutionMsgs. This is +// used by the Switch on start-up. +func (r *resolutionStore) fetchAllResolutionMsg() ( + []*contractcourt.ResolutionMsg, error) { + + var msgs []*contractcourt.ResolutionMsg + + err := kvdb.View(r.backend, func(tx kvdb.RTx) error { + resBucket := tx.ReadBucket(resBucketKey) + if resBucket == nil { + return nil + } + + return resBucket.ForEach(func(k, v []byte) error { + kr := bytes.NewReader(k) + outKey := &CircuitKey{} + if err := outKey.Decode(kr); err != nil { + return err + } + + vr := bytes.NewReader(v) + resMsg, err := deserializeResolutionMsg(vr) + if err != nil { + return err + } + + // Set the CircuitKey values on the ResolutionMsg. + resMsg.SourceChan = outKey.ChanID + resMsg.HtlcIndex = outKey.HtlcID + + msgs = append(msgs, resMsg) + return nil + }) + }, func() { + msgs = nil + }) + if err != nil { + return nil, err + } + + return msgs, nil +} + +// deleteResolutionMsg removes a ResolutionMsg with the passed-in CircuitKey. +func (r *resolutionStore) deleteResolutionMsg(outKey *CircuitKey) error { + err := kvdb.Update(r.backend, func(tx kvdb.RwTx) error { + resBucket, err := tx.CreateTopLevelBucket(resBucketKey) + if err != nil { + return err + } + + return resBucket.Delete(outKey.Bytes()) + }, func() {}) + return err +} + +// serializeResolutionMsg writes part of a ResolutionMsg to the passed +// io.Writer. +func serializeResolutionMsg(w io.Writer, + resMsg *contractcourt.ResolutionMsg) error { + + isFail := resMsg.Failure != nil + + if err := channeldb.WriteElement(w, isFail); err != nil { + return err + } + + // If this is a failure message, then we're done serializing. + if isFail { + return nil + } + + // Else this is a settle message, and we need to write the preimage. + return channeldb.WriteElement(w, *resMsg.PreImage) +} + +// deserializeResolutionMsg reads part of a ResolutionMsg from the passed +// io.Reader. +func deserializeResolutionMsg(r io.Reader) (*contractcourt.ResolutionMsg, + error) { + + resMsg := &contractcourt.ResolutionMsg{} + var isFail bool + + if err := channeldb.ReadElements(r, &isFail); err != nil { + return nil, err + } + + // If a failure resolution msg was stored, set the Failure field. + if isFail { + failureMsg := &lnwire.FailPermanentChannelFailure{} + resMsg.Failure = failureMsg + return resMsg, nil + } + + var preimage [32]byte + resMsg.PreImage = &preimage + + // Else this is a settle resolution msg and we will read the preimage. + if err := channeldb.ReadElement(r, resMsg.PreImage); err != nil { + return nil, err + } + + return resMsg, nil +} diff --git a/htlcswitch/resolution_store_test.go b/htlcswitch/resolution_store_test.go new file mode 100644 index 0000000000..f58f39d85f --- /dev/null +++ b/htlcswitch/resolution_store_test.go @@ -0,0 +1,154 @@ +package htlcswitch + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/lightningnetwork/lnd/contractcourt" + "github.com/lightningnetwork/lnd/kvdb" + "github.com/lightningnetwork/lnd/lnwire" + "github.com/stretchr/testify/require" +) + +// TestInsertAndDelete tests that an inserted resolution message can be +// deleted. +func TestInsertAndDelete(t *testing.T) { + t.Parallel() + + scid := lnwire.NewShortChanIDFromInt(1) + + failResMsg := &contractcourt.ResolutionMsg{ + SourceChan: scid, + HtlcIndex: 2, + Failure: &lnwire.FailTemporaryChannelFailure{}, + } + + settleBytes := [32]byte{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + } + + settleResMsg := &contractcourt.ResolutionMsg{ + SourceChan: scid, + HtlcIndex: 3, + PreImage: &settleBytes, + } + + // Create the backend database and use it to create the resolution + // store. + dbDir, err := ioutil.TempDir("", "resolutionStore") + require.NoError(t, err) + + dbPath := filepath.Join(dbDir, "testdb") + db, err := kvdb.Create( + kvdb.BoltBackendName, dbPath, true, kvdb.DefaultDBTimeout, + ) + require.NoError(t, err) + + cleanUp := func() { + db.Close() + os.RemoveAll(dbDir) + } + defer cleanUp() + + resStore := newResolutionStore(db) + + // We'll add the failure resolution message first, then check that it + // exists in the store. + err = resStore.addResolutionMsg(failResMsg) + require.NoError(t, err) + + // Assert that checkResolutionMsg returns nil, signalling that the + // resolution message was properly stored. + outKey := &CircuitKey{ + ChanID: failResMsg.SourceChan, + HtlcID: failResMsg.HtlcIndex, + } + err = resStore.checkResolutionMsg(outKey) + require.NoError(t, err) + + resMsgs, err := resStore.fetchAllResolutionMsg() + require.NoError(t, err) + require.Equal(t, 1, len(resMsgs)) + + // It should match failResMsg above. + require.Equal(t, failResMsg.SourceChan, resMsgs[0].SourceChan) + require.Equal(t, failResMsg.HtlcIndex, resMsgs[0].HtlcIndex) + require.NotNil(t, resMsgs[0].Failure) + require.Nil(t, resMsgs[0].PreImage) + + // We'll add the settleResMsg now. + err = resStore.addResolutionMsg(settleResMsg) + require.NoError(t, err) + + // Check that checkResolutionMsg returns nil for the settle CircuitKey. + outKey.ChanID = settleResMsg.SourceChan + outKey.HtlcID = settleResMsg.HtlcIndex + err = resStore.checkResolutionMsg(outKey) + require.NoError(t, err) + + // We should have two resolution messages in the store, one failure and + // one success. + resMsgs, err = resStore.fetchAllResolutionMsg() + require.NoError(t, err) + require.Equal(t, 2, len(resMsgs)) + + // The first resolution message should be the failure. + require.Equal(t, failResMsg.SourceChan, resMsgs[0].SourceChan) + require.Equal(t, failResMsg.HtlcIndex, resMsgs[0].HtlcIndex) + require.NotNil(t, resMsgs[0].Failure) + require.Nil(t, resMsgs[0].PreImage) + + // The second resolution message should be the success. + require.Equal(t, settleResMsg.SourceChan, resMsgs[1].SourceChan) + require.Equal(t, settleResMsg.HtlcIndex, resMsgs[1].HtlcIndex) + require.Nil(t, resMsgs[1].Failure) + require.Equal(t, settleBytes, *resMsgs[1].PreImage) + + // We'll now delete the failure resolution message and assert that only + // the success is left. + failKey := &CircuitKey{ + ChanID: scid, + HtlcID: failResMsg.HtlcIndex, + } + + err = resStore.deleteResolutionMsg(failKey) + require.NoError(t, err) + + // Assert that checkResolutionMsg returns errResMsgNotFound. + err = resStore.checkResolutionMsg(failKey) + require.ErrorIs(t, err, errResMsgNotFound) + + resMsgs, err = resStore.fetchAllResolutionMsg() + require.NoError(t, err) + require.Equal(t, 1, len(resMsgs)) + + // Assert that the success is left. + require.Equal(t, settleResMsg.SourceChan, resMsgs[0].SourceChan) + require.Equal(t, settleResMsg.HtlcIndex, resMsgs[0].HtlcIndex) + require.Nil(t, resMsgs[0].Failure) + require.Equal(t, settleBytes, *resMsgs[0].PreImage) + + // Now we'll delete the settle resolution message and assert that the + // store is empty. + settleKey := &CircuitKey{ + ChanID: scid, + HtlcID: settleResMsg.HtlcIndex, + } + + err = resStore.deleteResolutionMsg(settleKey) + require.NoError(t, err) + + // Assert that checkResolutionMsg returns errResMsgNotFound for the + // settle key. + err = resStore.checkResolutionMsg(settleKey) + require.ErrorIs(t, err, errResMsgNotFound) + + resMsgs, err = resStore.fetchAllResolutionMsg() + require.NoError(t, err) + require.Equal(t, 0, len(resMsgs)) +} diff --git a/htlcswitch/switch.go b/htlcswitch/switch.go index 99c46737a7..d6af62acca 100644 --- a/htlcswitch/switch.go +++ b/htlcswitch/switch.go @@ -9,8 +9,8 @@ import ( "sync/atomic" "time" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" @@ -290,22 +290,30 @@ type Switch struct { // blockEpochStream is an active block epoch event stream backed by an // active ChainNotifier instance. This will be used to retrieve the - // lastest height of the chain. + // latest height of the chain. blockEpochStream *chainntnfs.BlockEpochEvent // pendingSettleFails is the set of settle/fail entries that we need to // ack in the forwarding package of the outgoing link. This was added to // make pipelining settles more efficient. pendingSettleFails []channeldb.SettleFailRef + + // resMsgStore is used to store the set of ResolutionMsg that come from + // contractcourt. This is used so the Switch can properly forward them, + // even on restarts. + resMsgStore *resolutionStore } // New creates the new instance of htlc switch. func New(cfg Config, currentHeight uint32) (*Switch, error) { + resStore := newResolutionStore(cfg.DB) + circuitMap, err := NewCircuitMap(&CircuitMapConfig{ DB: cfg.DB, FetchAllOpenChannels: cfg.FetchAllOpenChannels, FetchClosedChannels: cfg.FetchClosedChannels, ExtractErrorEncrypter: cfg.ExtractErrorEncrypter, + CheckResolutionMsg: resStore.checkResolutionMsg, }) if err != nil { return nil, err @@ -323,6 +331,7 @@ func New(cfg Config, currentHeight uint32) (*Switch, error) { htlcPlex: make(chan *plexPacket), chanCloseRequests: make(chan *ChanClose), resolutionMsgs: make(chan *resolutionMsg), + resMsgStore: resStore, quit: make(chan struct{}), } @@ -342,7 +351,7 @@ func New(cfg Config, currentHeight uint32) (*Switch, error) { type resolutionMsg struct { contractcourt.ResolutionMsg - doneChan chan struct{} + errChan chan error } // ProcessContractResolution is called by active contract resolvers once a @@ -351,25 +360,23 @@ type resolutionMsg struct { // didn't need to go to the chain in order to fulfill a contract. We'll process // this message just as if it came from an active outgoing channel. func (s *Switch) ProcessContractResolution(msg contractcourt.ResolutionMsg) error { - - done := make(chan struct{}) + errChan := make(chan error, 1) select { case s.resolutionMsgs <- &resolutionMsg{ ResolutionMsg: msg, - doneChan: done, + errChan: errChan, }: case <-s.quit: return ErrSwitchExiting } select { - case <-done: + case err := <-errChan: + return err case <-s.quit: return ErrSwitchExiting } - - return nil } // GetPaymentResult returns the the result of the payment attempt with the @@ -728,7 +735,7 @@ func (s *Switch) ForwardPackets(linkQuit chan struct{}, return nil } -// logFwdErrs logs any errors received on `fwdChan` +// logFwdErrs logs any errors received on `fwdChan`. func (s *Switch) logFwdErrs(num *int, wg *sync.WaitGroup, fwdChan chan error) { defer s.wg.Done() @@ -1130,7 +1137,7 @@ func (s *Switch) handlePacketForward(packet *htlcPacket) error { // this htlc. The reason for randomization is to evenly // distribute the htlc load without making assumptions about // what the best channel is. - destination := destinations[rand.Intn(len(destinations))] + destination := destinations[rand.Intn(len(destinations))] // nolint:gosec // Retrieve the incoming link by its ShortChannelID. Note that // the incomingChanID is never set to hop.Source here. @@ -1678,6 +1685,28 @@ out: go s.cfg.LocalChannelClose(peerPub[:], req) case resolutionMsg := <-s.resolutionMsgs: + // We'll persist the resolution message to the Switch's + // resolution store. + resMsg := resolutionMsg.ResolutionMsg + err := s.resMsgStore.addResolutionMsg(&resMsg) + if err != nil { + // This will only fail if there is a database + // error or a serialization error. Sending the + // error prevents the contractcourt from being + // in a state where it believes the send was + // successful, when it wasn't. + log.Errorf("unable to add resolution msg: %v", + err) + resolutionMsg.errChan <- err + continue + } + + // At this point, the resolution message has been + // persisted. It is safe to signal success by sending + // a nil error since the Switch will re-deliver the + // resolution message on restart. + resolutionMsg.errChan <- nil + pkt := &htlcPacket{ outgoingChanID: resolutionMsg.SourceChan, outgoingHTLCID: resolutionMsg.HtlcIndex, @@ -1703,14 +1732,11 @@ out: // encounter is due to the circuit already being // closed. This is fine, as processing this message is // meant to be idempotent. - err := s.handlePacketForward(pkt) + err = s.handlePacketForward(pkt) if err != nil { log.Errorf("Unable to forward resolution msg: %v", err) } - // With the message processed, we'll now close out - close(resolutionMsg.doneChan) - // A new packet has arrived for forwarding, we'll interpret the // packet concretely, then either forward it along, or // interpret a return packet to a locally initialized one. @@ -1846,7 +1872,7 @@ func (s *Switch) Start() error { return errors.New("htlc switch already started") } - log.Infof("Starting HTLC Switch") + log.Infof("HTLC Switch starting") blockEpochStream, err := s.cfg.Notifier.RegisterBlockEpochNtfn(nil) if err != nil { @@ -1863,6 +1889,72 @@ func (s *Switch) Start() error { return err } + if err := s.reforwardResolutions(); err != nil { + // We are already stopping so we can ignore the error. + _ = s.Stop() + log.Errorf("unable to reforward resolutions: %v", err) + return err + } + + return nil +} + +// reforwardResolutions fetches the set of resolution messages stored on-disk +// and reforwards them if their circuits are still open. If the circuits have +// been deleted, then we will delete the resolution message from the database. +func (s *Switch) reforwardResolutions() error { + // Fetch all stored resolution messages, deleting the ones that are + // resolved. + resMsgs, err := s.resMsgStore.fetchAllResolutionMsg() + if err != nil { + return err + } + + switchPackets := make([]*htlcPacket, 0, len(resMsgs)) + for _, resMsg := range resMsgs { + // If the open circuit no longer exists, then we can remove the + // message from the store. + outKey := CircuitKey{ + ChanID: resMsg.SourceChan, + HtlcID: resMsg.HtlcIndex, + } + + if s.circuits.LookupOpenCircuit(outKey) == nil { + // The open circuit doesn't exist. + err := s.resMsgStore.deleteResolutionMsg(&outKey) + if err != nil { + return err + } + + continue + } + + // The circuit is still open, so we can assume that the link or + // switch (if we are the source) hasn't cleaned it up yet. + resPkt := &htlcPacket{ + outgoingChanID: resMsg.SourceChan, + outgoingHTLCID: resMsg.HtlcIndex, + isResolution: true, + } + + if resMsg.Failure != nil { + resPkt.htlc = &lnwire.UpdateFailHTLC{} + } else { + resPkt.htlc = &lnwire.UpdateFulfillHTLC{ + PaymentPreimage: *resMsg.PreImage, + } + } + + switchPackets = append(switchPackets, resPkt) + } + + // We'll now dispatch the set of resolution messages to the proper + // destination. An error is only encountered here if the switch is + // shutting down. + if err := s.ForwardPackets(nil, switchPackets...); err != nil { + return err + } + return nil } diff --git a/htlcswitch/switch_test.go b/htlcswitch/switch_test.go index b68384500c..7500611267 100644 --- a/htlcswitch/switch_test.go +++ b/htlcswitch/switch_test.go @@ -1,6 +1,7 @@ package htlcswitch import ( + "bytes" "crypto/rand" "crypto/sha256" "fmt" @@ -10,14 +11,17 @@ import ( "testing" "time" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" "github.com/davecgh/go-spew/spew" + "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/contractcourt" "github.com/lightningnetwork/lnd/htlcswitch/hodl" "github.com/lightningnetwork/lnd/htlcswitch/hop" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/ticker" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -1546,7 +1550,7 @@ func TestCheckCircularForward(t *testing.T) { } // TestSkipIneligibleLinksMultiHopForward tests that if a multi-hop HTLC comes -// along, then we won't attempt to froward it down al ink that isn't yet able +// along, then we won't attempt to forward it down al ink that isn't yet able // to forward any HTLC's. func TestSkipIneligibleLinksMultiHopForward(t *testing.T) { tests := []multiHopFwdTest{ @@ -1601,7 +1605,7 @@ func TestSkipIneligibleLinksMultiHopForward(t *testing.T) { } // testSkipIneligibleLinksMultiHopForward tests that if a multi-hop HTLC comes -// along, then we won't attempt to froward it down al ink that isn't yet able +// along, then we won't attempt to forward it down al ink that isn't yet able // to forward any HTLC's. func testSkipIneligibleLinksMultiHopForward(t *testing.T, testCase *multiHopFwdTest) { @@ -2563,7 +2567,7 @@ func TestSwitchGetPaymentResult(t *testing.T) { t.Fatalf("unable to store result: %v", err) } - // The result should be availble. + // The result should be available. select { case res, ok := <-resultChan: if !ok { @@ -2907,7 +2911,7 @@ func testHtcNotifier(t *testing.T, testOpts []serverOption, iterations int, // Add the htlcNotifier option to any other options // set in the test. - options := append(testOpts, notifierOption) + options := append(testOpts, notifierOption) // nolint:gocritic n := newThreeHopNetwork( t, channels.aliceToBob, @@ -2959,7 +2963,6 @@ func testHtcNotifier(t *testing.T, testOpts []serverOption, iterations int, checkHtlcEvents(t, aliceEvents.Updates(), alice) checkHtlcEvents(t, bobEvents.Updates(), bob) checkHtlcEvents(t, carolEvents.Updates(), carol) - } } @@ -3139,38 +3142,44 @@ func getThreeHopEvents(channels *clusterChannels, htlcID uint64, } type mockForwardInterceptor struct { - intercepted InterceptedForward + t *testing.T + + interceptedChan chan InterceptedPacket } -func (m *mockForwardInterceptor) InterceptForwardHtlc(intercepted InterceptedForward) bool { +func (m *mockForwardInterceptor) InterceptForwardHtlc( + intercepted InterceptedPacket) error { - m.intercepted = intercepted - return true -} + m.interceptedChan <- intercepted -func (m *mockForwardInterceptor) settle(preimage lntypes.Preimage) error { - return m.intercepted.Settle(preimage) + return nil } -func (m *mockForwardInterceptor) fail() error { - return m.intercepted.Fail() -} +func (m *mockForwardInterceptor) getIntercepted() InterceptedPacket { + select { + case p := <-m.interceptedChan: + return p + + case <-time.After(time.Second): + require.Fail(m.t, "timeout") -func (m *mockForwardInterceptor) resume() error { - return m.intercepted.Resume() + return InterceptedPacket{} + } } func assertNumCircuits(t *testing.T, s *Switch, pending, opened int) { if s.circuits.NumPending() != pending { - t.Fatal("wrong amount of half circuits") + t.Fatalf("wrong amount of half circuits, expected %v but "+ + "got %v", pending, s.circuits.NumPending()) } if s.circuits.NumOpen() != opened { - t.Fatal("wrong amount of circuits") + t.Fatalf("wrong amount of circuits, expected %v but got %v", + opened, s.circuits.NumOpen()) } } func assertOutgoingLinkReceive(t *testing.T, targetLink *mockChannelLink, - expectReceive bool) { + expectReceive bool) *htlcPacket { // Pull packet from targetLink link. select { @@ -3181,11 +3190,25 @@ func assertOutgoingLinkReceive(t *testing.T, targetLink *mockChannelLink, t.Fatalf("unable to complete payment circuit: %v", err) } + return packet + case <-time.After(time.Second): if expectReceive { t.Fatal("request was not propagated to destination") } } + + return nil +} + +func assertOutgoingLinkReceiveIntercepted(t *testing.T, + targetLink *mockChannelLink) { + + select { + case <-targetLink.packets: + case <-time.After(time.Second): + t.Fatal("request was not propagated to destination") + } } func TestSwitchHoldForward(t *testing.T) { @@ -3247,78 +3270,304 @@ func TestSwitchHoldForward(t *testing.T) { // bob channel link. preimage := [sha256.Size]byte{1} rhash := sha256.Sum256(preimage[:]) - ogPacket := &htlcPacket{ - incomingChanID: aliceChannelLink.ShortChanID(), - incomingHTLCID: 0, - outgoingChanID: bobChannelLink.ShortChanID(), - obfuscator: NewMockObfuscator(), - htlc: &lnwire.UpdateAddHTLC{ - PaymentHash: rhash, - Amount: 1, - }, + onionBlob := [1366]byte{4, 5, 6} + incomingHtlcID := uint64(0) + + const cltvRejectDelta = 13 + + createTestPacket := func() *htlcPacket { + incomingHtlcID++ + + return &htlcPacket{ + incomingChanID: aliceChannelLink.ShortChanID(), + incomingHTLCID: incomingHtlcID, + incomingTimeout: testStartingHeight + cltvRejectDelta + 1, + outgoingChanID: bobChannelLink.ShortChanID(), + obfuscator: NewMockObfuscator(), + htlc: &lnwire.UpdateAddHTLC{ + PaymentHash: rhash, + Amount: 1, + OnionBlob: onionBlob, + }, + } + } + + createSettlePacket := func(outgoingHTLCID uint64) *htlcPacket { + return &htlcPacket{ + outgoingChanID: bobChannelLink.ShortChanID(), + outgoingHTLCID: outgoingHTLCID, + amount: 1, + htlc: &lnwire.UpdateFulfillHTLC{ + PaymentPreimage: preimage, + }, + } + } + + forwardInterceptor := &mockForwardInterceptor{ + t: t, + interceptedChan: make(chan InterceptedPacket), } + switchForwardInterceptor := NewInterceptableSwitch( + s, cltvRejectDelta, false, + ) + require.NoError(t, switchForwardInterceptor.Start()) - forwardInterceptor := &mockForwardInterceptor{} - switchForwardInterceptor := NewInterceptableSwitch(s) switchForwardInterceptor.SetInterceptor(forwardInterceptor.InterceptForwardHtlc) linkQuit := make(chan struct{}) - // Test resume a hold forward + // Test a forward that expires too soon. + packet := createTestPacket() + packet.incomingTimeout = testStartingHeight + cltvRejectDelta - 1 + + err = switchForwardInterceptor.ForwardPackets(linkQuit, false, packet) + if err != nil { + t.Fatalf("can't forward htlc packet: %v", err) + } + assertOutgoingLinkReceive(t, bobChannelLink, false) + assertOutgoingLinkReceiveIntercepted(t, aliceChannelLink) assertNumCircuits(t, s, 0, 0) - if err := switchForwardInterceptor.ForwardPackets(linkQuit, ogPacket); err != nil { + + // Test a forward that expires too soon and can't be failed. + packet = createTestPacket() + packet.incomingTimeout = testStartingHeight + cltvRejectDelta - 1 + + // Simulate an error during the composition of the failure message. + currentCallback := s.cfg.FetchLastChannelUpdate + s.cfg.FetchLastChannelUpdate = func( + lnwire.ShortChannelID) (*lnwire.ChannelUpdate, error) { + + return nil, errors.New("cannot fetch update") + } + + err = switchForwardInterceptor.ForwardPackets(linkQuit, false, packet) + if err != nil { t.Fatalf("can't forward htlc packet: %v", err) } + receivedPkt := assertOutgoingLinkReceive(t, bobChannelLink, true) + assertNumCircuits(t, s, 1, 1) + + require.NoError(t, switchForwardInterceptor.ForwardPackets( + linkQuit, false, + createSettlePacket(receivedPkt.outgoingHTLCID), + )) + + assertOutgoingLinkReceive(t, aliceChannelLink, true) + assertNumCircuits(t, s, 0, 0) + + s.cfg.FetchLastChannelUpdate = currentCallback + + // Test resume a hold forward. + assertNumCircuits(t, s, 0, 0) + err = switchForwardInterceptor.ForwardPackets( + linkQuit, false, createTestPacket(), + ) + require.NoError(t, err) + assertNumCircuits(t, s, 0, 0) assertOutgoingLinkReceive(t, bobChannelLink, false) - if err := forwardInterceptor.resume(); err != nil { - t.Fatalf("failed to resume forward") - } - assertOutgoingLinkReceive(t, bobChannelLink, true) + require.NoError(t, switchForwardInterceptor.Resolve(&FwdResolution{ + Action: FwdActionResume, + Key: forwardInterceptor.getIntercepted().IncomingCircuit, + })) + receivedPkt = assertOutgoingLinkReceive(t, bobChannelLink, true) assertNumCircuits(t, s, 1, 1) // settling the htlc to close the circuit. - settle := &htlcPacket{ - outgoingChanID: bobChannelLink.ShortChanID(), - outgoingHTLCID: 0, - amount: 1, - htlc: &lnwire.UpdateFulfillHTLC{ - PaymentPreimage: preimage, - }, - } - if err := switchForwardInterceptor.ForwardPackets(linkQuit, settle); err != nil { - t.Fatalf("can't forward htlc packet: %v", err) - } + err = switchForwardInterceptor.ForwardPackets( + linkQuit, false, + createSettlePacket(receivedPkt.outgoingHTLCID), + ) + require.NoError(t, err) + + assertOutgoingLinkReceive(t, aliceChannelLink, true) + assertNumCircuits(t, s, 0, 0) + + // Test resume a hold forward after disconnection. + require.NoError(t, switchForwardInterceptor.ForwardPackets( + linkQuit, false, createTestPacket(), + )) + + // Wait until the packet is offered to the interceptor. + _ = forwardInterceptor.getIntercepted() + + // No forward expected yet. + assertNumCircuits(t, s, 0, 0) + assertOutgoingLinkReceive(t, bobChannelLink, false) + + // Disconnect should resume the forwarding. + switchForwardInterceptor.SetInterceptor(nil) + + receivedPkt = assertOutgoingLinkReceive(t, bobChannelLink, true) + assertNumCircuits(t, s, 1, 1) + + // Settle the htlc to close the circuit. + require.NoError(t, switchForwardInterceptor.ForwardPackets( + linkQuit, false, + createSettlePacket(receivedPkt.outgoingHTLCID), + )) + assertOutgoingLinkReceive(t, aliceChannelLink, true) assertNumCircuits(t, s, 0, 0) // Test failing a hold forward - if err := switchForwardInterceptor.ForwardPackets(linkQuit, ogPacket); err != nil { - t.Fatalf("can't forward htlc packet: %v", err) - } + switchForwardInterceptor.SetInterceptor( + forwardInterceptor.InterceptForwardHtlc, + ) + + require.NoError(t, switchForwardInterceptor.ForwardPackets( + linkQuit, false, createTestPacket(), + )) assertNumCircuits(t, s, 0, 0) assertOutgoingLinkReceive(t, bobChannelLink, false) - if err := forwardInterceptor.fail(); err != nil { - t.Fatalf("failed to cancel forward %v", err) - } + require.NoError(t, switchForwardInterceptor.Resolve(&FwdResolution{ + Action: FwdActionFail, + Key: forwardInterceptor.getIntercepted().IncomingCircuit, + FailureCode: lnwire.CodeTemporaryChannelFailure, + })) assertOutgoingLinkReceive(t, bobChannelLink, false) assertOutgoingLinkReceive(t, aliceChannelLink, true) assertNumCircuits(t, s, 0, 0) - // Test settling a hold forward - if err := switchForwardInterceptor.ForwardPackets(linkQuit, ogPacket); err != nil { - t.Fatalf("can't forward htlc packet: %v", err) - } + // Test failing a hold forward with a failure message. + require.NoError(t, + switchForwardInterceptor.ForwardPackets( + linkQuit, false, createTestPacket(), + ), + ) + assertNumCircuits(t, s, 0, 0) + assertOutgoingLinkReceive(t, bobChannelLink, false) + + reason := lnwire.OpaqueReason([]byte{1, 2, 3}) + require.NoError(t, switchForwardInterceptor.Resolve(&FwdResolution{ + Action: FwdActionFail, + Key: forwardInterceptor.getIntercepted().IncomingCircuit, + FailureMessage: reason, + })) + + assertOutgoingLinkReceive(t, bobChannelLink, false) + packet = assertOutgoingLinkReceive(t, aliceChannelLink, true) + + require.Equal(t, reason, packet.htlc.(*lnwire.UpdateFailHTLC).Reason) + + assertNumCircuits(t, s, 0, 0) + + // Test failing a hold forward with a malformed htlc failure. + err = switchForwardInterceptor.ForwardPackets( + linkQuit, false, createTestPacket(), + ) + require.NoError(t, err) + assertNumCircuits(t, s, 0, 0) assertOutgoingLinkReceive(t, bobChannelLink, false) - if err := forwardInterceptor.settle(preimage); err != nil { - t.Fatal("failed to cancel forward") + code := lnwire.CodeInvalidOnionKey + require.NoError(t, switchForwardInterceptor.Resolve(&FwdResolution{ + Action: FwdActionFail, + Key: forwardInterceptor.getIntercepted().IncomingCircuit, + FailureCode: code, + })) + + assertOutgoingLinkReceive(t, bobChannelLink, false) + packet = assertOutgoingLinkReceive(t, aliceChannelLink, true) + failPacket := packet.htlc.(*lnwire.UpdateFailHTLC) + + shaOnionBlob := sha256.Sum256(onionBlob[:]) + expectedFailure := &lnwire.FailInvalidOnionKey{ + OnionSHA256: shaOnionBlob, } + var b bytes.Buffer + require.NoError(t, lnwire.EncodeFailure(&b, expectedFailure, 0)) + + assert.Equal(t, lnwire.OpaqueReason(b.Bytes()), failPacket.Reason) + + assertNumCircuits(t, s, 0, 0) + + // Test settling a hold forward + require.NoError(t, switchForwardInterceptor.ForwardPackets( + linkQuit, false, createTestPacket(), + )) + assertNumCircuits(t, s, 0, 0) + assertOutgoingLinkReceive(t, bobChannelLink, false) + + require.NoError(t, switchForwardInterceptor.Resolve(&FwdResolution{ + Key: forwardInterceptor.getIntercepted().IncomingCircuit, + Action: FwdActionSettle, + Preimage: preimage, + })) assertOutgoingLinkReceive(t, bobChannelLink, false) assertOutgoingLinkReceive(t, aliceChannelLink, true) assertNumCircuits(t, s, 0, 0) + + require.NoError(t, switchForwardInterceptor.Stop()) + + // Test always-on interception. + switchForwardInterceptor = NewInterceptableSwitch(s, cltvRejectDelta, true) + require.NoError(t, switchForwardInterceptor.Start()) + + // Forward a fresh packet. It is expected to be failed immediately, + // because there is no interceptor registered. + require.NoError(t, switchForwardInterceptor.ForwardPackets( + linkQuit, false, createTestPacket(), + )) + + assertOutgoingLinkReceive(t, bobChannelLink, false) + assertOutgoingLinkReceive(t, aliceChannelLink, true) + assertNumCircuits(t, s, 0, 0) + + // Forward a replayed packet. It is expected to be held until the + // interceptor connects. To continue the test, it needs to be ran in a + // goroutine. + errChan := make(chan error) + go func() { + errChan <- switchForwardInterceptor.ForwardPackets( + linkQuit, true, createTestPacket(), + ) + }() + + // Assert that nothing is forward to the switch. + assertOutgoingLinkReceive(t, bobChannelLink, false) + assertNumCircuits(t, s, 0, 0) + + // Register an interceptor. + switchForwardInterceptor.SetInterceptor( + forwardInterceptor.InterceptForwardHtlc, + ) + + // Expect the ForwardPackets call to unblock. + require.NoError(t, <-errChan) + + // Now expect the queued packet to come through. + forwardInterceptor.getIntercepted() + + // Disconnect and reconnect interceptor. + switchForwardInterceptor.SetInterceptor(nil) + switchForwardInterceptor.SetInterceptor( + forwardInterceptor.InterceptForwardHtlc, + ) + + // A replay of the held packet is expected. + intercepted := forwardInterceptor.getIntercepted() + + // Settle the packet. + require.NoError(t, switchForwardInterceptor.Resolve(&FwdResolution{ + Key: intercepted.IncomingCircuit, + Action: FwdActionSettle, + Preimage: preimage, + })) + assertOutgoingLinkReceive(t, bobChannelLink, false) + assertOutgoingLinkReceive(t, aliceChannelLink, true) + assertNumCircuits(t, s, 0, 0) + + require.NoError(t, switchForwardInterceptor.Stop()) + + select { + case <-forwardInterceptor.interceptedChan: + require.Fail(t, "unexpected interception") + + default: + } } // TestSwitchDustForwarding tests that the switch properly fails HTLC's which @@ -3675,3 +3924,144 @@ func TestSwitchMailboxDust(t *testing.T) { t.Fatal("no timely reply from switch") } } + +// TestSwitchResolution checks the ability of the switch to persist and handle +// resolution messages. +func TestSwitchResolution(t *testing.T) { + t.Parallel() + + alicePeer, err := newMockServer( + t, "alice", testStartingHeight, nil, testDefaultDelta, + ) + require.NoError(t, err) + + bobPeer, err := newMockServer( + t, "bob", testStartingHeight, nil, testDefaultDelta, + ) + require.NoError(t, err) + + s, err := initSwitchWithDB(testStartingHeight, nil) + require.NoError(t, err) + + err = s.Start() + require.NoError(t, err) + + chanID1, chanID2, aliceChanID, bobChanID := genIDs() + + aliceChannelLink := newMockChannelLink( + s, chanID1, aliceChanID, alicePeer, true, + ) + bobChannelLink := newMockChannelLink( + s, chanID2, bobChanID, bobPeer, true, + ) + err = s.AddLink(aliceChannelLink) + require.NoError(t, err) + err = s.AddLink(bobChannelLink) + require.NoError(t, err) + + // Create an add htlcPacket that Alice will send to Bob. + preimage, err := genPreimage() + require.NoError(t, err) + + rhash := sha256.Sum256(preimage[:]) + packet := &htlcPacket{ + incomingChanID: aliceChannelLink.ShortChanID(), + incomingHTLCID: 0, + outgoingChanID: bobChannelLink.ShortChanID(), + obfuscator: NewMockObfuscator(), + htlc: &lnwire.UpdateAddHTLC{ + PaymentHash: rhash, + Amount: 1, + }, + } + + err = s.ForwardPackets(nil, packet) + require.NoError(t, err) + + // Bob will receive the packet and open the circuit. + select { + case <-bobChannelLink.packets: + err = bobChannelLink.completeCircuit(packet) + require.NoError(t, err) + case <-time.After(time.Second): + t.Fatal("request was not propagated to destination") + } + + // Check that only one circuit is open. + require.Equal(t, 1, s.circuits.NumOpen()) + + // We'll send a settle resolution to Switch that should go to Alice. + settleResMsg := contractcourt.ResolutionMsg{ + SourceChan: bobChanID, + HtlcIndex: 0, + PreImage: &preimage, + } + + // Before the resolution is sent, remove alice's link so we can assert + // that the resolution is actually stored. Otherwise, it would be + // deleted shortly after being sent. + s.RemoveLink(chanID1) + + // Send the resolution message. + err = s.ProcessContractResolution(settleResMsg) + require.NoError(t, err) + + // Assert that the resolution store contains the settle reoslution. + resMsgs, err := s.resMsgStore.fetchAllResolutionMsg() + require.NoError(t, err) + + require.Equal(t, 1, len(resMsgs)) + require.Equal(t, settleResMsg.SourceChan, resMsgs[0].SourceChan) + require.Equal(t, settleResMsg.HtlcIndex, resMsgs[0].HtlcIndex) + require.Nil(t, resMsgs[0].Failure) + require.Equal(t, preimage, *resMsgs[0].PreImage) + + // Now we'll restart Alice's link and delete the circuit. + err = s.AddLink(aliceChannelLink) + require.NoError(t, err) + + // Alice will receive the packet and open the circuit. + select { + case alicePkt := <-aliceChannelLink.packets: + err = aliceChannelLink.completeCircuit(alicePkt) + require.NoError(t, err) + case <-time.After(time.Second): + t.Fatal("request was not propagated to destination") + } + + // Assert that there are no more circuits. + require.Equal(t, 0, s.circuits.NumOpen()) + + // We'll restart the Switch and assert that Alice does not receive + // another packet. + switchDB := s.cfg.DB.(*channeldb.DB) + err = s.Stop() + require.NoError(t, err) + + s, err = initSwitchWithDB(testStartingHeight, switchDB) + require.NoError(t, err) + + err = s.Start() + require.NoError(t, err) + defer func() { + _ = s.Stop() + }() + + err = s.AddLink(aliceChannelLink) + require.NoError(t, err) + err = s.AddLink(bobChannelLink) + require.NoError(t, err) + + // Alice should not receive a packet since the Switch should have + // deleted the resolution message since the circuit was closed. + select { + case alicePkt := <-aliceChannelLink.packets: + t.Fatalf("received erroneous packet: %v", alicePkt) + case <-time.After(time.Second * 5): + } + + // Check that the resolution message no longer exists in the store. + resMsgs, err = s.resMsgStore.fetchAllResolutionMsg() + require.NoError(t, err) + require.Equal(t, 0, len(resMsgs)) +} diff --git a/htlcswitch/test_utils.go b/htlcswitch/test_utils.go index 3fa57f6013..7dfd1599c3 100644 --- a/htlcswitch/test_utils.go +++ b/htlcswitch/test_utils.go @@ -5,9 +5,9 @@ import ( crand "crypto/rand" "crypto/sha256" "encoding/binary" + "encoding/hex" "fmt" "io/ioutil" - "math/big" "net" "os" "runtime" @@ -16,10 +16,11 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/go-errors/errors" sphinx "github.com/lightningnetwork/lightning-onion" "github.com/lightningnetwork/lnd/channeldb" @@ -45,16 +46,15 @@ var ( bobPrivKey = []byte("bob priv key") carolPrivKey = []byte("carol priv key") - testSig = &btcec.Signature{ - R: new(big.Int), - S: new(big.Int), - } - wireSig, _ = lnwire.NewSigFromSignature(testSig) + testRBytes, _ = hex.DecodeString("8ce2bc69281ce27da07e6683571319d18e949ddfa2965fb6caa1bf0314f882d7") + testSBytes, _ = hex.DecodeString("299105481d63e0f4bc2a88121167221b6700d72a0ead154c03be696a292d24ae") + testRScalar = new(btcec.ModNScalar) + testSScalar = new(btcec.ModNScalar) + _ = testRScalar.SetByteSlice(testRBytes) + _ = testSScalar.SetByteSlice(testSBytes) + testSig = ecdsa.NewSignature(testRScalar, testSScalar) - _, _ = testSig.R.SetString("6372440660162918006277497454296753625158993"+ - "5445068131219452686511677818569431", 10) - _, _ = testSig.S.SetString("1880105606924982582529128710493133386286603"+ - "3135609736119018462340006816851118", 10) + wireSig, _ = lnwire.NewSigFromSignature(testSig) testBatchTimeout = 50 * time.Millisecond ) @@ -128,8 +128,8 @@ func createTestChannel(alicePrivKey, bobPrivKey []byte, chanID lnwire.ShortChannelID) (*testLightningChannel, *testLightningChannel, func(), error) { - aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(btcec.S256(), alicePrivKey) - bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(btcec.S256(), bobPrivKey) + aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(alicePrivKey) + bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(bobPrivKey) channelCapacity := aliceAmount + bobAmount csvTimeoutAlice := uint32(5) @@ -1122,14 +1122,27 @@ func (h *hopNetwork) createChannelLink(server, peer *mockServer, maxFeeUpdateTimeout = 40 * time.Minute ) + notifyUpdateChan := make(chan *contractcourt.ContractUpdate) + doneChan := make(chan struct{}) + notifyContractUpdate := func(u *contractcourt.ContractUpdate) error { + select { + case notifyUpdateChan <- u: + case <-doneChan: + } + + return nil + } + link := NewChannelLink( ChannelLinkConfig{ - Switch: server.htlcSwitch, - BestHeight: server.htlcSwitch.BestHeight, - FwrdingPolicy: h.globalPolicy, - Peer: peer, - Circuits: server.htlcSwitch.CircuitModifier(), - ForwardPackets: server.htlcSwitch.ForwardPackets, + Switch: server.htlcSwitch, + BestHeight: server.htlcSwitch.BestHeight, + FwrdingPolicy: h.globalPolicy, + Peer: peer, + Circuits: server.htlcSwitch.CircuitModifier(), + ForwardPackets: func(linkQuit chan struct{}, _ bool, packets ...*htlcPacket) error { + return server.htlcSwitch.ForwardPackets(linkQuit, packets...) + }, DecodeHopIterators: decoder.DecodeHopIterators, ExtractErrorEncrypter: func(*btcec.PublicKey) ( hop.ErrorEncrypter, lnwire.FailCode) { @@ -1142,6 +1155,7 @@ func (h *hopNetwork) createChannelLink(server, peer *mockServer, UpdateContractSignals: func(*contractcourt.ContractSignals) error { return nil }, + NotifyContractUpdate: notifyContractUpdate, ChainEvents: &contractcourt.ChainEventSubscription{}, SyncStates: true, BatchSize: 10, @@ -1169,8 +1183,9 @@ func (h *hopNetwork) createChannelLink(server, peer *mockServer, go func() { for { select { - case <-link.(*channelLink).htlcUpdates: + case <-notifyUpdateChan: case <-link.(*channelLink).quit: + close(doneChan) return } } diff --git a/input/input.go b/input/input.go index 87d4b9fcd4..98ae2c056c 100644 --- a/input/input.go +++ b/input/input.go @@ -1,9 +1,9 @@ package input import ( + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/lntypes" ) @@ -42,6 +42,7 @@ type Input interface { // also nested p2sh outputs. CraftInputScript(signer Signer, txn *wire.MsgTx, hashCache *txscript.TxSigHashes, + prevOutputFetcher txscript.PrevOutputFetcher, txinIdx int) (*Script, error) // BlocksToMaturity returns the relative timelock, as a number of @@ -221,9 +222,13 @@ func NewCsvInputWithCltv(outpoint *wire.OutPoint, witnessType WitnessType, // txIndex within the passed transaction. The input scripts generated by this // method support spending p2wkh, p2wsh, and also nested p2sh outputs. func (bi *BaseInput) CraftInputScript(signer Signer, txn *wire.MsgTx, - hashCache *txscript.TxSigHashes, txinIdx int) (*Script, error) { + hashCache *txscript.TxSigHashes, + prevOutputFetcher txscript.PrevOutputFetcher, txinIdx int) (*Script, + error) { - witnessFunc := bi.witnessType.WitnessGenerator(signer, bi.SignDesc()) + signDesc := bi.SignDesc() + signDesc.PrevOutputFetcher = prevOutputFetcher + witnessFunc := bi.witnessType.WitnessGenerator(signer, signDesc) return witnessFunc(txn, hashCache, txinIdx) } @@ -260,11 +265,14 @@ func MakeHtlcSucceedInput(outpoint *wire.OutPoint, // txIndex within the passed transaction. The input scripts generated by this // method support spending p2wkh, p2wsh, and also nested p2sh outputs. func (h *HtlcSucceedInput) CraftInputScript(signer Signer, txn *wire.MsgTx, - hashCache *txscript.TxSigHashes, txinIdx int) (*Script, error) { + hashCache *txscript.TxSigHashes, + prevOutputFetcher txscript.PrevOutputFetcher, txinIdx int) (*Script, + error) { desc := h.signDesc desc.SigHashes = hashCache desc.InputIndex = txinIdx + desc.PrevOutputFetcher = prevOutputFetcher witness, err := SenderHtlcSpendRedeem( signer, &desc, txn, h.preimage, @@ -291,7 +299,9 @@ type HtlcSecondLevelAnchorInput struct { // createWitness creates a witness allowing the passed transaction to // spend the input. createWitness func(signer Signer, txn *wire.MsgTx, - hashCache *txscript.TxSigHashes, txinIdx int) (wire.TxWitness, error) + hashCache *txscript.TxSigHashes, + prevOutputFetcher txscript.PrevOutputFetcher, + txinIdx int) (wire.TxWitness, error) } // RequiredTxOut returns the tx out needed to be present on the sweep tx for @@ -313,9 +323,12 @@ func (i *HtlcSecondLevelAnchorInput) RequiredLockTime() (uint32, bool) { // method support spending p2wkh, p2wsh, and also nested p2sh outputs. func (i *HtlcSecondLevelAnchorInput) CraftInputScript(signer Signer, txn *wire.MsgTx, hashCache *txscript.TxSigHashes, - txinIdx int) (*Script, error) { + prevOutputFetcher txscript.PrevOutputFetcher, txinIdx int) (*Script, + error) { - witness, err := i.createWitness(signer, txn, hashCache, txinIdx) + witness, err := i.createWitness( + signer, txn, hashCache, prevOutputFetcher, txinIdx, + ) if err != nil { return nil, err } @@ -335,11 +348,13 @@ func MakeHtlcSecondLevelTimeoutAnchorInput(signedTx *wire.MsgTx, // 2nd timeout transaction. createWitness := func(signer Signer, txn *wire.MsgTx, hashCache *txscript.TxSigHashes, + prevOutputFetcher txscript.PrevOutputFetcher, txinIdx int) (wire.TxWitness, error) { desc := signDetails.SignDesc - desc.SigHashes = txscript.NewTxSigHashes(txn) + desc.SigHashes = txscript.NewTxSigHashes(txn, prevOutputFetcher) desc.InputIndex = txinIdx + desc.PrevOutputFetcher = prevOutputFetcher return SenderHtlcSpendTimeout( signDetails.PeerSig, signDetails.SigHashType, signer, @@ -373,11 +388,13 @@ func MakeHtlcSecondLevelSuccessAnchorInput(signedTx *wire.MsgTx, // success transaction. createWitness := func(signer Signer, txn *wire.MsgTx, hashCache *txscript.TxSigHashes, + prevOutputFetcher txscript.PrevOutputFetcher, txinIdx int) (wire.TxWitness, error) { desc := signDetails.SignDesc desc.SigHashes = hashCache desc.InputIndex = txinIdx + desc.PrevOutputFetcher = prevOutputFetcher return ReceiverHtlcSpendRedeem( signDetails.PeerSig, signDetails.SigHashType, diff --git a/input/musig2.go b/input/musig2.go new file mode 100644 index 0000000000..2fc689d774 --- /dev/null +++ b/input/musig2.go @@ -0,0 +1,231 @@ +package input + +import ( + "bytes" + "crypto/sha256" + "fmt" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" + "github.com/lightningnetwork/lnd/keychain" +) + +const ( + // MuSig2PartialSigSize is the size of a MuSig2 partial signature. + // Because a partial signature is just the s value, this corresponds to + // the length of a scalar. + MuSig2PartialSigSize = 32 +) + +// MuSig2SessionID is a type for a session ID that is just a hash of the MuSig2 +// combined key and the local public nonces. +type MuSig2SessionID [sha256.Size]byte + +// MuSig2Signer is an interface that declares all methods that a MuSig2 +// compatible signer needs to implement. +type MuSig2Signer interface { + // MuSig2CreateSession creates a new MuSig2 signing session using the + // local key identified by the key locator. The complete list of all + // public keys of all signing parties must be provided, including the + // public key of the local signing key. If nonces of other parties are + // already known, they can be submitted as well to reduce the number of + // method calls necessary later on. + MuSig2CreateSession(keychain.KeyLocator, []*btcec.PublicKey, + *MuSig2Tweaks, [][musig2.PubNonceSize]byte) (*MuSig2SessionInfo, + error) + + // MuSig2RegisterNonces registers one or more public nonces of other + // signing participants for a session identified by its ID. This method + // returns true once we have all nonces for all other signing + // participants. + MuSig2RegisterNonces(MuSig2SessionID, + [][musig2.PubNonceSize]byte) (bool, error) + + // MuSig2Sign creates a partial signature using the local signing key + // that was specified when the session was created. This can only be + // called when all public nonces of all participants are known and have + // been registered with the session. If this node isn't responsible for + // combining all the partial signatures, then the cleanup parameter + // should be set, indicating that the session can be removed from memory + // once the signature was produced. + MuSig2Sign(MuSig2SessionID, [sha256.Size]byte, + bool) (*musig2.PartialSignature, error) + + // MuSig2CombineSig combines the given partial signature(s) with the + // local one, if it already exists. Once a partial signature of all + // participants is registered, the final signature will be combined and + // returned. + MuSig2CombineSig(MuSig2SessionID, + []*musig2.PartialSignature) (*schnorr.Signature, bool, error) + + // MuSig2Cleanup removes a session from memory to free up resources. + MuSig2Cleanup(MuSig2SessionID) error +} + +// MuSig2SessionInfo is a struct for keeping track of a signing session +// information in memory. +type MuSig2SessionInfo struct { + // SessionID is the wallet's internal unique ID of this session. The ID + // is the hash over the combined public key and the local public nonces. + SessionID [32]byte + + // PublicNonce contains the public nonce of the local signer session. + PublicNonce [musig2.PubNonceSize]byte + + // CombinedKey is the combined public key with all tweaks applied to it. + CombinedKey *btcec.PublicKey + + // TaprootTweak indicates whether a taproot tweak (BIP-0086 or script + // path) was used. The TaprootInternalKey will only be set if this is + // set to true. + TaprootTweak bool + + // TaprootInternalKey is the raw combined public key without any tweaks + // applied to it. This is only set if TaprootTweak is true. + TaprootInternalKey *btcec.PublicKey + + // HaveAllNonces indicates whether this session already has all nonces + // of all other signing participants registered. + HaveAllNonces bool + + // HaveAllSigs indicates whether this session already has all partial + // signatures of all other signing participants registered. + HaveAllSigs bool +} + +// MuSig2Tweaks is a struct that contains all tweaks that can be applied to a +// MuSig2 combined public key. +type MuSig2Tweaks struct { + // GenericTweaks is a list of normal tweaks to apply to the combined + // public key (and to the private key when signing). + GenericTweaks []musig2.KeyTweakDesc + + // TaprootBIP0086Tweak indicates that the final key should use the + // taproot tweak as defined in BIP 341, with the BIP 86 modification: + // outputKey = internalKey + h_tapTweak(internalKey)*G. + // In this case, the aggregated key before the tweak will be used as the + // internal key. If this is set to true then TaprootTweak will be + // ignored. + TaprootBIP0086Tweak bool + + // TaprootTweak specifies that the final key should use the taproot + // tweak as defined in BIP 341: + // outputKey = internalKey + h_tapTweak(internalKey || scriptRoot). + // In this case, the aggregated key before the tweak will be used as the + // internal key. Will be ignored if TaprootBIP0086Tweak is set to true. + TaprootTweak []byte +} + +// HasTaprootTweak returns true if either a taproot BIP0086 tweak or a taproot +// script root tweak is set. +func (t *MuSig2Tweaks) HasTaprootTweak() bool { + return t.TaprootBIP0086Tweak || len(t.TaprootTweak) > 0 +} + +// ToContextOptions converts the tweak descriptor to context options. +func (t *MuSig2Tweaks) ToContextOptions() []musig2.ContextOption { + var tweakOpts []musig2.ContextOption + if len(t.GenericTweaks) > 0 { + tweakOpts = append(tweakOpts, musig2.WithTweakedContext( + t.GenericTweaks..., + )) + } + + // The BIP0086 tweak and the taproot script tweak are mutually + // exclusive. + if t.TaprootBIP0086Tweak { + tweakOpts = append(tweakOpts, musig2.WithBip86TweakCtx()) + } else if len(t.TaprootTweak) > 0 { + tweakOpts = append(tweakOpts, musig2.WithTaprootTweakCtx( + t.TaprootTweak, + )) + } + + return tweakOpts +} + +// MuSig2CombineKeys combines the given set of public keys into a single +// combined MuSig2 combined public key, applying the given tweaks. +func MuSig2CombineKeys(allSignerPubKeys []*btcec.PublicKey, + tweaks *MuSig2Tweaks) (*musig2.AggregateKey, error) { + + // Convert the tweak options into the appropriate MuSig2 API functional + // options. + var keyAggOpts []musig2.KeyAggOption + switch { + case tweaks.TaprootBIP0086Tweak: + keyAggOpts = append(keyAggOpts, musig2.WithBIP86KeyTweak()) + case len(tweaks.TaprootTweak) > 0: + keyAggOpts = append(keyAggOpts, musig2.WithTaprootKeyTweak( + tweaks.TaprootTweak, + )) + case len(tweaks.GenericTweaks) > 0: + keyAggOpts = append(keyAggOpts, musig2.WithKeyTweaks( + tweaks.GenericTweaks..., + )) + } + + // Then we'll use this information to compute the aggregated public key. + combinedKey, _, _, err := musig2.AggregateKeys( + allSignerPubKeys, true, keyAggOpts..., + ) + return combinedKey, err +} + +// NewMuSig2SessionID returns the unique ID of a MuSig2 session by using the +// combined key and the local public nonces and hashing that data. +func NewMuSig2SessionID(combinedKey *btcec.PublicKey, + publicNonces [musig2.PubNonceSize]byte) MuSig2SessionID { + + // We hash the data to save some bytes in memory. + hash := sha256.New() + _, _ = hash.Write(combinedKey.SerializeCompressed()) + _, _ = hash.Write(publicNonces[:]) + + id := MuSig2SessionID{} + copy(id[:], hash.Sum(nil)) + return id +} + +// SerializePartialSignature encodes the partial signature to a fixed size byte +// array. +func SerializePartialSignature( + sig *musig2.PartialSignature) ([MuSig2PartialSigSize]byte, error) { + + var ( + buf bytes.Buffer + result [MuSig2PartialSigSize]byte + ) + if err := sig.Encode(&buf); err != nil { + return result, fmt.Errorf("error encoding partial signature: "+ + "%v", err) + } + + if buf.Len() != MuSig2PartialSigSize { + return result, fmt.Errorf("invalid partial signature length, "+ + "got %d wanted %d", buf.Len(), MuSig2PartialSigSize) + } + + copy(result[:], buf.Bytes()) + + return result, nil +} + +// DeserializePartialSignature decodes a partial signature from a byte slice. +func DeserializePartialSignature(scalarBytes []byte) (*musig2.PartialSignature, + error) { + + if len(scalarBytes) != MuSig2PartialSigSize { + return nil, fmt.Errorf("invalid partial signature length, got "+ + "%d wanted %d", len(scalarBytes), MuSig2PartialSigSize) + } + + sig := &musig2.PartialSignature{} + if err := sig.Decode(bytes.NewReader(scalarBytes)); err != nil { + return nil, fmt.Errorf("error decoding partial signature: %v", + err) + } + + return sig, nil +} diff --git a/input/script_utils.go b/input/script_utils.go index 6a672a7045..e12b57c43a 100644 --- a/input/script_utils.go +++ b/input/script_utils.go @@ -4,14 +4,12 @@ import ( "bytes" "crypto/sha256" "fmt" - "math/big" - "golang.org/x/crypto/ripemd160" - - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "golang.org/x/crypto/ripemd160" ) var ( @@ -328,7 +326,7 @@ func SenderHTLCScript(senderHtlcKey, receiverHtlcKey, // HTLC to claim the output with knowledge of the revocation private key in the // scenario that the sender of the HTLC broadcasts a previously revoked // commitment transaction. A valid spend requires knowledge of the private key -// that corresponds to their revocation base point and also the private key fro +// that corresponds to their revocation base point and also the private key from // the per commitment point, and a valid signature under the combined public // key. func SenderHtlcSpendRevokeWithKey(signer Signer, signDesc *SignDescriptor, @@ -382,6 +380,7 @@ func IsHtlcSpendRevoke(txIn *wire.TxIn, signDesc *SignDescriptor) ( if len(txIn.Witness) == 3 && bytes.Equal(txIn.Witness[1], revokeKey.SerializeCompressed()) { + return true, nil } @@ -799,7 +798,7 @@ func SecondLevelHtlcScript(revocationKey, delayKey *btcec.PublicKey, // OP_DROP // // OP_ENDIF -// OP_CHECKSIG +// OP_CHECKSIG. func LeaseSecondLevelHtlcScript(revocationKey, delayKey *btcec.PublicKey, csvDelay, cltvExpiry uint32) ([]byte, error) { @@ -864,7 +863,7 @@ func HtlcSpendSuccess(signer Signer, signDesc *SignDescriptor, // As we mutated the transaction, we'll re-calculate the sighashes for // this instance. - signDesc.SigHashes = txscript.NewTxSigHashes(sweepTx) + signDesc.SigHashes = NewTxSigHashesV0Only(sweepTx) // With the proper sequence and version set, we'll now sign the timeout // transaction using the passed signed descriptor. In order to generate @@ -1391,17 +1390,22 @@ func TweakPubKey(basePoint, commitPoint *btcec.PublicKey) *btcec.PublicKey { // TweakPubKeyWithTweak is the exact same as the TweakPubKey function, however // it accepts the raw tweak bytes directly rather than the commitment point. -func TweakPubKeyWithTweak(pubKey *btcec.PublicKey, tweakBytes []byte) *btcec.PublicKey { - curve := btcec.S256() - tweakX, tweakY := curve.ScalarBaseMult(tweakBytes) - - // TODO(roasbeef): check that both passed on curve? - x, y := curve.Add(pubKey.X, pubKey.Y, tweakX, tweakY) - return &btcec.PublicKey{ - X: x, - Y: y, - Curve: curve, - } +func TweakPubKeyWithTweak(pubKey *btcec.PublicKey, + tweakBytes []byte) *btcec.PublicKey { + + var ( + pubKeyJacobian btcec.JacobianPoint + tweakJacobian btcec.JacobianPoint + resultJacobian btcec.JacobianPoint + ) + tweakKey, _ := btcec.PrivKeyFromBytes(tweakBytes) + btcec.ScalarBaseMultNonConst(&tweakKey.Key, &tweakJacobian) + + pubKey.AsJacobian(&pubKeyJacobian) + btcec.AddNonConst(&pubKeyJacobian, &tweakJacobian, &resultJacobian) + + resultJacobian.ToAffine() + return btcec.NewPublicKey(&resultJacobian.X, &resultJacobian.Y) } // TweakPrivKey tweaks the private key of a public base point given a per @@ -1414,15 +1418,16 @@ func TweakPubKeyWithTweak(pubKey *btcec.PublicKey, tweakBytes []byte) *btcec.Pub // * tweakPriv := basePriv + sha256(commitment || basePub) mod N // // Where N is the order of the sub-group. -func TweakPrivKey(basePriv *btcec.PrivateKey, commitTweak []byte) *btcec.PrivateKey { +func TweakPrivKey(basePriv *btcec.PrivateKey, + commitTweak []byte) *btcec.PrivateKey { + // tweakInt := sha256(commitPoint || basePub) - tweakInt := new(big.Int).SetBytes(commitTweak) + tweakScalar := new(btcec.ModNScalar) + tweakScalar.SetByteSlice(commitTweak) - tweakInt = tweakInt.Add(tweakInt, basePriv.D) - tweakInt = tweakInt.Mod(tweakInt, btcec.S256().N) + tweakScalar.Add(&basePriv.Key) - tweakPriv, _ := btcec.PrivKeyFromBytes(btcec.S256(), tweakInt.Bytes()) - return tweakPriv + return &btcec.PrivateKey{Key: *tweakScalar} } // DeriveRevocationPubkey derives the revocation public key given the @@ -1451,28 +1456,46 @@ func TweakPrivKey(basePriv *btcec.PrivateKey, commitTweak []byte) *btcec.Private // (commitSecret * sha256(commitPoint || revocationBase)) mod N // // Where N is the order of the sub-group. -func DeriveRevocationPubkey(revokeBase, commitPoint *btcec.PublicKey) *btcec.PublicKey { +func DeriveRevocationPubkey(revokeBase, + commitPoint *btcec.PublicKey) *btcec.PublicKey { // R = revokeBase * sha256(revocationBase || commitPoint) revokeTweakBytes := SingleTweakBytes(revokeBase, commitPoint) - rX, rY := btcec.S256().ScalarMult(revokeBase.X, revokeBase.Y, - revokeTweakBytes) + revokeTweakScalar := new(btcec.ModNScalar) + revokeTweakScalar.SetByteSlice(revokeTweakBytes) + + var ( + revokeBaseJacobian btcec.JacobianPoint + rJacobian btcec.JacobianPoint + ) + revokeBase.AsJacobian(&revokeBaseJacobian) + btcec.ScalarMultNonConst( + revokeTweakScalar, &revokeBaseJacobian, &rJacobian, + ) // C = commitPoint * sha256(commitPoint || revocationBase) commitTweakBytes := SingleTweakBytes(commitPoint, revokeBase) - cX, cY := btcec.S256().ScalarMult(commitPoint.X, commitPoint.Y, - commitTweakBytes) + commitTweakScalar := new(btcec.ModNScalar) + commitTweakScalar.SetByteSlice(commitTweakBytes) + + var ( + commitPointJacobian btcec.JacobianPoint + cJacobian btcec.JacobianPoint + ) + commitPoint.AsJacobian(&commitPointJacobian) + btcec.ScalarMultNonConst( + commitTweakScalar, &commitPointJacobian, &cJacobian, + ) // Now that we have the revocation point, we add this to their commitment // public key in order to obtain the revocation public key. // // P = R + C - revX, revY := btcec.S256().Add(rX, rY, cX, cY) - return &btcec.PublicKey{ - X: revX, - Y: revY, - Curve: btcec.S256(), - } + var resultJacobian btcec.JacobianPoint + btcec.AddNonConst(&rJacobian, &cJacobian, &resultJacobian) + + resultJacobian.ToAffine() + return btcec.NewPublicKey(&resultJacobian.X, &resultJacobian.Y) } // DeriveRevocationPrivKey derives the revocation private key given a node's @@ -1490,14 +1513,18 @@ func DeriveRevocationPrivKey(revokeBasePriv *btcec.PrivateKey, commitSecret *btcec.PrivateKey) *btcec.PrivateKey { // r = sha256(revokeBasePub || commitPoint) - revokeTweakBytes := SingleTweakBytes(revokeBasePriv.PubKey(), - commitSecret.PubKey()) - revokeTweakInt := new(big.Int).SetBytes(revokeTweakBytes) + revokeTweakBytes := SingleTweakBytes( + revokeBasePriv.PubKey(), commitSecret.PubKey(), + ) + revokeTweakScalar := new(btcec.ModNScalar) + revokeTweakScalar.SetByteSlice(revokeTweakBytes) // c = sha256(commitPoint || revokeBasePub) - commitTweakBytes := SingleTweakBytes(commitSecret.PubKey(), - revokeBasePriv.PubKey()) - commitTweakInt := new(big.Int).SetBytes(commitTweakBytes) + commitTweakBytes := SingleTweakBytes( + commitSecret.PubKey(), revokeBasePriv.PubKey(), + ) + commitTweakScalar := new(btcec.ModNScalar) + commitTweakScalar.SetByteSlice(commitTweakBytes) // Finally to derive the revocation secret key we'll perform the // following operation: @@ -1508,14 +1535,12 @@ func DeriveRevocationPrivKey(revokeBasePriv *btcec.PrivateKey, // P = (G*a)*b + (G*c)*d // P = G*(a*b) + G*(c*d) // P = G*(a*b + c*d) - revokeHalfPriv := revokeTweakInt.Mul(revokeTweakInt, revokeBasePriv.D) - commitHalfPriv := commitTweakInt.Mul(commitTweakInt, commitSecret.D) + revokeHalfPriv := revokeTweakScalar.Mul(&revokeBasePriv.Key) + commitHalfPriv := commitTweakScalar.Mul(&commitSecret.Key) - revocationPriv := revokeHalfPriv.Add(revokeHalfPriv, commitHalfPriv) - revocationPriv = revocationPriv.Mod(revocationPriv, btcec.S256().N) + revocationPriv := revokeHalfPriv.Add(commitHalfPriv) - priv, _ := btcec.PrivKeyFromBytes(btcec.S256(), revocationPriv.Bytes()) - return priv + return &btcec.PrivateKey{Key: *revocationPriv} } // ComputeCommitmentPoint generates a commitment point given a commitment @@ -1523,11 +1548,6 @@ func DeriveRevocationPrivKey(revokeBasePriv *btcec.PrivateKey, // the key-ring and also to used as a tweak to derive new public+private keys // for the state. func ComputeCommitmentPoint(commitSecret []byte) *btcec.PublicKey { - x, y := btcec.S256().ScalarBaseMult(commitSecret) - - return &btcec.PublicKey{ - X: x, - Y: y, - Curve: btcec.S256(), - } + _, pubKey := btcec.PrivKeyFromBytes(commitSecret) + return pubKey } diff --git a/input/script_utils_test.go b/input/script_utils_test.go index e72c8c68c6..a99873af83 100644 --- a/input/script_utils_test.go +++ b/input/script_utils_test.go @@ -7,11 +7,12 @@ import ( "fmt" "testing" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/keychain" "github.com/stretchr/testify/require" ) @@ -22,6 +23,7 @@ import ( // prints debug information to stdout. func assertEngineExecution(t *testing.T, testNum int, valid bool, newEngine func() (*txscript.Engine, error)) { + t.Helper() // Get a new VM to execute. @@ -58,11 +60,11 @@ func assertEngineExecution(t *testing.T, testNum int, valid bool, done, err = vm.Step() if err != nil && valid { - fmt.Println(debugBuf.String()) + t.Log(debugBuf.String()) t.Fatalf("spend test case #%v failed, spend "+ "should be valid: %v", testNum, err) } else if err == nil && !valid && done { - fmt.Println(debugBuf.String()) + t.Log(debugBuf.String()) t.Fatalf("spend test case #%v succeed, spend "+ "should be invalid: %v", testNum, err) } @@ -79,7 +81,7 @@ func assertEngineExecution(t *testing.T, testNum int, valid bool, validity = "valid" } - fmt.Println(debugBuf.String()) + t.Log(debugBuf.String()) t.Fatalf("%v spend test case #%v execution ended with: %v", validity, testNum, vmErr) } @@ -92,13 +94,11 @@ func TestRevocationKeyDerivation(t *testing.T) { // First, we'll generate a commitment point, and a commitment secret. // These will be used to derive the ultimate revocation keys. revocationPreimage := testHdSeed.CloneBytes() - commitSecret, commitPoint := btcec.PrivKeyFromBytes(btcec.S256(), - revocationPreimage) + commitSecret, commitPoint := btcec.PrivKeyFromBytes(revocationPreimage) // With the commitment secrets generated, we'll now create the base // keys we'll use to derive the revocation key from. - basePriv, basePub := btcec.PrivKeyFromBytes(btcec.S256(), - testWalletPrivKey) + basePriv, basePub := btcec.PrivKeyFromBytes(testWalletPrivKey) // With the point and key obtained, we can now derive the revocation // key itself. @@ -121,7 +121,7 @@ func TestTweakKeyDerivation(t *testing.T) { // First, we'll generate a base public key that we'll be "tweaking". baseSecret := testHdSeed.CloneBytes() - basePriv, basePub := btcec.PrivKeyFromBytes(btcec.S256(), baseSecret) + basePriv, basePub := btcec.PrivKeyFromBytes(baseSecret) // With the base key create, we'll now create a commitment point, and // from that derive the bytes we'll used to tweak the base public key. @@ -190,8 +190,7 @@ func TestHTLCSenderSpendValidation(t *testing.T) { // Next we'll the commitment secret for our commitment tx and also the // revocation key that we'll use as well. revokePreimage := testHdSeed.CloneBytes() - commitSecret, commitPoint := btcec.PrivKeyFromBytes(btcec.S256(), - revokePreimage) + commitSecret, commitPoint := btcec.PrivKeyFromBytes(revokePreimage) // Generate a payment preimage to be used below. paymentPreimage := revokePreimage @@ -200,10 +199,8 @@ func TestHTLCSenderSpendValidation(t *testing.T) { // We'll also need some tests keys for alice and bob, and metadata of // the HTLC output. - aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(btcec.S256(), - testWalletPrivKey) - bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(btcec.S256(), - bobsPrivKey) + aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(testWalletPrivKey) + bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(bobsPrivKey) paymentAmt := btcutil.Amount(1 * 10e8) aliceLocalKey := TweakPubKey(aliceKeyPub, commitPoint) @@ -227,7 +224,7 @@ func TestHTLCSenderSpendValidation(t *testing.T) { htlcOutput *wire.TxOut sweepTxSigHashes *txscript.TxSigHashes senderCommitTx, sweepTx *wire.MsgTx - bobRecvrSig *btcec.Signature + bobRecvrSig *ecdsa.Signature bobSigHash txscript.SigHashType ) @@ -283,7 +280,7 @@ func TestHTLCSenderSpendValidation(t *testing.T) { }, ) - sweepTxSigHashes = txscript.NewTxSigHashes(sweepTx) + sweepTxSigHashes = NewTxSigHashesV0Only(sweepTx) bobSigHash = txscript.SigHashAll if confirmed { @@ -309,9 +306,7 @@ func TestHTLCSenderSpendValidation(t *testing.T) { t.Fatalf("unable to generate alice signature: %v", err) } - bobRecvrSig, err = btcec.ParseDERSignature( - bobSig.Serialize(), btcec.S256(), - ) + bobRecvrSig, err = ecdsa.ParseDERSignature(bobSig.Serialize()) if err != nil { t.Fatalf("unable to parse signature: %v", err) } @@ -551,9 +546,14 @@ func TestHTLCSenderSpendValidation(t *testing.T) { sweepTx.TxIn[0].Witness = testCase.witness() newEngine := func() (*txscript.Engine, error) { - return txscript.NewEngine(htlcPkScript, - sweepTx, 0, txscript.StandardVerifyFlags, nil, - nil, int64(paymentAmt)) + return txscript.NewEngine( + htlcPkScript, sweepTx, 0, + txscript.StandardVerifyFlags, nil, nil, + int64(paymentAmt), + txscript.NewCannedPrevOutputFetcher( + htlcPkScript, int64(paymentAmt), + ), + ) } assertEngineExecution(t, i, testCase.valid, newEngine) @@ -592,8 +592,7 @@ func TestHTLCReceiverSpendValidation(t *testing.T) { // Next we'll the commitment secret for our commitment tx and also the // revocation key that we'll use as well. revokePreimage := testHdSeed.CloneBytes() - commitSecret, commitPoint := btcec.PrivKeyFromBytes(btcec.S256(), - revokePreimage) + commitSecret, commitPoint := btcec.PrivKeyFromBytes(revokePreimage) // Generate a payment preimage to be used below. paymentPreimage := revokePreimage @@ -602,10 +601,8 @@ func TestHTLCReceiverSpendValidation(t *testing.T) { // We'll also need some tests keys for alice and bob, and metadata of // the HTLC output. - aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(btcec.S256(), - testWalletPrivKey) - bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(btcec.S256(), - bobsPrivKey) + aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(testWalletPrivKey) + bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(bobsPrivKey) paymentAmt := btcutil.Amount(1 * 10e8) cltvTimeout := uint32(8) @@ -630,7 +627,7 @@ func TestHTLCReceiverSpendValidation(t *testing.T) { htlcOutput *wire.TxOut receiverCommitTx, sweepTx *wire.MsgTx sweepTxSigHashes *txscript.TxSigHashes - aliceSenderSig *btcec.Signature + aliceSenderSig *ecdsa.Signature aliceSigHash txscript.SigHashType ) @@ -682,7 +679,7 @@ func TestHTLCReceiverSpendValidation(t *testing.T) { Value: 1 * 10e8, }, ) - sweepTxSigHashes = txscript.NewTxSigHashes(sweepTx) + sweepTxSigHashes = NewTxSigHashesV0Only(sweepTx) aliceSigHash = txscript.SigHashAll if confirmed { @@ -708,8 +705,8 @@ func TestHTLCReceiverSpendValidation(t *testing.T) { t.Fatalf("unable to generate alice signature: %v", err) } - aliceSenderSig, err = btcec.ParseDERSignature( - aliceSig.Serialize(), btcec.S256(), + aliceSenderSig, err = ecdsa.ParseDERSignature( + aliceSig.Serialize(), ) if err != nil { t.Fatalf("unable to parse signature: %v", err) @@ -970,9 +967,14 @@ func TestHTLCReceiverSpendValidation(t *testing.T) { sweepTx.TxIn[0].Witness = testCase.witness() newEngine := func() (*txscript.Engine, error) { - return txscript.NewEngine(htlcPkScript, + return txscript.NewEngine( + htlcPkScript, sweepTx, 0, txscript.StandardVerifyFlags, nil, - nil, int64(paymentAmt)) + nil, int64(paymentAmt), + txscript.NewCannedPrevOutputFetcher( + htlcPkScript, int64(paymentAmt), + ), + ) } assertEngineExecution(t, i, testCase.valid, newEngine) @@ -993,14 +995,11 @@ func TestSecondLevelHtlcSpends(t *testing.T) { // First we'll set up some initial key state for Alice and Bob that // will be used in the scripts we created below. - aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(btcec.S256(), - testWalletPrivKey) - bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(btcec.S256(), - bobsPrivKey) + aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(testWalletPrivKey) + bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(bobsPrivKey) revokePreimage := testHdSeed.CloneBytes() - commitSecret, commitPoint := btcec.PrivKeyFromBytes( - btcec.S256(), revokePreimage) + commitSecret, commitPoint := btcec.PrivKeyFromBytes(revokePreimage) // As we're modeling this as Bob sweeping the HTLC on-chain from his // commitment transaction after a period of time, we'll be using a @@ -1025,7 +1024,7 @@ func TestSecondLevelHtlcSpends(t *testing.T) { Value: 1 * 10e8, }, ) - sweepTxSigHashes := txscript.NewTxSigHashes(sweepTx) + sweepTxSigHashes := NewTxSigHashesV0Only(sweepTx) // The delay key will be crafted using Bob's public key as the output // we created will be spending from Alice's commitment transaction. @@ -1176,9 +1175,14 @@ func TestSecondLevelHtlcSpends(t *testing.T) { sweepTx.TxIn[0].Witness = testCase.witness() newEngine := func() (*txscript.Engine, error) { - return txscript.NewEngine(htlcPkScript, + return txscript.NewEngine( + htlcPkScript, sweepTx, 0, txscript.StandardVerifyFlags, nil, - nil, int64(htlcAmt)) + nil, int64(htlcAmt), + txscript.NewCannedPrevOutputFetcher( + htlcPkScript, int64(htlcAmt), + ), + ) } assertEngineExecution(t, i, testCase.valid, newEngine) @@ -1204,17 +1208,11 @@ func TestLeaseSecondLevelHtlcSpends(t *testing.T) { // First we'll set up some initial key state for Alice and Bob that // will be used in the scripts we created below. - aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes( - btcec.S256(), testWalletPrivKey, - ) - bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes( - btcec.S256(), bobsPrivKey, - ) + aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(testWalletPrivKey) + bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(bobsPrivKey) revokePreimage := testHdSeed.CloneBytes() - commitSecret, commitPoint := btcec.PrivKeyFromBytes( - btcec.S256(), revokePreimage, - ) + commitSecret, commitPoint := btcec.PrivKeyFromBytes(revokePreimage) // As we're modeling this as Bob sweeping the HTLC on-chain from his // commitment transaction after a period of time, we'll be using a @@ -1237,7 +1235,7 @@ func TestLeaseSecondLevelHtlcSpends(t *testing.T) { Value: 1 * 10e8, }, ) - sweepTxSigHashes := txscript.NewTxSigHashes(sweepTx) + sweepTxSigHashes := NewTxSigHashesV0Only(sweepTx) // The delay key will be crafted using Bob's public key as the output // we created will be spending from Alice's commitment transaction. @@ -1417,9 +1415,14 @@ func TestLeaseSecondLevelHtlcSpends(t *testing.T) { sweepTx.TxIn[0].Witness = testCase.witness() newEngine := func() (*txscript.Engine, error) { - return txscript.NewEngine(htlcPkScript, - sweepTx, 0, txscript.StandardVerifyFlags, nil, - nil, int64(htlcAmt)) + return txscript.NewEngine( + htlcPkScript, sweepTx, 0, + txscript.StandardVerifyFlags, nil, nil, + int64(htlcAmt), + txscript.NewCannedPrevOutputFetcher( + htlcPkScript, int64(htlcAmt), + ), + ) } assertEngineExecution(t, i, testCase.valid, newEngine) @@ -1439,18 +1442,12 @@ func TestLeaseCommmitSpendToSelf(t *testing.T) { // Set up some initial key state for Alice and Bob that will be used in // the scripts we created below. - aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes( - btcec.S256(), testWalletPrivKey, - ) - bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes( - btcec.S256(), bobsPrivKey, - ) + aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(testWalletPrivKey) + bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(bobsPrivKey) // We'll have Bob take the revocation path in some cases. revokePreimage := testHdSeed.CloneBytes() - commitSecret, commitPoint := btcec.PrivKeyFromBytes( - btcec.S256(), revokePreimage, - ) + commitSecret, commitPoint := btcec.PrivKeyFromBytes(revokePreimage) revocationKey := DeriveRevocationPubkey(bobKeyPub, commitPoint) // Construct the script enforced lease to_self commitment transaction @@ -1495,7 +1492,7 @@ func TestLeaseCommmitSpendToSelf(t *testing.T) { // Bob can spend with his revocation key, but not // without the proper tweak. makeWitnessTestCase(t, func() (wire.TxWitness, error) { - sweepTxSigHashes := txscript.NewTxSigHashes(sweepTx) + sweepTxSigHashes := NewTxSigHashesV0Only(sweepTx) signDesc := &SignDescriptor{ KeyDesc: keychain.KeyDescriptor{ PubKey: bobKeyPub, @@ -1517,7 +1514,7 @@ func TestLeaseCommmitSpendToSelf(t *testing.T) { // Bob can spend with his revocation key with the proper // tweak. makeWitnessTestCase(t, func() (wire.TxWitness, error) { - sweepTxSigHashes := txscript.NewTxSigHashes(sweepTx) + sweepTxSigHashes := NewTxSigHashesV0Only(sweepTx) signDesc := &SignDescriptor{ KeyDesc: keychain.KeyDescriptor{ PubKey: bobKeyPub, @@ -1544,7 +1541,7 @@ func TestLeaseCommmitSpendToSelf(t *testing.T) { sweepTx.TxIn[0].Sequence = LockTimeToSequence( false, csvDelay/2, ) - sweepTxSigHashes := txscript.NewTxSigHashes(sweepTx) + sweepTxSigHashes := NewTxSigHashesV0Only(sweepTx) signDesc := &SignDescriptor{ KeyDesc: keychain.KeyDescriptor{ PubKey: aliceKeyPub, @@ -1571,7 +1568,7 @@ func TestLeaseCommmitSpendToSelf(t *testing.T) { sweepTx.TxIn[0].Sequence = LockTimeToSequence( false, csvDelay, ) - sweepTxSigHashes := txscript.NewTxSigHashes(sweepTx) + sweepTxSigHashes := NewTxSigHashesV0Only(sweepTx) signDesc := &SignDescriptor{ KeyDesc: keychain.KeyDescriptor{ PubKey: aliceKeyPub, @@ -1598,7 +1595,7 @@ func TestLeaseCommmitSpendToSelf(t *testing.T) { sweepTx.TxIn[0].Sequence = LockTimeToSequence( false, csvDelay, ) - sweepTxSigHashes := txscript.NewTxSigHashes(sweepTx) + sweepTxSigHashes := NewTxSigHashesV0Only(sweepTx) signDesc := &SignDescriptor{ KeyDesc: keychain.KeyDescriptor{ PubKey: aliceKeyPub, @@ -1622,9 +1619,14 @@ func TestLeaseCommmitSpendToSelf(t *testing.T) { sweepTx.TxIn[0].Witness = testCase.witness() newEngine := func() (*txscript.Engine, error) { - return txscript.NewEngine(commitPkScript, + return txscript.NewEngine( + commitPkScript, sweepTx, 0, txscript.StandardVerifyFlags, nil, - nil, int64(outputVal)) + nil, int64(outputVal), + txscript.NewCannedPrevOutputFetcher( + commitPkScript, int64(outputVal), + ), + ) } assertEngineExecution(t, i, testCase.valid, newEngine) @@ -1639,8 +1641,7 @@ func TestCommitSpendToRemoteConfirmed(t *testing.T) { const outputVal = btcutil.Amount(2 * 10e8) - aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(btcec.S256(), - testWalletPrivKey) + aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(testWalletPrivKey) txid, err := chainhash.NewHash(testHdSeed.CloneBytes()) if err != nil { @@ -1683,7 +1684,7 @@ func TestCommitSpendToRemoteConfirmed(t *testing.T) { // Alice can spend after the a CSV delay has passed. makeWitnessTestCase(t, func() (wire.TxWitness, error) { sweepTx.TxIn[0].Sequence = LockTimeToSequence(false, 1) - sweepTxSigHashes := txscript.NewTxSigHashes(sweepTx) + sweepTxSigHashes := NewTxSigHashesV0Only(sweepTx) signDesc := &SignDescriptor{ KeyDesc: keychain.KeyDescriptor{ @@ -1705,7 +1706,7 @@ func TestCommitSpendToRemoteConfirmed(t *testing.T) { // Alice cannot spend output without sequence set. makeWitnessTestCase(t, func() (wire.TxWitness, error) { sweepTx.TxIn[0].Sequence = wire.MaxTxInSequenceNum - sweepTxSigHashes := txscript.NewTxSigHashes(sweepTx) + sweepTxSigHashes := NewTxSigHashesV0Only(sweepTx) signDesc := &SignDescriptor{ KeyDesc: keychain.KeyDescriptor{ @@ -1729,9 +1730,14 @@ func TestCommitSpendToRemoteConfirmed(t *testing.T) { sweepTx.TxIn[0].Witness = testCase.witness() newEngine := func() (*txscript.Engine, error) { - return txscript.NewEngine(commitPkScript, - sweepTx, 0, txscript.StandardVerifyFlags, nil, - nil, int64(outputVal)) + return txscript.NewEngine( + commitPkScript, sweepTx, 0, + txscript.StandardVerifyFlags, nil, nil, + int64(outputVal), + txscript.NewCannedPrevOutputFetcher( + commitPkScript, int64(outputVal), + ), + ) } assertEngineExecution(t, i, testCase.valid, newEngine) @@ -1750,7 +1756,7 @@ func TestLeaseCommitSpendToRemoteConfirmed(t *testing.T) { ) aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes( - btcec.S256(), testWalletPrivKey, + testWalletPrivKey, ) txid, err := chainhash.NewHash(testHdSeed.CloneBytes()) @@ -1794,7 +1800,7 @@ func TestLeaseCommitSpendToRemoteConfirmed(t *testing.T) { sweepTx.TxIn[0].Sequence = LockTimeToSequence( false, 1, ) - sweepTxSigHashes := txscript.NewTxSigHashes(sweepTx) + sweepTxSigHashes := NewTxSigHashesV0Only(sweepTx) signDesc := &SignDescriptor{ KeyDesc: keychain.KeyDescriptor{ @@ -1819,7 +1825,7 @@ func TestLeaseCommitSpendToRemoteConfirmed(t *testing.T) { makeWitnessTestCase(t, func() (wire.TxWitness, error) { sweepTx.LockTime = leaseExpiry sweepTx.TxIn[0].Sequence = wire.MaxTxInSequenceNum - sweepTxSigHashes := txscript.NewTxSigHashes(sweepTx) + sweepTxSigHashes := NewTxSigHashesV0Only(sweepTx) signDesc := &SignDescriptor{ KeyDesc: keychain.KeyDescriptor{ @@ -1844,7 +1850,7 @@ func TestLeaseCommitSpendToRemoteConfirmed(t *testing.T) { makeWitnessTestCase(t, func() (wire.TxWitness, error) { sweepTx.LockTime = 0 sweepTx.TxIn[0].Sequence = wire.MaxTxInSequenceNum - sweepTxSigHashes := txscript.NewTxSigHashes(sweepTx) + sweepTxSigHashes := NewTxSigHashesV0Only(sweepTx) signDesc := &SignDescriptor{ KeyDesc: keychain.KeyDescriptor{ @@ -1873,6 +1879,9 @@ func TestLeaseCommitSpendToRemoteConfirmed(t *testing.T) { commitPkScript, sweepTx, 0, txscript.StandardVerifyFlags, nil, nil, int64(outputVal), + txscript.NewCannedPrevOutputFetcher( + commitPkScript, int64(outputVal), + ), ) } @@ -1888,8 +1897,7 @@ func TestSpendAnchor(t *testing.T) { const anchorSize = 294 // First we'll set up some initial key state for Alice. - aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(btcec.S256(), - testWalletPrivKey) + aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(testWalletPrivKey) // Create a fake anchor outpoint that we'll use to generate the // sweeping transaction. @@ -1938,7 +1946,7 @@ func TestSpendAnchor(t *testing.T) { // Alice can spend immediately. makeWitnessTestCase(t, func() (wire.TxWitness, error) { sweepTx.TxIn[0].Sequence = wire.MaxTxInSequenceNum - sweepTxSigHashes := txscript.NewTxSigHashes(sweepTx) + sweepTxSigHashes := NewTxSigHashesV0Only(sweepTx) signDesc := &SignDescriptor{ KeyDesc: keychain.KeyDescriptor{ @@ -1978,9 +1986,14 @@ func TestSpendAnchor(t *testing.T) { sweepTx.TxIn[0].Witness = testCase.witness() newEngine := func() (*txscript.Engine, error) { - return txscript.NewEngine(anchorPkScript, + return txscript.NewEngine( + anchorPkScript, sweepTx, 0, txscript.StandardVerifyFlags, nil, - nil, int64(anchorSize)) + nil, int64(anchorSize), + txscript.NewCannedPrevOutputFetcher( + anchorPkScript, int64(anchorSize), + ), + ) } assertEngineExecution(t, i, testCase.valid, newEngine) diff --git a/input/signdescriptor.go b/input/signdescriptor.go index 2aa2af9c20..17008288bf 100644 --- a/input/signdescriptor.go +++ b/input/signdescriptor.go @@ -3,9 +3,10 @@ package input import ( "encoding/binary" "errors" + "fmt" "io" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/keychain" @@ -55,12 +56,30 @@ type SignDescriptor struct { // DoubleTweak, not both. DoubleTweak *btcec.PrivateKey + // TapTweak is a 32-byte value that will be used to derive a taproot + // output public key (or the corresponding private key) from an + // internal key and this tweak. The transformation applied is: + // * outputKey = internalKey + + // tagged_hash("tapTweak", internalKey || tapTweak) + // + // When attempting to sign an output derived via BIP 86, then this + // field should be an empty byte array. + // + // When attempting to sign for the key spend path of an output key that + // commits to an actual script tree, the script root should be used. + TapTweak []byte + // WitnessScript is the full script required to properly redeem the // output. This field should be set to the full script if a p2wsh // output is being signed. For p2wkh it should be set to the hashed // script (PkScript). WitnessScript []byte + // SignMethod specifies how the input should be signed. Depending on the + // selected method, either the TapTweak, WitnessScript or both need to + // be specified. + SignMethod SignMethod + // Output is the target output which should be signed. The PkScript and // Value fields within the output should be properly populated, // otherwise an invalid signature may be generated. @@ -74,11 +93,77 @@ type SignDescriptor struct { // generating the final sighash for signing. SigHashes *txscript.TxSigHashes + // PrevOutputFetcher is an interface that can return the output + // information on all UTXOs that are being spent in this transaction. + // This MUST be set when spending Taproot outputs. + PrevOutputFetcher txscript.PrevOutputFetcher + // InputIndex is the target input within the transaction that should be // signed. InputIndex int } +// SignMethod defines the different ways a signer can sign, given a specific +// input. +type SignMethod uint8 + +const ( + // WitnessV0SignMethod denotes that a SegWit v0 (p2wkh, np2wkh, p2wsh) + // input script should be signed. + WitnessV0SignMethod SignMethod = 0 + + // TaprootKeySpendBIP0086SignMethod denotes that a SegWit v1 (p2tr) + // input should be signed by using the BIP0086 method (commit to + // internal key only). + TaprootKeySpendBIP0086SignMethod SignMethod = 1 + + // TaprootKeySpendSignMethod denotes that a SegWit v1 (p2tr) + // input should be signed by using a given taproot hash to commit to in + // addition to the internal key. + TaprootKeySpendSignMethod SignMethod = 2 + + // TaprootScriptSpendSignMethod denotes that a SegWit v1 (p2tr) input + // should be spent using the script path and that a specific leaf script + // should be signed for. + TaprootScriptSpendSignMethod SignMethod = 3 +) + +// String returns a human-readable representation of the signing method. +func (s SignMethod) String() string { + switch s { + case WitnessV0SignMethod: + return "witness_v0" + case TaprootKeySpendBIP0086SignMethod: + return "taproot_key_spend_bip86" + case TaprootKeySpendSignMethod: + return "taproot_key_spend" + case TaprootScriptSpendSignMethod: + return "taproot_script_spend" + default: + return fmt.Sprintf("unknown<%d>", s) + } +} + +// PkScriptCompatible returns true if the given public key script is compatible +// with the sign method. +func (s SignMethod) PkScriptCompatible(pkScript []byte) bool { + switch s { + // SegWit v0 can be p2wkh, np2wkh, p2wsh. + case WitnessV0SignMethod: + return txscript.IsPayToWitnessPubKeyHash(pkScript) || + txscript.IsPayToWitnessScriptHash(pkScript) || + txscript.IsPayToScriptHash(pkScript) + + case TaprootKeySpendBIP0086SignMethod, TaprootKeySpendSignMethod, + TaprootScriptSpendSignMethod: + + return txscript.IsPayToTaproot(pkScript) + + default: + return false + } +} + // WriteSignDescriptor serializes a SignDescriptor struct into the passed // io.Writer stream. // @@ -159,9 +244,7 @@ func ReadSignDescriptor(r io.Reader, sd *SignDescriptor) error { if err != nil { return err } - sd.KeyDesc.PubKey, err = btcec.ParsePubKey( - pubKeyBytes, btcec.S256(), - ) + sd.KeyDesc.PubKey, err = btcec.ParsePubKey(pubKeyBytes) if err != nil { return err } @@ -196,7 +279,7 @@ func ReadSignDescriptor(r io.Reader, sd *SignDescriptor) error { if len(doubleTweakBytes) == 0 { sd.DoubleTweak = nil } else { - sd.DoubleTweak, _ = btcec.PrivKeyFromBytes(btcec.S256(), doubleTweakBytes) + sd.DoubleTweak, _ = btcec.PrivKeyFromBytes(doubleTweakBytes) } // Only one tweak should ever be set, fail if both are present. diff --git a/input/signdescriptor_test.go b/input/signdescriptor_test.go index 1e81e6ee63..a929e3a166 100644 --- a/input/signdescriptor_test.go +++ b/input/signdescriptor_test.go @@ -5,7 +5,7 @@ import ( "reflect" "testing" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/keychain" @@ -96,7 +96,7 @@ func TestSignDescriptorSerialization(t *testing.T) { for i := 0; i < len(signDescriptors); i++ { // Parse pubkeys for each sign descriptor. sd := &signDescriptors[i] - pubkey, err := btcec.ParsePubKey(keys[i], btcec.S256()) + pubkey, err := btcec.ParsePubKey(keys[i]) if err != nil { t.Fatalf("unable to parse pubkey: %v", err) } diff --git a/input/signer.go b/input/signer.go index 86622638ea..fb3609115f 100644 --- a/input/signer.go +++ b/input/signer.go @@ -10,12 +10,17 @@ import ( // Signer implementations such as hardware wallets, hardware tokens, HSM's, or // simply a regular wallet. type Signer interface { + // MuSig2Signer is an embedded interface to make sure all our signers + // also support MuSig2 signing, so we can forward calls to a remote + // signer as well. + MuSig2Signer + // SignOutputRaw generates a signature for the passed transaction // according to the data within the passed SignDescriptor. // // NOTE: The resulting signature should be void of a sighash byte. - SignOutputRaw(tx *wire.MsgTx, - signDesc *SignDescriptor) (Signature, error) + SignOutputRaw(tx *wire.MsgTx, signDesc *SignDescriptor) (Signature, + error) // ComputeInputScript generates a complete InputIndex for the passed // transaction with the signature as defined within the passed @@ -26,7 +31,8 @@ type Signer interface { // NOTE: This method will ignore any tweak parameters set within the // passed SignDescriptor as it assumes a set of typical script // templates (p2wkh, np2wkh, etc). - ComputeInputScript(tx *wire.MsgTx, signDesc *SignDescriptor) (*Script, error) + ComputeInputScript(tx *wire.MsgTx, signDesc *SignDescriptor) (*Script, + error) } // Script represents any script inputs required to redeem a previous diff --git a/input/size.go b/input/size.go index a8e18f1dd1..9796314cba 100644 --- a/input/size.go +++ b/input/size.go @@ -2,7 +2,9 @@ package input import ( "github.com/btcsuite/btcd/blockchain" + "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcwallet/waddrmgr" ) const ( @@ -47,7 +49,7 @@ const ( // - max-size: 40 bytes UnknownWitnessSize = 1 + 1 + 40 - // P2PKHSize 25 bytes + // P2PKHSize 25 bytes. P2PKHSize = 25 // P2PKHOutputSize 34 bytes @@ -68,7 +70,7 @@ const ( // - pkscript (p2wsh): 34 bytes P2WSHOutputSize = 8 + 1 + P2WSHSize - // P2SHSize 23 bytes + // P2SHSize 23 bytes. P2SHSize = 23 // P2SHOutputSize 32 bytes @@ -77,6 +79,18 @@ const ( // - pkscript (p2sh): 23 bytes P2SHOutputSize = 8 + 1 + P2SHSize + // P2TRSize 34 bytes + // - OP_0: 1 byte + // - OP_DATA: 1 byte (x-only public key length) + // - x-only public key length: 32 bytes + P2TRSize = 34 + + // P2TROutputSize 43 bytes + // - value: 8 bytes + // - var_int: 1 byte (pkscript_length) + // - pkscript (p2tr): 34 bytes + P2TROutputSize = 8 + 1 + P2TRSize + // P2PKHScriptSigSize 108 bytes // - OP_DATA: 1 byte (signature length) // - signature @@ -205,16 +219,16 @@ const ( BaseAnchorCommitmentTxSize = 4 + 1 + FundingInputSize + 3 + 2*CommitmentDelayOutput + 2*CommitmentAnchorOutput + 4 - // BaseAnchorCommitmentTxWeight 900 weight + // BaseAnchorCommitmentTxWeight 900 weight. BaseAnchorCommitmentTxWeight = witnessScaleFactor * BaseAnchorCommitmentTxSize - // CommitWeight 724 weight + // CommitWeight 724 weight. CommitWeight = BaseCommitmentTxWeight + WitnessCommitmentTxWeight - // AnchorCommitWeight 1124 weight + // AnchorCommitWeight 1124 weight. AnchorCommitWeight = BaseAnchorCommitmentTxWeight + WitnessCommitmentTxWeight - // HTLCWeight 172 weight + // HTLCWeight 172 weight. HTLCWeight = witnessScaleFactor * HTLCSize // HtlcTimeoutWeight 663 weight @@ -358,7 +372,7 @@ const ( AcceptedHtlcScriptSize = 3*1 + 20 + 5*1 + 33 + 8*1 + 20 + 4*1 + 33 + 5*1 + 4 + 5*1 - // AcceptedHtlcScriptSizeConfirmed 143 bytes + // AcceptedHtlcScriptSizeConfirmed 143 bytes. AcceptedHtlcScriptSizeConfirmed = AcceptedHtlcScriptSize + HtlcConfirmedScriptOverhead @@ -371,7 +385,7 @@ const ( // - witness_script: (accepted_htlc_script) AcceptedHtlcTimeoutWitnessSize = 1 + 1 + 73 + 1 + 1 + AcceptedHtlcScriptSize - // AcceptedHtlcTimeoutWitnessSizeConfirmed 220 bytes + // AcceptedHtlcTimeoutWitnessSizeConfirmed 220 bytes. AcceptedHtlcTimeoutWitnessSizeConfirmed = 1 + 1 + 73 + 1 + 1 + AcceptedHtlcScriptSizeConfirmed @@ -385,7 +399,7 @@ const ( // - witness_script (accepted_htlc_script) AcceptedHtlcPenaltyWitnessSize = 1 + 1 + 73 + 1 + 33 + 1 + AcceptedHtlcScriptSize - // AcceptedHtlcPenaltyWitnessSizeConfirmed 253 bytes + // AcceptedHtlcPenaltyWitnessSizeConfirmed 253 bytes. AcceptedHtlcPenaltyWitnessSizeConfirmed = 1 + 1 + 73 + 1 + 33 + 1 + AcceptedHtlcScriptSizeConfirmed @@ -448,7 +462,7 @@ const ( // - OP_ENDIF: 1 byte OfferedHtlcScriptSize = 3*1 + 20 + 5*1 + 33 + 10*1 + 33 + 5*1 + 20 + 4*1 - // OfferedHtlcScriptSizeConfirmed 136 bytes + // OfferedHtlcScriptSizeConfirmed 136 bytes. OfferedHtlcScriptSizeConfirmed = OfferedHtlcScriptSize + HtlcConfirmedScriptOverhead @@ -462,7 +476,7 @@ const ( // - witness_script (offered_htlc_script) OfferedHtlcSuccessWitnessSize = 1 + 1 + 73 + 1 + 32 + 1 + OfferedHtlcScriptSize - // OfferedHtlcSuccessWitnessSizeConfirmed 245 bytes + // OfferedHtlcSuccessWitnessSizeConfirmed 245 bytes. OfferedHtlcSuccessWitnessSizeConfirmed = 1 + 1 + 73 + 1 + 32 + 1 + OfferedHtlcScriptSizeConfirmed @@ -497,7 +511,7 @@ const ( // - witness_script (offered_htlc_script) OfferedHtlcPenaltyWitnessSize = 1 + 1 + 73 + 1 + 33 + 1 + OfferedHtlcScriptSize - // OfferedHtlcPenaltyWitnessSizeConfirmed 246 bytes + // OfferedHtlcPenaltyWitnessSizeConfirmed 246 bytes. OfferedHtlcPenaltyWitnessSizeConfirmed = 1 + 1 + 73 + 1 + 33 + 1 + OfferedHtlcScriptSizeConfirmed @@ -519,6 +533,29 @@ const ( // - witness_script_length: 1 byte // - witness_script (anchor_script) AnchorWitnessSize = 1 + 1 + 73 + 1 + AnchorScriptSize + + // TaprootSignatureWitnessSize 65 bytes + // - sigLength: 1 byte + // - sig: 64 bytes + TaprootSignatureWitnessSize = 1 + 64 + + // TaprootKeyPathWitnessSize 66 bytes + // - NumberOfWitnessElements: 1 byte + // - sigLength: 1 byte + // - sig: 64 bytes + TaprootKeyPathWitnessSize = 1 + TaprootSignatureWitnessSize + + // TaprootKeyPathCustomSighashWitnessSize 67 bytes + // - NumberOfWitnessElements: 1 byte + // - sigLength: 1 byte + // - sig: 64 bytes + // - sighashFlag: 1 byte + TaprootKeyPathCustomSighashWitnessSize = TaprootKeyPathWitnessSize + 1 + + // TaprootBaseControlBlockWitnessSize 33 bytes + // - leafVersionAndParity: 1 byte + // - schnorrPubKey: 32 byte + TaprootBaseControlBlockWitnessSize = 33 ) // EstimateCommitTxWeight estimate commitment transaction weight depending on @@ -582,6 +619,52 @@ func (twe *TxWeightEstimator) AddWitnessInput(witnessSize int) *TxWeightEstimato return twe } +// AddTapscriptInput updates the weight estimate to account for an additional +// input spending a segwit v1 pay-to-taproot output using the script path. This +// accepts the total size of the witness for the script leaf that is executed +// and adds the size of the control block to the total witness size. +// +// NOTE: The leaf witness size must be calculated without the byte that accounts +// for the number of witness elements, only the total size of all elements on +// the stack that are consumed by the revealed script should be counted. +func (twe *TxWeightEstimator) AddTapscriptInput(leafWitnessSize int, + tapscript *waddrmgr.Tapscript) *TxWeightEstimator { + + // We add 1 byte for the total number of witness elements. + controlBlockWitnessSize := 1 + TaprootBaseControlBlockWitnessSize + + // 1 byte for the length of the element plus the element itself. + 1 + len(tapscript.RevealedScript) + + 1 + len(tapscript.ControlBlock.InclusionProof) + + twe.inputSize += InputSize + twe.inputWitnessSize += leafWitnessSize + controlBlockWitnessSize + twe.inputCount++ + twe.hasWitness = true + + return twe +} + +// AddTaprootKeySpendInput updates the weight estimate to account for an +// additional input spending a segwit v1 pay-to-taproot output using the key +// spend path. This accepts the sighash type being used since that has an +// influence on the total size of the signature. +func (twe *TxWeightEstimator) AddTaprootKeySpendInput( + hashType txscript.SigHashType) *TxWeightEstimator { + + twe.inputSize += InputSize + + if hashType == txscript.SigHashDefault { + twe.inputWitnessSize += TaprootKeyPathWitnessSize + } else { + twe.inputWitnessSize += TaprootKeyPathCustomSighashWitnessSize + } + + twe.inputCount++ + twe.hasWitness = true + + return twe +} + // AddNestedP2WKHInput updates the weight estimate to account for an additional // input spending a P2SH output with a nested P2WKH redeem script. func (twe *TxWeightEstimator) AddNestedP2WKHInput() *TxWeightEstimator { @@ -639,6 +722,15 @@ func (twe *TxWeightEstimator) AddP2WSHOutput() *TxWeightEstimator { return twe } +// AddP2TROutput updates the weight estimate to account for an additional native +// SegWit v1 P2TR output. +func (twe *TxWeightEstimator) AddP2TROutput() *TxWeightEstimator { + twe.outputSize += P2TROutputSize + twe.outputCount++ + + return twe +} + // AddP2SHOutput updates the weight estimate to account for an additional P2SH // output. func (twe *TxWeightEstimator) AddP2SHOutput() *TxWeightEstimator { diff --git a/input/size_test.go b/input/size_test.go index b2d1e2bee6..c259284e34 100644 --- a/input/size_test.go +++ b/input/size_test.go @@ -1,22 +1,20 @@ package input_test import ( - "math/big" "testing" "github.com/btcsuite/btcd/blockchain" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" - "github.com/stretchr/testify/require" - "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwallet" + "github.com/stretchr/testify/require" ) const ( @@ -38,12 +36,9 @@ var ( testPreimage = make([]byte, 32) // testPubkey is a pubkey used in script size calculation. - testPubkey = &btcec.PublicKey{ - X: &big.Int{}, - Y: &big.Int{}, - } + testPubkey = &btcec.PublicKey{} - testPrivkey, _ = btcec.PrivKeyFromBytes(btcec.S256(), make([]byte, 32)) + testPrivkey, _ = btcec.PrivKeyFromBytes(make([]byte, 32)) testTx = wire.NewMsgTx(2) @@ -939,7 +934,6 @@ func genSuccessTx(chanType channeldb.ChannelType) (*wire.MsgTx, error) { successTx.TxIn[0].Witness = successWitness return successTx, nil - } type txSizeTest struct { diff --git a/input/taproot.go b/input/taproot.go new file mode 100644 index 0000000000..0319228bad --- /dev/null +++ b/input/taproot.go @@ -0,0 +1,107 @@ +package input + +import ( + "fmt" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcwallet/waddrmgr" +) + +const ( + // PubKeyFormatCompressedOdd is the identifier prefix byte for a public + // key whose Y coordinate is odd when serialized in the compressed + // format per section 2.3.4 of + // [SEC1](https://secg.org/sec1-v2.pdf#subsubsection.2.3.4). + // This is copied from the github.com/decred/dcrd/dcrec/secp256k1/v4 to + // avoid needing to directly reference (and by accident pull in + // incompatible crypto primitives) the package. + PubKeyFormatCompressedOdd byte = 0x03 +) + +// NewTxSigHashesV0Only returns a new txscript.TxSigHashes instance that will +// only calculate the sighash midstate values for segwit v0 inputs and can +// therefore never be used for transactions that want to spend segwit v1 +// (taproot) inputs. +func NewTxSigHashesV0Only(tx *wire.MsgTx) *txscript.TxSigHashes { + // The canned output fetcher returns a wire.TxOut instance with the + // given pk script and amount. We can get away with nil since the first + // thing the TxSigHashes constructor checks is the length of the pk + // script and whether it matches taproot output script length. If the + // length doesn't match it assumes v0 inputs only. + nilFetcher := txscript.NewCannedPrevOutputFetcher(nil, 0) + return txscript.NewTxSigHashes(tx, nilFetcher) +} + +// MultiPrevOutFetcher returns a txscript.MultiPrevOutFetcher for the given set +// of inputs. +func MultiPrevOutFetcher(inputs []Input) (*txscript.MultiPrevOutFetcher, error) { + fetcher := txscript.NewMultiPrevOutFetcher(nil) + for _, inp := range inputs { + op := inp.OutPoint() + desc := inp.SignDesc() + + if op == nil { + return nil, fmt.Errorf("missing input outpoint") + } + + if desc == nil || desc.Output == nil { + return nil, fmt.Errorf("missing input utxo information") + } + + fetcher.AddPrevOut(*op, desc.Output) + } + + return fetcher, nil +} + +// TapscriptFullTree creates a waddrmgr.Tapscript for the given internal key and +// tree leaves. +func TapscriptFullTree(internalKey *btcec.PublicKey, + allTreeLeaves ...txscript.TapLeaf) *waddrmgr.Tapscript { + + tree := txscript.AssembleTaprootScriptTree(allTreeLeaves...) + rootHash := tree.RootNode.TapHash() + tapKey := txscript.ComputeTaprootOutputKey(internalKey, rootHash[:]) + + var outputKeyYIsOdd bool + if tapKey.SerializeCompressed()[0] == PubKeyFormatCompressedOdd { + outputKeyYIsOdd = true + } + + return &waddrmgr.Tapscript{ + Type: waddrmgr.TapscriptTypeFullTree, + ControlBlock: &txscript.ControlBlock{ + InternalKey: internalKey, + OutputKeyYIsOdd: outputKeyYIsOdd, + LeafVersion: txscript.BaseLeafVersion, + }, + Leaves: allTreeLeaves, + } +} + +// TapscriptPartialReveal creates a waddrmgr.Tapscript for the given internal +// key and revealed script. +func TapscriptPartialReveal(internalKey *btcec.PublicKey, + revealedLeaf txscript.TapLeaf, + inclusionProof []byte) *waddrmgr.Tapscript { + + controlBlock := &txscript.ControlBlock{ + InternalKey: internalKey, + LeafVersion: txscript.BaseLeafVersion, + InclusionProof: inclusionProof, + } + rootHash := controlBlock.RootHash(revealedLeaf.Script) + tapKey := txscript.ComputeTaprootOutputKey(internalKey, rootHash) + + if tapKey.SerializeCompressed()[0] == PubKeyFormatCompressedOdd { + controlBlock.OutputKeyYIsOdd = true + } + + return &waddrmgr.Tapscript{ + Type: waddrmgr.TapscriptTypePartialReveal, + ControlBlock: controlBlock, + RevealedScript: revealedLeaf.Script, + } +} diff --git a/input/test_utils.go b/input/test_utils.go index 201858b2c8..99b8e050fd 100644 --- a/input/test_utils.go +++ b/input/test_utils.go @@ -2,15 +2,20 @@ package input import ( "bytes" + "crypto/sha256" "encoding/hex" "fmt" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/lightningnetwork/lnd/keychain" ) var ( @@ -74,7 +79,7 @@ func (m *MockSigner) SignOutputRaw(tx *wire.MsgTx, return nil, err } - return btcec.ParseDERSignature(sig[:len(sig)-1], btcec.S256()) + return ecdsa.ParseDERSignature(sig[:len(sig)-1]) } // ComputeInputScript generates a complete InputIndex for the passed transaction @@ -129,6 +134,55 @@ func (m *MockSigner) ComputeInputScript(tx *wire.MsgTx, signDesc *SignDescriptor } } +// MuSig2CreateSession creates a new MuSig2 signing session using the local +// key identified by the key locator. The complete list of all public keys of +// all signing parties must be provided, including the public key of the local +// signing key. If nonces of other parties are already known, they can be +// submitted as well to reduce the number of method calls necessary later on. +func (m *MockSigner) MuSig2CreateSession(keychain.KeyLocator, + []*btcec.PublicKey, *MuSig2Tweaks, + [][musig2.PubNonceSize]byte) (*MuSig2SessionInfo, error) { + + return nil, nil +} + +// MuSig2RegisterNonces registers one or more public nonces of other signing +// participants for a session identified by its ID. This method returns true +// once we have all nonces for all other signing participants. +func (m *MockSigner) MuSig2RegisterNonces(MuSig2SessionID, + [][musig2.PubNonceSize]byte) (bool, error) { + + return false, nil +} + +// MuSig2Sign creates a partial signature using the local signing key +// that was specified when the session was created. This can only be +// called when all public nonces of all participants are known and have +// been registered with the session. If this node isn't responsible for +// combining all the partial signatures, then the cleanup parameter +// should be set, indicating that the session can be removed from memory +// once the signature was produced. +func (m *MockSigner) MuSig2Sign(MuSig2SessionID, + [sha256.Size]byte, bool) (*musig2.PartialSignature, error) { + + return nil, nil +} + +// MuSig2CombineSig combines the given partial signature(s) with the +// local one, if it already exists. Once a partial signature of all +// participants is registered, the final signature will be combined and +// returned. +func (m *MockSigner) MuSig2CombineSig(MuSig2SessionID, + []*musig2.PartialSignature) (*schnorr.Signature, bool, error) { + + return nil, false, nil +} + +// MuSig2Cleanup removes a session from memory to free up resources. +func (m *MockSigner) MuSig2Cleanup(MuSig2SessionID) error { + return nil +} + // findKey searches through all stored private keys and returns one // corresponding to the hashed pubkey if it can be found. The public key may // either correspond directly to the private key or to the private key with a @@ -166,7 +220,7 @@ func pubkeyFromHex(keyHex string) (*btcec.PublicKey, error) { if err != nil { return nil, err } - return btcec.ParsePubKey(bytes, btcec.S256()) + return btcec.ParsePubKey(bytes) } // privkeyFromHex parses a Bitcoin private key from a hex encoded string. @@ -175,7 +229,7 @@ func privkeyFromHex(keyHex string) (*btcec.PrivateKey, error) { if err != nil { return nil, err } - key, _ := btcec.PrivKeyFromBytes(btcec.S256(), bytes) + key, _ := btcec.PrivKeyFromBytes(bytes) return key, nil } diff --git a/input/witnessgen.go b/input/witnessgen.go index 7d0f7fa731..7df388c7c3 100644 --- a/input/witnessgen.go +++ b/input/witnessgen.go @@ -84,7 +84,7 @@ const ( // us to sweep an HTLC output that we extended to a party, but was // never fulfilled. This _is_ the HTLC output directly on our // commitment transaction, and the input to the second-level HTLC - // tiemout transaction. It can only be spent after CLTV expiry, and + // timeout transaction. It can only be spent after CLTV expiry, and // commitment confirmation. HtlcOfferedTimeoutSecondLevelInputConfirmed StandardWitnessType = 15 @@ -175,6 +175,11 @@ const ( // and CLTV locktime as part of the script enforced lease commitment // type. LeaseHtlcAcceptedSuccessSecondLevel StandardWitnessType = 20 + + // TaprootPubKeySpend is a witness type that allows us to spend a + // regular p2tr output that's sent to an output which is under complete + // control of the backing wallet. + TaprootPubKeySpend StandardWitnessType = 21 ) // String returns a human readable version of the target WitnessType. @@ -245,6 +250,9 @@ func (wt StandardWitnessType) String() string { case LeaseHtlcAcceptedSuccessSecondLevel: return "LeaseHtlcAcceptedSuccessSecondLevel" + case TaprootPubKeySpend: + return "TaprootPubKeySpend" + default: return fmt.Sprintf("Unknown WitnessType: %v", uint32(wt)) } @@ -388,6 +396,8 @@ func (wt StandardWitnessType) WitnessGenerator(signer Signer, case WitnessKeyHash: fallthrough + case TaprootPubKeySpend: + fallthrough case NestedWitnessKeyHash: return signer.ComputeInputScript(tx, desc) @@ -405,7 +415,6 @@ func (wt StandardWitnessType) WitnessGenerator(signer Signer, // NOTE: This is part of the WitnessType interface. func (wt StandardWitnessType) SizeUpperBound() (int, bool, error) { switch wt { - // Outputs on a remote commitment transaction that pay directly to us. case CommitSpendNoDelayTweakless: fallthrough @@ -495,6 +504,9 @@ func (wt StandardWitnessType) SizeUpperBound() (int, bool, error) { // The revocation output of a second level output of an HTLC. case HtlcSecondLevelRevoke: return ToLocalPenaltyWitnessSize, false, nil + + case TaprootPubKeySpend: + return TaprootKeyPathCustomSighashWitnessSize, false, nil } return 0, false, fmt.Errorf("unexpected witness type: %v", wt) diff --git a/intercepted_forward.go b/intercepted_forward.go new file mode 100644 index 0000000000..70590d0e41 --- /dev/null +++ b/intercepted_forward.go @@ -0,0 +1,80 @@ +package lnd + +import ( + "errors" + + "github.com/lightningnetwork/lnd/htlcswitch" + "github.com/lightningnetwork/lnd/lntypes" + "github.com/lightningnetwork/lnd/lnwire" +) + +var ( + // ErrCannotResume is returned when an intercepted forward cannot be + // resumed. This is the case in the on-chain resolution flow. + ErrCannotResume = errors.New("cannot resume in the on-chain flow") + + // ErrCannotFail is returned when an intercepted forward cannot be failed. + // This is the case in the on-chain resolution flow. + ErrCannotFail = errors.New("cannot fail in the on-chain flow") + + // ErrPreimageMismatch is returned when the preimage that is specified to + // settle an htlc doesn't match the htlc hash. + ErrPreimageMismatch = errors.New("preimage does not match hash") +) + +// interceptedForward implements the on-chain behavior for the resolution of +// a forwarded htlc. +type interceptedForward struct { + packet *htlcswitch.InterceptedPacket + beacon *preimageBeacon +} + +func newInterceptedForward( + packet *htlcswitch.InterceptedPacket, + beacon *preimageBeacon) *interceptedForward { + + return &interceptedForward{ + beacon: beacon, + packet: packet, + } +} + +// Packet returns the intercepted htlc packet. +func (f *interceptedForward) Packet() htlcswitch.InterceptedPacket { + return *f.packet +} + +// Resume notifies the intention to resume an existing hold forward. This +// basically means the caller wants to resume with the default behavior for this +// htlc which usually means forward it. +func (f *interceptedForward) Resume() error { + return ErrCannotResume +} + +// Fail notifies the intention to fail an existing hold forward with an +// encrypted failure reason. +func (f *interceptedForward) Fail(_ []byte) error { + // We can't actively fail an htlc. The best we could do is abandon the + // resolver, but this wouldn't be a safe operation. There may be a race + // with the preimage beacon supplying a preimage. Therefore we don't + // attempt to fail and just return an error here. + return ErrCannotFail +} + +// FailWithCode notifies the intention to fail an existing hold forward with the +// specified failure code. +func (f *interceptedForward) FailWithCode(_ lnwire.FailCode) error { + return ErrCannotFail +} + +// Settle notifies the intention to settle an existing hold forward with a given +// preimage. +func (f *interceptedForward) Settle(preimage lntypes.Preimage) error { + if !preimage.Matches(f.packet.Hash) { + return ErrPreimageMismatch + } + + // Add preimage to the preimage beacon. The onchain resolver will pick + // up the preimage from the beacon. + return f.beacon.AddPreimages(preimage) +} diff --git a/invoices/interface.go b/invoices/interface.go index 732b39e17d..804817dd82 100644 --- a/invoices/interface.go +++ b/invoices/interface.go @@ -18,4 +18,8 @@ type Payload interface { // CustomRecords returns the custom tlv type records that were parsed // from the payload. CustomRecords() record.CustomSet + + // Metadata returns the additional data that is sent along with the + // payment to the payee. + Metadata() []byte } diff --git a/invoices/invoice_expiry_watcher.go b/invoices/invoice_expiry_watcher.go index 70d73608e4..5038e5e843 100644 --- a/invoices/invoice_expiry_watcher.go +++ b/invoices/invoice_expiry_watcher.go @@ -31,7 +31,7 @@ type invoiceExpiryTs struct { } // Less implements PriorityQueueItem.Less such that the top item in the -// priorty queue will be the one that expires next. +// priority queue will be the one that expires next. func (e invoiceExpiryTs) Less(other queue.PriorityQueueItem) bool { return e.Expiry.Before(other.(*invoiceExpiryTs).Expiry) } @@ -58,10 +58,10 @@ func (b invoiceExpiryHeight) expired(currentHeight, delta uint32) bool { return currentHeight+delta >= b.expiryHeight } -// InvoiceExpiryWatcher handles automatic invoice cancellation of expried +// InvoiceExpiryWatcher handles automatic invoice cancellation of expired // invoices. Upon start InvoiceExpiryWatcher will retrieve all pending (not yet -// settled or canceled) invoices invoices to its watcing queue. When a new -// invoice is added to the InvoiceRegistry, it'll be forarded to the +// settled or canceled) invoices invoices to its watching queue. When a new +// invoice is added to the InvoiceRegistry, it'll be forwarded to the // InvoiceExpiryWatcher and will end up in the watching queue as well. // If any of the watched invoices expire, they'll be removed from the watching // queue and will be cancelled through InvoiceRegistry.CancelInvoice(). @@ -353,6 +353,11 @@ func (ew *InvoiceExpiryWatcher) expireInvoice(hash lntypes.Hash, force bool) { case channeldb.ErrInvoiceAlreadySettled: + case channeldb.ErrInvoiceNotFound: + // It's possible that the user has manually canceled the invoice + // which will then be deleted by the garbage collector resulting + // in an ErrInvoiceNotFound error. + default: log.Errorf("Unable to cancel invoice: %v: %v", hash, err) } @@ -399,7 +404,6 @@ func (ew *InvoiceExpiryWatcher) mainLoop(blockNtfns *chainntnfs.BlockEpochEvent) cancelNext() select { - case newInvoices := <-ew.newInvoices: // Take newly forwarded invoices with higher priority // in order to not block the newInvoices channel. @@ -408,7 +412,6 @@ func (ew *InvoiceExpiryWatcher) mainLoop(blockNtfns *chainntnfs.BlockEpochEvent) default: select { - // Wait until the next invoice expires. case <-ew.nextTimestampExpiry(): cancelNext = ew.cancelNextExpiredInvoice diff --git a/invoices/invoiceregistry.go b/invoices/invoiceregistry.go index c495353308..6505e259e4 100644 --- a/invoices/invoiceregistry.go +++ b/invoices/invoiceregistry.go @@ -101,22 +101,22 @@ func (r *htlcReleaseEvent) Less(other queue.PriorityQueueItem) bool { type InvoiceRegistry struct { sync.RWMutex + nextClientID uint32 // must be used atomically + cdb *channeldb.DB // cfg contains the registry's configuration parameters. cfg *RegistryConfig - clientMtx sync.Mutex - nextClientID uint32 - notificationClients map[uint32]*InvoiceSubscription - singleNotificationClients map[uint32]*SingleInvoiceSubscription + notificationClients map[uint32]*InvoiceSubscription - newSubscriptions chan *InvoiceSubscription - subscriptionCancels chan uint32 + // TODO(yy): use map[lntypes.Hash]*SingleInvoiceSubscription for better + // performance. + singleNotificationClients map[uint32]*SingleInvoiceSubscription - // invoiceEvents is a single channel over which both invoice updates and - // new single invoice subscriptions are carried. - invoiceEvents chan interface{} + // invoiceEvents is a single channel over which invoice updates are + // carried. + invoiceEvents chan *invoiceEvent // subscriptions is a map from a circuit key to a list of subscribers. // It is used for efficient notification of links. @@ -147,9 +147,7 @@ func NewRegistry(cdb *channeldb.DB, expiryWatcher *InvoiceExpiryWatcher, cdb: cdb, notificationClients: make(map[uint32]*InvoiceSubscription), singleNotificationClients: make(map[uint32]*SingleInvoiceSubscription), - newSubscriptions: make(chan *InvoiceSubscription), - subscriptionCancels: make(chan uint32), - invoiceEvents: make(chan interface{}, 100), + invoiceEvents: make(chan *invoiceEvent, 100), hodlSubscriptions: make(map[channeldb.CircuitKey]map[chan<- interface{}]struct{}), hodlReverseSubscriptions: make(map[chan<- interface{}]map[channeldb.CircuitKey]struct{}), cfg: cfg, @@ -160,7 +158,7 @@ func NewRegistry(cdb *channeldb.DB, expiryWatcher *InvoiceExpiryWatcher, } // scanInvoicesOnStart will scan all invoices on start and add active invoices -// to the invoice expirt watcher while also attempting to delete all canceled +// to the invoice expiry watcher while also attempting to delete all canceled // invoices. func (i *InvoiceRegistry) scanInvoicesOnStart() error { var ( @@ -178,8 +176,8 @@ func (i *InvoiceRegistry) scanInvoicesOnStart() error { removable = make([]channeldb.InvoiceDeleteRef, 0) } - scanFunc := func( - paymentHash lntypes.Hash, invoice *channeldb.Invoice) error { + scanFunc := func(paymentHash lntypes.Hash, + invoice *channeldb.Invoice) error { if invoice.IsPending() { expiryRef := makeInvoiceExpiry(paymentHash, invoice) @@ -240,6 +238,8 @@ func (i *InvoiceRegistry) Start() error { return err } + log.Info("InvoiceRegistry starting") + i.wg.Add(1) go i.invoiceEventLoop() @@ -301,61 +301,18 @@ func (i *InvoiceRegistry) invoiceEventLoop() { } select { - // A new invoice subscription for all invoices has just arrived! - // We'll query for any backlog notifications, then add it to the - // set of clients. - case newClient := <-i.newSubscriptions: - log.Infof("New invoice subscription "+ - "client: id=%v", newClient.id) - - // With the backlog notifications delivered (if any), - // we'll add this to our active subscriptions and - // continue. - i.notificationClients[newClient.id] = newClient - - // A client no longer wishes to receive invoice notifications. - // So we'll remove them from the set of active clients. - case clientID := <-i.subscriptionCancels: - log.Infof("Cancelling invoice subscription for "+ - "client=%v", clientID) - - delete(i.notificationClients, clientID) - delete(i.singleNotificationClients, clientID) - - // An invoice event has come in. This can either be an update to - // an invoice or a new single invoice subscriber. Both type of - // events are passed in via the same channel, to make sure that - // subscribers get a consistent view of the event sequence. + // A sub-systems has just modified the invoice state, so we'll + // dispatch notifications to all registered clients. case event := <-i.invoiceEvents: - switch e := event.(type) { - - // A sub-systems has just modified the invoice state, so - // we'll dispatch notifications to all registered - // clients. - case *invoiceEvent: - // For backwards compatibility, do not notify - // all invoice subscribers of cancel and accept - // events. - state := e.invoice.State - if state != channeldb.ContractCanceled && - state != channeldb.ContractAccepted { - - i.dispatchToClients(e) - } - i.dispatchToSingleClients(e) - - // A new single invoice subscription has arrived. Add it - // to the set of clients. It is important to do this in - // sequence with any other invoice events, because an - // initial invoice update has already been sent out to - // the subscriber. - case *SingleInvoiceSubscription: - log.Infof("New single invoice subscription "+ - "client: id=%v, ref=%v", e.id, - e.invoiceRef) - - i.singleNotificationClients[e.id] = e + // For backwards compatibility, do not notify all + // invoice subscribers of cancel and accept events. + state := event.invoice.State + if state != channeldb.ContractCanceled && + state != channeldb.ContractAccepted { + + i.dispatchToClients(event) } + i.dispatchToSingleClients(event) // A new htlc came in for auto-release. case event := <-i.htlcAutoReleaseChan: @@ -386,27 +343,36 @@ func (i *InvoiceRegistry) invoiceEventLoop() { } } -// dispatchToSingleClients passes the supplied event to all notification clients -// that subscribed to all the invoice this event applies to. +// dispatchToSingleClients passes the supplied event to all notification +// clients that subscribed to all the invoice this event applies to. func (i *InvoiceRegistry) dispatchToSingleClients(event *invoiceEvent) { // Dispatch to single invoice subscribers. - for _, client := range i.singleNotificationClients { + clients := i.copySingleClients() + for _, client := range clients { payHash := client.invoiceRef.PayHash() + if payHash == nil || *payHash != event.hash { continue } + if atomic.LoadUint32(&client.canceled) == 1 { + log.Errorf("Client(id=%v) has stopped, skipped "+ + "notification for event(pay_hash=%v)", + client.id, payHash) + continue + } client.notify(event) } } // dispatchToClients passes the supplied event to all notification clients that -// subscribed to all invoices. Add and settle indices are used to make sure that -// clients don't receive duplicate or unwanted events. +// subscribed to all invoices. Add and settle indices are used to make sure +// that clients don't receive duplicate or unwanted events. func (i *InvoiceRegistry) dispatchToClients(event *invoiceEvent) { invoice := event.invoice - for clientID, client := range i.notificationClients { + clients := i.copyClients() + for clientID, client := range clients { // Before we dispatch this event, we'll check // to ensure that this client hasn't already // received this notification in order to @@ -464,7 +430,6 @@ func (i *InvoiceRegistry) dispatchToClients(event *invoiceEvent) { // event is added while we're catching up a new client. invState := event.invoice.State switch { - case invState == channeldb.ContractSettled: client.settleIndex = invoice.SettleIndex @@ -568,6 +533,9 @@ func (i *InvoiceRegistry) deliverSingleBacklogEvents( return err } + log.Debugf("Client(id=%v) delivered single backlog event: payHash=%v", + client.id, payHash) + return nil } @@ -663,7 +631,7 @@ func (i *InvoiceRegistry) cancelSingleHtlc(invoiceRef channeldb.InvoiceRef, updateInvoice := func(invoice *channeldb.Invoice) ( *channeldb.InvoiceUpdateDesc, error) { - // Only allow individual htlc cancelation on open invoices. + // Only allow individual htlc cancellation on open invoices. if invoice.State != channeldb.ContractOpen { log.Debugf("cancelSingleHtlc: invoice %v no longer "+ "open", invoiceRef) @@ -702,7 +670,7 @@ func (i *InvoiceRegistry) cancelSingleHtlc(invoiceRef channeldb.InvoiceRef, htlcState = htlc.State } - // Cancelation is only possible if the htlc wasn't already + // Cancellation is only possible if the htlc wasn't already // resolved. if htlcState != channeldb.HtlcStateAccepted { log.Debugf("cancelSingleHtlc: htlc %v on invoice %v "+ @@ -938,10 +906,10 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash, customRecords: payload.CustomRecords(), mpp: payload.MultiPath(), amp: payload.AMPRecord(), + metadata: payload.Metadata(), } switch { - // If we are accepting spontaneous AMP payments and this payload // contains an AMP record, create an AMP invoice that will be settled // below. @@ -972,12 +940,18 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash, // Execute locked notify exit hop logic. i.Lock() - resolution, err := i.notifyExitHopHtlcLocked(&ctx, hodlChan) + resolution, invoiceToExpire, err := i.notifyExitHopHtlcLocked( + &ctx, hodlChan, + ) i.Unlock() if err != nil { return nil, err } + if invoiceToExpire != nil { + i.expiryWatcher.AddInvoices(invoiceToExpire) + } + switch r := resolution.(type) { // The htlc is held. Start a timer outside the lock if the htlc should // be auto-released, because otherwise a deadlock may happen with the @@ -1008,10 +982,11 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash, } // notifyExitHopHtlcLocked is the internal implementation of NotifyExitHopHtlc -// that should be executed inside the registry lock. +// that should be executed inside the registry lock. The returned invoiceExpiry +// (if not nil) needs to be added to the expiry watcher outside of the lock. func (i *InvoiceRegistry) notifyExitHopHtlcLocked( ctx *invoiceUpdateCtx, hodlChan chan<- interface{}) ( - HtlcResolution, error) { + HtlcResolution, invoiceExpiry, error) { // We'll attempt to settle an invoice matching this rHash on disk (if // one exists). The callback will update the invoice state and/or htlcs. @@ -1047,15 +1022,17 @@ func (i *InvoiceRegistry) notifyExitHopHtlcLocked( return NewFailResolution( ctx.circuitKey, ctx.currentHeight, ResultInvoiceNotFound, - ), nil + ), nil, nil case nil: default: ctx.log(err.Error()) - return nil, err + return nil, nil, err } + var invoiceToExpire invoiceExpiry + switch res := resolution.(type) { case *HtlcFailResolution: // Inspect latest htlc state on the invoice. If it is found, @@ -1149,7 +1126,7 @@ func (i *InvoiceRegistry) notifyExitHopHtlcLocked( case *htlcAcceptResolution: invoiceHtlc, ok := invoice.Htlcs[ctx.circuitKey] if !ok { - return nil, fmt.Errorf("accepted htlc: %v not"+ + return nil, nil, fmt.Errorf("accepted htlc: %v not"+ " present on invoice: %x", ctx.circuitKey, ctx.hash[:]) } @@ -1178,8 +1155,7 @@ func (i *InvoiceRegistry) notifyExitHopHtlcLocked( // possible that we MppTimeout the htlcs, and then our relevant // expiry height could change. if res.outcome == resultAccepted { - expiry := makeInvoiceExpiry(ctx.hash, invoice) - i.expiryWatcher.AddInvoices(expiry) + invoiceToExpire = makeInvoiceExpiry(ctx.hash, invoice) } i.hodlSubscribe(hodlChan, ctx.circuitKey) @@ -1202,7 +1178,7 @@ func (i *InvoiceRegistry) notifyExitHopHtlcLocked( i.notifyClients(ctx.hash, invoice, setID) } - return resolution, nil + return resolution, invoiceToExpire, nil } // SettleHodlInvoice sets the preimage of a hodl invoice. @@ -1281,7 +1257,7 @@ func shouldCancel(state channeldb.ContractState, cancelAccepted bool) bool { } // If the invoice is accepted, we should only cancel if we want to - // force cancelation of accepted invoices. + // force cancellation of accepted invoices. return cancelAccepted } @@ -1404,8 +1380,11 @@ func (i *InvoiceRegistry) notifyClients(hash lntypes.Hash, // invoiceSubscriptionKit defines that are common to both all invoice // subscribers and single invoice subscribers. type invoiceSubscriptionKit struct { - id uint32 - inv *InvoiceRegistry + id uint32 // nolint:structcheck + + // quit is a chan mouted to InvoiceRegistry that signals a shutdown. + quit chan struct{} + ntfnQueue *queue.ConcurrentQueue canceled uint32 // To be used atomically. @@ -1426,7 +1405,7 @@ type InvoiceSubscription struct { // StartingInvoiceIndex field. NewInvoices chan *channeldb.Invoice - // SettledInvoices is a channel that we'll use to send all setted + // SettledInvoices is a channel that we'll use to send all settled // invoices with an invoices index greater than the specified // StartingInvoiceIndex field. SettledInvoices chan *channeldb.Invoice @@ -1464,11 +1443,6 @@ func (i *invoiceSubscriptionKit) Cancel() { return } - select { - case i.inv.subscriptionCancels <- i.id: - case <-i.inv.quit: - } - i.ntfnQueue.Stop() close(i.cancelChan) @@ -1478,7 +1452,7 @@ func (i *invoiceSubscriptionKit) Cancel() { func (i *invoiceSubscriptionKit) notify(event *invoiceEvent) error { select { case i.ntfnQueue.ChanIn() <- event: - case <-i.inv.quit: + case <-i.quit: return ErrShuttingDown } @@ -1499,17 +1473,16 @@ func (i *InvoiceRegistry) SubscribeNotifications( addIndex: addIndex, settleIndex: settleIndex, invoiceSubscriptionKit: invoiceSubscriptionKit{ - inv: i, + quit: i.quit, ntfnQueue: queue.NewConcurrentQueue(20), cancelChan: make(chan struct{}), }, } client.ntfnQueue.Start() - i.clientMtx.Lock() - client.id = i.nextClientID - i.nextClientID++ - i.clientMtx.Unlock() + // Always increment by 1 first, and our client ID will start with 1, + // not 0. + client.id = atomic.AddUint32(&i.nextClientID, 1) // Before we register this new invoice subscription, we'll launch a new // goroutine that will proxy all notifications appended to the end of @@ -1518,6 +1491,7 @@ func (i *InvoiceRegistry) SubscribeNotifications( i.wg.Add(1) go func() { defer i.wg.Done() + defer i.deleteClient(client.id) for { select { @@ -1569,9 +1543,6 @@ func (i *InvoiceRegistry) SubscribeNotifications( } }() - i.Lock() - defer i.Unlock() - // Query the database to see if based on the provided addIndex and // settledIndex we need to deliver any backlog notifications. err := i.deliverBacklogEvents(client) @@ -1579,11 +1550,13 @@ func (i *InvoiceRegistry) SubscribeNotifications( return nil, err } - select { - case i.newSubscriptions <- client: - case <-i.quit: - return nil, ErrShuttingDown - } + log.Infof("New invoice subscription client: id=%v", client.id) + + i.Lock() + // With the backlog notifications delivered (if any), we'll add this to + // our active subscriptions. + i.notificationClients[client.id] = client + i.Unlock() return client, nil } @@ -1596,7 +1569,7 @@ func (i *InvoiceRegistry) SubscribeSingleInvoice( client := &SingleInvoiceSubscription{ Updates: make(chan *channeldb.Invoice), invoiceSubscriptionKit: invoiceSubscriptionKit{ - inv: i, + quit: i.quit, ntfnQueue: queue.NewConcurrentQueue(20), cancelChan: make(chan struct{}), }, @@ -1604,10 +1577,9 @@ func (i *InvoiceRegistry) SubscribeSingleInvoice( } client.ntfnQueue.Start() - i.clientMtx.Lock() - client.id = i.nextClientID - i.nextClientID++ - i.clientMtx.Unlock() + // Always increment by 1 first, and our client ID will start with 1, + // not 0. + client.id = atomic.AddUint32(&i.nextClientID, 1) // Before we register this new invoice subscription, we'll launch a new // goroutine that will proxy all notifications appended to the end of @@ -1616,6 +1588,7 @@ func (i *InvoiceRegistry) SubscribeSingleInvoice( i.wg.Add(1) go func() { defer i.wg.Done() + defer i.deleteClient(client.id) for { select { @@ -1644,22 +1617,17 @@ func (i *InvoiceRegistry) SubscribeSingleInvoice( } }() - // Within the lock, we both query the invoice state and pass the client - // subscription to the invoiceEvents channel. This is to make sure that - // the client receives a consistent stream of events. - i.Lock() - defer i.Unlock() - err := i.deliverSingleBacklogEvents(client) if err != nil { return nil, err } - select { - case i.invoiceEvents <- client: - case <-i.quit: - return nil, ErrShuttingDown - } + log.Infof("New single invoice subscription client: id=%v, ref=%v", + client.id, client.invoiceRef) + + i.Lock() + i.singleNotificationClients[client.id] = client + i.Unlock() return client, nil } @@ -1724,3 +1692,40 @@ func (i *InvoiceRegistry) HodlUnsubscribeAll(subscriber chan<- interface{}) { delete(i.hodlReverseSubscriptions, subscriber) } + +// copySingleClients copies i.SingleInvoiceSubscription inside a lock. This is +// useful when we need to iterate the map to send notifications. +func (i *InvoiceRegistry) copySingleClients() map[uint32]*SingleInvoiceSubscription { + i.RLock() + defer i.RUnlock() + + clients := make(map[uint32]*SingleInvoiceSubscription) + for k, v := range i.singleNotificationClients { + clients[k] = v + } + return clients +} + +// copyClients copies i.notificationClients inside a lock. This is useful when +// we need to iterate the map to send notifications. +func (i *InvoiceRegistry) copyClients() map[uint32]*InvoiceSubscription { + i.RLock() + defer i.RUnlock() + + clients := make(map[uint32]*InvoiceSubscription) + for k, v := range i.notificationClients { + clients[k] = v + } + return clients +} + +// deleteClient removes a client by its ID inside a lock. Noop if the client is +// not found. +func (i *InvoiceRegistry) deleteClient(clientID uint32) { + i.Lock() + defer i.Unlock() + + log.Infof("Cancelling invoice subscription for client=%v", clientID) + delete(i.notificationClients, clientID) + delete(i.singleNotificationClients, clientID) +} diff --git a/invoices/invoiceregistry_test.go b/invoices/invoiceregistry_test.go index b968fe073f..dff66a7ce7 100644 --- a/invoices/invoiceregistry_test.go +++ b/invoices/invoiceregistry_test.go @@ -183,10 +183,10 @@ func TestSettleInvoice(t *testing.T) { // Try to cancel. err = ctx.registry.CancelInvoice(testInvoicePaymentHash) if err != channeldb.ErrInvoiceAlreadySettled { - t.Fatal("expected cancelation of a settled invoice to fail") + t.Fatal("expected cancellation of a settled invoice to fail") } - // As this is a direct sette, we expect nothing on the hodl chan. + // As this is a direct settle, we expect nothing on the hodl chan. select { case <-hodlChan: t.Fatal("unexpected resolution") @@ -325,10 +325,10 @@ func testCancelInvoice(t *testing.T, gc bool) { require.Equal(t, testCurrentHeight, failResolution.AcceptHeight) } -// TestCancelInvoice tests cancelation of an invoice and related notifications. +// TestCancelInvoice tests cancellation of an invoice and related notifications. func TestCancelInvoice(t *testing.T) { // Test cancellation both with garbage collection (meaning that canceled - // invoice will be deleted) and without (meain it'll be kept). + // invoice will be deleted) and without (meaning it'll be kept). t.Run("garbage collect", func(t *testing.T) { testCancelInvoice(t, true) }) @@ -507,7 +507,7 @@ func TestSettleHoldInvoice(t *testing.T) { // Try to cancel. err = registry.CancelInvoice(testInvoicePaymentHash) if err == nil { - t.Fatal("expected cancelation of a settled invoice to fail") + t.Fatal("expected cancellation of a settled invoice to fail") } } @@ -1074,7 +1074,7 @@ func TestOldInvoiceRemovalOnStart(t *testing.T) { i := 0 for paymentHash, invoice := range existingInvoices.expiredInvoices { - // Mark half of the invoices as settled, the other hald as + // Mark half of the invoices as settled, the other half as // canceled. if i%2 == 0 { invoice.State = channeldb.ContractSettled @@ -1225,7 +1225,7 @@ func testHeightExpiryWithRegistry(t *testing.T, numParts int, settle bool) { } // If we did not settle the invoice before its expiry, we now expect - // a cancelation. + // a cancellation. expectedState := channeldb.ContractSettled if !settle { expectedState = channeldb.ContractCanceled diff --git a/invoices/test_utils_test.go b/invoices/test_utils_test.go index 21b262d0e8..3d8842cb2d 100644 --- a/invoices/test_utils_test.go +++ b/invoices/test_utils_test.go @@ -12,7 +12,8 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/chainntnfs" @@ -29,6 +30,7 @@ type mockPayload struct { mpp *record.MPP amp *record.AMP customRecords record.CustomSet + metadata []byte } func (p *mockPayload) MultiPath() *record.MPP { @@ -49,6 +51,10 @@ func (p *mockPayload) CustomRecords() record.CustomSet { return p.customRecords } +func (p *mockPayload) Metadata() []byte { + return p.metadata +} + const ( testHtlcExpiry = uint32(5) @@ -69,10 +75,11 @@ var ( testInvoicePaymentHash = testInvoicePreimage.Hash() testPrivKeyBytes, _ = hex.DecodeString( - "e126f68f7eafcc8b74f54d269fe206be715000f94dac067d1c04a8ca3b2db734") + "e126f68f7eafcc8b74f54d269fe206be715000f94dac067d1c04a8ca3b2d" + + "b734", + ) - testPrivKey, _ = btcec.PrivKeyFromBytes( - btcec.S256(), testPrivKeyBytes) + testPrivKey, _ = btcec.PrivKeyFromBytes(testPrivKeyBytes) testInvoiceDescription = "coffee" @@ -83,9 +90,7 @@ var ( testMessageSigner = zpay32.MessageSigner{ SignCompact: func(msg []byte) ([]byte, error) { hash := chainhash.HashB(msg) - sig, err := btcec.SignCompact( - btcec.S256(), testPrivKey, hash, true, - ) + sig, err := ecdsa.SignCompact(testPrivKey, hash, true) if err != nil { return nil, fmt.Errorf("can't sign the message: %v", err) } diff --git a/invoices/update.go b/invoices/update.go index 77bb0dee57..e1a2469aca 100644 --- a/invoices/update.go +++ b/invoices/update.go @@ -1,6 +1,7 @@ package invoices import ( + "encoding/hex" "errors" "github.com/lightningnetwork/lnd/amp" @@ -22,6 +23,7 @@ type invoiceUpdateCtx struct { customRecords record.CustomSet mpp *record.MPP amp *record.AMP + metadata []byte } // invoiceRef returns an identifier that can be used to lookup or update the @@ -52,9 +54,16 @@ func (i invoiceUpdateCtx) setID() *[32]byte { // log logs a message specific to this update context. func (i *invoiceUpdateCtx) log(s string) { + // Don't use %x in the log statement below, because it doesn't + // distinguish between nil and empty metadata. + metadata := "" + if i.metadata != nil { + metadata = hex.EncodeToString(i.metadata) + } + log.Debugf("Invoice%v: %v, amt=%v, expiry=%v, circuit=%v, mpp=%v, "+ - "amp=%v", i.invoiceRef(), s, i.amtPaid, i.expiry, i.circuitKey, - i.mpp, i.amp) + "amp=%v, metadata=%v", i.invoiceRef(), s, i.amtPaid, i.expiry, + i.circuitKey, i.mpp, i.amp, metadata) } // failRes is a helper function which creates a failure resolution with @@ -128,7 +137,6 @@ func updateMpp(ctx *invoiceUpdateCtx, // Reject HTLCs to AMP invoices if they are missing an AMP payload, and // HTLCs to MPP invoices if they have an AMP payload. switch { - case inv.Terms.Features.RequiresFeature(lnwire.AMPRequired) && ctx.amp == nil: @@ -286,7 +294,7 @@ type HTLCPreimages = map[channeldb.CircuitKey]lntypes.Preimage // verifies that all derived child hashes match the payment hashes of the HTLCs // in the set. This method is meant to be called after receiving the full amount // committed to via mpp_total_msat. This method will return a fail resolution if -// any of the child hashes fail to matche theire corresponding HTLCs. +// any of the child hashes fail to match their corresponding HTLCs. func reconstructAMPPreimages(ctx *invoiceUpdateCtx, htlcSet HTLCSet) (HTLCPreimages, *HtlcFailResolution) { diff --git a/keychain/bench_test.go b/keychain/bench_test.go index 5c04eac51b..bd0442ad77 100644 --- a/keychain/bench_test.go +++ b/keychain/bench_test.go @@ -3,7 +3,7 @@ package keychain import ( "testing" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/stretchr/testify/require" ) diff --git a/keychain/btcwallet.go b/keychain/btcwallet.go index 65c23b78ba..3e041bc2eb 100644 --- a/keychain/btcwallet.go +++ b/keychain/btcwallet.go @@ -4,7 +4,8 @@ import ( "crypto/sha256" "fmt" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/wallet" @@ -391,12 +392,16 @@ func (b *BtcWalletKeyRing) ECDH(keyDesc KeyDescriptor, return [32]byte{}, err } - s := &btcec.PublicKey{} - x, y := btcec.S256().ScalarMult(pub.X, pub.Y, privKey.D.Bytes()) - s.X = x - s.Y = y + var ( + pubJacobian btcec.JacobianPoint + s btcec.JacobianPoint + ) + pub.AsJacobian(&pubJacobian) - h := sha256.Sum256(s.SerializeCompressed()) + btcec.ScalarMultNonConst(&privKey.Key, &pubJacobian, &s) + s.ToAffine() + sPubKey := btcec.NewPublicKey(&s.X, &s.Y) + h := sha256.Sum256(sPubKey.SerializeCompressed()) return h, nil } @@ -406,7 +411,7 @@ func (b *BtcWalletKeyRing) ECDH(keyDesc KeyDescriptor, // // NOTE: This is part of the keychain.MessageSignerRing interface. func (b *BtcWalletKeyRing) SignMessage(keyLoc KeyLocator, - msg []byte, doubleHash bool) (*btcec.Signature, error) { + msg []byte, doubleHash bool) (*ecdsa.Signature, error) { privKey, err := b.DerivePrivKey(KeyDescriptor{ KeyLocator: keyLoc, @@ -421,7 +426,7 @@ func (b *BtcWalletKeyRing) SignMessage(keyLoc KeyLocator, } else { digest = chainhash.HashB(msg) } - return privKey.Sign(digest) + return ecdsa.Sign(privKey, digest), nil } // SignMessageCompact signs the given message, single or double SHA256 hashing @@ -445,5 +450,5 @@ func (b *BtcWalletKeyRing) SignMessageCompact(keyLoc KeyLocator, } else { digest = chainhash.HashB(msg) } - return btcec.SignCompact(btcec.S256(), privKey, digest, true) + return ecdsa.SignCompact(privKey, digest, true) } diff --git a/keychain/derivation.go b/keychain/derivation.go index b10bb27330..c7cc0089e7 100644 --- a/keychain/derivation.go +++ b/keychain/derivation.go @@ -3,7 +3,8 @@ package keychain import ( "fmt" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" ) const ( @@ -84,12 +85,11 @@ const ( // p2p level (BOLT-0008). KeyFamilyNodeKey KeyFamily = 6 - // KeyFamilyStaticBackup is the family of keys that will be used to - // derive keys that we use to encrypt and decrypt our set of static - // backups. These backups may either be stored within watch towers for - // a payment, or self stored on disk in a single file containing all - // the static channel backups. - KeyFamilyStaticBackup KeyFamily = 7 + // KeyFamilyBaseEncryption is the family of keys that will be used to + // derive keys that we use to encrypt and decrypt any general blob data + // like static channel backups and the TLS private key. Often used when + // encrypting files on disk. + KeyFamilyBaseEncryption KeyFamily = 7 // KeyFamilyTowerSession is the family of keys that will be used to // derive session keys when negotiating sessions with watchtowers. The @@ -115,7 +115,7 @@ var VersionZeroKeyFamilies = []KeyFamily{ KeyFamilyDelayBase, KeyFamilyRevocationRoot, KeyFamilyNodeKey, - KeyFamilyStaticBackup, + KeyFamilyBaseEncryption, KeyFamilyTowerSession, KeyFamilyTowerID, } @@ -209,7 +209,7 @@ type MessageSignerRing interface { // SignMessage signs the given message, single or double SHA256 hashing // it first, with the private key described in the key locator. SignMessage(keyLoc KeyLocator, msg []byte, - doubleHash bool) (*btcec.Signature, error) + doubleHash bool) (*ecdsa.Signature, error) // SignMessageCompact signs the given message, single or double SHA256 // hashing it first, with the private key described in the key locator @@ -232,7 +232,7 @@ type SingleKeyMessageSigner interface { // SignMessage signs the given message, single or double SHA256 hashing // it first, with the wrapped private key. - SignMessage(message []byte, doubleHash bool) (*btcec.Signature, error) + SignMessage(message []byte, doubleHash bool) (*ecdsa.Signature, error) // SignMessageCompact signs the given message, single or double SHA256 // hashing it first, with the wrapped private key and returns the diff --git a/keychain/ecdh.go b/keychain/ecdh.go index 3d20c2d2a4..028e2d40d4 100644 --- a/keychain/ecdh.go +++ b/keychain/ecdh.go @@ -3,7 +3,7 @@ package keychain import ( "crypto/sha256" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" ) // NewPubKeyECDH wraps the given key of the key ring so it adheres to the @@ -72,10 +72,16 @@ func (p *PrivKeyECDH) PubKey() *btcec.PublicKey { // // NOTE: This is part of the SingleKeyECDH interface. func (p *PrivKeyECDH) ECDH(pub *btcec.PublicKey) ([32]byte, error) { - s := &btcec.PublicKey{} - s.X, s.Y = btcec.S256().ScalarMult(pub.X, pub.Y, p.PrivKey.D.Bytes()) + var ( + pubJacobian btcec.JacobianPoint + s btcec.JacobianPoint + ) + pub.AsJacobian(&pubJacobian) - return sha256.Sum256(s.SerializeCompressed()), nil + btcec.ScalarMultNonConst(&p.PrivKey.Key, &pubJacobian, &s) + s.ToAffine() + sPubKey := btcec.NewPublicKey(&s.X, &s.Y) + return sha256.Sum256(sPubKey.SerializeCompressed()), nil } var _ SingleKeyECDH = (*PubKeyECDH)(nil) diff --git a/keychain/interface_test.go b/keychain/interface_test.go index 3cd345e347..106cf700df 100644 --- a/keychain/interface_test.go +++ b/keychain/interface_test.go @@ -8,17 +8,16 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcwallet/snacl" "github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/wallet" "github.com/btcsuite/btcwallet/walletdb" + _ "github.com/btcsuite/btcwallet/walletdb/bdb" // Required in order to create the default database. "github.com/davecgh/go-spew/spew" "github.com/stretchr/testify/require" - - _ "github.com/btcsuite/btcwallet/walletdb/bdb" // Required in order to create the default database. ) var ( @@ -377,7 +376,7 @@ func TestSecretKeyRingDerivation(t *testing.T) { // We'll try again, but this time with an // unknown public key. _, pub := btcec.PrivKeyFromBytes( - btcec.S256(), testHDSeed[:], + testHDSeed[:], ) keyDesc.PubKey = pub diff --git a/keychain/signer.go b/keychain/signer.go index 9986edadd6..9605e72ec1 100644 --- a/keychain/signer.go +++ b/keychain/signer.go @@ -1,7 +1,8 @@ package keychain import ( - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/btcsuite/btcd/chaincfg/chainhash" ) @@ -30,7 +31,7 @@ func (p *PubKeyMessageSigner) KeyLocator() KeyLocator { } func (p *PubKeyMessageSigner) SignMessage(message []byte, - doubleHash bool) (*btcec.Signature, error) { + doubleHash bool) (*ecdsa.Signature, error) { return p.digestSigner.SignMessage(p.keyLoc, message, doubleHash) } @@ -64,7 +65,7 @@ func (p *PrivKeyMessageSigner) KeyLocator() KeyLocator { } func (p *PrivKeyMessageSigner) SignMessage(msg []byte, - doubleHash bool) (*btcec.Signature, error) { + doubleHash bool) (*ecdsa.Signature, error) { var digest []byte if doubleHash { @@ -72,7 +73,7 @@ func (p *PrivKeyMessageSigner) SignMessage(msg []byte, } else { digest = chainhash.HashB(msg) } - return p.privKey.Sign(digest) + return ecdsa.Sign(p.privKey, digest), nil } func (p *PrivKeyMessageSigner) SignMessageCompact(msg []byte, @@ -84,7 +85,7 @@ func (p *PrivKeyMessageSigner) SignMessageCompact(msg []byte, } else { digest = chainhash.HashB(msg) } - return btcec.SignCompact(btcec.S256(), p.privKey, digest, true) + return ecdsa.SignCompact(p.privKey, digest, true) } var _ SingleKeyMessageSigner = (*PubKeyMessageSigner)(nil) diff --git a/kvdb/backend.go b/kvdb/backend.go index 014260b2f1..7d4cbc36fd 100644 --- a/kvdb/backend.go +++ b/kvdb/backend.go @@ -5,7 +5,9 @@ package kvdb import ( "context" + "crypto/sha256" "encoding/binary" + "encoding/hex" "fmt" "io/ioutil" "os" @@ -245,7 +247,20 @@ func updateLastCompactionDate(dbFile string) error { func GetTestBackend(path, name string) (Backend, func(), error) { empty := func() {} - if TestBackend == BoltBackendName { + switch { + case PostgresBackend: + key := filepath.Join(path, name) + keyHash := sha256.Sum256([]byte(key)) + + f, err := NewPostgresFixture("test_" + hex.EncodeToString(keyHash[:])) + if err != nil { + return nil, func() {}, err + } + return f.DB(), func() { + _ = f.DB().Close() + }, nil + + case TestBackend == BoltBackendName: db, err := GetBoltBackend(&BoltBackendConfig{ DBPath: path, DBFileName: name, @@ -256,7 +271,8 @@ func GetTestBackend(path, name string) (Backend, func(), error) { return nil, nil, err } return db, empty, nil - } else if TestBackend == EtcdBackendName { + + case TestBackend == EtcdBackendName: etcdConfig, cancel, err := StartEtcdTestBackend(path, 0, 0, "") if err != nil { return nil, empty, err @@ -265,6 +281,7 @@ func GetTestBackend(path, name string) (Backend, func(), error) { EtcdBackendName, context.TODO(), etcdConfig, ) return backend, cancel, err + } return nil, nil, fmt.Errorf("unknown backend") diff --git a/kvdb/bolt_compact.go b/kvdb/bolt_compact.go index 7da51b3d37..766616f07b 100644 --- a/kvdb/bolt_compact.go +++ b/kvdb/bolt_compact.go @@ -8,6 +8,7 @@ package kvdb import ( + "encoding/hex" "fmt" "os" "path" @@ -79,7 +80,7 @@ func (cmd *compacter) execute() (int64, int64, error) { return 0, 0, fmt.Errorf("could not start compaction, "+ "destination folder %s only has %d bytes of free disk "+ "space available while we need at least %d for worst-"+ - "case compaction", destFolder, freeSpace, initialSize) + "case compaction", destFolder, freeSpace, uint64(marginSize)) } // Open source database. We open it in read only mode to avoid (and fix) @@ -219,7 +220,8 @@ func (cmd *compacter) walk(db *bbolt.DB, walkFn walkFunc) error { return tx.ForEach(func(name []byte, b *bbolt.Bucket) error { // This will log the top level buckets only to give the // user some sense of progress. - log.Debugf("Compacting top level bucket %s", name) + log.Debugf("Compacting top level bucket '%s'", + LoggableKeyName(name)) return cmd.walkBucket( b, nil, name, nil, b.Sequence(), walkFn, @@ -228,6 +230,30 @@ func (cmd *compacter) walk(db *bbolt.DB, walkFn walkFunc) error { }) } +// LoggableKeyName returns a printable name of the given key. +func LoggableKeyName(key []byte) string { + strKey := string(key) + if hasSpecialChars(strKey) { + return hex.EncodeToString(key) + } + + return strKey +} + +// hasSpecialChars returns true if any of the characters in the given string +// cannot be printed. +func hasSpecialChars(s string) bool { + for _, b := range s { + if !(b >= 'a' && b <= 'z') && !(b >= 'A' && b <= 'Z') && + !(b >= '0' && b <= '9') && b != '-' && b != '_' { + + return true + } + } + + return false +} + // walkBucket recursively walks through a bucket. func (cmd *compacter) walkBucket(b *bbolt.Bucket, keyPath [][]byte, k, v []byte, seq uint64, fn walkFunc) error { diff --git a/kvdb/etcd/config.go b/kvdb/etcd/config.go index 8798bcb60d..03cdb489fd 100644 --- a/kvdb/etcd/config.go +++ b/kvdb/etcd/config.go @@ -30,6 +30,8 @@ type Config struct { CollectStats bool `long:"collect_stats" description:"Whether to collect etcd commit stats."` + MaxMsgSize int `long:"max_msg_size" description:"The maximum message size in bytes that we may send to etcd."` + // SingleWriter should be set to true if we intend to only allow a // single writer to the database at a time. SingleWriter bool @@ -59,6 +61,7 @@ func (c *Config) CloneWithSubNamespace(subNamespace string) *Config { KeyFile: c.KeyFile, InsecureSkipVerify: c.InsecureSkipVerify, CollectStats: c.CollectStats, + MaxMsgSize: c.MaxMsgSize, SingleWriter: c.SingleWriter, } } @@ -79,6 +82,7 @@ func (c *Config) CloneWithSingleWriter() *Config { KeyFile: c.KeyFile, InsecureSkipVerify: c.InsecureSkipVerify, CollectStats: c.CollectStats, + MaxMsgSize: c.MaxMsgSize, SingleWriter: true, } } diff --git a/kvdb/etcd/db.go b/kvdb/etcd/db.go index 315359ecda..119816c85c 100644 --- a/kvdb/etcd/db.go +++ b/kvdb/etcd/db.go @@ -141,7 +141,7 @@ func newEtcdBackend(ctx context.Context, cfg Config) (*db, error) { DialTimeout: etcdConnectionTimeout, Username: cfg.User, Password: cfg.Pass, - MaxCallSendMsgSize: 16384*1024 - 1, + MaxCallSendMsgSize: cfg.MaxMsgSize, } if !cfg.DisableTLS { diff --git a/kvdb/etcd/embed.go b/kvdb/etcd/embed.go index 0df2f804f3..107fca1d34 100644 --- a/kvdb/etcd/embed.go +++ b/kvdb/etcd/embed.go @@ -68,7 +68,7 @@ func NewEmbeddedEtcdInstance(path string, clientPort, peerPort uint16, cfg.Dir = path // To ensure that we can submit large transactions. - cfg.MaxTxnOps = 8192 + cfg.MaxTxnOps = 16384 cfg.MaxRequestBytes = 16384 * 1024 cfg.Logger = "zap" if logFile != "" { @@ -109,6 +109,7 @@ func NewEmbeddedEtcdInstance(path string, clientPort, peerPort uint16, Host: "http://" + clientURL, InsecureSkipVerify: true, Namespace: defaultNamespace, + MaxMsgSize: int(cfg.MaxRequestBytes), } return connConfig, func() { diff --git a/kvdb/etcd/readwrite_bucket.go b/kvdb/etcd/readwrite_bucket.go index 790074332d..16fc892a7d 100644 --- a/kvdb/etcd/readwrite_bucket.go +++ b/kvdb/etcd/readwrite_bucket.go @@ -17,6 +17,9 @@ type readWriteBucket struct { // appropriate prefix to prefix the key. id []byte + // key is the bucket key. + key []byte + // tx holds the parent transaction. tx *readWriteTx } @@ -25,8 +28,9 @@ type readWriteBucket struct { // and bucket id. func newReadWriteBucket(tx *readWriteTx, key, id []byte) *readWriteBucket { return &readWriteBucket{ - id: id, - tx: tx, + id: id, + key: key, + tx: tx, } } @@ -66,6 +70,31 @@ func (b *readWriteBucket) ForEach(cb func(k, v []byte) error) error { return nil } +// ForAll is an optimized version of ForEach for the case when we know we will +// fetch all (or almost all) items. +// +// NOTE: ForAll differs from ForEach in that no additional queries can +// be executed within the callback. +func (b *readWriteBucket) ForAll(cb func(k, v []byte) error) error { + // When we opened this bucket, we fetched the bucket key using the STM + // which put a revision "lock" in the read set. We can leverage this + // by incrementing the revision on the bucket, making any transaction + // retry that'd touch this same bucket. This way we can safely read all + // keys from the bucket and not cache them in the STM. + // To increment the bucket's revision, we simply put in the bucket key + // value again (which is idempotent if the bucket has just been created). + b.tx.stm.Put(string(b.key), string(b.id)) + + // TODO(bhandras): page size should be configurable in ForAll. + return b.tx.stm.FetchRangePaginatedRaw( + string(b.id), 1000, + func(kv KV) error { + key, val := getKeyVal(&kv) + return cb(key, val) + }, + ) +} + // Get returns the value for the given key. Returns nil if the key does // not exist in this bucket. func (b *readWriteBucket) Get(key []byte) []byte { diff --git a/kvdb/etcd/stm.go b/kvdb/etcd/stm.go index 3a3d20233b..94a401f4b9 100644 --- a/kvdb/etcd/stm.go +++ b/kvdb/etcd/stm.go @@ -81,6 +81,11 @@ type STM interface { // Prefetch prefetches the passed keys and prefixes. For prefixes it'll // fetch the whole range. Prefetch(keys []string, prefix []string) + + // FetchRangePaginatedRaw will fetch the range with the passed prefix up + // to the passed limit per page. + FetchRangePaginatedRaw(prefix string, limit int64, + cb func(kv KV) error) error } // CommitError is used to check if there was an error @@ -589,6 +594,52 @@ func (ws writeSet) puts() []v3.Op { return puts } +// FetchRangePaginatedRaw will fetch the range with the passed prefix up to the +// passed limit per page. +func (s *stm) FetchRangePaginatedRaw(prefix string, limit int64, + cb func(kv KV) error) error { + + s.callCount++ + + opts := []v3.OpOption{ + v3.WithSort(v3.SortByKey, v3.SortAscend), + v3.WithRange(v3.GetPrefixRangeEnd(prefix)), + v3.WithLimit(limit), + } + + key := prefix + for { + resp, err := s.client.Get( + s.options.ctx, key, append(opts, s.getOpts...)..., + ) + if err != nil { + return DatabaseError{ + msg: "stm.fetch() failed", + err: err, + } + } + + // Fill the read set with key/values returned. + for _, kv := range resp.Kvs { + err := cb(KV{string(kv.Key), string(kv.Value)}) + + if err != nil { + return err + } + } + + // We've reached the range end. + if !resp.More { + break + } + + // Continue from the page end + "\x00". + key = string(resp.Kvs[len(resp.Kvs)-1].Key) + "\x00" + } + + return nil +} + // fetch is a helper to fetch key/value given options. If a value is returned // then fetch will try to fix the STM's snapshot revision (if not already set). // We'll also cache the returned key/value in the read set. diff --git a/kvdb/interface.go b/kvdb/interface.go index 6fe944394e..8924830245 100644 --- a/kvdb/interface.go +++ b/kvdb/interface.go @@ -109,6 +109,12 @@ type ExtendedRBucket interface { // Prefetch will attempt to prefetch all values under a path. Prefetch(paths ...[]string) + + // ForAll is an optimized version of ForEach. + // + // NOTE: ForAll differs from ForEach in that no additional queries can + // be executed within the callback. + ForAll(func(k, v []byte) error) error } // Prefetch will attempt to prefetch all values under a path from the passed @@ -119,6 +125,16 @@ func Prefetch(b RBucket, paths ...[]string) { } } +// ForAll is an optimized version of ForEach with the limitation that no +// additional queries can be executed within the callback. +func ForAll(b RBucket, cb func(k, v []byte) error) error { + if bucket, ok := b.(ExtendedRBucket); ok { + return bucket.ForAll(cb) + } + + return b.ForEach(cb) +} + // RootBucket is a wrapper to ExtendedRTx.RootBucket which does nothing if // the implementation doesn't have ExtendedRTx. func RootBucket(t RTx) RBucket { diff --git a/kvdb/kvdb_no_postgres.go b/kvdb/kvdb_no_postgres.go new file mode 100644 index 0000000000..740f6a2769 --- /dev/null +++ b/kvdb/kvdb_no_postgres.go @@ -0,0 +1,20 @@ +//go:build !kvdb_postgres +// +build !kvdb_postgres + +package kvdb + +import ( + "errors" + + "github.com/lightningnetwork/lnd/kvdb/postgres" +) + +const PostgresBackend = false + +func NewPostgresFixture(dbName string) (postgres.Fixture, error) { + return nil, errors.New("postgres backend not available") +} + +func StartEmbeddedPostgres() (func() error, error) { + return nil, errors.New("postgres backend not available") +} diff --git a/kvdb/kvdb_postgres.go b/kvdb/kvdb_postgres.go new file mode 100644 index 0000000000..b0d2edee85 --- /dev/null +++ b/kvdb/kvdb_postgres.go @@ -0,0 +1,16 @@ +//go:build kvdb_postgres +// +build kvdb_postgres + +package kvdb + +import "github.com/lightningnetwork/lnd/kvdb/postgres" + +const PostgresBackend = true + +func NewPostgresFixture(dbName string) (postgres.Fixture, error) { + return postgres.NewFixture(dbName) +} + +func StartEmbeddedPostgres() (func() error, error) { + return postgres.StartEmbeddedPostgres() +} diff --git a/kvdb/postgres/config.go b/kvdb/postgres/config.go index 55c0134e38..6bf6ced3d5 100644 --- a/kvdb/postgres/config.go +++ b/kvdb/postgres/config.go @@ -4,6 +4,7 @@ import "time" // Config holds postgres configuration data. type Config struct { - Dsn string `long:"dsn" description:"Database connection string."` - Timeout time.Duration `long:"timeout" description:"Database connection timeout. Set to zero to disable."` + Dsn string `long:"dsn" description:"Database connection string."` + Timeout time.Duration `long:"timeout" description:"Database connection timeout. Set to zero to disable."` + MaxConnections int `long:"maxconnections" description:"The maximum number of open connections to the database. Set to zero for unlimited."` } diff --git a/kvdb/postgres/db.go b/kvdb/postgres/db.go index 0db46b6dfc..7badd14339 100644 --- a/kvdb/postgres/db.go +++ b/kvdb/postgres/db.go @@ -1,3 +1,4 @@ +//go:build kvdb_postgres // +build kvdb_postgres package postgres @@ -12,7 +13,6 @@ import ( "time" "github.com/btcsuite/btcwallet/walletdb" - _ "github.com/jackc/pgx/v4/stdlib" ) const ( @@ -57,6 +57,14 @@ type db struct { // Enforce db implements the walletdb.DB interface. var _ walletdb.DB = (*db)(nil) +// Global set of database connections. +var dbConns *dbConnSet + +// Init initializes the global set of database connections. +func Init(maxConnections int) { + dbConns = newDbConnSet(maxConnections) +} + // newPostgresBackend returns a db object initialized with the passed backend // config. If postgres connection cannot be estabished, then returns error. func newPostgresBackend(ctx context.Context, config *Config, prefix string) ( @@ -66,7 +74,11 @@ func newPostgresBackend(ctx context.Context, config *Config, prefix string) ( return nil, errors.New("empty postgres prefix") } - dbConn, err := sql.Open("pgx", config.Dsn) + if dbConns == nil { + return nil, errors.New("db connection set not initialized") + } + + dbConn, err := dbConns.Open(config.Dsn) if err != nil { return nil, err } @@ -151,8 +163,15 @@ func (db *db) getPrefixedTableName(table string) string { func catchPanic(f func() error) (err error) { defer func() { if r := recover(); r != nil { - err = r.(error) - log.Criticalf("Caught unhandled error: %v", err) + log.Criticalf("Caught unhandled error: %v", r) + + switch data := r.(type) { + case error: + err = data + + default: + err = errors.New(fmt.Sprintf("%v", data)) + } } }() @@ -237,5 +256,7 @@ func (db *db) Copy(w io.Writer) error { // Close cleanly shuts down the database and syncs all data. // This function is part of the walletdb.Db interface implementation. func (db *db) Close() error { - return db.db.Close() + log.Infof("Closing database %v", db.prefix) + + return dbConns.Close(db.cfg.Dsn) } diff --git a/kvdb/postgres/db_conn_set.go b/kvdb/postgres/db_conn_set.go new file mode 100644 index 0000000000..062a977dc7 --- /dev/null +++ b/kvdb/postgres/db_conn_set.go @@ -0,0 +1,89 @@ +package postgres + +import ( + "database/sql" + "fmt" + "sync" + + _ "github.com/jackc/pgx/v4/stdlib" +) + +// dbConn stores the actual connection and a user count. +type dbConn struct { + db *sql.DB + count int +} + +// dbConnSet stores a set of connections. +type dbConnSet struct { + dbConn map[string]*dbConn + maxConnections int + + sync.Mutex +} + +// newDbConnSet initializes a new set of connections. +func newDbConnSet(maxConnections int) *dbConnSet { + return &dbConnSet{ + dbConn: make(map[string]*dbConn), + maxConnections: maxConnections, + } +} + +// Open opens a new database connection. If a connection already exists for the +// given dsn, the existing connection is returned. +func (d *dbConnSet) Open(dsn string) (*sql.DB, error) { + d.Lock() + defer d.Unlock() + + if dbConn, ok := d.dbConn[dsn]; ok { + dbConn.count++ + + return dbConn.db, nil + } + + db, err := sql.Open("pgx", dsn) + if err != nil { + return nil, err + } + + // Limit maximum number of open connections. This is useful to prevent + // the server from running out of connections and returning an error. + // With this client-side limit in place, lnd will wait for a connection + // to become available. + if d.maxConnections != 0 { + db.SetMaxOpenConns(d.maxConnections) + } + + d.dbConn[dsn] = &dbConn{ + db: db, + count: 1, + } + + return db, nil +} + +// Close closes the connection with the given dsn. If there are still other +// users of the same connection, this function does nothing. +func (d *dbConnSet) Close(dsn string) error { + d.Lock() + defer d.Unlock() + + dbConn, ok := d.dbConn[dsn] + if !ok { + return fmt.Errorf("connection not found: %v", dsn) + } + + // Reduce user count. + dbConn.count-- + + // Do not close if there are other users. + if dbConn.count > 0 { + return nil + } + + // Close connection. + delete(d.dbConn, dsn) + + return dbConn.db.Close() +} diff --git a/kvdb/postgres/db_test.go b/kvdb/postgres/db_test.go index faf853dd24..84c2b65038 100644 --- a/kvdb/postgres/db_test.go +++ b/kvdb/postgres/db_test.go @@ -1,3 +1,4 @@ +//go:build kvdb_postgres // +build kvdb_postgres package postgres @@ -14,15 +15,19 @@ import ( // TestInterface performs all interfaces tests for this database driver. func TestInterface(t *testing.T) { - f := NewFixture(t) - defer f.Cleanup() + stop, err := StartEmbeddedPostgres() + require.NoError(t, err) + defer stop() + + f, err := NewFixture("") + require.NoError(t, err) // dbType is the database type name for this driver. const dbType = "postgres" ctx := context.Background() cfg := &Config{ - Dsn: testDsn, + Dsn: f.Dsn, } walletdbtest.TestInterface(t, dbType, ctx, cfg, prefix) @@ -30,17 +35,19 @@ func TestInterface(t *testing.T) { // TestPanic tests recovery from panic conditions. func TestPanic(t *testing.T) { - f := NewFixture(t) - defer f.Cleanup() + stop, err := StartEmbeddedPostgres() + require.NoError(t, err) + defer stop() - d := f.NewBackend() + f, err := NewFixture("") + require.NoError(t, err) - err := d.(*db).Update(func(tx walletdb.ReadWriteTx) error { + err = f.Db.(*db).Update(func(tx walletdb.ReadWriteTx) error { bucket, err := tx.CreateTopLevelBucket([]byte("test")) require.NoError(t, err) // Stop database server. - f.Cleanup() + stop() // Keep trying to get data until Get panics because the // connection is lost. diff --git a/kvdb/postgres/driver.go b/kvdb/postgres/driver.go index 601c0195c4..1792eb6529 100644 --- a/kvdb/postgres/driver.go +++ b/kvdb/postgres/driver.go @@ -1,3 +1,4 @@ +//go:build kvdb_postgres // +build kvdb_postgres package postgres diff --git a/kvdb/postgres/fixture.go b/kvdb/postgres/fixture.go index 8b3ecae730..94356c31a8 100644 --- a/kvdb/postgres/fixture.go +++ b/kvdb/postgres/fixture.go @@ -1,90 +1,132 @@ +//go:build kvdb_postgres // +build kvdb_postgres package postgres import ( "context" + "crypto/rand" "database/sql" - "testing" + "encoding/hex" + "fmt" + "strings" + "time" "github.com/btcsuite/btcwallet/walletdb" embeddedpostgres "github.com/fergusstrange/embedded-postgres" - "github.com/stretchr/testify/require" ) const ( - testDsn = "postgres://postgres:postgres@localhost:9876/postgres?sslmode=disable" - prefix = "test" + testDsnTemplate = "postgres://postgres:postgres@localhost:9876/%v?sslmode=disable" + prefix = "test" ) -func clearTestDb(t *testing.T) { - dbConn, err := sql.Open("pgx", testDsn) - require.NoError(t, err) - - _, err = dbConn.ExecContext(context.Background(), "DROP SCHEMA IF EXISTS public CASCADE;") - require.NoError(t, err) +func getTestDsn(dbName string) string { + return fmt.Sprintf(testDsnTemplate, dbName) } -func openTestDb(t *testing.T) *db { - clearTestDb(t) +var testPostgres *embeddedpostgres.EmbeddedPostgres - db, err := newPostgresBackend( - context.Background(), - &Config{ - Dsn: testDsn, - }, - prefix, - ) - require.NoError(t, err) - - return db -} +const testMaxConnections = 50 -type fixture struct { - t *testing.T - tempDir string - postgres *embeddedpostgres.EmbeddedPostgres -} +// StartEmbeddedPostgres starts an embedded postgres instance. This only needs +// to be done once, because NewFixture will create random new databases on every +// call. It returns a stop closure that stops the database if called. +func StartEmbeddedPostgres() (func() error, error) { + Init(testMaxConnections) -func NewFixture(t *testing.T) *fixture { postgres := embeddedpostgres.NewDatabase( embeddedpostgres.DefaultConfig(). Port(9876)) err := postgres.Start() - require.NoError(t, err) - - return &fixture{ - t: t, - postgres: postgres, + if err != nil { + return nil, err } + + testPostgres = postgres + + return testPostgres.Stop, nil } -func (b *fixture) Cleanup() { - b.postgres.Stop() +// NewFixture returns a new postgres test database. The database name is +// randomly generated. +func NewFixture(dbName string) (*fixture, error) { + if dbName == "" { + // Create random database name. + randBytes := make([]byte, 8) + _, err := rand.Read(randBytes) + if err != nil { + return nil, err + } + + dbName = "test_" + hex.EncodeToString(randBytes) + } + + // Create database if it doesn't exist yet. + dbConn, err := sql.Open("pgx", getTestDsn("postgres")) + if err != nil { + return nil, err + } + defer dbConn.Close() + + _, err = dbConn.ExecContext( + context.Background(), "CREATE DATABASE "+dbName, + ) + if err != nil && !strings.Contains(err.Error(), "already exists") { + return nil, err + } + + // Open database + dsn := getTestDsn(dbName) + db, err := newPostgresBackend( + context.Background(), + &Config{ + Dsn: dsn, + Timeout: time.Minute, + }, + prefix, + ) + if err != nil { + return nil, err + } + + return &fixture{ + Dsn: dsn, + Db: db, + }, nil } -func (b *fixture) NewBackend() walletdb.DB { - clearTestDb(b.t) - db := openTestDb(b.t) +type fixture struct { + Dsn string + Db walletdb.DB +} - return db +func (b *fixture) DB() walletdb.DB { + return b.Db } -func (b *fixture) Dump() map[string]interface{} { - dbConn, err := sql.Open("pgx", testDsn) - require.NoError(b.t, err) +// Dump returns the raw contents of the database. +func (b *fixture) Dump() (map[string]interface{}, error) { + dbConn, err := sql.Open("pgx", b.Dsn) + if err != nil { + return nil, err + } rows, err := dbConn.Query( "SELECT tablename FROM pg_catalog.pg_tables WHERE schemaname='public'", ) - require.NoError(b.t, err) + if err != nil { + return nil, err + } var tables []string for rows.Next() { var table string err := rows.Scan(&table) - require.NoError(b.t, err) + if err != nil { + return nil, err + } tables = append(tables, table) } @@ -93,10 +135,14 @@ func (b *fixture) Dump() map[string]interface{} { for _, table := range tables { rows, err := dbConn.Query("SELECT * FROM " + table) - require.NoError(b.t, err) + if err != nil { + return nil, err + } cols, err := rows.Columns() - require.NoError(b.t, err) + if err != nil { + return nil, err + } colCount := len(cols) var tableRows []map[string]interface{} @@ -108,7 +154,9 @@ func (b *fixture) Dump() map[string]interface{} { } err := rows.Scan(valuePtrs...) - require.NoError(b.t, err) + if err != nil { + return nil, err + } tableData := make(map[string]interface{}) for i, v := range values { @@ -127,5 +175,5 @@ func (b *fixture) Dump() map[string]interface{} { result[table] = tableRows } - return result + return result, nil } diff --git a/kvdb/postgres/fixture_interface.go b/kvdb/postgres/fixture_interface.go new file mode 100644 index 0000000000..7a1f8b9e90 --- /dev/null +++ b/kvdb/postgres/fixture_interface.go @@ -0,0 +1,8 @@ +package postgres + +import "github.com/btcsuite/btcwallet/walletdb" + +type Fixture interface { + DB() walletdb.DB + Dump() (map[string]interface{}, error) +} diff --git a/kvdb/postgres/no_db.go b/kvdb/postgres/no_db.go new file mode 100644 index 0000000000..edac449e41 --- /dev/null +++ b/kvdb/postgres/no_db.go @@ -0,0 +1,6 @@ +//go:build !kvdb_postgres +// +build !kvdb_postgres + +package postgres + +func Init(maxConnections int) {} diff --git a/kvdb/postgres/readwrite_bucket.go b/kvdb/postgres/readwrite_bucket.go index b0fb57833f..933769919f 100644 --- a/kvdb/postgres/readwrite_bucket.go +++ b/kvdb/postgres/readwrite_bucket.go @@ -1,3 +1,4 @@ +//go:build kvdb_postgres // +build kvdb_postgres package postgres @@ -74,10 +75,12 @@ func (b *readWriteBucket) Get(key []byte) []byte { } var value *[]byte - err := b.tx.QueryRow( + row, cancel := b.tx.QueryRow( "SELECT value FROM "+b.table+" WHERE "+parentSelector(b.id)+ " AND key=$1", key, - ).Scan(&value) + ) + defer cancel() + err := row.Scan(&value) switch { case err == sql.ErrNoRows: @@ -105,10 +108,12 @@ func (b *readWriteBucket) NestedReadWriteBucket( } var id int64 - err := b.tx.QueryRow( + row, cancel := b.tx.QueryRow( "SELECT id FROM "+b.table+" WHERE "+parentSelector(b.id)+ " AND key=$1 AND value IS NULL", key, - ).Scan(&id) + ) + defer cancel() + err := row.Scan(&id) switch { case err == sql.ErrNoRows: @@ -138,10 +143,12 @@ func (b *readWriteBucket) CreateBucket(key []byte) ( value *[]byte id int64 ) - err := b.tx.QueryRow( + row, cancel := b.tx.QueryRow( "SELECT id,value FROM "+b.table+" WHERE "+parentSelector(b.id)+ " AND key=$1", key, - ).Scan(&id, &value) + ) + defer cancel() + err := row.Scan(&id, &value) switch { case err == sql.ErrNoRows: @@ -158,10 +165,12 @@ func (b *readWriteBucket) CreateBucket(key []byte) ( // Bucket does not yet exist, so create it. Postgres will generate a // bucket id for the new bucket. - err = b.tx.QueryRow( + row, cancel = b.tx.QueryRow( "INSERT INTO "+b.table+" (parent_id, key) "+ "VALUES($1, $2) RETURNING id", b.id, key, - ).Scan(&id) + ) + defer cancel() + err = row.Scan(&id) if err != nil { return nil, err } @@ -186,19 +195,23 @@ func (b *readWriteBucket) CreateBucketIfNotExists(key []byte) ( value *[]byte id int64 ) - err := b.tx.QueryRow( + row, cancel := b.tx.QueryRow( "SELECT id,value FROM "+b.table+" WHERE "+parentSelector(b.id)+ " AND key=$1", key, - ).Scan(&id, &value) + ) + defer cancel() + err := row.Scan(&id, &value) switch { // Bucket does not yet exist, so create it now. Postgres will generate a // bucket id for the new bucket. case err == sql.ErrNoRows: - err = b.tx.QueryRow( + row, cancel := b.tx.QueryRow( "INSERT INTO "+b.table+" (parent_id, key) "+ - "VALUES($1, $2) RETURNING id", b.id, key). - Scan(&id) + "VALUES($1, $2) RETURNING id", b.id, key, + ) + defer cancel() + err := row.Scan(&id) if err != nil { return nil, err } @@ -314,10 +327,12 @@ func (b *readWriteBucket) Delete(key []byte) error { // Check to see if a bucket with this key exists. var dummy int - err := b.tx.QueryRow( + row, cancel := b.tx.QueryRow( "SELECT 1 FROM "+b.table+" WHERE "+parentSelector(b.id)+ " AND key=$1 AND value IS NULL", key, - ).Scan(&dummy) + ) + defer cancel() + err := row.Scan(&dummy) switch { // No bucket exists, proceed to deletion of the key. case err == sql.ErrNoRows: @@ -394,11 +409,13 @@ func (b *readWriteBucket) Sequence() uint64 { } var seq int64 - err := b.tx.QueryRow( + row, cancel := b.tx.QueryRow( "SELECT sequence FROM "+b.table+" WHERE id=$1 "+ "AND sequence IS NOT NULL", b.id, - ).Scan(&seq) + ) + defer cancel() + err := row.Scan(&seq) switch { case err == sql.ErrNoRows: @@ -410,3 +427,36 @@ func (b *readWriteBucket) Sequence() uint64 { return uint64(seq) } + +// Prefetch will attempt to prefetch all values under a path from the passed +// bucket. +func (b *readWriteBucket) Prefetch(paths ...[]string) {} + +// ForAll is an optimized version of ForEach with the limitation that no +// additional queries can be executed within the callback. +func (b *readWriteBucket) ForAll(cb func(k, v []byte) error) error { + rows, cancel, err := b.tx.Query( + "SELECT key, value FROM " + b.table + " WHERE " + + parentSelector(b.id) + " ORDER BY key", + ) + if err != nil { + return err + } + defer cancel() + + for rows.Next() { + var key, value []byte + + err := rows.Scan(&key, &value) + if err != nil { + return err + } + + err = cb(key, value) + if err != nil { + return err + } + } + + return nil +} diff --git a/kvdb/postgres/readwrite_cursor.go b/kvdb/postgres/readwrite_cursor.go index 8b95f64700..80e321e061 100644 --- a/kvdb/postgres/readwrite_cursor.go +++ b/kvdb/postgres/readwrite_cursor.go @@ -1,3 +1,4 @@ +//go:build kvdb_postgres // +build kvdb_postgres package postgres @@ -30,11 +31,13 @@ func (c *readWriteCursor) First() ([]byte, []byte) { key []byte value []byte ) - err := c.bucket.tx.QueryRow( - "SELECT key, value FROM "+c.bucket.table+" WHERE "+ - parentSelector(c.bucket.id)+ + row, cancel := c.bucket.tx.QueryRow( + "SELECT key, value FROM " + c.bucket.table + " WHERE " + + parentSelector(c.bucket.id) + " ORDER BY key LIMIT 1", - ).Scan(&key, &value) + ) + defer cancel() + err := row.Scan(&key, &value) switch { case err == sql.ErrNoRows: @@ -58,11 +61,13 @@ func (c *readWriteCursor) Last() ([]byte, []byte) { key []byte value []byte ) - err := c.bucket.tx.QueryRow( - "SELECT key, value FROM "+c.bucket.table+" WHERE "+ - parentSelector(c.bucket.id)+ + row, cancel := c.bucket.tx.QueryRow( + "SELECT key, value FROM " + c.bucket.table + " WHERE " + + parentSelector(c.bucket.id) + " ORDER BY key DESC LIMIT 1", - ).Scan(&key, &value) + ) + defer cancel() + err := row.Scan(&key, &value) switch { case err == sql.ErrNoRows: @@ -86,12 +91,14 @@ func (c *readWriteCursor) Next() ([]byte, []byte) { key []byte value []byte ) - err := c.bucket.tx.QueryRow( + row, cancel := c.bucket.tx.QueryRow( "SELECT key, value FROM "+c.bucket.table+" WHERE "+ parentSelector(c.bucket.id)+ " AND key>$1 ORDER BY key LIMIT 1", c.currKey, - ).Scan(&key, &value) + ) + defer cancel() + err := row.Scan(&key, &value) switch { case err == sql.ErrNoRows: @@ -115,12 +122,14 @@ func (c *readWriteCursor) Prev() ([]byte, []byte) { key []byte value []byte ) - err := c.bucket.tx.QueryRow( + row, cancel := c.bucket.tx.QueryRow( "SELECT key, value FROM "+c.bucket.table+" WHERE "+ parentSelector(c.bucket.id)+ " AND key<$1 ORDER BY key DESC LIMIT 1", c.currKey, - ).Scan(&key, &value) + ) + defer cancel() + err := row.Scan(&key, &value) switch { case err == sql.ErrNoRows: @@ -151,12 +160,14 @@ func (c *readWriteCursor) Seek(seek []byte) ([]byte, []byte) { key []byte value []byte ) - err := c.bucket.tx.QueryRow( + row, cancel := c.bucket.tx.QueryRow( "SELECT key, value FROM "+c.bucket.table+" WHERE "+ parentSelector(c.bucket.id)+ " AND key>=$1 ORDER BY key LIMIT 1", seek, - ).Scan(&key, &value) + ) + defer cancel() + err := row.Scan(&key, &value) switch { case err == sql.ErrNoRows: @@ -179,12 +190,14 @@ func (c *readWriteCursor) Seek(seek []byte) ([]byte, []byte) { func (c *readWriteCursor) Delete() error { // Get first record at or after cursor. var key []byte - err := c.bucket.tx.QueryRow( + row, cancel := c.bucket.tx.QueryRow( "SELECT key FROM "+c.bucket.table+" WHERE "+ parentSelector(c.bucket.id)+ " AND key>=$1 ORDER BY key LIMIT 1", c.currKey, - ).Scan(&key) + ) + defer cancel() + err := row.Scan(&key) switch { case err == sql.ErrNoRows: diff --git a/kvdb/postgres/readwrite_tx.go b/kvdb/postgres/readwrite_tx.go index 0b20e19d33..e0c3e23710 100644 --- a/kvdb/postgres/readwrite_tx.go +++ b/kvdb/postgres/readwrite_tx.go @@ -1,8 +1,10 @@ +//go:build kvdb_postgres // +build kvdb_postgres package postgres import ( + "context" "database/sql" "sync" @@ -38,7 +40,16 @@ func newReadWriteTx(db *db, readOnly bool) (*readWriteTx, error) { } locker.Lock() - tx, err := db.db.Begin() + // Start the transaction. Don't use the timeout context because it would + // be applied to the transaction as a whole. If possible, mark the + // transaction as read-only to make sure that potential programming + // errors cannot cause changes to the database. + tx, err := db.db.BeginTx( + context.Background(), + &sql.TxOptions{ + ReadOnly: readOnly, + }, + ) if err != nil { locker.Unlock() return nil, err @@ -157,21 +168,26 @@ func (tx *readWriteTx) OnCommit(cb func()) { } // QueryRow executes a QueryRow call with a timeout context. -func (tx *readWriteTx) QueryRow(query string, args ...interface{}) *sql.Row { - ctx, cancel := tx.db.getTimeoutCtx() - defer cancel() +func (tx *readWriteTx) QueryRow(query string, args ...interface{}) (*sql.Row, + func()) { - return tx.tx.QueryRowContext(ctx, query, args...) + ctx, cancel := tx.db.getTimeoutCtx() + return tx.tx.QueryRowContext(ctx, query, args...), cancel } -// Query executes a Query call with a timeout context. +// Query executes a multi-row query call with a timeout context. func (tx *readWriteTx) Query(query string, args ...interface{}) (*sql.Rows, - error) { + func(), error) { ctx, cancel := tx.db.getTimeoutCtx() - defer cancel() + rows, err := tx.tx.QueryContext(ctx, query, args...) + if err != nil { + cancel() + + return nil, func() {}, err + } - return tx.tx.QueryContext(ctx, query, args...) + return rows, cancel, nil } // Exec executes a Exec call with a timeout context. diff --git a/kvdb/postgres_test.go b/kvdb/postgres_test.go index a880ceea72..4645e6a308 100644 --- a/kvdb/postgres_test.go +++ b/kvdb/postgres_test.go @@ -1,3 +1,4 @@ +//go:build kvdb_postgres // +build kvdb_postgres package kvdb @@ -13,8 +14,9 @@ import ( type m = map[string]interface{} func TestPostgres(t *testing.T) { - f := postgres.NewFixture(t) - defer f.Cleanup() + stop, err := postgres.StartEmbeddedPostgres() + require.NoError(t, err) + defer stop() tests := []struct { name string @@ -34,13 +36,13 @@ func TestPostgres(t *testing.T) { test: testReadWriteCursor, expectedDb: m{ "test_kv": []m{ - m{"id": int64(1), "key": "apple", "parent_id": nil, "sequence": nil, "value": nil}, - m{"id": int64(4), "key": "da", "parent_id": int64(1), "sequence": nil, "value": "3"}, - m{"id": int64(6), "key": "a", "parent_id": int64(1), "sequence": nil, "value": "0"}, - m{"id": int64(7), "key": "f", "parent_id": int64(1), "sequence": nil, "value": "5"}, - m{"id": int64(3), "key": "c", "parent_id": int64(1), "sequence": nil, "value": "3"}, - m{"id": int64(9), "key": "cx", "parent_id": int64(1), "sequence": nil, "value": "x"}, - m{"id": int64(10), "key": "cy", "parent_id": int64(1), "sequence": nil, "value": "y"}, + {"id": int64(1), "key": "apple", "parent_id": nil, "sequence": nil, "value": nil}, + {"id": int64(4), "key": "da", "parent_id": int64(1), "sequence": nil, "value": "3"}, + {"id": int64(6), "key": "a", "parent_id": int64(1), "sequence": nil, "value": "0"}, + {"id": int64(7), "key": "f", "parent_id": int64(1), "sequence": nil, "value": "5"}, + {"id": int64(3), "key": "c", "parent_id": int64(1), "sequence": nil, "value": "3"}, + {"id": int64(9), "key": "cx", "parent_id": int64(1), "sequence": nil, "value": "x"}, + {"id": int64(10), "key": "cy", "parent_id": int64(1), "sequence": nil, "value": "y"}, }, }, }, @@ -49,10 +51,10 @@ func TestPostgres(t *testing.T) { test: testReadWriteCursorWithBucketAndValue, expectedDb: m{ "test_kv": []m{ - m{"id": int64(1), "key": "apple", "parent_id": nil, "sequence": nil, "value": nil}, - m{"id": int64(2), "key": "key", "parent_id": int64(1), "sequence": nil, "value": "val"}, - m{"id": int64(3), "key": "banana", "parent_id": int64(1), "sequence": nil, "value": nil}, - m{"id": int64(4), "key": "pear", "parent_id": int64(1), "sequence": nil, "value": nil}, + {"id": int64(1), "key": "apple", "parent_id": nil, "sequence": nil, "value": nil}, + {"id": int64(2), "key": "key", "parent_id": int64(1), "sequence": nil, "value": "val"}, + {"id": int64(3), "key": "banana", "parent_id": int64(1), "sequence": nil, "value": nil}, + {"id": int64(4), "key": "pear", "parent_id": int64(1), "sequence": nil, "value": nil}, }, }, }, @@ -61,10 +63,10 @@ func TestPostgres(t *testing.T) { test: testBucketCreation, expectedDb: m{ "test_kv": []m{ - m{"id": int64(1), "key": "apple", "parent_id": nil, "sequence": nil, "value": nil}, - m{"id": int64(2), "key": "banana", "parent_id": int64(1), "sequence": nil, "value": nil}, - m{"id": int64(3), "key": "mango", "parent_id": int64(1), "sequence": nil, "value": nil}, - m{"id": int64(4), "key": "pear", "parent_id": int64(2), "sequence": nil, "value": nil}, + {"id": int64(1), "key": "apple", "parent_id": nil, "sequence": nil, "value": nil}, + {"id": int64(2), "key": "banana", "parent_id": int64(1), "sequence": nil, "value": nil}, + {"id": int64(3), "key": "mango", "parent_id": int64(1), "sequence": nil, "value": nil}, + {"id": int64(4), "key": "pear", "parent_id": int64(2), "sequence": nil, "value": nil}, }, }, }, @@ -73,26 +75,54 @@ func TestPostgres(t *testing.T) { test: testBucketDeletion, expectedDb: m{ "test_kv": []m{ - m{"id": int64(1), "key": "apple", "parent_id": nil, "sequence": nil, "value": nil}, - m{"id": int64(2), "key": "banana", "parent_id": int64(1), "sequence": nil, "value": nil}, - m{"id": int64(3), "key": "key1", "parent_id": int64(2), "sequence": nil, "value": "val1"}, - m{"id": int64(5), "key": "key3", "parent_id": int64(2), "sequence": nil, "value": "val3"}, + {"id": int64(1), "key": "apple", "parent_id": nil, "sequence": nil, "value": nil}, + {"id": int64(2), "key": "banana", "parent_id": int64(1), "sequence": nil, "value": nil}, + {"id": int64(3), "key": "key1", "parent_id": int64(2), "sequence": nil, "value": "val1"}, + {"id": int64(5), "key": "key3", "parent_id": int64(2), "sequence": nil, "value": "val3"}, }, }, }, { name: "bucket for each", - test: testBucketForEach, + test: func(t *testing.T, db walletdb.DB) { + testBucketIterator(t, db, func(bucket walletdb.ReadWriteBucket, + callback func(key, val []byte) error) error { + + return bucket.ForEach(callback) + }) + }, expectedDb: m{ "test_kv": []m{ - m{"id": int64(1), "key": "apple", "parent_id": nil, "sequence": nil, "value": nil}, - m{"id": int64(2), "key": "banana", "parent_id": int64(1), "sequence": nil, "value": nil}, - m{"id": int64(3), "key": "key1", "parent_id": int64(1), "sequence": nil, "value": "val1"}, - m{"id": int64(4), "key": "key1", "parent_id": int64(2), "sequence": nil, "value": "val1"}, - m{"id": int64(5), "key": "key2", "parent_id": int64(1), "sequence": nil, "value": "val2"}, - m{"id": int64(6), "key": "key2", "parent_id": int64(2), "sequence": nil, "value": "val2"}, - m{"id": int64(7), "key": "key3", "parent_id": int64(1), "sequence": nil, "value": "val3"}, - m{"id": int64(8), "key": "key3", "parent_id": int64(2), "sequence": nil, "value": "val3"}, + {"id": int64(1), "key": "apple", "parent_id": nil, "sequence": nil, "value": nil}, + {"id": int64(2), "key": "banana", "parent_id": int64(1), "sequence": nil, "value": nil}, + {"id": int64(3), "key": "key1", "parent_id": int64(1), "sequence": nil, "value": "val1"}, + {"id": int64(4), "key": "key1", "parent_id": int64(2), "sequence": nil, "value": "val1"}, + {"id": int64(5), "key": "key2", "parent_id": int64(1), "sequence": nil, "value": "val2"}, + {"id": int64(6), "key": "key2", "parent_id": int64(2), "sequence": nil, "value": "val2"}, + {"id": int64(7), "key": "key3", "parent_id": int64(1), "sequence": nil, "value": "val3"}, + {"id": int64(8), "key": "key3", "parent_id": int64(2), "sequence": nil, "value": "val3"}, + }, + }, + }, + { + name: "bucket for all", + test: func(t *testing.T, db walletdb.DB) { + testBucketIterator(t, db, func(bucket walletdb.ReadWriteBucket, + callback func(key, val []byte) error) error { + + return ForAll(bucket, callback) + }) + }, + expectedDb: m{ + "test_kv": []m{ + {"id": int64(1), "key": "apple", "parent_id": nil, "sequence": nil, "value": nil}, + {"id": int64(2), "key": "banana", "parent_id": int64(1), "sequence": nil, "value": nil}, + {"id": int64(3), "key": "key1", "parent_id": int64(1), "sequence": nil, "value": "val1"}, + {"id": int64(4), "key": "key1", "parent_id": int64(2), "sequence": nil, "value": "val1"}, + {"id": int64(5), "key": "key2", "parent_id": int64(1), "sequence": nil, "value": "val2"}, + {"id": int64(6), "key": "key2", "parent_id": int64(2), "sequence": nil, "value": "val2"}, + {"id": int64(7), "key": "key3", "parent_id": int64(1), "sequence": nil, "value": "val3"}, + {"id": int64(8), "key": "key3", "parent_id": int64(2), "sequence": nil, "value": "val3"}, }, }, }, @@ -101,11 +131,11 @@ func TestPostgres(t *testing.T) { test: testBucketForEachWithError, expectedDb: m{ "test_kv": []m{ - m{"id": int64(1), "key": "apple", "parent_id": nil, "sequence": nil, "value": nil}, - m{"id": int64(2), "key": "banana", "parent_id": int64(1), "sequence": nil, "value": nil}, - m{"id": int64(3), "key": "pear", "parent_id": int64(1), "sequence": nil, "value": nil}, - m{"id": int64(4), "key": "key1", "parent_id": int64(1), "sequence": nil, "value": "val1"}, - m{"id": int64(5), "key": "key2", "parent_id": int64(1), "sequence": nil, "value": "val2"}, + {"id": int64(1), "key": "apple", "parent_id": nil, "sequence": nil, "value": nil}, + {"id": int64(2), "key": "banana", "parent_id": int64(1), "sequence": nil, "value": nil}, + {"id": int64(3), "key": "pear", "parent_id": int64(1), "sequence": nil, "value": nil}, + {"id": int64(4), "key": "key1", "parent_id": int64(1), "sequence": nil, "value": "val1"}, + {"id": int64(5), "key": "key2", "parent_id": int64(1), "sequence": nil, "value": "val2"}, }, }, }, @@ -114,8 +144,8 @@ func TestPostgres(t *testing.T) { test: testBucketSequence, expectedDb: m{ "test_kv": []m{ - m{"id": int64(2), "key": "banana", "parent_id": int64(1), "sequence": nil, "value": nil}, - m{"id": int64(1), "key": "apple", "parent_id": nil, "sequence": int64(4), "value": nil}, + {"id": int64(2), "key": "banana", "parent_id": int64(1), "sequence": nil, "value": nil}, + {"id": int64(1), "key": "apple", "parent_id": nil, "sequence": int64(4), "value": nil}, }, }, }, @@ -124,9 +154,9 @@ func TestPostgres(t *testing.T) { test: testKeyClash, expectedDb: m{ "test_kv": []m{ - m{"id": int64(1), "key": "apple", "parent_id": nil, "sequence": nil, "value": nil}, - m{"id": int64(2), "key": "key", "parent_id": int64(1), "sequence": nil, "value": "val"}, - m{"id": int64(3), "key": "banana", "parent_id": int64(1), "sequence": nil, "value": nil}, + {"id": int64(1), "key": "apple", "parent_id": nil, "sequence": nil, "value": nil}, + {"id": int64(2), "key": "key", "parent_id": int64(1), "sequence": nil, "value": "val"}, + {"id": int64(3), "key": "banana", "parent_id": int64(1), "sequence": nil, "value": nil}, }, }, }, @@ -135,8 +165,8 @@ func TestPostgres(t *testing.T) { test: testBucketCreateDelete, expectedDb: m{ "test_kv": []m{ - m{"id": int64(1), "key": "apple", "parent_id": nil, "sequence": nil, "value": nil}, - m{"id": int64(3), "key": "banana", "parent_id": int64(1), "sequence": nil, "value": "value"}, + {"id": int64(1), "key": "apple", "parent_id": nil, "sequence": nil, "value": nil}, + {"id": int64(3), "key": "banana", "parent_id": int64(1), "sequence": nil, "value": "value"}, }, }, }, @@ -145,8 +175,8 @@ func TestPostgres(t *testing.T) { test: testTxManualCommit, expectedDb: m{ "test_kv": []m{ - m{"id": int64(1), "key": "apple", "parent_id": nil, "sequence": nil, "value": nil}, - m{"id": int64(2), "key": "testKey", "parent_id": int64(1), "sequence": nil, "value": "testVal"}, + {"id": int64(1), "key": "apple", "parent_id": nil, "sequence": nil, "value": nil}, + {"id": int64(2), "key": "testKey", "parent_id": int64(1), "sequence": nil, "value": "testVal"}, }, }, }, @@ -173,10 +203,14 @@ func TestPostgres(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - test.test(t, f.NewBackend()) + f, err := postgres.NewFixture("") + require.NoError(t, err) + + test.test(t, f.Db) if test.expectedDb != nil { - dump := f.Dump() + dump, err := f.Dump() + require.NoError(t, err) require.Equal(t, test.expectedDb, dump) } }) diff --git a/kvdb/readwrite_bucket_test.go b/kvdb/readwrite_bucket_test.go index 764d482312..46ac4c12ab 100644 --- a/kvdb/readwrite_bucket_test.go +++ b/kvdb/readwrite_bucket_test.go @@ -159,7 +159,20 @@ func testBucketDeletion(t *testing.T, db walletdb.DB) { require.Nil(t, err) } +type bucketIterator = func(walletdb.ReadWriteBucket, + func(key, val []byte) error) error + func testBucketForEach(t *testing.T, db walletdb.DB) { + testBucketIterator(t, db, func(bucket walletdb.ReadWriteBucket, + callback func(key, val []byte) error) error { + + return bucket.ForEach(callback) + }) +} + +func testBucketIterator(t *testing.T, db walletdb.DB, + iterator bucketIterator) { + err := Update(db, func(tx walletdb.ReadWriteTx) error { // "apple" apple, err := tx.CreateTopLevelBucket([]byte("apple")) @@ -199,7 +212,7 @@ func testBucketForEach(t *testing.T, db walletdb.DB) { require.Equal(t, expected, got) got = make(map[string]string) - err = banana.ForEach(func(key, val []byte) error { + err = iterator(banana, func(key, val []byte) error { got[string(key)] = string(val) return nil }) diff --git a/kvdb/test_utils.go b/kvdb/test_utils.go new file mode 100644 index 0000000000..adc1156e73 --- /dev/null +++ b/kvdb/test_utils.go @@ -0,0 +1,34 @@ +package kvdb + +import ( + "fmt" + "os" + "testing" +) + +// RunTests is a helper function to run the tests in a package with +// initialization and tear-down of a test kvdb backend. +func RunTests(m *testing.M) { + var close func() error + if PostgresBackend { + var err error + close, err = StartEmbeddedPostgres() + if err != nil { + fmt.Printf("Error: %v\n", err) + os.Exit(1) + } + } + + // os.Exit() does not respect defer statements + code := m.Run() + + if close != nil { + err := close() + if err != nil { + fmt.Printf("Error: %v\n", err) + } + } + + os.Exit(code) + +} diff --git a/lncfg/address.go b/lncfg/address.go index f45accb111..74bb934e06 100644 --- a/lncfg/address.go +++ b/lncfg/address.go @@ -9,15 +9,11 @@ import ( "strconv" "strings" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/tor" ) -var ( - loopBackAddrs = []string{"localhost", "127.0.0.1", "[::1]"} -) - // TCPResolver is a function signature that resolves an address on a given // network. type TCPResolver = func(network, addr string) (*net.TCPAddr, error) @@ -112,14 +108,18 @@ func TLSListenOnAddress(addr net.Addr, } // IsLoopback returns true if an address describes a loopback interface. -func IsLoopback(addr string) bool { - for _, loopback := range loopBackAddrs { - if strings.Contains(addr, loopback) { - return true - } +func IsLoopback(host string) bool { + if strings.Contains(host, "localhost") { + return true + } + + rawHost, _, _ := net.SplitHostPort(host) + addr := net.ParseIP(rawHost) + if addr == nil { + return false } - return false + return addr.IsLoopback() } // isIPv6Host returns true if the host is IPV6 and false otherwise. @@ -134,6 +134,16 @@ func isIPv6Host(host string) bool { return v6Addr.To4() == nil } +// isUnspecifiedHost returns true if the host IP is considered unspecified. +func isUnspecifiedHost(host string) bool { + addr := net.ParseIP(host) + if addr == nil { + return false + } + + return addr.IsUnspecified() +} + // IsUnix returns true if an address describes an Unix socket address. func IsUnix(addr net.Addr) bool { return strings.HasPrefix(addr.Network(), "unix") @@ -230,17 +240,18 @@ func ParseAddressString(strAddress string, defaultPort string, } // Otherwise, we'll attempt the resolve the host. The Tor - // resolver is unable to resolve local or IPv6 addresses, so - // we'll use the system resolver instead. + // resolver is unable to resolve local addresses, + // IPv6 addresses, or the all-interfaces address, so we'll use + // the system resolver instead for those. if rawHost == "" || IsLoopback(rawHost) || - isIPv6Host(rawHost) { + isIPv6Host(rawHost) || isUnspecifiedHost(rawHost) { return net.ResolveTCPAddr("tcp", addrWithPort) } // If we've reached this point, then it's possible that this // resolve returns an error if it isn't able to resolve the - // host. For eaxmple, local entries in /etc/hosts will fail to + // host. For example, local entries in /etc/hosts will fail to // be resolved by Tor. In order to handle this case, we'll fall // back to the normal system resolver if we fail with an // identifiable error. @@ -267,12 +278,33 @@ func ParseAddressString(strAddress string, defaultPort string, func ParseLNAddressString(strAddress string, defaultPort string, tcpResolver TCPResolver) (*lnwire.NetAddress, error) { + pubKey, parsedAddr, err := ParseLNAddressPubkey(strAddress) + if err != nil { + return nil, err + } + + // Finally, parse the address string using our generic address parser. + addr, err := ParseAddressString(parsedAddr, defaultPort, tcpResolver) + if err != nil { + return nil, fmt.Errorf("invalid lightning address address: %v", err) + } + + return &lnwire.NetAddress{ + IdentityKey: pubKey, + Address: addr, + }, nil +} + +// ParseLNAddressPubkey converts a string of the form @ into two +// pieces: the pubkey bytes and an addr string. It validates that the pubkey +// is of a valid form. +func ParseLNAddressPubkey(strAddress string) (*btcec.PublicKey, string, error) { // Split the address string around the @ sign. parts := strings.Split(strAddress, "@") // The string is malformed if there are not exactly two parts. if len(parts) != 2 { - return nil, fmt.Errorf("invalid lightning address %s: "+ + return nil, "", fmt.Errorf("invalid lightning address %s: "+ "must be of the form @", strAddress) } @@ -283,32 +315,23 @@ func ParseLNAddressString(strAddress string, defaultPort string, // Decode the hex pubkey to get the raw compressed pubkey bytes. pubKeyBytes, err := hex.DecodeString(parsedPubKey) if err != nil { - return nil, fmt.Errorf("invalid lightning address pubkey: %v", err) + return nil, "", fmt.Errorf("invalid lightning address pubkey: %v", err) } // The compressed pubkey should have a length of exactly 33 bytes. if len(pubKeyBytes) != 33 { - return nil, fmt.Errorf("invalid lightning address pubkey: "+ + return nil, "", fmt.Errorf("invalid lightning address pubkey: "+ "length must be 33 bytes, found %d", len(pubKeyBytes)) } // Parse the pubkey bytes to verify that it corresponds to valid public // key on the secp256k1 curve. - pubKey, err := btcec.ParsePubKey(pubKeyBytes, btcec.S256()) - if err != nil { - return nil, fmt.Errorf("invalid lightning address pubkey: %v", err) - } - - // Finally, parse the address string using our generic address parser. - addr, err := ParseAddressString(parsedAddr, defaultPort, tcpResolver) + pubKey, err := btcec.ParsePubKey(pubKeyBytes) if err != nil { - return nil, fmt.Errorf("invalid lightning address address: %v", err) + return nil, "", fmt.Errorf("invalid lightning address pubkey: %v", err) } - return &lnwire.NetAddress{ - IdentityKey: pubKey, - Address: addr, - }, nil + return pubKey, parsedAddr, nil } // verifyPort makes sure that an address string has both a host and a port. If diff --git a/lncfg/address_test.go b/lncfg/address_test.go index 009093e759..cb3634a5f3 100644 --- a/lncfg/address_test.go +++ b/lncfg/address_test.go @@ -9,7 +9,7 @@ import ( "net" "testing" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/stretchr/testify/require" ) @@ -140,7 +140,7 @@ var ( pubKeyHex = hex.EncodeToString(pubKeyBytes) - pubKey, _ = btcec.ParsePubKey(pubKeyBytes, btcec.S256()) + pubKey, _ = btcec.ParsePubKey(pubKeyBytes) ) type lnAddressCase struct { diff --git a/lncfg/bitcoind.go b/lncfg/bitcoind.go index 40bbcd8e97..cf0f002329 100644 --- a/lncfg/bitcoind.go +++ b/lncfg/bitcoind.go @@ -1,14 +1,22 @@ package lncfg +import "time" + // Bitcoind holds the configuration options for the daemon's connection to // bitcoind. type Bitcoind struct { - Dir string `long:"dir" description:"The base directory that contains the node's data, logs, configuration file, etc."` - RPCHost string `long:"rpchost" description:"The daemon's rpc listening address. If a port is omitted, then the default port for the selected chain parameters will be used."` - RPCUser string `long:"rpcuser" description:"Username for RPC connections"` - RPCPass string `long:"rpcpass" default-mask:"-" description:"Password for RPC connections"` - ZMQPubRawBlock string `long:"zmqpubrawblock" description:"The address listening for ZMQ connections to deliver raw block notifications"` - ZMQPubRawTx string `long:"zmqpubrawtx" description:"The address listening for ZMQ connections to deliver raw transaction notifications"` - EstimateMode string `long:"estimatemode" description:"The fee estimate mode. Must be either ECONOMICAL or CONSERVATIVE."` - PrunedNodeMaxPeers int `long:"pruned-node-max-peers" description:"The maximum number of peers lnd will choose from the backend node to retrieve pruned blocks from. This only applies to pruned nodes."` + Dir string `long:"dir" description:"The base directory that contains the node's data, logs, configuration file, etc."` + ConfigPath string `long:"config" description:"Configuration filepath. If not set, will default to the default filename under 'dir'."` + RPCCookie string `long:"rpccookie" description:"Authentication cookie file for RPC connections. If not set, will default to .cookie under 'dir'."` + RPCHost string `long:"rpchost" description:"The daemon's rpc listening address. If a port is omitted, then the default port for the selected chain parameters will be used."` + RPCUser string `long:"rpcuser" description:"Username for RPC connections"` + RPCPass string `long:"rpcpass" default-mask:"-" description:"Password for RPC connections"` + ZMQPubRawBlock string `long:"zmqpubrawblock" description:"The address listening for ZMQ connections to deliver raw block notifications"` + ZMQPubRawTx string `long:"zmqpubrawtx" description:"The address listening for ZMQ connections to deliver raw transaction notifications"` + ZMQReadDeadline time.Duration `long:"zmqreaddeadline" description:"The read deadline for reading ZMQ messages from both the block and tx subscriptions"` + EstimateMode string `long:"estimatemode" description:"The fee estimate mode. Must be either ECONOMICAL or CONSERVATIVE."` + PrunedNodeMaxPeers int `long:"pruned-node-max-peers" description:"The maximum number of peers lnd will choose from the backend node to retrieve pruned blocks from. This only applies to pruned nodes."` + RPCPolling bool `long:"rpcpolling" description:"Poll the bitcoind RPC interface for block and transaction notifications instead of using the ZMQ interface"` + BlockPollingInterval time.Duration `long:"blockpollinginterval" description:"The interval that will be used to poll bitcoind for new blocks. Only used if rpcpolling is true."` + TxPollingInterval time.Duration `long:"txpollinginterval" description:"The interval that will be used to poll bitcoind for new tx. Only used if rpcpolling is true."` } diff --git a/lncfg/caches.go b/lncfg/caches.go index 47b9a97e88..abb1cf24d7 100644 --- a/lncfg/caches.go +++ b/lncfg/caches.go @@ -1,6 +1,9 @@ package lncfg -import "fmt" +import ( + "fmt" + "time" +) const ( // MinRejectCacheSize is a floor on the maximum capacity allowed for @@ -10,6 +13,10 @@ const ( // MinChannelCacheSize is a floor on the maximum capacity allowed for // channeldb's channel cache. This amounts to roughly 2 MB when full. MinChannelCacheSize = 1000 + + // DefaultRPCGraphCacheDuration is the default interval that the RPC + // response to DescribeGraph should be cached for. + DefaultRPCGraphCacheDuration = time.Minute ) // Caches holds the configuration for various caches within lnd. @@ -24,6 +31,10 @@ type Caches struct { // peers querying for gossip traffic. Memory usage is roughly 2Kb per // entry. ChannelCacheSize int `long:"channel-cache-size" description:"Maximum number of entries contained in the channel cache, which is used to reduce memory allocations from gossip queries from peers. Each entry requires roughly 2Kb."` + + // RPCGraphCacheDuration is used to control the flush interval of the + // channel graph cache. + RPCGraphCacheDuration time.Duration `long:"rpc-graph-cache-duration" description:"The period of time expressed as a duration (1s, 1m, 1h, etc) that the RPC response to DescribeGraph should be cached for."` } // Validate checks the Caches configuration for values that are too small to be diff --git a/lncfg/chain.go b/lncfg/chain.go index 014cd19cad..0ee4e3ada5 100644 --- a/lncfg/chain.go +++ b/lncfg/chain.go @@ -11,7 +11,7 @@ type Chain struct { Active bool `long:"active" description:"If the chain should be active or not."` ChainDir string `long:"chaindir" description:"The directory to store the chain's data within."` - Node string `long:"node" description:"The blockchain interface to use." choice:"btcd" choice:"bitcoind" choice:"neutrino" choice:"ltcd" choice:"litecoind"` + Node string `long:"node" description:"The blockchain interface to use." choice:"btcd" choice:"bitcoind" choice:"neutrino" choice:"ltcd" choice:"litecoind" choice:"nochainbackend"` MainNet bool `long:"mainnet" description:"Use the main network"` TestNet3 bool `long:"testnet" description:"Use the test network"` diff --git a/lncfg/cluster.go b/lncfg/cluster.go index 6497787965..8b3b14f5fb 100644 --- a/lncfg/cluster.go +++ b/lncfg/cluster.go @@ -20,9 +20,11 @@ type Cluster struct { LeaderElector string `long:"leader-elector" choice:"etcd" description:"Leader elector to use. Valid values: \"etcd\"."` - EtcdElectionPrefix string `long:"etcd-election-prefix" description:"Election key prefix when using etcd leader elector. Defaults to \"/leader/\"."` + EtcdElectionPrefix string `long:"etcd-election-prefix" description:"Election key prefix when using etcd leader elector."` ID string `long:"id" description:"Identifier for this node inside the cluster (used in leader election). Defaults to the hostname."` + + LeaderSessionTTL int `long:"leader-session-ttl" description:"The TTL in seconds to use for the leader election session."` } // DefaultCluster creates and returns a new default DB config. @@ -31,6 +33,7 @@ func DefaultCluster() *Cluster { return &Cluster{ LeaderElector: cluster.EtcdLeaderElector, EtcdElectionPrefix: DefaultEtcdElectionPrefix, + LeaderSessionTTL: 60, ID: hostname, } } @@ -43,7 +46,7 @@ func (c *Cluster) MakeLeaderElector(electionCtx context.Context, db *DB) ( if c.LeaderElector == cluster.EtcdLeaderElector { return cluster.MakeLeaderElector( electionCtx, c.LeaderElector, c.ID, - c.EtcdElectionPrefix, db.Etcd, + c.EtcdElectionPrefix, c.LeaderSessionTTL, db.Etcd, ) } @@ -57,7 +60,6 @@ func (c *Cluster) Validate() error { } switch c.LeaderElector { - case cluster.EtcdLeaderElector: if c.EtcdElectionPrefix == "" { return fmt.Errorf("etcd-election-prefix must be set") diff --git a/lncfg/color.go b/lncfg/color.go new file mode 100644 index 0000000000..75708684ac --- /dev/null +++ b/lncfg/color.go @@ -0,0 +1,34 @@ +package lncfg + +import ( + "encoding/hex" + "errors" + "image/color" + "regexp" +) + +var ( + // validColorRegexp is a regexp that lets you check if a particular + // color string matches the standard hex color format #RRGGBB. + validColorRegexp = regexp.MustCompile("^#[A-Fa-f0-9]{6}$") +) + +// ParseHexColor takes a hex string representation of a color in the +// form "#RRGGBB", parses the hex color values, and returns a color.RGBA +// struct of the same color. +func ParseHexColor(colorStr string) (color.RGBA, error) { + // Check if the hex color string is a valid color representation. + if !validColorRegexp.MatchString(colorStr) { + return color.RGBA{}, errors.New("color must be specified " + + "using a hexadecimal value in the form #RRGGBB") + } + + // Decode the hex color string to bytes. + // The resulting byte array is in the form [R, G, B]. + colorBytes, err := hex.DecodeString(colorStr[1:]) + if err != nil { + return color.RGBA{}, err + } + + return color.RGBA{R: colorBytes[0], G: colorBytes[1], B: colorBytes[2]}, nil +} diff --git a/lncfg/color_test.go b/lncfg/color_test.go new file mode 100644 index 0000000000..ae68be8989 --- /dev/null +++ b/lncfg/color_test.go @@ -0,0 +1,42 @@ +package lncfg + +import ( + "testing" +) + +func TestParseHexColor(t *testing.T) { + var colorTestCases = []struct { + test string + valid bool // If valid format + R byte + G byte + B byte + }{ + {"#123", false, 0, 0, 0}, + {"#1234567", false, 0, 0, 0}, + {"$123456", false, 0, 0, 0}, + {"#12345+", false, 0, 0, 0}, + {"#fFGG00", false, 0, 0, 0}, + {"", false, 0, 0, 0}, + {"#123456", true, 0x12, 0x34, 0x56}, + {"#C0FfeE", true, 0xc0, 0xff, 0xee}, + } + + // Perform the table driven tests. + for _, ct := range colorTestCases { + color, err := ParseHexColor(ct.test) + if !ct.valid && err == nil { + t.Fatalf("Invalid color string: %s, should return "+ + "error, but did not", ct.test) + } + + if ct.valid && err != nil { + t.Fatalf("Color %s valid to parse: %s", ct.test, err) + } + + // Ensure that the string to hex decoding is working properly. + if color.R != ct.R || color.G != ct.G || color.B != ct.B { + t.Fatalf("Color %s incorrectly parsed as %v", ct.test, color) + } + } +} diff --git a/lncfg/db.go b/lncfg/db.go index 7c0004776d..34c7f21374 100644 --- a/lncfg/db.go +++ b/lncfg/db.go @@ -23,6 +23,8 @@ const ( PostgresBackend = "postgres" DefaultBatchCommitInterval = 500 * time.Millisecond + defaultPostgresMaxConnections = 50 + // NSChannelDB is the namespace name that we use for the combined graph // and channel state DB. NSChannelDB = "channeldb" @@ -71,6 +73,13 @@ func DefaultDB() *DB { AutoCompactMinAge: kvdb.DefaultBoltAutoCompactMinAge, DBTimeout: kvdb.DefaultDBTimeout, }, + Etcd: &etcd.Config{ + // Allow at most 32 MiB messages by default. + MaxMsgSize: 32768 * 1024, + }, + Postgres: &postgres.Config{ + MaxConnections: defaultPostgresMaxConnections, + }, } } @@ -113,7 +122,8 @@ func (db *DB) Validate() error { // on configuration. func (db *DB) Init(ctx context.Context, dbPath string) error { // Start embedded etcd server if requested. - if db.Backend == EtcdBackend && db.Etcd.Embedded { + switch { + case db.Backend == EtcdBackend && db.Etcd.Embedded: cfg, _, err := kvdb.StartEtcdTestBackend( dbPath, db.Etcd.EmbeddedClientPort, db.Etcd.EmbeddedPeerPort, db.Etcd.EmbeddedLogFile, @@ -125,6 +135,9 @@ func (db *DB) Init(ctx context.Context, dbPath string) error { // Override the original config with the config for // the embedded instance. db.Etcd = cfg + + case db.Backend == PostgresBackend: + postgres.Init(db.Postgres.MaxConnections) } return nil diff --git a/lncfg/healthcheck.go b/lncfg/healthcheck.go index 2c2a77c997..ce668ea08f 100644 --- a/lncfg/healthcheck.go +++ b/lncfg/healthcheck.go @@ -30,6 +30,8 @@ type HealthCheckConfig struct { TLSCheck *CheckConfig `group:"tls" namespace:"tls"` TorConnection *CheckConfig `group:"torconnection" namespace:"torconnection"` + + RemoteSigner *CheckConfig `group:"remotesigner" namespace:"remotesigner"` } // Validate checks the values configured for our health checks. diff --git a/lncfg/monitoring_on.go b/lncfg/monitoring_on.go index 872b463306..579c83638a 100644 --- a/lncfg/monitoring_on.go +++ b/lncfg/monitoring_on.go @@ -13,6 +13,12 @@ type Prometheus struct { // Enable indicates whether to export lnd gRPC performance metrics to // Prometheus. Default is false. Enable bool `long:"enable" description:"enable Prometheus exporting of lnd gRPC performance metrics."` + + // PerfHistograms indicates if the additional histogram information for + // latency, and handling time of gRPC calls should be enabled. This + // generates additional data, and consume more memory for the + // Prometheus server. + PerfHistograms bool `long:"perfhistograms" description:"enable additional histogram to track gRPC call processing performance (latency, etc)"` } // DefaultPrometheus is the default configuration for the Prometheus metrics diff --git a/lncfg/protocol.go b/lncfg/protocol.go index 86580d0187..54ca3be992 100644 --- a/lncfg/protocol.go +++ b/lncfg/protocol.go @@ -9,7 +9,7 @@ package lncfg type ProtocolOptions struct { // LegacyProtocol is a sub-config that houses all the legacy protocol // options. These are mostly used for integration tests as most modern - // nodes shuld always run with them on by default. + // nodes should always run with them on by default. LegacyProtocol `group:"legacy" namespace:"legacy"` // ExperimentalProtocol is a sub-config that houses any experimental diff --git a/lncfg/protocol_legacy_off.go b/lncfg/protocol_legacy_off.go index 4dc0a814c6..028573e643 100644 --- a/lncfg/protocol_legacy_off.go +++ b/lncfg/protocol_legacy_off.go @@ -4,7 +4,7 @@ package lncfg // Legacy is a sub-config that houses all the legacy protocol options. These -// are mostly used for integration tests as most modern nodes shuld always run +// are mostly used for integration tests as most modern nodes should always run // with them on by default. type LegacyProtocol struct { } diff --git a/lncfg/protocol_legacy_on.go b/lncfg/protocol_legacy_on.go index 699f894116..80236c94e3 100644 --- a/lncfg/protocol_legacy_on.go +++ b/lncfg/protocol_legacy_on.go @@ -4,7 +4,7 @@ package lncfg // Legacy is a sub-config that houses all the legacy protocol options. These -// are mostly used for integration tests as most modern nodes shuld always run +// are mostly used for integration tests as most modern nodes should always run // with them on by default. type LegacyProtocol struct { // LegacyOnionFormat if set to true, then we won't signal diff --git a/lncfg/protocol_rpctest.go b/lncfg/protocol_rpctest.go index 5630cf52b7..25668c4c07 100644 --- a/lncfg/protocol_rpctest.go +++ b/lncfg/protocol_rpctest.go @@ -9,7 +9,7 @@ package lncfg type ProtocolOptions struct { // LegacyProtocol is a sub-config that houses all the legacy protocol // options. These are mostly used for integration tests as most modern - // nodes shuld always run with them on by default. + // nodes should always run with them on by default. LegacyProtocol `group:"legacy" namespace:"legacy"` // ExperimentalProtocol is a sub-config that houses any experimental diff --git a/lncfg/remotesigner.go b/lncfg/remotesigner.go index 90d1988cf6..66995122cd 100644 --- a/lncfg/remotesigner.go +++ b/lncfg/remotesigner.go @@ -1,9 +1,43 @@ package lncfg +import ( + "fmt" + "time" +) + +const ( + // DefaultRemoteSignerRPCTimeout is the default timeout that is used + // when forwarding a request to the remote signer through RPC. + DefaultRemoteSignerRPCTimeout = 5 * time.Second +) + // RemoteSigner holds the configuration options for a remote RPC signer. type RemoteSigner struct { - Enable bool `long:"enable" description:"Use a remote signer for signing any on-chain related transactions or messages. Only recommended if local wallet is initialized as watch-only. Remote signer must use the same seed/root key as the local watch-only wallet but must have private keys."` - RPCHost string `long:"rpchost" description:"The remote signer's RPC host:port"` - MacaroonPath string `long:"macaroonpath" description:"The macaroon to use for authenticating with the remote signer"` - TLSCertPath string `long:"tlscertpath" description:"The TLS certificate to use for establishing the remote signer's identity"` + Enable bool `long:"enable" description:"Use a remote signer for signing any on-chain related transactions or messages. Only recommended if local wallet is initialized as watch-only. Remote signer must use the same seed/root key as the local watch-only wallet but must have private keys."` + RPCHost string `long:"rpchost" description:"The remote signer's RPC host:port"` + MacaroonPath string `long:"macaroonpath" description:"The macaroon to use for authenticating with the remote signer"` + TLSCertPath string `long:"tlscertpath" description:"The TLS certificate to use for establishing the remote signer's identity"` + Timeout time.Duration `long:"timeout" description:"The timeout for connecting to and signing requests with the remote signer. Valid time units are {s, m, h}."` + MigrateWatchOnly bool `long:"migrate-wallet-to-watch-only" description:"If a wallet with private key material already exists, migrate it into a watch-only wallet on first startup. WARNING: This cannot be undone! Make sure you have backed up your seed before you use this flag! All private keys will be purged from the wallet after first unlock with this flag!"` +} + +// Validate checks the values configured for our remote RPC signer. +func (r *RemoteSigner) Validate() error { + if !r.Enable { + return nil + } + + if r.Timeout < time.Millisecond { + return fmt.Errorf("remote signer: timeout of %v is invalid, "+ + "cannot be smaller than %v", r.Timeout, + time.Millisecond) + } + + if r.MigrateWatchOnly && !r.Enable { + return fmt.Errorf("remote signer: cannot turn on wallet " + + "migration to watch-only if remote signing is not " + + "enabled") + } + + return nil } diff --git a/lncfg/tor.go b/lncfg/tor.go index be58e7ea5a..2dcd18c931 100644 --- a/lncfg/tor.go +++ b/lncfg/tor.go @@ -13,5 +13,6 @@ type Tor struct { V2 bool `long:"v2" description:"Automatically set up a v2 onion service to listen for inbound connections"` V3 bool `long:"v3" description:"Automatically set up a v3 onion service to listen for inbound connections"` PrivateKeyPath string `long:"privatekeypath" description:"The path to the private key of the onion service being created"` + EncryptKey bool `long:"encryptkey" description:"Encrypts the Tor private key file on disk"` WatchtowerKeyPath string `long:"watchtowerkeypath" description:"The path to the private key of the watchtower onion service being created"` } diff --git a/lncfg/wtclient.go b/lncfg/wtclient.go index e755967813..587e3fe416 100644 --- a/lncfg/wtclient.go +++ b/lncfg/wtclient.go @@ -23,11 +23,11 @@ type WtClient struct { func (c *WtClient) Validate() error { // TODO(wilmer): remove in v0.9.0 release. if len(c.PrivateTowerURIs) > 0 { - fmt.Println("The `wtclient.private-tower-uris` option has " + - "been deprecated as of v0.8.0-beta and will be " + + return fmt.Errorf("the `wtclient.private-tower-uris` option " + + "has been deprecated as of v0.8.0-beta and will be " + "removed in v0.9.0-beta. To setup watchtowers for " + "the client, set `wtclient.active` and run " + - "`lncli wtclient -h` for more information.") + "`lncli wtclient -h` for more information") } return nil diff --git a/lnd.go b/lnd.go index 83a23c8461..2f69afc5b4 100644 --- a/lnd.go +++ b/lnd.go @@ -1,33 +1,28 @@ // Copyright (c) 2013-2017 The btcsuite developers // Copyright (c) 2015-2016 The Decred developers -// Copyright (C) 2015-2017 The Lightning Network Developers +// Copyright (C) 2015-2022 The Lightning Network Developers package lnd import ( + "bytes" "context" "crypto/tls" + "crypto/x509" "errors" "fmt" "io/ioutil" "net" "net/http" - _ "net/http/pprof" // Blank import to set up profiling HTTP handlers. + _ "net/http/pprof" // nolint:gosec // used to set up profiling HTTP handlers. "os" "runtime/pprof" "strings" "sync" "time" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" proxy "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" - "golang.org/x/crypto/acme/autocert" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials" - "google.golang.org/protobuf/encoding/protojson" - "gopkg.in/macaroon-bakery.v2/bakery" - "gopkg.in/macaroon.v2" - "github.com/lightningnetwork/lnd/autopilot" "github.com/lightningnetwork/lnd/build" "github.com/lightningnetwork/lnd/cert" @@ -35,6 +30,7 @@ import ( "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lncfg" + "github.com/lightningnetwork/lnd/lnencrypt" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/macaroons" @@ -44,6 +40,12 @@ import ( "github.com/lightningnetwork/lnd/tor" "github.com/lightningnetwork/lnd/walletunlocker" "github.com/lightningnetwork/lnd/watchtower" + "golang.org/x/crypto/acme/autocert" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/protobuf/encoding/protojson" + "gopkg.in/macaroon-bakery.v2/bakery" + "gopkg.in/macaroon.v2" ) const ( @@ -70,7 +72,9 @@ const ( // // NOTE: This should only be called after the RPCListener has signaled it is // ready. -func AdminAuthOptions(cfg *Config, skipMacaroons bool) ([]grpc.DialOption, error) { +func AdminAuthOptions(cfg *Config, skipMacaroons bool) ([]grpc.DialOption, + error) { + creds, err := credentials.NewClientTLSFromFile(cfg.TLSCertPath, "") if err != nil { return nil, fmt.Errorf("unable to read TLS cert: %v", err) @@ -107,8 +111,8 @@ func AdminAuthOptions(cfg *Config, skipMacaroons bool) ([]grpc.DialOption, error return opts, nil } -// ListenerWithSignal is a net.Listener that has an additional Ready channel that -// will be closed when a server starts listening. +// ListenerWithSignal is a net.Listener that has an additional Ready channel +// that will be closed when a server starts listening. type ListenerWithSignal struct { net.Listener @@ -148,10 +152,16 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, } }() + mkErr := func(format string, args ...interface{}) error { + ltndLog.Errorf("Shutting down because error in main "+ + "method: "+format, args...) + return fmt.Errorf(format, args...) + } + // Show version at startup. - ltndLog.Infof("Version: %s commit=%s, build=%s, logging=%s, debuglevel=%s", - build.Version(), build.Commit, build.Deployment, - build.LoggingType, cfg.DebugLevel) + ltndLog.Infof("Version: %s commit=%s, build=%s, logging=%s, "+ + "debuglevel=%s", build.Version(), build.Commit, + build.Deployment, build.LoggingType, cfg.DebugLevel) var network string switch { @@ -191,10 +201,7 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, if cfg.CPUProfile != "" { f, err := os.Create(cfg.CPUProfile) if err != nil { - err := fmt.Errorf("unable to create CPU profile: %v", - err) - ltndLog.Error(err) - return err + return mkErr("unable to create CPU profile: %v", err) } pprof.StartCPUProfile(f) defer f.Close() @@ -209,25 +216,33 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, // needs to be done early and once during the startup process, before // any DB access. if err := cfg.DB.Init(ctx, cfg.graphDatabaseDir()); err != nil { - return err + return mkErr("error initializing DBs: %v", err) } + // The real KeyRing isn't available until after the wallet is unlocked, + // but we need one now if --tlsencryptkey is true. Because we aren't + // encrypting anything here it can be an empty KeyRing. + var emptyKeyRing keychain.KeyRing + // Only process macaroons if --no-macaroons isn't set. - serverOpts, restDialOpts, restListen, cleanUp, err := getTLSConfig(cfg) + tlsManager := TLSManager{ + cfg: cfg, + keyRing: emptyKeyRing, + } + serverOpts, + restDialOpts, + restListen, + cleanUp, + err := tlsManager.getConfig() if err != nil { - err := fmt.Errorf("unable to load TLS credentials: %v", err) - ltndLog.Error(err) - return err + return mkErr("unable to load TLS credentials: %v", err) } - defer cleanUp() // If we have chosen to start with a dedicated listener for the // rpc server, we set it directly. - var grpcListeners []*ListenerWithSignal - if len(lisCfg.RPCListeners) > 0 { - grpcListeners = append(grpcListeners, lisCfg.RPCListeners...) - } else { + grpcListeners := append([]*ListenerWithSignal{}, lisCfg.RPCListeners...) + if len(grpcListeners) == 0 { // Otherwise we create listeners from the RPCListeners defined // in the config. for _, grpcEndpoint := range cfg.RPCListeners { @@ -235,9 +250,8 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, // connections. lis, err := lncfg.ListenOnAddress(grpcEndpoint) if err != nil { - ltndLog.Errorf("unable to listen on %s", - grpcEndpoint) - return err + return mkErr("unable to listen on %s: %v", + grpcEndpoint, err) } defer lis.Close() @@ -245,7 +259,8 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, grpcListeners, &ListenerWithSignal{ Listener: lis, Ready: make(chan struct{}), - }) + }, + ) } } @@ -255,7 +270,7 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, rpcsLog, cfg.NoMacaroons, cfg.RPCMiddleware.Mandatory, ) if err := interceptorChain.Start(); err != nil { - return err + return mkErr("error starting interceptor chain: %v", err) } defer func() { err := interceptorChain.Stop() @@ -267,6 +282,9 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, rpcServerOpts := interceptorChain.CreateServerOpts() serverOpts = append(serverOpts, rpcServerOpts...) + serverOpts = append( + serverOpts, grpc.MaxRecvMsgSize(lnrpc.MaxGrpcMsgSize), + ) grpcServer := grpc.NewServer(serverOpts...) defer grpcServer.Stop() @@ -280,14 +298,14 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, rpcServer := newRPCServer(cfg, interceptorChain, implCfg, interceptor) err = rpcServer.RegisterWithGrpcServer(grpcServer) if err != nil { - return err + return mkErr("error registering gRPC server: %v", err) } // Now that both the WalletUnlocker and LightningService have been // registered with the GRPC server, we can start listening. err = startGrpcListen(cfg, grpcServer, grpcListeners) if err != nil { - return err + return mkErr("error starting gRPC listener: %v", err) } // Now start the REST proxy for our gRPC server above. We'll ensure @@ -298,7 +316,7 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, cfg, rpcServer, restDialOpts, restListen, ) if err != nil { - return err + return mkErr("error starting REST proxy: %v", err) } defer stopProxy() @@ -342,8 +360,7 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, cfg.Cluster.ID) if err := leaderElector.Campaign(electionCtx); err != nil { - ltndLog.Errorf("Leadership campaign failed: %v", err) - return err + return mkErr("leadership campaign failed: %v", err) } elected = true @@ -356,7 +373,7 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, ltndLog.Infof("%v, exiting", err) return nil case err != nil: - return fmt.Errorf("unable to open databases: %v", err) + return mkErr("unable to open databases: %v", err) } defer cleanUp() @@ -365,7 +382,7 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, ctx, dbs, interceptorChain, grpcListeners, ) if err != nil { - return fmt.Errorf("error creating wallet config: %v", err) + return mkErr("error creating wallet config: %v", err) } defer cleanUp() @@ -374,7 +391,7 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, partialChainControl, walletConfig, ) if err != nil { - return fmt.Errorf("error loading chain control: %v", err) + return mkErr("error loading chain control: %v", err) } defer cleanUp() @@ -393,9 +410,7 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, }, ) if err != nil { - err := fmt.Errorf("error deriving node key: %v", err) - ltndLog.Error(err) - return err + return mkErr("error deriving node key: %v", err) } if cfg.Tor.StreamIsolation && cfg.Tor.SkipProxyForClearNetTargets { @@ -404,33 +419,37 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, if cfg.Tor.Active { if cfg.Tor.SkipProxyForClearNetTargets { - srvrLog.Info("Onion services are accessible via Tor! NOTE: " + - "Traffic to clearnet services is not routed via Tor.") + srvrLog.Info("Onion services are accessible via Tor! " + + "NOTE: Traffic to clearnet services is not " + + "routed via Tor.") } else { srvrLog.Infof("Proxying all network traffic via Tor "+ - "(stream_isolation=%v)! NOTE: Ensure the backend node "+ - "is proxying over Tor as well", cfg.Tor.StreamIsolation) + "(stream_isolation=%v)! NOTE: Ensure the "+ + "backend node is proxying over Tor as well", + cfg.Tor.StreamIsolation) } } - // If tor is active and either v2 or v3 onion services have been specified, - // make a tor controller and pass it into both the watchtower server and - // the regular lnd server. + // If tor is active and either v2 or v3 onion services have been + // specified, make a tor controller and pass it into both the watchtower + // server and the regular lnd server. var torController *tor.Controller if cfg.Tor.Active && (cfg.Tor.V2 || cfg.Tor.V3) { torController = tor.NewController( - cfg.Tor.Control, cfg.Tor.TargetIPAddress, cfg.Tor.Password, + cfg.Tor.Control, cfg.Tor.TargetIPAddress, + cfg.Tor.Password, ) - // Start the tor controller before giving it to any other subsystems. + // Start the tor controller before giving it to any other + // subsystems. if err := torController.Start(); err != nil { - err := fmt.Errorf("unable to initialize tor controller: %v", err) - ltndLog.Error(err) - return err + return mkErr("unable to initialize tor controller: %v", + err) } defer func() { if err := torController.Stop(); err != nil { - ltndLog.Errorf("error stopping tor controller: %v", err) + ltndLog.Errorf("error stopping tor "+ + "controller: %v", err) } }() } @@ -444,9 +463,7 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, }, ) if err != nil { - err := fmt.Errorf("error deriving tower key: %v", err) - ltndLog.Error(err) - return err + return mkErr("error deriving tower key: %v", err) } wtCfg := &watchtower.Config{ @@ -467,11 +484,13 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, ChainHash: *cfg.ActiveNetParams.GenesisHash, } - // If there is a tor controller (user wants auto hidden services), then - // store a pointer in the watchtower config. + // If there is a tor controller (user wants auto hidden + // services), then store a pointer in the watchtower config. if torController != nil { wtCfg.TorController = torController wtCfg.WatchtowerKeyPath = cfg.Tor.WatchtowerKeyPath + wtCfg.EncryptKey = cfg.Tor.EncryptKey + wtCfg.KeyRing = activeChainControl.KeyRing switch { case cfg.Tor.V2: @@ -481,19 +500,16 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, } } - wtConfig, err := cfg.Watchtower.Apply(wtCfg, lncfg.NormalizeAddresses) + wtConfig, err := cfg.Watchtower.Apply( + wtCfg, lncfg.NormalizeAddresses, + ) if err != nil { - err := fmt.Errorf("unable to configure watchtower: %v", - err) - ltndLog.Error(err) - return err + return mkErr("unable to configure watchtower: %v", err) } tower, err = watchtower.New(wtConfig) if err != nil { - err := fmt.Errorf("unable to create watchtower: %v", err) - ltndLog.Error(err) - return err + return mkErr("unable to create watchtower: %v", err) } } @@ -508,9 +524,7 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, chainedAcceptor, torController, ) if err != nil { - err := fmt.Errorf("unable to create server: %v", err) - ltndLog.Error(err) - return err + return mkErr("unable to create server: %v", err) } // Set up an autopilot manager from the current config. This will be @@ -521,24 +535,32 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, cfg.ActiveNetParams, ) if err != nil { - err := fmt.Errorf("unable to initialize autopilot: %v", err) - ltndLog.Error(err) - return err + return mkErr("unable to initialize autopilot: %v", err) } atplManager, err := autopilot.NewManager(atplCfg) if err != nil { - err := fmt.Errorf("unable to create autopilot manager: %v", err) - ltndLog.Error(err) - return err + return mkErr("unable to create autopilot manager: %v", err) } if err := atplManager.Start(); err != nil { - err := fmt.Errorf("unable to start autopilot manager: %v", err) - ltndLog.Error(err) - return err + return mkErr("unable to start autopilot manager: %v", err) } defer atplManager.Stop() + // If --tlsencryptkey is set, we previously generated a throwaway TLSConfig + // Now we want to remove that and load the persistent TLSConfig + // The wallet is unlocked at this point so we can use the real KeyRing + if cfg.TLSEncryptKey { + // Update tlsManager with actual key. + tlsManager.keyRing = activeChainControl.KeyRing + err := tlsManager.reloadCertificate() + if err != nil { + err := fmt.Errorf("unable to reload TLS credentials: "+ + "%v", err) + ltndLog.Error(err) + } + } + // Now we have created all dependencies necessary to populate and // start the RPC server. err = rpcServer.addDeps( @@ -546,14 +568,10 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, atplManager, server.invoices, tower, chainedAcceptor, ) if err != nil { - err := fmt.Errorf("unable to add deps to RPC server: %v", err) - ltndLog.Error(err) - return err + return mkErr("unable to add deps to RPC server: %v", err) } if err := rpcServer.Start(); err != nil { - err := fmt.Errorf("unable to start RPC server: %v", err) - ltndLog.Error(err) - return err + return mkErr("unable to start RPC server: %v", err) } defer rpcServer.Stop() @@ -561,7 +579,7 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, interceptorChain.SetRPCActive() if err := interceptor.Notifier.NotifyReady(true); err != nil { - return err + return mkErr("error notifying ready: %v", err) } // We'll wait until we're fully synced to continue the start up of the @@ -570,10 +588,7 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, // funds. _, bestHeight, err := activeChainControl.ChainIO.GetBestBlock() if err != nil { - err := fmt.Errorf("unable to determine chain tip: %v", - err) - ltndLog.Error(err) - return err + return mkErr("unable to determine chain tip: %v", err) } ltndLog.Infof("Waiting for chain backend to finish sync, "+ @@ -586,10 +601,8 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, synced, _, err := activeChainControl.Wallet.IsSynced() if err != nil { - err := fmt.Errorf("unable to determine if "+ - "wallet is synced: %v", err) - ltndLog.Error(err) - return err + return mkErr("unable to determine if wallet is "+ + "synced: %v", err) } if synced { @@ -601,10 +614,7 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, _, bestHeight, err = activeChainControl.ChainIO.GetBestBlock() if err != nil { - err := fmt.Errorf("unable to determine chain tip: %v", - err) - ltndLog.Error(err) - return err + return mkErr("unable to determine chain tip: %v", err) } ltndLog.Infof("Chain backend is fully synced (end_height=%v)!", @@ -613,9 +623,7 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, // With all the relevant chains initialized, we can finally start the // server itself. if err := server.Start(); err != nil { - err := fmt.Errorf("unable to start server: %v", err) - ltndLog.Error(err) - return err + return mkErr("unable to start server: %v", err) } defer server.Stop() @@ -627,18 +635,13 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, // stopped together with the autopilot service. if cfg.Autopilot.Active { if err := atplManager.StartAgent(); err != nil { - err := fmt.Errorf("unable to start autopilot agent: %v", - err) - ltndLog.Error(err) - return err + return mkErr("unable to start autopilot agent: %v", err) } } if cfg.Watchtower.Active { if err := tower.Start(); err != nil { - err := fmt.Errorf("unable to start watchtower: %v", err) - ltndLog.Error(err) - return err + return mkErr("unable to start watchtower: %v", err) } defer tower.Stop() } @@ -649,105 +652,336 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, return nil } -// getTLSConfig returns a TLS configuration for the gRPC server and credentials +// TLSManager generates/renews a TLS certificate if needed and returns the +// certificate configuration options needed for gRPC and REST. +type TLSManager struct { + cfg *Config + + certBytes []byte + keyBytes []byte + certData *tls.Certificate + parsedCert *x509.Certificate + certPath string + keyRing keychain.KeyRing + + tlsCfg *tls.Config + tlsReloader *cert.TlsReloader +} + +// getConfig returns a TLS configuration for the gRPC server and credentials // and a proxy destination for the REST reverse proxy. -func getTLSConfig(cfg *Config) ([]grpc.ServerOption, []grpc.DialOption, +func (t *TLSManager) getConfig() ([]grpc.ServerOption, []grpc.DialOption, func(net.Addr) (net.Listener, error), func(), error) { - // Ensure we create TLS key and certificate if they don't exist. - if !fileExists(cfg.TLSCertPath) && !fileExists(cfg.TLSKeyPath) { - rpcsLog.Infof("Generating TLS certificates...") - err := cert.GenCertPair( - "lnd autogenerated cert", cfg.TLSCertPath, - cfg.TLSKeyPath, cfg.TLSExtraIPs, cfg.TLSExtraDomains, - cfg.TLSDisableAutofill, cfg.TLSCertDuration, - ) - if err != nil { - return nil, nil, nil, nil, err + err := t.generateOrRenewCert() + if err != nil { + return nil, nil, nil, nil, err + } + + tlsr, err := cert.NewTLSReloader(t.certBytes, t.keyBytes) + if err != nil { + return nil, nil, nil, nil, err + } + t.tlsReloader = tlsr + tlsCfg := cert.TLSConfFromCert(*t.certData) + t.tlsCfg = tlsCfg + t.tlsCfg.GetCertificate = tlsr.GetCertificateFunc() + + // Now that we know that we have a ceritificate, let's generate the + // required config options. + restCreds, err := credentials.NewClientTLSFromFile(t.certPath, "") + if err != nil { + return nil, nil, nil, nil, err + } + + cleanUp, err := t.setUpLetsEncrypt(tlsCfg) + if err != nil { + return nil, nil, nil, nil, err + } + + serverCreds := credentials.NewTLS(t.tlsCfg) + serverOpts := []grpc.ServerOption{grpc.Creds(serverCreds)} + + // For our REST dial options, we'll still use TLS, but also increase + // the max message size that we'll decode to allow clients to hit + // endpoints which return more data such as the DescribeGraph call. + // We set this to 200MiB atm. Should be the same value as maxMsgRecvSize + // in cmd/lncli/main.go. + restDialOpts := []grpc.DialOption{ + grpc.WithTransportCredentials(restCreds), + grpc.WithDefaultCallOptions( + grpc.MaxCallRecvMsgSize(lnrpc.MaxGrpcMsgSize), + ), + } + + // Return a function closure that can be used to listen on a given + // address with the current TLS config. + restListen := func(addr net.Addr) (net.Listener, error) { + // For restListen we will call ListenOnAddress if TLS is + // disabled. + if t.cfg.DisableRestTLS { + return lncfg.ListenOnAddress(addr) } - rpcsLog.Infof("Done generating TLS certificates") + + return lncfg.TLSListenOnAddress(addr, t.tlsCfg) + } + + return serverOpts, restDialOpts, restListen, cleanUp, nil +} + +// generateOrRenewCert generates a new TLS certificate if we're not using one +// yet or renews it if it's outdated. +func (t *TLSManager) generateOrRenewCert() error { + // Genereate a TLS pair if we don't have one yet. + err := t.createTLSPair() + if err != nil { + return err } certData, parsedCert, err := cert.LoadCert( - cfg.TLSCertPath, cfg.TLSKeyPath, + t.certBytes, t.keyBytes, ) if err != nil { - return nil, nil, nil, nil, err + return err + } + t.certData = &certData + t.parsedCert = parsedCert + + // Check to see if the certificate needs to be renewed. + err = t.certMaintenance() + if err != nil { + return err + } + + return nil +} + +// If a TLS pair doesn't exist yet, we create them and write them to disk. And +// we encrypt the pair if needed. If the TLS pair already exists, we load it +// into memory. +func (t *TLSManager) createTLSPair() error { + // If TLS Key Encryption is on but the keyring is empty then we need to + // generate a temporary certificate. + var emptyKeyRing keychain.KeyRing + if t.cfg.TLSEncryptKey && (t.keyRing == emptyKeyRing) { + rpcsLog.Infof("Generating ephemeral TLS certificates...") + + tmpValidity := 24 * time.Hour + // Append .tmp to the end of the cert for differentiation. + tmpCertPath := t.cfg.TLSCertPath + ".tmp" + t.certPath = tmpCertPath + // Pass in a blank string for the key path so the + // function doesn't write them to disk. + certBytes, keyBytes, err := cert.GenCertPair( + "lnd temporary autogenerated cert", tmpCertPath, + "", t.cfg.TLSExtraIPs, t.cfg.TLSExtraDomains, + t.cfg.TLSDisableAutofill, tmpValidity, + ) + if err != nil { + return err + } + t.certBytes = certBytes + t.keyBytes = keyBytes + + rpcsLog.Infof("Done generating ephemeral TLS certificates") + + } else { + // Ensure we create TLS key and certificate if they don't exist. + if !fileExists(t.cfg.TLSCertPath) && !fileExists(t.cfg.TLSKeyPath) { + rpcsLog.Infof("Generating TLS certificates...") + + certBytes, keyBytes, err := cert.GenCertPair( + "lnd autogenerated cert", t.cfg.TLSCertPath, + t.cfg.TLSKeyPath, t.cfg.TLSExtraIPs, + t.cfg.TLSExtraDomains, t.cfg.TLSDisableAutofill, + t.cfg.TLSCertDuration, + ) + if err != nil { + return err + } + t.certBytes = certBytes + t.keyBytes = keyBytes + + if t.cfg.TLSEncryptKey { + keyBuf := bytes.NewBuffer(t.keyBytes) + var b bytes.Buffer + err := lnencrypt.EncryptPayloadToWriter( + *keyBuf, &b, t.keyRing, + ) + if err != nil { + return err + } + if err = ioutil.WriteFile(t.cfg.TLSKeyPath, b.Bytes(), + 0600); err != nil { + return err + } + t.keyBytes = b.Bytes() + + } else { + keyBuf := bytes.NewBuffer(t.keyBytes) + if err := ioutil.WriteFile(t.cfg.TLSKeyPath, keyBuf.Bytes(), + 0600); err != nil { + return err + } + } + + } else { + rpcsLog.Info("Getting the existing cert data") + certBytes, keyBytes, err := cert.GetCertBytesFromPath(t.cfg.TLSCertPath, t.cfg.TLSKeyPath) + if err != nil { + return err + } + t.certBytes = certBytes + t.keyBytes = keyBytes + + certData, parsedCert, err := cert.LoadCert( + t.certBytes, t.keyBytes, + ) + if err != nil { + return err + } + t.certData = &certData + t.parsedCert = parsedCert + } + t.certPath = t.cfg.TLSCertPath + + rpcsLog.Infof("Done generating TLS certificates") + + err := t.getTLSKey() + if err != nil { + return err + } + } + + return nil +} + +// getTLSKey gets the TLS key, whether it's encrypted on disk or not. +func (t *TLSManager) getTLSKey() error { + // We check to see if the private key is encrypted or plaintext. + // If it's encrypted we need to try to decrypt it so we can use it + // in the gRPC server. + privateKeyPrefix := []byte("-----BEGIN EC PRIVATE KEY-----") + if !bytes.HasPrefix(t.keyBytes, privateKeyPrefix) { + rpcsLog.Info("Saw that the private key is encrypted") + // If the private key is encrypted but the user didn't pass + // --tlsencryptkey we error out. This is because the wallet is + // not unlocked yet and we don't have access to the keys yet + // for decrypt. + if !t.cfg.TLSEncryptKey { + return fmt.Errorf("it appears the TLS key is " + + "encrypted but you didn't pass the " + + "--tlsencryptkey flag. Please restart lnd " + + "with the --tlsencryptkey flag or delete the " + + "TLS files for regeneration") + } + reader := bytes.NewReader(t.keyBytes) + keyBytes, err := lnencrypt.DecryptPayloadFromReader( + reader, t.keyRing, + ) + if err != nil { + return err + } + t.keyBytes = keyBytes + + } else if t.cfg.TLSEncryptKey { + // If the user requests an encrypted key but the key is in plaintext + // we encrypt the key before writing to disk. + keyBuf := bytes.NewBuffer(t.keyBytes) + var b bytes.Buffer + err := lnencrypt.EncryptPayloadToWriter(*keyBuf, &b, t.keyRing) + if err != nil { + return err + } + if err = ioutil.WriteFile(t.cfg.TLSKeyPath, b.Bytes(), 0600); err != nil { + return err + } } - // We check whether the certifcate we have on disk match the IPs and + return nil +} + +// certMaintenance checks if the certificate IP and domains matches the config, +// and renews the certificate if either this data is outdated or the +// certificate is expired. +func (t *TLSManager) certMaintenance() error { + // We check whether the certificate we have on disk match the IPs and // domains specified by the config. If the extra IPs or domains have // changed from when the certificate was created, we will refresh the // certificate if auto refresh is active. refresh := false - if cfg.TLSAutoRefresh { + var err error + if t.cfg.TLSAutoRefresh { refresh, err = cert.IsOutdated( - parsedCert, cfg.TLSExtraIPs, - cfg.TLSExtraDomains, cfg.TLSDisableAutofill, + t.parsedCert, t.cfg.TLSExtraIPs, + t.cfg.TLSExtraDomains, t.cfg.TLSDisableAutofill, ) if err != nil { - return nil, nil, nil, nil, err + return err } } // If the certificate expired or it was outdated, delete it and the TLS // key and generate a new pair. - if time.Now().After(parsedCert.NotAfter) || refresh { + if time.Now().After(t.parsedCert.NotAfter) || refresh { ltndLog.Info("TLS certificate is expired or outdated, " + "generating a new one") - err := os.Remove(cfg.TLSCertPath) + err := os.Remove(t.cfg.TLSCertPath) if err != nil { - return nil, nil, nil, nil, err + return err } - err = os.Remove(cfg.TLSKeyPath) + err = os.Remove(t.cfg.TLSKeyPath) if err != nil { - return nil, nil, nil, nil, err + return err } rpcsLog.Infof("Renewing TLS certificates...") - err = cert.GenCertPair( - "lnd autogenerated cert", cfg.TLSCertPath, - cfg.TLSKeyPath, cfg.TLSExtraIPs, cfg.TLSExtraDomains, - cfg.TLSDisableAutofill, cfg.TLSCertDuration, + certBytes, keyBytes, err := cert.GenCertPair( + "lnd autogenerated cert", t.cfg.TLSCertPath, + t.cfg.TLSKeyPath, t.cfg.TLSExtraIPs, + t.cfg.TLSExtraDomains, t.cfg.TLSDisableAutofill, + t.cfg.TLSCertDuration, ) if err != nil { - return nil, nil, nil, nil, err + return err } + t.certBytes = certBytes + t.keyBytes = keyBytes rpcsLog.Infof("Done renewing TLS certificates") // Reload the certificate data. - certData, _, err = cert.LoadCert( - cfg.TLSCertPath, cfg.TLSKeyPath, + reloadedCertData, _, err := cert.LoadCert( + certBytes, keyBytes, ) if err != nil { - return nil, nil, nil, nil, err + return err } + t.certData = &reloadedCertData } - tlsCfg := cert.TLSConfFromCert(certData) - - restCreds, err := credentials.NewClientTLSFromFile(cfg.TLSCertPath, "") - if err != nil { - return nil, nil, nil, nil, err - } + return nil +} +// setUpLetsEncrypt automatically generates a Let's Encrypt certificate if the +// option is set. +func (t *TLSManager) setUpLetsEncrypt(tlsCfg *tls.Config) (func(), error) { // If Let's Encrypt is enabled, instantiate autocert to request/renew // the certificates. cleanUp := func() {} - if cfg.LetsEncryptDomain != "" { + if t.cfg.LetsEncryptDomain != "" { ltndLog.Infof("Using Let's Encrypt certificate for domain %v", - cfg.LetsEncryptDomain) + t.cfg.LetsEncryptDomain) manager := autocert.Manager{ - Cache: autocert.DirCache(cfg.LetsEncryptDir), + Cache: autocert.DirCache(t.cfg.LetsEncryptDir), Prompt: autocert.AcceptTOS, - HostPolicy: autocert.HostWhitelist(cfg.LetsEncryptDomain), + HostPolicy: autocert.HostWhitelist(t.cfg.LetsEncryptDomain), } srv := &http.Server{ - Addr: cfg.LetsEncryptListen, + Addr: t.cfg.LetsEncryptListen, Handler: manager.HTTPHandler(nil), } shutdownCompleted := make(chan struct{}) @@ -765,7 +999,7 @@ func getTLSConfig(cfg *Config) ([]grpc.ServerOption, []grpc.DialOption, go func() { ltndLog.Infof("Autocert challenge listener started "+ - "at %v", cfg.LetsEncryptListen) + "at %v", t.cfg.LetsEncryptListen) err := srv.ListenAndServe() if err != http.ErrServerClosed { @@ -780,7 +1014,7 @@ func getTLSConfig(cfg *Config) ([]grpc.ServerOption, []grpc.DialOption, lecert, err := manager.GetCertificate(h) if err != nil { ltndLog.Errorf("GetCertificate: %v", err) - return &certData, nil + return t.certData, nil } return lecert, err @@ -790,34 +1024,47 @@ func getTLSConfig(cfg *Config) ([]grpc.ServerOption, []grpc.DialOption, tlsCfg.GetCertificate = getCertificate } - serverCreds := credentials.NewTLS(tlsCfg) - serverOpts := []grpc.ServerOption{grpc.Creds(serverCreds)} + return cleanUp, nil +} - // For our REST dial options, we'll still use TLS, but also increase - // the max message size that we'll decode to allow clients to hit - // endpoints which return more data such as the DescribeGraph call. - // We set this to 200MiB atm. Should be the same value as maxMsgRecvSize - // in cmd/lncli/main.go. - restDialOpts := []grpc.DialOption{ - grpc.WithTransportCredentials(restCreds), - grpc.WithDefaultCallOptions( - grpc.MaxCallRecvMsgSize(1 * 1024 * 1024 * 200), - ), +// reloadCertificate deletes the temporary certificate file and generates a new +// one with the real keyring. +func (t *TLSManager) reloadCertificate() error { + tmpCertPath := t.cfg.TLSCertPath + ".tmp" + err := os.Remove(tmpCertPath) + if err != nil { + ltndLog.Warn("unable to delete temp cert at %v", tmpCertPath) } - // Return a function closure that can be used to listen on a given - // address with the current TLS config. - restListen := func(addr net.Addr) (net.Listener, error) { - // For restListen we will call ListenOnAddress if TLS is - // disabled. - if cfg.DisableRestTLS { - return lncfg.ListenOnAddress(addr) - } + // Ensure the persistent TLS credentials are created + _, _, _, _, err = t.getConfig() + if err != nil { + err := fmt.Errorf("unable to load TLS credentials: %v", err) + ltndLog.Error(err) + return err + } + certBytes, encryptedKeyBytes, err := cert.GetCertBytesFromPath( + t.cfg.TLSCertPath, t.cfg.TLSKeyPath, + ) + if err != nil { + return err + } + t.certBytes = certBytes + + reader := bytes.NewReader(encryptedKeyBytes) + keyBytes, err := lnencrypt.DecryptPayloadFromReader(reader, t.keyRing) + if err != nil { + return err + } + t.keyBytes = keyBytes - return lncfg.TLSListenOnAddress(addr, tlsCfg) + // Switch the server's TLS certificate to the persistent one + err = t.tlsReloader.AttemptReload(t.certBytes, t.keyBytes) + if err != nil { + return err } - return serverOpts, restDialOpts, restListen, cleanUp, nil + return nil } // fileExists reports whether the named file or directory exists. @@ -1001,7 +1248,16 @@ func startRestProxy(cfg *Config, rpcServer *rpcServer, restDialOpts []grpc.DialO }, }, ) - mux := proxy.NewServeMux(customMarshalerOption) + mux := proxy.NewServeMux( + customMarshalerOption, + + // Don't allow falling back to other HTTP methods, we want exact + // matches only. The actual method to be used can be overwritten + // by setting X-HTTP-Method-Override so there should be no + // reason for not specifying the correct method in the first + // place. + proxy.WithDisablePathLengthFallback(), + ) // Register our services with the REST proxy. err := lnrpc.RegisterStateHandlerFromEndpoint( diff --git a/chanbackup/crypto.go b/lnencrypt/crypto.go similarity index 73% rename from chanbackup/crypto.go rename to lnencrypt/crypto.go index 8fdb46f685..f5b4efce2f 100644 --- a/chanbackup/crypto.go +++ b/lnencrypt/crypto.go @@ -1,4 +1,4 @@ -package chanbackup +package lnencrypt import ( "bytes" @@ -15,23 +15,23 @@ import ( // TODO(roasbeef): interface in front of? // baseEncryptionKeyLoc is the KeyLocator that we'll use to derive the base -// encryption key used for encrypting all static channel backups. We use this -// to then derive the actual key that we'll use for encryption. We do this +// encryption key used for encrypting all payloads. We use this to then +// derive the actual key that we'll use for encryption. We do this // rather than using the raw key, as we assume that we can't obtain the raw // keys, and we don't want to require that the HSM know our target cipher for // encryption. // // TODO(roasbeef): possibly unique encrypt? var baseEncryptionKeyLoc = keychain.KeyLocator{ - Family: keychain.KeyFamilyStaticBackup, + Family: keychain.KeyFamilyBaseEncryption, Index: 0, } -// genEncryptionKey derives the key that we'll use to encrypt all of our static -// channel backups. The key itself, is the sha2 of a base key that we get from -// the keyring. We derive the key this way as we don't force the HSM (or any -// future abstractions) to be able to derive and know of the cipher that we'll -// use within our protocol. +// genEncryptionKey derives the key that we'll use to encrypt all of our files +// that are written to disk. The key itself, is the sha2 of a base key that we +// get from the keyring. We derive the key this way as we don't force the HSM +// (or any future abstractions) to be able to derive and know of the cipher +// that we'll use within our protocol. func genEncryptionKey(keyRing keychain.KeyRing) ([]byte, error) { // key = SHA256(baseKey) baseKey, err := keyRing.DeriveKey( @@ -50,18 +50,18 @@ func genEncryptionKey(keyRing keychain.KeyRing) ([]byte, error) { return encryptionKey[:], nil } -// encryptPayloadToWriter attempts to write the set of bytes contained within +// EncryptPayloadToWriter attempts to write the set of bytes contained within // the passed byes.Buffer into the passed io.Writer in an encrypted form. We // use a 24-byte chachapoly AEAD instance with a randomized nonce that's // pre-pended to the final payload and used as associated data in the AEAD. We // use the passed keyRing to generate the encryption key, see genEncryptionKey // for further details. -func encryptPayloadToWriter(payload bytes.Buffer, w io.Writer, +func EncryptPayloadToWriter(payload bytes.Buffer, w io.Writer, keyRing keychain.KeyRing) error { // First, we'll derive the key that we'll use to encrypt the payload - // for safe storage without giving away the details of any of our - // channels. The final operation is: + // for safe storage without giving away the details of any sensitive + // details. The final operation is: // // key = SHA256(baseKey) encryptionKey, err := genEncryptionKey(keyRing) @@ -96,15 +96,14 @@ func encryptPayloadToWriter(payload bytes.Buffer, w io.Writer, return nil } -// decryptPayloadFromReader attempts to decrypt the encrypted bytes within the +// DecryptPayloadFromReader attempts to decrypt the encrypted bytes within the // passed io.Reader instance using the key derived from the passed keyRing. For // further details regarding the key derivation protocol, see the // genEncryptionKey method. -func decryptPayloadFromReader(payload io.Reader, +func DecryptPayloadFromReader(payload io.Reader, keyRing keychain.KeyRing) ([]byte, error) { - // First, we'll re-generate the encryption key that we use for all the - // SCBs. + // First, we'll re-generate the encryption key. encryptionKey, err := genEncryptionKey(keyRing) if err != nil { return nil, err @@ -112,21 +111,20 @@ func decryptPayloadFromReader(payload io.Reader, // Next, we'll read out the entire blob as we need to isolate the nonce // from the rest of the ciphertext. - packedBackup, err := ioutil.ReadAll(payload) + packedPayload, err := ioutil.ReadAll(payload) if err != nil { return nil, err } - if len(packedBackup) < chacha20poly1305.NonceSizeX { + if len(packedPayload) < chacha20poly1305.NonceSizeX { return nil, fmt.Errorf("payload size too small, must be at "+ "least %v bytes", chacha20poly1305.NonceSizeX) } - nonce := packedBackup[:chacha20poly1305.NonceSizeX] - ciphertext := packedBackup[chacha20poly1305.NonceSizeX:] + nonce := packedPayload[:chacha20poly1305.NonceSizeX] + ciphertext := packedPayload[chacha20poly1305.NonceSizeX:] // Now that we have the cipher text and the nonce separated, we can go - // ahead and decrypt the final blob so we can properly serialized the - // SCB. + // ahead and decrypt the final blob so we can properly serialize. cipher, err := chacha20poly1305.NewX(encryptionKey) if err != nil { return nil, err diff --git a/chanbackup/crypto_test.go b/lnencrypt/crypto_test.go similarity index 74% rename from chanbackup/crypto_test.go rename to lnencrypt/crypto_test.go index 6b4b27fe73..5de2983f54 100644 --- a/chanbackup/crypto_test.go +++ b/lnencrypt/crypto_test.go @@ -1,41 +1,20 @@ -package chanbackup +package lnencrypt import ( "bytes" - "fmt" "testing" - "github.com/btcsuite/btcd/btcec" - "github.com/lightningnetwork/lnd/keychain" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/lightningnetwork/lnd/lntest/channels" + "github.com/lightningnetwork/lnd/lntest/mock" ) var ( - testWalletPrivKey = []byte{ - 0x2b, 0xd8, 0x06, 0xc9, 0x7f, 0x0e, 0x00, 0xaf, - 0x1a, 0x1f, 0xc3, 0x32, 0x8f, 0xa7, 0x63, 0xa9, - 0x26, 0x97, 0x23, 0xc8, 0xdb, 0x8f, 0xac, 0x4f, - 0x93, 0xaf, 0x71, 0xdb, 0x18, 0x6d, 0x6e, 0x90, - } + privKey, _ = btcec.PrivKeyFromBytes( + channels.AlicesPrivKey, + ) ) -type mockKeyRing struct { - fail bool -} - -func (m *mockKeyRing) DeriveNextKey(keyFam keychain.KeyFamily) (keychain.KeyDescriptor, error) { - return keychain.KeyDescriptor{}, nil -} -func (m *mockKeyRing) DeriveKey(keyLoc keychain.KeyLocator) (keychain.KeyDescriptor, error) { - if m.fail { - return keychain.KeyDescriptor{}, fmt.Errorf("fail") - } - - _, pub := btcec.PrivKeyFromBytes(btcec.S256(), testWalletPrivKey) - return keychain.KeyDescriptor{ - PubKey: pub, - }, nil -} - // TestEncryptDecryptPayload tests that given a static key, we're able to // properly decrypt and encrypted payload. We also test that we'll reject a // ciphertext that has been modified. @@ -81,14 +60,16 @@ func TestEncryptDecryptPayload(t *testing.T) { }, } - keyRing := &mockKeyRing{} + keyRing := &mock.SecretKeyRing{ + RootKey: privKey, + } for i, payloadCase := range payloadCases { var cipherBuffer bytes.Buffer // First, we'll encrypt the passed payload with our scheme. payloadReader := bytes.NewBuffer(payloadCase.plaintext) - err := encryptPayloadToWriter( + err := EncryptPayloadToWriter( *payloadReader, &cipherBuffer, keyRing, ) if err != nil { @@ -107,7 +88,7 @@ func TestEncryptDecryptPayload(t *testing.T) { cipherBuffer.Write(cipherText) } - plaintext, err := decryptPayloadFromReader(&cipherBuffer, keyRing) + plaintext, err := DecryptPayloadFromReader(&cipherBuffer, keyRing) switch { // If this was meant to be a valid decryption, but we failed, @@ -137,7 +118,9 @@ func TestInvalidKeyEncryption(t *testing.T) { t.Parallel() var b bytes.Buffer - err := encryptPayloadToWriter(b, &b, &mockKeyRing{true}) + keyRing := &mock.SecretKeyRing{} + keyRing.Fail = true + err := EncryptPayloadToWriter(b, &b, keyRing) if err == nil { t.Fatalf("expected error due to fail key gen") } @@ -149,7 +132,9 @@ func TestInvalidKeyDecrytion(t *testing.T) { t.Parallel() var b bytes.Buffer - _, err := decryptPayloadFromReader(&b, &mockKeyRing{true}) + keyRing := &mock.SecretKeyRing{} + keyRing.Fail = true + _, err := DecryptPayloadFromReader(&b, keyRing) if err == nil { t.Fatalf("expected error due to fail key gen") } diff --git a/lnencrypt/test_utils.go b/lnencrypt/test_utils.go new file mode 100644 index 0000000000..cae308ba2d --- /dev/null +++ b/lnencrypt/test_utils.go @@ -0,0 +1,36 @@ +package lnencrypt + +import ( + "fmt" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/lightningnetwork/lnd/keychain" +) + +var ( + testWalletPrivKey = []byte{ + 0x2b, 0xd8, 0x06, 0xc9, 0x7f, 0x0e, 0x00, 0xaf, + 0x1a, 0x1f, 0xc3, 0x32, 0x8f, 0xa7, 0x63, 0xa9, + 0x26, 0x97, 0x23, 0xc8, 0xdb, 0x8f, 0xac, 0x4f, + 0x93, 0xaf, 0x71, 0xdb, 0x18, 0x6d, 0x6e, 0x90, + } +) + +type MockKeyRing struct { + Fail bool +} + +func (m *MockKeyRing) DeriveNextKey(keyFam keychain.KeyFamily) (keychain.KeyDescriptor, error) { + return keychain.KeyDescriptor{}, nil +} + +func (m *MockKeyRing) DeriveKey(keyLoc keychain.KeyLocator) (keychain.KeyDescriptor, error) { + if m.Fail { + return keychain.KeyDescriptor{}, fmt.Errorf("fail") + } + + _, pub := btcec.PrivKeyFromBytes(testWalletPrivKey) + return keychain.KeyDescriptor{ + PubKey: pub, + }, nil +} diff --git a/lnpeer/peer.go b/lnpeer/peer.go index fb6589dd2a..465a41cb90 100644 --- a/lnpeer/peer.go +++ b/lnpeer/peer.go @@ -3,7 +3,7 @@ package lnpeer import ( "net" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/lnwire" diff --git a/lnrpc/Dockerfile b/lnrpc/Dockerfile index d9a02ac05b..1152740d14 100644 --- a/lnrpc/Dockerfile +++ b/lnrpc/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.17.1-buster +FROM golang:1.17.3-buster RUN apt-get update && apt-get install -y \ git \ @@ -19,7 +19,6 @@ ENV GOMODCACHE=/tmp/build/.modcache RUN cd /tmp \ && mkdir -p /tmp/build/.cache \ && mkdir -p /tmp/build/.modcache \ - && export GO111MODULE=on \ && go install google.golang.org/protobuf/cmd/protoc-gen-go@${PROTOBUF_VERSION} \ && go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@${PROTOC_GEN_GO_GRPC_VERSION} \ && go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@${GRPC_GATEWAY_VERSION} \ diff --git a/lnrpc/autopilotrpc/autopilot_server.go b/lnrpc/autopilotrpc/autopilot_server.go index ee8bbc644b..761d5f0926 100644 --- a/lnrpc/autopilotrpc/autopilot_server.go +++ b/lnrpc/autopilotrpc/autopilot_server.go @@ -8,7 +8,7 @@ import ( "encoding/hex" "sync/atomic" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "github.com/lightningnetwork/lnd/autopilot" "github.com/lightningnetwork/lnd/lnrpc" @@ -226,7 +226,7 @@ func (s *Server) QueryScores(ctx context.Context, in *QueryScoresRequest) ( if err != nil { return nil, err } - pubKey, err := btcec.ParsePubKey(pubHex, btcec.S256()) + pubKey, err := btcec.ParsePubKey(pubHex) if err != nil { return nil, err } @@ -283,7 +283,7 @@ func (s *Server) SetScores(ctx context.Context, if err != nil { return nil, err } - pubKey, err := btcec.ParsePubKey(pubHex, btcec.S256()) + pubKey, err := btcec.ParsePubKey(pubHex) if err != nil { return nil, err } diff --git a/lnrpc/autopilotrpc/driver.go b/lnrpc/autopilotrpc/driver.go index c62ecc1b4a..850540df4c 100644 --- a/lnrpc/autopilotrpc/driver.go +++ b/lnrpc/autopilotrpc/driver.go @@ -36,7 +36,7 @@ func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( } // Before we try to make the new service instance, we'll perform - // some sanity checks on the arguments to ensure that they're useable. + // some sanity checks on the arguments to ensure that they're usable. switch { case config.Manager == nil: return nil, nil, fmt.Errorf("Manager must be set to create " + diff --git a/lnrpc/autopilotrpc/log.go b/lnrpc/autopilotrpc/log.go index e844bcab9d..113486d0d9 100644 --- a/lnrpc/autopilotrpc/log.go +++ b/lnrpc/autopilotrpc/log.go @@ -5,7 +5,7 @@ import ( "github.com/lightningnetwork/lnd/build" ) -// log is a logger that is initialized with no output filters. This means the +// log is a logger that is initialized with no output filters. This means the // package will not perform any logging by default until the caller requests // it. var log btclog.Logger @@ -21,25 +21,9 @@ func DisableLog() { UseLogger(btclog.Disabled) } -// UseLogger uses a specified Logger to output package logging info. This +// UseLogger uses a specified Logger to output package logging info. This // should be used in preference to SetLogWriter if the caller is also using // btclog. func UseLogger(logger btclog.Logger) { log = logger } - -// logClosure is used to provide a closure over expensive logging operations so -// don't have to be performed when the logging level doesn't warrant it. -type logClosure func() string // nolint:unused - -// String invokes the underlying function and returns the result. -func (c logClosure) String() string { - return c() -} - -// newLogClosure returns a new closure over a function that returns a string -// which itself provides a Stringer interface so that it can be used with the -// logging system. -func newLogClosure(c func() string) logClosure { // nolint:unused - return logClosure(c) -} diff --git a/lnrpc/chainrpc/chainnotifier.pb.go b/lnrpc/chainrpc/chainnotifier.pb.go index 46416767e2..19317b87b0 100644 --- a/lnrpc/chainrpc/chainnotifier.pb.go +++ b/lnrpc/chainrpc/chainnotifier.pb.go @@ -372,7 +372,11 @@ type SpendRequest struct { // //The outpoint for which we should request a spend notification for. If set to //a zero outpoint, then the spend notification will be requested for the - //script instead. + //script instead. A zero or nil outpoint is not supported for Taproot spends + //because the output script cannot reliably be computed from the witness alone + //and the spent output script is not always available in the rescan context. + //So an outpoint must _always_ be specified when registering a spend + //notification for a Taproot output. Outpoint *Outpoint `protobuf:"bytes,1,opt,name=outpoint,proto3" json:"outpoint,omitempty"` // //The output script for the outpoint above. This will be used by light clients diff --git a/lnrpc/chainrpc/chainnotifier.proto b/lnrpc/chainrpc/chainnotifier.proto index 5c9202b2a5..4045d81c37 100644 --- a/lnrpc/chainrpc/chainnotifier.proto +++ b/lnrpc/chainrpc/chainnotifier.proto @@ -12,9 +12,11 @@ service ChainNotifier { registers an intent for a client to be notified once a confirmation request has reached its required number of confirmations on-chain. - A client can specify whether the confirmation request should be for a - particular transaction by its hash or for an output script by specifying a - zero hash. + A confirmation request must have a valid output script. It is also possible + to give a transaction ID. If the transaction ID is not set, a notification + is sent once the output script confirms. If the transaction ID is also set, + a notification is sent once the output script confirms in the given + transaction. */ rpc RegisterConfirmationsNtfn (ConfRequest) returns (stream ConfEvent); @@ -119,7 +121,11 @@ message SpendRequest { /* The outpoint for which we should request a spend notification for. If set to a zero outpoint, then the spend notification will be requested for the - script instead. + script instead. A zero or nil outpoint is not supported for Taproot spends + because the output script cannot reliably be computed from the witness alone + and the spent output script is not always available in the rescan context. + So an outpoint must _always_ be specified when registering a spend + notification for a Taproot output. */ Outpoint outpoint = 1; diff --git a/lnrpc/chainrpc/chainnotifier.swagger.json b/lnrpc/chainrpc/chainnotifier.swagger.json index ee684f1692..a1c4b1d2c5 100644 --- a/lnrpc/chainrpc/chainnotifier.swagger.json +++ b/lnrpc/chainrpc/chainnotifier.swagger.json @@ -62,7 +62,7 @@ "/v2/chainnotifier/register/confirmations": { "post": { "summary": "RegisterConfirmationsNtfn is a synchronous response-streaming RPC that\nregisters an intent for a client to be notified once a confirmation request\nhas reached its required number of confirmations on-chain.", - "description": "A client can specify whether the confirmation request should be for a\nparticular transaction by its hash or for an output script by specifying a\nzero hash.", + "description": "A confirmation request must have a valid output script. It is also possible\nto give a transaction ID. If the transaction ID is not set, a notification\nis sent once the output script confirms. If the transaction ID is also set,\na notification is sent once the output script confirms in the given\ntransaction.", "operationId": "ChainNotifier_RegisterConfirmationsNtfn", "responses": { "200": { @@ -290,7 +290,7 @@ "properties": { "outpoint": { "$ref": "#/definitions/chainrpcOutpoint", - "description": "The outpoint for which we should request a spend notification for. If set to\na zero outpoint, then the spend notification will be requested for the\nscript instead." + "description": "The outpoint for which we should request a spend notification for. If set to\na zero outpoint, then the spend notification will be requested for the\nscript instead. A zero or nil outpoint is not supported for Taproot spends\nbecause the output script cannot reliably be computed from the witness alone\nand the spent output script is not always available in the rescan context.\nSo an outpoint must _always_ be specified when registering a spend\nnotification for a Taproot output." }, "script": { "type": "string", diff --git a/lnrpc/chainrpc/chainnotifier_grpc.pb.go b/lnrpc/chainrpc/chainnotifier_grpc.pb.go index c04140d1b4..9b96db4c9e 100644 --- a/lnrpc/chainrpc/chainnotifier_grpc.pb.go +++ b/lnrpc/chainrpc/chainnotifier_grpc.pb.go @@ -23,9 +23,11 @@ type ChainNotifierClient interface { //registers an intent for a client to be notified once a confirmation request //has reached its required number of confirmations on-chain. // - //A client can specify whether the confirmation request should be for a - //particular transaction by its hash or for an output script by specifying a - //zero hash. + //A confirmation request must have a valid output script. It is also possible + //to give a transaction ID. If the transaction ID is not set, a notification + //is sent once the output script confirms. If the transaction ID is also set, + //a notification is sent once the output script confirms in the given + //transaction. RegisterConfirmationsNtfn(ctx context.Context, in *ConfRequest, opts ...grpc.CallOption) (ChainNotifier_RegisterConfirmationsNtfnClient, error) // //RegisterSpendNtfn is a synchronous response-streaming RPC that registers an @@ -161,9 +163,11 @@ type ChainNotifierServer interface { //registers an intent for a client to be notified once a confirmation request //has reached its required number of confirmations on-chain. // - //A client can specify whether the confirmation request should be for a - //particular transaction by its hash or for an output script by specifying a - //zero hash. + //A confirmation request must have a valid output script. It is also possible + //to give a transaction ID. If the transaction ID is not set, a notification + //is sent once the output script confirms. If the transaction ID is also set, + //a notification is sent once the output script confirms in the given + //transaction. RegisterConfirmationsNtfn(*ConfRequest, ChainNotifier_RegisterConfirmationsNtfnServer) error // //RegisterSpendNtfn is a synchronous response-streaming RPC that registers an diff --git a/lnrpc/chainrpc/log.go b/lnrpc/chainrpc/log.go index 0d47ea9710..6b68e324aa 100644 --- a/lnrpc/chainrpc/log.go +++ b/lnrpc/chainrpc/log.go @@ -5,7 +5,7 @@ import ( "github.com/lightningnetwork/lnd/build" ) -// log is a logger that is initialized with no output filters. This +// log is a logger that is initialized with no output filters. This // means the package will not perform any logging by default until the caller // requests it. var log btclog.Logger @@ -27,19 +27,3 @@ func DisableLog() { func UseLogger(logger btclog.Logger) { log = logger } - -// logClosure is used to provide a closure over expensive logging operations so -// don't have to be performed when the logging level doesn't warrant it. -type logClosure func() string // nolint:unused - -// String invokes the underlying function and returns the result. -func (c logClosure) String() string { - return c() -} - -// newLogClosure returns a new closure over a function that returns a string -// which itself provides a Stringer interface so that it can be used with the -// logging system. -func newLogClosure(c func() string) logClosure { // nolint:unused - return logClosure(c) -} diff --git a/lnrpc/devrpc/config_active.go b/lnrpc/devrpc/config_active.go new file mode 100644 index 0000000000..6fc274f2e9 --- /dev/null +++ b/lnrpc/devrpc/config_active.go @@ -0,0 +1,19 @@ +//go:build dev +// +build dev + +package devrpc + +import ( + "github.com/btcsuite/btcd/chaincfg" + "github.com/lightningnetwork/lnd/channeldb" +) + +// Config is the primary configuration struct for the DEV RPC server. It +// contains all the items required for the rpc server to carry out its +// duties. Any fields with struct tags are meant to be parsed as normal +// configuration options, while if able to be populated, the latter fields MUST +// also be specified. +type Config struct { + ActiveNetParams *chaincfg.Params + GraphDB *channeldb.ChannelGraph +} diff --git a/lnrpc/devrpc/config_default.go b/lnrpc/devrpc/config_default.go new file mode 100644 index 0000000000..88328039f9 --- /dev/null +++ b/lnrpc/devrpc/config_default.go @@ -0,0 +1,7 @@ +//go:build !dev +// +build !dev + +package devrpc + +// Config is empty for non-devrpc builds. +type Config struct{} diff --git a/lnrpc/devrpc/dev.pb.go b/lnrpc/devrpc/dev.pb.go new file mode 100644 index 0000000000..c9199f753a --- /dev/null +++ b/lnrpc/devrpc/dev.pb.go @@ -0,0 +1,144 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.26.0 +// protoc v3.6.1 +// source: devrpc/dev.proto + +package devrpc + +import ( + lnrpc "github.com/lightningnetwork/lnd/lnrpc" + 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) +) + +type ImportGraphResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *ImportGraphResponse) Reset() { + *x = ImportGraphResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_devrpc_dev_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ImportGraphResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ImportGraphResponse) ProtoMessage() {} + +func (x *ImportGraphResponse) ProtoReflect() protoreflect.Message { + mi := &file_devrpc_dev_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ImportGraphResponse.ProtoReflect.Descriptor instead. +func (*ImportGraphResponse) Descriptor() ([]byte, []int) { + return file_devrpc_dev_proto_rawDescGZIP(), []int{0} +} + +var File_devrpc_dev_proto protoreflect.FileDescriptor + +var file_devrpc_dev_proto_rawDesc = []byte{ + 0x0a, 0x10, 0x64, 0x65, 0x76, 0x72, 0x70, 0x63, 0x2f, 0x64, 0x65, 0x76, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x12, 0x06, 0x64, 0x65, 0x76, 0x72, 0x70, 0x63, 0x1a, 0x0f, 0x6c, 0x69, 0x67, 0x68, + 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x15, 0x0a, 0x13, 0x49, + 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x47, 0x72, 0x61, 0x70, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x32, 0x46, 0x0a, 0x03, 0x44, 0x65, 0x76, 0x12, 0x3f, 0x0a, 0x0b, 0x49, 0x6d, 0x70, + 0x6f, 0x72, 0x74, 0x47, 0x72, 0x61, 0x70, 0x68, 0x12, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x47, 0x72, 0x61, 0x70, 0x68, 0x1a, 0x1b, 0x2e, + 0x64, 0x65, 0x76, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x47, 0x72, 0x61, + 0x70, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x2e, 0x5a, 0x2c, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, + 0x6e, 0x67, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2f, 0x64, 0x65, 0x76, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, +} + +var ( + file_devrpc_dev_proto_rawDescOnce sync.Once + file_devrpc_dev_proto_rawDescData = file_devrpc_dev_proto_rawDesc +) + +func file_devrpc_dev_proto_rawDescGZIP() []byte { + file_devrpc_dev_proto_rawDescOnce.Do(func() { + file_devrpc_dev_proto_rawDescData = protoimpl.X.CompressGZIP(file_devrpc_dev_proto_rawDescData) + }) + return file_devrpc_dev_proto_rawDescData +} + +var file_devrpc_dev_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_devrpc_dev_proto_goTypes = []interface{}{ + (*ImportGraphResponse)(nil), // 0: devrpc.ImportGraphResponse + (*lnrpc.ChannelGraph)(nil), // 1: lnrpc.ChannelGraph +} +var file_devrpc_dev_proto_depIdxs = []int32{ + 1, // 0: devrpc.Dev.ImportGraph:input_type -> lnrpc.ChannelGraph + 0, // 1: devrpc.Dev.ImportGraph:output_type -> devrpc.ImportGraphResponse + 1, // [1:2] is the sub-list for method output_type + 0, // [0:1] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_devrpc_dev_proto_init() } +func file_devrpc_dev_proto_init() { + if File_devrpc_dev_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_devrpc_dev_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ImportGraphResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_devrpc_dev_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_devrpc_dev_proto_goTypes, + DependencyIndexes: file_devrpc_dev_proto_depIdxs, + MessageInfos: file_devrpc_dev_proto_msgTypes, + }.Build() + File_devrpc_dev_proto = out.File + file_devrpc_dev_proto_rawDesc = nil + file_devrpc_dev_proto_goTypes = nil + file_devrpc_dev_proto_depIdxs = nil +} diff --git a/lnrpc/devrpc/dev.pb.gw.go b/lnrpc/devrpc/dev.pb.gw.go new file mode 100644 index 0000000000..d702804cd6 --- /dev/null +++ b/lnrpc/devrpc/dev.pb.gw.go @@ -0,0 +1,168 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: devrpc/dev.proto + +/* +Package devrpc is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package devrpc + +import ( + "context" + "io" + "net/http" + + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/grpc-ecosystem/grpc-gateway/v2/utilities" + "github.com/lightningnetwork/lnd/lnrpc" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = metadata.Join + +func request_Dev_ImportGraph_0(ctx context.Context, marshaler runtime.Marshaler, client DevClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq lnrpc.ChannelGraph + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.ImportGraph(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Dev_ImportGraph_0(ctx context.Context, marshaler runtime.Marshaler, server DevServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq lnrpc.ChannelGraph + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.ImportGraph(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterDevHandlerServer registers the http handlers for service Dev to "mux". +// UnaryRPC :call DevServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterDevHandlerFromEndpoint instead. +func RegisterDevHandlerServer(ctx context.Context, mux *runtime.ServeMux, server DevServer) error { + + mux.Handle("POST", pattern_Dev_ImportGraph_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/devrpc.Dev/ImportGraph", runtime.WithHTTPPathPattern("/v2/dev/importgraph")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Dev_ImportGraph_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Dev_ImportGraph_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterDevHandlerFromEndpoint is same as RegisterDevHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterDevHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterDevHandler(ctx, mux, conn) +} + +// RegisterDevHandler registers the http handlers for service Dev to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterDevHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterDevHandlerClient(ctx, mux, NewDevClient(conn)) +} + +// RegisterDevHandlerClient registers the http handlers for service Dev +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "DevClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "DevClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "DevClient" to call the correct interceptors. +func RegisterDevHandlerClient(ctx context.Context, mux *runtime.ServeMux, client DevClient) error { + + mux.Handle("POST", pattern_Dev_ImportGraph_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req, "/devrpc.Dev/ImportGraph", runtime.WithHTTPPathPattern("/v2/dev/importgraph")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Dev_ImportGraph_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Dev_ImportGraph_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_Dev_ImportGraph_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "dev", "importgraph"}, "")) +) + +var ( + forward_Dev_ImportGraph_0 = runtime.ForwardResponseMessage +) diff --git a/lnrpc/devrpc/dev.pb.json.go b/lnrpc/devrpc/dev.pb.json.go new file mode 100644 index 0000000000..13927671f6 --- /dev/null +++ b/lnrpc/devrpc/dev.pb.json.go @@ -0,0 +1,51 @@ +// Code generated by falafel 0.9.1. DO NOT EDIT. +// source: dev.proto + +// +build js + +package devrpc + +import ( + "context" + + gateway "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/lightningnetwork/lnd/lnrpc" + "google.golang.org/grpc" + "google.golang.org/protobuf/encoding/protojson" +) + +func RegisterDevJSONCallbacks(registry map[string]func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error))) { + + marshaler := &gateway.JSONPb{ + MarshalOptions: protojson.MarshalOptions{ + UseProtoNames: true, + EmitUnpopulated: true, + }, + } + + registry["devrpc.Dev.ImportGraph"] = func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { + + req := &lnrpc.ChannelGraph{} + err := marshaler.Unmarshal([]byte(reqJSON), req) + if err != nil { + callback("", err) + return + } + + client := NewDevClient(conn) + resp, err := client.ImportGraph(ctx, req) + if err != nil { + callback("", err) + return + } + + respBytes, err := marshaler.Marshal(resp) + if err != nil { + callback("", err) + return + } + callback(string(respBytes), nil) + } +} diff --git a/lnrpc/devrpc/dev.proto b/lnrpc/devrpc/dev.proto new file mode 100644 index 0000000000..50ed70fddb --- /dev/null +++ b/lnrpc/devrpc/dev.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +import "lightning.proto"; + +package devrpc; + +option go_package = "github.com/lightningnetwork/lnd/lnrpc/devrpc"; + +service Dev { + /* + ImportGraph imports a ChannelGraph into the graph database. Should only be + used for development. + */ + rpc ImportGraph (lnrpc.ChannelGraph) returns (ImportGraphResponse); +} + +message ImportGraphResponse { +} diff --git a/lnrpc/devrpc/dev.swagger.json b/lnrpc/devrpc/dev.swagger.json new file mode 100644 index 0000000000..14a4bf0faf --- /dev/null +++ b/lnrpc/devrpc/dev.swagger.json @@ -0,0 +1,230 @@ +{ + "swagger": "2.0", + "info": { + "title": "devrpc/dev.proto", + "version": "version not set" + }, + "tags": [ + { + "name": "Dev" + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": { + "/v2/dev/importgraph": { + "post": { + "summary": "ImportGraph imports a ChannelGraph into the graph database. Should only be\nused for development.", + "operationId": "Dev_ImportGraph", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/devrpcImportGraphResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/lnrpcChannelGraph" + } + } + ], + "tags": [ + "Dev" + ] + } + } + }, + "definitions": { + "devrpcImportGraphResponse": { + "type": "object" + }, + "lnrpcChannelEdge": { + "type": "object", + "properties": { + "channel_id": { + "type": "string", + "format": "uint64", + "description": "The unique channel ID for the channel. The first 3 bytes are the block\nheight, the next 3 the index within the block, and the last 2 bytes are the\noutput index for the channel." + }, + "chan_point": { + "type": "string" + }, + "last_update": { + "type": "integer", + "format": "int64" + }, + "node1_pub": { + "type": "string" + }, + "node2_pub": { + "type": "string" + }, + "capacity": { + "type": "string", + "format": "int64" + }, + "node1_policy": { + "$ref": "#/definitions/lnrpcRoutingPolicy" + }, + "node2_policy": { + "$ref": "#/definitions/lnrpcRoutingPolicy" + } + }, + "description": "A fully authenticated channel along with all its unique attributes.\nOnce an authenticated channel announcement has been processed on the network,\nthen an instance of ChannelEdgeInfo encapsulating the channels attributes is\nstored. The other portions relevant to routing policy of a channel are stored\nwithin a ChannelEdgePolicy for each direction of the channel." + }, + "lnrpcChannelGraph": { + "type": "object", + "properties": { + "nodes": { + "type": "array", + "items": { + "$ref": "#/definitions/lnrpcLightningNode" + }, + "title": "The list of `LightningNode`s in this channel graph" + }, + "edges": { + "type": "array", + "items": { + "$ref": "#/definitions/lnrpcChannelEdge" + }, + "title": "The list of `ChannelEdge`s in this channel graph" + } + }, + "description": "Returns a new instance of the directed channel graph." + }, + "lnrpcFeature": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "is_required": { + "type": "boolean" + }, + "is_known": { + "type": "boolean" + } + } + }, + "lnrpcLightningNode": { + "type": "object", + "properties": { + "last_update": { + "type": "integer", + "format": "int64" + }, + "pub_key": { + "type": "string" + }, + "alias": { + "type": "string" + }, + "addresses": { + "type": "array", + "items": { + "$ref": "#/definitions/lnrpcNodeAddress" + } + }, + "color": { + "type": "string" + }, + "features": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/lnrpcFeature" + } + } + }, + "description": "An individual vertex/node within the channel graph. A node is\nconnected to other nodes by one or more channel edges emanating from it. As the\ngraph is directed, a node will also have an incoming edge attached to it for\neach outgoing edge." + }, + "lnrpcNodeAddress": { + "type": "object", + "properties": { + "network": { + "type": "string" + }, + "addr": { + "type": "string" + } + } + }, + "lnrpcRoutingPolicy": { + "type": "object", + "properties": { + "time_lock_delta": { + "type": "integer", + "format": "int64" + }, + "min_htlc": { + "type": "string", + "format": "int64" + }, + "fee_base_msat": { + "type": "string", + "format": "int64" + }, + "fee_rate_milli_msat": { + "type": "string", + "format": "int64" + }, + "disabled": { + "type": "boolean" + }, + "max_htlc_msat": { + "type": "string", + "format": "uint64" + }, + "last_update": { + "type": "integer", + "format": "int64" + } + } + }, + "protobufAny": { + "type": "object", + "properties": { + "type_url": { + "type": "string" + }, + "value": { + "type": "string", + "format": "byte" + } + } + }, + "rpcStatus": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "$ref": "#/definitions/protobufAny" + } + } + } + } + } +} diff --git a/lnrpc/devrpc/dev.yaml b/lnrpc/devrpc/dev.yaml new file mode 100644 index 0000000000..18c4e26b40 --- /dev/null +++ b/lnrpc/devrpc/dev.yaml @@ -0,0 +1,8 @@ +type: google.api.Service +config_version: 3 + +http: + rules: + - selector: devrpc.Dev.ImportGraph + post: "/v2/dev/importgraph" + body: "*" diff --git a/lnrpc/devrpc/dev_grpc.pb.go b/lnrpc/devrpc/dev_grpc.pb.go new file mode 100644 index 0000000000..f76a459cc2 --- /dev/null +++ b/lnrpc/devrpc/dev_grpc.pb.go @@ -0,0 +1,108 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. + +package devrpc + +import ( + context "context" + lnrpc "github.com/lightningnetwork/lnd/lnrpc" + 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 + +// DevClient is the client API for Dev 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 DevClient interface { + // + //ImportGraph imports a ChannelGraph into the graph database. Should only be + //used for development. + ImportGraph(ctx context.Context, in *lnrpc.ChannelGraph, opts ...grpc.CallOption) (*ImportGraphResponse, error) +} + +type devClient struct { + cc grpc.ClientConnInterface +} + +func NewDevClient(cc grpc.ClientConnInterface) DevClient { + return &devClient{cc} +} + +func (c *devClient) ImportGraph(ctx context.Context, in *lnrpc.ChannelGraph, opts ...grpc.CallOption) (*ImportGraphResponse, error) { + out := new(ImportGraphResponse) + err := c.cc.Invoke(ctx, "/devrpc.Dev/ImportGraph", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// DevServer is the server API for Dev service. +// All implementations must embed UnimplementedDevServer +// for forward compatibility +type DevServer interface { + // + //ImportGraph imports a ChannelGraph into the graph database. Should only be + //used for development. + ImportGraph(context.Context, *lnrpc.ChannelGraph) (*ImportGraphResponse, error) + mustEmbedUnimplementedDevServer() +} + +// UnimplementedDevServer must be embedded to have forward compatible implementations. +type UnimplementedDevServer struct { +} + +func (UnimplementedDevServer) ImportGraph(context.Context, *lnrpc.ChannelGraph) (*ImportGraphResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ImportGraph not implemented") +} +func (UnimplementedDevServer) mustEmbedUnimplementedDevServer() {} + +// UnsafeDevServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to DevServer will +// result in compilation errors. +type UnsafeDevServer interface { + mustEmbedUnimplementedDevServer() +} + +func RegisterDevServer(s grpc.ServiceRegistrar, srv DevServer) { + s.RegisterService(&Dev_ServiceDesc, srv) +} + +func _Dev_ImportGraph_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(lnrpc.ChannelGraph) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DevServer).ImportGraph(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/devrpc.Dev/ImportGraph", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DevServer).ImportGraph(ctx, req.(*lnrpc.ChannelGraph)) + } + return interceptor(ctx, in, info, handler) +} + +// Dev_ServiceDesc is the grpc.ServiceDesc for Dev service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Dev_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "devrpc.Dev", + HandlerType: (*DevServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "ImportGraph", + Handler: _Dev_ImportGraph_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "devrpc/dev.proto", +} diff --git a/lnrpc/devrpc/dev_server.go b/lnrpc/devrpc/dev_server.go new file mode 100644 index 0000000000..d6b2f1f68a --- /dev/null +++ b/lnrpc/devrpc/dev_server.go @@ -0,0 +1,342 @@ +//go:build dev +// +build dev + +package devrpc + +import ( + "context" + "encoding/hex" + "fmt" + "strconv" + "strings" + "sync/atomic" + "time" + + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/lncfg" + "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lnwire" + "google.golang.org/grpc" + "gopkg.in/macaroon-bakery.v2/bakery" +) + +const ( + // subServerName is the name of the sub rpc server. We'll use this name + // to register ourselves, and we also require that the main + // SubServerConfigDispatcher instance recognize tt as the name of our + // RPC service. + subServerName = "DevRPC" +) + +var ( + // macPermissions maps RPC calls to the permissions they require. + macPermissions = map[string][]bakery.Op{ + "/devrpc.Dev/ImportGraph": {{ + Entity: "offchain", + Action: "write", + }}, + } +) + +// ServerShell is a shell struct holding a reference to the actual sub-server. +// It is used to register the gRPC sub-server with the root server before we +// have the necessary dependencies to populate the actual sub-server. +type ServerShell struct { + DevServer +} + +// Server is a sub-server of the main RPC server: the dev RPC. This sub +// RPC server allows developers to set and query LND state that is not possible +// during normal operation. +type Server struct { + started int32 // To be used atomically. + shutdown int32 // To be used atomically. + + // Required by the grpc-gateway/v2 library for forward compatibility. + // Must be after the atomically used variables to not break struct + // alignment. + UnimplementedDevServer + + cfg *Config +} + +// A compile time check to ensure that Server fully implements the +// DevServer gRPC service. +var _ DevServer = (*Server)(nil) + +// New returns a new instance of the devrpc Dev sub-server. We also return the +// set of permissions for the macaroons that we may create within this method. +// If the macaroons we need aren't found in the filepath, then we'll create them +// on start up. If we're unable to locate, or create the macaroons we need, then +// we'll return with an error. +func New(cfg *Config) (*Server, lnrpc.MacaroonPerms, error) { + // We don't create any new macaroons for this subserver, instead reuse + // existing onchain/offchain permissions. + server := &Server{ + cfg: cfg, + } + + return server, macPermissions, nil +} + +// Start launches any helper goroutines required for the Server to function. +// +// NOTE: This is part of the lnrpc.SubServer interface. +func (s *Server) Start() error { + if atomic.AddInt32(&s.started, 1) != 1 { + return nil + } + + return nil +} + +// Stop signals any active goroutines for a graceful closure. +// +// NOTE: This is part of the lnrpc.SubServer interface. +func (s *Server) Stop() error { + if atomic.AddInt32(&s.shutdown, 1) != 1 { + return nil + } + + return nil +} + +// Name returns a unique string representation of the sub-server. This can be +// used to identify the sub-server and also de-duplicate them. +// +// NOTE: This is part of the lnrpc.SubServer interface. +func (s *Server) Name() string { + return subServerName +} + +// RegisterWithRootServer will be called by the root gRPC server to direct a +// sub RPC server to register itself with the main gRPC root server. Until this +// is called, each sub-server won't be able to have +// requests routed towards it. +// +// NOTE: This is part of the lnrpc.GrpcHandler interface. +func (r *ServerShell) RegisterWithRootServer(grpcServer *grpc.Server) error { + // We make sure that we register it with the main gRPC server to ensure + // all our methods are routed properly. + RegisterDevServer(grpcServer, r) + + log.Debugf("DEV RPC server successfully register with root the " + + "gRPC server") + + return nil +} + +// RegisterWithRestServer will be called by the root REST mux to direct a sub +// RPC server to register itself with the main REST mux server. Until this is +// called, each sub-server won't be able to have requests routed towards it. +// +// NOTE: This is part of the lnrpc.GrpcHandler interface. +func (r *ServerShell) RegisterWithRestServer(ctx context.Context, + mux *runtime.ServeMux, dest string, opts []grpc.DialOption) error { + + // We make sure that we register it with the main REST server to ensure + // all our methods are routed properly. + err := RegisterDevHandlerFromEndpoint(ctx, mux, dest, opts) + if err != nil { + log.Errorf("Could not register DEV REST server with the root "+ + "REST server: %v", err) + return err + } + + log.Debugf("DEV REST server successfully registered with the root " + + "REST server") + return nil +} + +// CreateSubServer populates the subserver's dependencies using the passed +// SubServerConfigDispatcher. This method should fully initialize the +// sub-server instance, making it ready for action. It returns the macaroon +// permissions that the sub-server wishes to pass on to the root server for all +// methods routed towards it. +// +// NOTE: This is part of the lnrpc.GrpcHandler interface. +func (r *ServerShell) CreateSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( + lnrpc.SubServer, lnrpc.MacaroonPerms, error) { + + subServer, macPermissions, err := createNewSubServer(configRegistry) + if err != nil { + return nil, nil, err + } + + r.DevServer = subServer + return subServer, macPermissions, nil +} + +func parseOutPoint(s string) (*wire.OutPoint, error) { + split := strings.Split(s, ":") + if len(split) != 2 { + return nil, fmt.Errorf("expecting outpoint to be in format of: " + + "txid:index") + } + + index, err := strconv.ParseInt(split[1], 10, 32) + if err != nil { + return nil, fmt.Errorf("unable to decode output index: %v", err) + } + + txid, err := chainhash.NewHashFromStr(split[0]) + if err != nil { + return nil, fmt.Errorf("unable to parse hex string: %v", err) + } + + return &wire.OutPoint{ + Hash: *txid, + Index: uint32(index), + }, nil +} + +func parsePubKey(pubKeyStr string) ([33]byte, error) { + var pubKey [33]byte + pubKeyBytes, err := hex.DecodeString(pubKeyStr) + if err != nil || len(pubKeyBytes) != 33 { + return pubKey, fmt.Errorf("invalid pubkey: %v", pubKeyStr) + } + + copy(pubKey[:], pubKeyBytes) + return pubKey, nil +} + +// ImportGraph imports a graph dump (without auth proofs). +// +// NOTE: Part of the DevServer interface. +func (s *Server) ImportGraph(ctx context.Context, + graph *lnrpc.ChannelGraph) (*ImportGraphResponse, error) { + + // Obtain the pointer to the global singleton channel graph. + graphDB := s.cfg.GraphDB + + var err error + for _, rpcNode := range graph.Nodes { + node := &channeldb.LightningNode{ + HaveNodeAnnouncement: true, + LastUpdate: time.Unix( + int64(rpcNode.LastUpdate), 0, + ), + Alias: rpcNode.Alias, + } + + node.PubKeyBytes, err = parsePubKey(rpcNode.PubKey) + if err != nil { + return nil, err + } + + featureBits := make([]lnwire.FeatureBit, 0, len(rpcNode.Features)) + featureNames := make(map[lnwire.FeatureBit]string) + + for featureBit, feature := range rpcNode.Features { + featureBits = append( + featureBits, lnwire.FeatureBit(featureBit), + ) + + featureNames[lnwire.FeatureBit(featureBit)] = feature.Name + } + + featureVector := lnwire.NewRawFeatureVector(featureBits...) + node.Features = lnwire.NewFeatureVector( + featureVector, featureNames, + ) + + node.Color, err = lncfg.ParseHexColor(rpcNode.Color) + if err != nil { + return nil, err + } + + if err := graphDB.AddLightningNode(node); err != nil { + return nil, fmt.Errorf("unable to add node %v: %v", + rpcNode.PubKey, err) + } + + log.Debugf("Imported node: %v", rpcNode.PubKey) + } + + for _, rpcEdge := range graph.Edges { + rpcEdge := rpcEdge + + edge := &channeldb.ChannelEdgeInfo{ + ChannelID: rpcEdge.ChannelId, + ChainHash: *s.cfg.ActiveNetParams.GenesisHash, + Capacity: btcutil.Amount(rpcEdge.Capacity), + } + + edge.NodeKey1Bytes, err = parsePubKey(rpcEdge.Node1Pub) + if err != nil { + return nil, err + } + + edge.NodeKey2Bytes, err = parsePubKey(rpcEdge.Node2Pub) + if err != nil { + return nil, err + } + + channelPoint, err := parseOutPoint(rpcEdge.ChanPoint) + if err != nil { + return nil, err + } + edge.ChannelPoint = *channelPoint + + if err := graphDB.AddChannelEdge(edge); err != nil { + return nil, fmt.Errorf("unable to add edge %v: %v", + rpcEdge.ChanPoint, err) + } + + makePolicy := func(rpcPolicy *lnrpc.RoutingPolicy) *channeldb.ChannelEdgePolicy { + policy := &channeldb.ChannelEdgePolicy{ + ChannelID: rpcEdge.ChannelId, + LastUpdate: time.Unix( + int64(rpcPolicy.LastUpdate), 0, + ), + TimeLockDelta: uint16( + rpcPolicy.TimeLockDelta, + ), + MinHTLC: lnwire.MilliSatoshi( + rpcPolicy.MinHtlc, + ), + FeeBaseMSat: lnwire.MilliSatoshi( + rpcPolicy.FeeBaseMsat, + ), + FeeProportionalMillionths: lnwire.MilliSatoshi( + rpcPolicy.FeeRateMilliMsat, + ), + } + if rpcPolicy.MaxHtlcMsat > 0 { + policy.MaxHTLC = lnwire.MilliSatoshi( + rpcPolicy.MaxHtlcMsat, + ) + policy.MessageFlags |= lnwire.ChanUpdateOptionMaxHtlc + } + + return policy + } + + if rpcEdge.Node1Policy != nil { + policy := makePolicy(rpcEdge.Node1Policy) + policy.ChannelFlags = 0 + if err := graphDB.UpdateEdgePolicy(policy); err != nil { + return nil, fmt.Errorf( + "unable to update policy: %v", err) + } + } + + if rpcEdge.Node2Policy != nil { + policy := makePolicy(rpcEdge.Node2Policy) + policy.ChannelFlags = 1 + if err := graphDB.UpdateEdgePolicy(policy); err != nil { + return nil, fmt.Errorf( + "unable to update policy: %v", err) + } + } + + log.Debugf("Added edge: %v", rpcEdge.ChannelId) + } + + return &ImportGraphResponse{}, nil +} diff --git a/lnrpc/devrpc/driver.go b/lnrpc/devrpc/driver.go new file mode 100644 index 0000000000..a0d4d17c8c --- /dev/null +++ b/lnrpc/devrpc/driver.go @@ -0,0 +1,67 @@ +//go:build dev +// +build dev + +package devrpc + +import ( + "fmt" + + "github.com/lightningnetwork/lnd/lnrpc" +) + +// createNewSubServer is a helper method that will create the new sub server +// given the main config dispatcher method. If we're unable to find the config +// that is meant for us in the config dispatcher, then we'll exit with an +// error. +func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( + *Server, lnrpc.MacaroonPerms, error) { + + // We'll attempt to look up the config that we expect, according to our + // subServerName name. If we can't find this, then we'll exit with an + // error, as we're unable to properly initialize ourselves without this + // config. + subServerConf, ok := configRegistry.FetchConfig(subServerName) + if !ok { + return nil, nil, fmt.Errorf("unable to find config for "+ + "subserver type %s", subServerName) + } + + // Now that we've found an object mapping to our service name, we'll + // ensure that it's the type we need. + config, ok := subServerConf.(*Config) + if !ok { + return nil, nil, fmt.Errorf("wrong type of config for "+ + "subserver %s, expected %T got %T", subServerName, + &Config{}, subServerConf) + } + + // Before we try to make the new service instance, we'll perform + // some sanity checks on the arguments to ensure that they're useable. + if config.ActiveNetParams == nil { + return nil, nil, fmt.Errorf("ActiveNetParams must be set to " + + "create DevRPC") + } + + if config.GraphDB == nil { + return nil, nil, fmt.Errorf("GraphDB must be set to create " + + "DevRPC") + } + + return New(config) +} + +func init() { + subServer := &lnrpc.SubServerDriver{ + SubServerName: subServerName, + NewGrpcHandler: func() lnrpc.GrpcHandler { + return &ServerShell{} + }, + } + + // If the build tag is active, then we'll register ourselves as a + // sub-RPC server within the global lnrpc package namespace. + if err := lnrpc.RegisterSubServer(subServer); err != nil { + panic(fmt.Sprintf("failed to register sub server driver "+ + "'%s': %v", subServerName, err)) + } +} diff --git a/lnrpc/devrpc/log.go b/lnrpc/devrpc/log.go new file mode 100644 index 0000000000..2ff536b843 --- /dev/null +++ b/lnrpc/devrpc/log.go @@ -0,0 +1,45 @@ +package devrpc + +import ( + "github.com/btcsuite/btclog" + "github.com/lightningnetwork/lnd/build" +) + +// log is a logger that is initialized with no output filters. This means the +// package will not perform any logging by default until the caller requests +// it. +var log btclog.Logger + +// The default amount of logging is none. +func init() { + UseLogger(build.NewSubLogger("DRPC", nil)) +} + +// DisableLog disables all library log output. Logging output is disabled by +// by default until UseLogger is called. +func DisableLog() { + UseLogger(btclog.Disabled) +} + +// UseLogger uses a specified Logger to output package logging info. This +// should be used in preference to SetLogWriter if the caller is also using +// btclog. +func UseLogger(logger btclog.Logger) { + log = logger +} + +// logClosure is used to provide a closure over expensive logging operations so +// don't have to be performed when the logging level doesn't warrant it. +type logClosure func() string // nolint:unused + +// String invokes the underlying function and returns the result. +func (c logClosure) String() string { + return c() +} + +// newLogClosure returns a new closure over a function that returns a string +// which itself provides a Stringer interface so that it can be used with the +// logging system. +func newLogClosure(c func() string) logClosure { // nolint:unused + return logClosure(c) +} diff --git a/lnrpc/gen_protos.sh b/lnrpc/gen_protos.sh index bdbbd3a958..c523a203ea 100755 --- a/lnrpc/gen_protos.sh +++ b/lnrpc/gen_protos.sh @@ -48,7 +48,7 @@ function generate() { --custom_opt="$opts" \ lightning.proto stateservice.proto walletunlocker.proto - PACKAGES="autopilotrpc chainrpc invoicesrpc routerrpc signrpc verrpc walletrpc watchtowerrpc wtclientrpc" + PACKAGES="autopilotrpc chainrpc invoicesrpc neutrinorpc peersrpc routerrpc signrpc verrpc walletrpc watchtowerrpc wtclientrpc devrpc" for package in $PACKAGES; do # Special import for the wallet kit. manual_import="" @@ -56,6 +56,11 @@ function generate() { manual_import="github.com/lightningnetwork/lnd/lnrpc/signrpc" fi + # Special import for devrpc. + if [[ "$package" == "devrpc" ]]; then + manual_import="github.com/lightningnetwork/lnd/lnrpc" + fi + opts="package_name=$package,manual_import=$manual_import,js_stubs=1,build_tags=// +build js" pushd $package protoc -I/usr/local/include -I. -I.. \ diff --git a/lnrpc/invoicesrpc/addinvoice.go b/lnrpc/invoicesrpc/addinvoice.go index fda49f21c8..e0e0242ddb 100644 --- a/lnrpc/invoicesrpc/addinvoice.go +++ b/lnrpc/invoicesrpc/addinvoice.go @@ -9,11 +9,11 @@ import ( "math" "time" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/davecgh/go-spew/spew" - "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwire" @@ -30,6 +30,11 @@ const ( // DefaultAMPInvoiceExpiry is the default invoice expiry for new AMP // invoices. DefaultAMPInvoiceExpiry = 30 * 24 * time.Hour + + // hopHintFactor is factor by which we scale the total amount of + // inbound capacity we want our hop hints to represent, allowing us to + // have some leeway if peers go offline. + hopHintFactor = 2 ) // AddInvoiceConfig contains dependencies for invoice creation. @@ -152,7 +157,6 @@ func (d *AddInvoiceData) paymentHashAndPreimage() ( // The preimage will always be nil. func (d *AddInvoiceData) ampPaymentHashAndPreimage() (*lntypes.Preimage, lntypes.Hash, error) { switch { - // Preimages cannot be set on AMP invoice. case d.Preimage != nil: return nil, lntypes.Hash{}, @@ -280,7 +284,6 @@ func AddInvoice(ctx context.Context, cfg *AddInvoiceConfig, } switch { - // If expiry is set, specify it. If it is not provided, no expiry time // will be explicitly added to this payment request, which will imply // the default 3600 seconds. @@ -308,7 +311,6 @@ func AddInvoice(ctx context.Context, cfg *AddInvoiceConfig, // Otherwise, use the default AMP expiry. default: options = append(options, zpay32.Expiry(DefaultAMPInvoiceExpiry)) - } // If the description hash is set, then we add it do the list of options. @@ -379,22 +381,42 @@ func AddInvoice(ctx context.Context, cfg *AddInvoiceConfig, if len(openChannels) > 0 { // We filter the channels by excluding the ones that were specified by // the caller and were already added. - var filteredChannels []*channeldb.OpenChannel + var filteredChannels []*HopHintInfo for _, c := range openChannels { if _, ok := forcedHints[c.ShortChanID().ToUint64()]; ok { continue } - filteredChannels = append(filteredChannels, c) + + chanID := lnwire.NewChanIDFromOutPoint( + &c.FundingOutpoint, + ) + isActive := cfg.IsChannelActive(chanID) + + hopHintInfo := newHopHintInfo(c, isActive) + filteredChannels = append( + filteredChannels, hopHintInfo, + ) } // We'll restrict the number of individual route hints // to 20 to avoid creating overly large invoices. numMaxHophints := 20 - len(forcedHints) + + hopHintsCfg := newSelectHopHintsCfg(cfg) hopHints := SelectHopHints( - amtMSat, cfg, filteredChannels, numMaxHophints, + amtMSat, hopHintsCfg, filteredChannels, + numMaxHophints, ) - options = append(options, hopHints...) + // Convert our set of selected hop hints into route + // hints and add to our invoice options. + for _, hopHint := range hopHints { + routeHint := zpay32.RouteHint(hopHint) + + options = append( + options, routeHint, + ) + } } } @@ -466,24 +488,20 @@ func AddInvoice(ctx context.Context, cfg *AddInvoiceConfig, // chanCanBeHopHint returns true if the target channel is eligible to be a hop // hint. -func chanCanBeHopHint(channel *channeldb.OpenChannel, cfg *AddInvoiceConfig) ( +func chanCanBeHopHint(channel *HopHintInfo, cfg *SelectHopHintsCfg) ( *channeldb.ChannelEdgePolicy, bool) { // Since we're only interested in our private channels, we'll skip // public ones. - isPublic := channel.ChannelFlags&lnwire.FFAnnounceChannel != 0 - if isPublic { + if channel.IsPublic { return nil, false } // Make sure the channel is active. - chanPoint := lnwire.NewChanIDFromOutPoint( - &channel.FundingOutpoint, - ) - if !cfg.IsChannelActive(chanPoint) { + if !channel.IsActive { log.Debugf("Skipping channel %v due to not "+ "being eligible to forward payments", - chanPoint) + channel.ShortChannelID) return nil, false } @@ -493,8 +511,8 @@ func chanCanBeHopHint(channel *channeldb.OpenChannel, cfg *AddInvoiceConfig) ( // unadvertised, like in the case of a node only having private // channels. var remotePub [33]byte - copy(remotePub[:], channel.IdentityPub.SerializeCompressed()) - isRemoteNodePublic, err := cfg.Graph.IsPublicNode(remotePub) + copy(remotePub[:], channel.RemotePubkey.SerializeCompressed()) + isRemoteNodePublic, err := cfg.IsPublicNode(remotePub) if err != nil { log.Errorf("Unable to determine if node %x "+ "is advertised: %v", remotePub, err) @@ -504,17 +522,16 @@ func chanCanBeHopHint(channel *channeldb.OpenChannel, cfg *AddInvoiceConfig) ( if !isRemoteNodePublic { log.Debugf("Skipping channel %v due to "+ "counterparty %x being unadvertised", - chanPoint, remotePub) + channel.ShortChannelID, remotePub) return nil, false } // Fetch the policies for each end of the channel. - chanID := channel.ShortChanID().ToUint64() - info, p1, p2, err := cfg.Graph.FetchChannelEdgesByID(chanID) + info, p1, p2, err := cfg.FetchChannelEdgesByID(channel.ShortChannelID) if err != nil { log.Errorf("Unable to fetch the routing "+ "policies for the edges of the channel "+ - "%v: %v", chanPoint, err) + "%v: %v", channel.ShortChannelID, err) return nil, false } @@ -532,39 +549,138 @@ func chanCanBeHopHint(channel *channeldb.OpenChannel, cfg *AddInvoiceConfig) ( // addHopHint creates a hop hint out of the passed channel and channel policy. // The new hop hint is appended to the passed slice. -func addHopHint(hopHints *[]func(*zpay32.Invoice), - channel *channeldb.OpenChannel, chanPolicy *channeldb.ChannelEdgePolicy) { +func addHopHint(hopHints *[][]zpay32.HopHint, + channel *HopHintInfo, chanPolicy *channeldb.ChannelEdgePolicy) { hopHint := zpay32.HopHint{ - NodeID: channel.IdentityPub, - ChannelID: channel.ShortChanID().ToUint64(), + NodeID: channel.RemotePubkey, + ChannelID: channel.ShortChannelID, FeeBaseMSat: uint32(chanPolicy.FeeBaseMSat), FeeProportionalMillionths: uint32( chanPolicy.FeeProportionalMillionths, ), CLTVExpiryDelta: chanPolicy.TimeLockDelta, } - *hopHints = append( - *hopHints, zpay32.RouteHint([]zpay32.HopHint{hopHint}), - ) + + *hopHints = append(*hopHints, []zpay32.HopHint{hopHint}) +} + +// HopHintInfo contains the channel information required to create a hop hint. +type HopHintInfo struct { + // IsPublic indicates whether a channel is advertised to the network. + IsPublic bool + + // IsActive indicates whether the channel is online and available for + // use. + IsActive bool + + // FundingOutpoint is the funding txid:index for the channel. + FundingOutpoint wire.OutPoint + + // RemotePubkey is the public key of the remote party that this channel + // is in. + RemotePubkey *btcec.PublicKey + + // RemoteBalance is the remote party's balance (our current incoming + // capacity). + RemoteBalance lnwire.MilliSatoshi + + // ShortChannelID is the short channel ID of the channel. + ShortChannelID uint64 +} + +func newHopHintInfo(c *channeldb.OpenChannel, isActive bool) *HopHintInfo { + isPublic := c.ChannelFlags&lnwire.FFAnnounceChannel != 0 + + return &HopHintInfo{ + IsPublic: isPublic, + IsActive: isActive, + FundingOutpoint: c.FundingOutpoint, + RemotePubkey: c.IdentityPub, + RemoteBalance: c.LocalCommitment.RemoteBalance, + ShortChannelID: c.ShortChannelID.ToUint64(), + } +} + +// SelectHopHintsCfg contains the dependencies required to obtain hop hints +// for an invoice. +type SelectHopHintsCfg struct { + // IsPublicNode is returns a bool indicating whether the node with the + // given public key is seen as a public node in the graph from the + // graph's source node's point of view. + IsPublicNode func(pubKey [33]byte) (bool, error) + + // FetchChannelEdgesByID attempts to lookup the two directed edges for + // the channel identified by the channel ID. + FetchChannelEdgesByID func(chanID uint64) (*channeldb.ChannelEdgeInfo, + *channeldb.ChannelEdgePolicy, *channeldb.ChannelEdgePolicy, + error) +} + +func newSelectHopHintsCfg(invoicesCfg *AddInvoiceConfig) *SelectHopHintsCfg { + return &SelectHopHintsCfg{ + IsPublicNode: invoicesCfg.Graph.IsPublicNode, + FetchChannelEdgesByID: invoicesCfg.Graph.FetchChannelEdgesByID, + } +} + +// sufficientHints checks whether we have sufficient hop hints, based on the +// following criteria: +// - Hop hint count: limit to a set number of hop hints, regardless of whether +// we've reached our invoice amount or not. +// - Total incoming capacity: limit to our invoice amount * scaling factor to +// allow for some of our links going offline. +// +// We limit our number of hop hints like this to keep our invoice size down, +// and to avoid leaking all our private channels when we don't need to. +func sufficientHints(numHints, maxHints, scalingFactor int, amount, + totalHintAmount lnwire.MilliSatoshi) bool { + + if numHints >= maxHints { + log.Debug("Reached maximum number of hop hints") + return true + } + + requiredAmount := amount * lnwire.MilliSatoshi(scalingFactor) + if totalHintAmount >= requiredAmount { + log.Debugf("Total hint amount: %v has reached target hint "+ + "bandwidth: %v (invoice amount: %v * factor: %v)", + totalHintAmount, requiredAmount, amount, + scalingFactor) + + return true + } + + return false } // SelectHopHints will select up to numMaxHophints from the set of passed open // channels. The set of hop hints will be returned as a slice of functional // options that'll append the route hint to the set of all route hints. // -// TODO(roasbeef): do proper sub-set sum max hints usually << numChans -func SelectHopHints(amtMSat lnwire.MilliSatoshi, cfg *AddInvoiceConfig, - openChannels []*channeldb.OpenChannel, - numMaxHophints int) []func(*zpay32.Invoice) { +// TODO(roasbeef): do proper sub-set sum max hints usually << numChans. +func SelectHopHints(amtMSat lnwire.MilliSatoshi, cfg *SelectHopHintsCfg, + openChannels []*HopHintInfo, + numMaxHophints int) [][]zpay32.HopHint { // We'll add our hop hints in two passes, first we'll add all channels // that are eligible to be hop hints, and also have a local balance // above the payment amount. var totalHintBandwidth lnwire.MilliSatoshi hopHintChans := make(map[wire.OutPoint]struct{}) - hopHints := make([]func(*zpay32.Invoice), 0, numMaxHophints) + hopHints := make([][]zpay32.HopHint, 0, numMaxHophints) for _, channel := range openChannels { + enoughHopHints := sufficientHints( + len(hopHints), numMaxHophints, hopHintFactor, amtMSat, + totalHintBandwidth, + ) + if enoughHopHints { + log.Debugf("First pass of hop selection has " + + "sufficient hints") + + return hopHints + } + // If this channel can't be a hop hint, then skip it. edgePolicy, canBeHopHint := chanCanBeHopHint(channel, cfg) if edgePolicy == nil || !canBeHopHint { @@ -573,7 +689,7 @@ func SelectHopHints(amtMSat lnwire.MilliSatoshi, cfg *AddInvoiceConfig, // Similarly, in this first pass, we'll ignore all channels in // isolation can't satisfy this payment. - if channel.LocalCommitment.RemoteBalance < amtMSat { + if channel.RemoteBalance < amtMSat { continue } @@ -582,13 +698,7 @@ func SelectHopHints(amtMSat lnwire.MilliSatoshi, cfg *AddInvoiceConfig, addHopHint(&hopHints, channel, edgePolicy) hopHintChans[channel.FundingOutpoint] = struct{}{} - totalHintBandwidth += channel.LocalCommitment.RemoteBalance - } - - // If we have enough hop hints at this point, then we'll exit early. - // Otherwise, we'll continue to add more that may help out mpp users. - if len(hopHints) >= numMaxHophints { - return hopHints + totalHintBandwidth += channel.RemoteBalance } // In this second pass we'll add channels, and we'll either stop when @@ -596,14 +706,16 @@ func SelectHopHints(amtMSat lnwire.MilliSatoshi, cfg *AddInvoiceConfig, // or if the sum of available bandwidth in the routing hints exceeds 2x // the payment amount. We do 2x here to account for a margin of error // if some of the selected channels no longer become operable. - hopHintFactor := lnwire.MilliSatoshi(2) for i := 0; i < len(openChannels); i++ { - // If we hit either of our early termination conditions, then - // we'll break the loop here. - if totalHintBandwidth > amtMSat*hopHintFactor || - len(hopHints) >= numMaxHophints { - - break + enoughHopHints := sufficientHints( + len(hopHints), numMaxHophints, hopHintFactor, amtMSat, + totalHintBandwidth, + ) + if enoughHopHints { + log.Debugf("Second pass of hop selection has " + + "sufficient hints") + + return hopHints } channel := openChannels[i] @@ -629,7 +741,7 @@ func SelectHopHints(amtMSat lnwire.MilliSatoshi, cfg *AddInvoiceConfig, // available balance now to update our tally. // // TODO(roasbeef): have a cut off based on min bandwidth? - totalHintBandwidth += channel.LocalCommitment.RemoteBalance + totalHintBandwidth += channel.RemoteBalance } return hopHints diff --git a/lnrpc/invoicesrpc/addinvoice_test.go b/lnrpc/invoicesrpc/addinvoice_test.go new file mode 100644 index 0000000000..38fab71f2e --- /dev/null +++ b/lnrpc/invoicesrpc/addinvoice_test.go @@ -0,0 +1,624 @@ +package invoicesrpc + +import ( + "encoding/hex" + "errors" + "testing" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/zpay32" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +type hopHintsConfigMock struct { + mock.Mock +} + +// IsPublicNode mocks node public state lookup. +func (h *hopHintsConfigMock) IsPublicNode(pubKey [33]byte) (bool, error) { + args := h.Mock.Called(pubKey) + return args.Bool(0), args.Error(1) +} + +// FetchChannelEdgesByID mocks channel edge lookup. +func (h *hopHintsConfigMock) FetchChannelEdgesByID(chanID uint64) ( + *channeldb.ChannelEdgeInfo, *channeldb.ChannelEdgePolicy, + *channeldb.ChannelEdgePolicy, error) { + + args := h.Mock.Called(chanID) + + // If our error is non-nil, we expect nil responses otherwise. Our + // casts below will fail with nil values, so we check our error and + // return early on failure first. + err := args.Error(3) + if err != nil { + return nil, nil, nil, err + } + + edgeInfo := args.Get(0).(*channeldb.ChannelEdgeInfo) + policy1 := args.Get(1).(*channeldb.ChannelEdgePolicy) + policy2 := args.Get(2).(*channeldb.ChannelEdgePolicy) + + return edgeInfo, policy1, policy2, err +} + +// TestSelectHopHints tests selection of hop hints for a node with private +// channels. +func TestSelectHopHints(t *testing.T) { + var ( + // We need to serialize our pubkey in SelectHopHints so it + // needs to be valid. + pubkeyBytes, _ = hex.DecodeString( + "598ec453728e0ffe0ae2f5e174243cf58f2" + + "a3f2c83d2457b43036db568b11093", + ) + pubKeyY = new(btcec.FieldVal) + _ = pubKeyY.SetByteSlice(pubkeyBytes) + pubkey = btcec.NewPublicKey( + new(btcec.FieldVal).SetInt(4), + pubKeyY, + ) + compressed = pubkey.SerializeCompressed() + + publicChannel = &HopHintInfo{ + IsPublic: true, + IsActive: true, + FundingOutpoint: wire.OutPoint{ + Index: 0, + }, + RemoteBalance: 10, + ShortChannelID: 0, + } + + inactiveChannel = &HopHintInfo{ + IsPublic: false, + IsActive: false, + } + + // Create a private channel that we'll generate hints from. + private1ShortID uint64 = 1 + privateChannel1 = &HopHintInfo{ + IsPublic: false, + IsActive: true, + FundingOutpoint: wire.OutPoint{ + Index: 1, + }, + RemotePubkey: pubkey, + RemoteBalance: 100, + ShortChannelID: private1ShortID, + } + + // Create a edge policy for private channel 1. + privateChan1Policy = &channeldb.ChannelEdgePolicy{ + FeeBaseMSat: 10, + FeeProportionalMillionths: 100, + TimeLockDelta: 1000, + } + + // Create an edge policy different to ours which we'll use for + // the other direction + otherChanPolicy = &channeldb.ChannelEdgePolicy{ + FeeBaseMSat: 90, + FeeProportionalMillionths: 900, + TimeLockDelta: 9000, + } + + // Create a hop hint based on privateChan1Policy. + privateChannel1Hint = zpay32.HopHint{ + NodeID: privateChannel1.RemotePubkey, + ChannelID: private1ShortID, + FeeBaseMSat: uint32(privateChan1Policy.FeeBaseMSat), + FeeProportionalMillionths: uint32( + privateChan1Policy.FeeProportionalMillionths, + ), + CLTVExpiryDelta: privateChan1Policy.TimeLockDelta, + } + + // Create a second private channel that we'll use for hints. + private2ShortID uint64 = 2 + privateChannel2 = &HopHintInfo{ + IsPublic: false, + IsActive: true, + FundingOutpoint: wire.OutPoint{ + Index: 2, + }, + RemotePubkey: pubkey, + RemoteBalance: 100, + ShortChannelID: private2ShortID, + } + + // Create a edge policy for private channel 1. + privateChan2Policy = &channeldb.ChannelEdgePolicy{ + FeeBaseMSat: 20, + FeeProportionalMillionths: 200, + TimeLockDelta: 2000, + } + + // Create a hop hint based on privateChan2Policy. + privateChannel2Hint = zpay32.HopHint{ + NodeID: privateChannel2.RemotePubkey, + ChannelID: private2ShortID, + FeeBaseMSat: uint32(privateChan2Policy.FeeBaseMSat), + FeeProportionalMillionths: uint32( + privateChan2Policy.FeeProportionalMillionths, + ), + CLTVExpiryDelta: privateChan2Policy.TimeLockDelta, + } + + // Create a third private channel that we'll use for hints. + private3ShortID uint64 = 3 + privateChannel3 = &HopHintInfo{ + IsPublic: false, + IsActive: true, + FundingOutpoint: wire.OutPoint{ + Index: 3, + }, + RemotePubkey: pubkey, + RemoteBalance: 100, + ShortChannelID: private3ShortID, + } + + // Create a edge policy for private channel 1. + privateChan3Policy = &channeldb.ChannelEdgePolicy{ + FeeBaseMSat: 30, + FeeProportionalMillionths: 300, + TimeLockDelta: 3000, + } + + // Create a hop hint based on privateChan2Policy. + privateChannel3Hint = zpay32.HopHint{ + NodeID: privateChannel3.RemotePubkey, + ChannelID: private3ShortID, + FeeBaseMSat: uint32(privateChan3Policy.FeeBaseMSat), + FeeProportionalMillionths: uint32( + privateChan3Policy.FeeProportionalMillionths, + ), + CLTVExpiryDelta: privateChan3Policy.TimeLockDelta, + } + ) + + // We can't copy in the above var decls, so we copy in our pubkey here. + var peer [33]byte + copy(peer[:], compressed) + + var ( + // We pick our policy based on which node (1 or 2) the remote + // peer is. Here we create two different sets of edge + // information. One where our peer is node 1, the other where + // our peer is edge 2. This ensures that we always pick the + // right edge policy for our hint. + infoNode1 = &channeldb.ChannelEdgeInfo{ + NodeKey1Bytes: peer, + } + + infoNode2 = &channeldb.ChannelEdgeInfo{ + NodeKey1Bytes: [33]byte{9, 9, 9}, + NodeKey2Bytes: peer, + } + + // setMockChannelUsed preps our mock for the case where we + // want our private channel to be used for a hop hint. + setMockChannelUsed = func(h *hopHintsConfigMock, + shortID uint64, + policy *channeldb.ChannelEdgePolicy) { + + // Return public node = true so that we'll consider + // this node for our hop hints. + h.Mock.On( + "IsPublicNode", peer, + ).Once().Return(true, nil) + + // When it gets time to find an edge policy for this + // node, fail it. We won't use it as a hop hint. + h.Mock.On( + "FetchChannelEdgesByID", + shortID, + ).Once().Return( + infoNode1, policy, otherChanPolicy, nil, + ) + } + ) + + tests := []struct { + name string + setupMock func(*hopHintsConfigMock) + amount lnwire.MilliSatoshi + channels []*HopHintInfo + numHints int + + // expectedHints is the set of hop hints that we expect. We + // initialize this slice with our max hop hints length, so this + // value won't be nil even if its empty. + expectedHints [][]zpay32.HopHint + }{ + { + // We don't need hop hints for public channels. + name: "channel is public", + // When a channel is public, we exit before we make any + // calls. + setupMock: func(h *hopHintsConfigMock) { + }, + amount: 100, + channels: []*HopHintInfo{ + publicChannel, + }, + numHints: 2, + expectedHints: nil, + }, + { + name: "channel is inactive", + setupMock: func(h *hopHintsConfigMock) {}, + amount: 100, + channels: []*HopHintInfo{ + inactiveChannel, + }, + numHints: 2, + expectedHints: nil, + }, + { + // If we can't lookup an edge policy, we skip channels. + name: "no edge policy", + setupMock: func(h *hopHintsConfigMock) { + // Return public node = true so that we'll + // consider this node for our hop hints. + h.Mock.On( + "IsPublicNode", peer, + ).Return(true, nil) + + // When it gets time to find an edge policy for + // this node, fail it. We won't use it as a + // hop hint. + h.Mock.On( + "FetchChannelEdgesByID", + private1ShortID, + ).Return( + nil, nil, nil, + errors.New("no edge"), + ) + }, + amount: 100, + channels: []*HopHintInfo{ + privateChannel1, + }, + numHints: 3, + expectedHints: nil, + }, + { + // If one of our private channels belongs to a node + // that is otherwise not announced to the network, we're + // polite and don't include them (they can't be routed + // through anyway). + name: "node is private", + setupMock: func(h *hopHintsConfigMock) { + // Return public node = false so that we'll + // give up on this node. + h.Mock.On( + "IsPublicNode", peer, + ).Return(false, nil) + }, + amount: 100, + channels: []*HopHintInfo{ + privateChannel1, + }, + numHints: 1, + expectedHints: nil, + }, + { + // This test case asserts that we limit our hop hints + // when we've reached our maximum number of hints. + name: "too many hints", + setupMock: func(h *hopHintsConfigMock) { + setMockChannelUsed( + h, private1ShortID, privateChan1Policy, + ) + }, + // Set our amount to less than our channel balance of + // 100. + amount: 30, + channels: []*HopHintInfo{ + privateChannel1, privateChannel2, + }, + numHints: 1, + expectedHints: [][]zpay32.HopHint{ + { + privateChannel1Hint, + }, + }, + }, + { + // If a channel has more balance than the amount we're + // looking for, it'll be added in our first pass. We + // can be sure we're adding it in our first pass because + // we assert that there are no additional calls to our + // mock (which would happen if we ran a second pass). + // + // We set our peer to be node 1 in our policy ordering. + name: "balance > total amount, node 1", + setupMock: func(h *hopHintsConfigMock) { + setMockChannelUsed( + h, private1ShortID, privateChan1Policy, + ) + }, + // Our channel has balance of 100 (> 50). + amount: 50, + channels: []*HopHintInfo{ + privateChannel1, + }, + numHints: 2, + expectedHints: [][]zpay32.HopHint{ + { + privateChannel1Hint, + }, + }, + }, + { + // As above, but we set our peer to be node 2 in our + // policy ordering. + name: "balance > total amount, node 2", + setupMock: func(h *hopHintsConfigMock) { + // Return public node = true so that we'll + // consider this node for our hop hints. + h.Mock.On( + "IsPublicNode", peer, + ).Return(true, nil) + + // When it gets time to find an edge policy for + // this node, fail it. We won't use it as a + // hop hint. + h.Mock.On( + "FetchChannelEdgesByID", + private1ShortID, + ).Return( + infoNode2, otherChanPolicy, + privateChan1Policy, nil, + ) + }, + // Our channel has balance of 100 (> 50). + amount: 50, + channels: []*HopHintInfo{ + privateChannel1, + }, + numHints: 2, + expectedHints: [][]zpay32.HopHint{ + { + privateChannel1Hint, + }, + }, + }, + { + // Since our balance is less than the amount we're + // looking to route, we expect this hint to be picked + // up in our second pass on the channel set. + name: "balance < total amount", + setupMock: func(h *hopHintsConfigMock) { + // We expect to call all our checks twice + // because we pick up this channel in the + // second round. + setMockChannelUsed( + h, private1ShortID, privateChan1Policy, + ) + setMockChannelUsed( + h, private1ShortID, privateChan1Policy, + ) + }, + // Our channel has balance of 100 (< 150). + amount: 150, + channels: []*HopHintInfo{ + privateChannel1, + }, + numHints: 2, + expectedHints: [][]zpay32.HopHint{ + { + privateChannel1Hint, + }, + }, + }, + { + // Test the case where we hit our total amount of + // required liquidity in our first pass. + name: "first pass sufficient balance", + setupMock: func(h *hopHintsConfigMock) { + setMockChannelUsed( + h, private1ShortID, privateChan1Policy, + ) + }, + // Divide our balance by hop hint factor so that the + // channel balance will always reach our factored up + // amount, even if we change this value. + amount: privateChannel1.RemoteBalance / hopHintFactor, + channels: []*HopHintInfo{ + privateChannel1, + }, + numHints: 2, + expectedHints: [][]zpay32.HopHint{ + { + privateChannel1Hint, + }, + }, + }, + { + // Setup our amount so that we don't have enough + // inbound total for our amount, but we hit our + // desired hint limit. + name: "second pass sufficient hint count", + setupMock: func(h *hopHintsConfigMock) { + // We expect all of our channels to be passed + // on in the first pass. + setMockChannelUsed( + h, private1ShortID, privateChan1Policy, + ) + + setMockChannelUsed( + h, private2ShortID, privateChan2Policy, + ) + + // In the second pass, our first two channels + // should be added before we hit our hint count. + setMockChannelUsed( + h, private1ShortID, privateChan1Policy, + ) + }, + // Add two channels that we'd want to use, but the + // second one will be cut off due to our hop hint count + // limit. + channels: []*HopHintInfo{ + privateChannel1, privateChannel2, + }, + // Set the amount we need to more than our two channels + // can provide us. + amount: privateChannel1.RemoteBalance + + privateChannel2.RemoteBalance, + numHints: 1, + expectedHints: [][]zpay32.HopHint{ + { + privateChannel1Hint, + }, + }, + }, + { + // Add three channels that are all less than the amount + // we wish to receive, but collectively will reach the + // total amount that we need. + name: "second pass reaches bandwidth requirement", + setupMock: func(h *hopHintsConfigMock) { + // In the first round, all channels should be + // passed on. + setMockChannelUsed( + h, private1ShortID, privateChan1Policy, + ) + + setMockChannelUsed( + h, private2ShortID, privateChan2Policy, + ) + + setMockChannelUsed( + h, private3ShortID, privateChan3Policy, + ) + + // In the second round, we'll pick up all of + // our hop hints. + setMockChannelUsed( + h, private1ShortID, privateChan1Policy, + ) + + setMockChannelUsed( + h, private2ShortID, privateChan2Policy, + ) + + setMockChannelUsed( + h, private3ShortID, privateChan3Policy, + ) + }, + channels: []*HopHintInfo{ + privateChannel1, privateChannel2, + privateChannel3, + }, + + // All of our channels have 100 inbound, so none will + // be picked up in the first round. + amount: 110, + numHints: 5, + expectedHints: [][]zpay32.HopHint{ + { + privateChannel1Hint, + }, + { + privateChannel2Hint, + }, + { + privateChannel3Hint, + }, + }, + }, + } + + for _, test := range tests { + test := test + + t.Run(test.name, func(t *testing.T) { + // Create mock and prime it for the test case. + mock := &hopHintsConfigMock{} + test.setupMock(mock) + defer mock.AssertExpectations(t) + + cfg := &SelectHopHintsCfg{ + IsPublicNode: mock.IsPublicNode, + FetchChannelEdgesByID: mock.FetchChannelEdgesByID, + } + + hints := SelectHopHints( + test.amount, cfg, test.channels, test.numHints, + ) + + // SelectHopHints preallocates its hop hint slice, so + // we check that it is empty if we don't expect any + // hints, and otherwise assert that the two slices are + // equal. This allows tests to set their expected value + // to nil, rather than providing a preallocated empty + // slice. + if len(test.expectedHints) == 0 { + require.Zero(t, len(hints)) + } else { + require.Equal(t, test.expectedHints, hints) + } + }) + } +} + +// TestSufficientHopHints tests limiting our hops to a set number of hints or +// scaled amount of capacity. +func TestSufficientHopHints(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + numHints int + maxHints int + scalingFactor int + amount lnwire.MilliSatoshi + totalHintAmount lnwire.MilliSatoshi + sufficient bool + }{ + { + name: "not enough hints or amount", + numHints: 3, + maxHints: 10, + // We want to have at least 200, and we currently have + // 10. + scalingFactor: 2, + amount: 100, + totalHintAmount: 10, + sufficient: false, + }, + { + name: "enough hints", + numHints: 3, + maxHints: 3, + sufficient: true, + }, + { + name: "not enough hints, insufficient bandwidth", + numHints: 1, + maxHints: 3, + // We want at least 200, and we have enough. + scalingFactor: 2, + amount: 100, + totalHintAmount: 700, + sufficient: true, + }, + } + + for _, testCase := range tests { + sufficient := sufficientHints( + testCase.numHints, testCase.maxHints, + testCase.scalingFactor, testCase.amount, + testCase.totalHintAmount, + ) + + require.Equal(t, testCase.sufficient, sufficient) + } +} diff --git a/lnrpc/invoicesrpc/invoices_server.go b/lnrpc/invoicesrpc/invoices_server.go index 102abb0c9e..5c0d71ac40 100644 --- a/lnrpc/invoicesrpc/invoices_server.go +++ b/lnrpc/invoicesrpc/invoices_server.go @@ -10,16 +10,15 @@ import ( "os" "path/filepath" - "google.golang.org/grpc" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - "gopkg.in/macaroon-bakery.v2/bakery" - "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/macaroons" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "gopkg.in/macaroon-bakery.v2/bakery" ) const ( @@ -244,6 +243,8 @@ func (s *Server) SubscribeSingleInvoice(req *SubscribeSingleInvoiceRequest, } defer invoiceClient.Cancel() + log.Debugf("Created new single invoice(pay_hash=%v) subscription", hash) + for { select { case newInvoice := <-invoiceClient.Updates: @@ -265,7 +266,9 @@ func (s *Server) SubscribeSingleInvoice(req *SubscribeSingleInvoiceRequest, } case <-updateStream.Context().Done(): - return updateStream.Context().Err() + return fmt.Errorf("subscription for "+ + "invoice(pay_hash=%v): %w", hash, + updateStream.Context().Err()) case <-s.quit: return nil diff --git a/lnrpc/invoicesrpc/log.go b/lnrpc/invoicesrpc/log.go index d29c737809..8429ee51cf 100644 --- a/lnrpc/invoicesrpc/log.go +++ b/lnrpc/invoicesrpc/log.go @@ -5,7 +5,7 @@ import ( "github.com/lightningnetwork/lnd/build" ) -// log is a logger that is initialized with no output filters. This means the +// log is a logger that is initialized with no output filters. This means the // package will not perform any logging by default until the caller requests // it. var log btclog.Logger @@ -21,7 +21,7 @@ func DisableLog() { UseLogger(btclog.Disabled) } -// UseLogger uses a specified Logger to output package logging info. This +// UseLogger uses a specified Logger to output package logging info. This // should be used in preference to SetLogWriter if the caller is also using // btclog. func UseLogger(logger btclog.Logger) { diff --git a/lnrpc/invoicesrpc/utils.go b/lnrpc/invoicesrpc/utils.go index 731431b00f..283e72ce4d 100644 --- a/lnrpc/invoicesrpc/utils.go +++ b/lnrpc/invoicesrpc/utils.go @@ -4,7 +4,7 @@ import ( "encoding/hex" "fmt" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/chaincfg" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/lnrpc" @@ -38,7 +38,6 @@ func decodePayReq(invoice *channeldb.Invoice, "request: %v", err) } return decoded, nil - } // CreateRPCInvoice creates an *lnrpc.Invoice from the *channeldb.Invoice. @@ -179,7 +178,6 @@ func CreateRPCInvoice(invoice *channeldb.Invoice, rpcInvoice.AmpInvoiceState = make(map[string]*lnrpc.AMPInvoiceState) for setID, ampState := range invoice.AMPState { - setIDStr := hex.EncodeToString(setID[:]) var state lnrpc.InvoiceHTLCState @@ -277,7 +275,7 @@ func CreateZpay32HopHints(routeHints []*lnrpc.RouteHint) ([][]zpay32.HopHint, er if err != nil { return nil, err } - p, err := btcec.ParsePubKey(pubKeyBytes, btcec.S256()) + p, err := btcec.ParsePubKey(pubKeyBytes) if err != nil { return nil, err } diff --git a/lnrpc/lightning.pb.go b/lnrpc/lightning.pb.go index b3c1eb03d3..a08e5af19c 100644 --- a/lnrpc/lightning.pb.go +++ b/lnrpc/lightning.pb.go @@ -20,11 +20,79 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +type OutputScriptType int32 + +const ( + OutputScriptType_SCRIPT_TYPE_PUBKEY_HASH OutputScriptType = 0 + OutputScriptType_SCRIPT_TYPE_SCRIPT_HASH OutputScriptType = 1 + OutputScriptType_SCRIPT_TYPE_WITNESS_V0_PUBKEY_HASH OutputScriptType = 2 + OutputScriptType_SCRIPT_TYPE_WITNESS_V0_SCRIPT_HASH OutputScriptType = 3 + OutputScriptType_SCRIPT_TYPE_PUBKEY OutputScriptType = 4 + OutputScriptType_SCRIPT_TYPE_MULTISIG OutputScriptType = 5 + OutputScriptType_SCRIPT_TYPE_NULLDATA OutputScriptType = 6 + OutputScriptType_SCRIPT_TYPE_NON_STANDARD OutputScriptType = 7 + OutputScriptType_SCRIPT_TYPE_WITNESS_UNKNOWN OutputScriptType = 8 +) + +// Enum value maps for OutputScriptType. +var ( + OutputScriptType_name = map[int32]string{ + 0: "SCRIPT_TYPE_PUBKEY_HASH", + 1: "SCRIPT_TYPE_SCRIPT_HASH", + 2: "SCRIPT_TYPE_WITNESS_V0_PUBKEY_HASH", + 3: "SCRIPT_TYPE_WITNESS_V0_SCRIPT_HASH", + 4: "SCRIPT_TYPE_PUBKEY", + 5: "SCRIPT_TYPE_MULTISIG", + 6: "SCRIPT_TYPE_NULLDATA", + 7: "SCRIPT_TYPE_NON_STANDARD", + 8: "SCRIPT_TYPE_WITNESS_UNKNOWN", + } + OutputScriptType_value = map[string]int32{ + "SCRIPT_TYPE_PUBKEY_HASH": 0, + "SCRIPT_TYPE_SCRIPT_HASH": 1, + "SCRIPT_TYPE_WITNESS_V0_PUBKEY_HASH": 2, + "SCRIPT_TYPE_WITNESS_V0_SCRIPT_HASH": 3, + "SCRIPT_TYPE_PUBKEY": 4, + "SCRIPT_TYPE_MULTISIG": 5, + "SCRIPT_TYPE_NULLDATA": 6, + "SCRIPT_TYPE_NON_STANDARD": 7, + "SCRIPT_TYPE_WITNESS_UNKNOWN": 8, + } +) + +func (x OutputScriptType) Enum() *OutputScriptType { + p := new(OutputScriptType) + *p = x + return p +} + +func (x OutputScriptType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (OutputScriptType) Descriptor() protoreflect.EnumDescriptor { + return file_lightning_proto_enumTypes[0].Descriptor() +} + +func (OutputScriptType) Type() protoreflect.EnumType { + return &file_lightning_proto_enumTypes[0] +} + +func (x OutputScriptType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use OutputScriptType.Descriptor instead. +func (OutputScriptType) EnumDescriptor() ([]byte, []int) { + return file_lightning_proto_rawDescGZIP(), []int{0} +} + // //`AddressType` has to be one of: // //- `p2wkh`: Pay to witness key hash (`WITNESS_PUBKEY_HASH` = 0) //- `np2wkh`: Pay to nested witness key hash (`NESTED_PUBKEY_HASH` = 1) +//- `p2tr`: Pay to taproot pubkey (`TAPROOT_PUBKEY` = 4) type AddressType int32 const ( @@ -32,6 +100,8 @@ const ( AddressType_NESTED_PUBKEY_HASH AddressType = 1 AddressType_UNUSED_WITNESS_PUBKEY_HASH AddressType = 2 AddressType_UNUSED_NESTED_PUBKEY_HASH AddressType = 3 + AddressType_TAPROOT_PUBKEY AddressType = 4 + AddressType_UNUSED_TAPROOT_PUBKEY AddressType = 5 ) // Enum value maps for AddressType. @@ -41,12 +111,16 @@ var ( 1: "NESTED_PUBKEY_HASH", 2: "UNUSED_WITNESS_PUBKEY_HASH", 3: "UNUSED_NESTED_PUBKEY_HASH", + 4: "TAPROOT_PUBKEY", + 5: "UNUSED_TAPROOT_PUBKEY", } AddressType_value = map[string]int32{ "WITNESS_PUBKEY_HASH": 0, "NESTED_PUBKEY_HASH": 1, "UNUSED_WITNESS_PUBKEY_HASH": 2, "UNUSED_NESTED_PUBKEY_HASH": 3, + "TAPROOT_PUBKEY": 4, + "UNUSED_TAPROOT_PUBKEY": 5, } ) @@ -61,11 +135,11 @@ func (x AddressType) String() string { } func (AddressType) Descriptor() protoreflect.EnumDescriptor { - return file_lightning_proto_enumTypes[0].Descriptor() + return file_lightning_proto_enumTypes[1].Descriptor() } func (AddressType) Type() protoreflect.EnumType { - return &file_lightning_proto_enumTypes[0] + return &file_lightning_proto_enumTypes[1] } func (x AddressType) Number() protoreflect.EnumNumber { @@ -74,7 +148,7 @@ func (x AddressType) Number() protoreflect.EnumNumber { // Deprecated: Use AddressType.Descriptor instead. func (AddressType) EnumDescriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{0} + return file_lightning_proto_rawDescGZIP(), []int{1} } type CommitmentType int32 @@ -136,11 +210,11 @@ func (x CommitmentType) String() string { } func (CommitmentType) Descriptor() protoreflect.EnumDescriptor { - return file_lightning_proto_enumTypes[1].Descriptor() + return file_lightning_proto_enumTypes[2].Descriptor() } func (CommitmentType) Type() protoreflect.EnumType { - return &file_lightning_proto_enumTypes[1] + return &file_lightning_proto_enumTypes[2] } func (x CommitmentType) Number() protoreflect.EnumNumber { @@ -149,7 +223,7 @@ func (x CommitmentType) Number() protoreflect.EnumNumber { // Deprecated: Use CommitmentType.Descriptor instead. func (CommitmentType) EnumDescriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{1} + return file_lightning_proto_rawDescGZIP(), []int{2} } type Initiator int32 @@ -188,11 +262,11 @@ func (x Initiator) String() string { } func (Initiator) Descriptor() protoreflect.EnumDescriptor { - return file_lightning_proto_enumTypes[2].Descriptor() + return file_lightning_proto_enumTypes[3].Descriptor() } func (Initiator) Type() protoreflect.EnumType { - return &file_lightning_proto_enumTypes[2] + return &file_lightning_proto_enumTypes[3] } func (x Initiator) Number() protoreflect.EnumNumber { @@ -201,7 +275,7 @@ func (x Initiator) Number() protoreflect.EnumNumber { // Deprecated: Use Initiator.Descriptor instead. func (Initiator) EnumDescriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{2} + return file_lightning_proto_rawDescGZIP(), []int{3} } type ResolutionType int32 @@ -253,11 +327,11 @@ func (x ResolutionType) String() string { } func (ResolutionType) Descriptor() protoreflect.EnumDescriptor { - return file_lightning_proto_enumTypes[3].Descriptor() + return file_lightning_proto_enumTypes[4].Descriptor() } func (ResolutionType) Type() protoreflect.EnumType { - return &file_lightning_proto_enumTypes[3] + return &file_lightning_proto_enumTypes[4] } func (x ResolutionType) Number() protoreflect.EnumNumber { @@ -266,7 +340,7 @@ func (x ResolutionType) Number() protoreflect.EnumNumber { // Deprecated: Use ResolutionType.Descriptor instead. func (ResolutionType) EnumDescriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{3} + return file_lightning_proto_rawDescGZIP(), []int{4} } type ResolutionOutcome int32 @@ -324,11 +398,11 @@ func (x ResolutionOutcome) String() string { } func (ResolutionOutcome) Descriptor() protoreflect.EnumDescriptor { - return file_lightning_proto_enumTypes[4].Descriptor() + return file_lightning_proto_enumTypes[5].Descriptor() } func (ResolutionOutcome) Type() protoreflect.EnumType { - return &file_lightning_proto_enumTypes[4] + return &file_lightning_proto_enumTypes[5] } func (x ResolutionOutcome) Number() protoreflect.EnumNumber { @@ -337,7 +411,7 @@ func (x ResolutionOutcome) Number() protoreflect.EnumNumber { // Deprecated: Use ResolutionOutcome.Descriptor instead. func (ResolutionOutcome) EnumDescriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{4} + return file_lightning_proto_rawDescGZIP(), []int{5} } type NodeMetricType int32 @@ -370,11 +444,11 @@ func (x NodeMetricType) String() string { } func (NodeMetricType) Descriptor() protoreflect.EnumDescriptor { - return file_lightning_proto_enumTypes[5].Descriptor() + return file_lightning_proto_enumTypes[6].Descriptor() } func (NodeMetricType) Type() protoreflect.EnumType { - return &file_lightning_proto_enumTypes[5] + return &file_lightning_proto_enumTypes[6] } func (x NodeMetricType) Number() protoreflect.EnumNumber { @@ -383,7 +457,7 @@ func (x NodeMetricType) Number() protoreflect.EnumNumber { // Deprecated: Use NodeMetricType.Descriptor instead. func (NodeMetricType) EnumDescriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{5} + return file_lightning_proto_rawDescGZIP(), []int{6} } type InvoiceHTLCState int32 @@ -419,11 +493,11 @@ func (x InvoiceHTLCState) String() string { } func (InvoiceHTLCState) Descriptor() protoreflect.EnumDescriptor { - return file_lightning_proto_enumTypes[6].Descriptor() + return file_lightning_proto_enumTypes[7].Descriptor() } func (InvoiceHTLCState) Type() protoreflect.EnumType { - return &file_lightning_proto_enumTypes[6] + return &file_lightning_proto_enumTypes[7] } func (x InvoiceHTLCState) Number() protoreflect.EnumNumber { @@ -432,7 +506,7 @@ func (x InvoiceHTLCState) Number() protoreflect.EnumNumber { // Deprecated: Use InvoiceHTLCState.Descriptor instead. func (InvoiceHTLCState) EnumDescriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{6} + return file_lightning_proto_rawDescGZIP(), []int{7} } type PaymentFailureReason int32 @@ -491,11 +565,11 @@ func (x PaymentFailureReason) String() string { } func (PaymentFailureReason) Descriptor() protoreflect.EnumDescriptor { - return file_lightning_proto_enumTypes[7].Descriptor() + return file_lightning_proto_enumTypes[8].Descriptor() } func (PaymentFailureReason) Type() protoreflect.EnumType { - return &file_lightning_proto_enumTypes[7] + return &file_lightning_proto_enumTypes[8] } func (x PaymentFailureReason) Number() protoreflect.EnumNumber { @@ -504,7 +578,7 @@ func (x PaymentFailureReason) Number() protoreflect.EnumNumber { // Deprecated: Use PaymentFailureReason.Descriptor instead. func (PaymentFailureReason) EnumDescriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{7} + return file_lightning_proto_rawDescGZIP(), []int{8} } type FeatureBit int32 @@ -606,11 +680,11 @@ func (x FeatureBit) String() string { } func (FeatureBit) Descriptor() protoreflect.EnumDescriptor { - return file_lightning_proto_enumTypes[8].Descriptor() + return file_lightning_proto_enumTypes[9].Descriptor() } func (FeatureBit) Type() protoreflect.EnumType { - return &file_lightning_proto_enumTypes[8] + return &file_lightning_proto_enumTypes[9] } func (x FeatureBit) Number() protoreflect.EnumNumber { @@ -619,7 +693,7 @@ func (x FeatureBit) Number() protoreflect.EnumNumber { // Deprecated: Use FeatureBit.Descriptor instead. func (FeatureBit) EnumDescriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{8} + return file_lightning_proto_rawDescGZIP(), []int{9} } type UpdateFailure int32 @@ -661,11 +735,11 @@ func (x UpdateFailure) String() string { } func (UpdateFailure) Descriptor() protoreflect.EnumDescriptor { - return file_lightning_proto_enumTypes[9].Descriptor() + return file_lightning_proto_enumTypes[10].Descriptor() } func (UpdateFailure) Type() protoreflect.EnumType { - return &file_lightning_proto_enumTypes[9] + return &file_lightning_proto_enumTypes[10] } func (x UpdateFailure) Number() protoreflect.EnumNumber { @@ -674,7 +748,7 @@ func (x UpdateFailure) Number() protoreflect.EnumNumber { // Deprecated: Use UpdateFailure.Descriptor instead. func (UpdateFailure) EnumDescriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{9} + return file_lightning_proto_rawDescGZIP(), []int{10} } type ChannelCloseSummary_ClosureType int32 @@ -719,11 +793,11 @@ func (x ChannelCloseSummary_ClosureType) String() string { } func (ChannelCloseSummary_ClosureType) Descriptor() protoreflect.EnumDescriptor { - return file_lightning_proto_enumTypes[10].Descriptor() + return file_lightning_proto_enumTypes[11].Descriptor() } func (ChannelCloseSummary_ClosureType) Type() protoreflect.EnumType { - return &file_lightning_proto_enumTypes[10] + return &file_lightning_proto_enumTypes[11] } func (x ChannelCloseSummary_ClosureType) Number() protoreflect.EnumNumber { @@ -732,7 +806,7 @@ func (x ChannelCloseSummary_ClosureType) Number() protoreflect.EnumNumber { // Deprecated: Use ChannelCloseSummary_ClosureType.Descriptor instead. func (ChannelCloseSummary_ClosureType) EnumDescriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{40, 0} + return file_lightning_proto_rawDescGZIP(), []int{41, 0} } type Peer_SyncType int32 @@ -779,11 +853,11 @@ func (x Peer_SyncType) String() string { } func (Peer_SyncType) Descriptor() protoreflect.EnumDescriptor { - return file_lightning_proto_enumTypes[11].Descriptor() + return file_lightning_proto_enumTypes[12].Descriptor() } func (Peer_SyncType) Type() protoreflect.EnumType { - return &file_lightning_proto_enumTypes[11] + return &file_lightning_proto_enumTypes[12] } func (x Peer_SyncType) Number() protoreflect.EnumNumber { @@ -792,7 +866,7 @@ func (x Peer_SyncType) Number() protoreflect.EnumNumber { // Deprecated: Use Peer_SyncType.Descriptor instead. func (Peer_SyncType) EnumDescriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{44, 0} + return file_lightning_proto_rawDescGZIP(), []int{45, 0} } type PeerEvent_EventType int32 @@ -825,11 +899,11 @@ func (x PeerEvent_EventType) String() string { } func (PeerEvent_EventType) Descriptor() protoreflect.EnumDescriptor { - return file_lightning_proto_enumTypes[12].Descriptor() + return file_lightning_proto_enumTypes[13].Descriptor() } func (PeerEvent_EventType) Type() protoreflect.EnumType { - return &file_lightning_proto_enumTypes[12] + return &file_lightning_proto_enumTypes[13] } func (x PeerEvent_EventType) Number() protoreflect.EnumNumber { @@ -838,7 +912,7 @@ func (x PeerEvent_EventType) Number() protoreflect.EnumNumber { // Deprecated: Use PeerEvent_EventType.Descriptor instead. func (PeerEvent_EventType) EnumDescriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{49, 0} + return file_lightning_proto_rawDescGZIP(), []int{50, 0} } type PendingChannelsResponse_ForceClosedChannel_AnchorState int32 @@ -874,11 +948,11 @@ func (x PendingChannelsResponse_ForceClosedChannel_AnchorState) String() string } func (PendingChannelsResponse_ForceClosedChannel_AnchorState) Descriptor() protoreflect.EnumDescriptor { - return file_lightning_proto_enumTypes[13].Descriptor() + return file_lightning_proto_enumTypes[14].Descriptor() } func (PendingChannelsResponse_ForceClosedChannel_AnchorState) Type() protoreflect.EnumType { - return &file_lightning_proto_enumTypes[13] + return &file_lightning_proto_enumTypes[14] } func (x PendingChannelsResponse_ForceClosedChannel_AnchorState) Number() protoreflect.EnumNumber { @@ -887,7 +961,7 @@ func (x PendingChannelsResponse_ForceClosedChannel_AnchorState) Number() protore // Deprecated: Use PendingChannelsResponse_ForceClosedChannel_AnchorState.Descriptor instead. func (PendingChannelsResponse_ForceClosedChannel_AnchorState) EnumDescriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{79, 5, 0} + return file_lightning_proto_rawDescGZIP(), []int{80, 5, 0} } type ChannelEventUpdate_UpdateType int32 @@ -932,11 +1006,11 @@ func (x ChannelEventUpdate_UpdateType) String() string { } func (ChannelEventUpdate_UpdateType) Descriptor() protoreflect.EnumDescriptor { - return file_lightning_proto_enumTypes[14].Descriptor() + return file_lightning_proto_enumTypes[15].Descriptor() } func (ChannelEventUpdate_UpdateType) Type() protoreflect.EnumType { - return &file_lightning_proto_enumTypes[14] + return &file_lightning_proto_enumTypes[15] } func (x ChannelEventUpdate_UpdateType) Number() protoreflect.EnumNumber { @@ -945,7 +1019,7 @@ func (x ChannelEventUpdate_UpdateType) Number() protoreflect.EnumNumber { // Deprecated: Use ChannelEventUpdate_UpdateType.Descriptor instead. func (ChannelEventUpdate_UpdateType) EnumDescriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{81, 0} + return file_lightning_proto_rawDescGZIP(), []int{82, 0} } type Invoice_InvoiceState int32 @@ -984,11 +1058,11 @@ func (x Invoice_InvoiceState) String() string { } func (Invoice_InvoiceState) Descriptor() protoreflect.EnumDescriptor { - return file_lightning_proto_enumTypes[15].Descriptor() + return file_lightning_proto_enumTypes[16].Descriptor() } func (Invoice_InvoiceState) Type() protoreflect.EnumType { - return &file_lightning_proto_enumTypes[15] + return &file_lightning_proto_enumTypes[16] } func (x Invoice_InvoiceState) Number() protoreflect.EnumNumber { @@ -997,7 +1071,7 @@ func (x Invoice_InvoiceState) Number() protoreflect.EnumNumber { // Deprecated: Use Invoice_InvoiceState.Descriptor instead. func (Invoice_InvoiceState) EnumDescriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{121, 0} + return file_lightning_proto_rawDescGZIP(), []int{122, 0} } type Payment_PaymentStatus int32 @@ -1036,11 +1110,11 @@ func (x Payment_PaymentStatus) String() string { } func (Payment_PaymentStatus) Descriptor() protoreflect.EnumDescriptor { - return file_lightning_proto_enumTypes[16].Descriptor() + return file_lightning_proto_enumTypes[17].Descriptor() } func (Payment_PaymentStatus) Type() protoreflect.EnumType { - return &file_lightning_proto_enumTypes[16] + return &file_lightning_proto_enumTypes[17] } func (x Payment_PaymentStatus) Number() protoreflect.EnumNumber { @@ -1049,7 +1123,7 @@ func (x Payment_PaymentStatus) Number() protoreflect.EnumNumber { // Deprecated: Use Payment_PaymentStatus.Descriptor instead. func (Payment_PaymentStatus) EnumDescriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{129, 0} + return file_lightning_proto_rawDescGZIP(), []int{130, 0} } type HTLCAttempt_HTLCStatus int32 @@ -1085,11 +1159,11 @@ func (x HTLCAttempt_HTLCStatus) String() string { } func (HTLCAttempt_HTLCStatus) Descriptor() protoreflect.EnumDescriptor { - return file_lightning_proto_enumTypes[17].Descriptor() + return file_lightning_proto_enumTypes[18].Descriptor() } func (HTLCAttempt_HTLCStatus) Type() protoreflect.EnumType { - return &file_lightning_proto_enumTypes[17] + return &file_lightning_proto_enumTypes[18] } func (x HTLCAttempt_HTLCStatus) Number() protoreflect.EnumNumber { @@ -1098,7 +1172,7 @@ func (x HTLCAttempt_HTLCStatus) Number() protoreflect.EnumNumber { // Deprecated: Use HTLCAttempt_HTLCStatus.Descriptor instead. func (HTLCAttempt_HTLCStatus) EnumDescriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{130, 0} + return file_lightning_proto_rawDescGZIP(), []int{131, 0} } type Failure_FailureCode int32 @@ -1220,11 +1294,11 @@ func (x Failure_FailureCode) String() string { } func (Failure_FailureCode) Descriptor() protoreflect.EnumDescriptor { - return file_lightning_proto_enumTypes[18].Descriptor() + return file_lightning_proto_enumTypes[19].Descriptor() } func (Failure_FailureCode) Type() protoreflect.EnumType { - return &file_lightning_proto_enumTypes[18] + return &file_lightning_proto_enumTypes[19] } func (x Failure_FailureCode) Number() protoreflect.EnumNumber { @@ -1233,7 +1307,7 @@ func (x Failure_FailureCode) Number() protoreflect.EnumNumber { // Deprecated: Use Failure_FailureCode.Descriptor instead. func (Failure_FailureCode) EnumDescriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{173, 0} + return file_lightning_proto_rawDescGZIP(), []int{174, 0} } type SubscribeCustomMessagesRequest struct { @@ -1537,6 +1611,99 @@ func (x *Utxo) GetConfirmations() int64 { return 0 } +type OutputDetail struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The type of the output + OutputType OutputScriptType `protobuf:"varint,1,opt,name=output_type,json=outputType,proto3,enum=lnrpc.OutputScriptType" json:"output_type,omitempty"` + // The address + Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"` + // The pkscript in hex + PkScript string `protobuf:"bytes,3,opt,name=pk_script,json=pkScript,proto3" json:"pk_script,omitempty"` + // The output index used in the raw transaction + OutputIndex int64 `protobuf:"varint,4,opt,name=output_index,json=outputIndex,proto3" json:"output_index,omitempty"` + // The value of the output coin in satoshis + Amount int64 `protobuf:"varint,5,opt,name=amount,proto3" json:"amount,omitempty"` + // Denotes if the output is controlled by the internal wallet + IsOurAddress bool `protobuf:"varint,6,opt,name=is_our_address,json=isOurAddress,proto3" json:"is_our_address,omitempty"` +} + +func (x *OutputDetail) Reset() { + *x = OutputDetail{} + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OutputDetail) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OutputDetail) ProtoMessage() {} + +func (x *OutputDetail) ProtoReflect() protoreflect.Message { + mi := &file_lightning_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OutputDetail.ProtoReflect.Descriptor instead. +func (*OutputDetail) Descriptor() ([]byte, []int) { + return file_lightning_proto_rawDescGZIP(), []int{5} +} + +func (x *OutputDetail) GetOutputType() OutputScriptType { + if x != nil { + return x.OutputType + } + return OutputScriptType_SCRIPT_TYPE_PUBKEY_HASH +} + +func (x *OutputDetail) GetAddress() string { + if x != nil { + return x.Address + } + return "" +} + +func (x *OutputDetail) GetPkScript() string { + if x != nil { + return x.PkScript + } + return "" +} + +func (x *OutputDetail) GetOutputIndex() int64 { + if x != nil { + return x.OutputIndex + } + return 0 +} + +func (x *OutputDetail) GetAmount() int64 { + if x != nil { + return x.Amount + } + return 0 +} + +func (x *OutputDetail) GetIsOurAddress() bool { + if x != nil { + return x.IsOurAddress + } + return false +} + type Transaction struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1556,8 +1723,13 @@ type Transaction struct { TimeStamp int64 `protobuf:"varint,6,opt,name=time_stamp,json=timeStamp,proto3" json:"time_stamp,omitempty"` // Fees paid for this transaction TotalFees int64 `protobuf:"varint,7,opt,name=total_fees,json=totalFees,proto3" json:"total_fees,omitempty"` - // Addresses that received funds for this transaction + // Addresses that received funds for this transaction. Deprecated as it is + // now incorporated in the output_details field. + // + // Deprecated: Do not use. DestAddresses []string `protobuf:"bytes,8,rep,name=dest_addresses,json=destAddresses,proto3" json:"dest_addresses,omitempty"` + // Outputs that received funds for this transaction + OutputDetails []*OutputDetail `protobuf:"bytes,11,rep,name=output_details,json=outputDetails,proto3" json:"output_details,omitempty"` // The raw transaction hex. RawTxHex string `protobuf:"bytes,9,opt,name=raw_tx_hex,json=rawTxHex,proto3" json:"raw_tx_hex,omitempty"` // A label that was optionally set on transaction broadcast. @@ -1567,7 +1739,7 @@ type Transaction struct { func (x *Transaction) Reset() { *x = Transaction{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[5] + mi := &file_lightning_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1580,7 +1752,7 @@ func (x *Transaction) String() string { func (*Transaction) ProtoMessage() {} func (x *Transaction) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[5] + mi := &file_lightning_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1593,7 +1765,7 @@ func (x *Transaction) ProtoReflect() protoreflect.Message { // Deprecated: Use Transaction.ProtoReflect.Descriptor instead. func (*Transaction) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{5} + return file_lightning_proto_rawDescGZIP(), []int{6} } func (x *Transaction) GetTxHash() string { @@ -1645,6 +1817,7 @@ func (x *Transaction) GetTotalFees() int64 { return 0 } +// Deprecated: Do not use. func (x *Transaction) GetDestAddresses() []string { if x != nil { return x.DestAddresses @@ -1652,6 +1825,13 @@ func (x *Transaction) GetDestAddresses() []string { return nil } +func (x *Transaction) GetOutputDetails() []*OutputDetail { + if x != nil { + return x.OutputDetails + } + return nil +} + func (x *Transaction) GetRawTxHex() string { if x != nil { return x.RawTxHex @@ -1689,7 +1869,7 @@ type GetTransactionsRequest struct { func (x *GetTransactionsRequest) Reset() { *x = GetTransactionsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[6] + mi := &file_lightning_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1702,7 +1882,7 @@ func (x *GetTransactionsRequest) String() string { func (*GetTransactionsRequest) ProtoMessage() {} func (x *GetTransactionsRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[6] + mi := &file_lightning_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1715,7 +1895,7 @@ func (x *GetTransactionsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetTransactionsRequest.ProtoReflect.Descriptor instead. func (*GetTransactionsRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{6} + return file_lightning_proto_rawDescGZIP(), []int{7} } func (x *GetTransactionsRequest) GetStartHeight() int32 { @@ -1751,7 +1931,7 @@ type TransactionDetails struct { func (x *TransactionDetails) Reset() { *x = TransactionDetails{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[7] + mi := &file_lightning_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1764,7 +1944,7 @@ func (x *TransactionDetails) String() string { func (*TransactionDetails) ProtoMessage() {} func (x *TransactionDetails) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[7] + mi := &file_lightning_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1777,7 +1957,7 @@ func (x *TransactionDetails) ProtoReflect() protoreflect.Message { // Deprecated: Use TransactionDetails.ProtoReflect.Descriptor instead. func (*TransactionDetails) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{7} + return file_lightning_proto_rawDescGZIP(), []int{8} } func (x *TransactionDetails) GetTransactions() []*Transaction { @@ -1802,7 +1982,7 @@ type FeeLimit struct { func (x *FeeLimit) Reset() { *x = FeeLimit{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[8] + mi := &file_lightning_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1815,7 +1995,7 @@ func (x *FeeLimit) String() string { func (*FeeLimit) ProtoMessage() {} func (x *FeeLimit) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[8] + mi := &file_lightning_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1828,7 +2008,7 @@ func (x *FeeLimit) ProtoReflect() protoreflect.Message { // Deprecated: Use FeeLimit.ProtoReflect.Descriptor instead. func (*FeeLimit) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{8} + return file_lightning_proto_rawDescGZIP(), []int{9} } func (m *FeeLimit) GetLimit() isFeeLimit_Limit { @@ -1938,7 +2118,8 @@ type SendRequest struct { //The maximum number of satoshis that will be paid as a fee of the payment. //This value can be represented either as a percentage of the amount being //sent, or as a fixed amount of the maximum fee the user is willing the pay to - //send the payment. + //send the payment. If not specified, lnd will use a default value of 100% + //fees for small amounts (<=1k sat) or 5% fees for larger amounts. FeeLimit *FeeLimit `protobuf:"bytes,8,opt,name=fee_limit,json=feeLimit,proto3" json:"fee_limit,omitempty"` // //The channel id of the channel that must be taken to the first hop. If zero, @@ -1976,7 +2157,7 @@ type SendRequest struct { func (x *SendRequest) Reset() { *x = SendRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[9] + mi := &file_lightning_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1989,7 +2170,7 @@ func (x *SendRequest) String() string { func (*SendRequest) ProtoMessage() {} func (x *SendRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[9] + mi := &file_lightning_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2002,7 +2183,7 @@ func (x *SendRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SendRequest.ProtoReflect.Descriptor instead. func (*SendRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{9} + return file_lightning_proto_rawDescGZIP(), []int{10} } func (x *SendRequest) GetDest() []byte { @@ -2133,7 +2314,7 @@ type SendResponse struct { func (x *SendResponse) Reset() { *x = SendResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[10] + mi := &file_lightning_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2146,7 +2327,7 @@ func (x *SendResponse) String() string { func (*SendResponse) ProtoMessage() {} func (x *SendResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[10] + mi := &file_lightning_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2159,7 +2340,7 @@ func (x *SendResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SendResponse.ProtoReflect.Descriptor instead. func (*SendResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{10} + return file_lightning_proto_rawDescGZIP(), []int{11} } func (x *SendResponse) GetPaymentError() string { @@ -2212,7 +2393,7 @@ type SendToRouteRequest struct { func (x *SendToRouteRequest) Reset() { *x = SendToRouteRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[11] + mi := &file_lightning_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2225,7 +2406,7 @@ func (x *SendToRouteRequest) String() string { func (*SendToRouteRequest) ProtoMessage() {} func (x *SendToRouteRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[11] + mi := &file_lightning_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2238,7 +2419,7 @@ func (x *SendToRouteRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SendToRouteRequest.ProtoReflect.Descriptor instead. func (*SendToRouteRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{11} + return file_lightning_proto_rawDescGZIP(), []int{12} } func (x *SendToRouteRequest) GetPaymentHash() []byte { @@ -2308,7 +2489,7 @@ type ChannelAcceptRequest struct { func (x *ChannelAcceptRequest) Reset() { *x = ChannelAcceptRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[12] + mi := &file_lightning_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2321,7 +2502,7 @@ func (x *ChannelAcceptRequest) String() string { func (*ChannelAcceptRequest) ProtoMessage() {} func (x *ChannelAcceptRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[12] + mi := &file_lightning_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2334,7 +2515,7 @@ func (x *ChannelAcceptRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelAcceptRequest.ProtoReflect.Descriptor instead. func (*ChannelAcceptRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{12} + return file_lightning_proto_rawDescGZIP(), []int{13} } func (x *ChannelAcceptRequest) GetNodePubkey() []byte { @@ -2485,7 +2666,7 @@ type ChannelAcceptResponse struct { func (x *ChannelAcceptResponse) Reset() { *x = ChannelAcceptResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[13] + mi := &file_lightning_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2498,7 +2679,7 @@ func (x *ChannelAcceptResponse) String() string { func (*ChannelAcceptResponse) ProtoMessage() {} func (x *ChannelAcceptResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[13] + mi := &file_lightning_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2511,7 +2692,7 @@ func (x *ChannelAcceptResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelAcceptResponse.ProtoReflect.Descriptor instead. func (*ChannelAcceptResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{13} + return file_lightning_proto_rawDescGZIP(), []int{14} } func (x *ChannelAcceptResponse) GetAccept() bool { @@ -2600,7 +2781,7 @@ type ChannelPoint struct { func (x *ChannelPoint) Reset() { *x = ChannelPoint{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[14] + mi := &file_lightning_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2613,7 +2794,7 @@ func (x *ChannelPoint) String() string { func (*ChannelPoint) ProtoMessage() {} func (x *ChannelPoint) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[14] + mi := &file_lightning_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2626,7 +2807,7 @@ func (x *ChannelPoint) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelPoint.ProtoReflect.Descriptor instead. func (*ChannelPoint) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{14} + return file_lightning_proto_rawDescGZIP(), []int{15} } func (m *ChannelPoint) GetFundingTxid() isChannelPoint_FundingTxid { @@ -2695,7 +2876,7 @@ type OutPoint struct { func (x *OutPoint) Reset() { *x = OutPoint{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[15] + mi := &file_lightning_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2708,7 +2889,7 @@ func (x *OutPoint) String() string { func (*OutPoint) ProtoMessage() {} func (x *OutPoint) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[15] + mi := &file_lightning_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2721,7 +2902,7 @@ func (x *OutPoint) ProtoReflect() protoreflect.Message { // Deprecated: Use OutPoint.ProtoReflect.Descriptor instead. func (*OutPoint) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{15} + return file_lightning_proto_rawDescGZIP(), []int{16} } func (x *OutPoint) GetTxidBytes() []byte { @@ -2760,7 +2941,7 @@ type LightningAddress struct { func (x *LightningAddress) Reset() { *x = LightningAddress{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[16] + mi := &file_lightning_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2773,7 +2954,7 @@ func (x *LightningAddress) String() string { func (*LightningAddress) ProtoMessage() {} func (x *LightningAddress) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[16] + mi := &file_lightning_proto_msgTypes[17] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2786,7 +2967,7 @@ func (x *LightningAddress) ProtoReflect() protoreflect.Message { // Deprecated: Use LightningAddress.ProtoReflect.Descriptor instead. func (*LightningAddress) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{16} + return file_lightning_proto_rawDescGZIP(), []int{17} } func (x *LightningAddress) GetPubkey() string { @@ -2823,7 +3004,7 @@ type EstimateFeeRequest struct { func (x *EstimateFeeRequest) Reset() { *x = EstimateFeeRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[17] + mi := &file_lightning_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2836,7 +3017,7 @@ func (x *EstimateFeeRequest) String() string { func (*EstimateFeeRequest) ProtoMessage() {} func (x *EstimateFeeRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[17] + mi := &file_lightning_proto_msgTypes[18] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2849,7 +3030,7 @@ func (x *EstimateFeeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use EstimateFeeRequest.ProtoReflect.Descriptor instead. func (*EstimateFeeRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{17} + return file_lightning_proto_rawDescGZIP(), []int{18} } func (x *EstimateFeeRequest) GetAddrToAmount() map[string]int64 { @@ -2899,7 +3080,7 @@ type EstimateFeeResponse struct { func (x *EstimateFeeResponse) Reset() { *x = EstimateFeeResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[18] + mi := &file_lightning_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2912,7 +3093,7 @@ func (x *EstimateFeeResponse) String() string { func (*EstimateFeeResponse) ProtoMessage() {} func (x *EstimateFeeResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[18] + mi := &file_lightning_proto_msgTypes[19] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2925,7 +3106,7 @@ func (x *EstimateFeeResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use EstimateFeeResponse.ProtoReflect.Descriptor instead. func (*EstimateFeeResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{18} + return file_lightning_proto_rawDescGZIP(), []int{19} } func (x *EstimateFeeResponse) GetFeeSat() int64 { @@ -2981,7 +3162,7 @@ type SendManyRequest struct { func (x *SendManyRequest) Reset() { *x = SendManyRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[19] + mi := &file_lightning_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2994,7 +3175,7 @@ func (x *SendManyRequest) String() string { func (*SendManyRequest) ProtoMessage() {} func (x *SendManyRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[19] + mi := &file_lightning_proto_msgTypes[20] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3007,7 +3188,7 @@ func (x *SendManyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SendManyRequest.ProtoReflect.Descriptor instead. func (*SendManyRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{19} + return file_lightning_proto_rawDescGZIP(), []int{20} } func (x *SendManyRequest) GetAddrToAmount() map[string]int64 { @@ -3072,7 +3253,7 @@ type SendManyResponse struct { func (x *SendManyResponse) Reset() { *x = SendManyResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[20] + mi := &file_lightning_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3085,7 +3266,7 @@ func (x *SendManyResponse) String() string { func (*SendManyResponse) ProtoMessage() {} func (x *SendManyResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[20] + mi := &file_lightning_proto_msgTypes[21] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3098,7 +3279,7 @@ func (x *SendManyResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SendManyResponse.ProtoReflect.Descriptor instead. func (*SendManyResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{20} + return file_lightning_proto_rawDescGZIP(), []int{21} } func (x *SendManyResponse) GetTxid() string { @@ -3146,7 +3327,7 @@ type SendCoinsRequest struct { func (x *SendCoinsRequest) Reset() { *x = SendCoinsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[21] + mi := &file_lightning_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3159,7 +3340,7 @@ func (x *SendCoinsRequest) String() string { func (*SendCoinsRequest) ProtoMessage() {} func (x *SendCoinsRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[21] + mi := &file_lightning_proto_msgTypes[22] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3172,7 +3353,7 @@ func (x *SendCoinsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SendCoinsRequest.ProtoReflect.Descriptor instead. func (*SendCoinsRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{21} + return file_lightning_proto_rawDescGZIP(), []int{22} } func (x *SendCoinsRequest) GetAddr() string { @@ -3251,7 +3432,7 @@ type SendCoinsResponse struct { func (x *SendCoinsResponse) Reset() { *x = SendCoinsResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[22] + mi := &file_lightning_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3264,7 +3445,7 @@ func (x *SendCoinsResponse) String() string { func (*SendCoinsResponse) ProtoMessage() {} func (x *SendCoinsResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[22] + mi := &file_lightning_proto_msgTypes[23] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3277,7 +3458,7 @@ func (x *SendCoinsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SendCoinsResponse.ProtoReflect.Descriptor instead. func (*SendCoinsResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{22} + return file_lightning_proto_rawDescGZIP(), []int{23} } func (x *SendCoinsResponse) GetTxid() string { @@ -3303,7 +3484,7 @@ type ListUnspentRequest struct { func (x *ListUnspentRequest) Reset() { *x = ListUnspentRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[23] + mi := &file_lightning_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3316,7 +3497,7 @@ func (x *ListUnspentRequest) String() string { func (*ListUnspentRequest) ProtoMessage() {} func (x *ListUnspentRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[23] + mi := &file_lightning_proto_msgTypes[24] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3329,7 +3510,7 @@ func (x *ListUnspentRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListUnspentRequest.ProtoReflect.Descriptor instead. func (*ListUnspentRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{23} + return file_lightning_proto_rawDescGZIP(), []int{24} } func (x *ListUnspentRequest) GetMinConfs() int32 { @@ -3365,7 +3546,7 @@ type ListUnspentResponse struct { func (x *ListUnspentResponse) Reset() { *x = ListUnspentResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[24] + mi := &file_lightning_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3378,7 +3559,7 @@ func (x *ListUnspentResponse) String() string { func (*ListUnspentResponse) ProtoMessage() {} func (x *ListUnspentResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[24] + mi := &file_lightning_proto_msgTypes[25] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3391,7 +3572,7 @@ func (x *ListUnspentResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListUnspentResponse.ProtoReflect.Descriptor instead. func (*ListUnspentResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{24} + return file_lightning_proto_rawDescGZIP(), []int{25} } func (x *ListUnspentResponse) GetUtxos() []*Utxo { @@ -3417,7 +3598,7 @@ type NewAddressRequest struct { func (x *NewAddressRequest) Reset() { *x = NewAddressRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[25] + mi := &file_lightning_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3430,7 +3611,7 @@ func (x *NewAddressRequest) String() string { func (*NewAddressRequest) ProtoMessage() {} func (x *NewAddressRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[25] + mi := &file_lightning_proto_msgTypes[26] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3443,7 +3624,7 @@ func (x *NewAddressRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use NewAddressRequest.ProtoReflect.Descriptor instead. func (*NewAddressRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{25} + return file_lightning_proto_rawDescGZIP(), []int{26} } func (x *NewAddressRequest) GetType() AddressType { @@ -3472,7 +3653,7 @@ type NewAddressResponse struct { func (x *NewAddressResponse) Reset() { *x = NewAddressResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[26] + mi := &file_lightning_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3485,7 +3666,7 @@ func (x *NewAddressResponse) String() string { func (*NewAddressResponse) ProtoMessage() {} func (x *NewAddressResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[26] + mi := &file_lightning_proto_msgTypes[27] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3498,7 +3679,7 @@ func (x *NewAddressResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use NewAddressResponse.ProtoReflect.Descriptor instead. func (*NewAddressResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{26} + return file_lightning_proto_rawDescGZIP(), []int{27} } func (x *NewAddressResponse) GetAddress() string { @@ -3526,7 +3707,7 @@ type SignMessageRequest struct { func (x *SignMessageRequest) Reset() { *x = SignMessageRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[27] + mi := &file_lightning_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3539,7 +3720,7 @@ func (x *SignMessageRequest) String() string { func (*SignMessageRequest) ProtoMessage() {} func (x *SignMessageRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[27] + mi := &file_lightning_proto_msgTypes[28] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3552,7 +3733,7 @@ func (x *SignMessageRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SignMessageRequest.ProtoReflect.Descriptor instead. func (*SignMessageRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{27} + return file_lightning_proto_rawDescGZIP(), []int{28} } func (x *SignMessageRequest) GetMsg() []byte { @@ -3581,7 +3762,7 @@ type SignMessageResponse struct { func (x *SignMessageResponse) Reset() { *x = SignMessageResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[28] + mi := &file_lightning_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3594,7 +3775,7 @@ func (x *SignMessageResponse) String() string { func (*SignMessageResponse) ProtoMessage() {} func (x *SignMessageResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[28] + mi := &file_lightning_proto_msgTypes[29] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3607,7 +3788,7 @@ func (x *SignMessageResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SignMessageResponse.ProtoReflect.Descriptor instead. func (*SignMessageResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{28} + return file_lightning_proto_rawDescGZIP(), []int{29} } func (x *SignMessageResponse) GetSignature() string { @@ -3633,7 +3814,7 @@ type VerifyMessageRequest struct { func (x *VerifyMessageRequest) Reset() { *x = VerifyMessageRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[29] + mi := &file_lightning_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3646,7 +3827,7 @@ func (x *VerifyMessageRequest) String() string { func (*VerifyMessageRequest) ProtoMessage() {} func (x *VerifyMessageRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[29] + mi := &file_lightning_proto_msgTypes[30] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3659,7 +3840,7 @@ func (x *VerifyMessageRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use VerifyMessageRequest.ProtoReflect.Descriptor instead. func (*VerifyMessageRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{29} + return file_lightning_proto_rawDescGZIP(), []int{30} } func (x *VerifyMessageRequest) GetMsg() []byte { @@ -3690,7 +3871,7 @@ type VerifyMessageResponse struct { func (x *VerifyMessageResponse) Reset() { *x = VerifyMessageResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[30] + mi := &file_lightning_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3703,7 +3884,7 @@ func (x *VerifyMessageResponse) String() string { func (*VerifyMessageResponse) ProtoMessage() {} func (x *VerifyMessageResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[30] + mi := &file_lightning_proto_msgTypes[31] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3716,7 +3897,7 @@ func (x *VerifyMessageResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use VerifyMessageResponse.ProtoReflect.Descriptor instead. func (*VerifyMessageResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{30} + return file_lightning_proto_rawDescGZIP(), []int{31} } func (x *VerifyMessageResponse) GetValid() bool { @@ -3752,7 +3933,7 @@ type ConnectPeerRequest struct { func (x *ConnectPeerRequest) Reset() { *x = ConnectPeerRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[31] + mi := &file_lightning_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3765,7 +3946,7 @@ func (x *ConnectPeerRequest) String() string { func (*ConnectPeerRequest) ProtoMessage() {} func (x *ConnectPeerRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[31] + mi := &file_lightning_proto_msgTypes[32] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3778,7 +3959,7 @@ func (x *ConnectPeerRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ConnectPeerRequest.ProtoReflect.Descriptor instead. func (*ConnectPeerRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{31} + return file_lightning_proto_rawDescGZIP(), []int{32} } func (x *ConnectPeerRequest) GetAddr() *LightningAddress { @@ -3811,7 +3992,7 @@ type ConnectPeerResponse struct { func (x *ConnectPeerResponse) Reset() { *x = ConnectPeerResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[32] + mi := &file_lightning_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3824,7 +4005,7 @@ func (x *ConnectPeerResponse) String() string { func (*ConnectPeerResponse) ProtoMessage() {} func (x *ConnectPeerResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[32] + mi := &file_lightning_proto_msgTypes[33] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3837,7 +4018,7 @@ func (x *ConnectPeerResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ConnectPeerResponse.ProtoReflect.Descriptor instead. func (*ConnectPeerResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{32} + return file_lightning_proto_rawDescGZIP(), []int{33} } type DisconnectPeerRequest struct { @@ -3852,7 +4033,7 @@ type DisconnectPeerRequest struct { func (x *DisconnectPeerRequest) Reset() { *x = DisconnectPeerRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[33] + mi := &file_lightning_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3865,7 +4046,7 @@ func (x *DisconnectPeerRequest) String() string { func (*DisconnectPeerRequest) ProtoMessage() {} func (x *DisconnectPeerRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[33] + mi := &file_lightning_proto_msgTypes[34] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3878,7 +4059,7 @@ func (x *DisconnectPeerRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DisconnectPeerRequest.ProtoReflect.Descriptor instead. func (*DisconnectPeerRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{33} + return file_lightning_proto_rawDescGZIP(), []int{34} } func (x *DisconnectPeerRequest) GetPubKey() string { @@ -3897,7 +4078,7 @@ type DisconnectPeerResponse struct { func (x *DisconnectPeerResponse) Reset() { *x = DisconnectPeerResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[34] + mi := &file_lightning_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3910,7 +4091,7 @@ func (x *DisconnectPeerResponse) String() string { func (*DisconnectPeerResponse) ProtoMessage() {} func (x *DisconnectPeerResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[34] + mi := &file_lightning_proto_msgTypes[35] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3923,7 +4104,7 @@ func (x *DisconnectPeerResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DisconnectPeerResponse.ProtoReflect.Descriptor instead. func (*DisconnectPeerResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{34} + return file_lightning_proto_rawDescGZIP(), []int{35} } type HTLC struct { @@ -3952,7 +4133,7 @@ type HTLC struct { func (x *HTLC) Reset() { *x = HTLC{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[35] + mi := &file_lightning_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3965,7 +4146,7 @@ func (x *HTLC) String() string { func (*HTLC) ProtoMessage() {} func (x *HTLC) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[35] + mi := &file_lightning_proto_msgTypes[36] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3978,7 +4159,7 @@ func (x *HTLC) ProtoReflect() protoreflect.Message { // Deprecated: Use HTLC.ProtoReflect.Descriptor instead. func (*HTLC) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{35} + return file_lightning_proto_rawDescGZIP(), []int{36} } func (x *HTLC) GetIncoming() bool { @@ -4055,7 +4236,7 @@ type ChannelConstraints struct { func (x *ChannelConstraints) Reset() { *x = ChannelConstraints{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[36] + mi := &file_lightning_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4068,7 +4249,7 @@ func (x *ChannelConstraints) String() string { func (*ChannelConstraints) ProtoMessage() {} func (x *ChannelConstraints) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[36] + mi := &file_lightning_proto_msgTypes[37] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4081,7 +4262,7 @@ func (x *ChannelConstraints) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelConstraints.ProtoReflect.Descriptor instead. func (*ChannelConstraints) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{36} + return file_lightning_proto_rawDescGZIP(), []int{37} } func (x *ChannelConstraints) GetCsvDelay() uint32 { @@ -4250,7 +4431,7 @@ type Channel struct { func (x *Channel) Reset() { *x = Channel{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[37] + mi := &file_lightning_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4263,7 +4444,7 @@ func (x *Channel) String() string { func (*Channel) ProtoMessage() {} func (x *Channel) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[37] + mi := &file_lightning_proto_msgTypes[38] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4276,7 +4457,7 @@ func (x *Channel) ProtoReflect() protoreflect.Message { // Deprecated: Use Channel.ProtoReflect.Descriptor instead. func (*Channel) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{37} + return file_lightning_proto_rawDescGZIP(), []int{38} } func (x *Channel) GetActive() bool { @@ -4511,7 +4692,7 @@ type ListChannelsRequest struct { func (x *ListChannelsRequest) Reset() { *x = ListChannelsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[38] + mi := &file_lightning_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4524,7 +4705,7 @@ func (x *ListChannelsRequest) String() string { func (*ListChannelsRequest) ProtoMessage() {} func (x *ListChannelsRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[38] + mi := &file_lightning_proto_msgTypes[39] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4537,7 +4718,7 @@ func (x *ListChannelsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListChannelsRequest.ProtoReflect.Descriptor instead. func (*ListChannelsRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{38} + return file_lightning_proto_rawDescGZIP(), []int{39} } func (x *ListChannelsRequest) GetActiveOnly() bool { @@ -4587,7 +4768,7 @@ type ListChannelsResponse struct { func (x *ListChannelsResponse) Reset() { *x = ListChannelsResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[39] + mi := &file_lightning_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4600,7 +4781,7 @@ func (x *ListChannelsResponse) String() string { func (*ListChannelsResponse) ProtoMessage() {} func (x *ListChannelsResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[39] + mi := &file_lightning_proto_msgTypes[40] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4613,7 +4794,7 @@ func (x *ListChannelsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListChannelsResponse.ProtoReflect.Descriptor instead. func (*ListChannelsResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{39} + return file_lightning_proto_rawDescGZIP(), []int{40} } func (x *ListChannelsResponse) GetChannels() []*Channel { @@ -4666,7 +4847,7 @@ type ChannelCloseSummary struct { func (x *ChannelCloseSummary) Reset() { *x = ChannelCloseSummary{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[40] + mi := &file_lightning_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4679,7 +4860,7 @@ func (x *ChannelCloseSummary) String() string { func (*ChannelCloseSummary) ProtoMessage() {} func (x *ChannelCloseSummary) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[40] + mi := &file_lightning_proto_msgTypes[41] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4692,7 +4873,7 @@ func (x *ChannelCloseSummary) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelCloseSummary.ProtoReflect.Descriptor instead. func (*ChannelCloseSummary) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{40} + return file_lightning_proto_rawDescGZIP(), []int{41} } func (x *ChannelCloseSummary) GetChannelPoint() string { @@ -4807,7 +4988,7 @@ type Resolution struct { func (x *Resolution) Reset() { *x = Resolution{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[41] + mi := &file_lightning_proto_msgTypes[42] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4820,7 +5001,7 @@ func (x *Resolution) String() string { func (*Resolution) ProtoMessage() {} func (x *Resolution) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[41] + mi := &file_lightning_proto_msgTypes[42] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4833,7 +5014,7 @@ func (x *Resolution) ProtoReflect() protoreflect.Message { // Deprecated: Use Resolution.ProtoReflect.Descriptor instead. func (*Resolution) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{41} + return file_lightning_proto_rawDescGZIP(), []int{42} } func (x *Resolution) GetResolutionType() ResolutionType { @@ -4887,7 +5068,7 @@ type ClosedChannelsRequest struct { func (x *ClosedChannelsRequest) Reset() { *x = ClosedChannelsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[42] + mi := &file_lightning_proto_msgTypes[43] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4900,7 +5081,7 @@ func (x *ClosedChannelsRequest) String() string { func (*ClosedChannelsRequest) ProtoMessage() {} func (x *ClosedChannelsRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[42] + mi := &file_lightning_proto_msgTypes[43] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4913,7 +5094,7 @@ func (x *ClosedChannelsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ClosedChannelsRequest.ProtoReflect.Descriptor instead. func (*ClosedChannelsRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{42} + return file_lightning_proto_rawDescGZIP(), []int{43} } func (x *ClosedChannelsRequest) GetCooperative() bool { @@ -4969,7 +5150,7 @@ type ClosedChannelsResponse struct { func (x *ClosedChannelsResponse) Reset() { *x = ClosedChannelsResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[43] + mi := &file_lightning_proto_msgTypes[44] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4982,7 +5163,7 @@ func (x *ClosedChannelsResponse) String() string { func (*ClosedChannelsResponse) ProtoMessage() {} func (x *ClosedChannelsResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[43] + mi := &file_lightning_proto_msgTypes[44] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4995,7 +5176,7 @@ func (x *ClosedChannelsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ClosedChannelsResponse.ProtoReflect.Descriptor instead. func (*ClosedChannelsResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{43} + return file_lightning_proto_rawDescGZIP(), []int{44} } func (x *ClosedChannelsResponse) GetChannels() []*ChannelCloseSummary { @@ -5055,7 +5236,7 @@ type Peer struct { func (x *Peer) Reset() { *x = Peer{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[44] + mi := &file_lightning_proto_msgTypes[45] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5068,7 +5249,7 @@ func (x *Peer) String() string { func (*Peer) ProtoMessage() {} func (x *Peer) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[44] + mi := &file_lightning_proto_msgTypes[45] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5081,7 +5262,7 @@ func (x *Peer) ProtoReflect() protoreflect.Message { // Deprecated: Use Peer.ProtoReflect.Descriptor instead. func (*Peer) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{44} + return file_lightning_proto_rawDescGZIP(), []int{45} } func (x *Peer) GetPubKey() string { @@ -5196,7 +5377,7 @@ type TimestampedError struct { func (x *TimestampedError) Reset() { *x = TimestampedError{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[45] + mi := &file_lightning_proto_msgTypes[46] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5209,7 +5390,7 @@ func (x *TimestampedError) String() string { func (*TimestampedError) ProtoMessage() {} func (x *TimestampedError) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[45] + mi := &file_lightning_proto_msgTypes[46] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5222,7 +5403,7 @@ func (x *TimestampedError) ProtoReflect() protoreflect.Message { // Deprecated: Use TimestampedError.ProtoReflect.Descriptor instead. func (*TimestampedError) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{45} + return file_lightning_proto_rawDescGZIP(), []int{46} } func (x *TimestampedError) GetTimestamp() uint64 { @@ -5254,7 +5435,7 @@ type ListPeersRequest struct { func (x *ListPeersRequest) Reset() { *x = ListPeersRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[46] + mi := &file_lightning_proto_msgTypes[47] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5267,7 +5448,7 @@ func (x *ListPeersRequest) String() string { func (*ListPeersRequest) ProtoMessage() {} func (x *ListPeersRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[46] + mi := &file_lightning_proto_msgTypes[47] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5280,7 +5461,7 @@ func (x *ListPeersRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListPeersRequest.ProtoReflect.Descriptor instead. func (*ListPeersRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{46} + return file_lightning_proto_rawDescGZIP(), []int{47} } func (x *ListPeersRequest) GetLatestError() bool { @@ -5302,7 +5483,7 @@ type ListPeersResponse struct { func (x *ListPeersResponse) Reset() { *x = ListPeersResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[47] + mi := &file_lightning_proto_msgTypes[48] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5315,7 +5496,7 @@ func (x *ListPeersResponse) String() string { func (*ListPeersResponse) ProtoMessage() {} func (x *ListPeersResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[47] + mi := &file_lightning_proto_msgTypes[48] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5328,7 +5509,7 @@ func (x *ListPeersResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListPeersResponse.ProtoReflect.Descriptor instead. func (*ListPeersResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{47} + return file_lightning_proto_rawDescGZIP(), []int{48} } func (x *ListPeersResponse) GetPeers() []*Peer { @@ -5347,7 +5528,7 @@ type PeerEventSubscription struct { func (x *PeerEventSubscription) Reset() { *x = PeerEventSubscription{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[48] + mi := &file_lightning_proto_msgTypes[49] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5360,7 +5541,7 @@ func (x *PeerEventSubscription) String() string { func (*PeerEventSubscription) ProtoMessage() {} func (x *PeerEventSubscription) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[48] + mi := &file_lightning_proto_msgTypes[49] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5373,7 +5554,7 @@ func (x *PeerEventSubscription) ProtoReflect() protoreflect.Message { // Deprecated: Use PeerEventSubscription.ProtoReflect.Descriptor instead. func (*PeerEventSubscription) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{48} + return file_lightning_proto_rawDescGZIP(), []int{49} } type PeerEvent struct { @@ -5389,7 +5570,7 @@ type PeerEvent struct { func (x *PeerEvent) Reset() { *x = PeerEvent{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[49] + mi := &file_lightning_proto_msgTypes[50] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5402,7 +5583,7 @@ func (x *PeerEvent) String() string { func (*PeerEvent) ProtoMessage() {} func (x *PeerEvent) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[49] + mi := &file_lightning_proto_msgTypes[50] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5415,7 +5596,7 @@ func (x *PeerEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use PeerEvent.ProtoReflect.Descriptor instead. func (*PeerEvent) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{49} + return file_lightning_proto_rawDescGZIP(), []int{50} } func (x *PeerEvent) GetPubKey() string { @@ -5441,7 +5622,7 @@ type GetInfoRequest struct { func (x *GetInfoRequest) Reset() { *x = GetInfoRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[50] + mi := &file_lightning_proto_msgTypes[51] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5454,7 +5635,7 @@ func (x *GetInfoRequest) String() string { func (*GetInfoRequest) ProtoMessage() {} func (x *GetInfoRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[50] + mi := &file_lightning_proto_msgTypes[51] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5467,7 +5648,7 @@ func (x *GetInfoRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetInfoRequest.ProtoReflect.Descriptor instead. func (*GetInfoRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{50} + return file_lightning_proto_rawDescGZIP(), []int{51} } type GetInfoResponse struct { @@ -5517,12 +5698,15 @@ type GetInfoResponse struct { //Features that our node has advertised in our init message, node //announcements and invoices. Features map[uint32]*Feature `protobuf:"bytes,19,rep,name=features,proto3" json:"features,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + // + //Indicates whether the HTLC interceptor API is in always-on mode. + RequireHtlcInterceptor bool `protobuf:"varint,21,opt,name=require_htlc_interceptor,json=requireHtlcInterceptor,proto3" json:"require_htlc_interceptor,omitempty"` } func (x *GetInfoResponse) Reset() { *x = GetInfoResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[51] + mi := &file_lightning_proto_msgTypes[52] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5535,7 +5719,7 @@ func (x *GetInfoResponse) String() string { func (*GetInfoResponse) ProtoMessage() {} func (x *GetInfoResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[51] + mi := &file_lightning_proto_msgTypes[52] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5548,7 +5732,7 @@ func (x *GetInfoResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetInfoResponse.ProtoReflect.Descriptor instead. func (*GetInfoResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{51} + return file_lightning_proto_rawDescGZIP(), []int{52} } func (x *GetInfoResponse) GetVersion() string { @@ -5678,6 +5862,13 @@ func (x *GetInfoResponse) GetFeatures() map[uint32]*Feature { return nil } +func (x *GetInfoResponse) GetRequireHtlcInterceptor() bool { + if x != nil { + return x.RequireHtlcInterceptor + } + return false +} + type GetRecoveryInfoRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -5687,7 +5878,7 @@ type GetRecoveryInfoRequest struct { func (x *GetRecoveryInfoRequest) Reset() { *x = GetRecoveryInfoRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[52] + mi := &file_lightning_proto_msgTypes[53] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5700,7 +5891,7 @@ func (x *GetRecoveryInfoRequest) String() string { func (*GetRecoveryInfoRequest) ProtoMessage() {} func (x *GetRecoveryInfoRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[52] + mi := &file_lightning_proto_msgTypes[53] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5713,7 +5904,7 @@ func (x *GetRecoveryInfoRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRecoveryInfoRequest.ProtoReflect.Descriptor instead. func (*GetRecoveryInfoRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{52} + return file_lightning_proto_rawDescGZIP(), []int{53} } type GetRecoveryInfoResponse struct { @@ -5732,7 +5923,7 @@ type GetRecoveryInfoResponse struct { func (x *GetRecoveryInfoResponse) Reset() { *x = GetRecoveryInfoResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[53] + mi := &file_lightning_proto_msgTypes[54] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5745,7 +5936,7 @@ func (x *GetRecoveryInfoResponse) String() string { func (*GetRecoveryInfoResponse) ProtoMessage() {} func (x *GetRecoveryInfoResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[53] + mi := &file_lightning_proto_msgTypes[54] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5758,7 +5949,7 @@ func (x *GetRecoveryInfoResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRecoveryInfoResponse.ProtoReflect.Descriptor instead. func (*GetRecoveryInfoResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{53} + return file_lightning_proto_rawDescGZIP(), []int{54} } func (x *GetRecoveryInfoResponse) GetRecoveryMode() bool { @@ -5796,7 +5987,7 @@ type Chain struct { func (x *Chain) Reset() { *x = Chain{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[54] + mi := &file_lightning_proto_msgTypes[55] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5809,7 +6000,7 @@ func (x *Chain) String() string { func (*Chain) ProtoMessage() {} func (x *Chain) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[54] + mi := &file_lightning_proto_msgTypes[55] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5822,7 +6013,7 @@ func (x *Chain) ProtoReflect() protoreflect.Message { // Deprecated: Use Chain.ProtoReflect.Descriptor instead. func (*Chain) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{54} + return file_lightning_proto_rawDescGZIP(), []int{55} } func (x *Chain) GetChain() string { @@ -5852,7 +6043,7 @@ type ConfirmationUpdate struct { func (x *ConfirmationUpdate) Reset() { *x = ConfirmationUpdate{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[55] + mi := &file_lightning_proto_msgTypes[56] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5865,7 +6056,7 @@ func (x *ConfirmationUpdate) String() string { func (*ConfirmationUpdate) ProtoMessage() {} func (x *ConfirmationUpdate) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[55] + mi := &file_lightning_proto_msgTypes[56] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5878,7 +6069,7 @@ func (x *ConfirmationUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use ConfirmationUpdate.ProtoReflect.Descriptor instead. func (*ConfirmationUpdate) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{55} + return file_lightning_proto_rawDescGZIP(), []int{56} } func (x *ConfirmationUpdate) GetBlockSha() []byte { @@ -5913,7 +6104,7 @@ type ChannelOpenUpdate struct { func (x *ChannelOpenUpdate) Reset() { *x = ChannelOpenUpdate{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[56] + mi := &file_lightning_proto_msgTypes[57] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5926,7 +6117,7 @@ func (x *ChannelOpenUpdate) String() string { func (*ChannelOpenUpdate) ProtoMessage() {} func (x *ChannelOpenUpdate) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[56] + mi := &file_lightning_proto_msgTypes[57] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5939,7 +6130,7 @@ func (x *ChannelOpenUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelOpenUpdate.ProtoReflect.Descriptor instead. func (*ChannelOpenUpdate) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{56} + return file_lightning_proto_rawDescGZIP(), []int{57} } func (x *ChannelOpenUpdate) GetChannelPoint() *ChannelPoint { @@ -5961,7 +6152,7 @@ type ChannelCloseUpdate struct { func (x *ChannelCloseUpdate) Reset() { *x = ChannelCloseUpdate{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[57] + mi := &file_lightning_proto_msgTypes[58] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5974,7 +6165,7 @@ func (x *ChannelCloseUpdate) String() string { func (*ChannelCloseUpdate) ProtoMessage() {} func (x *ChannelCloseUpdate) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[57] + mi := &file_lightning_proto_msgTypes[58] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5987,7 +6178,7 @@ func (x *ChannelCloseUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelCloseUpdate.ProtoReflect.Descriptor instead. func (*ChannelCloseUpdate) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{57} + return file_lightning_proto_rawDescGZIP(), []int{58} } func (x *ChannelCloseUpdate) GetClosingTxid() []byte { @@ -6040,7 +6231,7 @@ type CloseChannelRequest struct { func (x *CloseChannelRequest) Reset() { *x = CloseChannelRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[58] + mi := &file_lightning_proto_msgTypes[59] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6053,7 +6244,7 @@ func (x *CloseChannelRequest) String() string { func (*CloseChannelRequest) ProtoMessage() {} func (x *CloseChannelRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[58] + mi := &file_lightning_proto_msgTypes[59] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6066,7 +6257,7 @@ func (x *CloseChannelRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CloseChannelRequest.ProtoReflect.Descriptor instead. func (*CloseChannelRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{58} + return file_lightning_proto_rawDescGZIP(), []int{59} } func (x *CloseChannelRequest) GetChannelPoint() *ChannelPoint { @@ -6126,7 +6317,7 @@ type CloseStatusUpdate struct { func (x *CloseStatusUpdate) Reset() { *x = CloseStatusUpdate{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[59] + mi := &file_lightning_proto_msgTypes[60] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6139,7 +6330,7 @@ func (x *CloseStatusUpdate) String() string { func (*CloseStatusUpdate) ProtoMessage() {} func (x *CloseStatusUpdate) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[59] + mi := &file_lightning_proto_msgTypes[60] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6152,7 +6343,7 @@ func (x *CloseStatusUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use CloseStatusUpdate.ProtoReflect.Descriptor instead. func (*CloseStatusUpdate) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{59} + return file_lightning_proto_rawDescGZIP(), []int{60} } func (m *CloseStatusUpdate) GetUpdate() isCloseStatusUpdate_Update { @@ -6204,7 +6395,7 @@ type PendingUpdate struct { func (x *PendingUpdate) Reset() { *x = PendingUpdate{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[60] + mi := &file_lightning_proto_msgTypes[61] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6217,7 +6408,7 @@ func (x *PendingUpdate) String() string { func (*PendingUpdate) ProtoMessage() {} func (x *PendingUpdate) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[60] + mi := &file_lightning_proto_msgTypes[61] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6230,7 +6421,7 @@ func (x *PendingUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use PendingUpdate.ProtoReflect.Descriptor instead. func (*PendingUpdate) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{60} + return file_lightning_proto_rawDescGZIP(), []int{61} } func (x *PendingUpdate) GetTxid() []byte { @@ -6271,7 +6462,7 @@ type ReadyForPsbtFunding struct { func (x *ReadyForPsbtFunding) Reset() { *x = ReadyForPsbtFunding{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[61] + mi := &file_lightning_proto_msgTypes[62] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6284,7 +6475,7 @@ func (x *ReadyForPsbtFunding) String() string { func (*ReadyForPsbtFunding) ProtoMessage() {} func (x *ReadyForPsbtFunding) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[61] + mi := &file_lightning_proto_msgTypes[62] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6297,7 +6488,7 @@ func (x *ReadyForPsbtFunding) ProtoReflect() protoreflect.Message { // Deprecated: Use ReadyForPsbtFunding.ProtoReflect.Descriptor instead. func (*ReadyForPsbtFunding) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{61} + return file_lightning_proto_rawDescGZIP(), []int{62} } func (x *ReadyForPsbtFunding) GetFundingAddress() string { @@ -6347,7 +6538,7 @@ type BatchOpenChannelRequest struct { func (x *BatchOpenChannelRequest) Reset() { *x = BatchOpenChannelRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[62] + mi := &file_lightning_proto_msgTypes[63] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6360,7 +6551,7 @@ func (x *BatchOpenChannelRequest) String() string { func (*BatchOpenChannelRequest) ProtoMessage() {} func (x *BatchOpenChannelRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[62] + mi := &file_lightning_proto_msgTypes[63] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6373,7 +6564,7 @@ func (x *BatchOpenChannelRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use BatchOpenChannelRequest.ProtoReflect.Descriptor instead. func (*BatchOpenChannelRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{62} + return file_lightning_proto_rawDescGZIP(), []int{63} } func (x *BatchOpenChannelRequest) GetChannels() []*BatchOpenChannel { @@ -6464,7 +6655,7 @@ type BatchOpenChannel struct { func (x *BatchOpenChannel) Reset() { *x = BatchOpenChannel{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[63] + mi := &file_lightning_proto_msgTypes[64] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6477,7 +6668,7 @@ func (x *BatchOpenChannel) String() string { func (*BatchOpenChannel) ProtoMessage() {} func (x *BatchOpenChannel) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[63] + mi := &file_lightning_proto_msgTypes[64] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6490,7 +6681,7 @@ func (x *BatchOpenChannel) ProtoReflect() protoreflect.Message { // Deprecated: Use BatchOpenChannel.ProtoReflect.Descriptor instead. func (*BatchOpenChannel) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{63} + return file_lightning_proto_rawDescGZIP(), []int{64} } func (x *BatchOpenChannel) GetNodePubkey() []byte { @@ -6567,7 +6758,7 @@ type BatchOpenChannelResponse struct { func (x *BatchOpenChannelResponse) Reset() { *x = BatchOpenChannelResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[64] + mi := &file_lightning_proto_msgTypes[65] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6580,7 +6771,7 @@ func (x *BatchOpenChannelResponse) String() string { func (*BatchOpenChannelResponse) ProtoMessage() {} func (x *BatchOpenChannelResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[64] + mi := &file_lightning_proto_msgTypes[65] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6593,7 +6784,7 @@ func (x *BatchOpenChannelResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use BatchOpenChannelResponse.ProtoReflect.Descriptor instead. func (*BatchOpenChannelResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{64} + return file_lightning_proto_rawDescGZIP(), []int{65} } func (x *BatchOpenChannelResponse) GetPendingChannels() []*PendingUpdate { @@ -6688,7 +6879,7 @@ type OpenChannelRequest struct { func (x *OpenChannelRequest) Reset() { *x = OpenChannelRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[65] + mi := &file_lightning_proto_msgTypes[66] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6701,7 +6892,7 @@ func (x *OpenChannelRequest) String() string { func (*OpenChannelRequest) ProtoMessage() {} func (x *OpenChannelRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[65] + mi := &file_lightning_proto_msgTypes[66] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6714,7 +6905,7 @@ func (x *OpenChannelRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use OpenChannelRequest.ProtoReflect.Descriptor instead. func (*OpenChannelRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{65} + return file_lightning_proto_rawDescGZIP(), []int{66} } func (x *OpenChannelRequest) GetSatPerVbyte() uint64 { @@ -6864,7 +7055,7 @@ type OpenStatusUpdate struct { func (x *OpenStatusUpdate) Reset() { *x = OpenStatusUpdate{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[66] + mi := &file_lightning_proto_msgTypes[67] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6877,7 +7068,7 @@ func (x *OpenStatusUpdate) String() string { func (*OpenStatusUpdate) ProtoMessage() {} func (x *OpenStatusUpdate) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[66] + mi := &file_lightning_proto_msgTypes[67] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6890,7 +7081,7 @@ func (x *OpenStatusUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use OpenStatusUpdate.ProtoReflect.Descriptor instead. func (*OpenStatusUpdate) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{66} + return file_lightning_proto_rawDescGZIP(), []int{67} } func (m *OpenStatusUpdate) GetUpdate() isOpenStatusUpdate_Update { @@ -6973,7 +7164,7 @@ type KeyLocator struct { func (x *KeyLocator) Reset() { *x = KeyLocator{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[67] + mi := &file_lightning_proto_msgTypes[68] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6986,7 +7177,7 @@ func (x *KeyLocator) String() string { func (*KeyLocator) ProtoMessage() {} func (x *KeyLocator) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[67] + mi := &file_lightning_proto_msgTypes[68] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6999,7 +7190,7 @@ func (x *KeyLocator) ProtoReflect() protoreflect.Message { // Deprecated: Use KeyLocator.ProtoReflect.Descriptor instead. func (*KeyLocator) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{67} + return file_lightning_proto_rawDescGZIP(), []int{68} } func (x *KeyLocator) GetKeyFamily() int32 { @@ -7032,7 +7223,7 @@ type KeyDescriptor struct { func (x *KeyDescriptor) Reset() { *x = KeyDescriptor{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[68] + mi := &file_lightning_proto_msgTypes[69] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7045,7 +7236,7 @@ func (x *KeyDescriptor) String() string { func (*KeyDescriptor) ProtoMessage() {} func (x *KeyDescriptor) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[68] + mi := &file_lightning_proto_msgTypes[69] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7058,7 +7249,7 @@ func (x *KeyDescriptor) ProtoReflect() protoreflect.Message { // Deprecated: Use KeyDescriptor.ProtoReflect.Descriptor instead. func (*KeyDescriptor) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{68} + return file_lightning_proto_rawDescGZIP(), []int{69} } func (x *KeyDescriptor) GetRawKeyBytes() []byte { @@ -7108,7 +7299,7 @@ type ChanPointShim struct { func (x *ChanPointShim) Reset() { *x = ChanPointShim{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[69] + mi := &file_lightning_proto_msgTypes[70] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7121,7 +7312,7 @@ func (x *ChanPointShim) String() string { func (*ChanPointShim) ProtoMessage() {} func (x *ChanPointShim) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[69] + mi := &file_lightning_proto_msgTypes[70] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7134,7 +7325,7 @@ func (x *ChanPointShim) ProtoReflect() protoreflect.Message { // Deprecated: Use ChanPointShim.ProtoReflect.Descriptor instead. func (*ChanPointShim) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{69} + return file_lightning_proto_rawDescGZIP(), []int{70} } func (x *ChanPointShim) GetAmt() int64 { @@ -7206,7 +7397,7 @@ type PsbtShim struct { func (x *PsbtShim) Reset() { *x = PsbtShim{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[70] + mi := &file_lightning_proto_msgTypes[71] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7219,7 +7410,7 @@ func (x *PsbtShim) String() string { func (*PsbtShim) ProtoMessage() {} func (x *PsbtShim) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[70] + mi := &file_lightning_proto_msgTypes[71] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7232,7 +7423,7 @@ func (x *PsbtShim) ProtoReflect() protoreflect.Message { // Deprecated: Use PsbtShim.ProtoReflect.Descriptor instead. func (*PsbtShim) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{70} + return file_lightning_proto_rawDescGZIP(), []int{71} } func (x *PsbtShim) GetPendingChanId() []byte { @@ -7270,7 +7461,7 @@ type FundingShim struct { func (x *FundingShim) Reset() { *x = FundingShim{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[71] + mi := &file_lightning_proto_msgTypes[72] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7283,7 +7474,7 @@ func (x *FundingShim) String() string { func (*FundingShim) ProtoMessage() {} func (x *FundingShim) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[71] + mi := &file_lightning_proto_msgTypes[72] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7296,7 +7487,7 @@ func (x *FundingShim) ProtoReflect() protoreflect.Message { // Deprecated: Use FundingShim.ProtoReflect.Descriptor instead. func (*FundingShim) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{71} + return file_lightning_proto_rawDescGZIP(), []int{72} } func (m *FundingShim) GetShim() isFundingShim_Shim { @@ -7354,7 +7545,7 @@ type FundingShimCancel struct { func (x *FundingShimCancel) Reset() { *x = FundingShimCancel{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[72] + mi := &file_lightning_proto_msgTypes[73] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7367,7 +7558,7 @@ func (x *FundingShimCancel) String() string { func (*FundingShimCancel) ProtoMessage() {} func (x *FundingShimCancel) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[72] + mi := &file_lightning_proto_msgTypes[73] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7380,7 +7571,7 @@ func (x *FundingShimCancel) ProtoReflect() protoreflect.Message { // Deprecated: Use FundingShimCancel.ProtoReflect.Descriptor instead. func (*FundingShimCancel) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{72} + return file_lightning_proto_rawDescGZIP(), []int{73} } func (x *FundingShimCancel) GetPendingChanId() []byte { @@ -7419,7 +7610,7 @@ type FundingPsbtVerify struct { func (x *FundingPsbtVerify) Reset() { *x = FundingPsbtVerify{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[73] + mi := &file_lightning_proto_msgTypes[74] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7432,7 +7623,7 @@ func (x *FundingPsbtVerify) String() string { func (*FundingPsbtVerify) ProtoMessage() {} func (x *FundingPsbtVerify) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[73] + mi := &file_lightning_proto_msgTypes[74] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7445,7 +7636,7 @@ func (x *FundingPsbtVerify) ProtoReflect() protoreflect.Message { // Deprecated: Use FundingPsbtVerify.ProtoReflect.Descriptor instead. func (*FundingPsbtVerify) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{73} + return file_lightning_proto_rawDescGZIP(), []int{74} } func (x *FundingPsbtVerify) GetFundedPsbt() []byte { @@ -7491,7 +7682,7 @@ type FundingPsbtFinalize struct { func (x *FundingPsbtFinalize) Reset() { *x = FundingPsbtFinalize{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[74] + mi := &file_lightning_proto_msgTypes[75] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7504,7 +7695,7 @@ func (x *FundingPsbtFinalize) String() string { func (*FundingPsbtFinalize) ProtoMessage() {} func (x *FundingPsbtFinalize) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[74] + mi := &file_lightning_proto_msgTypes[75] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7517,7 +7708,7 @@ func (x *FundingPsbtFinalize) ProtoReflect() protoreflect.Message { // Deprecated: Use FundingPsbtFinalize.ProtoReflect.Descriptor instead. func (*FundingPsbtFinalize) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{74} + return file_lightning_proto_rawDescGZIP(), []int{75} } func (x *FundingPsbtFinalize) GetSignedPsbt() []byte { @@ -7557,7 +7748,7 @@ type FundingTransitionMsg struct { func (x *FundingTransitionMsg) Reset() { *x = FundingTransitionMsg{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[75] + mi := &file_lightning_proto_msgTypes[76] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7570,7 +7761,7 @@ func (x *FundingTransitionMsg) String() string { func (*FundingTransitionMsg) ProtoMessage() {} func (x *FundingTransitionMsg) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[75] + mi := &file_lightning_proto_msgTypes[76] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7583,7 +7774,7 @@ func (x *FundingTransitionMsg) ProtoReflect() protoreflect.Message { // Deprecated: Use FundingTransitionMsg.ProtoReflect.Descriptor instead. func (*FundingTransitionMsg) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{75} + return file_lightning_proto_rawDescGZIP(), []int{76} } func (m *FundingTransitionMsg) GetTrigger() isFundingTransitionMsg_Trigger { @@ -7672,7 +7863,7 @@ type FundingStateStepResp struct { func (x *FundingStateStepResp) Reset() { *x = FundingStateStepResp{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[76] + mi := &file_lightning_proto_msgTypes[77] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7685,7 +7876,7 @@ func (x *FundingStateStepResp) String() string { func (*FundingStateStepResp) ProtoMessage() {} func (x *FundingStateStepResp) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[76] + mi := &file_lightning_proto_msgTypes[77] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7698,7 +7889,7 @@ func (x *FundingStateStepResp) ProtoReflect() protoreflect.Message { // Deprecated: Use FundingStateStepResp.ProtoReflect.Descriptor instead. func (*FundingStateStepResp) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{76} + return file_lightning_proto_rawDescGZIP(), []int{77} } type PendingHTLC struct { @@ -7726,7 +7917,7 @@ type PendingHTLC struct { func (x *PendingHTLC) Reset() { *x = PendingHTLC{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[77] + mi := &file_lightning_proto_msgTypes[78] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7739,7 +7930,7 @@ func (x *PendingHTLC) String() string { func (*PendingHTLC) ProtoMessage() {} func (x *PendingHTLC) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[77] + mi := &file_lightning_proto_msgTypes[78] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7752,7 +7943,7 @@ func (x *PendingHTLC) ProtoReflect() protoreflect.Message { // Deprecated: Use PendingHTLC.ProtoReflect.Descriptor instead. func (*PendingHTLC) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{77} + return file_lightning_proto_rawDescGZIP(), []int{78} } func (x *PendingHTLC) GetIncoming() bool { @@ -7806,7 +7997,7 @@ type PendingChannelsRequest struct { func (x *PendingChannelsRequest) Reset() { *x = PendingChannelsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[78] + mi := &file_lightning_proto_msgTypes[79] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7819,7 +8010,7 @@ func (x *PendingChannelsRequest) String() string { func (*PendingChannelsRequest) ProtoMessage() {} func (x *PendingChannelsRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[78] + mi := &file_lightning_proto_msgTypes[79] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7832,7 +8023,7 @@ func (x *PendingChannelsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PendingChannelsRequest.ProtoReflect.Descriptor instead. func (*PendingChannelsRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{78} + return file_lightning_proto_rawDescGZIP(), []int{79} } type PendingChannelsResponse struct { @@ -7860,7 +8051,7 @@ type PendingChannelsResponse struct { func (x *PendingChannelsResponse) Reset() { *x = PendingChannelsResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[79] + mi := &file_lightning_proto_msgTypes[80] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7873,7 +8064,7 @@ func (x *PendingChannelsResponse) String() string { func (*PendingChannelsResponse) ProtoMessage() {} func (x *PendingChannelsResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[79] + mi := &file_lightning_proto_msgTypes[80] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7886,7 +8077,7 @@ func (x *PendingChannelsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PendingChannelsResponse.ProtoReflect.Descriptor instead. func (*PendingChannelsResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{79} + return file_lightning_proto_rawDescGZIP(), []int{80} } func (x *PendingChannelsResponse) GetTotalLimboBalance() int64 { @@ -7934,7 +8125,7 @@ type ChannelEventSubscription struct { func (x *ChannelEventSubscription) Reset() { *x = ChannelEventSubscription{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[80] + mi := &file_lightning_proto_msgTypes[81] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7947,7 +8138,7 @@ func (x *ChannelEventSubscription) String() string { func (*ChannelEventSubscription) ProtoMessage() {} func (x *ChannelEventSubscription) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[80] + mi := &file_lightning_proto_msgTypes[81] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7960,7 +8151,7 @@ func (x *ChannelEventSubscription) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelEventSubscription.ProtoReflect.Descriptor instead. func (*ChannelEventSubscription) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{80} + return file_lightning_proto_rawDescGZIP(), []int{81} } type ChannelEventUpdate struct { @@ -7982,7 +8173,7 @@ type ChannelEventUpdate struct { func (x *ChannelEventUpdate) Reset() { *x = ChannelEventUpdate{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[81] + mi := &file_lightning_proto_msgTypes[82] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7995,7 +8186,7 @@ func (x *ChannelEventUpdate) String() string { func (*ChannelEventUpdate) ProtoMessage() {} func (x *ChannelEventUpdate) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[81] + mi := &file_lightning_proto_msgTypes[82] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8008,7 +8199,7 @@ func (x *ChannelEventUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelEventUpdate.ProtoReflect.Descriptor instead. func (*ChannelEventUpdate) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{81} + return file_lightning_proto_rawDescGZIP(), []int{82} } func (m *ChannelEventUpdate) GetChannel() isChannelEventUpdate_Channel { @@ -8121,7 +8312,7 @@ type WalletAccountBalance struct { func (x *WalletAccountBalance) Reset() { *x = WalletAccountBalance{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[82] + mi := &file_lightning_proto_msgTypes[83] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8134,7 +8325,7 @@ func (x *WalletAccountBalance) String() string { func (*WalletAccountBalance) ProtoMessage() {} func (x *WalletAccountBalance) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[82] + mi := &file_lightning_proto_msgTypes[83] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8147,7 +8338,7 @@ func (x *WalletAccountBalance) ProtoReflect() protoreflect.Message { // Deprecated: Use WalletAccountBalance.ProtoReflect.Descriptor instead. func (*WalletAccountBalance) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{82} + return file_lightning_proto_rawDescGZIP(), []int{83} } func (x *WalletAccountBalance) GetConfirmedBalance() int64 { @@ -8173,7 +8364,7 @@ type WalletBalanceRequest struct { func (x *WalletBalanceRequest) Reset() { *x = WalletBalanceRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[83] + mi := &file_lightning_proto_msgTypes[84] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8186,7 +8377,7 @@ func (x *WalletBalanceRequest) String() string { func (*WalletBalanceRequest) ProtoMessage() {} func (x *WalletBalanceRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[83] + mi := &file_lightning_proto_msgTypes[84] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8199,7 +8390,7 @@ func (x *WalletBalanceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use WalletBalanceRequest.ProtoReflect.Descriptor instead. func (*WalletBalanceRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{83} + return file_lightning_proto_rawDescGZIP(), []int{84} } type WalletBalanceResponse struct { @@ -8213,6 +8404,9 @@ type WalletBalanceResponse struct { ConfirmedBalance int64 `protobuf:"varint,2,opt,name=confirmed_balance,json=confirmedBalance,proto3" json:"confirmed_balance,omitempty"` // The unconfirmed balance of a wallet(with 0 confirmations) UnconfirmedBalance int64 `protobuf:"varint,3,opt,name=unconfirmed_balance,json=unconfirmedBalance,proto3" json:"unconfirmed_balance,omitempty"` + // The total amount of wallet UTXOs held in outputs that are locked for + // other usage. + LockedBalance int64 `protobuf:"varint,5,opt,name=locked_balance,json=lockedBalance,proto3" json:"locked_balance,omitempty"` // A mapping of each wallet account's name to its balance. AccountBalance map[string]*WalletAccountBalance `protobuf:"bytes,4,rep,name=account_balance,json=accountBalance,proto3" json:"account_balance,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } @@ -8220,7 +8414,7 @@ type WalletBalanceResponse struct { func (x *WalletBalanceResponse) Reset() { *x = WalletBalanceResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[84] + mi := &file_lightning_proto_msgTypes[85] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8233,7 +8427,7 @@ func (x *WalletBalanceResponse) String() string { func (*WalletBalanceResponse) ProtoMessage() {} func (x *WalletBalanceResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[84] + mi := &file_lightning_proto_msgTypes[85] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8246,7 +8440,7 @@ func (x *WalletBalanceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use WalletBalanceResponse.ProtoReflect.Descriptor instead. func (*WalletBalanceResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{84} + return file_lightning_proto_rawDescGZIP(), []int{85} } func (x *WalletBalanceResponse) GetTotalBalance() int64 { @@ -8270,6 +8464,13 @@ func (x *WalletBalanceResponse) GetUnconfirmedBalance() int64 { return 0 } +func (x *WalletBalanceResponse) GetLockedBalance() int64 { + if x != nil { + return x.LockedBalance + } + return 0 +} + func (x *WalletBalanceResponse) GetAccountBalance() map[string]*WalletAccountBalance { if x != nil { return x.AccountBalance @@ -8291,7 +8492,7 @@ type Amount struct { func (x *Amount) Reset() { *x = Amount{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[85] + mi := &file_lightning_proto_msgTypes[86] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8304,7 +8505,7 @@ func (x *Amount) String() string { func (*Amount) ProtoMessage() {} func (x *Amount) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[85] + mi := &file_lightning_proto_msgTypes[86] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8317,7 +8518,7 @@ func (x *Amount) ProtoReflect() protoreflect.Message { // Deprecated: Use Amount.ProtoReflect.Descriptor instead. func (*Amount) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{85} + return file_lightning_proto_rawDescGZIP(), []int{86} } func (x *Amount) GetSat() uint64 { @@ -8343,7 +8544,7 @@ type ChannelBalanceRequest struct { func (x *ChannelBalanceRequest) Reset() { *x = ChannelBalanceRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[86] + mi := &file_lightning_proto_msgTypes[87] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8356,7 +8557,7 @@ func (x *ChannelBalanceRequest) String() string { func (*ChannelBalanceRequest) ProtoMessage() {} func (x *ChannelBalanceRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[86] + mi := &file_lightning_proto_msgTypes[87] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8369,7 +8570,7 @@ func (x *ChannelBalanceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelBalanceRequest.ProtoReflect.Descriptor instead. func (*ChannelBalanceRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{86} + return file_lightning_proto_rawDescGZIP(), []int{87} } type ChannelBalanceResponse struct { @@ -8402,7 +8603,7 @@ type ChannelBalanceResponse struct { func (x *ChannelBalanceResponse) Reset() { *x = ChannelBalanceResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[87] + mi := &file_lightning_proto_msgTypes[88] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8415,7 +8616,7 @@ func (x *ChannelBalanceResponse) String() string { func (*ChannelBalanceResponse) ProtoMessage() {} func (x *ChannelBalanceResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[87] + mi := &file_lightning_proto_msgTypes[88] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8428,7 +8629,7 @@ func (x *ChannelBalanceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelBalanceResponse.ProtoReflect.Descriptor instead. func (*ChannelBalanceResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{87} + return file_lightning_proto_rawDescGZIP(), []int{88} } // Deprecated: Do not use. @@ -8517,7 +8718,8 @@ type QueryRoutesRequest struct { //The maximum number of satoshis that will be paid as a fee of the payment. //This value can be represented either as a percentage of the amount being //sent, or as a fixed amount of the maximum fee the user is willing the pay to - //send the payment. + //send the payment. If not specified, lnd will use a default value of 100% + //fees for small amounts (<=1k sat) or 5% fees for larger amounts. FeeLimit *FeeLimit `protobuf:"bytes,5,opt,name=fee_limit,json=feeLimit,proto3" json:"fee_limit,omitempty"` // //A list of nodes to ignore during path finding. When using REST, these fields @@ -8569,12 +8771,16 @@ type QueryRoutesRequest struct { //the router will try to load destination features from the graph as a //fallback. DestFeatures []FeatureBit `protobuf:"varint,17,rep,packed,name=dest_features,json=destFeatures,proto3,enum=lnrpc.FeatureBit" json:"dest_features,omitempty"` + // + //The time preference for this payment. Set to -1 to optimize for fees + //only, to 1 to optimize for reliability only or a value inbetween for a mix. + TimePref float64 `protobuf:"fixed64,18,opt,name=time_pref,json=timePref,proto3" json:"time_pref,omitempty"` } func (x *QueryRoutesRequest) Reset() { *x = QueryRoutesRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[88] + mi := &file_lightning_proto_msgTypes[89] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8587,7 +8793,7 @@ func (x *QueryRoutesRequest) String() string { func (*QueryRoutesRequest) ProtoMessage() {} func (x *QueryRoutesRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[88] + mi := &file_lightning_proto_msgTypes[89] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8600,7 +8806,7 @@ func (x *QueryRoutesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use QueryRoutesRequest.ProtoReflect.Descriptor instead. func (*QueryRoutesRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{88} + return file_lightning_proto_rawDescGZIP(), []int{89} } func (x *QueryRoutesRequest) GetPubKey() string { @@ -8716,6 +8922,13 @@ func (x *QueryRoutesRequest) GetDestFeatures() []FeatureBit { return nil } +func (x *QueryRoutesRequest) GetTimePref() float64 { + if x != nil { + return x.TimePref + } + return 0 +} + type NodePair struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -8734,7 +8947,7 @@ type NodePair struct { func (x *NodePair) Reset() { *x = NodePair{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[89] + mi := &file_lightning_proto_msgTypes[90] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8747,7 +8960,7 @@ func (x *NodePair) String() string { func (*NodePair) ProtoMessage() {} func (x *NodePair) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[89] + mi := &file_lightning_proto_msgTypes[90] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8760,7 +8973,7 @@ func (x *NodePair) ProtoReflect() protoreflect.Message { // Deprecated: Use NodePair.ProtoReflect.Descriptor instead. func (*NodePair) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{89} + return file_lightning_proto_rawDescGZIP(), []int{90} } func (x *NodePair) GetFrom() []byte { @@ -8795,7 +9008,7 @@ type EdgeLocator struct { func (x *EdgeLocator) Reset() { *x = EdgeLocator{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[90] + mi := &file_lightning_proto_msgTypes[91] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8808,7 +9021,7 @@ func (x *EdgeLocator) String() string { func (*EdgeLocator) ProtoMessage() {} func (x *EdgeLocator) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[90] + mi := &file_lightning_proto_msgTypes[91] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8821,7 +9034,7 @@ func (x *EdgeLocator) ProtoReflect() protoreflect.Message { // Deprecated: Use EdgeLocator.ProtoReflect.Descriptor instead. func (*EdgeLocator) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{90} + return file_lightning_proto_rawDescGZIP(), []int{91} } func (x *EdgeLocator) GetChannelId() uint64 { @@ -8856,7 +9069,7 @@ type QueryRoutesResponse struct { func (x *QueryRoutesResponse) Reset() { *x = QueryRoutesResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[91] + mi := &file_lightning_proto_msgTypes[92] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8869,7 +9082,7 @@ func (x *QueryRoutesResponse) String() string { func (*QueryRoutesResponse) ProtoMessage() {} func (x *QueryRoutesResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[91] + mi := &file_lightning_proto_msgTypes[92] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8882,7 +9095,7 @@ func (x *QueryRoutesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use QueryRoutesResponse.ProtoReflect.Descriptor instead. func (*QueryRoutesResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{91} + return file_lightning_proto_rawDescGZIP(), []int{92} } func (x *QueryRoutesResponse) GetRoutes() []*Route { @@ -8926,6 +9139,8 @@ type Hop struct { //If set to true, then this hop will be encoded using the new variable length //TLV format. Note that if any custom tlv_records below are specified, then //this field MUST be set to true for them to be encoded properly. + // + // Deprecated: Do not use. TlvPayload bool `protobuf:"varint,9,opt,name=tlv_payload,json=tlvPayload,proto3" json:"tlv_payload,omitempty"` // //An optional TLV record that signals the use of an MPP payment. If present, @@ -8946,12 +9161,14 @@ type Hop struct { //of the SendToRoute call as it allows callers to specify arbitrary K-V pairs //to drop off at each hop within the onion. CustomRecords map[uint64][]byte `protobuf:"bytes,11,rep,name=custom_records,json=customRecords,proto3" json:"custom_records,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + // The payment metadata to send along with the payment to the payee. + Metadata []byte `protobuf:"bytes,13,opt,name=metadata,proto3" json:"metadata,omitempty"` } func (x *Hop) Reset() { *x = Hop{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[92] + mi := &file_lightning_proto_msgTypes[93] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8964,7 +9181,7 @@ func (x *Hop) String() string { func (*Hop) ProtoMessage() {} func (x *Hop) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[92] + mi := &file_lightning_proto_msgTypes[93] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8977,7 +9194,7 @@ func (x *Hop) ProtoReflect() protoreflect.Message { // Deprecated: Use Hop.ProtoReflect.Descriptor instead. func (*Hop) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{92} + return file_lightning_proto_rawDescGZIP(), []int{93} } func (x *Hop) GetChanId() uint64 { @@ -9039,6 +9256,7 @@ func (x *Hop) GetPubKey() string { return "" } +// Deprecated: Do not use. func (x *Hop) GetTlvPayload() bool { if x != nil { return x.TlvPayload @@ -9067,6 +9285,13 @@ func (x *Hop) GetCustomRecords() map[uint64][]byte { return nil } +func (x *Hop) GetMetadata() []byte { + if x != nil { + return x.Metadata + } + return nil +} + type MPPRecord struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -9089,7 +9314,7 @@ type MPPRecord struct { func (x *MPPRecord) Reset() { *x = MPPRecord{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[93] + mi := &file_lightning_proto_msgTypes[94] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9102,7 +9327,7 @@ func (x *MPPRecord) String() string { func (*MPPRecord) ProtoMessage() {} func (x *MPPRecord) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[93] + mi := &file_lightning_proto_msgTypes[94] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9115,7 +9340,7 @@ func (x *MPPRecord) ProtoReflect() protoreflect.Message { // Deprecated: Use MPPRecord.ProtoReflect.Descriptor instead. func (*MPPRecord) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{93} + return file_lightning_proto_rawDescGZIP(), []int{94} } func (x *MPPRecord) GetPaymentAddr() []byte { @@ -9145,7 +9370,7 @@ type AMPRecord struct { func (x *AMPRecord) Reset() { *x = AMPRecord{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[94] + mi := &file_lightning_proto_msgTypes[95] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9158,7 +9383,7 @@ func (x *AMPRecord) String() string { func (*AMPRecord) ProtoMessage() {} func (x *AMPRecord) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[94] + mi := &file_lightning_proto_msgTypes[95] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9171,7 +9396,7 @@ func (x *AMPRecord) ProtoReflect() protoreflect.Message { // Deprecated: Use AMPRecord.ProtoReflect.Descriptor instead. func (*AMPRecord) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{94} + return file_lightning_proto_rawDescGZIP(), []int{95} } func (x *AMPRecord) GetRootShare() []byte { @@ -9242,7 +9467,7 @@ type Route struct { func (x *Route) Reset() { *x = Route{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[95] + mi := &file_lightning_proto_msgTypes[96] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9255,7 +9480,7 @@ func (x *Route) String() string { func (*Route) ProtoMessage() {} func (x *Route) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[95] + mi := &file_lightning_proto_msgTypes[96] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9268,7 +9493,7 @@ func (x *Route) ProtoReflect() protoreflect.Message { // Deprecated: Use Route.ProtoReflect.Descriptor instead. func (*Route) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{95} + return file_lightning_proto_rawDescGZIP(), []int{96} } func (x *Route) GetTotalTimeLock() uint32 { @@ -9329,7 +9554,7 @@ type NodeInfoRequest struct { func (x *NodeInfoRequest) Reset() { *x = NodeInfoRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[96] + mi := &file_lightning_proto_msgTypes[97] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9342,7 +9567,7 @@ func (x *NodeInfoRequest) String() string { func (*NodeInfoRequest) ProtoMessage() {} func (x *NodeInfoRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[96] + mi := &file_lightning_proto_msgTypes[97] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9355,7 +9580,7 @@ func (x *NodeInfoRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use NodeInfoRequest.ProtoReflect.Descriptor instead. func (*NodeInfoRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{96} + return file_lightning_proto_rawDescGZIP(), []int{97} } func (x *NodeInfoRequest) GetPubKey() string { @@ -9394,7 +9619,7 @@ type NodeInfo struct { func (x *NodeInfo) Reset() { *x = NodeInfo{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[97] + mi := &file_lightning_proto_msgTypes[98] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9407,7 +9632,7 @@ func (x *NodeInfo) String() string { func (*NodeInfo) ProtoMessage() {} func (x *NodeInfo) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[97] + mi := &file_lightning_proto_msgTypes[98] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9420,7 +9645,7 @@ func (x *NodeInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use NodeInfo.ProtoReflect.Descriptor instead. func (*NodeInfo) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{97} + return file_lightning_proto_rawDescGZIP(), []int{98} } func (x *NodeInfo) GetNode() *LightningNode { @@ -9472,7 +9697,7 @@ type LightningNode struct { func (x *LightningNode) Reset() { *x = LightningNode{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[98] + mi := &file_lightning_proto_msgTypes[99] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9485,7 +9710,7 @@ func (x *LightningNode) String() string { func (*LightningNode) ProtoMessage() {} func (x *LightningNode) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[98] + mi := &file_lightning_proto_msgTypes[99] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9498,7 +9723,7 @@ func (x *LightningNode) ProtoReflect() protoreflect.Message { // Deprecated: Use LightningNode.ProtoReflect.Descriptor instead. func (*LightningNode) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{98} + return file_lightning_proto_rawDescGZIP(), []int{99} } func (x *LightningNode) GetLastUpdate() uint32 { @@ -9555,7 +9780,7 @@ type NodeAddress struct { func (x *NodeAddress) Reset() { *x = NodeAddress{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[99] + mi := &file_lightning_proto_msgTypes[100] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9568,7 +9793,7 @@ func (x *NodeAddress) String() string { func (*NodeAddress) ProtoMessage() {} func (x *NodeAddress) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[99] + mi := &file_lightning_proto_msgTypes[100] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9581,7 +9806,7 @@ func (x *NodeAddress) ProtoReflect() protoreflect.Message { // Deprecated: Use NodeAddress.ProtoReflect.Descriptor instead. func (*NodeAddress) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{99} + return file_lightning_proto_rawDescGZIP(), []int{100} } func (x *NodeAddress) GetNetwork() string { @@ -9615,7 +9840,7 @@ type RoutingPolicy struct { func (x *RoutingPolicy) Reset() { *x = RoutingPolicy{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[100] + mi := &file_lightning_proto_msgTypes[101] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9628,7 +9853,7 @@ func (x *RoutingPolicy) String() string { func (*RoutingPolicy) ProtoMessage() {} func (x *RoutingPolicy) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[100] + mi := &file_lightning_proto_msgTypes[101] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9641,7 +9866,7 @@ func (x *RoutingPolicy) ProtoReflect() protoreflect.Message { // Deprecated: Use RoutingPolicy.ProtoReflect.Descriptor instead. func (*RoutingPolicy) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{100} + return file_lightning_proto_rawDescGZIP(), []int{101} } func (x *RoutingPolicy) GetTimeLockDelta() uint32 { @@ -9722,7 +9947,7 @@ type ChannelEdge struct { func (x *ChannelEdge) Reset() { *x = ChannelEdge{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[101] + mi := &file_lightning_proto_msgTypes[102] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9735,7 +9960,7 @@ func (x *ChannelEdge) String() string { func (*ChannelEdge) ProtoMessage() {} func (x *ChannelEdge) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[101] + mi := &file_lightning_proto_msgTypes[102] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9748,7 +9973,7 @@ func (x *ChannelEdge) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelEdge.ProtoReflect.Descriptor instead. func (*ChannelEdge) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{101} + return file_lightning_proto_rawDescGZIP(), []int{102} } func (x *ChannelEdge) GetChannelId() uint64 { @@ -9823,7 +10048,7 @@ type ChannelGraphRequest struct { func (x *ChannelGraphRequest) Reset() { *x = ChannelGraphRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[102] + mi := &file_lightning_proto_msgTypes[103] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9836,7 +10061,7 @@ func (x *ChannelGraphRequest) String() string { func (*ChannelGraphRequest) ProtoMessage() {} func (x *ChannelGraphRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[102] + mi := &file_lightning_proto_msgTypes[103] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9849,7 +10074,7 @@ func (x *ChannelGraphRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelGraphRequest.ProtoReflect.Descriptor instead. func (*ChannelGraphRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{102} + return file_lightning_proto_rawDescGZIP(), []int{103} } func (x *ChannelGraphRequest) GetIncludeUnannounced() bool { @@ -9874,7 +10099,7 @@ type ChannelGraph struct { func (x *ChannelGraph) Reset() { *x = ChannelGraph{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[103] + mi := &file_lightning_proto_msgTypes[104] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9887,7 +10112,7 @@ func (x *ChannelGraph) String() string { func (*ChannelGraph) ProtoMessage() {} func (x *ChannelGraph) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[103] + mi := &file_lightning_proto_msgTypes[104] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9900,7 +10125,7 @@ func (x *ChannelGraph) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelGraph.ProtoReflect.Descriptor instead. func (*ChannelGraph) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{103} + return file_lightning_proto_rawDescGZIP(), []int{104} } func (x *ChannelGraph) GetNodes() []*LightningNode { @@ -9929,7 +10154,7 @@ type NodeMetricsRequest struct { func (x *NodeMetricsRequest) Reset() { *x = NodeMetricsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[104] + mi := &file_lightning_proto_msgTypes[105] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9942,7 +10167,7 @@ func (x *NodeMetricsRequest) String() string { func (*NodeMetricsRequest) ProtoMessage() {} func (x *NodeMetricsRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[104] + mi := &file_lightning_proto_msgTypes[105] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9955,7 +10180,7 @@ func (x *NodeMetricsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use NodeMetricsRequest.ProtoReflect.Descriptor instead. func (*NodeMetricsRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{104} + return file_lightning_proto_rawDescGZIP(), []int{105} } func (x *NodeMetricsRequest) GetTypes() []NodeMetricType { @@ -9982,7 +10207,7 @@ type NodeMetricsResponse struct { func (x *NodeMetricsResponse) Reset() { *x = NodeMetricsResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[105] + mi := &file_lightning_proto_msgTypes[106] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9995,7 +10220,7 @@ func (x *NodeMetricsResponse) String() string { func (*NodeMetricsResponse) ProtoMessage() {} func (x *NodeMetricsResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[105] + mi := &file_lightning_proto_msgTypes[106] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10008,7 +10233,7 @@ func (x *NodeMetricsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use NodeMetricsResponse.ProtoReflect.Descriptor instead. func (*NodeMetricsResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{105} + return file_lightning_proto_rawDescGZIP(), []int{106} } func (x *NodeMetricsResponse) GetBetweennessCentrality() map[string]*FloatMetric { @@ -10032,7 +10257,7 @@ type FloatMetric struct { func (x *FloatMetric) Reset() { *x = FloatMetric{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[106] + mi := &file_lightning_proto_msgTypes[107] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10045,7 +10270,7 @@ func (x *FloatMetric) String() string { func (*FloatMetric) ProtoMessage() {} func (x *FloatMetric) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[106] + mi := &file_lightning_proto_msgTypes[107] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10058,7 +10283,7 @@ func (x *FloatMetric) ProtoReflect() protoreflect.Message { // Deprecated: Use FloatMetric.ProtoReflect.Descriptor instead. func (*FloatMetric) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{106} + return file_lightning_proto_rawDescGZIP(), []int{107} } func (x *FloatMetric) GetValue() float64 { @@ -10090,7 +10315,7 @@ type ChanInfoRequest struct { func (x *ChanInfoRequest) Reset() { *x = ChanInfoRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[107] + mi := &file_lightning_proto_msgTypes[108] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10103,7 +10328,7 @@ func (x *ChanInfoRequest) String() string { func (*ChanInfoRequest) ProtoMessage() {} func (x *ChanInfoRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[107] + mi := &file_lightning_proto_msgTypes[108] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10116,7 +10341,7 @@ func (x *ChanInfoRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ChanInfoRequest.ProtoReflect.Descriptor instead. func (*ChanInfoRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{107} + return file_lightning_proto_rawDescGZIP(), []int{108} } func (x *ChanInfoRequest) GetChanId() uint64 { @@ -10135,7 +10360,7 @@ type NetworkInfoRequest struct { func (x *NetworkInfoRequest) Reset() { *x = NetworkInfoRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[108] + mi := &file_lightning_proto_msgTypes[109] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10148,7 +10373,7 @@ func (x *NetworkInfoRequest) String() string { func (*NetworkInfoRequest) ProtoMessage() {} func (x *NetworkInfoRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[108] + mi := &file_lightning_proto_msgTypes[109] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10161,7 +10386,7 @@ func (x *NetworkInfoRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use NetworkInfoRequest.ProtoReflect.Descriptor instead. func (*NetworkInfoRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{108} + return file_lightning_proto_rawDescGZIP(), []int{109} } type NetworkInfo struct { @@ -10186,7 +10411,7 @@ type NetworkInfo struct { func (x *NetworkInfo) Reset() { *x = NetworkInfo{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[109] + mi := &file_lightning_proto_msgTypes[110] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10199,7 +10424,7 @@ func (x *NetworkInfo) String() string { func (*NetworkInfo) ProtoMessage() {} func (x *NetworkInfo) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[109] + mi := &file_lightning_proto_msgTypes[110] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10212,7 +10437,7 @@ func (x *NetworkInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use NetworkInfo.ProtoReflect.Descriptor instead. func (*NetworkInfo) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{109} + return file_lightning_proto_rawDescGZIP(), []int{110} } func (x *NetworkInfo) GetGraphDiameter() uint32 { @@ -10301,7 +10526,7 @@ type StopRequest struct { func (x *StopRequest) Reset() { *x = StopRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[110] + mi := &file_lightning_proto_msgTypes[111] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10314,7 +10539,7 @@ func (x *StopRequest) String() string { func (*StopRequest) ProtoMessage() {} func (x *StopRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[110] + mi := &file_lightning_proto_msgTypes[111] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10327,7 +10552,7 @@ func (x *StopRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use StopRequest.ProtoReflect.Descriptor instead. func (*StopRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{110} + return file_lightning_proto_rawDescGZIP(), []int{111} } type StopResponse struct { @@ -10339,7 +10564,7 @@ type StopResponse struct { func (x *StopResponse) Reset() { *x = StopResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[111] + mi := &file_lightning_proto_msgTypes[112] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10352,7 +10577,7 @@ func (x *StopResponse) String() string { func (*StopResponse) ProtoMessage() {} func (x *StopResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[111] + mi := &file_lightning_proto_msgTypes[112] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10365,7 +10590,7 @@ func (x *StopResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use StopResponse.ProtoReflect.Descriptor instead. func (*StopResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{111} + return file_lightning_proto_rawDescGZIP(), []int{112} } type GraphTopologySubscription struct { @@ -10377,7 +10602,7 @@ type GraphTopologySubscription struct { func (x *GraphTopologySubscription) Reset() { *x = GraphTopologySubscription{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[112] + mi := &file_lightning_proto_msgTypes[113] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10390,7 +10615,7 @@ func (x *GraphTopologySubscription) String() string { func (*GraphTopologySubscription) ProtoMessage() {} func (x *GraphTopologySubscription) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[112] + mi := &file_lightning_proto_msgTypes[113] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10403,7 +10628,7 @@ func (x *GraphTopologySubscription) ProtoReflect() protoreflect.Message { // Deprecated: Use GraphTopologySubscription.ProtoReflect.Descriptor instead. func (*GraphTopologySubscription) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{112} + return file_lightning_proto_rawDescGZIP(), []int{113} } type GraphTopologyUpdate struct { @@ -10419,7 +10644,7 @@ type GraphTopologyUpdate struct { func (x *GraphTopologyUpdate) Reset() { *x = GraphTopologyUpdate{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[113] + mi := &file_lightning_proto_msgTypes[114] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10432,7 +10657,7 @@ func (x *GraphTopologyUpdate) String() string { func (*GraphTopologyUpdate) ProtoMessage() {} func (x *GraphTopologyUpdate) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[113] + mi := &file_lightning_proto_msgTypes[114] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10445,7 +10670,7 @@ func (x *GraphTopologyUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use GraphTopologyUpdate.ProtoReflect.Descriptor instead. func (*GraphTopologyUpdate) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{113} + return file_lightning_proto_rawDescGZIP(), []int{114} } func (x *GraphTopologyUpdate) GetNodeUpdates() []*NodeUpdate { @@ -10497,7 +10722,7 @@ type NodeUpdate struct { func (x *NodeUpdate) Reset() { *x = NodeUpdate{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[114] + mi := &file_lightning_proto_msgTypes[115] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10510,7 +10735,7 @@ func (x *NodeUpdate) String() string { func (*NodeUpdate) ProtoMessage() {} func (x *NodeUpdate) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[114] + mi := &file_lightning_proto_msgTypes[115] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10523,7 +10748,7 @@ func (x *NodeUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use NodeUpdate.ProtoReflect.Descriptor instead. func (*NodeUpdate) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{114} + return file_lightning_proto_rawDescGZIP(), []int{115} } // Deprecated: Do not use. @@ -10597,7 +10822,7 @@ type ChannelEdgeUpdate struct { func (x *ChannelEdgeUpdate) Reset() { *x = ChannelEdgeUpdate{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[115] + mi := &file_lightning_proto_msgTypes[116] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10610,7 +10835,7 @@ func (x *ChannelEdgeUpdate) String() string { func (*ChannelEdgeUpdate) ProtoMessage() {} func (x *ChannelEdgeUpdate) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[115] + mi := &file_lightning_proto_msgTypes[116] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10623,7 +10848,7 @@ func (x *ChannelEdgeUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelEdgeUpdate.ProtoReflect.Descriptor instead. func (*ChannelEdgeUpdate) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{115} + return file_lightning_proto_rawDescGZIP(), []int{116} } func (x *ChannelEdgeUpdate) GetChanId() uint64 { @@ -10686,7 +10911,7 @@ type ClosedChannelUpdate struct { func (x *ClosedChannelUpdate) Reset() { *x = ClosedChannelUpdate{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[116] + mi := &file_lightning_proto_msgTypes[117] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10699,7 +10924,7 @@ func (x *ClosedChannelUpdate) String() string { func (*ClosedChannelUpdate) ProtoMessage() {} func (x *ClosedChannelUpdate) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[116] + mi := &file_lightning_proto_msgTypes[117] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10712,7 +10937,7 @@ func (x *ClosedChannelUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use ClosedChannelUpdate.ProtoReflect.Descriptor instead. func (*ClosedChannelUpdate) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{116} + return file_lightning_proto_rawDescGZIP(), []int{117} } func (x *ClosedChannelUpdate) GetChanId() uint64 { @@ -10765,7 +10990,7 @@ type HopHint struct { func (x *HopHint) Reset() { *x = HopHint{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[117] + mi := &file_lightning_proto_msgTypes[118] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10778,7 +11003,7 @@ func (x *HopHint) String() string { func (*HopHint) ProtoMessage() {} func (x *HopHint) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[117] + mi := &file_lightning_proto_msgTypes[118] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10791,7 +11016,7 @@ func (x *HopHint) ProtoReflect() protoreflect.Message { // Deprecated: Use HopHint.ProtoReflect.Descriptor instead. func (*HopHint) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{117} + return file_lightning_proto_rawDescGZIP(), []int{118} } func (x *HopHint) GetNodeId() string { @@ -10840,7 +11065,7 @@ type SetID struct { func (x *SetID) Reset() { *x = SetID{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[118] + mi := &file_lightning_proto_msgTypes[119] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10853,7 +11078,7 @@ func (x *SetID) String() string { func (*SetID) ProtoMessage() {} func (x *SetID) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[118] + mi := &file_lightning_proto_msgTypes[119] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10866,7 +11091,7 @@ func (x *SetID) ProtoReflect() protoreflect.Message { // Deprecated: Use SetID.ProtoReflect.Descriptor instead. func (*SetID) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{118} + return file_lightning_proto_rawDescGZIP(), []int{119} } func (x *SetID) GetSetId() []byte { @@ -10890,7 +11115,7 @@ type RouteHint struct { func (x *RouteHint) Reset() { *x = RouteHint{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[119] + mi := &file_lightning_proto_msgTypes[120] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10903,7 +11128,7 @@ func (x *RouteHint) String() string { func (*RouteHint) ProtoMessage() {} func (x *RouteHint) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[119] + mi := &file_lightning_proto_msgTypes[120] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10916,7 +11141,7 @@ func (x *RouteHint) ProtoReflect() protoreflect.Message { // Deprecated: Use RouteHint.ProtoReflect.Descriptor instead. func (*RouteHint) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{119} + return file_lightning_proto_rawDescGZIP(), []int{120} } func (x *RouteHint) GetHopHints() []*HopHint { @@ -10944,7 +11169,7 @@ type AMPInvoiceState struct { func (x *AMPInvoiceState) Reset() { *x = AMPInvoiceState{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[120] + mi := &file_lightning_proto_msgTypes[121] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10957,7 +11182,7 @@ func (x *AMPInvoiceState) String() string { func (*AMPInvoiceState) ProtoMessage() {} func (x *AMPInvoiceState) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[120] + mi := &file_lightning_proto_msgTypes[121] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10970,7 +11195,7 @@ func (x *AMPInvoiceState) ProtoReflect() protoreflect.Message { // Deprecated: Use AMPInvoiceState.ProtoReflect.Descriptor instead. func (*AMPInvoiceState) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{120} + return file_lightning_proto_rawDescGZIP(), []int{121} } func (x *AMPInvoiceState) GetState() InvoiceHTLCState { @@ -11126,7 +11351,7 @@ type Invoice struct { func (x *Invoice) Reset() { *x = Invoice{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[121] + mi := &file_lightning_proto_msgTypes[122] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11139,7 +11364,7 @@ func (x *Invoice) String() string { func (*Invoice) ProtoMessage() {} func (x *Invoice) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[121] + mi := &file_lightning_proto_msgTypes[122] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11152,7 +11377,7 @@ func (x *Invoice) ProtoReflect() protoreflect.Message { // Deprecated: Use Invoice.ProtoReflect.Descriptor instead. func (*Invoice) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{121} + return file_lightning_proto_rawDescGZIP(), []int{122} } func (x *Invoice) GetMemo() string { @@ -11379,7 +11604,7 @@ type InvoiceHTLC struct { func (x *InvoiceHTLC) Reset() { *x = InvoiceHTLC{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[122] + mi := &file_lightning_proto_msgTypes[123] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11392,7 +11617,7 @@ func (x *InvoiceHTLC) String() string { func (*InvoiceHTLC) ProtoMessage() {} func (x *InvoiceHTLC) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[122] + mi := &file_lightning_proto_msgTypes[123] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11405,7 +11630,7 @@ func (x *InvoiceHTLC) ProtoReflect() protoreflect.Message { // Deprecated: Use InvoiceHTLC.ProtoReflect.Descriptor instead. func (*InvoiceHTLC) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{122} + return file_lightning_proto_rawDescGZIP(), []int{123} } func (x *InvoiceHTLC) GetChanId() uint64 { @@ -11510,7 +11735,7 @@ type AMP struct { func (x *AMP) Reset() { *x = AMP{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[123] + mi := &file_lightning_proto_msgTypes[124] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11523,7 +11748,7 @@ func (x *AMP) String() string { func (*AMP) ProtoMessage() {} func (x *AMP) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[123] + mi := &file_lightning_proto_msgTypes[124] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11536,7 +11761,7 @@ func (x *AMP) ProtoReflect() protoreflect.Message { // Deprecated: Use AMP.ProtoReflect.Descriptor instead. func (*AMP) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{123} + return file_lightning_proto_rawDescGZIP(), []int{124} } func (x *AMP) GetRootShare() []byte { @@ -11601,7 +11826,7 @@ type AddInvoiceResponse struct { func (x *AddInvoiceResponse) Reset() { *x = AddInvoiceResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[124] + mi := &file_lightning_proto_msgTypes[125] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11614,7 +11839,7 @@ func (x *AddInvoiceResponse) String() string { func (*AddInvoiceResponse) ProtoMessage() {} func (x *AddInvoiceResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[124] + mi := &file_lightning_proto_msgTypes[125] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11627,7 +11852,7 @@ func (x *AddInvoiceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AddInvoiceResponse.ProtoReflect.Descriptor instead. func (*AddInvoiceResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{124} + return file_lightning_proto_rawDescGZIP(), []int{125} } func (x *AddInvoiceResponse) GetRHash() []byte { @@ -11680,7 +11905,7 @@ type PaymentHash struct { func (x *PaymentHash) Reset() { *x = PaymentHash{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[125] + mi := &file_lightning_proto_msgTypes[126] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11693,7 +11918,7 @@ func (x *PaymentHash) String() string { func (*PaymentHash) ProtoMessage() {} func (x *PaymentHash) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[125] + mi := &file_lightning_proto_msgTypes[126] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11706,7 +11931,7 @@ func (x *PaymentHash) ProtoReflect() protoreflect.Message { // Deprecated: Use PaymentHash.ProtoReflect.Descriptor instead. func (*PaymentHash) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{125} + return file_lightning_proto_rawDescGZIP(), []int{126} } // Deprecated: Do not use. @@ -11748,7 +11973,7 @@ type ListInvoiceRequest struct { func (x *ListInvoiceRequest) Reset() { *x = ListInvoiceRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[126] + mi := &file_lightning_proto_msgTypes[127] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11761,7 +11986,7 @@ func (x *ListInvoiceRequest) String() string { func (*ListInvoiceRequest) ProtoMessage() {} func (x *ListInvoiceRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[126] + mi := &file_lightning_proto_msgTypes[127] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11774,7 +11999,7 @@ func (x *ListInvoiceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListInvoiceRequest.ProtoReflect.Descriptor instead. func (*ListInvoiceRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{126} + return file_lightning_proto_rawDescGZIP(), []int{127} } func (x *ListInvoiceRequest) GetPendingOnly() bool { @@ -11827,7 +12052,7 @@ type ListInvoiceResponse struct { func (x *ListInvoiceResponse) Reset() { *x = ListInvoiceResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[127] + mi := &file_lightning_proto_msgTypes[128] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11840,7 +12065,7 @@ func (x *ListInvoiceResponse) String() string { func (*ListInvoiceResponse) ProtoMessage() {} func (x *ListInvoiceResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[127] + mi := &file_lightning_proto_msgTypes[128] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11853,7 +12078,7 @@ func (x *ListInvoiceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListInvoiceResponse.ProtoReflect.Descriptor instead. func (*ListInvoiceResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{127} + return file_lightning_proto_rawDescGZIP(), []int{128} } func (x *ListInvoiceResponse) GetInvoices() []*Invoice { @@ -11899,7 +12124,7 @@ type InvoiceSubscription struct { func (x *InvoiceSubscription) Reset() { *x = InvoiceSubscription{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[128] + mi := &file_lightning_proto_msgTypes[129] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11912,7 +12137,7 @@ func (x *InvoiceSubscription) String() string { func (*InvoiceSubscription) ProtoMessage() {} func (x *InvoiceSubscription) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[128] + mi := &file_lightning_proto_msgTypes[129] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11925,7 +12150,7 @@ func (x *InvoiceSubscription) ProtoReflect() protoreflect.Message { // Deprecated: Use InvoiceSubscription.ProtoReflect.Descriptor instead. func (*InvoiceSubscription) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{128} + return file_lightning_proto_rawDescGZIP(), []int{129} } func (x *InvoiceSubscription) GetAddIndex() uint64 { @@ -11990,7 +12215,7 @@ type Payment struct { func (x *Payment) Reset() { *x = Payment{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[129] + mi := &file_lightning_proto_msgTypes[130] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12003,7 +12228,7 @@ func (x *Payment) String() string { func (*Payment) ProtoMessage() {} func (x *Payment) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[129] + mi := &file_lightning_proto_msgTypes[130] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12016,7 +12241,7 @@ func (x *Payment) ProtoReflect() protoreflect.Message { // Deprecated: Use Payment.ProtoReflect.Descriptor instead. func (*Payment) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{129} + return file_lightning_proto_rawDescGZIP(), []int{130} } func (x *Payment) GetPaymentHash() string { @@ -12153,7 +12378,7 @@ type HTLCAttempt struct { func (x *HTLCAttempt) Reset() { *x = HTLCAttempt{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[130] + mi := &file_lightning_proto_msgTypes[131] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12166,7 +12391,7 @@ func (x *HTLCAttempt) String() string { func (*HTLCAttempt) ProtoMessage() {} func (x *HTLCAttempt) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[130] + mi := &file_lightning_proto_msgTypes[131] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12179,7 +12404,7 @@ func (x *HTLCAttempt) ProtoReflect() protoreflect.Message { // Deprecated: Use HTLCAttempt.ProtoReflect.Descriptor instead. func (*HTLCAttempt) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{130} + return file_lightning_proto_rawDescGZIP(), []int{131} } func (x *HTLCAttempt) GetAttemptId() uint64 { @@ -12256,12 +12481,18 @@ type ListPaymentsRequest struct { //specified index offset. This can be used to paginate backwards. The order //of the returned payments is always oldest first (ascending index order). Reversed bool `protobuf:"varint,4,opt,name=reversed,proto3" json:"reversed,omitempty"` + // + //If set, all payments (complete and incomplete, independent of the + //max_payments parameter) will be counted. Note that setting this to true will + //increase the run time of the call significantly on systems that have a lot + //of payments, as all of them have to be iterated through to be counted. + CountTotalPayments bool `protobuf:"varint,5,opt,name=count_total_payments,json=countTotalPayments,proto3" json:"count_total_payments,omitempty"` } func (x *ListPaymentsRequest) Reset() { *x = ListPaymentsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[131] + mi := &file_lightning_proto_msgTypes[132] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12274,7 +12505,7 @@ func (x *ListPaymentsRequest) String() string { func (*ListPaymentsRequest) ProtoMessage() {} func (x *ListPaymentsRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[131] + mi := &file_lightning_proto_msgTypes[132] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12287,7 +12518,7 @@ func (x *ListPaymentsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListPaymentsRequest.ProtoReflect.Descriptor instead. func (*ListPaymentsRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{131} + return file_lightning_proto_rawDescGZIP(), []int{132} } func (x *ListPaymentsRequest) GetIncludeIncomplete() bool { @@ -12318,6 +12549,13 @@ func (x *ListPaymentsRequest) GetReversed() bool { return false } +func (x *ListPaymentsRequest) GetCountTotalPayments() bool { + if x != nil { + return x.CountTotalPayments + } + return false +} + type ListPaymentsResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -12333,12 +12571,18 @@ type ListPaymentsResponse struct { //The index of the last item in the set of returned payments. This can be used //as the index_offset to continue seeking forwards in the next request. LastIndexOffset uint64 `protobuf:"varint,3,opt,name=last_index_offset,json=lastIndexOffset,proto3" json:"last_index_offset,omitempty"` + // + //Will only be set if count_total_payments in the request was set. Represents + //the total number of payments (complete and incomplete, independent of the + //number of payments requested in the query) currently present in the payments + //database. + TotalNumPayments uint64 `protobuf:"varint,4,opt,name=total_num_payments,json=totalNumPayments,proto3" json:"total_num_payments,omitempty"` } func (x *ListPaymentsResponse) Reset() { *x = ListPaymentsResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[132] + mi := &file_lightning_proto_msgTypes[133] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12351,7 +12595,7 @@ func (x *ListPaymentsResponse) String() string { func (*ListPaymentsResponse) ProtoMessage() {} func (x *ListPaymentsResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[132] + mi := &file_lightning_proto_msgTypes[133] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12364,7 +12608,7 @@ func (x *ListPaymentsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListPaymentsResponse.ProtoReflect.Descriptor instead. func (*ListPaymentsResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{132} + return file_lightning_proto_rawDescGZIP(), []int{133} } func (x *ListPaymentsResponse) GetPayments() []*Payment { @@ -12388,6 +12632,13 @@ func (x *ListPaymentsResponse) GetLastIndexOffset() uint64 { return 0 } +func (x *ListPaymentsResponse) GetTotalNumPayments() uint64 { + if x != nil { + return x.TotalNumPayments + } + return 0 +} + type DeletePaymentRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -12403,7 +12654,7 @@ type DeletePaymentRequest struct { func (x *DeletePaymentRequest) Reset() { *x = DeletePaymentRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[133] + mi := &file_lightning_proto_msgTypes[134] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12416,7 +12667,7 @@ func (x *DeletePaymentRequest) String() string { func (*DeletePaymentRequest) ProtoMessage() {} func (x *DeletePaymentRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[133] + mi := &file_lightning_proto_msgTypes[134] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12429,7 +12680,7 @@ func (x *DeletePaymentRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeletePaymentRequest.ProtoReflect.Descriptor instead. func (*DeletePaymentRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{133} + return file_lightning_proto_rawDescGZIP(), []int{134} } func (x *DeletePaymentRequest) GetPaymentHash() []byte { @@ -12461,7 +12712,7 @@ type DeleteAllPaymentsRequest struct { func (x *DeleteAllPaymentsRequest) Reset() { *x = DeleteAllPaymentsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[134] + mi := &file_lightning_proto_msgTypes[135] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12474,7 +12725,7 @@ func (x *DeleteAllPaymentsRequest) String() string { func (*DeleteAllPaymentsRequest) ProtoMessage() {} func (x *DeleteAllPaymentsRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[134] + mi := &file_lightning_proto_msgTypes[135] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12487,7 +12738,7 @@ func (x *DeleteAllPaymentsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteAllPaymentsRequest.ProtoReflect.Descriptor instead. func (*DeleteAllPaymentsRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{134} + return file_lightning_proto_rawDescGZIP(), []int{135} } func (x *DeleteAllPaymentsRequest) GetFailedPaymentsOnly() bool { @@ -12513,7 +12764,7 @@ type DeletePaymentResponse struct { func (x *DeletePaymentResponse) Reset() { *x = DeletePaymentResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[135] + mi := &file_lightning_proto_msgTypes[136] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12526,7 +12777,7 @@ func (x *DeletePaymentResponse) String() string { func (*DeletePaymentResponse) ProtoMessage() {} func (x *DeletePaymentResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[135] + mi := &file_lightning_proto_msgTypes[136] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12539,7 +12790,7 @@ func (x *DeletePaymentResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeletePaymentResponse.ProtoReflect.Descriptor instead. func (*DeletePaymentResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{135} + return file_lightning_proto_rawDescGZIP(), []int{136} } type DeleteAllPaymentsResponse struct { @@ -12551,7 +12802,7 @@ type DeleteAllPaymentsResponse struct { func (x *DeleteAllPaymentsResponse) Reset() { *x = DeleteAllPaymentsResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[136] + mi := &file_lightning_proto_msgTypes[137] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12564,7 +12815,7 @@ func (x *DeleteAllPaymentsResponse) String() string { func (*DeleteAllPaymentsResponse) ProtoMessage() {} func (x *DeleteAllPaymentsResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[136] + mi := &file_lightning_proto_msgTypes[137] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12577,7 +12828,7 @@ func (x *DeleteAllPaymentsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteAllPaymentsResponse.ProtoReflect.Descriptor instead. func (*DeleteAllPaymentsResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{136} + return file_lightning_proto_rawDescGZIP(), []int{137} } type AbandonChannelRequest struct { @@ -12597,7 +12848,7 @@ type AbandonChannelRequest struct { func (x *AbandonChannelRequest) Reset() { *x = AbandonChannelRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[137] + mi := &file_lightning_proto_msgTypes[138] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12610,7 +12861,7 @@ func (x *AbandonChannelRequest) String() string { func (*AbandonChannelRequest) ProtoMessage() {} func (x *AbandonChannelRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[137] + mi := &file_lightning_proto_msgTypes[138] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12623,7 +12874,7 @@ func (x *AbandonChannelRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AbandonChannelRequest.ProtoReflect.Descriptor instead. func (*AbandonChannelRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{137} + return file_lightning_proto_rawDescGZIP(), []int{138} } func (x *AbandonChannelRequest) GetChannelPoint() *ChannelPoint { @@ -12656,7 +12907,7 @@ type AbandonChannelResponse struct { func (x *AbandonChannelResponse) Reset() { *x = AbandonChannelResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[138] + mi := &file_lightning_proto_msgTypes[139] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12669,7 +12920,7 @@ func (x *AbandonChannelResponse) String() string { func (*AbandonChannelResponse) ProtoMessage() {} func (x *AbandonChannelResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[138] + mi := &file_lightning_proto_msgTypes[139] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12682,7 +12933,7 @@ func (x *AbandonChannelResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AbandonChannelResponse.ProtoReflect.Descriptor instead. func (*AbandonChannelResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{138} + return file_lightning_proto_rawDescGZIP(), []int{139} } type DebugLevelRequest struct { @@ -12697,7 +12948,7 @@ type DebugLevelRequest struct { func (x *DebugLevelRequest) Reset() { *x = DebugLevelRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[139] + mi := &file_lightning_proto_msgTypes[140] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12710,7 +12961,7 @@ func (x *DebugLevelRequest) String() string { func (*DebugLevelRequest) ProtoMessage() {} func (x *DebugLevelRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[139] + mi := &file_lightning_proto_msgTypes[140] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12723,7 +12974,7 @@ func (x *DebugLevelRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DebugLevelRequest.ProtoReflect.Descriptor instead. func (*DebugLevelRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{139} + return file_lightning_proto_rawDescGZIP(), []int{140} } func (x *DebugLevelRequest) GetShow() bool { @@ -12751,7 +13002,7 @@ type DebugLevelResponse struct { func (x *DebugLevelResponse) Reset() { *x = DebugLevelResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[140] + mi := &file_lightning_proto_msgTypes[141] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12764,7 +13015,7 @@ func (x *DebugLevelResponse) String() string { func (*DebugLevelResponse) ProtoMessage() {} func (x *DebugLevelResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[140] + mi := &file_lightning_proto_msgTypes[141] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12777,7 +13028,7 @@ func (x *DebugLevelResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DebugLevelResponse.ProtoReflect.Descriptor instead. func (*DebugLevelResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{140} + return file_lightning_proto_rawDescGZIP(), []int{141} } func (x *DebugLevelResponse) GetSubSystems() string { @@ -12799,7 +13050,7 @@ type PayReqString struct { func (x *PayReqString) Reset() { *x = PayReqString{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[141] + mi := &file_lightning_proto_msgTypes[142] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12812,7 +13063,7 @@ func (x *PayReqString) String() string { func (*PayReqString) ProtoMessage() {} func (x *PayReqString) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[141] + mi := &file_lightning_proto_msgTypes[142] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12825,7 +13076,7 @@ func (x *PayReqString) ProtoReflect() protoreflect.Message { // Deprecated: Use PayReqString.ProtoReflect.Descriptor instead. func (*PayReqString) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{141} + return file_lightning_proto_rawDescGZIP(), []int{142} } func (x *PayReqString) GetPayReq() string { @@ -12858,7 +13109,7 @@ type PayReq struct { func (x *PayReq) Reset() { *x = PayReq{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[142] + mi := &file_lightning_proto_msgTypes[143] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12871,7 +13122,7 @@ func (x *PayReq) String() string { func (*PayReq) ProtoMessage() {} func (x *PayReq) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[142] + mi := &file_lightning_proto_msgTypes[143] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12884,7 +13135,7 @@ func (x *PayReq) ProtoReflect() protoreflect.Message { // Deprecated: Use PayReq.ProtoReflect.Descriptor instead. func (*PayReq) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{142} + return file_lightning_proto_rawDescGZIP(), []int{143} } func (x *PayReq) GetDestination() string { @@ -12991,7 +13242,7 @@ type Feature struct { func (x *Feature) Reset() { *x = Feature{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[143] + mi := &file_lightning_proto_msgTypes[144] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13004,7 +13255,7 @@ func (x *Feature) String() string { func (*Feature) ProtoMessage() {} func (x *Feature) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[143] + mi := &file_lightning_proto_msgTypes[144] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13017,7 +13268,7 @@ func (x *Feature) ProtoReflect() protoreflect.Message { // Deprecated: Use Feature.ProtoReflect.Descriptor instead. func (*Feature) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{143} + return file_lightning_proto_rawDescGZIP(), []int{144} } func (x *Feature) GetName() string { @@ -13050,7 +13301,7 @@ type FeeReportRequest struct { func (x *FeeReportRequest) Reset() { *x = FeeReportRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[144] + mi := &file_lightning_proto_msgTypes[145] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13063,7 +13314,7 @@ func (x *FeeReportRequest) String() string { func (*FeeReportRequest) ProtoMessage() {} func (x *FeeReportRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[144] + mi := &file_lightning_proto_msgTypes[145] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13076,7 +13327,7 @@ func (x *FeeReportRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FeeReportRequest.ProtoReflect.Descriptor instead. func (*FeeReportRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{144} + return file_lightning_proto_rawDescGZIP(), []int{145} } type ChannelFeeReport struct { @@ -13101,7 +13352,7 @@ type ChannelFeeReport struct { func (x *ChannelFeeReport) Reset() { *x = ChannelFeeReport{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[145] + mi := &file_lightning_proto_msgTypes[146] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13114,7 +13365,7 @@ func (x *ChannelFeeReport) String() string { func (*ChannelFeeReport) ProtoMessage() {} func (x *ChannelFeeReport) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[145] + mi := &file_lightning_proto_msgTypes[146] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13127,7 +13378,7 @@ func (x *ChannelFeeReport) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelFeeReport.ProtoReflect.Descriptor instead. func (*ChannelFeeReport) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{145} + return file_lightning_proto_rawDescGZIP(), []int{146} } func (x *ChannelFeeReport) GetChanId() uint64 { @@ -13187,7 +13438,7 @@ type FeeReportResponse struct { func (x *FeeReportResponse) Reset() { *x = FeeReportResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[146] + mi := &file_lightning_proto_msgTypes[147] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13200,7 +13451,7 @@ func (x *FeeReportResponse) String() string { func (*FeeReportResponse) ProtoMessage() {} func (x *FeeReportResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[146] + mi := &file_lightning_proto_msgTypes[147] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13213,7 +13464,7 @@ func (x *FeeReportResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FeeReportResponse.ProtoReflect.Descriptor instead. func (*FeeReportResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{146} + return file_lightning_proto_rawDescGZIP(), []int{147} } func (x *FeeReportResponse) GetChannelFees() []*ChannelFeeReport { @@ -13258,6 +13509,8 @@ type PolicyUpdateRequest struct { // The effective fee rate in milli-satoshis. The precision of this value // goes up to 6 decimal places, so 1e-6. FeeRate float64 `protobuf:"fixed64,4,opt,name=fee_rate,json=feeRate,proto3" json:"fee_rate,omitempty"` + // The effective fee rate in micro-satoshis (parts per million). + FeeRatePpm uint32 `protobuf:"varint,9,opt,name=fee_rate_ppm,json=feeRatePpm,proto3" json:"fee_rate_ppm,omitempty"` // The required timelock delta for HTLCs forwarded over the channel. TimeLockDelta uint32 `protobuf:"varint,5,opt,name=time_lock_delta,json=timeLockDelta,proto3" json:"time_lock_delta,omitempty"` // If set, the maximum HTLC size in milli-satoshis. If unset, the maximum @@ -13273,7 +13526,7 @@ type PolicyUpdateRequest struct { func (x *PolicyUpdateRequest) Reset() { *x = PolicyUpdateRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[147] + mi := &file_lightning_proto_msgTypes[148] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13286,7 +13539,7 @@ func (x *PolicyUpdateRequest) String() string { func (*PolicyUpdateRequest) ProtoMessage() {} func (x *PolicyUpdateRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[147] + mi := &file_lightning_proto_msgTypes[148] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13299,7 +13552,7 @@ func (x *PolicyUpdateRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PolicyUpdateRequest.ProtoReflect.Descriptor instead. func (*PolicyUpdateRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{147} + return file_lightning_proto_rawDescGZIP(), []int{148} } func (m *PolicyUpdateRequest) GetScope() isPolicyUpdateRequest_Scope { @@ -13337,6 +13590,13 @@ func (x *PolicyUpdateRequest) GetFeeRate() float64 { return 0 } +func (x *PolicyUpdateRequest) GetFeeRatePpm() uint32 { + if x != nil { + return x.FeeRatePpm + } + return 0 +} + func (x *PolicyUpdateRequest) GetTimeLockDelta() uint32 { if x != nil { return x.TimeLockDelta @@ -13399,7 +13659,7 @@ type FailedUpdate struct { func (x *FailedUpdate) Reset() { *x = FailedUpdate{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[148] + mi := &file_lightning_proto_msgTypes[149] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13412,7 +13672,7 @@ func (x *FailedUpdate) String() string { func (*FailedUpdate) ProtoMessage() {} func (x *FailedUpdate) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[148] + mi := &file_lightning_proto_msgTypes[149] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13425,7 +13685,7 @@ func (x *FailedUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use FailedUpdate.ProtoReflect.Descriptor instead. func (*FailedUpdate) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{148} + return file_lightning_proto_rawDescGZIP(), []int{149} } func (x *FailedUpdate) GetOutpoint() *OutPoint { @@ -13461,7 +13721,7 @@ type PolicyUpdateResponse struct { func (x *PolicyUpdateResponse) Reset() { *x = PolicyUpdateResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[149] + mi := &file_lightning_proto_msgTypes[150] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13474,7 +13734,7 @@ func (x *PolicyUpdateResponse) String() string { func (*PolicyUpdateResponse) ProtoMessage() {} func (x *PolicyUpdateResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[149] + mi := &file_lightning_proto_msgTypes[150] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13487,7 +13747,7 @@ func (x *PolicyUpdateResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PolicyUpdateResponse.ProtoReflect.Descriptor instead. func (*PolicyUpdateResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{149} + return file_lightning_proto_rawDescGZIP(), []int{150} } func (x *PolicyUpdateResponse) GetFailedUpdates() []*FailedUpdate { @@ -13521,7 +13781,7 @@ type ForwardingHistoryRequest struct { func (x *ForwardingHistoryRequest) Reset() { *x = ForwardingHistoryRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[150] + mi := &file_lightning_proto_msgTypes[151] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13534,7 +13794,7 @@ func (x *ForwardingHistoryRequest) String() string { func (*ForwardingHistoryRequest) ProtoMessage() {} func (x *ForwardingHistoryRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[150] + mi := &file_lightning_proto_msgTypes[151] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13547,7 +13807,7 @@ func (x *ForwardingHistoryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ForwardingHistoryRequest.ProtoReflect.Descriptor instead. func (*ForwardingHistoryRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{150} + return file_lightning_proto_rawDescGZIP(), []int{151} } func (x *ForwardingHistoryRequest) GetStartTime() uint64 { @@ -13617,7 +13877,7 @@ type ForwardingEvent struct { func (x *ForwardingEvent) Reset() { *x = ForwardingEvent{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[151] + mi := &file_lightning_proto_msgTypes[152] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13630,7 +13890,7 @@ func (x *ForwardingEvent) String() string { func (*ForwardingEvent) ProtoMessage() {} func (x *ForwardingEvent) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[151] + mi := &file_lightning_proto_msgTypes[152] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13643,7 +13903,7 @@ func (x *ForwardingEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use ForwardingEvent.ProtoReflect.Descriptor instead. func (*ForwardingEvent) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{151} + return file_lightning_proto_rawDescGZIP(), []int{152} } // Deprecated: Do not use. @@ -13733,7 +13993,7 @@ type ForwardingHistoryResponse struct { func (x *ForwardingHistoryResponse) Reset() { *x = ForwardingHistoryResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[152] + mi := &file_lightning_proto_msgTypes[153] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13746,7 +14006,7 @@ func (x *ForwardingHistoryResponse) String() string { func (*ForwardingHistoryResponse) ProtoMessage() {} func (x *ForwardingHistoryResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[152] + mi := &file_lightning_proto_msgTypes[153] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13759,7 +14019,7 @@ func (x *ForwardingHistoryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ForwardingHistoryResponse.ProtoReflect.Descriptor instead. func (*ForwardingHistoryResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{152} + return file_lightning_proto_rawDescGZIP(), []int{153} } func (x *ForwardingHistoryResponse) GetForwardingEvents() []*ForwardingEvent { @@ -13788,7 +14048,7 @@ type ExportChannelBackupRequest struct { func (x *ExportChannelBackupRequest) Reset() { *x = ExportChannelBackupRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[153] + mi := &file_lightning_proto_msgTypes[154] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13801,7 +14061,7 @@ func (x *ExportChannelBackupRequest) String() string { func (*ExportChannelBackupRequest) ProtoMessage() {} func (x *ExportChannelBackupRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[153] + mi := &file_lightning_proto_msgTypes[154] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13814,7 +14074,7 @@ func (x *ExportChannelBackupRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ExportChannelBackupRequest.ProtoReflect.Descriptor instead. func (*ExportChannelBackupRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{153} + return file_lightning_proto_rawDescGZIP(), []int{154} } func (x *ExportChannelBackupRequest) GetChanPoint() *ChannelPoint { @@ -13843,7 +14103,7 @@ type ChannelBackup struct { func (x *ChannelBackup) Reset() { *x = ChannelBackup{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[154] + mi := &file_lightning_proto_msgTypes[155] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13856,7 +14116,7 @@ func (x *ChannelBackup) String() string { func (*ChannelBackup) ProtoMessage() {} func (x *ChannelBackup) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[154] + mi := &file_lightning_proto_msgTypes[155] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13869,7 +14129,7 @@ func (x *ChannelBackup) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelBackup.ProtoReflect.Descriptor instead. func (*ChannelBackup) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{154} + return file_lightning_proto_rawDescGZIP(), []int{155} } func (x *ChannelBackup) GetChanPoint() *ChannelPoint { @@ -13905,7 +14165,7 @@ type MultiChanBackup struct { func (x *MultiChanBackup) Reset() { *x = MultiChanBackup{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[155] + mi := &file_lightning_proto_msgTypes[156] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13918,7 +14178,7 @@ func (x *MultiChanBackup) String() string { func (*MultiChanBackup) ProtoMessage() {} func (x *MultiChanBackup) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[155] + mi := &file_lightning_proto_msgTypes[156] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13931,7 +14191,7 @@ func (x *MultiChanBackup) ProtoReflect() protoreflect.Message { // Deprecated: Use MultiChanBackup.ProtoReflect.Descriptor instead. func (*MultiChanBackup) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{155} + return file_lightning_proto_rawDescGZIP(), []int{156} } func (x *MultiChanBackup) GetChanPoints() []*ChannelPoint { @@ -13957,7 +14217,7 @@ type ChanBackupExportRequest struct { func (x *ChanBackupExportRequest) Reset() { *x = ChanBackupExportRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[156] + mi := &file_lightning_proto_msgTypes[157] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13970,7 +14230,7 @@ func (x *ChanBackupExportRequest) String() string { func (*ChanBackupExportRequest) ProtoMessage() {} func (x *ChanBackupExportRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[156] + mi := &file_lightning_proto_msgTypes[157] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13983,7 +14243,7 @@ func (x *ChanBackupExportRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ChanBackupExportRequest.ProtoReflect.Descriptor instead. func (*ChanBackupExportRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{156} + return file_lightning_proto_rawDescGZIP(), []int{157} } type ChanBackupSnapshot struct { @@ -14004,7 +14264,7 @@ type ChanBackupSnapshot struct { func (x *ChanBackupSnapshot) Reset() { *x = ChanBackupSnapshot{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[157] + mi := &file_lightning_proto_msgTypes[158] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14017,7 +14277,7 @@ func (x *ChanBackupSnapshot) String() string { func (*ChanBackupSnapshot) ProtoMessage() {} func (x *ChanBackupSnapshot) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[157] + mi := &file_lightning_proto_msgTypes[158] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14030,7 +14290,7 @@ func (x *ChanBackupSnapshot) ProtoReflect() protoreflect.Message { // Deprecated: Use ChanBackupSnapshot.ProtoReflect.Descriptor instead. func (*ChanBackupSnapshot) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{157} + return file_lightning_proto_rawDescGZIP(), []int{158} } func (x *ChanBackupSnapshot) GetSingleChanBackups() *ChannelBackups { @@ -14060,7 +14320,7 @@ type ChannelBackups struct { func (x *ChannelBackups) Reset() { *x = ChannelBackups{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[158] + mi := &file_lightning_proto_msgTypes[159] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14073,7 +14333,7 @@ func (x *ChannelBackups) String() string { func (*ChannelBackups) ProtoMessage() {} func (x *ChannelBackups) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[158] + mi := &file_lightning_proto_msgTypes[159] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14086,7 +14346,7 @@ func (x *ChannelBackups) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelBackups.ProtoReflect.Descriptor instead. func (*ChannelBackups) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{158} + return file_lightning_proto_rawDescGZIP(), []int{159} } func (x *ChannelBackups) GetChanBackups() []*ChannelBackup { @@ -14110,7 +14370,7 @@ type RestoreChanBackupRequest struct { func (x *RestoreChanBackupRequest) Reset() { *x = RestoreChanBackupRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[159] + mi := &file_lightning_proto_msgTypes[160] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14123,7 +14383,7 @@ func (x *RestoreChanBackupRequest) String() string { func (*RestoreChanBackupRequest) ProtoMessage() {} func (x *RestoreChanBackupRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[159] + mi := &file_lightning_proto_msgTypes[160] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14136,7 +14396,7 @@ func (x *RestoreChanBackupRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RestoreChanBackupRequest.ProtoReflect.Descriptor instead. func (*RestoreChanBackupRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{159} + return file_lightning_proto_rawDescGZIP(), []int{160} } func (m *RestoreChanBackupRequest) GetBackup() isRestoreChanBackupRequest_Backup { @@ -14190,7 +14450,7 @@ type RestoreBackupResponse struct { func (x *RestoreBackupResponse) Reset() { *x = RestoreBackupResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[160] + mi := &file_lightning_proto_msgTypes[161] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14203,7 +14463,7 @@ func (x *RestoreBackupResponse) String() string { func (*RestoreBackupResponse) ProtoMessage() {} func (x *RestoreBackupResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[160] + mi := &file_lightning_proto_msgTypes[161] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14216,7 +14476,7 @@ func (x *RestoreBackupResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RestoreBackupResponse.ProtoReflect.Descriptor instead. func (*RestoreBackupResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{160} + return file_lightning_proto_rawDescGZIP(), []int{161} } type ChannelBackupSubscription struct { @@ -14228,7 +14488,7 @@ type ChannelBackupSubscription struct { func (x *ChannelBackupSubscription) Reset() { *x = ChannelBackupSubscription{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[161] + mi := &file_lightning_proto_msgTypes[162] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14241,7 +14501,7 @@ func (x *ChannelBackupSubscription) String() string { func (*ChannelBackupSubscription) ProtoMessage() {} func (x *ChannelBackupSubscription) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[161] + mi := &file_lightning_proto_msgTypes[162] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14254,7 +14514,7 @@ func (x *ChannelBackupSubscription) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelBackupSubscription.ProtoReflect.Descriptor instead. func (*ChannelBackupSubscription) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{161} + return file_lightning_proto_rawDescGZIP(), []int{162} } type VerifyChanBackupResponse struct { @@ -14266,7 +14526,7 @@ type VerifyChanBackupResponse struct { func (x *VerifyChanBackupResponse) Reset() { *x = VerifyChanBackupResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[162] + mi := &file_lightning_proto_msgTypes[163] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14279,7 +14539,7 @@ func (x *VerifyChanBackupResponse) String() string { func (*VerifyChanBackupResponse) ProtoMessage() {} func (x *VerifyChanBackupResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[162] + mi := &file_lightning_proto_msgTypes[163] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14292,7 +14552,7 @@ func (x *VerifyChanBackupResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use VerifyChanBackupResponse.ProtoReflect.Descriptor instead. func (*VerifyChanBackupResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{162} + return file_lightning_proto_rawDescGZIP(), []int{163} } type MacaroonPermission struct { @@ -14309,7 +14569,7 @@ type MacaroonPermission struct { func (x *MacaroonPermission) Reset() { *x = MacaroonPermission{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[163] + mi := &file_lightning_proto_msgTypes[164] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14322,7 +14582,7 @@ func (x *MacaroonPermission) String() string { func (*MacaroonPermission) ProtoMessage() {} func (x *MacaroonPermission) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[163] + mi := &file_lightning_proto_msgTypes[164] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14335,7 +14595,7 @@ func (x *MacaroonPermission) ProtoReflect() protoreflect.Message { // Deprecated: Use MacaroonPermission.ProtoReflect.Descriptor instead. func (*MacaroonPermission) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{163} + return file_lightning_proto_rawDescGZIP(), []int{164} } func (x *MacaroonPermission) GetEntity() string { @@ -14370,7 +14630,7 @@ type BakeMacaroonRequest struct { func (x *BakeMacaroonRequest) Reset() { *x = BakeMacaroonRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[164] + mi := &file_lightning_proto_msgTypes[165] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14383,7 +14643,7 @@ func (x *BakeMacaroonRequest) String() string { func (*BakeMacaroonRequest) ProtoMessage() {} func (x *BakeMacaroonRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[164] + mi := &file_lightning_proto_msgTypes[165] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14396,7 +14656,7 @@ func (x *BakeMacaroonRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use BakeMacaroonRequest.ProtoReflect.Descriptor instead. func (*BakeMacaroonRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{164} + return file_lightning_proto_rawDescGZIP(), []int{165} } func (x *BakeMacaroonRequest) GetPermissions() []*MacaroonPermission { @@ -14432,7 +14692,7 @@ type BakeMacaroonResponse struct { func (x *BakeMacaroonResponse) Reset() { *x = BakeMacaroonResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[165] + mi := &file_lightning_proto_msgTypes[166] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14445,7 +14705,7 @@ func (x *BakeMacaroonResponse) String() string { func (*BakeMacaroonResponse) ProtoMessage() {} func (x *BakeMacaroonResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[165] + mi := &file_lightning_proto_msgTypes[166] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14458,7 +14718,7 @@ func (x *BakeMacaroonResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use BakeMacaroonResponse.ProtoReflect.Descriptor instead. func (*BakeMacaroonResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{165} + return file_lightning_proto_rawDescGZIP(), []int{166} } func (x *BakeMacaroonResponse) GetMacaroon() string { @@ -14477,7 +14737,7 @@ type ListMacaroonIDsRequest struct { func (x *ListMacaroonIDsRequest) Reset() { *x = ListMacaroonIDsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[166] + mi := &file_lightning_proto_msgTypes[167] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14490,7 +14750,7 @@ func (x *ListMacaroonIDsRequest) String() string { func (*ListMacaroonIDsRequest) ProtoMessage() {} func (x *ListMacaroonIDsRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[166] + mi := &file_lightning_proto_msgTypes[167] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14503,7 +14763,7 @@ func (x *ListMacaroonIDsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListMacaroonIDsRequest.ProtoReflect.Descriptor instead. func (*ListMacaroonIDsRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{166} + return file_lightning_proto_rawDescGZIP(), []int{167} } type ListMacaroonIDsResponse struct { @@ -14518,7 +14778,7 @@ type ListMacaroonIDsResponse struct { func (x *ListMacaroonIDsResponse) Reset() { *x = ListMacaroonIDsResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[167] + mi := &file_lightning_proto_msgTypes[168] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14531,7 +14791,7 @@ func (x *ListMacaroonIDsResponse) String() string { func (*ListMacaroonIDsResponse) ProtoMessage() {} func (x *ListMacaroonIDsResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[167] + mi := &file_lightning_proto_msgTypes[168] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14544,7 +14804,7 @@ func (x *ListMacaroonIDsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListMacaroonIDsResponse.ProtoReflect.Descriptor instead. func (*ListMacaroonIDsResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{167} + return file_lightning_proto_rawDescGZIP(), []int{168} } func (x *ListMacaroonIDsResponse) GetRootKeyIds() []uint64 { @@ -14566,7 +14826,7 @@ type DeleteMacaroonIDRequest struct { func (x *DeleteMacaroonIDRequest) Reset() { *x = DeleteMacaroonIDRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[168] + mi := &file_lightning_proto_msgTypes[169] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14579,7 +14839,7 @@ func (x *DeleteMacaroonIDRequest) String() string { func (*DeleteMacaroonIDRequest) ProtoMessage() {} func (x *DeleteMacaroonIDRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[168] + mi := &file_lightning_proto_msgTypes[169] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14592,7 +14852,7 @@ func (x *DeleteMacaroonIDRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteMacaroonIDRequest.ProtoReflect.Descriptor instead. func (*DeleteMacaroonIDRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{168} + return file_lightning_proto_rawDescGZIP(), []int{169} } func (x *DeleteMacaroonIDRequest) GetRootKeyId() uint64 { @@ -14614,7 +14874,7 @@ type DeleteMacaroonIDResponse struct { func (x *DeleteMacaroonIDResponse) Reset() { *x = DeleteMacaroonIDResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[169] + mi := &file_lightning_proto_msgTypes[170] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14627,7 +14887,7 @@ func (x *DeleteMacaroonIDResponse) String() string { func (*DeleteMacaroonIDResponse) ProtoMessage() {} func (x *DeleteMacaroonIDResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[169] + mi := &file_lightning_proto_msgTypes[170] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14640,7 +14900,7 @@ func (x *DeleteMacaroonIDResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteMacaroonIDResponse.ProtoReflect.Descriptor instead. func (*DeleteMacaroonIDResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{169} + return file_lightning_proto_rawDescGZIP(), []int{170} } func (x *DeleteMacaroonIDResponse) GetDeleted() bool { @@ -14662,7 +14922,7 @@ type MacaroonPermissionList struct { func (x *MacaroonPermissionList) Reset() { *x = MacaroonPermissionList{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[170] + mi := &file_lightning_proto_msgTypes[171] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14675,7 +14935,7 @@ func (x *MacaroonPermissionList) String() string { func (*MacaroonPermissionList) ProtoMessage() {} func (x *MacaroonPermissionList) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[170] + mi := &file_lightning_proto_msgTypes[171] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14688,7 +14948,7 @@ func (x *MacaroonPermissionList) ProtoReflect() protoreflect.Message { // Deprecated: Use MacaroonPermissionList.ProtoReflect.Descriptor instead. func (*MacaroonPermissionList) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{170} + return file_lightning_proto_rawDescGZIP(), []int{171} } func (x *MacaroonPermissionList) GetPermissions() []*MacaroonPermission { @@ -14707,7 +14967,7 @@ type ListPermissionsRequest struct { func (x *ListPermissionsRequest) Reset() { *x = ListPermissionsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[171] + mi := &file_lightning_proto_msgTypes[172] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14720,7 +14980,7 @@ func (x *ListPermissionsRequest) String() string { func (*ListPermissionsRequest) ProtoMessage() {} func (x *ListPermissionsRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[171] + mi := &file_lightning_proto_msgTypes[172] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14733,7 +14993,7 @@ func (x *ListPermissionsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListPermissionsRequest.ProtoReflect.Descriptor instead. func (*ListPermissionsRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{171} + return file_lightning_proto_rawDescGZIP(), []int{172} } type ListPermissionsResponse struct { @@ -14750,7 +15010,7 @@ type ListPermissionsResponse struct { func (x *ListPermissionsResponse) Reset() { *x = ListPermissionsResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[172] + mi := &file_lightning_proto_msgTypes[173] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14763,7 +15023,7 @@ func (x *ListPermissionsResponse) String() string { func (*ListPermissionsResponse) ProtoMessage() {} func (x *ListPermissionsResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[172] + mi := &file_lightning_proto_msgTypes[173] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14776,7 +15036,7 @@ func (x *ListPermissionsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListPermissionsResponse.ProtoReflect.Descriptor instead. func (*ListPermissionsResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{172} + return file_lightning_proto_rawDescGZIP(), []int{173} } func (x *ListPermissionsResponse) GetMethodPermissions() map[string]*MacaroonPermissionList { @@ -14814,7 +15074,7 @@ type Failure struct { func (x *Failure) Reset() { *x = Failure{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[173] + mi := &file_lightning_proto_msgTypes[174] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14827,7 +15087,7 @@ func (x *Failure) String() string { func (*Failure) ProtoMessage() {} func (x *Failure) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[173] + mi := &file_lightning_proto_msgTypes[174] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14840,7 +15100,7 @@ func (x *Failure) ProtoReflect() protoreflect.Message { // Deprecated: Use Failure.ProtoReflect.Descriptor instead. func (*Failure) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{173} + return file_lightning_proto_rawDescGZIP(), []int{174} } func (x *Failure) GetCode() Failure_FailureCode { @@ -14966,7 +15226,7 @@ type ChannelUpdate struct { func (x *ChannelUpdate) Reset() { *x = ChannelUpdate{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[174] + mi := &file_lightning_proto_msgTypes[175] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14979,7 +15239,7 @@ func (x *ChannelUpdate) String() string { func (*ChannelUpdate) ProtoMessage() {} func (x *ChannelUpdate) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[174] + mi := &file_lightning_proto_msgTypes[175] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14992,7 +15252,7 @@ func (x *ChannelUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelUpdate.ProtoReflect.Descriptor instead. func (*ChannelUpdate) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{174} + return file_lightning_proto_rawDescGZIP(), []int{175} } func (x *ChannelUpdate) GetSignature() []byte { @@ -15092,7 +15352,7 @@ type MacaroonId struct { func (x *MacaroonId) Reset() { *x = MacaroonId{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[175] + mi := &file_lightning_proto_msgTypes[176] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15105,7 +15365,7 @@ func (x *MacaroonId) String() string { func (*MacaroonId) ProtoMessage() {} func (x *MacaroonId) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[175] + mi := &file_lightning_proto_msgTypes[176] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15118,7 +15378,7 @@ func (x *MacaroonId) ProtoReflect() protoreflect.Message { // Deprecated: Use MacaroonId.ProtoReflect.Descriptor instead. func (*MacaroonId) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{175} + return file_lightning_proto_rawDescGZIP(), []int{176} } func (x *MacaroonId) GetNonce() []byte { @@ -15154,7 +15414,7 @@ type Op struct { func (x *Op) Reset() { *x = Op{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[176] + mi := &file_lightning_proto_msgTypes[177] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15167,7 +15427,7 @@ func (x *Op) String() string { func (*Op) ProtoMessage() {} func (x *Op) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[176] + mi := &file_lightning_proto_msgTypes[177] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15180,7 +15440,7 @@ func (x *Op) ProtoReflect() protoreflect.Message { // Deprecated: Use Op.ProtoReflect.Descriptor instead. func (*Op) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{176} + return file_lightning_proto_rawDescGZIP(), []int{177} } func (x *Op) GetEntity() string { @@ -15210,7 +15470,7 @@ type CheckMacPermRequest struct { func (x *CheckMacPermRequest) Reset() { *x = CheckMacPermRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[177] + mi := &file_lightning_proto_msgTypes[178] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15223,7 +15483,7 @@ func (x *CheckMacPermRequest) String() string { func (*CheckMacPermRequest) ProtoMessage() {} func (x *CheckMacPermRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[177] + mi := &file_lightning_proto_msgTypes[178] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15236,7 +15496,7 @@ func (x *CheckMacPermRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CheckMacPermRequest.ProtoReflect.Descriptor instead. func (*CheckMacPermRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{177} + return file_lightning_proto_rawDescGZIP(), []int{178} } func (x *CheckMacPermRequest) GetMacaroon() []byte { @@ -15271,7 +15531,7 @@ type CheckMacPermResponse struct { func (x *CheckMacPermResponse) Reset() { *x = CheckMacPermResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[178] + mi := &file_lightning_proto_msgTypes[179] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15284,7 +15544,7 @@ func (x *CheckMacPermResponse) String() string { func (*CheckMacPermResponse) ProtoMessage() {} func (x *CheckMacPermResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[178] + mi := &file_lightning_proto_msgTypes[179] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15297,7 +15557,7 @@ func (x *CheckMacPermResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CheckMacPermResponse.ProtoReflect.Descriptor instead. func (*CheckMacPermResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{178} + return file_lightning_proto_rawDescGZIP(), []int{179} } func (x *CheckMacPermResponse) GetValid() bool { @@ -15313,8 +15573,10 @@ type RPCMiddlewareRequest struct { unknownFields protoimpl.UnknownFields // - //The unique ID of the intercepted request. Useful for mapping request to - //response when implementing full duplex message interception. + //The unique ID of the intercepted original gRPC request. Useful for mapping + //request to response when implementing full duplex message interception. For + //streaming requests, this will be the same ID for all incoming and outgoing + //middleware intercept messages of the _same_ stream. RequestId uint64 `protobuf:"varint,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` // //The raw bytes of the complete macaroon as sent by the gRPC client in the @@ -15339,12 +15601,20 @@ type RPCMiddlewareRequest struct { // *RPCMiddlewareRequest_Request // *RPCMiddlewareRequest_Response InterceptType isRPCMiddlewareRequest_InterceptType `protobuf_oneof:"intercept_type"` + // + //The unique message ID of this middleware intercept message. There can be + //multiple middleware intercept messages per single gRPC request (one for the + //incoming request and one for the outgoing response) or gRPC stream (one for + //each incoming message and one for each outgoing response). This message ID + //must be referenced when responding (accepting/rejecting/modifying) to an + //intercept message. + MsgId uint64 `protobuf:"varint,7,opt,name=msg_id,json=msgId,proto3" json:"msg_id,omitempty"` } func (x *RPCMiddlewareRequest) Reset() { *x = RPCMiddlewareRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[179] + mi := &file_lightning_proto_msgTypes[180] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15357,7 +15627,7 @@ func (x *RPCMiddlewareRequest) String() string { func (*RPCMiddlewareRequest) ProtoMessage() {} func (x *RPCMiddlewareRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[179] + mi := &file_lightning_proto_msgTypes[180] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15370,7 +15640,7 @@ func (x *RPCMiddlewareRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RPCMiddlewareRequest.ProtoReflect.Descriptor instead. func (*RPCMiddlewareRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{179} + return file_lightning_proto_rawDescGZIP(), []int{180} } func (x *RPCMiddlewareRequest) GetRequestId() uint64 { @@ -15422,6 +15692,13 @@ func (x *RPCMiddlewareRequest) GetResponse() *RPCMessage { return nil } +func (x *RPCMiddlewareRequest) GetMsgId() uint64 { + if x != nil { + return x.MsgId + } + return 0 +} + type isRPCMiddlewareRequest_InterceptType interface { isRPCMiddlewareRequest_InterceptType() } @@ -15478,7 +15755,7 @@ type StreamAuth struct { func (x *StreamAuth) Reset() { *x = StreamAuth{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[180] + mi := &file_lightning_proto_msgTypes[181] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15491,7 +15768,7 @@ func (x *StreamAuth) String() string { func (*StreamAuth) ProtoMessage() {} func (x *StreamAuth) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[180] + mi := &file_lightning_proto_msgTypes[181] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15504,7 +15781,7 @@ func (x *StreamAuth) ProtoReflect() protoreflect.Message { // Deprecated: Use StreamAuth.ProtoReflect.Descriptor instead. func (*StreamAuth) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{180} + return file_lightning_proto_rawDescGZIP(), []int{181} } func (x *StreamAuth) GetMethodFullUri() string { @@ -15540,7 +15817,7 @@ type RPCMessage struct { func (x *RPCMessage) Reset() { *x = RPCMessage{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[181] + mi := &file_lightning_proto_msgTypes[182] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15553,7 +15830,7 @@ func (x *RPCMessage) String() string { func (*RPCMessage) ProtoMessage() {} func (x *RPCMessage) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[181] + mi := &file_lightning_proto_msgTypes[182] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15566,7 +15843,7 @@ func (x *RPCMessage) ProtoReflect() protoreflect.Message { // Deprecated: Use RPCMessage.ProtoReflect.Descriptor instead. func (*RPCMessage) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{181} + return file_lightning_proto_rawDescGZIP(), []int{182} } func (x *RPCMessage) GetMethodFullUri() string { @@ -15603,10 +15880,10 @@ type RPCMiddlewareResponse struct { unknownFields protoimpl.UnknownFields // - //The unique ID of the intercepted request that this response refers to. Must - //always be set when giving feedback to an intercept but is ignored for the - //initial registration message. - RequestId uint64 `protobuf:"varint,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + //The request message ID this response refers to. Must always be set when + //giving feedback to an intercept but is ignored for the initial registration + //message. + RefMsgId uint64 `protobuf:"varint,1,opt,name=ref_msg_id,json=refMsgId,proto3" json:"ref_msg_id,omitempty"` // //The middleware can only send two types of messages to lnd: The initial //registration message that identifies the middleware and after that only @@ -15621,7 +15898,7 @@ type RPCMiddlewareResponse struct { func (x *RPCMiddlewareResponse) Reset() { *x = RPCMiddlewareResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[182] + mi := &file_lightning_proto_msgTypes[183] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15634,7 +15911,7 @@ func (x *RPCMiddlewareResponse) String() string { func (*RPCMiddlewareResponse) ProtoMessage() {} func (x *RPCMiddlewareResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[182] + mi := &file_lightning_proto_msgTypes[183] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15647,12 +15924,12 @@ func (x *RPCMiddlewareResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RPCMiddlewareResponse.ProtoReflect.Descriptor instead. func (*RPCMiddlewareResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{182} + return file_lightning_proto_rawDescGZIP(), []int{183} } -func (x *RPCMiddlewareResponse) GetRequestId() uint64 { +func (x *RPCMiddlewareResponse) GetRefMsgId() uint64 { if x != nil { - return x.RequestId + return x.RefMsgId } return 0 } @@ -15738,7 +16015,7 @@ type MiddlewareRegistration struct { func (x *MiddlewareRegistration) Reset() { *x = MiddlewareRegistration{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[183] + mi := &file_lightning_proto_msgTypes[184] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15751,7 +16028,7 @@ func (x *MiddlewareRegistration) String() string { func (*MiddlewareRegistration) ProtoMessage() {} func (x *MiddlewareRegistration) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[183] + mi := &file_lightning_proto_msgTypes[184] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15764,7 +16041,7 @@ func (x *MiddlewareRegistration) ProtoReflect() protoreflect.Message { // Deprecated: Use MiddlewareRegistration.ProtoReflect.Descriptor instead. func (*MiddlewareRegistration) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{183} + return file_lightning_proto_rawDescGZIP(), []int{184} } func (x *MiddlewareRegistration) GetMiddlewareName() string { @@ -15816,7 +16093,7 @@ type InterceptFeedback struct { func (x *InterceptFeedback) Reset() { *x = InterceptFeedback{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[184] + mi := &file_lightning_proto_msgTypes[185] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15829,7 +16106,7 @@ func (x *InterceptFeedback) String() string { func (*InterceptFeedback) ProtoMessage() {} func (x *InterceptFeedback) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[184] + mi := &file_lightning_proto_msgTypes[185] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15842,7 +16119,7 @@ func (x *InterceptFeedback) ProtoReflect() protoreflect.Message { // Deprecated: Use InterceptFeedback.ProtoReflect.Descriptor instead. func (*InterceptFeedback) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{184} + return file_lightning_proto_rawDescGZIP(), []int{185} } func (x *InterceptFeedback) GetError() string { @@ -15889,12 +16166,16 @@ type PendingChannelsResponse_PendingChannel struct { CommitmentType CommitmentType `protobuf:"varint,9,opt,name=commitment_type,json=commitmentType,proto3,enum=lnrpc.CommitmentType" json:"commitment_type,omitempty"` // Total number of forwarding packages created in this channel. NumForwardingPackages int64 `protobuf:"varint,10,opt,name=num_forwarding_packages,json=numForwardingPackages,proto3" json:"num_forwarding_packages,omitempty"` + // A set of flags showing the current state of the channel. + ChanStatusFlags string `protobuf:"bytes,11,opt,name=chan_status_flags,json=chanStatusFlags,proto3" json:"chan_status_flags,omitempty"` + // Whether this channel is advertised to the network or not. + Private bool `protobuf:"varint,12,opt,name=private,proto3" json:"private,omitempty"` } func (x *PendingChannelsResponse_PendingChannel) Reset() { *x = PendingChannelsResponse_PendingChannel{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[190] + mi := &file_lightning_proto_msgTypes[191] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15907,7 +16188,7 @@ func (x *PendingChannelsResponse_PendingChannel) String() string { func (*PendingChannelsResponse_PendingChannel) ProtoMessage() {} func (x *PendingChannelsResponse_PendingChannel) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[190] + mi := &file_lightning_proto_msgTypes[191] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15920,7 +16201,7 @@ func (x *PendingChannelsResponse_PendingChannel) ProtoReflect() protoreflect.Mes // Deprecated: Use PendingChannelsResponse_PendingChannel.ProtoReflect.Descriptor instead. func (*PendingChannelsResponse_PendingChannel) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{79, 0} + return file_lightning_proto_rawDescGZIP(), []int{80, 0} } func (x *PendingChannelsResponse_PendingChannel) GetRemoteNodePub() string { @@ -15993,6 +16274,20 @@ func (x *PendingChannelsResponse_PendingChannel) GetNumForwardingPackages() int6 return 0 } +func (x *PendingChannelsResponse_PendingChannel) GetChanStatusFlags() string { + if x != nil { + return x.ChanStatusFlags + } + return "" +} + +func (x *PendingChannelsResponse_PendingChannel) GetPrivate() bool { + if x != nil { + return x.Private + } + return false +} + type PendingChannelsResponse_PendingOpenChannel struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -16000,8 +16295,6 @@ type PendingChannelsResponse_PendingOpenChannel struct { // The pending channel Channel *PendingChannelsResponse_PendingChannel `protobuf:"bytes,1,opt,name=channel,proto3" json:"channel,omitempty"` - // The height at which this channel will be confirmed - ConfirmationHeight uint32 `protobuf:"varint,2,opt,name=confirmation_height,json=confirmationHeight,proto3" json:"confirmation_height,omitempty"` // //The amount calculated to be paid in fees for the current set of //commitment transactions. The fee amount is persisted with the channel @@ -16021,7 +16314,7 @@ type PendingChannelsResponse_PendingOpenChannel struct { func (x *PendingChannelsResponse_PendingOpenChannel) Reset() { *x = PendingChannelsResponse_PendingOpenChannel{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[191] + mi := &file_lightning_proto_msgTypes[192] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16034,7 +16327,7 @@ func (x *PendingChannelsResponse_PendingOpenChannel) String() string { func (*PendingChannelsResponse_PendingOpenChannel) ProtoMessage() {} func (x *PendingChannelsResponse_PendingOpenChannel) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[191] + mi := &file_lightning_proto_msgTypes[192] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16047,7 +16340,7 @@ func (x *PendingChannelsResponse_PendingOpenChannel) ProtoReflect() protoreflect // Deprecated: Use PendingChannelsResponse_PendingOpenChannel.ProtoReflect.Descriptor instead. func (*PendingChannelsResponse_PendingOpenChannel) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{79, 1} + return file_lightning_proto_rawDescGZIP(), []int{80, 1} } func (x *PendingChannelsResponse_PendingOpenChannel) GetChannel() *PendingChannelsResponse_PendingChannel { @@ -16057,13 +16350,6 @@ func (x *PendingChannelsResponse_PendingOpenChannel) GetChannel() *PendingChanne return nil } -func (x *PendingChannelsResponse_PendingOpenChannel) GetConfirmationHeight() uint32 { - if x != nil { - return x.ConfirmationHeight - } - return 0 -} - func (x *PendingChannelsResponse_PendingOpenChannel) GetCommitFee() int64 { if x != nil { return x.CommitFee @@ -16098,12 +16384,14 @@ type PendingChannelsResponse_WaitingCloseChannel struct { //A list of valid commitment transactions. Any of these can confirm at //this point. Commitments *PendingChannelsResponse_Commitments `protobuf:"bytes,3,opt,name=commitments,proto3" json:"commitments,omitempty"` + // The transaction id of the closing transaction + ClosingTxid string `protobuf:"bytes,4,opt,name=closing_txid,json=closingTxid,proto3" json:"closing_txid,omitempty"` } func (x *PendingChannelsResponse_WaitingCloseChannel) Reset() { *x = PendingChannelsResponse_WaitingCloseChannel{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[192] + mi := &file_lightning_proto_msgTypes[193] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16116,7 +16404,7 @@ func (x *PendingChannelsResponse_WaitingCloseChannel) String() string { func (*PendingChannelsResponse_WaitingCloseChannel) ProtoMessage() {} func (x *PendingChannelsResponse_WaitingCloseChannel) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[192] + mi := &file_lightning_proto_msgTypes[193] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16129,7 +16417,7 @@ func (x *PendingChannelsResponse_WaitingCloseChannel) ProtoReflect() protoreflec // Deprecated: Use PendingChannelsResponse_WaitingCloseChannel.ProtoReflect.Descriptor instead. func (*PendingChannelsResponse_WaitingCloseChannel) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{79, 2} + return file_lightning_proto_rawDescGZIP(), []int{80, 2} } func (x *PendingChannelsResponse_WaitingCloseChannel) GetChannel() *PendingChannelsResponse_PendingChannel { @@ -16153,6 +16441,13 @@ func (x *PendingChannelsResponse_WaitingCloseChannel) GetCommitments() *PendingC return nil } +func (x *PendingChannelsResponse_WaitingCloseChannel) GetClosingTxid() string { + if x != nil { + return x.ClosingTxid + } + return "" +} + type PendingChannelsResponse_Commitments struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -16181,7 +16476,7 @@ type PendingChannelsResponse_Commitments struct { func (x *PendingChannelsResponse_Commitments) Reset() { *x = PendingChannelsResponse_Commitments{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[193] + mi := &file_lightning_proto_msgTypes[194] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16194,7 +16489,7 @@ func (x *PendingChannelsResponse_Commitments) String() string { func (*PendingChannelsResponse_Commitments) ProtoMessage() {} func (x *PendingChannelsResponse_Commitments) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[193] + mi := &file_lightning_proto_msgTypes[194] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16207,7 +16502,7 @@ func (x *PendingChannelsResponse_Commitments) ProtoReflect() protoreflect.Messag // Deprecated: Use PendingChannelsResponse_Commitments.ProtoReflect.Descriptor instead. func (*PendingChannelsResponse_Commitments) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{79, 3} + return file_lightning_proto_rawDescGZIP(), []int{80, 3} } func (x *PendingChannelsResponse_Commitments) GetLocalTxid() string { @@ -16266,7 +16561,7 @@ type PendingChannelsResponse_ClosedChannel struct { func (x *PendingChannelsResponse_ClosedChannel) Reset() { *x = PendingChannelsResponse_ClosedChannel{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[194] + mi := &file_lightning_proto_msgTypes[195] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16279,7 +16574,7 @@ func (x *PendingChannelsResponse_ClosedChannel) String() string { func (*PendingChannelsResponse_ClosedChannel) ProtoMessage() {} func (x *PendingChannelsResponse_ClosedChannel) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[194] + mi := &file_lightning_proto_msgTypes[195] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16292,7 +16587,7 @@ func (x *PendingChannelsResponse_ClosedChannel) ProtoReflect() protoreflect.Mess // Deprecated: Use PendingChannelsResponse_ClosedChannel.ProtoReflect.Descriptor instead. func (*PendingChannelsResponse_ClosedChannel) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{79, 4} + return file_lightning_proto_rawDescGZIP(), []int{80, 4} } func (x *PendingChannelsResponse_ClosedChannel) GetChannel() *PendingChannelsResponse_PendingChannel { @@ -16336,7 +16631,7 @@ type PendingChannelsResponse_ForceClosedChannel struct { func (x *PendingChannelsResponse_ForceClosedChannel) Reset() { *x = PendingChannelsResponse_ForceClosedChannel{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[195] + mi := &file_lightning_proto_msgTypes[196] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16349,7 +16644,7 @@ func (x *PendingChannelsResponse_ForceClosedChannel) String() string { func (*PendingChannelsResponse_ForceClosedChannel) ProtoMessage() {} func (x *PendingChannelsResponse_ForceClosedChannel) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[195] + mi := &file_lightning_proto_msgTypes[196] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16362,7 +16657,7 @@ func (x *PendingChannelsResponse_ForceClosedChannel) ProtoReflect() protoreflect // Deprecated: Use PendingChannelsResponse_ForceClosedChannel.ProtoReflect.Descriptor instead. func (*PendingChannelsResponse_ForceClosedChannel) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{79, 5} + return file_lightning_proto_rawDescGZIP(), []int{80, 5} } func (x *PendingChannelsResponse_ForceClosedChannel) GetChannel() *PendingChannelsResponse_PendingChannel { @@ -16454,2638 +16749,2701 @@ var file_lightning_proto_rawDesc = []byte{ 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xc6, 0x02, 0x0a, 0x0b, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x12, 0x16, - 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, - 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2b, 0x0a, 0x11, 0x6e, 0x75, 0x6d, 0x5f, 0x63, 0x6f, - 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x10, 0x6e, 0x75, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x61, 0x73, - 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, - 0x73, 0x68, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, - 0x68, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, - 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x73, 0x74, - 0x61, 0x6d, 0x70, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x53, - 0x74, 0x61, 0x6d, 0x70, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x66, 0x65, - 0x65, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x46, - 0x65, 0x65, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, - 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x64, 0x65, 0x73, - 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x1c, 0x0a, 0x0a, 0x72, 0x61, - 0x77, 0x5f, 0x74, 0x78, 0x5f, 0x68, 0x65, 0x78, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x72, 0x61, 0x77, 0x54, 0x78, 0x48, 0x65, 0x78, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, - 0x6c, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x22, 0x74, - 0x0a, 0x16, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x74, 0x61, 0x72, - 0x74, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, - 0x73, 0x74, 0x61, 0x72, 0x74, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x65, - 0x6e, 0x64, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, - 0x09, 0x65, 0x6e, 0x64, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x63, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x63, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x4c, 0x0a, 0x12, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x36, 0x0a, 0x0c, 0x74, 0x72, - 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x22, 0x68, 0x0a, 0x08, 0x46, 0x65, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x16, - 0x0a, 0x05, 0x66, 0x69, 0x78, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, - 0x05, 0x66, 0x69, 0x78, 0x65, 0x64, 0x12, 0x1f, 0x0a, 0x0a, 0x66, 0x69, 0x78, 0x65, 0x64, 0x5f, - 0x6d, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x09, 0x66, 0x69, - 0x78, 0x65, 0x64, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x1a, 0x0a, 0x07, 0x70, 0x65, 0x72, 0x63, 0x65, - 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x07, 0x70, 0x65, 0x72, 0x63, - 0x65, 0x6e, 0x74, 0x42, 0x07, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x22, 0xea, 0x05, 0x0a, - 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, - 0x64, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x65, 0x73, 0x74, - 0x12, 0x23, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, 0x64, 0x65, 0x73, 0x74, 0x53, - 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x6d, 0x74, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x03, 0x61, 0x6d, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x6d, 0x74, 0x5f, 0x6d, - 0x73, 0x61, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x61, 0x6d, 0x74, 0x4d, 0x73, - 0x61, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, - 0x73, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, - 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x32, 0x0a, 0x13, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, - 0x5f, 0x68, 0x61, 0x73, 0x68, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x11, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, - 0x61, 0x73, 0x68, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x27, 0x0a, 0x0f, 0x70, 0x61, 0x79, - 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0e, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x63, 0x6c, 0x74, 0x76, - 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x66, 0x69, - 0x6e, 0x61, 0x6c, 0x43, 0x6c, 0x74, 0x76, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x2c, 0x0a, 0x09, - 0x66, 0x65, 0x65, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, - 0x52, 0x08, 0x66, 0x65, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x2c, 0x0a, 0x10, 0x6f, 0x75, - 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x09, - 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x0e, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, - 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x6c, 0x61, 0x73, 0x74, - 0x5f, 0x68, 0x6f, 0x70, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x0d, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x0d, 0x6c, 0x61, 0x73, 0x74, 0x48, 0x6f, 0x70, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, - 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x0a, - 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x63, 0x6c, 0x74, 0x76, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, - 0x59, 0x0a, 0x13, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, - 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x2e, 0x44, 0x65, 0x73, 0x74, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, - 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x11, 0x64, 0x65, 0x73, 0x74, 0x43, 0x75, 0x73, - 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x61, 0x6c, - 0x6c, 0x6f, 0x77, 0x5f, 0x73, 0x65, 0x6c, 0x66, 0x5f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, - 0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x53, 0x65, 0x6c, - 0x66, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x36, 0x0a, 0x0d, 0x64, 0x65, 0x73, 0x74, - 0x5f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x0e, 0x32, - 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x42, - 0x69, 0x74, 0x52, 0x0c, 0x64, 0x65, 0x73, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, - 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, - 0x18, 0x10, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x41, - 0x64, 0x64, 0x72, 0x1a, 0x44, 0x0a, 0x16, 0x44, 0x65, 0x73, 0x74, 0x43, 0x75, 0x73, 0x74, 0x6f, - 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, - 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, - 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xb4, 0x01, 0x0a, 0x0c, 0x53, 0x65, - 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x61, - 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, - 0x29, 0x0a, 0x10, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x70, 0x72, 0x65, 0x69, 0x6d, - 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x70, 0x61, 0x79, 0x6d, 0x65, - 0x6e, 0x74, 0x50, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x31, 0x0a, 0x0d, 0x70, 0x61, - 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x0c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, - 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x21, 0x0a, - 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, - 0x22, 0x95, 0x01, 0x0a, 0x12, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, - 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, - 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x32, 0x0a, 0x13, 0x70, 0x61, - 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, - 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x11, 0x70, 0x61, 0x79, - 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x22, - 0x0a, 0x05, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x05, 0x72, 0x6f, 0x75, - 0x74, 0x65, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x22, 0x9a, 0x04, 0x0a, 0x14, 0x43, 0x68, 0x61, - 0x6e, 0x6e, 0x65, 0x6c, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x6e, 0x6f, 0x64, 0x65, 0x50, 0x75, 0x62, 0x6b, - 0x65, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x68, 0x61, 0x73, 0x68, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x48, 0x61, 0x73, - 0x68, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, - 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x70, 0x65, 0x6e, 0x64, - 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x75, 0x6e, - 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x6d, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, - 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x41, 0x6d, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x70, 0x75, - 0x73, 0x68, 0x5f, 0x61, 0x6d, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x70, 0x75, - 0x73, 0x68, 0x41, 0x6d, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x75, 0x73, 0x74, 0x5f, 0x6c, 0x69, - 0x6d, 0x69, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x64, 0x75, 0x73, 0x74, 0x4c, - 0x69, 0x6d, 0x69, 0x74, 0x12, 0x2d, 0x0a, 0x13, 0x6d, 0x61, 0x78, 0x5f, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x5f, 0x69, 0x6e, 0x5f, 0x66, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x10, 0x6d, 0x61, 0x78, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x49, 0x6e, 0x46, 0x6c, 0x69, - 0x67, 0x68, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x72, - 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x63, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x12, 0x19, 0x0a, 0x08, - 0x6d, 0x69, 0x6e, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, - 0x6d, 0x69, 0x6e, 0x48, 0x74, 0x6c, 0x63, 0x12, 0x1c, 0x0a, 0x0a, 0x66, 0x65, 0x65, 0x5f, 0x70, - 0x65, 0x72, 0x5f, 0x6b, 0x77, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x66, 0x65, 0x65, - 0x50, 0x65, 0x72, 0x4b, 0x77, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x73, 0x76, 0x5f, 0x64, 0x65, 0x6c, - 0x61, 0x79, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x63, 0x73, 0x76, 0x44, 0x65, 0x6c, - 0x61, 0x79, 0x12, 0x2c, 0x0a, 0x12, 0x6d, 0x61, 0x78, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, - 0x65, 0x64, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, - 0x6d, 0x61, 0x78, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x73, - 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x66, 0x6c, 0x61, 0x67, - 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x46, 0x6c, 0x61, 0x67, 0x73, 0x12, 0x3e, 0x0a, 0x0f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, - 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, - 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0e, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, - 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0xf3, 0x02, 0x0a, 0x15, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x16, 0x0a, 0x06, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x06, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x65, 0x6e, 0x64, 0x69, - 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x0d, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, - 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, - 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x29, 0x0a, 0x10, 0x75, 0x70, 0x66, 0x72, 0x6f, 0x6e, 0x74, - 0x5f, 0x73, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0f, 0x75, 0x70, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, - 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x73, 0x76, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x0d, 0x52, 0x08, 0x63, 0x73, 0x76, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x12, 0x1f, 0x0a, - 0x0b, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x04, 0x52, 0x0a, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x53, 0x61, 0x74, 0x12, 0x2b, - 0x0a, 0x12, 0x69, 0x6e, 0x5f, 0x66, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x6d, 0x61, 0x78, 0x5f, - 0x6d, 0x73, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x69, 0x6e, 0x46, 0x6c, - 0x69, 0x67, 0x68, 0x74, 0x4d, 0x61, 0x78, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x6d, - 0x61, 0x78, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x08, 0x20, - 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x6d, 0x61, 0x78, 0x48, 0x74, 0x6c, 0x63, 0x43, 0x6f, 0x75, 0x6e, - 0x74, 0x12, 0x1e, 0x0a, 0x0b, 0x6d, 0x69, 0x6e, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x69, 0x6e, - 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x6d, 0x69, 0x6e, 0x48, 0x74, 0x6c, 0x63, 0x49, - 0x6e, 0x12, 0x28, 0x0a, 0x10, 0x6d, 0x69, 0x6e, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x5f, - 0x64, 0x65, 0x70, 0x74, 0x68, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x6d, 0x69, 0x6e, - 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x44, 0x65, 0x70, 0x74, 0x68, 0x22, 0x9d, 0x01, 0x0a, 0x0c, - 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x2e, 0x0a, 0x12, - 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x5f, 0x62, 0x79, 0x74, - 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x10, 0x66, 0x75, 0x6e, 0x64, - 0x69, 0x6e, 0x67, 0x54, 0x78, 0x69, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x2a, 0x0a, 0x10, - 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x5f, 0x73, 0x74, 0x72, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0e, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, - 0x67, 0x54, 0x78, 0x69, 0x64, 0x53, 0x74, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x6f, 0x75, 0x74, 0x70, - 0x75, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, - 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x42, 0x0e, 0x0a, 0x0c, 0x66, - 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x22, 0x67, 0x0a, 0x08, 0x4f, - 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x78, 0x69, 0x64, 0x5f, - 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x74, 0x78, 0x69, - 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x78, 0x69, 0x64, 0x5f, 0x73, - 0x74, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x74, 0x78, 0x69, 0x64, 0x53, 0x74, - 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, - 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x49, - 0x6e, 0x64, 0x65, 0x78, 0x22, 0x3e, 0x0a, 0x10, 0x4c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, - 0x67, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6b, - 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, - 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x68, 0x6f, 0x73, 0x74, 0x22, 0x91, 0x02, 0x0a, 0x12, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, - 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4f, 0x0a, 0x0c, 0x41, - 0x64, 0x64, 0x72, 0x54, 0x6f, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x2b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, - 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x41, 0x64, 0x64, - 0x72, 0x54, 0x6f, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0c, - 0x41, 0x64, 0x64, 0x72, 0x54, 0x6f, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, - 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x1b, 0x0a, - 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x73, 0x70, - 0x65, 0x6e, 0x64, 0x5f, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x55, 0x6e, 0x63, 0x6f, - 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x1a, 0x3f, 0x0a, 0x11, 0x41, 0x64, 0x64, 0x72, 0x54, - 0x6f, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, - 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, - 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x87, 0x01, 0x0a, 0x13, 0x45, 0x73, 0x74, - 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x17, 0x0a, 0x07, 0x66, 0x65, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x06, 0x66, 0x65, 0x65, 0x53, 0x61, 0x74, 0x12, 0x33, 0x0a, 0x14, 0x66, 0x65, 0x65, - 0x72, 0x61, 0x74, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x74, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x11, 0x66, 0x65, 0x65, - 0x72, 0x61, 0x74, 0x65, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x12, 0x22, - 0x0a, 0x0d, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, - 0x74, 0x65, 0x22, 0xeb, 0x02, 0x0a, 0x0f, 0x53, 0x65, 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x79, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4c, 0x0a, 0x0c, 0x41, 0x64, 0x64, 0x72, 0x54, 0x6f, - 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x79, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x54, 0x6f, 0x41, 0x6d, 0x6f, 0x75, 0x6e, - 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0c, 0x41, 0x64, 0x64, 0x72, 0x54, 0x6f, 0x41, 0x6d, - 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, - 0x6f, 0x6e, 0x66, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, - 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, - 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x61, - 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x12, 0x24, 0x0a, 0x0c, 0x73, 0x61, 0x74, - 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x42, - 0x02, 0x18, 0x01, 0x52, 0x0a, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x12, - 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, - 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, - 0x66, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, - 0x66, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x5f, 0x75, 0x6e, 0x63, 0x6f, - 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x73, - 0x70, 0x65, 0x6e, 0x64, 0x55, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x1a, - 0x3f, 0x0a, 0x11, 0x41, 0x64, 0x64, 0x72, 0x54, 0x6f, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, - 0x22, 0x26, 0x0a, 0x10, 0x53, 0x65, 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x79, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x22, 0xa4, 0x02, 0x0a, 0x10, 0x53, 0x65, 0x6e, - 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, - 0x04, 0x61, 0x64, 0x64, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, 0x64, - 0x72, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x61, 0x72, - 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, - 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x61, - 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x0b, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x12, 0x24, - 0x0a, 0x0c, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, - 0x42, 0x79, 0x74, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x61, 0x6c, 0x6c, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x65, 0x6e, 0x64, 0x41, 0x6c, 0x6c, 0x12, - 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, - 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, - 0x66, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, - 0x66, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x5f, 0x75, 0x6e, 0x63, 0x6f, - 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x73, - 0x70, 0x65, 0x6e, 0x64, 0x55, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x22, - 0x27, 0x0a, 0x11, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x22, 0x68, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, - 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, - 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x6d, - 0x61, 0x78, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, - 0x6d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x22, 0x38, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, - 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x05, 0x75, 0x74, 0x78, - 0x6f, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x55, 0x74, 0x78, 0x6f, 0x52, 0x05, 0x75, 0x74, 0x78, 0x6f, 0x73, 0x22, 0x55, 0x0a, 0x11, - 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x26, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, - 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, - 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x63, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x22, 0x2e, 0x0a, 0x12, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, - 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, - 0x65, 0x73, 0x73, 0x22, 0x47, 0x0a, 0x12, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x12, 0x1f, 0x0a, 0x0b, 0x73, - 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x0a, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x48, 0x61, 0x73, 0x68, 0x22, 0x33, 0x0a, 0x13, - 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, - 0x65, 0x22, 0x46, 0x0a, 0x14, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x12, 0x1c, 0x0a, 0x09, 0x73, - 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x45, 0x0a, 0x15, 0x56, 0x65, 0x72, - 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6b, - 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, - 0x22, 0x6f, 0x0a, 0x12, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x04, 0x61, 0x64, 0x64, 0x72, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x67, - 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x04, 0x61, - 0x64, 0x64, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x65, 0x72, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x04, 0x70, 0x65, 0x72, 0x6d, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, - 0x75, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, - 0x74, 0x22, 0x15, 0x0a, 0x13, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x30, 0x0a, 0x15, 0x44, 0x69, 0x73, 0x63, - 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x22, 0x18, 0x0a, 0x16, 0x44, 0x69, - 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x86, 0x02, 0x0a, 0x04, 0x48, 0x54, 0x4c, 0x43, 0x12, 0x1a, 0x0a, - 0x08, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x08, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, - 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, - 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x68, 0x61, 0x73, 0x68, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x68, 0x61, 0x73, 0x68, 0x4c, 0x6f, 0x63, 0x6b, 0x12, 0x2b, - 0x0a, 0x11, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x68, 0x65, 0x69, - 0x67, 0x68, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x65, 0x78, 0x70, 0x69, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x68, - 0x74, 0x6c, 0x63, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, - 0x09, 0x68, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x2d, 0x0a, 0x12, 0x66, 0x6f, - 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x11, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, - 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x32, 0x0a, 0x15, 0x66, 0x6f, 0x72, - 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x69, 0x6e, 0x64, - 0x65, 0x78, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x13, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, - 0x64, 0x69, 0x6e, 0x67, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x84, 0x02, - 0x0a, 0x12, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, - 0x69, 0x6e, 0x74, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x73, 0x76, 0x5f, 0x64, 0x65, 0x6c, 0x61, - 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x63, 0x73, 0x76, 0x44, 0x65, 0x6c, 0x61, - 0x79, 0x12, 0x28, 0x0a, 0x10, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, - 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x63, 0x68, 0x61, - 0x6e, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x53, 0x61, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x64, - 0x75, 0x73, 0x74, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x0c, 0x64, 0x75, 0x73, 0x74, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x53, 0x61, - 0x74, 0x12, 0x2f, 0x0a, 0x14, 0x6d, 0x61, 0x78, 0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, - 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, - 0x11, 0x6d, 0x61, 0x78, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x41, 0x6d, 0x74, 0x4d, 0x73, - 0x61, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x69, 0x6e, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, - 0x73, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x6d, 0x69, 0x6e, 0x48, 0x74, - 0x6c, 0x63, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x6d, 0x61, 0x78, 0x5f, 0x61, 0x63, - 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x0d, 0x52, 0x10, 0x6d, 0x61, 0x78, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x48, - 0x74, 0x6c, 0x63, 0x73, 0x22, 0xd7, 0x09, 0x0a, 0x07, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x6d, 0x6f, - 0x74, 0x65, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0c, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x23, 0x0a, - 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, - 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, - 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x6c, - 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0c, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, - 0x12, 0x25, 0x0a, 0x0e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, - 0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, - 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x69, - 0x74, 0x5f, 0x66, 0x65, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f, 0x6d, - 0x6d, 0x69, 0x74, 0x46, 0x65, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, - 0x5f, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x63, - 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x57, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x1c, 0x0a, 0x0a, 0x66, - 0x65, 0x65, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x6b, 0x77, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x08, 0x66, 0x65, 0x65, 0x50, 0x65, 0x72, 0x4b, 0x77, 0x12, 0x2b, 0x0a, 0x11, 0x75, 0x6e, 0x73, - 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x0b, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x75, 0x6e, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x42, - 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, - 0x73, 0x61, 0x74, 0x6f, 0x73, 0x68, 0x69, 0x73, 0x5f, 0x73, 0x65, 0x6e, 0x74, 0x18, 0x0c, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x11, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x53, 0x61, 0x74, 0x6f, 0x73, 0x68, - 0x69, 0x73, 0x53, 0x65, 0x6e, 0x74, 0x12, 0x36, 0x0a, 0x17, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, - 0x73, 0x61, 0x74, 0x6f, 0x73, 0x68, 0x69, 0x73, 0x5f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, - 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x53, 0x61, - 0x74, 0x6f, 0x73, 0x68, 0x69, 0x73, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x12, 0x1f, - 0x0a, 0x0b, 0x6e, 0x75, 0x6d, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x0e, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x0a, 0x6e, 0x75, 0x6d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x12, - 0x30, 0x0a, 0x0d, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x73, - 0x18, 0x0f, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x48, - 0x54, 0x4c, 0x43, 0x52, 0x0c, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x74, 0x6c, 0x63, - 0x73, 0x12, 0x1f, 0x0a, 0x09, 0x63, 0x73, 0x76, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x18, 0x10, - 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x08, 0x63, 0x73, 0x76, 0x44, 0x65, 0x6c, - 0x61, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x18, 0x11, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x12, 0x1c, 0x0a, 0x09, - 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x12, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x09, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x2a, 0x0a, 0x11, 0x63, 0x68, - 0x61, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x18, - 0x13, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x12, 0x37, 0x0a, 0x16, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, - 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x5f, 0x73, 0x61, 0x74, - 0x18, 0x14, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x13, 0x6c, 0x6f, 0x63, 0x61, - 0x6c, 0x43, 0x68, 0x61, 0x6e, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x53, 0x61, 0x74, 0x12, - 0x39, 0x0a, 0x17, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x72, - 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x15, 0x20, 0x01, 0x28, 0x03, - 0x42, 0x02, 0x18, 0x01, 0x52, 0x14, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, - 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x53, 0x61, 0x74, 0x12, 0x2e, 0x0a, 0x11, 0x73, 0x74, - 0x61, 0x74, 0x69, 0x63, 0x5f, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, - 0x16, 0x20, 0x01, 0x28, 0x08, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0f, 0x73, 0x74, 0x61, 0x74, 0x69, - 0x63, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x3e, 0x0a, 0x0f, 0x63, 0x6f, - 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x1a, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, - 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0e, 0x63, 0x6f, 0x6d, 0x6d, - 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x69, - 0x66, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x17, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x6c, 0x69, - 0x66, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x70, 0x74, 0x69, 0x6d, 0x65, - 0x18, 0x18, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x23, - 0x0a, 0x0d, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, - 0x19, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x41, 0x64, 0x64, 0x72, - 0x65, 0x73, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x75, 0x73, 0x68, 0x5f, 0x61, 0x6d, 0x6f, 0x75, - 0x6e, 0x74, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x70, 0x75, - 0x73, 0x68, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x61, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x74, - 0x68, 0x61, 0x77, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x1c, 0x20, 0x01, 0x28, 0x0d, - 0x52, 0x0a, 0x74, 0x68, 0x61, 0x77, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x46, 0x0a, 0x11, - 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, - 0x73, 0x18, 0x1d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, - 0x74, 0x73, 0x52, 0x10, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, - 0x69, 0x6e, 0x74, 0x73, 0x12, 0x48, 0x0a, 0x12, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x63, - 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x73, 0x52, 0x11, 0x72, 0x65, 0x6d, - 0x6f, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x73, 0x22, 0xb3, - 0x01, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, - 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x61, 0x63, 0x74, - 0x69, 0x76, 0x65, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e, 0x61, 0x63, 0x74, - 0x69, 0x76, 0x65, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, - 0x69, 0x6e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x1f, 0x0a, 0x0b, - 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x21, 0x0a, - 0x0c, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x0b, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4f, 0x6e, 0x6c, 0x79, - 0x12, 0x12, 0x0a, 0x04, 0x70, 0x65, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, - 0x70, 0x65, 0x65, 0x72, 0x22, 0x42, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x68, 0x61, 0x6e, - 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x08, - 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x08, - 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x22, 0xd8, 0x05, 0x0a, 0x13, 0x43, 0x68, 0x61, - 0x6e, 0x6e, 0x65, 0x6c, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xe0, 0x01, 0x0a, 0x0c, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, + 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x12, 0x38, 0x0a, 0x0b, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, + 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x6b, + 0x5f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, + 0x6b, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x6f, 0x75, 0x74, 0x70, 0x75, + 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x6f, + 0x75, 0x74, 0x70, 0x75, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, + 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, + 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x69, 0x73, 0x5f, 0x6f, 0x75, 0x72, 0x5f, 0x61, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x69, 0x73, 0x4f, 0x75, + 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x86, 0x03, 0x0a, 0x0b, 0x54, 0x72, 0x61, + 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x68, + 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x78, 0x48, 0x61, 0x73, + 0x68, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2b, 0x0a, 0x11, 0x6e, 0x75, 0x6d, + 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x10, 0x6e, 0x75, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, + 0x68, 0x61, 0x73, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, 0x6c, 0x6f, 0x63, + 0x6b, 0x48, 0x61, 0x73, 0x68, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, + 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x62, 0x6c, 0x6f, + 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x69, 0x6d, 0x65, + 0x5f, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x69, + 0x6d, 0x65, 0x53, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, + 0x5f, 0x66, 0x65, 0x65, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x6f, 0x74, + 0x61, 0x6c, 0x46, 0x65, 0x65, 0x73, 0x12, 0x29, 0x0a, 0x0e, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x61, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x42, 0x02, + 0x18, 0x01, 0x52, 0x0d, 0x64, 0x65, 0x73, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, + 0x73, 0x12, 0x3a, 0x0a, 0x0e, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x64, 0x65, 0x74, 0x61, + 0x69, 0x6c, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x52, 0x0d, + 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x1c, 0x0a, + 0x0a, 0x72, 0x61, 0x77, 0x5f, 0x74, 0x78, 0x5f, 0x68, 0x65, 0x78, 0x18, 0x09, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x72, 0x61, 0x77, 0x54, 0x78, 0x48, 0x65, 0x78, 0x12, 0x14, 0x0a, 0x05, 0x6c, + 0x61, 0x62, 0x65, 0x6c, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, + 0x6c, 0x22, 0x74, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x73, + 0x74, 0x61, 0x72, 0x74, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x0b, 0x73, 0x74, 0x61, 0x72, 0x74, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x1d, + 0x0a, 0x0a, 0x65, 0x6e, 0x64, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x09, 0x65, 0x6e, 0x64, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x18, 0x0a, + 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x4c, 0x0a, 0x12, 0x54, 0x72, 0x61, 0x6e, 0x73, + 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x36, 0x0a, + 0x0c, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x61, 0x6e, + 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x68, 0x0a, 0x08, 0x46, 0x65, 0x65, 0x4c, 0x69, 0x6d, 0x69, + 0x74, 0x12, 0x16, 0x0a, 0x05, 0x66, 0x69, 0x78, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, + 0x48, 0x00, 0x52, 0x05, 0x66, 0x69, 0x78, 0x65, 0x64, 0x12, 0x1f, 0x0a, 0x0a, 0x66, 0x69, 0x78, + 0x65, 0x64, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, + 0x09, 0x66, 0x69, 0x78, 0x65, 0x64, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x1a, 0x0a, 0x07, 0x70, 0x65, + 0x72, 0x63, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x07, 0x70, + 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x42, 0x07, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x22, + 0xea, 0x05, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, + 0x65, 0x73, 0x74, 0x12, 0x23, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x73, 0x74, 0x72, 0x69, + 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, 0x64, 0x65, + 0x73, 0x74, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x6d, 0x74, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x61, 0x6d, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x6d, + 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x61, 0x6d, + 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, + 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x32, 0x0a, 0x13, 0x70, 0x61, 0x79, 0x6d, + 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x11, 0x70, 0x61, 0x79, 0x6d, 0x65, + 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x27, 0x0a, 0x0f, + 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x63, + 0x6c, 0x74, 0x76, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x0e, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x43, 0x6c, 0x74, 0x76, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, + 0x2c, 0x0a, 0x09, 0x66, 0x65, 0x65, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x08, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x65, 0x4c, 0x69, + 0x6d, 0x69, 0x74, 0x52, 0x08, 0x66, 0x65, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x2c, 0x0a, + 0x10, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, + 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x0e, 0x6f, 0x75, 0x74, + 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x6c, + 0x61, 0x73, 0x74, 0x5f, 0x68, 0x6f, 0x70, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x0d, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x6c, 0x61, 0x73, 0x74, 0x48, 0x6f, 0x70, 0x50, 0x75, 0x62, + 0x6b, 0x65, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x6c, 0x69, 0x6d, 0x69, + 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x63, 0x6c, 0x74, 0x76, 0x4c, 0x69, 0x6d, + 0x69, 0x74, 0x12, 0x59, 0x0a, 0x13, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f, + 0x6d, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x29, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x2e, 0x44, 0x65, 0x73, 0x74, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, + 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x11, 0x64, 0x65, 0x73, 0x74, + 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x2c, 0x0a, + 0x12, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x73, 0x65, 0x6c, 0x66, 0x5f, 0x70, 0x61, 0x79, 0x6d, + 0x65, 0x6e, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x61, 0x6c, 0x6c, 0x6f, 0x77, + 0x53, 0x65, 0x6c, 0x66, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x36, 0x0a, 0x0d, 0x64, + 0x65, 0x73, 0x74, 0x5f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x0f, 0x20, 0x03, + 0x28, 0x0e, 0x32, 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x42, 0x69, 0x74, 0x52, 0x0c, 0x64, 0x65, 0x73, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x61, + 0x64, 0x64, 0x72, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, + 0x6e, 0x74, 0x41, 0x64, 0x64, 0x72, 0x1a, 0x44, 0x0a, 0x16, 0x44, 0x65, 0x73, 0x74, 0x43, 0x75, + 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, + 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xb4, 0x01, 0x0a, + 0x0c, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, + 0x0d, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x45, 0x72, 0x72, + 0x6f, 0x72, 0x12, 0x29, 0x0a, 0x10, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x70, 0x72, + 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x70, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x50, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x31, 0x0a, + 0x0d, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, + 0x74, 0x65, 0x52, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, + 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, + 0x61, 0x73, 0x68, 0x22, 0x95, 0x01, 0x0a, 0x12, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, + 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x32, 0x0a, + 0x13, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x5f, 0x73, 0x74, + 0x72, 0x69, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x11, + 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x53, 0x74, 0x72, 0x69, 0x6e, + 0x67, 0x12, 0x22, 0x0a, 0x05, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x0c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x05, + 0x72, 0x6f, 0x75, 0x74, 0x65, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x22, 0x9a, 0x04, 0x0a, 0x14, + 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x70, 0x75, 0x62, + 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x6e, 0x6f, 0x64, 0x65, 0x50, + 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x68, + 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x63, 0x68, 0x61, 0x69, 0x6e, + 0x48, 0x61, 0x73, 0x68, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, + 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x70, + 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, + 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x6d, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x0a, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x41, 0x6d, 0x74, 0x12, 0x19, 0x0a, + 0x08, 0x70, 0x75, 0x73, 0x68, 0x5f, 0x61, 0x6d, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x07, 0x70, 0x75, 0x73, 0x68, 0x41, 0x6d, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x75, 0x73, 0x74, + 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x64, 0x75, + 0x73, 0x74, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x2d, 0x0a, 0x13, 0x6d, 0x61, 0x78, 0x5f, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x69, 0x6e, 0x5f, 0x66, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x6d, 0x61, 0x78, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x49, 0x6e, + 0x46, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x5f, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x0e, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x12, + 0x19, 0x0a, 0x08, 0x6d, 0x69, 0x6e, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x18, 0x09, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x07, 0x6d, 0x69, 0x6e, 0x48, 0x74, 0x6c, 0x63, 0x12, 0x1c, 0x0a, 0x0a, 0x66, 0x65, + 0x65, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x6b, 0x77, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, + 0x66, 0x65, 0x65, 0x50, 0x65, 0x72, 0x4b, 0x77, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x73, 0x76, 0x5f, + 0x64, 0x65, 0x6c, 0x61, 0x79, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x63, 0x73, 0x76, + 0x44, 0x65, 0x6c, 0x61, 0x79, 0x12, 0x2c, 0x0a, 0x12, 0x6d, 0x61, 0x78, 0x5f, 0x61, 0x63, 0x63, + 0x65, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x10, 0x6d, 0x61, 0x78, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x48, 0x74, + 0x6c, 0x63, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x66, + 0x6c, 0x61, 0x67, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x12, 0x3e, 0x0a, 0x0f, 0x63, 0x6f, 0x6d, 0x6d, + 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, + 0x0e, 0x32, 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, + 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0e, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, + 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0xf3, 0x02, 0x0a, 0x15, 0x43, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x06, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x65, + 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, + 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x29, 0x0a, 0x10, 0x75, 0x70, 0x66, 0x72, + 0x6f, 0x6e, 0x74, 0x5f, 0x73, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0f, 0x75, 0x70, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x53, 0x68, 0x75, 0x74, 0x64, + 0x6f, 0x77, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x73, 0x76, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x63, 0x73, 0x76, 0x44, 0x65, 0x6c, 0x61, 0x79, + 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x53, 0x61, + 0x74, 0x12, 0x2b, 0x0a, 0x12, 0x69, 0x6e, 0x5f, 0x66, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x6d, + 0x61, 0x78, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x69, + 0x6e, 0x46, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x4d, 0x61, 0x78, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x24, + 0x0a, 0x0e, 0x6d, 0x61, 0x78, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x6d, 0x61, 0x78, 0x48, 0x74, 0x6c, 0x63, 0x43, + 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1e, 0x0a, 0x0b, 0x6d, 0x69, 0x6e, 0x5f, 0x68, 0x74, 0x6c, 0x63, + 0x5f, 0x69, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x6d, 0x69, 0x6e, 0x48, 0x74, + 0x6c, 0x63, 0x49, 0x6e, 0x12, 0x28, 0x0a, 0x10, 0x6d, 0x69, 0x6e, 0x5f, 0x61, 0x63, 0x63, 0x65, + 0x70, 0x74, 0x5f, 0x64, 0x65, 0x70, 0x74, 0x68, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, + 0x6d, 0x69, 0x6e, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x44, 0x65, 0x70, 0x74, 0x68, 0x22, 0x9d, + 0x01, 0x0a, 0x0c, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, + 0x2e, 0x0a, 0x12, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x5f, + 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x10, 0x66, + 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x78, 0x69, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, + 0x2a, 0x0a, 0x10, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x5f, + 0x73, 0x74, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0e, 0x66, 0x75, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x54, 0x78, 0x69, 0x64, 0x53, 0x74, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x6f, + 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x0b, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x42, 0x0e, + 0x0a, 0x0c, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x22, 0x67, + 0x0a, 0x08, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x78, + 0x69, 0x64, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, + 0x74, 0x78, 0x69, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x78, 0x69, + 0x64, 0x5f, 0x73, 0x74, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x74, 0x78, 0x69, + 0x64, 0x53, 0x74, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x69, + 0x6e, 0x64, 0x65, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x6f, 0x75, 0x74, 0x70, + 0x75, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x3e, 0x0a, 0x10, 0x4c, 0x69, 0x67, 0x68, 0x74, + 0x6e, 0x69, 0x6e, 0x67, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x70, + 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, + 0x6b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x22, 0x91, 0x02, 0x0a, 0x12, 0x45, 0x73, 0x74, 0x69, + 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4f, + 0x0a, 0x0c, 0x41, 0x64, 0x64, 0x72, 0x54, 0x6f, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x73, 0x74, + 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, + 0x41, 0x64, 0x64, 0x72, 0x54, 0x6f, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x52, 0x0c, 0x41, 0x64, 0x64, 0x72, 0x54, 0x6f, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, + 0x1f, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, + 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x2b, 0x0a, + 0x11, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x5f, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, + 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x55, + 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x1a, 0x3f, 0x0a, 0x11, 0x41, 0x64, + 0x64, 0x72, 0x54, 0x6f, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, + 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, + 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x87, 0x01, 0x0a, 0x13, + 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x66, 0x65, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x66, 0x65, 0x65, 0x53, 0x61, 0x74, 0x12, 0x33, 0x0a, 0x14, + 0x66, 0x65, 0x65, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, + 0x62, 0x79, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x11, + 0x66, 0x65, 0x65, 0x72, 0x61, 0x74, 0x65, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, + 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, + 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, + 0x56, 0x62, 0x79, 0x74, 0x65, 0x22, 0xeb, 0x02, 0x0a, 0x0f, 0x53, 0x65, 0x6e, 0x64, 0x4d, 0x61, + 0x6e, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4c, 0x0a, 0x0c, 0x41, 0x64, 0x64, + 0x72, 0x54, 0x6f, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x28, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x79, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x54, 0x6f, 0x41, 0x6d, + 0x6f, 0x75, 0x6e, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0c, 0x41, 0x64, 0x64, 0x72, 0x54, + 0x6f, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x74, 0x61, + 0x72, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x61, 0x74, 0x5f, + 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x0b, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x12, 0x24, 0x0a, 0x0c, + 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, + 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, + 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e, + 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x5f, 0x75, + 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x10, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x55, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, + 0x65, 0x64, 0x1a, 0x3f, 0x0a, 0x11, 0x41, 0x64, 0x64, 0x72, 0x54, 0x6f, 0x41, 0x6d, 0x6f, 0x75, + 0x6e, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, + 0x02, 0x38, 0x01, 0x22, 0x26, 0x0a, 0x10, 0x53, 0x65, 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x79, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x22, 0xa4, 0x02, 0x0a, 0x10, + 0x53, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x12, 0x0a, 0x04, 0x61, 0x64, 0x64, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x61, 0x64, 0x64, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, + 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x22, 0x0a, + 0x0d, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, + 0x65, 0x12, 0x24, 0x0a, 0x0c, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x74, + 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, 0x73, 0x61, 0x74, + 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x65, 0x6e, 0x64, 0x5f, + 0x61, 0x6c, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x65, 0x6e, 0x64, 0x41, + 0x6c, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, + 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e, + 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x5f, 0x75, + 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x10, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x55, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, + 0x65, 0x64, 0x22, 0x27, 0x0a, 0x11, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x22, 0x68, 0x0a, 0x12, 0x4c, + 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x1b, + 0x0a, 0x09, 0x6d, 0x61, 0x78, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x61, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x63, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x38, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, + 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x05, + 0x75, 0x74, 0x78, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x55, 0x74, 0x78, 0x6f, 0x52, 0x05, 0x75, 0x74, 0x78, 0x6f, 0x73, 0x22, + 0x55, 0x0a, 0x11, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, + 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x2e, 0x0a, 0x12, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, + 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x47, 0x0a, 0x12, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, + 0x6d, 0x73, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x12, 0x1f, + 0x0a, 0x0b, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x0a, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x48, 0x61, 0x73, 0x68, 0x22, + 0x33, 0x0a, 0x13, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, + 0x74, 0x75, 0x72, 0x65, 0x22, 0x46, 0x0a, 0x14, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, + 0x6d, 0x73, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x12, 0x1c, + 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x45, 0x0a, 0x15, + 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x70, + 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, + 0x6b, 0x65, 0x79, 0x22, 0x6f, 0x0a, 0x12, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, + 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x04, 0x61, 0x64, 0x64, + 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x4c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x52, 0x04, 0x61, 0x64, 0x64, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x65, 0x72, 0x6d, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x70, 0x65, 0x72, 0x6d, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x69, + 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x74, 0x69, 0x6d, + 0x65, 0x6f, 0x75, 0x74, 0x22, 0x15, 0x0a, 0x13, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, + 0x65, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x30, 0x0a, 0x15, 0x44, + 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x22, 0x18, 0x0a, + 0x16, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x86, 0x02, 0x0a, 0x04, 0x48, 0x54, 0x4c, 0x43, + 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x08, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x12, 0x16, 0x0a, 0x06, + 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, + 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x68, 0x61, 0x73, 0x68, 0x5f, 0x6c, 0x6f, 0x63, + 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x68, 0x61, 0x73, 0x68, 0x4c, 0x6f, 0x63, + 0x6b, 0x12, 0x2b, 0x0a, 0x11, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, + 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x65, 0x78, + 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x1d, + 0x0a, 0x0a, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x09, 0x68, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x2d, 0x0a, + 0x12, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x11, 0x66, 0x6f, 0x72, 0x77, 0x61, + 0x72, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x32, 0x0a, 0x15, + 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, + 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x13, 0x66, 0x6f, 0x72, + 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x64, 0x65, 0x78, + 0x22, 0x84, 0x02, 0x0a, 0x12, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x43, 0x6f, 0x6e, 0x73, + 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x73, 0x76, 0x5f, 0x64, + 0x65, 0x6c, 0x61, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x63, 0x73, 0x76, 0x44, + 0x65, 0x6c, 0x61, 0x79, 0x12, 0x28, 0x0a, 0x10, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x72, 0x65, 0x73, + 0x65, 0x72, 0x76, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, + 0x63, 0x68, 0x61, 0x6e, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x53, 0x61, 0x74, 0x12, 0x24, + 0x0a, 0x0e, 0x64, 0x75, 0x73, 0x74, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x5f, 0x73, 0x61, 0x74, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x64, 0x75, 0x73, 0x74, 0x4c, 0x69, 0x6d, 0x69, + 0x74, 0x53, 0x61, 0x74, 0x12, 0x2f, 0x0a, 0x14, 0x6d, 0x61, 0x78, 0x5f, 0x70, 0x65, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x11, 0x6d, 0x61, 0x78, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x41, 0x6d, + 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x69, 0x6e, 0x5f, 0x68, 0x74, 0x6c, + 0x63, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x6d, 0x69, + 0x6e, 0x48, 0x74, 0x6c, 0x63, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x6d, 0x61, 0x78, + 0x5f, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x6d, 0x61, 0x78, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, + 0x65, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x73, 0x22, 0xd7, 0x09, 0x0a, 0x07, 0x43, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x72, + 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x70, 0x6f, 0x69, 0x6e, - 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, - 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x68, 0x61, 0x73, 0x68, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x48, 0x61, 0x73, - 0x68, 0x12, 0x26, 0x0a, 0x0f, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x78, 0x5f, - 0x68, 0x61, 0x73, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x63, 0x6c, 0x6f, 0x73, - 0x69, 0x6e, 0x67, 0x54, 0x78, 0x48, 0x61, 0x73, 0x68, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x6d, - 0x6f, 0x74, 0x65, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0c, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x1a, - 0x0a, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6c, - 0x6f, 0x73, 0x65, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, - 0x52, 0x0b, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x27, 0x0a, - 0x0f, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, - 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x42, - 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6c, - 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x09, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x11, 0x74, 0x69, 0x6d, 0x65, 0x4c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x42, - 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x45, 0x0a, 0x0a, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, - 0x74, 0x79, 0x70, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x26, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x53, - 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x75, 0x72, 0x65, 0x54, 0x79, - 0x70, 0x65, 0x52, 0x09, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x37, 0x0a, - 0x0e, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x18, - 0x0b, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x10, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, - 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x0d, 0x6f, 0x70, 0x65, 0x6e, 0x49, 0x6e, 0x69, - 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x39, 0x0a, 0x0f, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, - 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0e, 0x32, - 0x10, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, - 0x72, 0x52, 0x0e, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, - 0x72, 0x12, 0x33, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, - 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x72, 0x65, 0x73, 0x6f, 0x6c, - 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x8a, 0x01, 0x0a, 0x0b, 0x43, 0x6c, 0x6f, 0x73, 0x75, - 0x72, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x15, 0x0a, 0x11, 0x43, 0x4f, 0x4f, 0x50, 0x45, 0x52, - 0x41, 0x54, 0x49, 0x56, 0x45, 0x5f, 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x10, 0x00, 0x12, 0x15, 0x0a, - 0x11, 0x4c, 0x4f, 0x43, 0x41, 0x4c, 0x5f, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x43, 0x4c, 0x4f, - 0x53, 0x45, 0x10, 0x01, 0x12, 0x16, 0x0a, 0x12, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x46, - 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x10, 0x02, 0x12, 0x10, 0x0a, 0x0c, - 0x42, 0x52, 0x45, 0x41, 0x43, 0x48, 0x5f, 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x10, 0x03, 0x12, 0x14, - 0x0a, 0x10, 0x46, 0x55, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x5f, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, - 0x45, 0x44, 0x10, 0x04, 0x12, 0x0d, 0x0a, 0x09, 0x41, 0x42, 0x41, 0x4e, 0x44, 0x4f, 0x4e, 0x45, - 0x44, 0x10, 0x05, 0x22, 0xeb, 0x01, 0x0a, 0x0a, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, - 0x6f, 0x6e, 0x12, 0x3e, 0x0a, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, - 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, - 0x70, 0x65, 0x52, 0x0e, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, - 0x70, 0x65, 0x12, 0x32, 0x0a, 0x07, 0x6f, 0x75, 0x74, 0x63, 0x6f, 0x6d, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x6f, - 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x75, 0x74, 0x63, 0x6f, 0x6d, 0x65, 0x52, 0x07, 0x6f, - 0x75, 0x74, 0x63, 0x6f, 0x6d, 0x65, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, - 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, - 0x69, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x73, 0x61, - 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x53, - 0x61, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x77, 0x65, 0x65, 0x70, 0x5f, 0x74, 0x78, 0x69, 0x64, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x77, 0x65, 0x65, 0x70, 0x54, 0x78, 0x69, - 0x64, 0x22, 0xde, 0x01, 0x0a, 0x15, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, - 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x0b, 0x63, - 0x6f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x76, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x0b, 0x63, 0x6f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x76, 0x65, 0x12, 0x1f, 0x0a, - 0x0b, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x0a, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x12, 0x21, - 0x0a, 0x0c, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x63, - 0x65, 0x12, 0x16, 0x0a, 0x06, 0x62, 0x72, 0x65, 0x61, 0x63, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x06, 0x62, 0x72, 0x65, 0x61, 0x63, 0x68, 0x12, 0x29, 0x0a, 0x10, 0x66, 0x75, 0x6e, - 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x65, 0x64, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x0f, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x61, 0x6e, 0x63, - 0x65, 0x6c, 0x65, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x62, 0x61, 0x6e, 0x64, 0x6f, 0x6e, 0x65, - 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x61, 0x62, 0x61, 0x6e, 0x64, 0x6f, 0x6e, - 0x65, 0x64, 0x22, 0x50, 0x0a, 0x16, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, - 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x08, - 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x43, 0x6c, - 0x6f, 0x73, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x08, 0x63, 0x68, 0x61, 0x6e, - 0x6e, 0x65, 0x6c, 0x73, 0x22, 0x8b, 0x05, 0x0a, 0x04, 0x50, 0x65, 0x65, 0x72, 0x12, 0x17, 0x0a, - 0x07, 0x70, 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, - 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x73, 0x65, 0x6e, 0x74, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x62, 0x79, 0x74, 0x65, 0x73, 0x53, 0x65, 0x6e, 0x74, 0x12, - 0x1d, 0x0a, 0x0a, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x72, 0x65, 0x63, 0x76, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x09, 0x62, 0x79, 0x74, 0x65, 0x73, 0x52, 0x65, 0x63, 0x76, 0x12, 0x19, - 0x0a, 0x08, 0x73, 0x61, 0x74, 0x5f, 0x73, 0x65, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x07, 0x73, 0x61, 0x74, 0x53, 0x65, 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x61, 0x74, - 0x5f, 0x72, 0x65, 0x63, 0x76, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x73, 0x61, 0x74, - 0x52, 0x65, 0x63, 0x76, 0x12, 0x18, 0x0a, 0x07, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x18, - 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x1b, - 0x0a, 0x09, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x08, 0x70, 0x69, 0x6e, 0x67, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x31, 0x0a, 0x09, 0x73, - 0x79, 0x6e, 0x63, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x14, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x2e, 0x53, 0x79, 0x6e, 0x63, - 0x54, 0x79, 0x70, 0x65, 0x52, 0x08, 0x73, 0x79, 0x6e, 0x63, 0x54, 0x79, 0x70, 0x65, 0x12, 0x35, - 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x2e, 0x46, 0x65, - 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x66, 0x65, 0x61, - 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x2f, 0x0a, 0x06, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x18, - 0x0c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x69, - 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x65, 0x64, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x06, - 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x66, 0x6c, 0x61, 0x70, 0x5f, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x66, 0x6c, 0x61, 0x70, - 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x20, 0x0a, 0x0c, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x66, 0x6c, - 0x61, 0x70, 0x5f, 0x6e, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x6c, 0x61, 0x73, - 0x74, 0x46, 0x6c, 0x61, 0x70, 0x4e, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6c, 0x61, 0x73, 0x74, 0x5f, - 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x0f, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x50, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x79, 0x6c, - 0x6f, 0x61, 0x64, 0x1a, 0x4b, 0x0a, 0x0d, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0d, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, - 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, - 0x22, 0x50, 0x0a, 0x08, 0x53, 0x79, 0x6e, 0x63, 0x54, 0x79, 0x70, 0x65, 0x12, 0x10, 0x0a, 0x0c, - 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x53, 0x59, 0x4e, 0x43, 0x10, 0x00, 0x12, 0x0f, - 0x0a, 0x0b, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x5f, 0x53, 0x59, 0x4e, 0x43, 0x10, 0x01, 0x12, - 0x10, 0x0a, 0x0c, 0x50, 0x41, 0x53, 0x53, 0x49, 0x56, 0x45, 0x5f, 0x53, 0x59, 0x4e, 0x43, 0x10, - 0x02, 0x12, 0x0f, 0x0a, 0x0b, 0x50, 0x49, 0x4e, 0x4e, 0x45, 0x44, 0x5f, 0x53, 0x59, 0x4e, 0x43, - 0x10, 0x03, 0x22, 0x46, 0x0a, 0x10, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x65, - 0x64, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, - 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, - 0x74, 0x61, 0x6d, 0x70, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x35, 0x0a, 0x10, 0x4c, 0x69, - 0x73, 0x74, 0x50, 0x65, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, - 0x0a, 0x0c, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x45, 0x72, 0x72, 0x6f, - 0x72, 0x22, 0x36, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x65, 0x72, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, - 0x65, 0x72, 0x52, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x22, 0x17, 0x0a, 0x15, 0x50, 0x65, 0x65, - 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, - 0x6f, 0x6e, 0x22, 0x84, 0x01, 0x0a, 0x09, 0x50, 0x65, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, + 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x12, 0x23, + 0x0a, 0x0d, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x42, 0x61, 0x6c, 0x61, + 0x6e, 0x63, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x62, 0x61, + 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x72, 0x65, 0x6d, + 0x6f, 0x74, 0x65, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, + 0x6d, 0x6d, 0x69, 0x74, 0x5f, 0x66, 0x65, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, + 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x46, 0x65, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x6d, + 0x6d, 0x69, 0x74, 0x5f, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0c, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x57, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x1c, + 0x0a, 0x0a, 0x66, 0x65, 0x65, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x6b, 0x77, 0x18, 0x0a, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x08, 0x66, 0x65, 0x65, 0x50, 0x65, 0x72, 0x4b, 0x77, 0x12, 0x2b, 0x0a, 0x11, + 0x75, 0x6e, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, + 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x75, 0x6e, 0x73, 0x65, 0x74, 0x74, 0x6c, + 0x65, 0x64, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x74, 0x6f, 0x74, + 0x61, 0x6c, 0x5f, 0x73, 0x61, 0x74, 0x6f, 0x73, 0x68, 0x69, 0x73, 0x5f, 0x73, 0x65, 0x6e, 0x74, + 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x53, 0x61, 0x74, + 0x6f, 0x73, 0x68, 0x69, 0x73, 0x53, 0x65, 0x6e, 0x74, 0x12, 0x36, 0x0a, 0x17, 0x74, 0x6f, 0x74, + 0x61, 0x6c, 0x5f, 0x73, 0x61, 0x74, 0x6f, 0x73, 0x68, 0x69, 0x73, 0x5f, 0x72, 0x65, 0x63, 0x65, + 0x69, 0x76, 0x65, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x74, 0x6f, 0x74, 0x61, + 0x6c, 0x53, 0x61, 0x74, 0x6f, 0x73, 0x68, 0x69, 0x73, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, + 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x6e, 0x75, 0x6d, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, + 0x18, 0x0e, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x6e, 0x75, 0x6d, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x73, 0x12, 0x30, 0x0a, 0x0d, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x74, + 0x6c, 0x63, 0x73, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x48, 0x54, 0x4c, 0x43, 0x52, 0x0c, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x48, + 0x74, 0x6c, 0x63, 0x73, 0x12, 0x1f, 0x0a, 0x09, 0x63, 0x73, 0x76, 0x5f, 0x64, 0x65, 0x6c, 0x61, + 0x79, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x08, 0x63, 0x73, 0x76, + 0x44, 0x65, 0x6c, 0x61, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, + 0x18, 0x11, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x12, + 0x1c, 0x0a, 0x09, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x12, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x09, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x2a, 0x0a, + 0x11, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x66, 0x6c, 0x61, + 0x67, 0x73, 0x18, 0x13, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x68, 0x61, 0x6e, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x12, 0x37, 0x0a, 0x16, 0x6c, 0x6f, 0x63, + 0x61, 0x6c, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x5f, + 0x73, 0x61, 0x74, 0x18, 0x14, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x13, 0x6c, + 0x6f, 0x63, 0x61, 0x6c, 0x43, 0x68, 0x61, 0x6e, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x53, + 0x61, 0x74, 0x12, 0x39, 0x0a, 0x17, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x63, 0x68, 0x61, + 0x6e, 0x5f, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x15, 0x20, + 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x14, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, + 0x68, 0x61, 0x6e, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x53, 0x61, 0x74, 0x12, 0x2e, 0x0a, + 0x11, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x5f, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x6b, + 0x65, 0x79, 0x18, 0x16, 0x20, 0x01, 0x28, 0x08, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0f, 0x73, 0x74, + 0x61, 0x74, 0x69, 0x63, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x3e, 0x0a, + 0x0f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, + 0x18, 0x1a, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, + 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0e, 0x63, + 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, + 0x08, 0x6c, 0x69, 0x66, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x17, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x08, 0x6c, 0x69, 0x66, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x70, 0x74, + 0x69, 0x6d, 0x65, 0x18, 0x18, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x70, 0x74, 0x69, 0x6d, + 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x18, 0x19, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x41, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x75, 0x73, 0x68, 0x5f, 0x61, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x0d, 0x70, 0x75, 0x73, 0x68, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x61, 0x74, 0x12, 0x1f, + 0x0a, 0x0b, 0x74, 0x68, 0x61, 0x77, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x1c, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x74, 0x68, 0x61, 0x77, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, + 0x46, 0x0a, 0x11, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, + 0x69, 0x6e, 0x74, 0x73, 0x18, 0x1d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, + 0x61, 0x69, 0x6e, 0x74, 0x73, 0x52, 0x10, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x43, 0x6f, 0x6e, 0x73, + 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x48, 0x0a, 0x12, 0x72, 0x65, 0x6d, 0x6f, 0x74, + 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x1e, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x73, 0x52, 0x11, + 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, + 0x73, 0x22, 0xb3, 0x01, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x63, 0x74, + 0x69, 0x76, 0x65, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, + 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e, + 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x0c, 0x69, 0x6e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x4f, 0x6e, 0x6c, 0x79, 0x12, + 0x1f, 0x0a, 0x0b, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4f, 0x6e, 0x6c, 0x79, + 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4f, + 0x6e, 0x6c, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x65, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x04, 0x70, 0x65, 0x65, 0x72, 0x22, 0x42, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x2a, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x52, 0x08, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x22, 0xd8, 0x05, 0x0a, 0x13, + 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x53, 0x75, 0x6d, 0x6d, + 0x61, 0x72, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x70, + 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x06, 0x63, + 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x68, + 0x61, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x68, 0x61, 0x69, 0x6e, + 0x48, 0x61, 0x73, 0x68, 0x12, 0x26, 0x0a, 0x0f, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x5f, + 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x63, + 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x54, 0x78, 0x48, 0x61, 0x73, 0x68, 0x12, 0x23, 0x0a, 0x0d, + 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x75, 0x62, 0x6b, 0x65, + 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x12, 0x21, 0x0a, + 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, + 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x6c, 0x61, + 0x6e, 0x63, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x73, 0x65, 0x74, 0x74, 0x6c, + 0x65, 0x64, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x74, 0x69, 0x6d, + 0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, + 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x74, 0x69, 0x6d, 0x65, 0x4c, 0x6f, 0x63, 0x6b, + 0x65, 0x64, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x45, 0x0a, 0x0a, 0x63, 0x6c, 0x6f, + 0x73, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x26, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x43, 0x6c, 0x6f, + 0x73, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x75, 0x72, + 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x09, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x37, 0x0a, 0x0e, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, + 0x6f, 0x72, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x10, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x0d, 0x6f, 0x70, 0x65, 0x6e, + 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x39, 0x0a, 0x0f, 0x63, 0x6c, 0x6f, + 0x73, 0x65, 0x5f, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x0c, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x10, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x69, + 0x61, 0x74, 0x6f, 0x72, 0x52, 0x0e, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x49, 0x6e, 0x69, 0x74, 0x69, + 0x61, 0x74, 0x6f, 0x72, 0x12, 0x33, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x72, 0x65, + 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x8a, 0x01, 0x0a, 0x0b, 0x43, 0x6c, + 0x6f, 0x73, 0x75, 0x72, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x15, 0x0a, 0x11, 0x43, 0x4f, 0x4f, + 0x50, 0x45, 0x52, 0x41, 0x54, 0x49, 0x56, 0x45, 0x5f, 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x10, 0x00, + 0x12, 0x15, 0x0a, 0x11, 0x4c, 0x4f, 0x43, 0x41, 0x4c, 0x5f, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, + 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x10, 0x01, 0x12, 0x16, 0x0a, 0x12, 0x52, 0x45, 0x4d, 0x4f, 0x54, + 0x45, 0x5f, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x10, 0x02, 0x12, + 0x10, 0x0a, 0x0c, 0x42, 0x52, 0x45, 0x41, 0x43, 0x48, 0x5f, 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x10, + 0x03, 0x12, 0x14, 0x0a, 0x10, 0x46, 0x55, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x5f, 0x43, 0x41, 0x4e, + 0x43, 0x45, 0x4c, 0x45, 0x44, 0x10, 0x04, 0x12, 0x0d, 0x0a, 0x09, 0x41, 0x42, 0x41, 0x4e, 0x44, + 0x4f, 0x4e, 0x45, 0x44, 0x10, 0x05, 0x22, 0xeb, 0x01, 0x0a, 0x0a, 0x52, 0x65, 0x73, 0x6f, 0x6c, + 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3e, 0x0a, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, + 0x6e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0e, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, + 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x32, 0x0a, 0x07, 0x6f, 0x75, 0x74, 0x63, 0x6f, 0x6d, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, + 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x75, 0x74, 0x63, 0x6f, 0x6d, 0x65, + 0x52, 0x07, 0x6f, 0x75, 0x74, 0x63, 0x6f, 0x6d, 0x65, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75, 0x74, + 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, + 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, + 0x5f, 0x73, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x61, 0x6d, 0x6f, 0x75, + 0x6e, 0x74, 0x53, 0x61, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x77, 0x65, 0x65, 0x70, 0x5f, 0x74, + 0x78, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x77, 0x65, 0x65, 0x70, + 0x54, 0x78, 0x69, 0x64, 0x22, 0xde, 0x01, 0x0a, 0x15, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, + 0x0a, 0x0b, 0x63, 0x6f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x76, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x0b, 0x63, 0x6f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x76, 0x65, + 0x12, 0x1f, 0x0a, 0x0b, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x46, 0x6f, 0x72, 0x63, + 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x66, 0x6f, 0x72, 0x63, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x46, + 0x6f, 0x72, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x62, 0x72, 0x65, 0x61, 0x63, 0x68, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x62, 0x72, 0x65, 0x61, 0x63, 0x68, 0x12, 0x29, 0x0a, 0x10, + 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x65, 0x64, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, + 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x65, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x62, 0x61, 0x6e, 0x64, + 0x6f, 0x6e, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x61, 0x62, 0x61, 0x6e, + 0x64, 0x6f, 0x6e, 0x65, 0x64, 0x22, 0x50, 0x0a, 0x16, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x36, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x08, 0x63, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x22, 0x8b, 0x05, 0x0a, 0x04, 0x50, 0x65, 0x65, 0x72, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x2e, 0x0a, 0x04, 0x74, 0x79, 0x70, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x50, 0x65, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, - 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x2e, 0x0a, 0x09, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0f, 0x0a, 0x0b, 0x50, 0x45, 0x45, 0x52, 0x5f, 0x4f, - 0x4e, 0x4c, 0x49, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x50, 0x45, 0x45, 0x52, 0x5f, - 0x4f, 0x46, 0x46, 0x4c, 0x49, 0x4e, 0x45, 0x10, 0x01, 0x22, 0x10, 0x0a, 0x0e, 0x47, 0x65, 0x74, - 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x87, 0x06, 0x0a, 0x0f, - 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, 0x6d, - 0x6d, 0x69, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, - 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x27, 0x0a, 0x0f, 0x69, 0x64, - 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0e, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x50, 0x75, 0x62, - 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x05, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x6c, - 0x6f, 0x72, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x12, - 0x30, 0x0a, 0x14, 0x6e, 0x75, 0x6d, 0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, - 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x12, 0x6e, - 0x75, 0x6d, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x6e, 0x75, 0x6d, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, - 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11, - 0x6e, 0x75, 0x6d, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x73, 0x12, 0x32, 0x0a, 0x15, 0x6e, 0x75, 0x6d, 0x5f, 0x69, 0x6e, 0x61, 0x63, 0x74, 0x69, 0x76, - 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0d, - 0x52, 0x13, 0x6e, 0x75, 0x6d, 0x49, 0x6e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x43, 0x68, 0x61, - 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x75, 0x6d, 0x5f, 0x70, 0x65, 0x65, - 0x72, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x6e, 0x75, 0x6d, 0x50, 0x65, 0x65, - 0x72, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, - 0x68, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, - 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, - 0x61, 0x73, 0x68, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b, - 0x48, 0x61, 0x73, 0x68, 0x12, 0x32, 0x0a, 0x15, 0x62, 0x65, 0x73, 0x74, 0x5f, 0x68, 0x65, 0x61, - 0x64, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x0d, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x13, 0x62, 0x65, 0x73, 0x74, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x54, - 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x26, 0x0a, 0x0f, 0x73, 0x79, 0x6e, 0x63, - 0x65, 0x64, 0x5f, 0x74, 0x6f, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x0d, 0x73, 0x79, 0x6e, 0x63, 0x65, 0x64, 0x54, 0x6f, 0x43, 0x68, 0x61, 0x69, 0x6e, - 0x12, 0x26, 0x0a, 0x0f, 0x73, 0x79, 0x6e, 0x63, 0x65, 0x64, 0x5f, 0x74, 0x6f, 0x5f, 0x67, 0x72, - 0x61, 0x70, 0x68, 0x18, 0x12, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x73, 0x79, 0x6e, 0x63, 0x65, - 0x64, 0x54, 0x6f, 0x47, 0x72, 0x61, 0x70, 0x68, 0x12, 0x1c, 0x0a, 0x07, 0x74, 0x65, 0x73, 0x74, - 0x6e, 0x65, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x42, 0x02, 0x18, 0x01, 0x52, 0x07, 0x74, - 0x65, 0x73, 0x74, 0x6e, 0x65, 0x74, 0x12, 0x24, 0x0a, 0x06, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x73, - 0x18, 0x10, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, - 0x68, 0x61, 0x69, 0x6e, 0x52, 0x06, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x12, 0x0a, 0x04, - 0x75, 0x72, 0x69, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x75, 0x72, 0x69, 0x73, - 0x12, 0x40, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x13, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, - 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, - 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, - 0x65, 0x73, 0x1a, 0x4b, 0x0a, 0x0d, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, - 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x61, - 0x74, 0x75, 0x72, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x4a, - 0x04, 0x08, 0x0b, 0x10, 0x0c, 0x22, 0x18, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x6f, - 0x76, 0x65, 0x72, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, - 0x87, 0x01, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x49, - 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x72, - 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x0c, 0x72, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x4d, 0x6f, 0x64, 0x65, - 0x12, 0x2b, 0x0a, 0x11, 0x72, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x5f, 0x66, 0x69, 0x6e, - 0x69, 0x73, 0x68, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x72, 0x65, 0x63, - 0x6f, 0x76, 0x65, 0x72, 0x79, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x12, 0x1a, 0x0a, - 0x08, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, - 0x08, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x22, 0x37, 0x0a, 0x05, 0x43, 0x68, 0x61, - 0x69, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x05, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77, - 0x6f, 0x72, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, - 0x72, 0x6b, 0x22, 0x7a, 0x0a, 0x12, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x62, 0x6c, 0x6f, 0x63, - 0x6b, 0x5f, 0x73, 0x68, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x62, 0x6c, 0x6f, - 0x63, 0x6b, 0x53, 0x68, 0x61, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, - 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x62, 0x6c, 0x6f, - 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x6e, 0x75, 0x6d, 0x5f, - 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x5f, 0x6c, 0x65, 0x66, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, - 0x52, 0x0c, 0x6e, 0x75, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x4c, 0x65, 0x66, 0x74, 0x22, 0x4d, - 0x0a, 0x11, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x4f, 0x70, 0x65, 0x6e, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x12, 0x38, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x70, - 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, - 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x22, 0x51, 0x0a, - 0x12, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x74, - 0x78, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x63, 0x6c, 0x6f, 0x73, 0x69, - 0x6e, 0x67, 0x54, 0x78, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, - 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, - 0x22, 0xfb, 0x01, 0x0a, 0x13, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x38, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, - 0x6e, 0x65, 0x6c, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, - 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, - 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, - 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x74, - 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x24, 0x0a, 0x0c, 0x73, 0x61, 0x74, - 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x42, - 0x02, 0x18, 0x01, 0x52, 0x0a, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x12, - 0x29, 0x0a, 0x10, 0x64, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x79, 0x5f, 0x61, 0x64, 0x64, 0x72, - 0x65, 0x73, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x64, 0x65, 0x6c, 0x69, 0x76, - 0x65, 0x72, 0x79, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x61, - 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x0b, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x22, 0x96, - 0x01, 0x0a, 0x11, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x12, 0x3b, 0x0a, 0x0d, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x70, 0x65, - 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x48, 0x00, 0x52, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, - 0x67, 0x12, 0x3a, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, + 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x73, 0x65, 0x6e, + 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x62, 0x79, 0x74, 0x65, 0x73, 0x53, 0x65, + 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x72, 0x65, 0x63, 0x76, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x62, 0x79, 0x74, 0x65, 0x73, 0x52, 0x65, 0x63, + 0x76, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x61, 0x74, 0x5f, 0x73, 0x65, 0x6e, 0x74, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x07, 0x73, 0x61, 0x74, 0x53, 0x65, 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, + 0x73, 0x61, 0x74, 0x5f, 0x72, 0x65, 0x63, 0x76, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, + 0x73, 0x61, 0x74, 0x52, 0x65, 0x63, 0x76, 0x12, 0x18, 0x0a, 0x07, 0x69, 0x6e, 0x62, 0x6f, 0x75, + 0x6e, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, + 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x09, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x70, 0x69, 0x6e, 0x67, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x31, + 0x0a, 0x09, 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, + 0x0e, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x2e, 0x53, + 0x79, 0x6e, 0x63, 0x54, 0x79, 0x70, 0x65, 0x52, 0x08, 0x73, 0x79, 0x6e, 0x63, 0x54, 0x79, 0x70, + 0x65, 0x12, 0x35, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x0b, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x65, 0x72, + 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, + 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x2f, 0x0a, 0x06, 0x65, 0x72, 0x72, 0x6f, + 0x72, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x65, 0x64, 0x45, 0x72, 0x72, 0x6f, + 0x72, 0x52, 0x06, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x66, 0x6c, 0x61, + 0x70, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x66, + 0x6c, 0x61, 0x70, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x20, 0x0a, 0x0c, 0x6c, 0x61, 0x73, 0x74, + 0x5f, 0x66, 0x6c, 0x61, 0x70, 0x5f, 0x6e, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, + 0x6c, 0x61, 0x73, 0x74, 0x46, 0x6c, 0x61, 0x70, 0x4e, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6c, 0x61, + 0x73, 0x74, 0x5f, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, + 0x0f, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x50, 0x69, 0x6e, 0x67, 0x50, + 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x1a, 0x4b, 0x0a, 0x0d, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, + 0x02, 0x38, 0x01, 0x22, 0x50, 0x0a, 0x08, 0x53, 0x79, 0x6e, 0x63, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x10, 0x0a, 0x0c, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x53, 0x59, 0x4e, 0x43, 0x10, + 0x00, 0x12, 0x0f, 0x0a, 0x0b, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x5f, 0x53, 0x59, 0x4e, 0x43, + 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x50, 0x41, 0x53, 0x53, 0x49, 0x56, 0x45, 0x5f, 0x53, 0x59, + 0x4e, 0x43, 0x10, 0x02, 0x12, 0x0f, 0x0a, 0x0b, 0x50, 0x49, 0x4e, 0x4e, 0x45, 0x44, 0x5f, 0x53, + 0x59, 0x4e, 0x43, 0x10, 0x03, 0x22, 0x46, 0x0a, 0x10, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x65, 0x64, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x74, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x35, 0x0a, + 0x10, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, + 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x45, + 0x72, 0x72, 0x6f, 0x72, 0x22, 0x36, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x65, 0x72, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x05, 0x70, 0x65, 0x65, + 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x50, 0x65, 0x65, 0x72, 0x52, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x22, 0x17, 0x0a, 0x15, + 0x50, 0x65, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x84, 0x01, 0x0a, 0x09, 0x50, 0x65, 0x65, 0x72, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x2e, 0x0a, 0x04, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x2e, 0x0a, 0x09, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0f, 0x0a, 0x0b, 0x50, 0x45, 0x45, + 0x52, 0x5f, 0x4f, 0x4e, 0x4c, 0x49, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x50, 0x45, + 0x45, 0x52, 0x5f, 0x4f, 0x46, 0x46, 0x4c, 0x49, 0x4e, 0x45, 0x10, 0x01, 0x22, 0x10, 0x0a, 0x0e, + 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xc1, + 0x06, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0e, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, + 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x14, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x27, 0x0a, + 0x0f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, + 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x14, 0x0a, 0x05, + 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x6f, 0x6c, + 0x6f, 0x72, 0x12, 0x30, 0x0a, 0x14, 0x6e, 0x75, 0x6d, 0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x12, 0x6e, 0x75, 0x6d, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x6e, 0x75, 0x6d, 0x5f, 0x61, 0x63, 0x74, 0x69, + 0x76, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x11, 0x6e, 0x75, 0x6d, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x43, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x6e, 0x75, 0x6d, 0x5f, 0x69, 0x6e, 0x61, 0x63, + 0x74, 0x69, 0x76, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x0f, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x13, 0x6e, 0x75, 0x6d, 0x49, 0x6e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, + 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x75, 0x6d, 0x5f, + 0x70, 0x65, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x6e, 0x75, 0x6d, + 0x50, 0x65, 0x65, 0x72, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, + 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x62, 0x6c, 0x6f, + 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x63, + 0x6b, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, 0x6c, + 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x12, 0x32, 0x0a, 0x15, 0x62, 0x65, 0x73, 0x74, 0x5f, + 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x62, 0x65, 0x73, 0x74, 0x48, 0x65, 0x61, 0x64, + 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x26, 0x0a, 0x0f, 0x73, + 0x79, 0x6e, 0x63, 0x65, 0x64, 0x5f, 0x74, 0x6f, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x09, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x73, 0x79, 0x6e, 0x63, 0x65, 0x64, 0x54, 0x6f, 0x43, 0x68, + 0x61, 0x69, 0x6e, 0x12, 0x26, 0x0a, 0x0f, 0x73, 0x79, 0x6e, 0x63, 0x65, 0x64, 0x5f, 0x74, 0x6f, + 0x5f, 0x67, 0x72, 0x61, 0x70, 0x68, 0x18, 0x12, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x73, 0x79, + 0x6e, 0x63, 0x65, 0x64, 0x54, 0x6f, 0x47, 0x72, 0x61, 0x70, 0x68, 0x12, 0x1c, 0x0a, 0x07, 0x74, + 0x65, 0x73, 0x74, 0x6e, 0x65, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x42, 0x02, 0x18, 0x01, + 0x52, 0x07, 0x74, 0x65, 0x73, 0x74, 0x6e, 0x65, 0x74, 0x12, 0x24, 0x0a, 0x06, 0x63, 0x68, 0x61, + 0x69, 0x6e, 0x73, 0x18, 0x10, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x52, 0x06, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x73, 0x12, + 0x12, 0x0a, 0x04, 0x75, 0x72, 0x69, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x75, + 0x72, 0x69, 0x73, 0x12, 0x40, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, + 0x13, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, + 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x46, 0x65, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x66, 0x65, 0x61, + 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x38, 0x0a, 0x18, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, + 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x6f, + 0x72, 0x18, 0x15, 0x20, 0x01, 0x28, 0x08, 0x52, 0x16, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, + 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x6f, 0x72, 0x1a, + 0x4b, 0x0a, 0x0d, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x6b, + 0x65, 0x79, 0x12, 0x24, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x4a, 0x04, 0x08, 0x0b, + 0x10, 0x0c, 0x22, 0x18, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, + 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x87, 0x01, 0x0a, + 0x17, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x49, 0x6e, 0x66, 0x6f, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x63, 0x6f, + 0x76, 0x65, 0x72, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x0c, 0x72, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x2b, 0x0a, + 0x11, 0x72, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x5f, 0x66, 0x69, 0x6e, 0x69, 0x73, 0x68, + 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x72, 0x65, 0x63, 0x6f, 0x76, 0x65, + 0x72, 0x79, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, + 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x08, 0x70, 0x72, + 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x22, 0x37, 0x0a, 0x05, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x12, + 0x14, 0x0a, 0x05, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x63, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x22, + 0x7a, 0x0a, 0x12, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x73, + 0x68, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x53, + 0x68, 0x61, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, + 0x68, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, + 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x6e, 0x75, 0x6d, 0x5f, 0x63, 0x6f, 0x6e, + 0x66, 0x73, 0x5f, 0x6c, 0x65, 0x66, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x6e, + 0x75, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x4c, 0x65, 0x66, 0x74, 0x22, 0x4d, 0x0a, 0x11, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x4f, 0x70, 0x65, 0x6e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x12, 0x38, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x70, 0x6f, 0x69, 0x6e, + 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x0c, 0x63, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x22, 0x51, 0x0a, 0x12, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x48, 0x00, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x42, 0x08, 0x0a, - 0x06, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x22, 0x46, 0x0a, 0x0d, 0x50, 0x65, 0x6e, 0x64, 0x69, - 0x6e, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x12, 0x21, 0x0a, 0x0c, - 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0d, 0x52, 0x0b, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, - 0x79, 0x0a, 0x13, 0x52, 0x65, 0x61, 0x64, 0x79, 0x46, 0x6f, 0x72, 0x50, 0x73, 0x62, 0x74, 0x46, - 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x27, 0x0a, 0x0f, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, - 0x67, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0e, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, - 0x25, 0x0a, 0x0e, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, - 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, - 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x73, 0x62, 0x74, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x70, 0x73, 0x62, 0x74, 0x22, 0xf3, 0x01, 0x0a, 0x17, 0x42, - 0x61, 0x74, 0x63, 0x68, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x33, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x52, 0x08, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x74, - 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x22, 0x0a, 0x0d, - 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0b, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, - 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x2b, 0x0a, - 0x11, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x5f, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, - 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x55, - 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, - 0x62, 0x65, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, - 0x22, 0xf5, 0x02, 0x0a, 0x10, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x70, 0x75, - 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x6e, 0x6f, 0x64, 0x65, - 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x14, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, + 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x78, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x54, + 0x78, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x22, 0xfb, 0x01, + 0x0a, 0x13, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x38, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, + 0x74, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, + 0x14, 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, + 0x66, 0x6f, 0x72, 0x63, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, + 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, + 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x24, 0x0a, 0x0c, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, + 0x72, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, + 0x52, 0x0a, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x12, 0x29, 0x0a, 0x10, + 0x64, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x79, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x64, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x79, + 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x61, 0x74, 0x5f, 0x70, + 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, + 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x22, 0x96, 0x01, 0x0a, 0x11, + 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x12, 0x3b, 0x0a, 0x0d, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69, + 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x48, 0x00, + 0x52, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x3a, + 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x48, 0x00, 0x52, + 0x09, 0x63, 0x68, 0x61, 0x6e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x42, 0x08, 0x0a, 0x06, 0x75, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x22, 0x46, 0x0a, 0x0d, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x6f, 0x75, 0x74, + 0x70, 0x75, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x0b, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x79, 0x0a, 0x13, + 0x52, 0x65, 0x61, 0x64, 0x79, 0x46, 0x6f, 0x72, 0x50, 0x73, 0x62, 0x74, 0x46, 0x75, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x12, 0x27, 0x0a, 0x0f, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x61, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x66, 0x75, + 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x46, 0x75, 0x6e, 0x64, 0x69, - 0x6e, 0x67, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x70, 0x75, 0x73, 0x68, - 0x5f, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x70, 0x75, 0x73, 0x68, - 0x53, 0x61, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x12, 0x22, 0x0a, - 0x0d, 0x6d, 0x69, 0x6e, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x6d, 0x69, 0x6e, 0x48, 0x74, 0x6c, 0x63, 0x4d, 0x73, 0x61, - 0x74, 0x12, 0x28, 0x0a, 0x10, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x63, 0x73, 0x76, 0x5f, - 0x64, 0x65, 0x6c, 0x61, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x72, 0x65, 0x6d, - 0x6f, 0x74, 0x65, 0x43, 0x73, 0x76, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x63, - 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, - 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x70, 0x65, 0x6e, 0x64, 0x69, - 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x3e, 0x0a, 0x0f, 0x63, 0x6f, 0x6d, 0x6d, - 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, - 0x0e, 0x32, 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, - 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0e, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, - 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0x5b, 0x0a, 0x18, 0x42, 0x61, 0x74, 0x63, - 0x68, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x10, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, - 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x52, 0x0f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, - 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x22, 0x80, 0x06, 0x0a, 0x12, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x22, 0x0a, 0x0d, - 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, - 0x12, 0x1f, 0x0a, 0x0b, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x6e, 0x6f, 0x64, 0x65, 0x50, 0x75, 0x62, 0x6b, 0x65, - 0x79, 0x12, 0x30, 0x0a, 0x12, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, - 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, - 0x01, 0x52, 0x10, 0x6e, 0x6f, 0x64, 0x65, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x53, 0x74, 0x72, - 0x69, 0x6e, 0x67, 0x12, 0x30, 0x0a, 0x14, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x66, 0x75, 0x6e, - 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x41, 0x6d, 0x6f, + 0x75, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x73, 0x62, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x04, 0x70, 0x73, 0x62, 0x74, 0x22, 0xf3, 0x01, 0x0a, 0x17, 0x42, 0x61, 0x74, 0x63, + 0x68, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x33, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x61, + 0x74, 0x63, 0x68, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x08, + 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, + 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x74, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x61, 0x74, + 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0b, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x12, 0x1b, 0x0a, + 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x73, 0x70, + 0x65, 0x6e, 0x64, 0x5f, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x55, 0x6e, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x22, 0xf5, 0x02, + 0x0a, 0x10, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x6e, 0x6f, 0x64, 0x65, 0x50, 0x75, 0x62, + 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x14, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x66, 0x75, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x70, 0x75, 0x73, 0x68, 0x5f, 0x73, 0x61, - 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x70, 0x75, 0x73, 0x68, 0x53, 0x61, 0x74, - 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6e, - 0x66, 0x12, 0x24, 0x0a, 0x0c, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x74, - 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, 0x73, 0x61, 0x74, - 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, - 0x74, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, - 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x69, 0x6e, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x73, - 0x61, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x6d, 0x69, 0x6e, 0x48, 0x74, 0x6c, - 0x63, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, - 0x63, 0x73, 0x76, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, - 0x0e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x73, 0x76, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x12, - 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x0b, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x2b, 0x0a, 0x11, - 0x73, 0x70, 0x65, 0x6e, 0x64, 0x5f, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, - 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x55, 0x6e, - 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6c, 0x6f, - 0x73, 0x65, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x35, - 0x0a, 0x0c, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x68, 0x69, 0x6d, 0x18, 0x0e, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, - 0x64, 0x69, 0x6e, 0x67, 0x53, 0x68, 0x69, 0x6d, 0x52, 0x0b, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, - 0x67, 0x53, 0x68, 0x69, 0x6d, 0x12, 0x43, 0x0a, 0x1f, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, - 0x6d, 0x61, 0x78, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x69, 0x6e, 0x5f, 0x66, 0x6c, 0x69, - 0x67, 0x68, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x04, 0x52, 0x1a, - 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4d, 0x61, 0x78, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x49, 0x6e, - 0x46, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x72, 0x65, - 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x18, 0x10, - 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4d, 0x61, 0x78, 0x48, - 0x74, 0x6c, 0x63, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x61, 0x78, 0x5f, 0x6c, 0x6f, 0x63, 0x61, - 0x6c, 0x5f, 0x63, 0x73, 0x76, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x6d, 0x61, 0x78, - 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x43, 0x73, 0x76, 0x12, 0x3e, 0x0a, 0x0f, 0x63, 0x6f, 0x6d, 0x6d, - 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x12, 0x20, 0x01, 0x28, - 0x0e, 0x32, 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, - 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0e, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, - 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0xf3, 0x01, 0x0a, 0x10, 0x4f, 0x70, 0x65, - 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x39, 0x0a, - 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, - 0x69, 0x6e, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x48, 0x00, 0x52, 0x0b, 0x63, 0x68, 0x61, - 0x6e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x37, 0x0a, 0x09, 0x63, 0x68, 0x61, 0x6e, - 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x4f, 0x70, 0x65, 0x6e, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x48, 0x00, 0x52, 0x08, 0x63, 0x68, 0x61, 0x6e, 0x4f, 0x70, 0x65, - 0x6e, 0x12, 0x39, 0x0a, 0x09, 0x70, 0x73, 0x62, 0x74, 0x5f, 0x66, 0x75, 0x6e, 0x64, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x61, - 0x64, 0x79, 0x46, 0x6f, 0x72, 0x50, 0x73, 0x62, 0x74, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, - 0x48, 0x00, 0x52, 0x08, 0x70, 0x73, 0x62, 0x74, 0x46, 0x75, 0x6e, 0x64, 0x12, 0x26, 0x0a, 0x0f, - 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, - 0x61, 0x6e, 0x49, 0x64, 0x42, 0x08, 0x0a, 0x06, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x22, 0x48, - 0x0a, 0x0a, 0x4b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x1d, 0x0a, 0x0a, - 0x6b, 0x65, 0x79, 0x5f, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x09, 0x6b, 0x65, 0x79, 0x46, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x6b, - 0x65, 0x79, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, - 0x6b, 0x65, 0x79, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x5f, 0x0a, 0x0d, 0x4b, 0x65, 0x79, 0x44, - 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x22, 0x0a, 0x0d, 0x72, 0x61, 0x77, - 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x0b, 0x72, 0x61, 0x77, 0x4b, 0x65, 0x79, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x2a, 0x0a, - 0x07, 0x6b, 0x65, 0x79, 0x5f, 0x6c, 0x6f, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x6f, - 0x72, 0x52, 0x06, 0x6b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x22, 0xf0, 0x01, 0x0a, 0x0d, 0x43, 0x68, - 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x53, 0x68, 0x69, 0x6d, 0x12, 0x10, 0x0a, 0x03, 0x61, - 0x6d, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x61, 0x6d, 0x74, 0x12, 0x32, 0x0a, - 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, - 0x74, 0x12, 0x31, 0x0a, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, - 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x61, - 0x6c, 0x4b, 0x65, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x6b, - 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, - 0x4b, 0x65, 0x79, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, - 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x70, 0x65, - 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x74, - 0x68, 0x61, 0x77, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, - 0x52, 0x0a, 0x74, 0x68, 0x61, 0x77, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x6e, 0x0a, 0x08, - 0x50, 0x73, 0x62, 0x74, 0x53, 0x68, 0x69, 0x6d, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x65, 0x6e, 0x64, - 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x0d, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, - 0x12, 0x1b, 0x0a, 0x09, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x70, 0x73, 0x62, 0x74, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x08, 0x62, 0x61, 0x73, 0x65, 0x50, 0x73, 0x62, 0x74, 0x12, 0x1d, 0x0a, - 0x0a, 0x6e, 0x6f, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x09, 0x6e, 0x6f, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x22, 0x85, 0x01, 0x0a, - 0x0b, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x68, 0x69, 0x6d, 0x12, 0x3e, 0x0a, 0x0f, - 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x73, 0x68, 0x69, 0x6d, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, - 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x53, 0x68, 0x69, 0x6d, 0x48, 0x00, 0x52, 0x0d, 0x63, - 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x53, 0x68, 0x69, 0x6d, 0x12, 0x2e, 0x0a, 0x09, - 0x70, 0x73, 0x62, 0x74, 0x5f, 0x73, 0x68, 0x69, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x73, 0x62, 0x74, 0x53, 0x68, 0x69, 0x6d, - 0x48, 0x00, 0x52, 0x08, 0x70, 0x73, 0x62, 0x74, 0x53, 0x68, 0x69, 0x6d, 0x42, 0x06, 0x0a, 0x04, - 0x73, 0x68, 0x69, 0x6d, 0x22, 0x3b, 0x0a, 0x11, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, - 0x68, 0x69, 0x6d, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x65, 0x6e, - 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x70, 0x75, 0x73, 0x68, 0x53, 0x61, 0x74, + 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x69, + 0x6e, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0b, 0x6d, 0x69, 0x6e, 0x48, 0x74, 0x6c, 0x63, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x28, + 0x0a, 0x10, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x63, 0x73, 0x76, 0x5f, 0x64, 0x65, 0x6c, + 0x61, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, + 0x43, 0x73, 0x76, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6c, 0x6f, 0x73, + 0x65, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x26, 0x0a, + 0x0f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, + 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x3e, 0x0a, 0x0f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, + 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, + 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0e, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, + 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0x5b, 0x0a, 0x18, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x70, + 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x3f, 0x0a, 0x10, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x52, 0x0f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x73, 0x22, 0x80, 0x06, 0x0a, 0x12, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x61, 0x74, + 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x0b, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x12, 0x1f, 0x0a, + 0x0b, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x0a, 0x6e, 0x6f, 0x64, 0x65, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x30, + 0x0a, 0x12, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x5f, 0x73, 0x74, + 0x72, 0x69, 0x6e, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x10, + 0x6e, 0x6f, 0x64, 0x65, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, + 0x12, 0x30, 0x0a, 0x14, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, + 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x41, 0x6d, 0x6f, 0x75, + 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x70, 0x75, 0x73, 0x68, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x70, 0x75, 0x73, 0x68, 0x53, 0x61, 0x74, 0x12, 0x1f, 0x0a, + 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x24, + 0x0a, 0x0c, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, + 0x42, 0x79, 0x74, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x18, + 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x12, 0x22, + 0x0a, 0x0d, 0x6d, 0x69, 0x6e, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, + 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x6d, 0x69, 0x6e, 0x48, 0x74, 0x6c, 0x63, 0x4d, 0x73, + 0x61, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x63, 0x73, 0x76, + 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x72, 0x65, + 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x73, 0x76, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x12, 0x1b, 0x0a, 0x09, + 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x08, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x73, 0x70, 0x65, + 0x6e, 0x64, 0x5f, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x18, 0x0c, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x55, 0x6e, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, + 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, + 0x6c, 0x6f, 0x73, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x35, 0x0a, 0x0c, 0x66, + 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x68, 0x69, 0x6d, 0x18, 0x0e, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x53, 0x68, 0x69, 0x6d, 0x52, 0x0b, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x68, + 0x69, 0x6d, 0x12, 0x43, 0x0a, 0x1f, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x78, + 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x69, 0x6e, 0x5f, 0x66, 0x6c, 0x69, 0x67, 0x68, 0x74, + 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x04, 0x52, 0x1a, 0x72, 0x65, 0x6d, + 0x6f, 0x74, 0x65, 0x4d, 0x61, 0x78, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x49, 0x6e, 0x46, 0x6c, 0x69, + 0x67, 0x68, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x72, 0x65, 0x6d, 0x6f, 0x74, + 0x65, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x18, 0x10, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x0e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4d, 0x61, 0x78, 0x48, 0x74, 0x6c, 0x63, + 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x61, 0x78, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x63, + 0x73, 0x76, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x4c, 0x6f, 0x63, + 0x61, 0x6c, 0x43, 0x73, 0x76, 0x12, 0x3e, 0x0a, 0x0f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, + 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, + 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0e, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, + 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0xf3, 0x01, 0x0a, 0x10, 0x4f, 0x70, 0x65, 0x6e, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x39, 0x0a, 0x0c, 0x63, 0x68, + 0x61, 0x6e, 0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x48, 0x00, 0x52, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x65, + 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x37, 0x0a, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x6f, 0x70, + 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x4f, 0x70, 0x65, 0x6e, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x48, 0x00, 0x52, 0x08, 0x63, 0x68, 0x61, 0x6e, 0x4f, 0x70, 0x65, 0x6e, 0x12, 0x39, + 0x0a, 0x09, 0x70, 0x73, 0x62, 0x74, 0x5f, 0x66, 0x75, 0x6e, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x79, 0x46, + 0x6f, 0x72, 0x50, 0x73, 0x62, 0x74, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x00, 0x52, + 0x08, 0x70, 0x73, 0x62, 0x74, 0x46, 0x75, 0x6e, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x65, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, - 0x64, 0x22, 0x81, 0x01, 0x0a, 0x11, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x73, 0x62, - 0x74, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x75, 0x6e, 0x64, 0x65, - 0x64, 0x5f, 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x66, 0x75, - 0x6e, 0x64, 0x65, 0x64, 0x50, 0x73, 0x62, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x65, 0x6e, 0x64, - 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x0d, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, - 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, - 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x73, 0x6b, 0x69, 0x70, 0x46, 0x69, 0x6e, - 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x22, 0x80, 0x01, 0x0a, 0x13, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, - 0x67, 0x50, 0x73, 0x62, 0x74, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x12, 0x1f, 0x0a, - 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x0a, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x50, 0x73, 0x62, 0x74, 0x12, 0x26, - 0x0a, 0x0f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, - 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x20, 0x0a, 0x0c, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x5f, - 0x72, 0x61, 0x77, 0x5f, 0x74, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x66, 0x69, - 0x6e, 0x61, 0x6c, 0x52, 0x61, 0x77, 0x54, 0x78, 0x22, 0x99, 0x02, 0x0a, 0x14, 0x46, 0x75, 0x6e, - 0x64, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x73, - 0x67, 0x12, 0x39, 0x0a, 0x0d, 0x73, 0x68, 0x69, 0x6d, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, - 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x68, 0x69, 0x6d, 0x48, 0x00, 0x52, 0x0c, - 0x73, 0x68, 0x69, 0x6d, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x3b, 0x0a, 0x0b, - 0x73, 0x68, 0x69, 0x6d, 0x5f, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, - 0x67, 0x53, 0x68, 0x69, 0x6d, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x48, 0x00, 0x52, 0x0a, 0x73, - 0x68, 0x69, 0x6d, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x12, 0x3b, 0x0a, 0x0b, 0x70, 0x73, 0x62, - 0x74, 0x5f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x73, - 0x62, 0x74, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x48, 0x00, 0x52, 0x0a, 0x70, 0x73, 0x62, 0x74, - 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x12, 0x41, 0x0a, 0x0d, 0x70, 0x73, 0x62, 0x74, 0x5f, 0x66, - 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x73, 0x62, - 0x74, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x48, 0x00, 0x52, 0x0c, 0x70, 0x73, 0x62, - 0x74, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x42, 0x09, 0x0a, 0x07, 0x74, 0x72, 0x69, - 0x67, 0x67, 0x65, 0x72, 0x22, 0x16, 0x0a, 0x14, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, - 0x74, 0x61, 0x74, 0x65, 0x53, 0x74, 0x65, 0x70, 0x52, 0x65, 0x73, 0x70, 0x22, 0xcc, 0x01, 0x0a, - 0x0b, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x54, 0x4c, 0x43, 0x12, 0x1a, 0x0a, 0x08, - 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, - 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, - 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, - 0x12, 0x1a, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x27, 0x0a, 0x0f, - 0x6d, 0x61, 0x74, 0x75, 0x72, 0x69, 0x74, 0x79, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x6d, 0x61, 0x74, 0x75, 0x72, 0x69, 0x74, 0x79, 0x48, - 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x5f, - 0x74, 0x69, 0x6c, 0x5f, 0x6d, 0x61, 0x74, 0x75, 0x72, 0x69, 0x74, 0x79, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x11, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x54, 0x69, 0x6c, 0x4d, 0x61, 0x74, - 0x75, 0x72, 0x69, 0x74, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x22, 0x18, 0x0a, 0x16, 0x50, + 0x64, 0x42, 0x08, 0x0a, 0x06, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x22, 0x48, 0x0a, 0x0a, 0x4b, + 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x6b, 0x65, 0x79, + 0x5f, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x6b, + 0x65, 0x79, 0x46, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x6b, 0x65, 0x79, 0x5f, + 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6b, 0x65, 0x79, + 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x5f, 0x0a, 0x0d, 0x4b, 0x65, 0x79, 0x44, 0x65, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x22, 0x0a, 0x0d, 0x72, 0x61, 0x77, 0x5f, 0x6b, 0x65, + 0x79, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x72, + 0x61, 0x77, 0x4b, 0x65, 0x79, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x2a, 0x0a, 0x07, 0x6b, 0x65, + 0x79, 0x5f, 0x6c, 0x6f, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x06, + 0x6b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x22, 0xf0, 0x01, 0x0a, 0x0d, 0x43, 0x68, 0x61, 0x6e, 0x50, + 0x6f, 0x69, 0x6e, 0x74, 0x53, 0x68, 0x69, 0x6d, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x6d, 0x74, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x61, 0x6d, 0x74, 0x12, 0x32, 0x0a, 0x0a, 0x63, 0x68, + 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, + 0x69, 0x6e, 0x74, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x31, + 0x0a, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x44, 0x65, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x4b, 0x65, + 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4b, 0x65, 0x79, + 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, + 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x70, 0x65, 0x6e, 0x64, 0x69, + 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x68, 0x61, 0x77, + 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x74, + 0x68, 0x61, 0x77, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x6e, 0x0a, 0x08, 0x50, 0x73, 0x62, + 0x74, 0x53, 0x68, 0x69, 0x6d, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, + 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, + 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1b, 0x0a, + 0x09, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x70, 0x73, 0x62, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x08, 0x62, 0x61, 0x73, 0x65, 0x50, 0x73, 0x62, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x6e, 0x6f, + 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, + 0x6e, 0x6f, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x22, 0x85, 0x01, 0x0a, 0x0b, 0x46, 0x75, + 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x68, 0x69, 0x6d, 0x12, 0x3e, 0x0a, 0x0f, 0x63, 0x68, 0x61, + 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x73, 0x68, 0x69, 0x6d, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x50, + 0x6f, 0x69, 0x6e, 0x74, 0x53, 0x68, 0x69, 0x6d, 0x48, 0x00, 0x52, 0x0d, 0x63, 0x68, 0x61, 0x6e, + 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x53, 0x68, 0x69, 0x6d, 0x12, 0x2e, 0x0a, 0x09, 0x70, 0x73, 0x62, + 0x74, 0x5f, 0x73, 0x68, 0x69, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x73, 0x62, 0x74, 0x53, 0x68, 0x69, 0x6d, 0x48, 0x00, 0x52, + 0x08, 0x70, 0x73, 0x62, 0x74, 0x53, 0x68, 0x69, 0x6d, 0x42, 0x06, 0x0a, 0x04, 0x73, 0x68, 0x69, + 0x6d, 0x22, 0x3b, 0x0a, 0x11, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x68, 0x69, 0x6d, + 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x0d, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x22, 0x81, + 0x01, 0x0a, 0x11, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x73, 0x62, 0x74, 0x56, 0x65, + 0x72, 0x69, 0x66, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, + 0x73, 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x66, 0x75, 0x6e, 0x64, 0x65, + 0x64, 0x50, 0x73, 0x62, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, + 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, + 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x23, 0x0a, + 0x0d, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x73, 0x6b, 0x69, 0x70, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, + 0x7a, 0x65, 0x22, 0x80, 0x01, 0x0a, 0x13, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x73, + 0x62, 0x74, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x69, + 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x0a, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x50, 0x73, 0x62, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x70, + 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, + 0x6e, 0x49, 0x64, 0x12, 0x20, 0x0a, 0x0c, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x72, 0x61, 0x77, + 0x5f, 0x74, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x66, 0x69, 0x6e, 0x61, 0x6c, + 0x52, 0x61, 0x77, 0x54, 0x78, 0x22, 0x99, 0x02, 0x0a, 0x14, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x73, 0x67, 0x12, 0x39, + 0x0a, 0x0d, 0x73, 0x68, 0x69, 0x6d, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, + 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x68, 0x69, 0x6d, 0x48, 0x00, 0x52, 0x0c, 0x73, 0x68, 0x69, + 0x6d, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x3b, 0x0a, 0x0b, 0x73, 0x68, 0x69, + 0x6d, 0x5f, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x68, + 0x69, 0x6d, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x48, 0x00, 0x52, 0x0a, 0x73, 0x68, 0x69, 0x6d, + 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x12, 0x3b, 0x0a, 0x0b, 0x70, 0x73, 0x62, 0x74, 0x5f, 0x76, + 0x65, 0x72, 0x69, 0x66, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x73, 0x62, 0x74, 0x56, + 0x65, 0x72, 0x69, 0x66, 0x79, 0x48, 0x00, 0x52, 0x0a, 0x70, 0x73, 0x62, 0x74, 0x56, 0x65, 0x72, + 0x69, 0x66, 0x79, 0x12, 0x41, 0x0a, 0x0d, 0x70, 0x73, 0x62, 0x74, 0x5f, 0x66, 0x69, 0x6e, 0x61, + 0x6c, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x73, 0x62, 0x74, 0x46, 0x69, + 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x48, 0x00, 0x52, 0x0c, 0x70, 0x73, 0x62, 0x74, 0x46, 0x69, + 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x42, 0x09, 0x0a, 0x07, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, + 0x72, 0x22, 0x16, 0x0a, 0x14, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, + 0x65, 0x53, 0x74, 0x65, 0x70, 0x52, 0x65, 0x73, 0x70, 0x22, 0xcc, 0x01, 0x0a, 0x0b, 0x50, 0x65, + 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x54, 0x4c, 0x43, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x63, + 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x6e, 0x63, + 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1a, 0x0a, + 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x6d, 0x61, 0x74, + 0x75, 0x72, 0x69, 0x74, 0x79, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x0e, 0x6d, 0x61, 0x74, 0x75, 0x72, 0x69, 0x74, 0x79, 0x48, 0x65, 0x69, 0x67, + 0x68, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x5f, 0x74, 0x69, 0x6c, + 0x5f, 0x6d, 0x61, 0x74, 0x75, 0x72, 0x69, 0x74, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x11, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x54, 0x69, 0x6c, 0x4d, 0x61, 0x74, 0x75, 0x72, 0x69, + 0x74, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x22, 0x18, 0x0a, 0x16, 0x50, 0x65, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x22, 0xf3, 0x12, 0x0a, 0x17, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, + 0x0a, 0x13, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x6c, 0x69, 0x6d, 0x62, 0x6f, 0x5f, 0x62, 0x61, + 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x74, 0x6f, 0x74, + 0x61, 0x6c, 0x4c, 0x69, 0x6d, 0x62, 0x6f, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x65, + 0x0a, 0x15, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x63, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x50, 0x65, + 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x52, 0x13, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x6a, 0x0a, 0x18, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, + 0x5f, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x02, 0x18, 0x01, 0x52, 0x16, 0x70, 0x65, 0x6e, 0x64, 0x69, + 0x6e, 0x67, 0x43, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x73, 0x12, 0x76, 0x0a, 0x1e, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x6f, 0x72, + 0x63, 0x65, 0x5f, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x43, + 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x1b, 0x70, 0x65, + 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x43, 0x6c, 0x6f, 0x73, 0x69, 0x6e, + 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x68, 0x0a, 0x16, 0x77, 0x61, 0x69, + 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x57, 0x61, 0x69, 0x74, 0x69, 0x6e, + 0x67, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x14, 0x77, + 0x61, 0x69, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x73, 0x1a, 0x9f, 0x04, 0x0a, 0x0e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x26, 0x0a, 0x0f, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, + 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x70, 0x75, 0x62, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0d, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x50, 0x75, 0x62, 0x12, 0x23, + 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, + 0x69, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x12, + 0x23, 0x0a, 0x0d, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x42, 0x61, 0x6c, + 0x61, 0x6e, 0x63, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x62, + 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x72, 0x65, + 0x6d, 0x6f, 0x74, 0x65, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x33, 0x0a, 0x16, 0x6c, + 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, + 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x6c, 0x6f, 0x63, + 0x61, 0x6c, 0x43, 0x68, 0x61, 0x6e, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x53, 0x61, 0x74, + 0x12, 0x35, 0x0a, 0x17, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, + 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x14, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x52, 0x65, 0x73, + 0x65, 0x72, 0x76, 0x65, 0x53, 0x61, 0x74, 0x12, 0x2e, 0x0a, 0x09, 0x69, 0x6e, 0x69, 0x74, 0x69, + 0x61, 0x74, 0x6f, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x10, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x09, 0x69, 0x6e, + 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x3e, 0x0a, 0x0f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, + 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, + 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0e, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, + 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x36, 0x0a, 0x17, 0x6e, 0x75, 0x6d, 0x5f, 0x66, + 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, + 0x65, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x6e, 0x75, 0x6d, 0x46, 0x6f, 0x72, + 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x12, + 0x2a, 0x0a, 0x11, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x66, + 0x6c, 0x61, 0x67, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x68, 0x61, 0x6e, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x70, + 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x70, 0x72, + 0x69, 0x76, 0x61, 0x74, 0x65, 0x1a, 0xc5, 0x01, 0x0a, 0x12, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x47, 0x0a, 0x07, + 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x50, 0x65, + 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x07, 0x63, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x5f, + 0x66, 0x65, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x69, + 0x74, 0x46, 0x65, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x5f, 0x77, + 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x63, 0x6f, 0x6d, + 0x6d, 0x69, 0x74, 0x57, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x1c, 0x0a, 0x0a, 0x66, 0x65, 0x65, + 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x6b, 0x77, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x66, + 0x65, 0x65, 0x50, 0x65, 0x72, 0x4b, 0x77, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x1a, 0xf4, 0x01, + 0x0a, 0x13, 0x57, 0x61, 0x69, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x47, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xb5, 0x12, 0x0a, 0x17, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, - 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x6c, 0x69, 0x6d, 0x62, 0x6f, - 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, - 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x4c, 0x69, 0x6d, 0x62, 0x6f, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, - 0x65, 0x12, 0x65, 0x0a, 0x15, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x70, 0x65, - 0x6e, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x31, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, - 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, - 0x6e, 0x65, 0x6c, 0x52, 0x13, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x70, 0x65, 0x6e, - 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x6a, 0x0a, 0x18, 0x70, 0x65, 0x6e, 0x64, - 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, - 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, - 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x02, 0x18, 0x01, 0x52, 0x16, 0x70, 0x65, - 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, - 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x76, 0x0a, 0x1e, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, - 0x66, 0x6f, 0x72, 0x63, 0x65, 0x5f, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x6c, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x23, + 0x0a, 0x0d, 0x6c, 0x69, 0x6d, 0x62, 0x6f, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x6c, 0x69, 0x6d, 0x62, 0x6f, 0x42, 0x61, 0x6c, 0x61, + 0x6e, 0x63, 0x65, 0x12, 0x4c, 0x0a, 0x0b, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, + 0x74, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, + 0x65, 0x6e, 0x74, 0x73, 0x52, 0x0b, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, + 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x78, 0x69, + 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, + 0x54, 0x78, 0x69, 0x64, 0x1a, 0xa3, 0x02, 0x0a, 0x0b, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, + 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x74, 0x78, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x54, + 0x78, 0x69, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x74, 0x78, + 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, + 0x54, 0x78, 0x69, 0x64, 0x12, 0x2e, 0x0a, 0x13, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x70, + 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x11, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, + 0x54, 0x78, 0x69, 0x64, 0x12, 0x2f, 0x0a, 0x14, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x63, 0x6f, + 0x6d, 0x6d, 0x69, 0x74, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x11, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x46, + 0x65, 0x65, 0x53, 0x61, 0x74, 0x12, 0x31, 0x0a, 0x15, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, + 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x12, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x6f, 0x6d, 0x6d, + 0x69, 0x74, 0x46, 0x65, 0x65, 0x53, 0x61, 0x74, 0x12, 0x40, 0x0a, 0x1d, 0x72, 0x65, 0x6d, 0x6f, + 0x74, 0x65, 0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, + 0x74, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x19, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x6f, + 0x6d, 0x6d, 0x69, 0x74, 0x46, 0x65, 0x65, 0x53, 0x61, 0x74, 0x1a, 0x7b, 0x0a, 0x0d, 0x43, 0x6c, + 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x47, 0x0a, 0x07, 0x63, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, - 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x46, 0x6f, 0x72, - 0x63, 0x65, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, - 0x1b, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x43, 0x6c, 0x6f, - 0x73, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x68, 0x0a, 0x16, - 0x77, 0x61, 0x69, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x63, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, - 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x57, 0x61, 0x69, - 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x52, 0x14, 0x77, 0x61, 0x69, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x1a, 0xd9, 0x03, 0x0a, 0x0e, 0x50, 0x65, 0x6e, 0x64, 0x69, - 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x26, 0x0a, 0x0f, 0x72, 0x65, 0x6d, - 0x6f, 0x74, 0x65, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x70, 0x75, 0x62, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0d, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x50, 0x75, - 0x62, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x70, 0x6f, 0x69, - 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, - 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, - 0x74, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x62, 0x61, 0x6c, 0x61, - 0x6e, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x6c, 0x6f, 0x63, 0x61, 0x6c, - 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x72, 0x65, 0x6d, 0x6f, 0x74, - 0x65, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0d, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x33, - 0x0a, 0x16, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x72, 0x65, 0x73, - 0x65, 0x72, 0x76, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, - 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x43, 0x68, 0x61, 0x6e, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, - 0x53, 0x61, 0x74, 0x12, 0x35, 0x0a, 0x17, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x63, 0x68, - 0x61, 0x6e, 0x5f, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x14, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, - 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x53, 0x61, 0x74, 0x12, 0x2e, 0x0a, 0x09, 0x69, 0x6e, - 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x10, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x52, - 0x09, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x3e, 0x0a, 0x0f, 0x63, 0x6f, - 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, - 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0e, 0x63, 0x6f, 0x6d, 0x6d, - 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x36, 0x0a, 0x17, 0x6e, 0x75, - 0x6d, 0x5f, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x61, 0x63, - 0x6b, 0x61, 0x67, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x6e, 0x75, 0x6d, - 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, - 0x65, 0x73, 0x1a, 0xf0, 0x01, 0x0a, 0x12, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x70, - 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x47, 0x0a, 0x07, 0x63, 0x68, 0x61, - 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, - 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x6e, - 0x65, 0x6c, 0x12, 0x2f, 0x0a, 0x13, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, - 0x12, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x65, 0x69, - 0x67, 0x68, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x5f, 0x66, 0x65, - 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x46, - 0x65, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x5f, 0x77, 0x65, 0x69, - 0x67, 0x68, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x63, 0x6f, 0x6d, 0x6d, 0x69, - 0x74, 0x57, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x1c, 0x0a, 0x0a, 0x66, 0x65, 0x65, 0x5f, 0x70, - 0x65, 0x72, 0x5f, 0x6b, 0x77, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x66, 0x65, 0x65, - 0x50, 0x65, 0x72, 0x4b, 0x77, 0x1a, 0xd1, 0x01, 0x0a, 0x13, 0x57, 0x61, 0x69, 0x74, 0x69, 0x6e, - 0x67, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x47, 0x0a, - 0x07, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, + 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x50, 0x65, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x07, 0x63, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x5f, + 0x74, 0x78, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6c, 0x6f, 0x73, + 0x69, 0x6e, 0x67, 0x54, 0x78, 0x69, 0x64, 0x1a, 0xee, 0x03, 0x0a, 0x12, 0x46, 0x6f, 0x72, 0x63, + 0x65, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x47, + 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x2d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, + 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x07, + 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x69, + 0x6e, 0x67, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, + 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x54, 0x78, 0x69, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x6c, 0x69, + 0x6d, 0x62, 0x6f, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0c, 0x6c, 0x69, 0x6d, 0x62, 0x6f, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, + 0x27, 0x0a, 0x0f, 0x6d, 0x61, 0x74, 0x75, 0x72, 0x69, 0x74, 0x79, 0x5f, 0x68, 0x65, 0x69, 0x67, + 0x68, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x6d, 0x61, 0x74, 0x75, 0x72, 0x69, + 0x74, 0x79, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x62, 0x6c, 0x6f, 0x63, + 0x6b, 0x73, 0x5f, 0x74, 0x69, 0x6c, 0x5f, 0x6d, 0x61, 0x74, 0x75, 0x72, 0x69, 0x74, 0x79, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x11, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x54, 0x69, 0x6c, + 0x4d, 0x61, 0x74, 0x75, 0x72, 0x69, 0x74, 0x79, 0x12, 0x2b, 0x0a, 0x11, 0x72, 0x65, 0x63, 0x6f, + 0x76, 0x65, 0x72, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x10, 0x72, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x65, 0x64, 0x42, 0x61, + 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x37, 0x0a, 0x0d, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, + 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x54, 0x4c, 0x43, + 0x52, 0x0c, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x74, 0x6c, 0x63, 0x73, 0x12, 0x55, + 0x0a, 0x06, 0x61, 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x3d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x50, - 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x07, 0x63, - 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x6c, 0x69, 0x6d, 0x62, 0x6f, 0x5f, - 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x6c, - 0x69, 0x6d, 0x62, 0x6f, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x4c, 0x0a, 0x0b, 0x63, - 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x2a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, - 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x0b, 0x63, 0x6f, - 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x1a, 0xa3, 0x02, 0x0a, 0x0b, 0x43, 0x6f, - 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x6f, 0x63, - 0x61, 0x6c, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6c, - 0x6f, 0x63, 0x61, 0x6c, 0x54, 0x78, 0x69, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x6d, 0x6f, - 0x74, 0x65, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, - 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x54, 0x78, 0x69, 0x64, 0x12, 0x2e, 0x0a, 0x13, 0x72, 0x65, 0x6d, - 0x6f, 0x74, 0x65, 0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x78, 0x69, 0x64, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, - 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x78, 0x69, 0x64, 0x12, 0x2f, 0x0a, 0x14, 0x6c, 0x6f, 0x63, - 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x73, 0x61, - 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x11, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x43, 0x6f, - 0x6d, 0x6d, 0x69, 0x74, 0x46, 0x65, 0x65, 0x53, 0x61, 0x74, 0x12, 0x31, 0x0a, 0x15, 0x72, 0x65, - 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x5f, 0x66, 0x65, 0x65, 0x5f, - 0x73, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x12, 0x72, 0x65, 0x6d, 0x6f, 0x74, - 0x65, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x46, 0x65, 0x65, 0x53, 0x61, 0x74, 0x12, 0x40, 0x0a, - 0x1d, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, - 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x19, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x6e, 0x64, - 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x46, 0x65, 0x65, 0x53, 0x61, 0x74, 0x1a, - 0x7b, 0x0a, 0x0d, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x12, 0x47, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x2d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, - 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x52, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6c, 0x6f, - 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0b, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x54, 0x78, 0x69, 0x64, 0x1a, 0xee, 0x03, 0x0a, - 0x12, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, - 0x6e, 0x65, 0x6c, 0x12, 0x47, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, - 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, - 0x6e, 0x65, 0x6c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x21, 0x0a, 0x0c, - 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x54, 0x78, 0x69, 0x64, 0x12, - 0x23, 0x0a, 0x0d, 0x6c, 0x69, 0x6d, 0x62, 0x6f, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x6c, 0x69, 0x6d, 0x62, 0x6f, 0x42, 0x61, 0x6c, - 0x61, 0x6e, 0x63, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x6d, 0x61, 0x74, 0x75, 0x72, 0x69, 0x74, 0x79, - 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x6d, - 0x61, 0x74, 0x75, 0x72, 0x69, 0x74, 0x79, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x2e, 0x0a, - 0x13, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x5f, 0x74, 0x69, 0x6c, 0x5f, 0x6d, 0x61, 0x74, 0x75, - 0x72, 0x69, 0x74, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x11, 0x62, 0x6c, 0x6f, 0x63, - 0x6b, 0x73, 0x54, 0x69, 0x6c, 0x4d, 0x61, 0x74, 0x75, 0x72, 0x69, 0x74, 0x79, 0x12, 0x2b, 0x0a, - 0x11, 0x72, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, - 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x72, 0x65, 0x63, 0x6f, 0x76, 0x65, - 0x72, 0x65, 0x64, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x37, 0x0a, 0x0d, 0x70, 0x65, - 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, - 0x67, 0x48, 0x54, 0x4c, 0x43, 0x52, 0x0c, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x74, - 0x6c, 0x63, 0x73, 0x12, 0x55, 0x0a, 0x06, 0x61, 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x18, 0x09, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x3d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, - 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x2e, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, - 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x2e, 0x41, 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x53, 0x74, 0x61, - 0x74, 0x65, 0x52, 0x06, 0x61, 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x22, 0x31, 0x0a, 0x0b, 0x41, 0x6e, - 0x63, 0x68, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x09, 0x0a, 0x05, 0x4c, 0x49, 0x4d, - 0x42, 0x4f, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x52, 0x45, 0x43, 0x4f, 0x56, 0x45, 0x52, 0x45, - 0x44, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x4c, 0x4f, 0x53, 0x54, 0x10, 0x02, 0x22, 0x1a, 0x0a, - 0x18, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x75, 0x62, - 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xff, 0x04, 0x0a, 0x12, 0x43, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x12, 0x33, 0x0a, 0x0c, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, - 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x48, 0x00, 0x52, 0x0b, 0x6f, 0x70, 0x65, 0x6e, 0x43, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x43, 0x0a, 0x0e, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x5f, - 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x43, 0x6c, 0x6f, - 0x73, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x48, 0x00, 0x52, 0x0d, 0x63, 0x6c, 0x6f, - 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x3c, 0x0a, 0x0e, 0x61, 0x63, - 0x74, 0x69, 0x76, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, - 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0d, 0x61, 0x63, 0x74, 0x69, 0x76, - 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x40, 0x0a, 0x10, 0x69, 0x6e, 0x61, 0x63, - 0x74, 0x69, 0x76, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, - 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0f, 0x69, 0x6e, 0x61, 0x63, 0x74, - 0x69, 0x76, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x48, 0x0a, 0x14, 0x70, 0x65, - 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, - 0x65, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x48, 0x00, - 0x52, 0x12, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, - 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x4b, 0x0a, 0x16, 0x66, 0x75, 0x6c, 0x6c, 0x79, 0x5f, 0x72, 0x65, - 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x64, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, - 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x14, 0x66, 0x75, 0x6c, - 0x6c, 0x79, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x12, 0x38, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, - 0x24, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x92, 0x01, 0x0a, 0x0a, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x10, 0x0a, 0x0c, 0x4f, 0x50, - 0x45, 0x4e, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, - 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x44, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x10, 0x01, - 0x12, 0x12, 0x0a, 0x0e, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, - 0x45, 0x4c, 0x10, 0x02, 0x12, 0x14, 0x0a, 0x10, 0x49, 0x4e, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, - 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x10, 0x03, 0x12, 0x18, 0x0a, 0x14, 0x50, 0x45, - 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x5f, 0x4f, 0x50, 0x45, 0x4e, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, - 0x45, 0x4c, 0x10, 0x04, 0x12, 0x1a, 0x0a, 0x16, 0x46, 0x55, 0x4c, 0x4c, 0x59, 0x5f, 0x52, 0x45, - 0x53, 0x4f, 0x4c, 0x56, 0x45, 0x44, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x10, 0x05, - 0x42, 0x09, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x22, 0x74, 0x0a, 0x14, 0x57, - 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x61, 0x6c, 0x61, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x46, + 0x6f, 0x72, 0x63, 0x65, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x2e, 0x41, 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x06, 0x61, + 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x22, 0x31, 0x0a, 0x0b, 0x41, 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x53, + 0x74, 0x61, 0x74, 0x65, 0x12, 0x09, 0x0a, 0x05, 0x4c, 0x49, 0x4d, 0x42, 0x4f, 0x10, 0x00, 0x12, + 0x0d, 0x0a, 0x09, 0x52, 0x45, 0x43, 0x4f, 0x56, 0x45, 0x52, 0x45, 0x44, 0x10, 0x01, 0x12, 0x08, + 0x0a, 0x04, 0x4c, 0x4f, 0x53, 0x54, 0x10, 0x02, 0x22, 0x1a, 0x0a, 0x18, 0x43, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xff, 0x04, 0x0a, 0x12, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x33, 0x0a, 0x0c, 0x6f, + 0x70, 0x65, 0x6e, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x48, 0x00, 0x52, 0x0b, 0x6f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x12, 0x43, 0x0a, 0x0e, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x53, 0x75, 0x6d, + 0x6d, 0x61, 0x72, 0x79, 0x48, 0x00, 0x52, 0x0d, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x3c, 0x0a, 0x0e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, + 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, + 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0d, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x43, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x12, 0x40, 0x0a, 0x10, 0x69, 0x6e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, + 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, + 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0f, 0x69, 0x6e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x43, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x48, 0x0a, 0x14, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, + 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x48, 0x00, 0x52, 0x12, 0x70, 0x65, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, + 0x4b, 0x0a, 0x16, 0x66, 0x75, 0x6c, 0x6c, 0x79, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, + 0x64, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, + 0x6f, 0x69, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x14, 0x66, 0x75, 0x6c, 0x6c, 0x79, 0x52, 0x65, 0x73, + 0x6f, 0x6c, 0x76, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x38, 0x0a, 0x04, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x24, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, + 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x92, 0x01, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x10, 0x0a, 0x0c, 0x4f, 0x50, 0x45, 0x4e, 0x5f, 0x43, 0x48, + 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x43, 0x4c, 0x4f, 0x53, 0x45, + 0x44, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x10, 0x01, 0x12, 0x12, 0x0a, 0x0e, 0x41, + 0x43, 0x54, 0x49, 0x56, 0x45, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x10, 0x02, 0x12, + 0x14, 0x0a, 0x10, 0x49, 0x4e, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x5f, 0x43, 0x48, 0x41, 0x4e, + 0x4e, 0x45, 0x4c, 0x10, 0x03, 0x12, 0x18, 0x0a, 0x14, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, + 0x5f, 0x4f, 0x50, 0x45, 0x4e, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x10, 0x04, 0x12, + 0x1a, 0x0a, 0x16, 0x46, 0x55, 0x4c, 0x4c, 0x59, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x4c, 0x56, 0x45, + 0x44, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x10, 0x05, 0x42, 0x09, 0x0a, 0x07, 0x63, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x22, 0x74, 0x0a, 0x14, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x2b, + 0x0a, 0x11, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x6c, 0x61, + 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x72, 0x6d, 0x65, 0x64, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x2f, 0x0a, 0x13, 0x75, + 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, + 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x72, 0x6d, 0x65, 0x64, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x22, 0x16, 0x0a, 0x14, + 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x22, 0xfc, 0x02, 0x0a, 0x15, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x42, + 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, + 0x0a, 0x0d, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, - 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, + 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x2f, 0x0a, 0x13, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x5f, - 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x75, + 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, - 0x65, 0x22, 0x16, 0x0a, 0x14, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, - 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xd5, 0x02, 0x0a, 0x15, 0x57, 0x61, - 0x6c, 0x6c, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x62, 0x61, 0x6c, - 0x61, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x6f, 0x74, 0x61, - 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x63, 0x6f, 0x6e, 0x66, - 0x69, 0x72, 0x6d, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x42, 0x61, - 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x2f, 0x0a, 0x13, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, - 0x72, 0x6d, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x12, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x42, - 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x59, 0x0a, 0x0f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x30, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x42, 0x61, - 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x41, 0x63, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x52, 0x0e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, - 0x65, 0x1a, 0x5e, 0x0a, 0x13, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x61, 0x6c, 0x61, - 0x6e, 0x63, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x31, 0x0a, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, - 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, - 0x01, 0x22, 0x2e, 0x0a, 0x06, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x73, - 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x73, 0x61, 0x74, 0x12, 0x12, 0x0a, - 0x04, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x6d, 0x73, 0x61, - 0x74, 0x22, 0x17, 0x0a, 0x15, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x6c, 0x61, - 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x80, 0x04, 0x0a, 0x16, 0x43, - 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x07, 0x62, 0x61, 0x6c, 0x61, - 0x6e, 0x63, 0x65, 0x12, 0x34, 0x0a, 0x14, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x6f, - 0x70, 0x65, 0x6e, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x12, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x70, - 0x65, 0x6e, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x32, 0x0a, 0x0d, 0x6c, 0x6f, 0x63, - 0x61, 0x6c, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x0d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x52, - 0x0c, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x34, 0x0a, - 0x0e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x6d, - 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x0d, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x42, 0x61, 0x6c, 0x61, - 0x6e, 0x63, 0x65, 0x12, 0x45, 0x0a, 0x17, 0x75, 0x6e, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64, - 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x6d, 0x6f, - 0x75, 0x6e, 0x74, 0x52, 0x15, 0x75, 0x6e, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x4c, 0x6f, - 0x63, 0x61, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x47, 0x0a, 0x18, 0x75, 0x6e, - 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x62, - 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x16, 0x75, 0x6e, 0x73, - 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x42, 0x61, 0x6c, 0x61, - 0x6e, 0x63, 0x65, 0x12, 0x4a, 0x0a, 0x1a, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x6f, - 0x70, 0x65, 0x6e, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, - 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x17, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, - 0x70, 0x65, 0x6e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, - 0x4c, 0x0a, 0x1b, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x5f, - 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x08, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x6d, 0x6f, - 0x75, 0x6e, 0x74, 0x52, 0x18, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x70, 0x65, 0x6e, - 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x22, 0xae, 0x06, - 0x0a, 0x12, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x10, 0x0a, - 0x03, 0x61, 0x6d, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x61, 0x6d, 0x74, 0x12, - 0x19, 0x0a, 0x08, 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x07, 0x61, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x66, 0x69, - 0x6e, 0x61, 0x6c, 0x5f, 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x43, 0x6c, 0x74, 0x76, 0x44, - 0x65, 0x6c, 0x74, 0x61, 0x12, 0x2c, 0x0a, 0x09, 0x66, 0x65, 0x65, 0x5f, 0x6c, 0x69, 0x6d, 0x69, - 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x46, 0x65, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x52, 0x08, 0x66, 0x65, 0x65, 0x4c, 0x69, 0x6d, - 0x69, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x64, 0x5f, 0x6e, 0x6f, - 0x64, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0c, 0x69, 0x67, 0x6e, 0x6f, 0x72, - 0x65, 0x64, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x12, 0x3b, 0x0a, 0x0d, 0x69, 0x67, 0x6e, 0x6f, 0x72, - 0x65, 0x64, 0x5f, 0x65, 0x64, 0x67, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x64, 0x67, 0x65, 0x4c, 0x6f, 0x63, 0x61, 0x74, - 0x6f, 0x72, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0c, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x64, 0x45, - 0x64, 0x67, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x70, - 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x2e, 0x0a, 0x13, 0x75, 0x73, - 0x65, 0x5f, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, - 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x75, 0x73, 0x65, 0x4d, 0x69, 0x73, 0x73, - 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x12, 0x34, 0x0a, 0x0d, 0x69, 0x67, - 0x6e, 0x6f, 0x72, 0x65, 0x64, 0x5f, 0x70, 0x61, 0x69, 0x72, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x50, 0x61, - 0x69, 0x72, 0x52, 0x0c, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x64, 0x50, 0x61, 0x69, 0x72, 0x73, - 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x0b, - 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x63, 0x6c, 0x74, 0x76, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, - 0x60, 0x0a, 0x13, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, - 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x44, 0x65, 0x73, 0x74, 0x43, 0x75, 0x73, 0x74, - 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x11, - 0x64, 0x65, 0x73, 0x74, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, - 0x73, 0x12, 0x2c, 0x0a, 0x10, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, - 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, - 0x0e, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, - 0x26, 0x0a, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x68, 0x6f, 0x70, 0x5f, 0x70, 0x75, 0x62, 0x6b, - 0x65, 0x79, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x6c, 0x61, 0x73, 0x74, 0x48, 0x6f, - 0x70, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x31, 0x0a, 0x0b, 0x72, 0x6f, 0x75, 0x74, 0x65, - 0x5f, 0x68, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x10, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x48, 0x69, 0x6e, 0x74, 0x52, 0x0a, - 0x72, 0x6f, 0x75, 0x74, 0x65, 0x48, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x36, 0x0a, 0x0d, 0x64, 0x65, - 0x73, 0x74, 0x5f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x11, 0x20, 0x03, 0x28, - 0x0e, 0x32, 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, - 0x65, 0x42, 0x69, 0x74, 0x52, 0x0c, 0x64, 0x65, 0x73, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, - 0x65, 0x73, 0x1a, 0x44, 0x0a, 0x16, 0x44, 0x65, 0x73, 0x74, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, - 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, - 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, - 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x22, 0x2e, - 0x0a, 0x08, 0x4e, 0x6f, 0x64, 0x65, 0x50, 0x61, 0x69, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x72, - 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x0e, - 0x0a, 0x02, 0x74, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x74, 0x6f, 0x22, 0x5d, - 0x0a, 0x0b, 0x45, 0x64, 0x67, 0x65, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x21, 0x0a, - 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x64, - 0x12, 0x2b, 0x0a, 0x11, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, - 0x76, 0x65, 0x72, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x64, 0x69, 0x72, - 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x22, 0x5e, 0x0a, - 0x13, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x18, 0x01, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, - 0x74, 0x65, 0x52, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, - 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x70, 0x72, 0x6f, 0x62, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, - 0x52, 0x0b, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x50, 0x72, 0x6f, 0x62, 0x22, 0x91, 0x04, - 0x0a, 0x03, 0x48, 0x6f, 0x70, 0x12, 0x1b, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, - 0x49, 0x64, 0x12, 0x27, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x63, 0x61, 0x70, 0x61, 0x63, - 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0c, 0x63, - 0x68, 0x61, 0x6e, 0x43, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x12, 0x28, 0x0a, 0x0e, 0x61, - 0x6d, 0x74, 0x5f, 0x74, 0x6f, 0x5f, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0c, 0x61, 0x6d, 0x74, 0x54, 0x6f, 0x46, 0x6f, - 0x72, 0x77, 0x61, 0x72, 0x64, 0x12, 0x14, 0x0a, 0x03, 0x66, 0x65, 0x65, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x03, 0x66, 0x65, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x65, - 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x65, 0x78, 0x70, - 0x69, 0x72, 0x79, 0x12, 0x2d, 0x0a, 0x13, 0x61, 0x6d, 0x74, 0x5f, 0x74, 0x6f, 0x5f, 0x66, 0x6f, - 0x72, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x10, 0x61, 0x6d, 0x74, 0x54, 0x6f, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x4d, 0x73, - 0x61, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x65, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x66, 0x65, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x17, 0x0a, - 0x07, 0x70, 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6c, 0x76, 0x5f, 0x70, 0x61, - 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x74, 0x6c, 0x76, - 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x2f, 0x0a, 0x0a, 0x6d, 0x70, 0x70, 0x5f, 0x72, - 0x65, 0x63, 0x6f, 0x72, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x50, 0x50, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x09, 0x6d, - 0x70, 0x70, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x2f, 0x0a, 0x0a, 0x61, 0x6d, 0x70, 0x5f, - 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x4d, 0x50, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x09, - 0x61, 0x6d, 0x70, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x44, 0x0a, 0x0e, 0x63, 0x75, 0x73, - 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x6f, 0x70, 0x2e, 0x43, 0x75, + 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x6c, 0x61, + 0x6e, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x6c, 0x6f, 0x63, 0x6b, 0x65, + 0x64, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x59, 0x0a, 0x0f, 0x61, 0x63, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x04, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x30, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, + 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x52, 0x0e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x61, 0x6c, 0x61, + 0x6e, 0x63, 0x65, 0x1a, 0x5e, 0x0a, 0x13, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x61, + 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x31, 0x0a, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, + 0x02, 0x38, 0x01, 0x22, 0x2e, 0x0a, 0x06, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x10, 0x0a, + 0x03, 0x73, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x73, 0x61, 0x74, 0x12, + 0x12, 0x0a, 0x04, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x6d, + 0x73, 0x61, 0x74, 0x22, 0x17, 0x0a, 0x15, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, + 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x80, 0x04, 0x0a, + 0x16, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, + 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x07, 0x62, 0x61, + 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x34, 0x0a, 0x14, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, + 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x12, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, + 0x4f, 0x70, 0x65, 0x6e, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x32, 0x0a, 0x0d, 0x6c, + 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, + 0x74, 0x52, 0x0c, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, + 0x34, 0x0a, 0x0e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, + 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x0d, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x42, 0x61, + 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x45, 0x0a, 0x17, 0x75, 0x6e, 0x73, 0x65, 0x74, 0x74, 0x6c, + 0x65, 0x64, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x15, 0x75, 0x6e, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64, + 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x47, 0x0a, 0x18, + 0x75, 0x6e, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, + 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x16, 0x75, + 0x6e, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x42, 0x61, + 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x4a, 0x0a, 0x1a, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, + 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x62, 0x61, 0x6c, 0x61, + 0x6e, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x17, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x4f, 0x70, 0x65, 0x6e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, + 0x65, 0x12, 0x4c, 0x0a, 0x1b, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x70, 0x65, + 0x6e, 0x5f, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x18, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x70, + 0x65, 0x6e, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x22, + 0xcb, 0x06, 0x0a, 0x12, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x75, 0x62, 0x5f, 0x6b, 0x65, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, + 0x10, 0x0a, 0x03, 0x61, 0x6d, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x61, 0x6d, + 0x74, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0c, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x07, 0x61, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x28, 0x0a, 0x10, + 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x43, 0x6c, 0x74, + 0x76, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x2c, 0x0a, 0x09, 0x66, 0x65, 0x65, 0x5f, 0x6c, 0x69, + 0x6d, 0x69, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x46, 0x65, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x52, 0x08, 0x66, 0x65, 0x65, 0x4c, + 0x69, 0x6d, 0x69, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x64, 0x5f, + 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0c, 0x69, 0x67, 0x6e, + 0x6f, 0x72, 0x65, 0x64, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x12, 0x3b, 0x0a, 0x0d, 0x69, 0x67, 0x6e, + 0x6f, 0x72, 0x65, 0x64, 0x5f, 0x65, 0x64, 0x67, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x64, 0x67, 0x65, 0x4c, 0x6f, 0x63, + 0x61, 0x74, 0x6f, 0x72, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0c, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, + 0x64, 0x45, 0x64, 0x67, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x5f, 0x70, 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x2e, 0x0a, 0x13, + 0x75, 0x73, 0x65, 0x5f, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x74, + 0x72, 0x6f, 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x75, 0x73, 0x65, 0x4d, 0x69, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x12, 0x34, 0x0a, 0x0d, + 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x64, 0x5f, 0x70, 0x61, 0x69, 0x72, 0x73, 0x18, 0x0a, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, + 0x50, 0x61, 0x69, 0x72, 0x52, 0x0c, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x64, 0x50, 0x61, 0x69, + 0x72, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, + 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x63, 0x6c, 0x74, 0x76, 0x4c, 0x69, 0x6d, 0x69, + 0x74, 0x12, 0x60, 0x0a, 0x13, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, + 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x30, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x6f, 0x75, 0x74, + 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x44, 0x65, 0x73, 0x74, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x52, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x1a, - 0x40, 0x0a, 0x12, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, - 0x01, 0x22, 0x54, 0x0a, 0x09, 0x4d, 0x50, 0x50, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x21, - 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x0b, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x41, 0x64, 0x64, - 0x72, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x6d, - 0x73, 0x61, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x6f, 0x74, 0x61, 0x6c, - 0x41, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x22, 0x62, 0x0a, 0x09, 0x41, 0x4d, 0x50, 0x52, 0x65, - 0x63, 0x6f, 0x72, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x73, 0x68, 0x61, - 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x72, 0x6f, 0x6f, 0x74, 0x53, 0x68, - 0x61, 0x72, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x65, 0x74, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x68, - 0x69, 0x6c, 0x64, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, - 0x0a, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0xe1, 0x01, 0x0a, 0x05, - 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x74, - 0x69, 0x6d, 0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, - 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x54, 0x69, 0x6d, 0x65, 0x4c, 0x6f, 0x63, 0x6b, 0x12, 0x21, 0x0a, - 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x66, 0x65, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x09, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x46, 0x65, 0x65, 0x73, - 0x12, 0x1f, 0x0a, 0x09, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x61, 0x6d, 0x74, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x08, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6d, - 0x74, 0x12, 0x1e, 0x0a, 0x04, 0x68, 0x6f, 0x70, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x0a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x6f, 0x70, 0x52, 0x04, 0x68, 0x6f, 0x70, - 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x66, 0x65, 0x65, 0x73, 0x5f, - 0x6d, 0x73, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x74, 0x6f, 0x74, 0x61, - 0x6c, 0x46, 0x65, 0x65, 0x73, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x6f, 0x74, - 0x61, 0x6c, 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0c, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x22, - 0x55, 0x0a, 0x0f, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x10, 0x69, - 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x43, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x22, 0xae, 0x01, 0x0a, 0x08, 0x4e, 0x6f, 0x64, 0x65, 0x49, - 0x6e, 0x66, 0x6f, 0x12, 0x28, 0x0a, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x67, 0x68, 0x74, 0x6e, - 0x69, 0x6e, 0x67, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x12, 0x21, 0x0a, - 0x0c, 0x6e, 0x75, 0x6d, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x02, 0x20, + 0x52, 0x11, 0x64, 0x65, 0x73, 0x74, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, + 0x72, 0x64, 0x73, 0x12, 0x2c, 0x0a, 0x10, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, + 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, + 0x01, 0x52, 0x0e, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, + 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x68, 0x6f, 0x70, 0x5f, 0x70, 0x75, + 0x62, 0x6b, 0x65, 0x79, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x6c, 0x61, 0x73, 0x74, + 0x48, 0x6f, 0x70, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x31, 0x0a, 0x0b, 0x72, 0x6f, 0x75, + 0x74, 0x65, 0x5f, 0x68, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x10, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x48, 0x69, 0x6e, 0x74, + 0x52, 0x0a, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x48, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x36, 0x0a, 0x0d, + 0x64, 0x65, 0x73, 0x74, 0x5f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x11, 0x20, + 0x03, 0x28, 0x0e, 0x32, 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x42, 0x69, 0x74, 0x52, 0x0c, 0x64, 0x65, 0x73, 0x74, 0x46, 0x65, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x70, 0x72, 0x65, + 0x66, 0x18, 0x12, 0x20, 0x01, 0x28, 0x01, 0x52, 0x08, 0x74, 0x69, 0x6d, 0x65, 0x50, 0x72, 0x65, + 0x66, 0x1a, 0x44, 0x0a, 0x16, 0x44, 0x65, 0x73, 0x74, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, + 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, + 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x22, 0x2e, 0x0a, + 0x08, 0x4e, 0x6f, 0x64, 0x65, 0x50, 0x61, 0x69, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x72, 0x6f, + 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x0e, 0x0a, + 0x02, 0x74, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x74, 0x6f, 0x22, 0x5d, 0x0a, + 0x0b, 0x45, 0x64, 0x67, 0x65, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x21, 0x0a, 0x0a, + 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, + 0x42, 0x02, 0x30, 0x01, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x64, 0x12, + 0x2b, 0x0a, 0x11, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x76, + 0x65, 0x72, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x64, 0x69, 0x72, 0x65, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x22, 0x5e, 0x0a, 0x13, + 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, + 0x65, 0x52, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x63, + 0x63, 0x65, 0x73, 0x73, 0x5f, 0x70, 0x72, 0x6f, 0x62, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, + 0x0b, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x50, 0x72, 0x6f, 0x62, 0x22, 0xb1, 0x04, 0x0a, + 0x03, 0x48, 0x6f, 0x70, 0x12, 0x1b, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, + 0x64, 0x12, 0x27, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, + 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0c, 0x63, 0x68, + 0x61, 0x6e, 0x43, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x12, 0x28, 0x0a, 0x0e, 0x61, 0x6d, + 0x74, 0x5f, 0x74, 0x6f, 0x5f, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0c, 0x61, 0x6d, 0x74, 0x54, 0x6f, 0x46, 0x6f, 0x72, + 0x77, 0x61, 0x72, 0x64, 0x12, 0x14, 0x0a, 0x03, 0x66, 0x65, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x03, 0x66, 0x65, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x78, + 0x70, 0x69, 0x72, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x65, 0x78, 0x70, 0x69, + 0x72, 0x79, 0x12, 0x2d, 0x0a, 0x13, 0x61, 0x6d, 0x74, 0x5f, 0x74, 0x6f, 0x5f, 0x66, 0x6f, 0x72, + 0x77, 0x61, 0x72, 0x64, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x10, 0x61, 0x6d, 0x74, 0x54, 0x6f, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x4d, 0x73, 0x61, + 0x74, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x65, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x07, 0x66, 0x65, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x17, 0x0a, 0x07, + 0x70, 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, + 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x23, 0x0a, 0x0b, 0x74, 0x6c, 0x76, 0x5f, 0x70, 0x61, 0x79, + 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, + 0x74, 0x6c, 0x76, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x2f, 0x0a, 0x0a, 0x6d, 0x70, + 0x70, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x50, 0x50, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, + 0x52, 0x09, 0x6d, 0x70, 0x70, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x2f, 0x0a, 0x0a, 0x61, + 0x6d, 0x70, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x10, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x4d, 0x50, 0x52, 0x65, 0x63, 0x6f, 0x72, + 0x64, 0x52, 0x09, 0x61, 0x6d, 0x70, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x44, 0x0a, 0x0e, + 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x0b, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x6f, 0x70, + 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x52, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, + 0x64, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0d, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x1a, 0x40, + 0x0a, 0x12, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, + 0x22, 0x54, 0x0a, 0x09, 0x4d, 0x50, 0x50, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x21, 0x0a, + 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x0b, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x41, 0x64, 0x64, 0x72, + 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, + 0x61, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x41, + 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x22, 0x62, 0x0a, 0x09, 0x41, 0x4d, 0x50, 0x52, 0x65, 0x63, + 0x6f, 0x72, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x73, 0x68, 0x61, 0x72, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x72, 0x6f, 0x6f, 0x74, 0x53, 0x68, 0x61, + 0x72, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x05, 0x73, 0x65, 0x74, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x68, 0x69, + 0x6c, 0x64, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, + 0x63, 0x68, 0x69, 0x6c, 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0xe1, 0x01, 0x0a, 0x05, 0x52, + 0x6f, 0x75, 0x74, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x74, 0x69, + 0x6d, 0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x74, + 0x6f, 0x74, 0x61, 0x6c, 0x54, 0x69, 0x6d, 0x65, 0x4c, 0x6f, 0x63, 0x6b, 0x12, 0x21, 0x0a, 0x0a, + 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x66, 0x65, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, + 0x42, 0x02, 0x18, 0x01, 0x52, 0x09, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x46, 0x65, 0x65, 0x73, 0x12, + 0x1f, 0x0a, 0x09, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x61, 0x6d, 0x74, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x08, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6d, 0x74, + 0x12, 0x1e, 0x0a, 0x04, 0x68, 0x6f, 0x70, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0a, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x6f, 0x70, 0x52, 0x04, 0x68, 0x6f, 0x70, 0x73, + 0x12, 0x26, 0x0a, 0x0f, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x66, 0x65, 0x65, 0x73, 0x5f, 0x6d, + 0x73, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x74, 0x6f, 0x74, 0x61, 0x6c, + 0x46, 0x65, 0x65, 0x73, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x6f, 0x74, 0x61, + 0x6c, 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0c, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x22, 0x55, + 0x0a, 0x0f, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x10, 0x69, 0x6e, + 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x43, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x22, 0xae, 0x01, 0x0a, 0x08, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, + 0x66, 0x6f, 0x12, 0x28, 0x0a, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, + 0x6e, 0x67, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x12, 0x21, 0x0a, 0x0c, + 0x6e, 0x75, 0x6d, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x0b, 0x6e, 0x75, 0x6d, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, + 0x25, 0x0a, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, + 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x61, + 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x12, 0x2e, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x64, 0x67, 0x65, 0x52, 0x08, 0x63, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x22, 0xb4, 0x02, 0x0a, 0x0d, 0x4c, 0x69, 0x67, 0x68, 0x74, + 0x6e, 0x69, 0x6e, 0x67, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6c, 0x61, 0x73, 0x74, + 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x6c, + 0x61, 0x73, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x75, 0x62, + 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x4b, + 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x30, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, + 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, + 0x6c, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x6f, 0x6c, 0x6f, 0x72, + 0x12, 0x3e, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x67, 0x68, 0x74, + 0x6e, 0x69, 0x6e, 0x67, 0x4e, 0x6f, 0x64, 0x65, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, + 0x1a, 0x4b, 0x0a, 0x0d, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, + 0x6b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x3b, 0x0a, + 0x0b, 0x4e, 0x6f, 0x64, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x18, 0x0a, 0x07, + 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, + 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x64, 0x64, 0x72, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, 0x64, 0x72, 0x22, 0x86, 0x02, 0x0a, 0x0d, 0x52, + 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x26, 0x0a, 0x0f, + 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x74, 0x69, 0x6d, 0x65, 0x4c, 0x6f, 0x63, 0x6b, 0x44, + 0x65, 0x6c, 0x74, 0x61, 0x12, 0x19, 0x0a, 0x08, 0x6d, 0x69, 0x6e, 0x5f, 0x68, 0x74, 0x6c, 0x63, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x6d, 0x69, 0x6e, 0x48, 0x74, 0x6c, 0x63, 0x12, + 0x22, 0x0a, 0x0d, 0x66, 0x65, 0x65, 0x5f, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x66, 0x65, 0x65, 0x42, 0x61, 0x73, 0x65, 0x4d, + 0x73, 0x61, 0x74, 0x12, 0x2d, 0x0a, 0x13, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x5f, + 0x6d, 0x69, 0x6c, 0x6c, 0x69, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x10, 0x66, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, 0x4d, 0x69, 0x6c, 0x6c, 0x69, 0x4d, 0x73, + 0x61, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x22, + 0x0a, 0x0d, 0x6d, 0x61, 0x78, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x48, 0x74, 0x6c, 0x63, 0x4d, 0x73, + 0x61, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x6c, 0x61, 0x73, 0x74, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x22, 0xbc, 0x02, 0x0a, 0x0b, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, + 0x64, 0x67, 0x65, 0x12, 0x21, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x09, 0x63, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, + 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, + 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0b, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x75, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, + 0x6c, 0x61, 0x73, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, + 0x64, 0x65, 0x31, 0x5f, 0x70, 0x75, 0x62, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, + 0x6f, 0x64, 0x65, 0x31, 0x50, 0x75, 0x62, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x32, + 0x5f, 0x70, 0x75, 0x62, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x6f, 0x64, 0x65, + 0x32, 0x50, 0x75, 0x62, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, + 0x12, 0x37, 0x0a, 0x0c, 0x6e, 0x6f, 0x64, 0x65, 0x31, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, + 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x0b, 0x6e, 0x6f, + 0x64, 0x65, 0x31, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x37, 0x0a, 0x0c, 0x6e, 0x6f, 0x64, + 0x65, 0x32, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x50, + 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x0b, 0x6e, 0x6f, 0x64, 0x65, 0x32, 0x50, 0x6f, 0x6c, 0x69, + 0x63, 0x79, 0x22, 0x46, 0x0a, 0x13, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x47, 0x72, 0x61, + 0x70, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2f, 0x0a, 0x13, 0x69, 0x6e, 0x63, + 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x75, 0x6e, 0x61, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x55, + 0x6e, 0x61, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x64, 0x22, 0x64, 0x0a, 0x0c, 0x43, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x47, 0x72, 0x61, 0x70, 0x68, 0x12, 0x2a, 0x0a, 0x05, 0x6e, 0x6f, + 0x64, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x4c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x4e, 0x6f, 0x64, 0x65, 0x52, + 0x05, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x12, 0x28, 0x0a, 0x05, 0x65, 0x64, 0x67, 0x65, 0x73, 0x18, + 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x64, 0x67, 0x65, 0x52, 0x05, 0x65, 0x64, 0x67, 0x65, 0x73, + 0x22, 0x41, 0x0a, 0x12, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x05, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, + 0x64, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x54, 0x79, 0x70, 0x65, 0x52, 0x05, 0x74, 0x79, + 0x70, 0x65, 0x73, 0x22, 0xe1, 0x01, 0x0a, 0x13, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, 0x72, + 0x69, 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, 0x0a, 0x16, 0x62, + 0x65, 0x74, 0x77, 0x65, 0x65, 0x6e, 0x6e, 0x65, 0x73, 0x73, 0x5f, 0x63, 0x65, 0x6e, 0x74, 0x72, + 0x61, 0x6c, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x42, 0x65, 0x74, 0x77, 0x65, 0x65, 0x6e, 0x6e, + 0x65, 0x73, 0x73, 0x43, 0x65, 0x6e, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x52, 0x15, 0x62, 0x65, 0x74, 0x77, 0x65, 0x65, 0x6e, 0x6e, 0x65, 0x73, 0x73, 0x43, + 0x65, 0x6e, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x1a, 0x5c, 0x0a, 0x1a, 0x42, 0x65, 0x74, + 0x77, 0x65, 0x65, 0x6e, 0x6e, 0x65, 0x73, 0x73, 0x43, 0x65, 0x6e, 0x74, 0x72, 0x61, 0x6c, 0x69, + 0x74, 0x79, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x28, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x4e, 0x0a, 0x0b, 0x46, 0x6c, 0x6f, 0x61, 0x74, + 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x29, 0x0a, 0x10, + 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0f, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x69, 0x7a, + 0x65, 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x2e, 0x0a, 0x0f, 0x43, 0x68, 0x61, 0x6e, 0x49, + 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x07, 0x63, 0x68, + 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, + 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x22, 0x14, 0x0a, 0x12, 0x4e, 0x65, 0x74, 0x77, 0x6f, + 0x72, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xd5, 0x03, + 0x0a, 0x0b, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x25, 0x0a, + 0x0e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x5f, 0x64, 0x69, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x67, 0x72, 0x61, 0x70, 0x68, 0x44, 0x69, 0x61, 0x6d, + 0x65, 0x74, 0x65, 0x72, 0x12, 0x24, 0x0a, 0x0e, 0x61, 0x76, 0x67, 0x5f, 0x6f, 0x75, 0x74, 0x5f, + 0x64, 0x65, 0x67, 0x72, 0x65, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0c, 0x61, 0x76, + 0x67, 0x4f, 0x75, 0x74, 0x44, 0x65, 0x67, 0x72, 0x65, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x6d, 0x61, + 0x78, 0x5f, 0x6f, 0x75, 0x74, 0x5f, 0x64, 0x65, 0x67, 0x72, 0x65, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x0c, 0x6d, 0x61, 0x78, 0x4f, 0x75, 0x74, 0x44, 0x65, 0x67, 0x72, 0x65, 0x65, + 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x75, 0x6d, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x08, 0x6e, 0x75, 0x6d, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x12, 0x21, 0x0a, + 0x0c, 0x6e, 0x75, 0x6d, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x6e, 0x75, 0x6d, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, - 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, - 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, - 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x12, 0x2e, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x6e, 0x6e, - 0x65, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x64, 0x67, 0x65, 0x52, 0x08, 0x63, - 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x22, 0xb4, 0x02, 0x0a, 0x0d, 0x4c, 0x69, 0x67, 0x68, - 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6c, 0x61, 0x73, - 0x74, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, - 0x6c, 0x61, 0x73, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x75, - 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, - 0x4b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x05, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x30, 0x0a, 0x09, 0x61, 0x64, 0x64, - 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x52, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x63, - 0x6f, 0x6c, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x6f, 0x6c, 0x6f, - 0x72, 0x12, 0x3e, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x06, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x67, 0x68, - 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x4e, 0x6f, 0x64, 0x65, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, + 0x12, 0x34, 0x0a, 0x16, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, + 0x6b, 0x5f, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x14, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x43, 0x61, + 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x12, 0x28, 0x0a, 0x10, 0x61, 0x76, 0x67, 0x5f, 0x63, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x01, + 0x52, 0x0e, 0x61, 0x76, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x53, 0x69, 0x7a, 0x65, + 0x12, 0x28, 0x0a, 0x10, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, + 0x73, 0x69, 0x7a, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6d, 0x69, 0x6e, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x6d, 0x61, + 0x78, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x09, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6d, 0x61, 0x78, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x53, 0x69, 0x7a, 0x65, 0x12, 0x35, 0x0a, 0x17, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x6e, 0x5f, 0x63, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, + 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x14, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x6e, 0x43, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x53, 0x69, 0x7a, 0x65, 0x53, 0x61, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x6e, + 0x75, 0x6d, 0x5f, 0x7a, 0x6f, 0x6d, 0x62, 0x69, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x73, 0x18, + 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x6e, 0x75, 0x6d, 0x5a, 0x6f, 0x6d, 0x62, 0x69, 0x65, + 0x43, 0x68, 0x61, 0x6e, 0x73, 0x22, 0x0d, 0x0a, 0x0b, 0x53, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x22, 0x0e, 0x0a, 0x0c, 0x53, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1b, 0x0a, 0x19, 0x47, 0x72, 0x61, 0x70, 0x68, 0x54, 0x6f, 0x70, + 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x22, 0xcd, 0x01, 0x0a, 0x13, 0x47, 0x72, 0x61, 0x70, 0x68, 0x54, 0x6f, 0x70, 0x6f, 0x6c, + 0x6f, 0x67, 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x34, 0x0a, 0x0c, 0x6e, 0x6f, 0x64, + 0x65, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x52, 0x0b, 0x6e, 0x6f, 0x64, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x12, + 0x41, 0x0a, 0x0f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x64, 0x67, 0x65, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x52, 0x0e, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x73, 0x12, 0x3d, 0x0a, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x5f, 0x63, 0x68, 0x61, + 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x52, 0x0b, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, + 0x73, 0x22, 0xef, 0x02, 0x0a, 0x0a, 0x4e, 0x6f, 0x64, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x12, 0x20, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x65, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x6b, + 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, + 0x74, 0x79, 0x4b, 0x65, 0x79, 0x12, 0x2b, 0x0a, 0x0f, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x5f, + 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x02, + 0x18, 0x01, 0x52, 0x0e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x6c, 0x6f, + 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x12, 0x39, + 0x0a, 0x0e, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, + 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, + 0x6f, 0x64, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x0d, 0x6e, 0x6f, 0x64, 0x65, + 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x3b, 0x0a, 0x08, 0x66, 0x65, 0x61, + 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x2e, 0x46, + 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x66, 0x65, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x1a, 0x4b, 0x0a, 0x0d, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, + 0x02, 0x38, 0x01, 0x22, 0x91, 0x02, 0x0a, 0x11, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, + 0x64, 0x67, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x07, 0x63, 0x68, 0x61, + 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x06, + 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x32, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, + 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, + 0x09, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, + 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x63, 0x61, + 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x12, 0x3b, 0x0a, 0x0e, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, + 0x67, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x6f, + 0x6c, 0x69, 0x63, 0x79, 0x52, 0x0d, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x6c, + 0x69, 0x63, 0x79, 0x12, 0x29, 0x0a, 0x10, 0x61, 0x64, 0x76, 0x65, 0x72, 0x74, 0x69, 0x73, 0x69, + 0x6e, 0x67, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x61, + 0x64, 0x76, 0x65, 0x72, 0x74, 0x69, 0x73, 0x69, 0x6e, 0x67, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x27, + 0x0a, 0x0f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x6e, 0x6f, 0x64, + 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x69, 0x6e, 0x67, 0x4e, 0x6f, 0x64, 0x65, 0x22, 0xa7, 0x01, 0x0a, 0x13, 0x43, 0x6c, 0x6f, 0x73, + 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, + 0x1b, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, + 0x42, 0x02, 0x30, 0x01, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, + 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, + 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6c, 0x6f, 0x73, + 0x65, 0x64, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x32, 0x0a, + 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, + 0x74, 0x22, 0xcf, 0x01, 0x0a, 0x07, 0x48, 0x6f, 0x70, 0x48, 0x69, 0x6e, 0x74, 0x12, 0x17, 0x0a, + 0x07, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x6e, 0x6f, 0x64, 0x65, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x06, 0x63, 0x68, 0x61, + 0x6e, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x66, 0x65, 0x65, 0x5f, 0x62, 0x61, 0x73, 0x65, 0x5f, + 0x6d, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x66, 0x65, 0x65, 0x42, + 0x61, 0x73, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x3e, 0x0a, 0x1b, 0x66, 0x65, 0x65, 0x5f, 0x70, + 0x72, 0x6f, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x5f, 0x6d, 0x69, 0x6c, 0x6c, + 0x69, 0x6f, 0x6e, 0x74, 0x68, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x19, 0x66, 0x65, + 0x65, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x4d, 0x69, 0x6c, + 0x6c, 0x69, 0x6f, 0x6e, 0x74, 0x68, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x63, 0x6c, 0x74, 0x76, 0x5f, + 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x0f, 0x63, 0x6c, 0x74, 0x76, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x44, 0x65, + 0x6c, 0x74, 0x61, 0x22, 0x1e, 0x0a, 0x05, 0x53, 0x65, 0x74, 0x49, 0x44, 0x12, 0x15, 0x0a, 0x06, + 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x65, + 0x74, 0x49, 0x64, 0x22, 0x38, 0x0a, 0x09, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x48, 0x69, 0x6e, 0x74, + 0x12, 0x2b, 0x0a, 0x09, 0x68, 0x6f, 0x70, 0x5f, 0x68, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x6f, 0x70, 0x48, + 0x69, 0x6e, 0x74, 0x52, 0x08, 0x68, 0x6f, 0x70, 0x48, 0x69, 0x6e, 0x74, 0x73, 0x22, 0xa8, 0x01, + 0x0a, 0x0f, 0x41, 0x4d, 0x50, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, + 0x65, 0x12, 0x2d, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, + 0x48, 0x54, 0x4c, 0x43, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, + 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x49, 0x6e, + 0x64, 0x65, 0x78, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x5f, 0x74, 0x69, + 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, + 0x54, 0x69, 0x6d, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x6d, 0x74, 0x5f, 0x70, 0x61, 0x69, 0x64, + 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x6d, 0x74, + 0x50, 0x61, 0x69, 0x64, 0x4d, 0x73, 0x61, 0x74, 0x22, 0xc3, 0x09, 0x0a, 0x07, 0x49, 0x6e, 0x76, + 0x6f, 0x69, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x65, 0x6d, 0x6f, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x6d, 0x65, 0x6d, 0x6f, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x5f, 0x70, 0x72, + 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x72, 0x50, + 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x72, 0x5f, 0x68, 0x61, 0x73, + 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x72, 0x48, 0x61, 0x73, 0x68, 0x12, 0x14, + 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x6d, 0x73, + 0x61, 0x74, 0x18, 0x17, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x4d, + 0x73, 0x61, 0x74, 0x12, 0x1c, 0x0a, 0x07, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x08, 0x42, 0x02, 0x18, 0x01, 0x52, 0x07, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, + 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x61, + 0x74, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, + 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x73, 0x65, 0x74, + 0x74, 0x6c, 0x65, 0x44, 0x61, 0x74, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x70, 0x61, 0x79, 0x6d, 0x65, + 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0e, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x29, 0x0a, 0x10, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, + 0x68, 0x61, 0x73, 0x68, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x64, 0x65, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x61, 0x73, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x65, + 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x65, 0x78, 0x70, + 0x69, 0x72, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x5f, + 0x61, 0x64, 0x64, 0x72, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x66, 0x61, 0x6c, 0x6c, + 0x62, 0x61, 0x63, 0x6b, 0x41, 0x64, 0x64, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6c, 0x74, 0x76, + 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x63, + 0x6c, 0x74, 0x76, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x31, 0x0a, 0x0b, 0x72, 0x6f, 0x75, + 0x74, 0x65, 0x5f, 0x68, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x48, 0x69, 0x6e, 0x74, + 0x52, 0x0a, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x48, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x18, 0x0a, 0x07, + 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x70, + 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x5f, 0x69, 0x6e, + 0x64, 0x65, 0x78, 0x18, 0x10, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x61, 0x64, 0x64, 0x49, 0x6e, + 0x64, 0x65, 0x78, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x5f, 0x69, 0x6e, + 0x64, 0x65, 0x78, 0x18, 0x11, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x65, 0x74, 0x74, 0x6c, + 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x1d, 0x0a, 0x08, 0x61, 0x6d, 0x74, 0x5f, 0x70, 0x61, + 0x69, 0x64, 0x18, 0x12, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x07, 0x61, 0x6d, + 0x74, 0x50, 0x61, 0x69, 0x64, 0x12, 0x20, 0x0a, 0x0c, 0x61, 0x6d, 0x74, 0x5f, 0x70, 0x61, 0x69, + 0x64, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x13, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x61, 0x6d, 0x74, + 0x50, 0x61, 0x69, 0x64, 0x53, 0x61, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x6d, 0x74, 0x5f, 0x70, + 0x61, 0x69, 0x64, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x14, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, + 0x61, 0x6d, 0x74, 0x50, 0x61, 0x69, 0x64, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x31, 0x0a, 0x05, 0x73, + 0x74, 0x61, 0x74, 0x65, 0x18, 0x15, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, + 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x28, + 0x0a, 0x05, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x18, 0x16, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x48, 0x54, 0x4c, + 0x43, 0x52, 0x05, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x12, 0x38, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x73, 0x18, 0x18, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x73, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x65, 0x6e, 0x64, + 0x18, 0x19, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x73, 0x4b, 0x65, 0x79, 0x73, 0x65, 0x6e, + 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, + 0x72, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x41, 0x64, 0x64, 0x72, 0x12, 0x15, 0x0a, 0x06, 0x69, 0x73, 0x5f, 0x61, 0x6d, 0x70, 0x18, 0x1b, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x69, 0x73, 0x41, 0x6d, 0x70, 0x12, 0x4f, 0x0a, 0x11, 0x61, + 0x6d, 0x70, 0x5f, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, + 0x18, 0x1c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, + 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x2e, 0x41, 0x6d, 0x70, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, + 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0f, 0x61, 0x6d, 0x70, + 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x1a, 0x4b, 0x0a, 0x0d, + 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, + 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, + 0x24, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x5a, 0x0a, 0x14, 0x41, 0x6d, 0x70, + 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, + 0x6b, 0x65, 0x79, 0x12, 0x2c, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x4d, 0x50, 0x49, 0x6e, + 0x76, 0x6f, 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x41, 0x0a, 0x0c, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, + 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x08, 0x0a, 0x04, 0x4f, 0x50, 0x45, 0x4e, 0x10, 0x00, 0x12, + 0x0b, 0x0a, 0x07, 0x53, 0x45, 0x54, 0x54, 0x4c, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, + 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0c, 0x0a, 0x08, 0x41, 0x43, + 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x10, 0x03, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x22, 0xfc, + 0x03, 0x0a, 0x0b, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x48, 0x54, 0x4c, 0x43, 0x12, 0x1b, + 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, + 0x02, 0x30, 0x01, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x68, + 0x74, 0x6c, 0x63, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x09, 0x68, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x6d, + 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x61, 0x6d, + 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x5f, + 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x61, 0x63, + 0x63, 0x65, 0x70, 0x74, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x63, + 0x63, 0x65, 0x70, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0a, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x72, + 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0b, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x23, + 0x0a, 0x0d, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x48, 0x65, 0x69, + 0x67, 0x68, 0x74, 0x12, 0x2d, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x08, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, + 0x63, 0x65, 0x48, 0x54, 0x4c, 0x43, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, + 0x74, 0x65, 0x12, 0x4c, 0x0a, 0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, + 0x6f, 0x72, 0x64, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x48, 0x54, 0x4c, 0x43, 0x2e, 0x43, + 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x52, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, + 0x12, 0x2b, 0x0a, 0x12, 0x6d, 0x70, 0x70, 0x5f, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x61, 0x6d, + 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x6d, 0x70, + 0x70, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x1c, 0x0a, + 0x03, 0x61, 0x6d, 0x70, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x41, 0x4d, 0x50, 0x52, 0x03, 0x61, 0x6d, 0x70, 0x1a, 0x40, 0x0a, 0x12, 0x43, + 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, + 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x8c, 0x01, + 0x0a, 0x03, 0x41, 0x4d, 0x50, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x73, 0x68, + 0x61, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x72, 0x6f, 0x6f, 0x74, 0x53, + 0x68, 0x61, 0x72, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x65, 0x74, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x63, + 0x68, 0x69, 0x6c, 0x64, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x0a, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x12, 0x0a, 0x04, + 0x68, 0x61, 0x73, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, + 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x22, 0x94, 0x01, 0x0a, + 0x12, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x72, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x05, 0x72, 0x48, 0x61, 0x73, 0x68, 0x12, 0x27, 0x0a, 0x0f, 0x70, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0e, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, + 0x18, 0x10, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x61, 0x64, 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, + 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, + 0x18, 0x11, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x41, + 0x64, 0x64, 0x72, 0x22, 0x46, 0x0a, 0x0b, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, + 0x73, 0x68, 0x12, 0x20, 0x0a, 0x0a, 0x72, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x5f, 0x73, 0x74, 0x72, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x08, 0x72, 0x48, 0x61, 0x73, + 0x68, 0x53, 0x74, 0x72, 0x12, 0x15, 0x0a, 0x06, 0x72, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x72, 0x48, 0x61, 0x73, 0x68, 0x22, 0xa0, 0x01, 0x0a, 0x12, + 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x6e, + 0x6c, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x6f, + 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x69, 0x6e, 0x64, + 0x65, 0x78, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x6e, 0x75, 0x6d, 0x5f, + 0x6d, 0x61, 0x78, 0x5f, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x0e, 0x6e, 0x75, 0x6d, 0x4d, 0x61, 0x78, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, + 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x64, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x64, 0x22, 0x9b, + 0x01, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x08, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, + 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x08, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, + 0x65, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, + 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x6c, + 0x61, 0x73, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x2c, + 0x0a, 0x12, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x6f, 0x66, + 0x66, 0x73, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x66, 0x69, 0x72, 0x73, + 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x22, 0x55, 0x0a, 0x13, + 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x61, 0x64, 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, + 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x49, 0x6e, + 0x64, 0x65, 0x78, 0x22, 0x8a, 0x05, 0x0a, 0x07, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, + 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, + 0x73, 0x68, 0x12, 0x18, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x27, 0x0a, 0x0d, + 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x44, 0x61, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x03, 0x66, 0x65, 0x65, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x03, 0x66, 0x65, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x70, + 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x50, 0x72, + 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, + 0x73, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x53, 0x61, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x6d, 0x73, 0x61, + 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x4d, 0x73, + 0x61, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x70, 0x61, 0x79, + 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x34, 0x0a, 0x06, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x79, 0x6d, + 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x12, 0x17, 0x0a, 0x07, 0x66, 0x65, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x0b, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x06, 0x66, 0x65, 0x65, 0x53, 0x61, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x65, + 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x66, 0x65, + 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6e, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x4e, 0x73, 0x12, + 0x28, 0x0a, 0x05, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x54, 0x4c, 0x43, 0x41, 0x74, 0x74, 0x65, 0x6d, + 0x70, 0x74, 0x52, 0x05, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x61, 0x79, + 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x42, + 0x0a, 0x0e, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, + 0x18, 0x10, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, + 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x65, 0x61, + 0x73, 0x6f, 0x6e, 0x52, 0x0d, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x65, 0x61, 0x73, + 0x6f, 0x6e, 0x22, 0x46, 0x0a, 0x0d, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, + 0x12, 0x0d, 0x0a, 0x09, 0x49, 0x4e, 0x5f, 0x46, 0x4c, 0x49, 0x47, 0x48, 0x54, 0x10, 0x01, 0x12, + 0x0d, 0x0a, 0x09, 0x53, 0x55, 0x43, 0x43, 0x45, 0x45, 0x44, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0a, + 0x0a, 0x06, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x03, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, + 0x22, 0xd5, 0x02, 0x0a, 0x0b, 0x48, 0x54, 0x4c, 0x43, 0x41, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, + 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x61, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x49, 0x64, 0x12, + 0x35, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x54, 0x4c, 0x43, 0x41, 0x74, 0x74, 0x65, + 0x6d, 0x70, 0x74, 0x2e, 0x48, 0x54, 0x4c, 0x43, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x22, 0x0a, 0x05, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, + 0x75, 0x74, 0x65, 0x52, 0x05, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x61, 0x74, + 0x74, 0x65, 0x6d, 0x70, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6e, 0x73, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0d, 0x61, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x54, 0x69, 0x6d, 0x65, + 0x4e, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x5f, 0x74, 0x69, + 0x6d, 0x65, 0x5f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x72, 0x65, 0x73, + 0x6f, 0x6c, 0x76, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x4e, 0x73, 0x12, 0x28, 0x0a, 0x07, 0x66, 0x61, + 0x69, 0x6c, 0x75, 0x72, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x07, 0x66, 0x61, 0x69, + 0x6c, 0x75, 0x72, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, + 0x22, 0x36, 0x0a, 0x0a, 0x48, 0x54, 0x4c, 0x43, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0d, + 0x0a, 0x09, 0x49, 0x4e, 0x5f, 0x46, 0x4c, 0x49, 0x47, 0x48, 0x54, 0x10, 0x00, 0x12, 0x0d, 0x0a, + 0x09, 0x53, 0x55, 0x43, 0x43, 0x45, 0x45, 0x44, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, + 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x02, 0x22, 0xd8, 0x01, 0x0a, 0x13, 0x4c, 0x69, 0x73, + 0x74, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x2d, 0x0a, 0x12, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x69, 0x6e, 0x63, 0x6f, + 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x69, 0x6e, + 0x63, 0x6c, 0x75, 0x64, 0x65, 0x49, 0x6e, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, + 0x21, 0x0a, 0x0c, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x4f, 0x66, 0x66, 0x73, + 0x65, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x61, 0x78, 0x5f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, + 0x74, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x50, 0x61, 0x79, + 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, + 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, + 0x64, 0x12, 0x30, 0x0a, 0x14, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x74, 0x6f, 0x74, 0x61, 0x6c, + 0x5f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x12, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x50, 0x61, 0x79, 0x6d, 0x65, + 0x6e, 0x74, 0x73, 0x22, 0xca, 0x01, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x79, 0x6d, + 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x08, + 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x08, + 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x66, 0x69, 0x72, 0x73, + 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x66, 0x69, 0x72, 0x73, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, + 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x2a, 0x0a, 0x11, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x69, + 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x4f, 0x66, 0x66, 0x73, + 0x65, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x6e, 0x75, 0x6d, 0x5f, + 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, + 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x4e, 0x75, 0x6d, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, + 0x22, 0x65, 0x0a, 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, + 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, + 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x2a, 0x0a, 0x11, 0x66, + 0x61, 0x69, 0x6c, 0x65, 0x64, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x48, 0x74, + 0x6c, 0x63, 0x73, 0x4f, 0x6e, 0x6c, 0x79, 0x22, 0x78, 0x0a, 0x18, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x41, 0x6c, 0x6c, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x30, 0x0a, 0x14, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x5f, 0x70, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x12, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x73, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x2a, 0x0a, 0x11, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x5f, + 0x68, 0x74, 0x6c, 0x63, 0x73, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x0f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x73, 0x4f, 0x6e, 0x6c, + 0x79, 0x22, 0x17, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, + 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1b, 0x0a, 0x19, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, 0x6c, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xbf, 0x01, 0x0a, 0x15, 0x41, 0x62, 0x61, 0x6e, + 0x64, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x38, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x70, 0x6f, 0x69, + 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x0c, 0x63, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x39, 0x0a, 0x19, 0x70, + 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x73, + 0x68, 0x69, 0x6d, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x16, + 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x68, + 0x69, 0x6d, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x31, 0x0a, 0x16, 0x69, 0x5f, 0x6b, 0x6e, 0x6f, 0x77, + 0x5f, 0x77, 0x68, 0x61, 0x74, 0x5f, 0x69, 0x5f, 0x61, 0x6d, 0x5f, 0x64, 0x6f, 0x69, 0x6e, 0x67, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x69, 0x4b, 0x6e, 0x6f, 0x77, 0x57, 0x68, 0x61, + 0x74, 0x49, 0x41, 0x6d, 0x44, 0x6f, 0x69, 0x6e, 0x67, 0x22, 0x18, 0x0a, 0x16, 0x41, 0x62, 0x61, + 0x6e, 0x64, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x46, 0x0a, 0x11, 0x44, 0x65, 0x62, 0x75, 0x67, 0x4c, 0x65, 0x76, 0x65, + 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x68, 0x6f, 0x77, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x73, 0x68, 0x6f, 0x77, 0x12, 0x1d, 0x0a, 0x0a, + 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x53, 0x70, 0x65, 0x63, 0x22, 0x35, 0x0a, 0x12, 0x44, + 0x65, 0x62, 0x75, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x75, 0x62, 0x5f, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x73, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x75, 0x62, 0x53, 0x79, 0x73, 0x74, 0x65, + 0x6d, 0x73, 0x22, 0x27, 0x0a, 0x0c, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x53, 0x74, 0x72, 0x69, + 0x6e, 0x67, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x5f, 0x72, 0x65, 0x71, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x61, 0x79, 0x52, 0x65, 0x71, 0x22, 0xb0, 0x04, 0x0a, 0x06, + 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, + 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, + 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, + 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x21, 0x0a, 0x0c, 0x6e, + 0x75, 0x6d, 0x5f, 0x73, 0x61, 0x74, 0x6f, 0x73, 0x68, 0x69, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0b, 0x6e, 0x75, 0x6d, 0x53, 0x61, 0x74, 0x6f, 0x73, 0x68, 0x69, 0x73, 0x12, 0x1c, + 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x16, 0x0a, 0x06, + 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x65, 0x78, + 0x70, 0x69, 0x72, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x29, 0x0a, 0x10, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x61, 0x73, + 0x68, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x5f, 0x61, 0x64, + 0x64, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, + 0x63, 0x6b, 0x41, 0x64, 0x64, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x65, + 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x63, 0x6c, 0x74, + 0x76, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x31, 0x0a, 0x0b, 0x72, 0x6f, 0x75, 0x74, 0x65, + 0x5f, 0x68, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x48, 0x69, 0x6e, 0x74, 0x52, 0x0a, + 0x72, 0x6f, 0x75, 0x74, 0x65, 0x48, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, 0x19, 0x0a, + 0x08, 0x6e, 0x75, 0x6d, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x07, 0x6e, 0x75, 0x6d, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x37, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x1a, 0x4b, 0x0a, 0x0d, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x61, 0x74, - 0x75, 0x72, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x3b, - 0x0a, 0x0b, 0x4e, 0x6f, 0x64, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x18, 0x0a, - 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x64, 0x64, 0x72, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, 0x64, 0x72, 0x22, 0x86, 0x02, 0x0a, 0x0d, - 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x26, 0x0a, - 0x0f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x74, 0x69, 0x6d, 0x65, 0x4c, 0x6f, 0x63, 0x6b, - 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x19, 0x0a, 0x08, 0x6d, 0x69, 0x6e, 0x5f, 0x68, 0x74, 0x6c, - 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x6d, 0x69, 0x6e, 0x48, 0x74, 0x6c, 0x63, - 0x12, 0x22, 0x0a, 0x0d, 0x66, 0x65, 0x65, 0x5f, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x6d, 0x73, 0x61, - 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x66, 0x65, 0x65, 0x42, 0x61, 0x73, 0x65, - 0x4d, 0x73, 0x61, 0x74, 0x12, 0x2d, 0x0a, 0x13, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, - 0x5f, 0x6d, 0x69, 0x6c, 0x6c, 0x69, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x10, 0x66, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, 0x4d, 0x69, 0x6c, 0x6c, 0x69, 0x4d, - 0x73, 0x61, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, - 0x22, 0x0a, 0x0d, 0x6d, 0x61, 0x78, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x73, 0x61, 0x74, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x48, 0x74, 0x6c, 0x63, 0x4d, - 0x73, 0x61, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x75, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x6c, 0x61, 0x73, 0x74, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x22, 0xbc, 0x02, 0x0a, 0x0b, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x45, 0x64, 0x67, 0x65, 0x12, 0x21, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x09, 0x63, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, - 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x68, 0x61, - 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0b, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x75, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, - 0x0a, 0x6c, 0x61, 0x73, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6e, - 0x6f, 0x64, 0x65, 0x31, 0x5f, 0x70, 0x75, 0x62, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x6e, 0x6f, 0x64, 0x65, 0x31, 0x50, 0x75, 0x62, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x64, 0x65, - 0x32, 0x5f, 0x70, 0x75, 0x62, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x6f, 0x64, - 0x65, 0x32, 0x50, 0x75, 0x62, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, - 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, - 0x79, 0x12, 0x37, 0x0a, 0x0c, 0x6e, 0x6f, 0x64, 0x65, 0x31, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, - 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x0b, 0x6e, - 0x6f, 0x64, 0x65, 0x31, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x37, 0x0a, 0x0c, 0x6e, 0x6f, - 0x64, 0x65, 0x32, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, - 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x0b, 0x6e, 0x6f, 0x64, 0x65, 0x32, 0x50, 0x6f, 0x6c, - 0x69, 0x63, 0x79, 0x22, 0x46, 0x0a, 0x13, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x47, 0x72, - 0x61, 0x70, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2f, 0x0a, 0x13, 0x69, 0x6e, - 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x75, 0x6e, 0x61, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, - 0x55, 0x6e, 0x61, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x64, 0x22, 0x64, 0x0a, 0x0c, 0x43, - 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x47, 0x72, 0x61, 0x70, 0x68, 0x12, 0x2a, 0x0a, 0x05, 0x6e, - 0x6f, 0x64, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x4e, 0x6f, 0x64, 0x65, - 0x52, 0x05, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x12, 0x28, 0x0a, 0x05, 0x65, 0x64, 0x67, 0x65, 0x73, - 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, - 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x64, 0x67, 0x65, 0x52, 0x05, 0x65, 0x64, 0x67, 0x65, - 0x73, 0x22, 0x41, 0x0a, 0x12, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x05, 0x74, 0x79, 0x70, 0x65, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, - 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x54, 0x79, 0x70, 0x65, 0x52, 0x05, 0x74, - 0x79, 0x70, 0x65, 0x73, 0x22, 0xe1, 0x01, 0x0a, 0x13, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, - 0x72, 0x69, 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, 0x0a, 0x16, - 0x62, 0x65, 0x74, 0x77, 0x65, 0x65, 0x6e, 0x6e, 0x65, 0x73, 0x73, 0x5f, 0x63, 0x65, 0x6e, 0x74, - 0x72, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x42, 0x65, 0x74, 0x77, 0x65, 0x65, 0x6e, - 0x6e, 0x65, 0x73, 0x73, 0x43, 0x65, 0x6e, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x52, 0x15, 0x62, 0x65, 0x74, 0x77, 0x65, 0x65, 0x6e, 0x6e, 0x65, 0x73, 0x73, - 0x43, 0x65, 0x6e, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x1a, 0x5c, 0x0a, 0x1a, 0x42, 0x65, - 0x74, 0x77, 0x65, 0x65, 0x6e, 0x6e, 0x65, 0x73, 0x73, 0x43, 0x65, 0x6e, 0x74, 0x72, 0x61, 0x6c, - 0x69, 0x74, 0x79, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x28, 0x0a, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x4e, 0x0a, 0x0b, 0x46, 0x6c, 0x6f, 0x61, - 0x74, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x29, 0x0a, - 0x10, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0f, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x69, - 0x7a, 0x65, 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x2e, 0x0a, 0x0f, 0x43, 0x68, 0x61, 0x6e, - 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x07, 0x63, - 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, - 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x22, 0x14, 0x0a, 0x12, 0x4e, 0x65, 0x74, 0x77, - 0x6f, 0x72, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xd5, - 0x03, 0x0a, 0x0b, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x25, - 0x0a, 0x0e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x5f, 0x64, 0x69, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x67, 0x72, 0x61, 0x70, 0x68, 0x44, 0x69, 0x61, - 0x6d, 0x65, 0x74, 0x65, 0x72, 0x12, 0x24, 0x0a, 0x0e, 0x61, 0x76, 0x67, 0x5f, 0x6f, 0x75, 0x74, - 0x5f, 0x64, 0x65, 0x67, 0x72, 0x65, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0c, 0x61, - 0x76, 0x67, 0x4f, 0x75, 0x74, 0x44, 0x65, 0x67, 0x72, 0x65, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x6d, - 0x61, 0x78, 0x5f, 0x6f, 0x75, 0x74, 0x5f, 0x64, 0x65, 0x67, 0x72, 0x65, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x6d, 0x61, 0x78, 0x4f, 0x75, 0x74, 0x44, 0x65, 0x67, 0x72, 0x65, - 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x75, 0x6d, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x6e, 0x75, 0x6d, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x12, 0x21, - 0x0a, 0x0c, 0x6e, 0x75, 0x6d, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x6e, 0x75, 0x6d, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x73, 0x12, 0x34, 0x0a, 0x16, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x6e, 0x65, 0x74, 0x77, 0x6f, - 0x72, 0x6b, 0x5f, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x14, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x43, - 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x12, 0x28, 0x0a, 0x10, 0x61, 0x76, 0x67, 0x5f, 0x63, - 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x01, 0x52, 0x0e, 0x61, 0x76, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x53, 0x69, 0x7a, - 0x65, 0x12, 0x28, 0x0a, 0x10, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6d, 0x69, 0x6e, - 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x6d, - 0x61, 0x78, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, - 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6d, 0x61, 0x78, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x35, 0x0a, 0x17, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x6e, 0x5f, - 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x5f, 0x73, 0x61, 0x74, - 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x14, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x6e, 0x43, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x53, 0x69, 0x7a, 0x65, 0x53, 0x61, 0x74, 0x12, 0x28, 0x0a, 0x10, - 0x6e, 0x75, 0x6d, 0x5f, 0x7a, 0x6f, 0x6d, 0x62, 0x69, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x73, - 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x6e, 0x75, 0x6d, 0x5a, 0x6f, 0x6d, 0x62, 0x69, - 0x65, 0x43, 0x68, 0x61, 0x6e, 0x73, 0x22, 0x0d, 0x0a, 0x0b, 0x53, 0x74, 0x6f, 0x70, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x0e, 0x0a, 0x0c, 0x53, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1b, 0x0a, 0x19, 0x47, 0x72, 0x61, 0x70, 0x68, 0x54, 0x6f, - 0x70, 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, - 0x6f, 0x6e, 0x22, 0xcd, 0x01, 0x0a, 0x13, 0x47, 0x72, 0x61, 0x70, 0x68, 0x54, 0x6f, 0x70, 0x6f, - 0x6c, 0x6f, 0x67, 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x34, 0x0a, 0x0c, 0x6e, 0x6f, - 0x64, 0x65, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x52, 0x0b, 0x6e, 0x6f, 0x64, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, - 0x12, 0x41, 0x0a, 0x0f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x75, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x64, 0x67, 0x65, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x52, 0x0e, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x73, 0x12, 0x3d, 0x0a, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x5f, 0x63, 0x68, - 0x61, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x0b, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, - 0x6e, 0x73, 0x22, 0xef, 0x02, 0x0a, 0x0a, 0x4e, 0x6f, 0x64, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x12, 0x20, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x01, - 0x20, 0x03, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, - 0x73, 0x65, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, - 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x69, 0x64, 0x65, 0x6e, 0x74, - 0x69, 0x74, 0x79, 0x4b, 0x65, 0x79, 0x12, 0x2b, 0x0a, 0x0f, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, - 0x5f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x42, - 0x02, 0x18, 0x01, 0x52, 0x0e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x46, 0x65, 0x61, 0x74, 0x75, - 0x72, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x05, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x6c, - 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x12, - 0x39, 0x0a, 0x0e, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, - 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x4e, 0x6f, 0x64, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x0d, 0x6e, 0x6f, 0x64, - 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x3b, 0x0a, 0x08, 0x66, 0x65, - 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x2e, - 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x66, - 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x1a, 0x4b, 0x0a, 0x0d, 0x46, 0x65, 0x61, 0x74, 0x75, - 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x3a, 0x02, 0x38, 0x01, 0x22, 0x91, 0x02, 0x0a, 0x11, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x45, 0x64, 0x67, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x07, 0x63, 0x68, - 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, - 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x32, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, - 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, - 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x63, - 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x63, - 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x12, 0x3b, 0x0a, 0x0e, 0x72, 0x6f, 0x75, 0x74, 0x69, - 0x6e, 0x67, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x50, - 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x0d, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x6f, - 0x6c, 0x69, 0x63, 0x79, 0x12, 0x29, 0x0a, 0x10, 0x61, 0x64, 0x76, 0x65, 0x72, 0x74, 0x69, 0x73, - 0x69, 0x6e, 0x67, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, - 0x61, 0x64, 0x76, 0x65, 0x72, 0x74, 0x69, 0x73, 0x69, 0x6e, 0x67, 0x4e, 0x6f, 0x64, 0x65, 0x12, - 0x27, 0x0a, 0x0f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x6e, 0x6f, - 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, - 0x74, 0x69, 0x6e, 0x67, 0x4e, 0x6f, 0x64, 0x65, 0x22, 0xa7, 0x01, 0x0a, 0x13, 0x43, 0x6c, 0x6f, - 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x12, 0x1b, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1a, 0x0a, - 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6c, 0x6f, - 0x73, 0x65, 0x64, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, - 0x52, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x32, - 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, - 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, - 0x6e, 0x74, 0x22, 0xcf, 0x01, 0x0a, 0x07, 0x48, 0x6f, 0x70, 0x48, 0x69, 0x6e, 0x74, 0x12, 0x17, - 0x0a, 0x07, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x6e, 0x6f, 0x64, 0x65, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f, - 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x06, 0x63, 0x68, - 0x61, 0x6e, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x66, 0x65, 0x65, 0x5f, 0x62, 0x61, 0x73, 0x65, - 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x66, 0x65, 0x65, - 0x42, 0x61, 0x73, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x3e, 0x0a, 0x1b, 0x66, 0x65, 0x65, 0x5f, - 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x5f, 0x6d, 0x69, 0x6c, - 0x6c, 0x69, 0x6f, 0x6e, 0x74, 0x68, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x19, 0x66, - 0x65, 0x65, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x4d, 0x69, - 0x6c, 0x6c, 0x69, 0x6f, 0x6e, 0x74, 0x68, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x63, 0x6c, 0x74, 0x76, - 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x0d, 0x52, 0x0f, 0x63, 0x6c, 0x74, 0x76, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x44, - 0x65, 0x6c, 0x74, 0x61, 0x22, 0x1e, 0x0a, 0x05, 0x53, 0x65, 0x74, 0x49, 0x44, 0x12, 0x15, 0x0a, - 0x06, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, - 0x65, 0x74, 0x49, 0x64, 0x22, 0x38, 0x0a, 0x09, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x48, 0x69, 0x6e, - 0x74, 0x12, 0x2b, 0x0a, 0x09, 0x68, 0x6f, 0x70, 0x5f, 0x68, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x01, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x6f, 0x70, - 0x48, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x68, 0x6f, 0x70, 0x48, 0x69, 0x6e, 0x74, 0x73, 0x22, 0xa8, - 0x01, 0x0a, 0x0f, 0x41, 0x4d, 0x50, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, - 0x74, 0x65, 0x12, 0x2d, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0e, 0x32, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, - 0x65, 0x48, 0x54, 0x4c, 0x43, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, - 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x5f, 0x69, 0x6e, 0x64, 0x65, - 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x49, - 0x6e, 0x64, 0x65, 0x78, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x5f, 0x74, - 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x73, 0x65, 0x74, 0x74, 0x6c, - 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x6d, 0x74, 0x5f, 0x70, 0x61, 0x69, - 0x64, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x6d, - 0x74, 0x50, 0x61, 0x69, 0x64, 0x4d, 0x73, 0x61, 0x74, 0x22, 0xc3, 0x09, 0x0a, 0x07, 0x49, 0x6e, - 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x65, 0x6d, 0x6f, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x6d, 0x65, 0x6d, 0x6f, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x5f, 0x70, - 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x72, - 0x50, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x72, 0x5f, 0x68, 0x61, - 0x73, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x72, 0x48, 0x61, 0x73, 0x68, 0x12, - 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x6d, - 0x73, 0x61, 0x74, 0x18, 0x17, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x4d, 0x73, 0x61, 0x74, 0x12, 0x1c, 0x0a, 0x07, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x08, 0x42, 0x02, 0x18, 0x01, 0x52, 0x07, 0x73, 0x65, 0x74, 0x74, 0x6c, - 0x65, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, - 0x61, 0x74, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x74, 0x74, 0x6c, - 0x65, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x73, 0x65, - 0x74, 0x74, 0x6c, 0x65, 0x44, 0x61, 0x74, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x70, 0x61, 0x79, 0x6d, - 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0e, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x29, 0x0a, 0x10, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, - 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x64, 0x65, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x61, 0x73, 0x68, 0x12, 0x16, 0x0a, 0x06, - 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x65, 0x78, - 0x70, 0x69, 0x72, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, - 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x66, 0x61, 0x6c, - 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x41, 0x64, 0x64, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6c, 0x74, - 0x76, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, - 0x63, 0x6c, 0x74, 0x76, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x31, 0x0a, 0x0b, 0x72, 0x6f, - 0x75, 0x74, 0x65, 0x5f, 0x68, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x10, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x48, 0x69, 0x6e, - 0x74, 0x52, 0x0a, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x48, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x18, 0x0a, - 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, - 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x5f, 0x69, - 0x6e, 0x64, 0x65, 0x78, 0x18, 0x10, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x61, 0x64, 0x64, 0x49, - 0x6e, 0x64, 0x65, 0x78, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x5f, 0x69, - 0x6e, 0x64, 0x65, 0x78, 0x18, 0x11, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x65, 0x74, 0x74, - 0x6c, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x1d, 0x0a, 0x08, 0x61, 0x6d, 0x74, 0x5f, 0x70, - 0x61, 0x69, 0x64, 0x18, 0x12, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x07, 0x61, - 0x6d, 0x74, 0x50, 0x61, 0x69, 0x64, 0x12, 0x20, 0x0a, 0x0c, 0x61, 0x6d, 0x74, 0x5f, 0x70, 0x61, - 0x69, 0x64, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x13, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x61, 0x6d, - 0x74, 0x50, 0x61, 0x69, 0x64, 0x53, 0x61, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x6d, 0x74, 0x5f, - 0x70, 0x61, 0x69, 0x64, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x14, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0b, 0x61, 0x6d, 0x74, 0x50, 0x61, 0x69, 0x64, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x31, 0x0a, 0x05, - 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x15, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x2e, 0x49, 0x6e, 0x76, 0x6f, - 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, - 0x28, 0x0a, 0x05, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x18, 0x16, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x48, 0x54, - 0x4c, 0x43, 0x52, 0x05, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x12, 0x38, 0x0a, 0x08, 0x66, 0x65, 0x61, - 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x18, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x2e, 0x46, 0x65, 0x61, 0x74, - 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, - 0x72, 0x65, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x73, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x65, 0x6e, - 0x64, 0x18, 0x19, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x73, 0x4b, 0x65, 0x79, 0x73, 0x65, - 0x6e, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x64, - 0x64, 0x72, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, - 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, 0x15, 0x0a, 0x06, 0x69, 0x73, 0x5f, 0x61, 0x6d, 0x70, 0x18, - 0x1b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x69, 0x73, 0x41, 0x6d, 0x70, 0x12, 0x4f, 0x0a, 0x11, - 0x61, 0x6d, 0x70, 0x5f, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x74, - 0x65, 0x18, 0x1c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x2e, 0x41, 0x6d, 0x70, 0x49, 0x6e, 0x76, 0x6f, 0x69, - 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0f, 0x61, 0x6d, - 0x70, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x1a, 0x4b, 0x0a, - 0x0d, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, - 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x6b, 0x65, 0x79, - 0x12, 0x24, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x5a, 0x0a, 0x14, 0x41, 0x6d, - 0x70, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x74, - 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2c, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x4d, 0x50, 0x49, - 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x41, 0x0a, 0x0c, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, - 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x08, 0x0a, 0x04, 0x4f, 0x50, 0x45, 0x4e, 0x10, 0x00, - 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x45, 0x54, 0x54, 0x4c, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0c, 0x0a, - 0x08, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0c, 0x0a, 0x08, 0x41, - 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x10, 0x03, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x22, - 0xfc, 0x03, 0x0a, 0x0b, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x48, 0x54, 0x4c, 0x43, 0x12, - 0x1b, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, - 0x42, 0x02, 0x30, 0x01, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, - 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x09, 0x68, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x19, 0x0a, 0x08, 0x61, - 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x61, - 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, - 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x61, - 0x63, 0x63, 0x65, 0x70, 0x74, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x61, - 0x63, 0x63, 0x65, 0x70, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0a, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, - 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0b, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, - 0x23, 0x0a, 0x0d, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, - 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x48, 0x65, - 0x69, 0x67, 0x68, 0x74, 0x12, 0x2d, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x08, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, - 0x69, 0x63, 0x65, 0x48, 0x54, 0x4c, 0x43, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, - 0x61, 0x74, 0x65, 0x12, 0x4c, 0x0a, 0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, - 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x48, 0x54, 0x4c, 0x43, 0x2e, - 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, - 0x72, 0x79, 0x52, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, - 0x73, 0x12, 0x2b, 0x0a, 0x12, 0x6d, 0x70, 0x70, 0x5f, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x61, - 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x6d, - 0x70, 0x70, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x1c, - 0x0a, 0x03, 0x61, 0x6d, 0x70, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x41, 0x4d, 0x50, 0x52, 0x03, 0x61, 0x6d, 0x70, 0x1a, 0x40, 0x0a, 0x12, - 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, - 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, - 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x8c, - 0x01, 0x0a, 0x03, 0x41, 0x4d, 0x50, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x73, - 0x68, 0x61, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x72, 0x6f, 0x6f, 0x74, - 0x53, 0x68, 0x61, 0x72, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x65, 0x74, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, - 0x63, 0x68, 0x69, 0x6c, 0x64, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0d, 0x52, 0x0a, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x12, 0x0a, - 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, - 0x68, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x22, 0x94, 0x01, - 0x0a, 0x12, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x72, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x72, 0x48, 0x61, 0x73, 0x68, 0x12, 0x27, 0x0a, 0x0f, 0x70, - 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x5f, 0x69, 0x6e, 0x64, 0x65, - 0x78, 0x18, 0x10, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x61, 0x64, 0x64, 0x49, 0x6e, 0x64, 0x65, - 0x78, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, - 0x72, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, - 0x41, 0x64, 0x64, 0x72, 0x22, 0x46, 0x0a, 0x0b, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, - 0x61, 0x73, 0x68, 0x12, 0x20, 0x0a, 0x0a, 0x72, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x5f, 0x73, 0x74, - 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x08, 0x72, 0x48, 0x61, - 0x73, 0x68, 0x53, 0x74, 0x72, 0x12, 0x15, 0x0a, 0x06, 0x72, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x72, 0x48, 0x61, 0x73, 0x68, 0x22, 0xa0, 0x01, 0x0a, - 0x12, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x6f, - 0x6e, 0x6c, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x70, 0x65, 0x6e, 0x64, 0x69, - 0x6e, 0x67, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, - 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x69, 0x6e, - 0x64, 0x65, 0x78, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x6e, 0x75, 0x6d, - 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x0e, 0x6e, 0x75, 0x6d, 0x4d, 0x61, 0x78, 0x49, 0x6e, 0x76, 0x6f, 0x69, - 0x63, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x64, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x64, 0x22, - 0x9b, 0x01, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x08, 0x69, 0x6e, 0x76, 0x6f, 0x69, - 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x08, 0x69, 0x6e, 0x76, 0x6f, 0x69, - 0x63, 0x65, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, - 0x78, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, - 0x6c, 0x61, 0x73, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, - 0x2c, 0x0a, 0x12, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x6f, - 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x66, 0x69, 0x72, - 0x73, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x22, 0x55, 0x0a, - 0x13, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, - 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x5f, 0x69, 0x6e, 0x64, 0x65, - 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x61, 0x64, 0x64, 0x49, 0x6e, 0x64, 0x65, - 0x78, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x5f, 0x69, 0x6e, 0x64, 0x65, - 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x49, - 0x6e, 0x64, 0x65, 0x78, 0x22, 0x8a, 0x05, 0x0a, 0x07, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, - 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, - 0x61, 0x73, 0x68, 0x12, 0x18, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x27, 0x0a, - 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x03, 0x66, 0x65, 0x65, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x03, 0x66, 0x65, 0x65, 0x12, 0x29, 0x0a, 0x10, - 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x50, - 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x5f, 0x73, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x53, 0x61, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x6d, 0x73, - 0x61, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x4d, - 0x73, 0x61, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x72, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x70, 0x61, - 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x34, 0x0a, 0x06, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x79, - 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x12, 0x17, 0x0a, 0x07, 0x66, 0x65, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x0b, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x06, 0x66, 0x65, 0x65, 0x53, 0x61, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x66, - 0x65, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x66, - 0x65, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6e, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x4e, 0x73, - 0x12, 0x28, 0x0a, 0x05, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x54, 0x4c, 0x43, 0x41, 0x74, 0x74, 0x65, - 0x6d, 0x70, 0x74, 0x52, 0x05, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x61, - 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x0f, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, - 0x42, 0x0a, 0x0e, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, - 0x6e, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x65, - 0x61, 0x73, 0x6f, 0x6e, 0x52, 0x0d, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x65, 0x61, - 0x73, 0x6f, 0x6e, 0x22, 0x46, 0x0a, 0x0d, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, - 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x49, 0x4e, 0x5f, 0x46, 0x4c, 0x49, 0x47, 0x48, 0x54, 0x10, 0x01, - 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x55, 0x43, 0x43, 0x45, 0x45, 0x44, 0x45, 0x44, 0x10, 0x02, 0x12, - 0x0a, 0x0a, 0x06, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x03, 0x4a, 0x04, 0x08, 0x04, 0x10, - 0x05, 0x22, 0xd5, 0x02, 0x0a, 0x0b, 0x48, 0x54, 0x4c, 0x43, 0x41, 0x74, 0x74, 0x65, 0x6d, 0x70, - 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x5f, 0x69, 0x64, 0x18, - 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x61, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x49, 0x64, - 0x12, 0x35, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, - 0x32, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x54, 0x4c, 0x43, 0x41, 0x74, 0x74, - 0x65, 0x6d, 0x70, 0x74, 0x2e, 0x48, 0x54, 0x4c, 0x43, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, - 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x22, 0x0a, 0x05, 0x72, 0x6f, 0x75, 0x74, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, - 0x6f, 0x75, 0x74, 0x65, 0x52, 0x05, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x61, - 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6e, 0x73, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x61, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x54, 0x69, 0x6d, - 0x65, 0x4e, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x5f, 0x74, - 0x69, 0x6d, 0x65, 0x5f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x72, 0x65, - 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x4e, 0x73, 0x12, 0x28, 0x0a, 0x07, 0x66, - 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x07, 0x66, 0x61, - 0x69, 0x6c, 0x75, 0x72, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, - 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, - 0x65, 0x22, 0x36, 0x0a, 0x0a, 0x48, 0x54, 0x4c, 0x43, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, - 0x0d, 0x0a, 0x09, 0x49, 0x4e, 0x5f, 0x46, 0x4c, 0x49, 0x47, 0x48, 0x54, 0x10, 0x00, 0x12, 0x0d, - 0x0a, 0x09, 0x53, 0x55, 0x43, 0x43, 0x45, 0x45, 0x44, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0a, 0x0a, - 0x06, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x02, 0x22, 0xa6, 0x01, 0x0a, 0x13, 0x4c, 0x69, - 0x73, 0x74, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x2d, 0x0a, 0x12, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x69, 0x6e, 0x63, - 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x69, - 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x49, 0x6e, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, - 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x4f, 0x66, 0x66, - 0x73, 0x65, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x61, 0x78, 0x5f, 0x70, 0x61, 0x79, 0x6d, 0x65, - 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x50, 0x61, - 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, - 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, - 0x65, 0x64, 0x22, 0x9c, 0x01, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x79, 0x6d, 0x65, - 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x08, 0x70, - 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x08, 0x70, - 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x66, 0x69, 0x72, 0x73, 0x74, - 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x10, 0x66, 0x69, 0x72, 0x73, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x4f, - 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x2a, 0x0a, 0x11, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x69, 0x6e, - 0x64, 0x65, 0x78, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x4f, 0x66, 0x66, 0x73, 0x65, - 0x74, 0x22, 0x65, 0x0a, 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, - 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, - 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x2a, 0x0a, 0x11, - 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x5f, 0x6f, 0x6e, 0x6c, - 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x48, - 0x74, 0x6c, 0x63, 0x73, 0x4f, 0x6e, 0x6c, 0x79, 0x22, 0x78, 0x0a, 0x18, 0x44, 0x65, 0x6c, 0x65, - 0x74, 0x65, 0x41, 0x6c, 0x6c, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x30, 0x0a, 0x14, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x5f, 0x70, - 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x12, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, - 0x74, 0x73, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x2a, 0x0a, 0x11, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, - 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x0f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x73, 0x4f, 0x6e, - 0x6c, 0x79, 0x22, 0x17, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, - 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1b, 0x0a, 0x19, 0x44, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, 0x6c, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xbf, 0x01, 0x0a, 0x15, 0x41, 0x62, 0x61, - 0x6e, 0x64, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x38, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x70, 0x6f, - 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x0c, - 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x39, 0x0a, 0x19, - 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, - 0x73, 0x68, 0x69, 0x6d, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x16, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, - 0x68, 0x69, 0x6d, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x31, 0x0a, 0x16, 0x69, 0x5f, 0x6b, 0x6e, 0x6f, - 0x77, 0x5f, 0x77, 0x68, 0x61, 0x74, 0x5f, 0x69, 0x5f, 0x61, 0x6d, 0x5f, 0x64, 0x6f, 0x69, 0x6e, - 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x69, 0x4b, 0x6e, 0x6f, 0x77, 0x57, 0x68, - 0x61, 0x74, 0x49, 0x41, 0x6d, 0x44, 0x6f, 0x69, 0x6e, 0x67, 0x22, 0x18, 0x0a, 0x16, 0x41, 0x62, - 0x61, 0x6e, 0x64, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x46, 0x0a, 0x11, 0x44, 0x65, 0x62, 0x75, 0x67, 0x4c, 0x65, 0x76, - 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x68, 0x6f, - 0x77, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x73, 0x68, 0x6f, 0x77, 0x12, 0x1d, 0x0a, - 0x0a, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x09, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x53, 0x70, 0x65, 0x63, 0x22, 0x35, 0x0a, 0x12, - 0x44, 0x65, 0x62, 0x75, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x75, 0x62, 0x5f, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, - 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x75, 0x62, 0x53, 0x79, 0x73, 0x74, - 0x65, 0x6d, 0x73, 0x22, 0x27, 0x0a, 0x0c, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x53, 0x74, 0x72, - 0x69, 0x6e, 0x67, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x5f, 0x72, 0x65, 0x71, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x61, 0x79, 0x52, 0x65, 0x71, 0x22, 0xb0, 0x04, 0x0a, - 0x06, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x74, 0x69, - 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, - 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, - 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x21, 0x0a, 0x0c, - 0x6e, 0x75, 0x6d, 0x5f, 0x73, 0x61, 0x74, 0x6f, 0x73, 0x68, 0x69, 0x73, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0b, 0x6e, 0x75, 0x6d, 0x53, 0x61, 0x74, 0x6f, 0x73, 0x68, 0x69, 0x73, 0x12, - 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x16, 0x0a, - 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x65, - 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, - 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x29, 0x0a, 0x10, 0x64, 0x65, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x61, - 0x73, 0x68, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x5f, 0x61, - 0x64, 0x64, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x66, 0x61, 0x6c, 0x6c, 0x62, - 0x61, 0x63, 0x6b, 0x41, 0x64, 0x64, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6c, 0x74, 0x76, 0x5f, - 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x63, 0x6c, - 0x74, 0x76, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x31, 0x0a, 0x0b, 0x72, 0x6f, 0x75, 0x74, - 0x65, 0x5f, 0x68, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x48, 0x69, 0x6e, 0x74, 0x52, - 0x0a, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x48, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x70, - 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x0b, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, 0x19, - 0x0a, 0x08, 0x6e, 0x75, 0x6d, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x07, 0x6e, 0x75, 0x6d, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x37, 0x0a, 0x08, 0x66, 0x65, 0x61, - 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, - 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, - 0x65, 0x73, 0x1a, 0x4b, 0x0a, 0x0d, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, - 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x61, - 0x74, 0x75, 0x72, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, - 0x59, 0x0a, 0x07, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, - 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1f, - 0x0a, 0x0b, 0x69, 0x73, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x0a, 0x69, 0x73, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, - 0x19, 0x0a, 0x08, 0x69, 0x73, 0x5f, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x07, 0x69, 0x73, 0x4b, 0x6e, 0x6f, 0x77, 0x6e, 0x22, 0x12, 0x0a, 0x10, 0x46, 0x65, - 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xb3, - 0x01, 0x0a, 0x10, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x46, 0x65, 0x65, 0x52, 0x65, 0x70, - 0x6f, 0x72, 0x74, 0x12, 0x1b, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, - 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x70, 0x6f, 0x69, 0x6e, - 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x66, 0x65, - 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x62, 0x61, - 0x73, 0x65, 0x46, 0x65, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x1e, 0x0a, 0x0b, 0x66, 0x65, 0x65, - 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x6d, 0x69, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, - 0x66, 0x65, 0x65, 0x50, 0x65, 0x72, 0x4d, 0x69, 0x6c, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x65, 0x65, - 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x07, 0x66, 0x65, 0x65, - 0x52, 0x61, 0x74, 0x65, 0x22, 0xb5, 0x01, 0x0a, 0x11, 0x46, 0x65, 0x65, 0x52, 0x65, 0x70, 0x6f, - 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x0c, 0x63, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x66, 0x65, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x46, 0x65, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x6e, - 0x65, 0x6c, 0x46, 0x65, 0x65, 0x73, 0x12, 0x1e, 0x0a, 0x0b, 0x64, 0x61, 0x79, 0x5f, 0x66, 0x65, - 0x65, 0x5f, 0x73, 0x75, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x64, 0x61, 0x79, - 0x46, 0x65, 0x65, 0x53, 0x75, 0x6d, 0x12, 0x20, 0x0a, 0x0c, 0x77, 0x65, 0x65, 0x6b, 0x5f, 0x66, - 0x65, 0x65, 0x5f, 0x73, 0x75, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x77, 0x65, - 0x65, 0x6b, 0x46, 0x65, 0x65, 0x53, 0x75, 0x6d, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x6f, 0x6e, 0x74, - 0x68, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x73, 0x75, 0x6d, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, - 0x0b, 0x6d, 0x6f, 0x6e, 0x74, 0x68, 0x46, 0x65, 0x65, 0x53, 0x75, 0x6d, 0x22, 0xd4, 0x02, 0x0a, - 0x13, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x06, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x06, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x12, 0x34, - 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, - 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x50, + 0x75, 0x72, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x59, + 0x0a, 0x07, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, + 0x0b, 0x69, 0x73, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x0a, 0x69, 0x73, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, 0x19, + 0x0a, 0x08, 0x69, 0x73, 0x5f, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x07, 0x69, 0x73, 0x4b, 0x6e, 0x6f, 0x77, 0x6e, 0x22, 0x12, 0x0a, 0x10, 0x46, 0x65, 0x65, + 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xb3, 0x01, + 0x0a, 0x10, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x46, 0x65, 0x65, 0x52, 0x65, 0x70, 0x6f, + 0x72, 0x74, 0x12, 0x1b, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, + 0x23, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x66, 0x65, 0x65, - 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x62, 0x61, 0x73, - 0x65, 0x46, 0x65, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x65, 0x65, 0x5f, + 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x62, 0x61, 0x73, + 0x65, 0x46, 0x65, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x1e, 0x0a, 0x0b, 0x66, 0x65, 0x65, 0x5f, + 0x70, 0x65, 0x72, 0x5f, 0x6d, 0x69, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x66, + 0x65, 0x65, 0x50, 0x65, 0x72, 0x4d, 0x69, 0x6c, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x07, 0x66, 0x65, 0x65, 0x52, - 0x61, 0x74, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, - 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x74, 0x69, - 0x6d, 0x65, 0x4c, 0x6f, 0x63, 0x6b, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x22, 0x0a, 0x0d, 0x6d, - 0x61, 0x78, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x04, 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x48, 0x74, 0x6c, 0x63, 0x4d, 0x73, 0x61, 0x74, 0x12, - 0x22, 0x0a, 0x0d, 0x6d, 0x69, 0x6e, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x73, 0x61, 0x74, - 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x6d, 0x69, 0x6e, 0x48, 0x74, 0x6c, 0x63, 0x4d, - 0x73, 0x61, 0x74, 0x12, 0x35, 0x0a, 0x17, 0x6d, 0x69, 0x6e, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, - 0x6d, 0x73, 0x61, 0x74, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x64, 0x18, 0x08, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x6d, 0x69, 0x6e, 0x48, 0x74, 0x6c, 0x63, 0x4d, 0x73, 0x61, - 0x74, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x64, 0x42, 0x07, 0x0a, 0x05, 0x73, 0x63, - 0x6f, 0x70, 0x65, 0x22, 0x8c, 0x01, 0x0a, 0x0c, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, - 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, - 0x74, 0x12, 0x2c, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0e, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, - 0x21, 0x0a, 0x0c, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x72, 0x72, - 0x6f, 0x72, 0x22, 0x52, 0x0a, 0x14, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x0e, 0x66, 0x61, - 0x69, 0x6c, 0x65, 0x64, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x65, - 0x64, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x0d, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x22, 0x9d, 0x01, 0x0a, 0x18, 0x46, 0x6f, 0x72, 0x77, 0x61, - 0x72, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, - 0x6d, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x21, 0x0a, - 0x0c, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, - 0x12, 0x24, 0x0a, 0x0e, 0x6e, 0x75, 0x6d, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x65, 0x76, 0x65, 0x6e, - 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x6e, 0x75, 0x6d, 0x4d, 0x61, 0x78, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x22, 0xbb, 0x02, 0x0a, 0x0f, 0x46, 0x6f, 0x72, 0x77, 0x61, - 0x72, 0x64, 0x69, 0x6e, 0x67, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x20, 0x0a, 0x09, 0x74, 0x69, - 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x18, - 0x01, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x20, 0x0a, 0x0a, - 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x5f, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, - 0x42, 0x02, 0x30, 0x01, 0x52, 0x08, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x49, 0x6e, 0x12, 0x22, - 0x0a, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x5f, 0x6f, 0x75, 0x74, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x4f, - 0x75, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x61, 0x6d, 0x74, 0x5f, 0x69, 0x6e, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x04, 0x52, 0x05, 0x61, 0x6d, 0x74, 0x49, 0x6e, 0x12, 0x17, 0x0a, 0x07, 0x61, 0x6d, 0x74, - 0x5f, 0x6f, 0x75, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x74, 0x4f, - 0x75, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x66, 0x65, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, - 0x03, 0x66, 0x65, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x65, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, - 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x66, 0x65, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, - 0x1e, 0x0a, 0x0b, 0x61, 0x6d, 0x74, 0x5f, 0x69, 0x6e, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x09, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x61, 0x6d, 0x74, 0x49, 0x6e, 0x4d, 0x73, 0x61, 0x74, 0x12, - 0x20, 0x0a, 0x0c, 0x61, 0x6d, 0x74, 0x5f, 0x6f, 0x75, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, - 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x61, 0x6d, 0x74, 0x4f, 0x75, 0x74, 0x4d, 0x73, 0x61, - 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x5f, 0x6e, - 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, - 0x6d, 0x70, 0x4e, 0x73, 0x22, 0x8c, 0x01, 0x0a, 0x19, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, - 0x69, 0x6e, 0x67, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x43, 0x0a, 0x11, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, - 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x10, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, - 0x67, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6c, 0x61, 0x73, 0x74, 0x5f, - 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0d, 0x52, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x49, 0x6e, - 0x64, 0x65, 0x78, 0x22, 0x50, 0x0a, 0x1a, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x68, 0x61, - 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x32, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, - 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x22, 0x64, 0x0a, 0x0d, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, 0x32, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, - 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, - 0x09, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x68, - 0x61, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x22, 0x73, 0x0a, 0x0f, 0x4d, - 0x75, 0x6c, 0x74, 0x69, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, 0x34, - 0x0a, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, - 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, - 0x69, 0x6e, 0x74, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x5f, 0x63, 0x68, - 0x61, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x61, 0x74, 0x65, 0x22, 0xb5, 0x01, 0x0a, 0x11, 0x46, 0x65, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x0c, 0x63, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x66, 0x65, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x46, + 0x65, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x46, 0x65, 0x65, 0x73, 0x12, 0x1e, 0x0a, 0x0b, 0x64, 0x61, 0x79, 0x5f, 0x66, 0x65, 0x65, + 0x5f, 0x73, 0x75, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x64, 0x61, 0x79, 0x46, + 0x65, 0x65, 0x53, 0x75, 0x6d, 0x12, 0x20, 0x0a, 0x0c, 0x77, 0x65, 0x65, 0x6b, 0x5f, 0x66, 0x65, + 0x65, 0x5f, 0x73, 0x75, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x77, 0x65, 0x65, + 0x6b, 0x46, 0x65, 0x65, 0x53, 0x75, 0x6d, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x6f, 0x6e, 0x74, 0x68, + 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x73, 0x75, 0x6d, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, + 0x6d, 0x6f, 0x6e, 0x74, 0x68, 0x46, 0x65, 0x65, 0x53, 0x75, 0x6d, 0x22, 0xf6, 0x02, 0x0a, 0x13, + 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x06, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x06, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x12, 0x34, 0x0a, + 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, + 0x69, 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x66, 0x65, 0x65, 0x5f, + 0x6d, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x62, 0x61, 0x73, 0x65, + 0x46, 0x65, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x65, 0x65, 0x5f, 0x72, + 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x07, 0x66, 0x65, 0x65, 0x52, 0x61, + 0x74, 0x65, 0x12, 0x20, 0x0a, 0x0c, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x70, + 0x70, 0x6d, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x66, 0x65, 0x65, 0x52, 0x61, 0x74, + 0x65, 0x50, 0x70, 0x6d, 0x12, 0x26, 0x0a, 0x0f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6c, 0x6f, 0x63, + 0x6b, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x74, + 0x69, 0x6d, 0x65, 0x4c, 0x6f, 0x63, 0x6b, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x22, 0x0a, 0x0d, + 0x6d, 0x61, 0x78, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x48, 0x74, 0x6c, 0x63, 0x4d, 0x73, 0x61, 0x74, + 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x69, 0x6e, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x73, 0x61, + 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x6d, 0x69, 0x6e, 0x48, 0x74, 0x6c, 0x63, + 0x4d, 0x73, 0x61, 0x74, 0x12, 0x35, 0x0a, 0x17, 0x6d, 0x69, 0x6e, 0x5f, 0x68, 0x74, 0x6c, 0x63, + 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x64, 0x18, + 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x6d, 0x69, 0x6e, 0x48, 0x74, 0x6c, 0x63, 0x4d, 0x73, + 0x61, 0x74, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x64, 0x42, 0x07, 0x0a, 0x05, 0x73, + 0x63, 0x6f, 0x70, 0x65, 0x22, 0x8c, 0x01, 0x0a, 0x0c, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, + 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, + 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, + 0x12, 0x21, 0x0a, 0x0c, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x72, + 0x72, 0x6f, 0x72, 0x22, 0x52, 0x0a, 0x14, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x0e, 0x66, + 0x61, 0x69, 0x6c, 0x65, 0x64, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x61, 0x69, 0x6c, + 0x65, 0x64, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x0d, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x22, 0x9d, 0x01, 0x0a, 0x18, 0x46, 0x6f, 0x72, 0x77, + 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, + 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, + 0x69, 0x6d, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x21, + 0x0a, 0x0c, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x4f, 0x66, 0x66, 0x73, 0x65, + 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x6e, 0x75, 0x6d, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x65, 0x76, 0x65, + 0x6e, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x6e, 0x75, 0x6d, 0x4d, 0x61, + 0x78, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x22, 0xbb, 0x02, 0x0a, 0x0f, 0x46, 0x6f, 0x72, 0x77, + 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x20, 0x0a, 0x09, 0x74, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, + 0x18, 0x01, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x20, 0x0a, + 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x5f, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x08, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x49, 0x6e, 0x12, + 0x22, 0x0a, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x5f, 0x6f, 0x75, 0x74, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, + 0x4f, 0x75, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x61, 0x6d, 0x74, 0x5f, 0x69, 0x6e, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x05, 0x61, 0x6d, 0x74, 0x49, 0x6e, 0x12, 0x17, 0x0a, 0x07, 0x61, 0x6d, + 0x74, 0x5f, 0x6f, 0x75, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x74, + 0x4f, 0x75, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x66, 0x65, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x03, 0x66, 0x65, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x65, 0x65, 0x5f, 0x6d, 0x73, 0x61, + 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x66, 0x65, 0x65, 0x4d, 0x73, 0x61, 0x74, + 0x12, 0x1e, 0x0a, 0x0b, 0x61, 0x6d, 0x74, 0x5f, 0x69, 0x6e, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, + 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x61, 0x6d, 0x74, 0x49, 0x6e, 0x4d, 0x73, 0x61, 0x74, + 0x12, 0x20, 0x0a, 0x0c, 0x61, 0x6d, 0x74, 0x5f, 0x6f, 0x75, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, + 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x61, 0x6d, 0x74, 0x4f, 0x75, 0x74, 0x4d, 0x73, + 0x61, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x5f, + 0x6e, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x4e, 0x73, 0x22, 0x8c, 0x01, 0x0a, 0x19, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, + 0x64, 0x69, 0x6e, 0x67, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x11, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, + 0x67, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, + 0x67, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x10, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, + 0x6e, 0x67, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6c, 0x61, 0x73, 0x74, + 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x49, + 0x6e, 0x64, 0x65, 0x78, 0x22, 0x50, 0x0a, 0x1a, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x32, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x09, 0x63, 0x68, 0x61, + 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x22, 0x64, 0x0a, 0x0d, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, 0x32, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, + 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, + 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x63, + 0x68, 0x61, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x22, 0x73, 0x0a, 0x0f, + 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, + 0x34, 0x0a, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x50, + 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x5f, 0x63, + 0x68, 0x61, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x0f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, + 0x70, 0x22, 0x19, 0x0a, 0x17, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x45, + 0x78, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x9f, 0x01, 0x0a, + 0x12, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x6e, 0x61, 0x70, 0x73, + 0x68, 0x6f, 0x74, 0x12, 0x45, 0x0a, 0x13, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x5f, 0x63, 0x68, + 0x61, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x52, 0x11, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x43, + 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x42, 0x0a, 0x11, 0x6d, 0x75, + 0x6c, 0x74, 0x69, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, + 0x6c, 0x74, 0x69, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x0f, 0x6d, + 0x75, 0x6c, 0x74, 0x69, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x22, 0x49, + 0x0a, 0x0e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, + 0x12, 0x37, 0x0a, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x0b, 0x63, 0x68, + 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x22, 0x8e, 0x01, 0x0a, 0x18, 0x52, 0x65, + 0x73, 0x74, 0x6f, 0x72, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3a, 0x0a, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x62, + 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, + 0x75, 0x70, 0x73, 0x48, 0x00, 0x52, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, + 0x70, 0x73, 0x12, 0x2c, 0x0a, 0x11, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x5f, 0x63, 0x68, 0x61, 0x6e, + 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x0f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, - 0x22, 0x19, 0x0a, 0x17, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x45, 0x78, - 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x9f, 0x01, 0x0a, 0x12, - 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, - 0x6f, 0x74, 0x12, 0x45, 0x0a, 0x13, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x5f, 0x63, 0x68, 0x61, - 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, - 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x52, 0x11, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x43, 0x68, - 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x42, 0x0a, 0x11, 0x6d, 0x75, 0x6c, - 0x74, 0x69, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x6c, - 0x74, 0x69, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x0f, 0x6d, 0x75, - 0x6c, 0x74, 0x69, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x22, 0x49, 0x0a, - 0x0e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, - 0x37, 0x0a, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x0b, 0x63, 0x68, 0x61, - 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x22, 0x8e, 0x01, 0x0a, 0x18, 0x52, 0x65, 0x73, - 0x74, 0x6f, 0x72, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3a, 0x0a, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x62, 0x61, - 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, - 0x70, 0x73, 0x48, 0x00, 0x52, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, - 0x73, 0x12, 0x2c, 0x0a, 0x11, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, - 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x0f, - 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x42, - 0x08, 0x0a, 0x06, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x22, 0x17, 0x0a, 0x15, 0x52, 0x65, 0x73, - 0x74, 0x6f, 0x72, 0x65, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x1b, 0x0a, 0x19, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, - 0x6b, 0x75, 0x70, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, - 0x1a, 0x0a, 0x18, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, - 0x6b, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x44, 0x0a, 0x12, 0x4d, - 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x22, 0xb0, 0x01, 0x0a, 0x13, 0x42, 0x61, 0x6b, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, - 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3b, 0x0a, 0x0b, 0x70, 0x65, 0x72, - 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x50, - 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, - 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1e, 0x0a, 0x0b, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x6b, - 0x65, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x72, 0x6f, 0x6f, - 0x74, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x12, 0x3c, 0x0a, 0x1a, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, - 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, - 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x18, 0x61, 0x6c, 0x6c, 0x6f, - 0x77, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, - 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x32, 0x0a, 0x14, 0x42, 0x61, 0x6b, 0x65, 0x4d, 0x61, 0x63, 0x61, - 0x72, 0x6f, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, - 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x22, 0x18, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, - 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x22, 0x3b, 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, - 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x20, 0x0a, - 0x0c, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x04, 0x52, 0x0a, 0x72, 0x6f, 0x6f, 0x74, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x73, 0x22, - 0x39, 0x0a, 0x17, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, - 0x6e, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1e, 0x0a, 0x0b, 0x72, 0x6f, - 0x6f, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, - 0x09, 0x72, 0x6f, 0x6f, 0x74, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x22, 0x34, 0x0a, 0x18, 0x44, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, - 0x22, 0x55, 0x0a, 0x16, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, - 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x3b, 0x0a, 0x0b, 0x70, 0x65, + 0x42, 0x08, 0x0a, 0x06, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x22, 0x17, 0x0a, 0x15, 0x52, 0x65, + 0x73, 0x74, 0x6f, 0x72, 0x65, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x1b, 0x0a, 0x19, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, + 0x63, 0x6b, 0x75, 0x70, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x22, 0x1a, 0x0a, 0x18, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, + 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x44, 0x0a, 0x12, + 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x22, 0xb0, 0x01, 0x0a, 0x13, 0x42, 0x61, 0x6b, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, + 0x6f, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3b, 0x0a, 0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x70, 0x65, 0x72, 0x6d, - 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x18, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x50, - 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x22, 0xe4, 0x01, 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x64, 0x0a, - 0x12, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, - 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, - 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x52, 0x11, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, - 0x6f, 0x6e, 0x73, 0x1a, 0x63, 0x0a, 0x16, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x50, 0x65, 0x72, - 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, - 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, - 0x33, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x50, - 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xb0, 0x08, 0x0a, 0x07, 0x46, 0x61, 0x69, - 0x6c, 0x75, 0x72, 0x65, 0x12, 0x2e, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x75, - 0x72, 0x65, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x04, - 0x63, 0x6f, 0x64, 0x65, 0x12, 0x3b, 0x0a, 0x0e, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, - 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x52, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x68, 0x74, 0x6c, 0x63, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x22, - 0x0a, 0x0d, 0x6f, 0x6e, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x68, 0x61, 0x5f, 0x32, 0x35, 0x36, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x6f, 0x6e, 0x69, 0x6f, 0x6e, 0x53, 0x68, 0x61, 0x32, - 0x35, 0x36, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, - 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x63, 0x6c, 0x74, 0x76, 0x45, 0x78, 0x70, - 0x69, 0x72, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x0d, 0x52, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x12, 0x30, 0x0a, 0x14, 0x66, 0x61, 0x69, - 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x6e, 0x64, 0x65, - 0x78, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x12, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, - 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x16, 0x0a, 0x06, 0x68, - 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x68, 0x65, 0x69, - 0x67, 0x68, 0x74, 0x22, 0xef, 0x05, 0x0a, 0x0b, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x43, - 0x6f, 0x64, 0x65, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x45, 0x53, 0x45, 0x52, 0x56, 0x45, 0x44, 0x10, - 0x00, 0x12, 0x28, 0x0a, 0x24, 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x4f, - 0x52, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x50, 0x41, 0x59, 0x4d, 0x45, 0x4e, - 0x54, 0x5f, 0x44, 0x45, 0x54, 0x41, 0x49, 0x4c, 0x53, 0x10, 0x01, 0x12, 0x1c, 0x0a, 0x18, 0x49, - 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x50, 0x41, 0x59, 0x4d, 0x45, 0x4e, 0x54, - 0x5f, 0x41, 0x4d, 0x4f, 0x55, 0x4e, 0x54, 0x10, 0x02, 0x12, 0x1f, 0x0a, 0x1b, 0x46, 0x49, 0x4e, - 0x41, 0x4c, 0x5f, 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x43, 0x4c, 0x54, - 0x56, 0x5f, 0x45, 0x58, 0x50, 0x49, 0x52, 0x59, 0x10, 0x03, 0x12, 0x1f, 0x0a, 0x1b, 0x46, 0x49, - 0x4e, 0x41, 0x4c, 0x5f, 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x48, 0x54, - 0x4c, 0x43, 0x5f, 0x41, 0x4d, 0x4f, 0x55, 0x4e, 0x54, 0x10, 0x04, 0x12, 0x19, 0x0a, 0x15, 0x46, - 0x49, 0x4e, 0x41, 0x4c, 0x5f, 0x45, 0x58, 0x50, 0x49, 0x52, 0x59, 0x5f, 0x54, 0x4f, 0x4f, 0x5f, - 0x53, 0x4f, 0x4f, 0x4e, 0x10, 0x05, 0x12, 0x11, 0x0a, 0x0d, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, - 0x44, 0x5f, 0x52, 0x45, 0x41, 0x4c, 0x4d, 0x10, 0x06, 0x12, 0x13, 0x0a, 0x0f, 0x45, 0x58, 0x50, - 0x49, 0x52, 0x59, 0x5f, 0x54, 0x4f, 0x4f, 0x5f, 0x53, 0x4f, 0x4f, 0x4e, 0x10, 0x07, 0x12, 0x19, - 0x0a, 0x15, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, - 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x10, 0x08, 0x12, 0x16, 0x0a, 0x12, 0x49, 0x4e, 0x56, - 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x48, 0x4d, 0x41, 0x43, 0x10, - 0x09, 0x12, 0x15, 0x0a, 0x11, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x4f, 0x4e, 0x49, - 0x4f, 0x4e, 0x5f, 0x4b, 0x45, 0x59, 0x10, 0x0a, 0x12, 0x18, 0x0a, 0x14, 0x41, 0x4d, 0x4f, 0x55, - 0x4e, 0x54, 0x5f, 0x42, 0x45, 0x4c, 0x4f, 0x57, 0x5f, 0x4d, 0x49, 0x4e, 0x49, 0x4d, 0x55, 0x4d, - 0x10, 0x0b, 0x12, 0x14, 0x0a, 0x10, 0x46, 0x45, 0x45, 0x5f, 0x49, 0x4e, 0x53, 0x55, 0x46, 0x46, - 0x49, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x10, 0x0c, 0x12, 0x19, 0x0a, 0x15, 0x49, 0x4e, 0x43, 0x4f, - 0x52, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x43, 0x4c, 0x54, 0x56, 0x5f, 0x45, 0x58, 0x50, 0x49, 0x52, - 0x59, 0x10, 0x0d, 0x12, 0x14, 0x0a, 0x10, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x5f, 0x44, - 0x49, 0x53, 0x41, 0x42, 0x4c, 0x45, 0x44, 0x10, 0x0e, 0x12, 0x1d, 0x0a, 0x19, 0x54, 0x45, 0x4d, - 0x50, 0x4f, 0x52, 0x41, 0x52, 0x59, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x5f, 0x46, - 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, 0x0f, 0x12, 0x21, 0x0a, 0x1d, 0x52, 0x45, 0x51, 0x55, - 0x49, 0x52, 0x45, 0x44, 0x5f, 0x4e, 0x4f, 0x44, 0x45, 0x5f, 0x46, 0x45, 0x41, 0x54, 0x55, 0x52, - 0x45, 0x5f, 0x4d, 0x49, 0x53, 0x53, 0x49, 0x4e, 0x47, 0x10, 0x10, 0x12, 0x24, 0x0a, 0x20, 0x52, - 0x45, 0x51, 0x55, 0x49, 0x52, 0x45, 0x44, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x5f, - 0x46, 0x45, 0x41, 0x54, 0x55, 0x52, 0x45, 0x5f, 0x4d, 0x49, 0x53, 0x53, 0x49, 0x4e, 0x47, 0x10, - 0x11, 0x12, 0x15, 0x0a, 0x11, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x4e, 0x45, 0x58, - 0x54, 0x5f, 0x50, 0x45, 0x45, 0x52, 0x10, 0x12, 0x12, 0x1a, 0x0a, 0x16, 0x54, 0x45, 0x4d, 0x50, - 0x4f, 0x52, 0x41, 0x52, 0x59, 0x5f, 0x4e, 0x4f, 0x44, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, - 0x52, 0x45, 0x10, 0x13, 0x12, 0x1a, 0x0a, 0x16, 0x50, 0x45, 0x52, 0x4d, 0x41, 0x4e, 0x45, 0x4e, - 0x54, 0x5f, 0x4e, 0x4f, 0x44, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, 0x14, - 0x12, 0x1d, 0x0a, 0x19, 0x50, 0x45, 0x52, 0x4d, 0x41, 0x4e, 0x45, 0x4e, 0x54, 0x5f, 0x43, 0x48, - 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, 0x15, 0x12, - 0x12, 0x0a, 0x0e, 0x45, 0x58, 0x50, 0x49, 0x52, 0x59, 0x5f, 0x54, 0x4f, 0x4f, 0x5f, 0x46, 0x41, - 0x52, 0x10, 0x16, 0x12, 0x0f, 0x0a, 0x0b, 0x4d, 0x50, 0x50, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, - 0x55, 0x54, 0x10, 0x17, 0x12, 0x19, 0x0a, 0x15, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, - 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x50, 0x41, 0x59, 0x4c, 0x4f, 0x41, 0x44, 0x10, 0x18, 0x12, - 0x15, 0x0a, 0x10, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x5f, 0x46, 0x41, 0x49, 0x4c, - 0x55, 0x52, 0x45, 0x10, 0xe5, 0x07, 0x12, 0x14, 0x0a, 0x0f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, - 0x4e, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, 0xe6, 0x07, 0x12, 0x17, 0x0a, 0x12, - 0x55, 0x4e, 0x52, 0x45, 0x41, 0x44, 0x41, 0x42, 0x4c, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, - 0x52, 0x45, 0x10, 0xe7, 0x07, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x22, 0xb3, 0x03, 0x0a, 0x0d, - 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x1c, 0x0a, - 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, - 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x09, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x48, 0x61, 0x73, 0x68, 0x12, 0x1b, 0x0a, 0x07, 0x63, 0x68, - 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, - 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, - 0x74, 0x61, 0x6d, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, - 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x23, 0x0a, 0x0d, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, - 0x5f, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x6d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x0d, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x12, - 0x26, 0x0a, 0x0f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x64, 0x65, 0x6c, - 0x74, 0x61, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x74, 0x69, 0x6d, 0x65, 0x4c, 0x6f, - 0x63, 0x6b, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x2a, 0x0a, 0x11, 0x68, 0x74, 0x6c, 0x63, 0x5f, - 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x04, 0x52, 0x0f, 0x68, 0x74, 0x6c, 0x63, 0x4d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x4d, - 0x73, 0x61, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x66, 0x65, 0x65, 0x18, - 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x62, 0x61, 0x73, 0x65, 0x46, 0x65, 0x65, 0x12, 0x19, - 0x0a, 0x08, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, - 0x52, 0x07, 0x66, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x68, 0x74, 0x6c, - 0x63, 0x5f, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0b, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x68, 0x74, 0x6c, 0x63, 0x4d, 0x61, 0x78, 0x69, 0x6d, 0x75, - 0x6d, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x2a, 0x0a, 0x11, 0x65, 0x78, 0x74, 0x72, 0x61, 0x5f, 0x6f, - 0x70, 0x61, 0x71, 0x75, 0x65, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x0f, 0x65, 0x78, 0x74, 0x72, 0x61, 0x4f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x44, 0x61, 0x74, - 0x61, 0x22, 0x5d, 0x0a, 0x0a, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x64, 0x12, - 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, - 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, - 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, - 0x65, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x03, 0x6f, 0x70, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x09, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x70, 0x52, 0x03, 0x6f, 0x70, 0x73, - 0x22, 0x36, 0x0a, 0x02, 0x4f, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x18, - 0x0a, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x8e, 0x01, 0x0a, 0x13, 0x43, 0x68, 0x65, - 0x63, 0x6b, 0x4d, 0x61, 0x63, 0x50, 0x65, 0x72, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x08, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x12, 0x3b, 0x0a, 0x0b, - 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, - 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x70, 0x65, - 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x66, 0x75, 0x6c, - 0x6c, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x66, - 0x75, 0x6c, 0x6c, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x22, 0x2c, 0x0a, 0x14, 0x43, 0x68, 0x65, - 0x63, 0x6b, 0x4d, 0x61, 0x63, 0x50, 0x65, 0x72, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x22, 0xb8, 0x02, 0x0a, 0x14, 0x52, 0x50, 0x43, 0x4d, - 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, - 0x21, 0x0a, 0x0c, 0x72, 0x61, 0x77, 0x5f, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x72, 0x61, 0x77, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, - 0x6f, 0x6e, 0x12, 0x36, 0x0a, 0x17, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x63, 0x61, 0x76, - 0x65, 0x61, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x15, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x61, 0x76, 0x65, 0x61, - 0x74, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x34, 0x0a, 0x0b, 0x73, 0x74, - 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x41, 0x75, - 0x74, 0x68, 0x48, 0x00, 0x52, 0x0a, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x41, 0x75, 0x74, 0x68, - 0x12, 0x2d, 0x0a, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x50, 0x43, 0x4d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x48, 0x00, 0x52, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x2f, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x50, 0x43, 0x4d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x48, 0x00, 0x52, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x42, 0x10, 0x0a, 0x0e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x5f, 0x74, 0x79, - 0x70, 0x65, 0x22, 0x34, 0x0a, 0x0a, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x41, 0x75, 0x74, 0x68, - 0x12, 0x26, 0x0a, 0x0f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x66, 0x75, 0x6c, 0x6c, 0x5f, - 0x75, 0x72, 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6d, 0x65, 0x74, 0x68, 0x6f, - 0x64, 0x46, 0x75, 0x6c, 0x6c, 0x55, 0x72, 0x69, 0x22, 0x90, 0x01, 0x0a, 0x0a, 0x52, 0x50, 0x43, - 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x6d, 0x65, 0x74, 0x68, 0x6f, - 0x64, 0x5f, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x75, 0x72, 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0d, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x46, 0x75, 0x6c, 0x6c, 0x55, 0x72, 0x69, 0x12, - 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x72, 0x70, 0x63, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x70, 0x63, 0x12, 0x1b, - 0x0a, 0x09, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x74, 0x79, 0x70, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x73, - 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x0a, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x22, 0xc1, 0x01, 0x0a, 0x15, - 0x52, 0x50, 0x43, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x49, 0x64, 0x12, 0x3b, 0x0a, 0x08, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, - 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, - 0x72, 0x12, 0x36, 0x0a, 0x08, 0x66, 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x74, 0x65, - 0x72, 0x63, 0x65, 0x70, 0x74, 0x46, 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x48, 0x00, 0x52, - 0x08, 0x66, 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x42, 0x14, 0x0a, 0x12, 0x6d, 0x69, 0x64, - 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, - 0xa6, 0x01, 0x0a, 0x16, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, - 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x27, 0x0a, 0x0f, 0x6d, 0x69, - 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x4e, - 0x61, 0x6d, 0x65, 0x12, 0x3d, 0x0a, 0x1b, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x6d, 0x61, - 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x5f, 0x63, 0x61, 0x76, 0x65, 0x61, 0x74, 0x5f, 0x6e, 0x61, - 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x18, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, - 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x43, 0x61, 0x76, 0x65, 0x61, 0x74, 0x4e, 0x61, - 0x6d, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x72, 0x65, 0x61, 0x64, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x5f, - 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x72, 0x65, 0x61, 0x64, - 0x4f, 0x6e, 0x6c, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x22, 0x8b, 0x01, 0x0a, 0x11, 0x49, 0x6e, 0x74, - 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x46, 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x14, - 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, - 0x72, 0x72, 0x6f, 0x72, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x5f, - 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, - 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x35, 0x0a, 0x16, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x73, - 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x15, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x69, - 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x2a, 0x7d, 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, - 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x17, 0x0a, 0x13, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, - 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x00, 0x12, 0x16, - 0x0a, 0x12, 0x4e, 0x45, 0x53, 0x54, 0x45, 0x44, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, - 0x48, 0x41, 0x53, 0x48, 0x10, 0x01, 0x12, 0x1e, 0x0a, 0x1a, 0x55, 0x4e, 0x55, 0x53, 0x45, 0x44, - 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, - 0x48, 0x41, 0x53, 0x48, 0x10, 0x02, 0x12, 0x1d, 0x0a, 0x19, 0x55, 0x4e, 0x55, 0x53, 0x45, 0x44, - 0x5f, 0x4e, 0x45, 0x53, 0x54, 0x45, 0x44, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, - 0x41, 0x53, 0x48, 0x10, 0x03, 0x2a, 0x78, 0x0a, 0x0e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, - 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x17, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, - 0x57, 0x4e, 0x5f, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, - 0x50, 0x45, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x4c, 0x45, 0x47, 0x41, 0x43, 0x59, 0x10, 0x01, - 0x12, 0x15, 0x0a, 0x11, 0x53, 0x54, 0x41, 0x54, 0x49, 0x43, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, - 0x45, 0x5f, 0x4b, 0x45, 0x59, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x4e, 0x43, 0x48, 0x4f, - 0x52, 0x53, 0x10, 0x03, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x45, - 0x4e, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x44, 0x5f, 0x4c, 0x45, 0x41, 0x53, 0x45, 0x10, 0x04, 0x2a, - 0x61, 0x0a, 0x09, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x15, 0x0a, 0x11, - 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x54, 0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, - 0x4e, 0x10, 0x00, 0x12, 0x13, 0x0a, 0x0f, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x54, 0x4f, 0x52, - 0x5f, 0x4c, 0x4f, 0x43, 0x41, 0x4c, 0x10, 0x01, 0x12, 0x14, 0x0a, 0x10, 0x49, 0x4e, 0x49, 0x54, - 0x49, 0x41, 0x54, 0x4f, 0x52, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x10, 0x02, 0x12, 0x12, - 0x0a, 0x0e, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x54, 0x4f, 0x52, 0x5f, 0x42, 0x4f, 0x54, 0x48, - 0x10, 0x03, 0x2a, 0x60, 0x0a, 0x0e, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, - 0x54, 0x79, 0x70, 0x65, 0x12, 0x10, 0x0a, 0x0c, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x4b, - 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, - 0x10, 0x01, 0x12, 0x11, 0x0a, 0x0d, 0x49, 0x4e, 0x43, 0x4f, 0x4d, 0x49, 0x4e, 0x47, 0x5f, 0x48, - 0x54, 0x4c, 0x43, 0x10, 0x02, 0x12, 0x11, 0x0a, 0x0d, 0x4f, 0x55, 0x54, 0x47, 0x4f, 0x49, 0x4e, - 0x47, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x10, 0x03, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x4f, 0x4d, 0x4d, - 0x49, 0x54, 0x10, 0x04, 0x2a, 0x71, 0x0a, 0x11, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, - 0x6f, 0x6e, 0x4f, 0x75, 0x74, 0x63, 0x6f, 0x6d, 0x65, 0x12, 0x13, 0x0a, 0x0f, 0x4f, 0x55, 0x54, - 0x43, 0x4f, 0x4d, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0b, - 0x0a, 0x07, 0x43, 0x4c, 0x41, 0x49, 0x4d, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x55, - 0x4e, 0x43, 0x4c, 0x41, 0x49, 0x4d, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x41, 0x42, - 0x41, 0x4e, 0x44, 0x4f, 0x4e, 0x45, 0x44, 0x10, 0x03, 0x12, 0x0f, 0x0a, 0x0b, 0x46, 0x49, 0x52, - 0x53, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x47, 0x45, 0x10, 0x04, 0x12, 0x0b, 0x0a, 0x07, 0x54, 0x49, - 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x05, 0x2a, 0x39, 0x0a, 0x0e, 0x4e, 0x6f, 0x64, 0x65, 0x4d, - 0x65, 0x74, 0x72, 0x69, 0x63, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, - 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x1a, 0x0a, 0x16, 0x42, 0x45, 0x54, 0x57, 0x45, 0x45, - 0x4e, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x43, 0x45, 0x4e, 0x54, 0x52, 0x41, 0x4c, 0x49, 0x54, 0x59, - 0x10, 0x01, 0x2a, 0x3b, 0x0a, 0x10, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x48, 0x54, 0x4c, - 0x43, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0c, 0x0a, 0x08, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, - 0x45, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x45, 0x54, 0x54, 0x4c, 0x45, 0x44, 0x10, - 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x45, 0x44, 0x10, 0x02, 0x2a, - 0xd9, 0x01, 0x0a, 0x14, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x46, 0x61, 0x69, 0x6c, 0x75, - 0x72, 0x65, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x17, 0x0a, 0x13, 0x46, 0x41, 0x49, 0x4c, - 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, 0x10, - 0x00, 0x12, 0x1a, 0x0a, 0x16, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, - 0x53, 0x4f, 0x4e, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x01, 0x12, 0x1b, 0x0a, - 0x17, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, - 0x4e, 0x4f, 0x5f, 0x52, 0x4f, 0x55, 0x54, 0x45, 0x10, 0x02, 0x12, 0x18, 0x0a, 0x14, 0x46, 0x41, - 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x45, 0x52, 0x52, - 0x4f, 0x52, 0x10, 0x03, 0x12, 0x2c, 0x0a, 0x28, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, - 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, - 0x5f, 0x50, 0x41, 0x59, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x44, 0x45, 0x54, 0x41, 0x49, 0x4c, 0x53, - 0x10, 0x04, 0x12, 0x27, 0x0a, 0x23, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, - 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x49, 0x4e, 0x53, 0x55, 0x46, 0x46, 0x49, 0x43, 0x49, 0x45, 0x4e, - 0x54, 0x5f, 0x42, 0x41, 0x4c, 0x41, 0x4e, 0x43, 0x45, 0x10, 0x05, 0x2a, 0xcf, 0x04, 0x0a, 0x0a, - 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x42, 0x69, 0x74, 0x12, 0x18, 0x0a, 0x14, 0x44, 0x41, - 0x54, 0x41, 0x4c, 0x4f, 0x53, 0x53, 0x5f, 0x50, 0x52, 0x4f, 0x54, 0x45, 0x43, 0x54, 0x5f, 0x52, - 0x45, 0x51, 0x10, 0x00, 0x12, 0x18, 0x0a, 0x14, 0x44, 0x41, 0x54, 0x41, 0x4c, 0x4f, 0x53, 0x53, - 0x5f, 0x50, 0x52, 0x4f, 0x54, 0x45, 0x43, 0x54, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x01, 0x12, 0x17, - 0x0a, 0x13, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x4c, 0x5f, 0x52, 0x4f, 0x55, 0x49, 0x4e, 0x47, - 0x5f, 0x53, 0x59, 0x4e, 0x43, 0x10, 0x03, 0x12, 0x1f, 0x0a, 0x1b, 0x55, 0x50, 0x46, 0x52, 0x4f, - 0x4e, 0x54, 0x5f, 0x53, 0x48, 0x55, 0x54, 0x44, 0x4f, 0x57, 0x4e, 0x5f, 0x53, 0x43, 0x52, 0x49, - 0x50, 0x54, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x04, 0x12, 0x1f, 0x0a, 0x1b, 0x55, 0x50, 0x46, 0x52, + 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1e, 0x0a, 0x0b, 0x72, 0x6f, 0x6f, 0x74, 0x5f, + 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x72, 0x6f, + 0x6f, 0x74, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x12, 0x3c, 0x0a, 0x1a, 0x61, 0x6c, 0x6c, 0x6f, 0x77, + 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x18, 0x61, 0x6c, 0x6c, + 0x6f, 0x77, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x32, 0x0a, 0x14, 0x42, 0x61, 0x6b, 0x65, 0x4d, 0x61, 0x63, + 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, + 0x08, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x22, 0x18, 0x0a, 0x16, 0x4c, 0x69, 0x73, + 0x74, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x22, 0x3b, 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x61, 0x72, + 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x20, + 0x0a, 0x0c, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x04, 0x52, 0x0a, 0x72, 0x6f, 0x6f, 0x74, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x73, + 0x22, 0x39, 0x0a, 0x17, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, + 0x6f, 0x6e, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1e, 0x0a, 0x0b, 0x72, + 0x6f, 0x6f, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x09, 0x72, 0x6f, 0x6f, 0x74, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x22, 0x34, 0x0a, 0x18, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x64, 0x22, 0x55, 0x0a, 0x16, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x50, 0x65, 0x72, + 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x3b, 0x0a, 0x0b, 0x70, + 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, + 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x70, 0x65, 0x72, + 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x18, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, + 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x22, 0xe4, 0x01, 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x64, + 0x0a, 0x12, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, + 0x64, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x52, 0x11, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x63, 0x0a, 0x16, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x50, 0x65, + 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, + 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, + 0x12, 0x33, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, + 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xb0, 0x08, 0x0a, 0x07, 0x46, 0x61, + 0x69, 0x6c, 0x75, 0x72, 0x65, 0x12, 0x2e, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x61, 0x69, 0x6c, + 0x75, 0x72, 0x65, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x52, + 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x3b, 0x0a, 0x0e, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x52, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x68, 0x74, 0x6c, 0x63, 0x4d, 0x73, 0x61, 0x74, 0x12, + 0x22, 0x0a, 0x0d, 0x6f, 0x6e, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x68, 0x61, 0x5f, 0x32, 0x35, 0x36, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x6f, 0x6e, 0x69, 0x6f, 0x6e, 0x53, 0x68, 0x61, + 0x32, 0x35, 0x36, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x65, 0x78, 0x70, 0x69, + 0x72, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x63, 0x6c, 0x74, 0x76, 0x45, 0x78, + 0x70, 0x69, 0x72, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x12, 0x30, 0x0a, 0x14, 0x66, 0x61, + 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x6e, 0x64, + 0x65, 0x78, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x12, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, + 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x16, 0x0a, 0x06, + 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x68, 0x65, + 0x69, 0x67, 0x68, 0x74, 0x22, 0xef, 0x05, 0x0a, 0x0b, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, + 0x43, 0x6f, 0x64, 0x65, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x45, 0x53, 0x45, 0x52, 0x56, 0x45, 0x44, + 0x10, 0x00, 0x12, 0x28, 0x0a, 0x24, 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, 0x5f, + 0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x50, 0x41, 0x59, 0x4d, 0x45, + 0x4e, 0x54, 0x5f, 0x44, 0x45, 0x54, 0x41, 0x49, 0x4c, 0x53, 0x10, 0x01, 0x12, 0x1c, 0x0a, 0x18, + 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x50, 0x41, 0x59, 0x4d, 0x45, 0x4e, + 0x54, 0x5f, 0x41, 0x4d, 0x4f, 0x55, 0x4e, 0x54, 0x10, 0x02, 0x12, 0x1f, 0x0a, 0x1b, 0x46, 0x49, + 0x4e, 0x41, 0x4c, 0x5f, 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x43, 0x4c, + 0x54, 0x56, 0x5f, 0x45, 0x58, 0x50, 0x49, 0x52, 0x59, 0x10, 0x03, 0x12, 0x1f, 0x0a, 0x1b, 0x46, + 0x49, 0x4e, 0x41, 0x4c, 0x5f, 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x48, + 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x4d, 0x4f, 0x55, 0x4e, 0x54, 0x10, 0x04, 0x12, 0x19, 0x0a, 0x15, + 0x46, 0x49, 0x4e, 0x41, 0x4c, 0x5f, 0x45, 0x58, 0x50, 0x49, 0x52, 0x59, 0x5f, 0x54, 0x4f, 0x4f, + 0x5f, 0x53, 0x4f, 0x4f, 0x4e, 0x10, 0x05, 0x12, 0x11, 0x0a, 0x0d, 0x49, 0x4e, 0x56, 0x41, 0x4c, + 0x49, 0x44, 0x5f, 0x52, 0x45, 0x41, 0x4c, 0x4d, 0x10, 0x06, 0x12, 0x13, 0x0a, 0x0f, 0x45, 0x58, + 0x50, 0x49, 0x52, 0x59, 0x5f, 0x54, 0x4f, 0x4f, 0x5f, 0x53, 0x4f, 0x4f, 0x4e, 0x10, 0x07, 0x12, + 0x19, 0x0a, 0x15, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x4f, 0x4e, 0x49, 0x4f, 0x4e, + 0x5f, 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x10, 0x08, 0x12, 0x16, 0x0a, 0x12, 0x49, 0x4e, + 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x48, 0x4d, 0x41, 0x43, + 0x10, 0x09, 0x12, 0x15, 0x0a, 0x11, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x4f, 0x4e, + 0x49, 0x4f, 0x4e, 0x5f, 0x4b, 0x45, 0x59, 0x10, 0x0a, 0x12, 0x18, 0x0a, 0x14, 0x41, 0x4d, 0x4f, + 0x55, 0x4e, 0x54, 0x5f, 0x42, 0x45, 0x4c, 0x4f, 0x57, 0x5f, 0x4d, 0x49, 0x4e, 0x49, 0x4d, 0x55, + 0x4d, 0x10, 0x0b, 0x12, 0x14, 0x0a, 0x10, 0x46, 0x45, 0x45, 0x5f, 0x49, 0x4e, 0x53, 0x55, 0x46, + 0x46, 0x49, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x10, 0x0c, 0x12, 0x19, 0x0a, 0x15, 0x49, 0x4e, 0x43, + 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x43, 0x4c, 0x54, 0x56, 0x5f, 0x45, 0x58, 0x50, 0x49, + 0x52, 0x59, 0x10, 0x0d, 0x12, 0x14, 0x0a, 0x10, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x5f, + 0x44, 0x49, 0x53, 0x41, 0x42, 0x4c, 0x45, 0x44, 0x10, 0x0e, 0x12, 0x1d, 0x0a, 0x19, 0x54, 0x45, + 0x4d, 0x50, 0x4f, 0x52, 0x41, 0x52, 0x59, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x5f, + 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, 0x0f, 0x12, 0x21, 0x0a, 0x1d, 0x52, 0x45, 0x51, + 0x55, 0x49, 0x52, 0x45, 0x44, 0x5f, 0x4e, 0x4f, 0x44, 0x45, 0x5f, 0x46, 0x45, 0x41, 0x54, 0x55, + 0x52, 0x45, 0x5f, 0x4d, 0x49, 0x53, 0x53, 0x49, 0x4e, 0x47, 0x10, 0x10, 0x12, 0x24, 0x0a, 0x20, + 0x52, 0x45, 0x51, 0x55, 0x49, 0x52, 0x45, 0x44, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, + 0x5f, 0x46, 0x45, 0x41, 0x54, 0x55, 0x52, 0x45, 0x5f, 0x4d, 0x49, 0x53, 0x53, 0x49, 0x4e, 0x47, + 0x10, 0x11, 0x12, 0x15, 0x0a, 0x11, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x4e, 0x45, + 0x58, 0x54, 0x5f, 0x50, 0x45, 0x45, 0x52, 0x10, 0x12, 0x12, 0x1a, 0x0a, 0x16, 0x54, 0x45, 0x4d, + 0x50, 0x4f, 0x52, 0x41, 0x52, 0x59, 0x5f, 0x4e, 0x4f, 0x44, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, + 0x55, 0x52, 0x45, 0x10, 0x13, 0x12, 0x1a, 0x0a, 0x16, 0x50, 0x45, 0x52, 0x4d, 0x41, 0x4e, 0x45, + 0x4e, 0x54, 0x5f, 0x4e, 0x4f, 0x44, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, + 0x14, 0x12, 0x1d, 0x0a, 0x19, 0x50, 0x45, 0x52, 0x4d, 0x41, 0x4e, 0x45, 0x4e, 0x54, 0x5f, 0x43, + 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, 0x15, + 0x12, 0x12, 0x0a, 0x0e, 0x45, 0x58, 0x50, 0x49, 0x52, 0x59, 0x5f, 0x54, 0x4f, 0x4f, 0x5f, 0x46, + 0x41, 0x52, 0x10, 0x16, 0x12, 0x0f, 0x0a, 0x0b, 0x4d, 0x50, 0x50, 0x5f, 0x54, 0x49, 0x4d, 0x45, + 0x4f, 0x55, 0x54, 0x10, 0x17, 0x12, 0x19, 0x0a, 0x15, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, + 0x5f, 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x50, 0x41, 0x59, 0x4c, 0x4f, 0x41, 0x44, 0x10, 0x18, + 0x12, 0x15, 0x0a, 0x10, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x5f, 0x46, 0x41, 0x49, + 0x4c, 0x55, 0x52, 0x45, 0x10, 0xe5, 0x07, 0x12, 0x14, 0x0a, 0x0f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, + 0x57, 0x4e, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, 0xe6, 0x07, 0x12, 0x17, 0x0a, + 0x12, 0x55, 0x4e, 0x52, 0x45, 0x41, 0x44, 0x41, 0x42, 0x4c, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, + 0x55, 0x52, 0x45, 0x10, 0xe7, 0x07, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x22, 0xb3, 0x03, 0x0a, + 0x0d, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x1c, + 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x1d, 0x0a, 0x0a, + 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x09, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x48, 0x61, 0x73, 0x68, 0x12, 0x1b, 0x0a, 0x07, 0x63, + 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, + 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x74, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x23, 0x0a, 0x0d, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x5f, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x6d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x63, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x46, 0x6c, 0x61, 0x67, 0x73, + 0x12, 0x26, 0x0a, 0x0f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x64, 0x65, + 0x6c, 0x74, 0x61, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x74, 0x69, 0x6d, 0x65, 0x4c, + 0x6f, 0x63, 0x6b, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x2a, 0x0a, 0x11, 0x68, 0x74, 0x6c, 0x63, + 0x5f, 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x0f, 0x68, 0x74, 0x6c, 0x63, 0x4d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, + 0x4d, 0x73, 0x61, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x66, 0x65, 0x65, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x62, 0x61, 0x73, 0x65, 0x46, 0x65, 0x65, 0x12, + 0x19, 0x0a, 0x08, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x07, 0x66, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x68, 0x74, + 0x6c, 0x63, 0x5f, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, + 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x68, 0x74, 0x6c, 0x63, 0x4d, 0x61, 0x78, 0x69, 0x6d, + 0x75, 0x6d, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x2a, 0x0a, 0x11, 0x65, 0x78, 0x74, 0x72, 0x61, 0x5f, + 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0c, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x0f, 0x65, 0x78, 0x74, 0x72, 0x61, 0x4f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x44, 0x61, + 0x74, 0x61, 0x22, 0x5d, 0x0a, 0x0a, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x64, + 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, + 0x65, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x74, 0x6f, 0x72, 0x61, + 0x67, 0x65, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x03, 0x6f, 0x70, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x09, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x70, 0x52, 0x03, 0x6f, 0x70, + 0x73, 0x22, 0x36, 0x0a, 0x02, 0x4f, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, + 0x18, 0x0a, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x8e, 0x01, 0x0a, 0x13, 0x43, 0x68, + 0x65, 0x63, 0x6b, 0x4d, 0x61, 0x63, 0x50, 0x65, 0x72, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x08, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x12, 0x3b, 0x0a, + 0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x63, 0x61, 0x72, + 0x6f, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x70, + 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x66, 0x75, + 0x6c, 0x6c, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, + 0x66, 0x75, 0x6c, 0x6c, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x22, 0x2c, 0x0a, 0x14, 0x43, 0x68, + 0x65, 0x63, 0x6b, 0x4d, 0x61, 0x63, 0x50, 0x65, 0x72, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x22, 0xcf, 0x02, 0x0a, 0x14, 0x52, 0x50, 0x43, + 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, + 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x61, 0x77, 0x5f, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x72, 0x61, 0x77, 0x4d, 0x61, 0x63, 0x61, 0x72, + 0x6f, 0x6f, 0x6e, 0x12, 0x36, 0x0a, 0x17, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x63, 0x61, + 0x76, 0x65, 0x61, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x61, 0x76, 0x65, + 0x61, 0x74, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x34, 0x0a, 0x0b, 0x73, + 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x41, + 0x75, 0x74, 0x68, 0x48, 0x00, 0x52, 0x0a, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x41, 0x75, 0x74, + 0x68, 0x12, 0x2d, 0x0a, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x50, 0x43, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x48, 0x00, 0x52, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x2f, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x50, 0x43, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x48, 0x00, 0x52, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x15, 0x0a, 0x06, 0x6d, 0x73, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x05, 0x6d, 0x73, 0x67, 0x49, 0x64, 0x42, 0x10, 0x0a, 0x0e, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x63, 0x65, 0x70, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x22, 0x34, 0x0a, 0x0a, 0x53, 0x74, + 0x72, 0x65, 0x61, 0x6d, 0x41, 0x75, 0x74, 0x68, 0x12, 0x26, 0x0a, 0x0f, 0x6d, 0x65, 0x74, 0x68, + 0x6f, 0x64, 0x5f, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x75, 0x72, 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0d, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x46, 0x75, 0x6c, 0x6c, 0x55, 0x72, 0x69, + 0x22, 0x90, 0x01, 0x0a, 0x0a, 0x52, 0x50, 0x43, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, + 0x26, 0x0a, 0x0f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x75, + 0x72, 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, + 0x46, 0x75, 0x6c, 0x6c, 0x55, 0x72, 0x69, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x72, 0x65, 0x61, + 0x6d, 0x5f, 0x72, 0x70, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x74, 0x72, + 0x65, 0x61, 0x6d, 0x52, 0x70, 0x63, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x79, 0x70, 0x65, 0x4e, + 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, + 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, + 0x7a, 0x65, 0x64, 0x22, 0xc0, 0x01, 0x0a, 0x15, 0x52, 0x50, 0x43, 0x4d, 0x69, 0x64, 0x64, 0x6c, + 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, + 0x0a, 0x72, 0x65, 0x66, 0x5f, 0x6d, 0x73, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x08, 0x72, 0x65, 0x66, 0x4d, 0x73, 0x67, 0x49, 0x64, 0x12, 0x3b, 0x0a, 0x08, 0x72, + 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, + 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x08, + 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x36, 0x0a, 0x08, 0x66, 0x65, 0x65, 0x64, + 0x62, 0x61, 0x63, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x46, 0x65, 0x65, 0x64, + 0x62, 0x61, 0x63, 0x6b, 0x48, 0x00, 0x52, 0x08, 0x66, 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, + 0x42, 0x14, 0x0a, 0x12, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x5f, 0x6d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0xa6, 0x01, 0x0a, 0x16, 0x4d, 0x69, 0x64, 0x64, 0x6c, + 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x27, 0x0a, 0x0f, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x5f, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6d, 0x69, 0x64, 0x64, + 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x3d, 0x0a, 0x1b, 0x63, 0x75, + 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x5f, 0x63, 0x61, + 0x76, 0x65, 0x61, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x18, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x43, + 0x61, 0x76, 0x65, 0x61, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x72, 0x65, 0x61, + 0x64, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x0c, 0x72, 0x65, 0x61, 0x64, 0x4f, 0x6e, 0x6c, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x22, + 0x8b, 0x01, 0x0a, 0x11, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x46, 0x65, 0x65, + 0x64, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x29, 0x0a, 0x10, 0x72, + 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x16, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, + 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x15, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x6d, + 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x2a, 0xa7, 0x02, + 0x0a, 0x10, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x54, 0x79, + 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x17, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x54, 0x59, 0x50, + 0x45, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x00, 0x12, + 0x1b, 0x0a, 0x17, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, + 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x01, 0x12, 0x26, 0x0a, 0x22, + 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x57, 0x49, 0x54, 0x4e, + 0x45, 0x53, 0x53, 0x5f, 0x56, 0x30, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, + 0x53, 0x48, 0x10, 0x02, 0x12, 0x26, 0x0a, 0x22, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x54, + 0x59, 0x50, 0x45, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x56, 0x30, 0x5f, 0x53, + 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x03, 0x12, 0x16, 0x0a, 0x12, + 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x50, 0x55, 0x42, 0x4b, + 0x45, 0x59, 0x10, 0x04, 0x12, 0x18, 0x0a, 0x14, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x54, + 0x59, 0x50, 0x45, 0x5f, 0x4d, 0x55, 0x4c, 0x54, 0x49, 0x53, 0x49, 0x47, 0x10, 0x05, 0x12, 0x18, + 0x0a, 0x14, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4e, 0x55, + 0x4c, 0x4c, 0x44, 0x41, 0x54, 0x41, 0x10, 0x06, 0x12, 0x1c, 0x0a, 0x18, 0x53, 0x43, 0x52, 0x49, + 0x50, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4e, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x4e, + 0x44, 0x41, 0x52, 0x44, 0x10, 0x07, 0x12, 0x1f, 0x0a, 0x1b, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, + 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x55, 0x4e, + 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x08, 0x2a, 0xac, 0x01, 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x17, 0x0a, 0x13, 0x57, 0x49, 0x54, 0x4e, 0x45, + 0x53, 0x53, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x00, + 0x12, 0x16, 0x0a, 0x12, 0x4e, 0x45, 0x53, 0x54, 0x45, 0x44, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, + 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x01, 0x12, 0x1e, 0x0a, 0x1a, 0x55, 0x4e, 0x55, 0x53, + 0x45, 0x44, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, + 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x02, 0x12, 0x1d, 0x0a, 0x19, 0x55, 0x4e, 0x55, 0x53, + 0x45, 0x44, 0x5f, 0x4e, 0x45, 0x53, 0x54, 0x45, 0x44, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, + 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x03, 0x12, 0x12, 0x0a, 0x0e, 0x54, 0x41, 0x50, 0x52, 0x4f, + 0x4f, 0x54, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x10, 0x04, 0x12, 0x19, 0x0a, 0x15, 0x55, + 0x4e, 0x55, 0x53, 0x45, 0x44, 0x5f, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x50, 0x55, + 0x42, 0x4b, 0x45, 0x59, 0x10, 0x05, 0x2a, 0x78, 0x0a, 0x0e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, + 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x17, 0x55, 0x4e, 0x4b, 0x4e, + 0x4f, 0x57, 0x4e, 0x5f, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x54, + 0x59, 0x50, 0x45, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x4c, 0x45, 0x47, 0x41, 0x43, 0x59, 0x10, + 0x01, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x54, 0x41, 0x54, 0x49, 0x43, 0x5f, 0x52, 0x45, 0x4d, 0x4f, + 0x54, 0x45, 0x5f, 0x4b, 0x45, 0x59, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x4e, 0x43, 0x48, + 0x4f, 0x52, 0x53, 0x10, 0x03, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, + 0x45, 0x4e, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x44, 0x5f, 0x4c, 0x45, 0x41, 0x53, 0x45, 0x10, 0x04, + 0x2a, 0x61, 0x0a, 0x09, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x15, 0x0a, + 0x11, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x54, 0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, + 0x57, 0x4e, 0x10, 0x00, 0x12, 0x13, 0x0a, 0x0f, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x54, 0x4f, + 0x52, 0x5f, 0x4c, 0x4f, 0x43, 0x41, 0x4c, 0x10, 0x01, 0x12, 0x14, 0x0a, 0x10, 0x49, 0x4e, 0x49, + 0x54, 0x49, 0x41, 0x54, 0x4f, 0x52, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x10, 0x02, 0x12, + 0x12, 0x0a, 0x0e, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x54, 0x4f, 0x52, 0x5f, 0x42, 0x4f, 0x54, + 0x48, 0x10, 0x03, 0x2a, 0x60, 0x0a, 0x0e, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, + 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x10, 0x0a, 0x0c, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, + 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x4e, 0x43, 0x48, 0x4f, + 0x52, 0x10, 0x01, 0x12, 0x11, 0x0a, 0x0d, 0x49, 0x4e, 0x43, 0x4f, 0x4d, 0x49, 0x4e, 0x47, 0x5f, + 0x48, 0x54, 0x4c, 0x43, 0x10, 0x02, 0x12, 0x11, 0x0a, 0x0d, 0x4f, 0x55, 0x54, 0x47, 0x4f, 0x49, + 0x4e, 0x47, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x10, 0x03, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x4f, 0x4d, + 0x4d, 0x49, 0x54, 0x10, 0x04, 0x2a, 0x71, 0x0a, 0x11, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, + 0x69, 0x6f, 0x6e, 0x4f, 0x75, 0x74, 0x63, 0x6f, 0x6d, 0x65, 0x12, 0x13, 0x0a, 0x0f, 0x4f, 0x55, + 0x54, 0x43, 0x4f, 0x4d, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, + 0x0b, 0x0a, 0x07, 0x43, 0x4c, 0x41, 0x49, 0x4d, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, + 0x55, 0x4e, 0x43, 0x4c, 0x41, 0x49, 0x4d, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x41, + 0x42, 0x41, 0x4e, 0x44, 0x4f, 0x4e, 0x45, 0x44, 0x10, 0x03, 0x12, 0x0f, 0x0a, 0x0b, 0x46, 0x49, + 0x52, 0x53, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x47, 0x45, 0x10, 0x04, 0x12, 0x0b, 0x0a, 0x07, 0x54, + 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x05, 0x2a, 0x39, 0x0a, 0x0e, 0x4e, 0x6f, 0x64, 0x65, + 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, + 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x1a, 0x0a, 0x16, 0x42, 0x45, 0x54, 0x57, 0x45, + 0x45, 0x4e, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x43, 0x45, 0x4e, 0x54, 0x52, 0x41, 0x4c, 0x49, 0x54, + 0x59, 0x10, 0x01, 0x2a, 0x3b, 0x0a, 0x10, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x48, 0x54, + 0x4c, 0x43, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0c, 0x0a, 0x08, 0x41, 0x43, 0x43, 0x45, 0x50, + 0x54, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x45, 0x54, 0x54, 0x4c, 0x45, 0x44, + 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x45, 0x44, 0x10, 0x02, + 0x2a, 0xd9, 0x01, 0x0a, 0x14, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x46, 0x61, 0x69, 0x6c, + 0x75, 0x72, 0x65, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x17, 0x0a, 0x13, 0x46, 0x41, 0x49, + 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, + 0x10, 0x00, 0x12, 0x1a, 0x0a, 0x16, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, + 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x01, 0x12, 0x1b, + 0x0a, 0x17, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, + 0x5f, 0x4e, 0x4f, 0x5f, 0x52, 0x4f, 0x55, 0x54, 0x45, 0x10, 0x02, 0x12, 0x18, 0x0a, 0x14, 0x46, + 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x45, 0x52, + 0x52, 0x4f, 0x52, 0x10, 0x03, 0x12, 0x2c, 0x0a, 0x28, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, + 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, + 0x54, 0x5f, 0x50, 0x41, 0x59, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x44, 0x45, 0x54, 0x41, 0x49, 0x4c, + 0x53, 0x10, 0x04, 0x12, 0x27, 0x0a, 0x23, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, + 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x49, 0x4e, 0x53, 0x55, 0x46, 0x46, 0x49, 0x43, 0x49, 0x45, + 0x4e, 0x54, 0x5f, 0x42, 0x41, 0x4c, 0x41, 0x4e, 0x43, 0x45, 0x10, 0x05, 0x2a, 0xcf, 0x04, 0x0a, + 0x0a, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x42, 0x69, 0x74, 0x12, 0x18, 0x0a, 0x14, 0x44, + 0x41, 0x54, 0x41, 0x4c, 0x4f, 0x53, 0x53, 0x5f, 0x50, 0x52, 0x4f, 0x54, 0x45, 0x43, 0x54, 0x5f, + 0x52, 0x45, 0x51, 0x10, 0x00, 0x12, 0x18, 0x0a, 0x14, 0x44, 0x41, 0x54, 0x41, 0x4c, 0x4f, 0x53, + 0x53, 0x5f, 0x50, 0x52, 0x4f, 0x54, 0x45, 0x43, 0x54, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x01, 0x12, + 0x17, 0x0a, 0x13, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x4c, 0x5f, 0x52, 0x4f, 0x55, 0x49, 0x4e, + 0x47, 0x5f, 0x53, 0x59, 0x4e, 0x43, 0x10, 0x03, 0x12, 0x1f, 0x0a, 0x1b, 0x55, 0x50, 0x46, 0x52, 0x4f, 0x4e, 0x54, 0x5f, 0x53, 0x48, 0x55, 0x54, 0x44, 0x4f, 0x57, 0x4e, 0x5f, 0x53, 0x43, 0x52, - 0x49, 0x50, 0x54, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x05, 0x12, 0x16, 0x0a, 0x12, 0x47, 0x4f, 0x53, - 0x53, 0x49, 0x50, 0x5f, 0x51, 0x55, 0x45, 0x52, 0x49, 0x45, 0x53, 0x5f, 0x52, 0x45, 0x51, 0x10, - 0x06, 0x12, 0x16, 0x0a, 0x12, 0x47, 0x4f, 0x53, 0x53, 0x49, 0x50, 0x5f, 0x51, 0x55, 0x45, 0x52, - 0x49, 0x45, 0x53, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x07, 0x12, 0x11, 0x0a, 0x0d, 0x54, 0x4c, 0x56, - 0x5f, 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x08, 0x12, 0x11, 0x0a, 0x0d, - 0x54, 0x4c, 0x56, 0x5f, 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x09, 0x12, - 0x1a, 0x0a, 0x16, 0x45, 0x58, 0x54, 0x5f, 0x47, 0x4f, 0x53, 0x53, 0x49, 0x50, 0x5f, 0x51, 0x55, - 0x45, 0x52, 0x49, 0x45, 0x53, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x0a, 0x12, 0x1a, 0x0a, 0x16, 0x45, - 0x58, 0x54, 0x5f, 0x47, 0x4f, 0x53, 0x53, 0x49, 0x50, 0x5f, 0x51, 0x55, 0x45, 0x52, 0x49, 0x45, - 0x53, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x0b, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x54, 0x41, 0x54, 0x49, - 0x43, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x52, 0x45, 0x51, - 0x10, 0x0c, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x54, 0x41, 0x54, 0x49, 0x43, 0x5f, 0x52, 0x45, 0x4d, - 0x4f, 0x54, 0x45, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x0d, 0x12, 0x14, 0x0a, - 0x10, 0x50, 0x41, 0x59, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x41, 0x44, 0x44, 0x52, 0x5f, 0x52, 0x45, - 0x51, 0x10, 0x0e, 0x12, 0x14, 0x0a, 0x10, 0x50, 0x41, 0x59, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x41, - 0x44, 0x44, 0x52, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x0f, 0x12, 0x0b, 0x0a, 0x07, 0x4d, 0x50, 0x50, - 0x5f, 0x52, 0x45, 0x51, 0x10, 0x10, 0x12, 0x0b, 0x0a, 0x07, 0x4d, 0x50, 0x50, 0x5f, 0x4f, 0x50, - 0x54, 0x10, 0x11, 0x12, 0x16, 0x0a, 0x12, 0x57, 0x55, 0x4d, 0x42, 0x4f, 0x5f, 0x43, 0x48, 0x41, - 0x4e, 0x4e, 0x45, 0x4c, 0x53, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x12, 0x12, 0x16, 0x0a, 0x12, 0x57, - 0x55, 0x4d, 0x42, 0x4f, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x53, 0x5f, 0x4f, 0x50, - 0x54, 0x10, 0x13, 0x12, 0x0f, 0x0a, 0x0b, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x53, 0x5f, 0x52, - 0x45, 0x51, 0x10, 0x14, 0x12, 0x0f, 0x0a, 0x0b, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x53, 0x5f, - 0x4f, 0x50, 0x54, 0x10, 0x15, 0x12, 0x1d, 0x0a, 0x19, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x53, - 0x5f, 0x5a, 0x45, 0x52, 0x4f, 0x5f, 0x46, 0x45, 0x45, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x52, - 0x45, 0x51, 0x10, 0x16, 0x12, 0x1d, 0x0a, 0x19, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x53, 0x5f, - 0x5a, 0x45, 0x52, 0x4f, 0x5f, 0x46, 0x45, 0x45, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4f, 0x50, - 0x54, 0x10, 0x17, 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x4d, 0x50, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x1e, - 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x4d, 0x50, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x1f, 0x2a, 0xac, 0x01, - 0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x12, - 0x1a, 0x0a, 0x16, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, - 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x1a, 0x0a, 0x16, 0x55, - 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x50, 0x45, - 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x1c, 0x0a, 0x18, 0x55, 0x50, 0x44, 0x41, 0x54, - 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x46, 0x4f, - 0x55, 0x4e, 0x44, 0x10, 0x02, 0x12, 0x1f, 0x0a, 0x1b, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, - 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, - 0x5f, 0x45, 0x52, 0x52, 0x10, 0x03, 0x12, 0x24, 0x0a, 0x20, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, - 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, - 0x5f, 0x50, 0x41, 0x52, 0x41, 0x4d, 0x45, 0x54, 0x45, 0x52, 0x10, 0x04, 0x32, 0xc9, 0x25, 0x0a, - 0x09, 0x4c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x12, 0x4a, 0x0a, 0x0d, 0x57, 0x61, - 0x6c, 0x6c, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x1b, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x0e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, - 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, + 0x49, 0x50, 0x54, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x04, 0x12, 0x1f, 0x0a, 0x1b, 0x55, 0x50, 0x46, + 0x52, 0x4f, 0x4e, 0x54, 0x5f, 0x53, 0x48, 0x55, 0x54, 0x44, 0x4f, 0x57, 0x4e, 0x5f, 0x53, 0x43, + 0x52, 0x49, 0x50, 0x54, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x05, 0x12, 0x16, 0x0a, 0x12, 0x47, 0x4f, + 0x53, 0x53, 0x49, 0x50, 0x5f, 0x51, 0x55, 0x45, 0x52, 0x49, 0x45, 0x53, 0x5f, 0x52, 0x45, 0x51, + 0x10, 0x06, 0x12, 0x16, 0x0a, 0x12, 0x47, 0x4f, 0x53, 0x53, 0x49, 0x50, 0x5f, 0x51, 0x55, 0x45, + 0x52, 0x49, 0x45, 0x53, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x07, 0x12, 0x11, 0x0a, 0x0d, 0x54, 0x4c, + 0x56, 0x5f, 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x08, 0x12, 0x11, 0x0a, + 0x0d, 0x54, 0x4c, 0x56, 0x5f, 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x09, + 0x12, 0x1a, 0x0a, 0x16, 0x45, 0x58, 0x54, 0x5f, 0x47, 0x4f, 0x53, 0x53, 0x49, 0x50, 0x5f, 0x51, + 0x55, 0x45, 0x52, 0x49, 0x45, 0x53, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x0a, 0x12, 0x1a, 0x0a, 0x16, + 0x45, 0x58, 0x54, 0x5f, 0x47, 0x4f, 0x53, 0x53, 0x49, 0x50, 0x5f, 0x51, 0x55, 0x45, 0x52, 0x49, + 0x45, 0x53, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x0b, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x54, 0x41, 0x54, + 0x49, 0x43, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x52, 0x45, + 0x51, 0x10, 0x0c, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x54, 0x41, 0x54, 0x49, 0x43, 0x5f, 0x52, 0x45, + 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x0d, 0x12, 0x14, + 0x0a, 0x10, 0x50, 0x41, 0x59, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x41, 0x44, 0x44, 0x52, 0x5f, 0x52, + 0x45, 0x51, 0x10, 0x0e, 0x12, 0x14, 0x0a, 0x10, 0x50, 0x41, 0x59, 0x4d, 0x45, 0x4e, 0x54, 0x5f, + 0x41, 0x44, 0x44, 0x52, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x0f, 0x12, 0x0b, 0x0a, 0x07, 0x4d, 0x50, + 0x50, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x10, 0x12, 0x0b, 0x0a, 0x07, 0x4d, 0x50, 0x50, 0x5f, 0x4f, + 0x50, 0x54, 0x10, 0x11, 0x12, 0x16, 0x0a, 0x12, 0x57, 0x55, 0x4d, 0x42, 0x4f, 0x5f, 0x43, 0x48, + 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x53, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x12, 0x12, 0x16, 0x0a, 0x12, + 0x57, 0x55, 0x4d, 0x42, 0x4f, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x53, 0x5f, 0x4f, + 0x50, 0x54, 0x10, 0x13, 0x12, 0x0f, 0x0a, 0x0b, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x53, 0x5f, + 0x52, 0x45, 0x51, 0x10, 0x14, 0x12, 0x0f, 0x0a, 0x0b, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x53, + 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x15, 0x12, 0x1d, 0x0a, 0x19, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, + 0x53, 0x5f, 0x5a, 0x45, 0x52, 0x4f, 0x5f, 0x46, 0x45, 0x45, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, + 0x52, 0x45, 0x51, 0x10, 0x16, 0x12, 0x1d, 0x0a, 0x19, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x53, + 0x5f, 0x5a, 0x45, 0x52, 0x4f, 0x5f, 0x46, 0x45, 0x45, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4f, + 0x50, 0x54, 0x10, 0x17, 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x4d, 0x50, 0x5f, 0x52, 0x45, 0x51, 0x10, + 0x1e, 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x4d, 0x50, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x1f, 0x2a, 0xac, + 0x01, 0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, + 0x12, 0x1a, 0x0a, 0x16, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, + 0x52, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x1a, 0x0a, 0x16, + 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x50, + 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x1c, 0x0a, 0x18, 0x55, 0x50, 0x44, 0x41, + 0x54, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x46, + 0x4f, 0x55, 0x4e, 0x44, 0x10, 0x02, 0x12, 0x1f, 0x0a, 0x1b, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, + 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, + 0x4c, 0x5f, 0x45, 0x52, 0x52, 0x10, 0x03, 0x12, 0x24, 0x0a, 0x20, 0x55, 0x50, 0x44, 0x41, 0x54, + 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, + 0x44, 0x5f, 0x50, 0x41, 0x52, 0x41, 0x4d, 0x45, 0x54, 0x45, 0x52, 0x10, 0x04, 0x32, 0xc9, 0x25, + 0x0a, 0x09, 0x4c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x12, 0x4a, 0x0a, 0x0d, 0x57, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x1b, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, + 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x0e, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, + 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x74, 0x61, + 0x69, 0x6c, 0x73, 0x12, 0x44, 0x0a, 0x0b, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, + 0x65, 0x65, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x73, 0x74, 0x69, 0x6d, + 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x09, 0x53, 0x65, 0x6e, + 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x12, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, + 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x69, 0x6e, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x0b, 0x4c, 0x69, 0x73, + 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x4c, 0x0a, 0x15, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, - 0x6c, 0x73, 0x12, 0x44, 0x0a, 0x0b, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, - 0x65, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, - 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x09, 0x53, 0x65, 0x6e, 0x64, - 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x12, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, - 0x6e, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, - 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, - 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, - 0x0a, 0x15, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, - 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, - 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x30, 0x01, 0x12, 0x3b, 0x0a, 0x08, - 0x53, 0x65, 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x79, 0x12, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4d, 0x61, 0x6e, - 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0a, 0x4e, 0x65, 0x77, - 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, - 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x0b, - 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x19, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, - 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x0d, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x12, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x69, - 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, - 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, - 0x0a, 0x0b, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x12, 0x19, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, - 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x0e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, - 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, - 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x69, 0x73, - 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x65, 0x72, 0x73, - 0x12, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x65, - 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x13, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, - 0x50, 0x65, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x10, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x50, 0x65, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x38, 0x0a, 0x07, - 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, - 0x6f, 0x76, 0x65, 0x72, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x30, 0x01, 0x12, 0x3b, 0x0a, + 0x08, 0x53, 0x65, 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x79, 0x12, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4d, 0x61, + 0x6e, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0a, 0x4e, 0x65, + 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x65, 0x77, 0x41, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, + 0x0b, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x19, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x0d, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, + 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x44, 0x0a, 0x0b, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x12, 0x19, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, + 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x0e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, + 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x69, + 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x65, 0x72, + 0x73, 0x12, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, + 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x13, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, + 0x65, 0x50, 0x65, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x75, 0x62, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x10, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x38, 0x0a, + 0x07, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x52, 0x65, + 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x49, 0x6e, + 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x49, 0x6e, 0x66, - 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x49, 0x6e, 0x66, 0x6f, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x50, 0x65, 0x6e, 0x64, - 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, + 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x50, 0x65, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x1d, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, - 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0c, 0x4c, 0x69, - 0x73, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x16, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, - 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1f, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x19, + 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0c, 0x4c, + 0x69, 0x73, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x16, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, + 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x30, 0x01, 0x12, 0x4d, 0x0a, 0x0e, 0x43, - 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x1c, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, - 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0f, 0x4f, 0x70, - 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x19, 0x2e, + 0x65, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, + 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x30, 0x01, 0x12, 0x4d, 0x0a, 0x0e, + 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x1c, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0f, 0x4f, + 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x19, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x43, + 0x0a, 0x0b, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x43, 0x0a, - 0x0b, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x19, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x4f, 0x70, 0x65, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x30, 0x01, 0x12, 0x53, 0x0a, 0x10, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x70, 0x65, 0x6e, 0x43, - 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, - 0x61, 0x74, 0x63, 0x68, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, - 0x61, 0x74, 0x63, 0x68, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x10, 0x46, 0x75, 0x6e, 0x64, 0x69, - 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x74, 0x65, 0x70, 0x12, 0x1b, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x61, 0x6e, 0x73, - 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x73, 0x67, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x74, 0x65, - 0x70, 0x52, 0x65, 0x73, 0x70, 0x12, 0x50, 0x0a, 0x0f, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, - 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x28, 0x01, 0x30, 0x01, 0x12, 0x46, 0x0a, 0x0c, 0x43, 0x6c, 0x6f, 0x73, 0x65, - 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, - 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x30, 0x01, 0x12, - 0x4d, 0x0a, 0x0e, 0x41, 0x62, 0x61, 0x6e, 0x64, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x62, 0x61, 0x6e, 0x64, 0x6f, - 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x62, 0x61, 0x6e, 0x64, 0x6f, 0x6e, 0x43, - 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, - 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x88, 0x02, 0x01, 0x28, 0x01, 0x30, 0x01, 0x12, - 0x3a, 0x0a, 0x0f, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x79, - 0x6e, 0x63, 0x12, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, + 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x30, 0x01, 0x12, 0x53, 0x0a, 0x10, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x70, 0x65, 0x6e, + 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x42, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x42, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x10, 0x46, 0x75, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x74, 0x65, 0x70, 0x12, 0x1b, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x61, 0x6e, + 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x73, 0x67, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x74, + 0x65, 0x70, 0x52, 0x65, 0x73, 0x70, 0x12, 0x50, 0x0a, 0x0f, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x28, 0x01, 0x30, 0x01, 0x12, 0x46, 0x0a, 0x0c, 0x43, 0x6c, 0x6f, 0x73, + 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, + 0x73, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x30, 0x01, + 0x12, 0x4d, 0x0a, 0x0e, 0x41, 0x62, 0x61, 0x6e, 0x64, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x62, 0x61, 0x6e, 0x64, + 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x62, 0x61, 0x6e, 0x64, 0x6f, 0x6e, + 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x3f, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x12, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x88, 0x02, 0x01, 0x28, 0x01, 0x30, 0x01, + 0x12, 0x3a, 0x0a, 0x0f, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, + 0x79, 0x6e, 0x63, 0x12, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x46, 0x0a, 0x0b, + 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x19, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, - 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x46, 0x0a, 0x0b, 0x53, - 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, - 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x88, 0x02, 0x01, 0x28, - 0x01, 0x30, 0x01, 0x12, 0x41, 0x0a, 0x0f, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, - 0x74, 0x65, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, - 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x0a, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x76, - 0x6f, 0x69, 0x63, 0x65, 0x12, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, - 0x6f, 0x69, 0x63, 0x65, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, - 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x45, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x12, - 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x6f, - 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x0d, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, - 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x1a, 0x0e, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x41, 0x0a, 0x11, 0x53, - 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, - 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, - 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x0e, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x30, 0x01, 0x12, 0x32, - 0x0a, 0x0c, 0x44, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x12, 0x13, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x53, 0x74, 0x72, - 0x69, 0x6e, 0x67, 0x1a, 0x0d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x52, - 0x65, 0x71, 0x12, 0x47, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, - 0x74, 0x73, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, - 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x79, 0x6d, 0x65, - 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x0d, 0x44, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1b, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, - 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x41, 0x6c, 0x6c, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1f, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, 0x6c, 0x50, 0x61, - 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, + 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x88, 0x02, 0x01, + 0x28, 0x01, 0x30, 0x01, 0x12, 0x41, 0x0a, 0x0f, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, + 0x75, 0x74, 0x65, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x0a, 0x41, 0x64, 0x64, 0x49, 0x6e, + 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, + 0x76, 0x6f, 0x69, 0x63, 0x65, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, + 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x45, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, + 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, + 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x0d, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, + 0x70, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x1a, 0x0e, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x41, 0x0a, 0x11, + 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, + 0x73, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, + 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x0e, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x30, 0x01, 0x12, + 0x32, 0x0a, 0x0c, 0x44, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x12, + 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x53, 0x74, + 0x72, 0x69, 0x6e, 0x67, 0x1a, 0x0d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, + 0x52, 0x65, 0x71, 0x12, 0x47, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x79, 0x6d, 0x65, + 0x6e, 0x74, 0x73, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x79, 0x6d, + 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x0d, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1b, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, + 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x41, 0x6c, 0x6c, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, 0x6c, 0x50, - 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x40, 0x0a, 0x0d, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x47, 0x72, 0x61, 0x70, 0x68, - 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x47, 0x72, 0x61, 0x70, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x47, 0x72, 0x61, 0x70, - 0x68, 0x12, 0x47, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, 0x72, - 0x69, 0x63, 0x73, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, - 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, - 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x0b, 0x47, 0x65, - 0x74, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x45, 0x64, 0x67, 0x65, 0x12, 0x36, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x64, 0x65, - 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, - 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x44, 0x0a, - 0x0b, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x19, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, - 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x65, - 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, - 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x35, 0x0a, 0x0a, 0x53, 0x74, 0x6f, 0x70, 0x44, 0x61, 0x65, 0x6d, - 0x6f, 0x6e, 0x12, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, - 0x74, 0x6f, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x15, 0x53, - 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x47, - 0x72, 0x61, 0x70, 0x68, 0x12, 0x20, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x72, 0x61, - 0x70, 0x68, 0x54, 0x6f, 0x70, 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, - 0x72, 0x61, 0x70, 0x68, 0x54, 0x6f, 0x70, 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x30, 0x01, 0x12, 0x41, 0x0a, 0x0a, 0x44, 0x65, 0x62, 0x75, 0x67, 0x4c, 0x65, 0x76, - 0x65, 0x6c, 0x12, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, - 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x09, 0x46, 0x65, 0x65, 0x52, 0x65, - 0x70, 0x6f, 0x72, 0x74, 0x12, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x65, - 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x1a, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x11, 0x46, 0x6f, 0x72, 0x77, 0x61, - 0x72, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x1f, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x48, - 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, + 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, 0x6c, + 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x40, 0x0a, 0x0d, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x47, 0x72, 0x61, 0x70, + 0x68, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x47, 0x72, 0x61, 0x70, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x47, 0x72, 0x61, + 0x70, 0x68, 0x12, 0x47, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, + 0x72, 0x69, 0x63, 0x73, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, + 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, 0x72, + 0x69, 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x0b, 0x47, + 0x65, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x16, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x45, 0x64, 0x67, 0x65, 0x12, 0x36, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x64, + 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, + 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x44, + 0x0a, 0x0b, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x19, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x6f, 0x75, 0x74, 0x65, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x4e, 0x65, 0x74, 0x77, 0x6f, + 0x72, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, + 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, + 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x35, 0x0a, 0x0a, 0x53, 0x74, 0x6f, 0x70, 0x44, 0x61, 0x65, + 0x6d, 0x6f, 0x6e, 0x12, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x6f, 0x70, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x53, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x15, + 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x47, 0x72, 0x61, 0x70, 0x68, 0x12, 0x20, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x72, + 0x61, 0x70, 0x68, 0x54, 0x6f, 0x70, 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x53, 0x75, 0x62, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x47, 0x72, 0x61, 0x70, 0x68, 0x54, 0x6f, 0x70, 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x30, 0x01, 0x12, 0x41, 0x0a, 0x0a, 0x44, 0x65, 0x62, 0x75, 0x67, 0x4c, 0x65, + 0x76, 0x65, 0x6c, 0x12, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x62, 0x75, + 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x09, 0x46, 0x65, 0x65, 0x52, + 0x65, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, + 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, + 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x11, 0x46, 0x6f, 0x72, 0x77, + 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, - 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x4e, 0x0a, 0x13, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, 0x21, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x45, - 0x78, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, - 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, - 0x54, 0x0a, 0x17, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x6c, 0x6c, 0x43, 0x68, 0x61, 0x6e, - 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x45, 0x78, 0x70, - 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, + 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, + 0x67, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x4e, 0x0a, 0x13, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, 0x21, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, + 0x6b, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, + 0x12, 0x54, 0x0a, 0x17, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x6c, 0x6c, 0x43, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x1e, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x45, 0x78, + 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x6e, + 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x12, 0x4e, 0x0a, 0x10, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, + 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x6e, 0x61, - 0x70, 0x73, 0x68, 0x6f, 0x74, 0x12, 0x4e, 0x0a, 0x10, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x43, - 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x6e, 0x61, 0x70, - 0x73, 0x68, 0x6f, 0x74, 0x1a, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, - 0x69, 0x66, 0x79, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x15, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, - 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x1f, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x43, 0x68, - 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x42, - 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x58, 0x0a, - 0x17, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x20, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x75, - 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x6e, 0x61, - 0x70, 0x73, 0x68, 0x6f, 0x74, 0x30, 0x01, 0x12, 0x47, 0x0a, 0x0c, 0x42, 0x61, 0x6b, 0x65, 0x4d, - 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x42, 0x61, 0x6b, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x61, 0x6b, 0x65, - 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x50, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, - 0x49, 0x44, 0x73, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, - 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x53, 0x0a, 0x10, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x61, - 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x50, - 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x73, 0x68, 0x6f, 0x74, 0x1a, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, + 0x72, 0x69, 0x66, 0x79, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x15, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, + 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, + 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x43, + 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, + 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x58, + 0x0a, 0x17, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x20, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, + 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x6e, + 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x30, 0x01, 0x12, 0x47, 0x0a, 0x0c, 0x42, 0x61, 0x6b, 0x65, + 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x42, 0x61, 0x6b, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x61, 0x6b, + 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, + 0x6e, 0x49, 0x44, 0x73, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x10, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, + 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, + 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x18, 0x43, 0x68, 0x65, - 0x63, 0x6b, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, - 0x65, 0x63, 0x6b, 0x4d, 0x61, 0x63, 0x50, 0x65, 0x72, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4d, - 0x61, 0x63, 0x50, 0x65, 0x72, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, - 0x0a, 0x15, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x50, 0x43, 0x4d, 0x69, 0x64, - 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x52, 0x50, 0x43, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x50, - 0x43, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x28, 0x01, 0x30, 0x01, 0x12, 0x56, 0x0a, 0x11, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x75, - 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1f, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6c, + 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x18, 0x43, 0x68, + 0x65, 0x63, 0x6b, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, + 0x68, 0x65, 0x63, 0x6b, 0x4d, 0x61, 0x63, 0x50, 0x65, 0x72, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, + 0x4d, 0x61, 0x63, 0x50, 0x65, 0x72, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x56, 0x0a, 0x15, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x50, 0x43, 0x4d, 0x69, + 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x52, 0x50, 0x43, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, + 0x50, 0x43, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x28, 0x01, 0x30, 0x01, 0x12, 0x56, 0x0a, 0x11, 0x53, 0x65, 0x6e, 0x64, 0x43, + 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, - 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x58, - 0x0a, 0x17, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, - 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x12, 0x25, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, - 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, - 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x30, 0x01, 0x42, 0x27, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, - 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, - 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x58, 0x0a, 0x17, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x43, 0x75, 0x73, 0x74, + 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x12, 0x25, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x43, 0x75, 0x73, 0x74, + 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x30, 0x01, 0x42, 0x27, 0x5a, 0x25, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, + 0x67, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -19100,539 +19458,543 @@ func file_lightning_proto_rawDescGZIP() []byte { return file_lightning_proto_rawDescData } -var file_lightning_proto_enumTypes = make([]protoimpl.EnumInfo, 19) -var file_lightning_proto_msgTypes = make([]protoimpl.MessageInfo, 207) +var file_lightning_proto_enumTypes = make([]protoimpl.EnumInfo, 20) +var file_lightning_proto_msgTypes = make([]protoimpl.MessageInfo, 208) var file_lightning_proto_goTypes = []interface{}{ - (AddressType)(0), // 0: lnrpc.AddressType - (CommitmentType)(0), // 1: lnrpc.CommitmentType - (Initiator)(0), // 2: lnrpc.Initiator - (ResolutionType)(0), // 3: lnrpc.ResolutionType - (ResolutionOutcome)(0), // 4: lnrpc.ResolutionOutcome - (NodeMetricType)(0), // 5: lnrpc.NodeMetricType - (InvoiceHTLCState)(0), // 6: lnrpc.InvoiceHTLCState - (PaymentFailureReason)(0), // 7: lnrpc.PaymentFailureReason - (FeatureBit)(0), // 8: lnrpc.FeatureBit - (UpdateFailure)(0), // 9: lnrpc.UpdateFailure - (ChannelCloseSummary_ClosureType)(0), // 10: lnrpc.ChannelCloseSummary.ClosureType - (Peer_SyncType)(0), // 11: lnrpc.Peer.SyncType - (PeerEvent_EventType)(0), // 12: lnrpc.PeerEvent.EventType - (PendingChannelsResponse_ForceClosedChannel_AnchorState)(0), // 13: lnrpc.PendingChannelsResponse.ForceClosedChannel.AnchorState - (ChannelEventUpdate_UpdateType)(0), // 14: lnrpc.ChannelEventUpdate.UpdateType - (Invoice_InvoiceState)(0), // 15: lnrpc.Invoice.InvoiceState - (Payment_PaymentStatus)(0), // 16: lnrpc.Payment.PaymentStatus - (HTLCAttempt_HTLCStatus)(0), // 17: lnrpc.HTLCAttempt.HTLCStatus - (Failure_FailureCode)(0), // 18: lnrpc.Failure.FailureCode - (*SubscribeCustomMessagesRequest)(nil), // 19: lnrpc.SubscribeCustomMessagesRequest - (*CustomMessage)(nil), // 20: lnrpc.CustomMessage - (*SendCustomMessageRequest)(nil), // 21: lnrpc.SendCustomMessageRequest - (*SendCustomMessageResponse)(nil), // 22: lnrpc.SendCustomMessageResponse - (*Utxo)(nil), // 23: lnrpc.Utxo - (*Transaction)(nil), // 24: lnrpc.Transaction - (*GetTransactionsRequest)(nil), // 25: lnrpc.GetTransactionsRequest - (*TransactionDetails)(nil), // 26: lnrpc.TransactionDetails - (*FeeLimit)(nil), // 27: lnrpc.FeeLimit - (*SendRequest)(nil), // 28: lnrpc.SendRequest - (*SendResponse)(nil), // 29: lnrpc.SendResponse - (*SendToRouteRequest)(nil), // 30: lnrpc.SendToRouteRequest - (*ChannelAcceptRequest)(nil), // 31: lnrpc.ChannelAcceptRequest - (*ChannelAcceptResponse)(nil), // 32: lnrpc.ChannelAcceptResponse - (*ChannelPoint)(nil), // 33: lnrpc.ChannelPoint - (*OutPoint)(nil), // 34: lnrpc.OutPoint - (*LightningAddress)(nil), // 35: lnrpc.LightningAddress - (*EstimateFeeRequest)(nil), // 36: lnrpc.EstimateFeeRequest - (*EstimateFeeResponse)(nil), // 37: lnrpc.EstimateFeeResponse - (*SendManyRequest)(nil), // 38: lnrpc.SendManyRequest - (*SendManyResponse)(nil), // 39: lnrpc.SendManyResponse - (*SendCoinsRequest)(nil), // 40: lnrpc.SendCoinsRequest - (*SendCoinsResponse)(nil), // 41: lnrpc.SendCoinsResponse - (*ListUnspentRequest)(nil), // 42: lnrpc.ListUnspentRequest - (*ListUnspentResponse)(nil), // 43: lnrpc.ListUnspentResponse - (*NewAddressRequest)(nil), // 44: lnrpc.NewAddressRequest - (*NewAddressResponse)(nil), // 45: lnrpc.NewAddressResponse - (*SignMessageRequest)(nil), // 46: lnrpc.SignMessageRequest - (*SignMessageResponse)(nil), // 47: lnrpc.SignMessageResponse - (*VerifyMessageRequest)(nil), // 48: lnrpc.VerifyMessageRequest - (*VerifyMessageResponse)(nil), // 49: lnrpc.VerifyMessageResponse - (*ConnectPeerRequest)(nil), // 50: lnrpc.ConnectPeerRequest - (*ConnectPeerResponse)(nil), // 51: lnrpc.ConnectPeerResponse - (*DisconnectPeerRequest)(nil), // 52: lnrpc.DisconnectPeerRequest - (*DisconnectPeerResponse)(nil), // 53: lnrpc.DisconnectPeerResponse - (*HTLC)(nil), // 54: lnrpc.HTLC - (*ChannelConstraints)(nil), // 55: lnrpc.ChannelConstraints - (*Channel)(nil), // 56: lnrpc.Channel - (*ListChannelsRequest)(nil), // 57: lnrpc.ListChannelsRequest - (*ListChannelsResponse)(nil), // 58: lnrpc.ListChannelsResponse - (*ChannelCloseSummary)(nil), // 59: lnrpc.ChannelCloseSummary - (*Resolution)(nil), // 60: lnrpc.Resolution - (*ClosedChannelsRequest)(nil), // 61: lnrpc.ClosedChannelsRequest - (*ClosedChannelsResponse)(nil), // 62: lnrpc.ClosedChannelsResponse - (*Peer)(nil), // 63: lnrpc.Peer - (*TimestampedError)(nil), // 64: lnrpc.TimestampedError - (*ListPeersRequest)(nil), // 65: lnrpc.ListPeersRequest - (*ListPeersResponse)(nil), // 66: lnrpc.ListPeersResponse - (*PeerEventSubscription)(nil), // 67: lnrpc.PeerEventSubscription - (*PeerEvent)(nil), // 68: lnrpc.PeerEvent - (*GetInfoRequest)(nil), // 69: lnrpc.GetInfoRequest - (*GetInfoResponse)(nil), // 70: lnrpc.GetInfoResponse - (*GetRecoveryInfoRequest)(nil), // 71: lnrpc.GetRecoveryInfoRequest - (*GetRecoveryInfoResponse)(nil), // 72: lnrpc.GetRecoveryInfoResponse - (*Chain)(nil), // 73: lnrpc.Chain - (*ConfirmationUpdate)(nil), // 74: lnrpc.ConfirmationUpdate - (*ChannelOpenUpdate)(nil), // 75: lnrpc.ChannelOpenUpdate - (*ChannelCloseUpdate)(nil), // 76: lnrpc.ChannelCloseUpdate - (*CloseChannelRequest)(nil), // 77: lnrpc.CloseChannelRequest - (*CloseStatusUpdate)(nil), // 78: lnrpc.CloseStatusUpdate - (*PendingUpdate)(nil), // 79: lnrpc.PendingUpdate - (*ReadyForPsbtFunding)(nil), // 80: lnrpc.ReadyForPsbtFunding - (*BatchOpenChannelRequest)(nil), // 81: lnrpc.BatchOpenChannelRequest - (*BatchOpenChannel)(nil), // 82: lnrpc.BatchOpenChannel - (*BatchOpenChannelResponse)(nil), // 83: lnrpc.BatchOpenChannelResponse - (*OpenChannelRequest)(nil), // 84: lnrpc.OpenChannelRequest - (*OpenStatusUpdate)(nil), // 85: lnrpc.OpenStatusUpdate - (*KeyLocator)(nil), // 86: lnrpc.KeyLocator - (*KeyDescriptor)(nil), // 87: lnrpc.KeyDescriptor - (*ChanPointShim)(nil), // 88: lnrpc.ChanPointShim - (*PsbtShim)(nil), // 89: lnrpc.PsbtShim - (*FundingShim)(nil), // 90: lnrpc.FundingShim - (*FundingShimCancel)(nil), // 91: lnrpc.FundingShimCancel - (*FundingPsbtVerify)(nil), // 92: lnrpc.FundingPsbtVerify - (*FundingPsbtFinalize)(nil), // 93: lnrpc.FundingPsbtFinalize - (*FundingTransitionMsg)(nil), // 94: lnrpc.FundingTransitionMsg - (*FundingStateStepResp)(nil), // 95: lnrpc.FundingStateStepResp - (*PendingHTLC)(nil), // 96: lnrpc.PendingHTLC - (*PendingChannelsRequest)(nil), // 97: lnrpc.PendingChannelsRequest - (*PendingChannelsResponse)(nil), // 98: lnrpc.PendingChannelsResponse - (*ChannelEventSubscription)(nil), // 99: lnrpc.ChannelEventSubscription - (*ChannelEventUpdate)(nil), // 100: lnrpc.ChannelEventUpdate - (*WalletAccountBalance)(nil), // 101: lnrpc.WalletAccountBalance - (*WalletBalanceRequest)(nil), // 102: lnrpc.WalletBalanceRequest - (*WalletBalanceResponse)(nil), // 103: lnrpc.WalletBalanceResponse - (*Amount)(nil), // 104: lnrpc.Amount - (*ChannelBalanceRequest)(nil), // 105: lnrpc.ChannelBalanceRequest - (*ChannelBalanceResponse)(nil), // 106: lnrpc.ChannelBalanceResponse - (*QueryRoutesRequest)(nil), // 107: lnrpc.QueryRoutesRequest - (*NodePair)(nil), // 108: lnrpc.NodePair - (*EdgeLocator)(nil), // 109: lnrpc.EdgeLocator - (*QueryRoutesResponse)(nil), // 110: lnrpc.QueryRoutesResponse - (*Hop)(nil), // 111: lnrpc.Hop - (*MPPRecord)(nil), // 112: lnrpc.MPPRecord - (*AMPRecord)(nil), // 113: lnrpc.AMPRecord - (*Route)(nil), // 114: lnrpc.Route - (*NodeInfoRequest)(nil), // 115: lnrpc.NodeInfoRequest - (*NodeInfo)(nil), // 116: lnrpc.NodeInfo - (*LightningNode)(nil), // 117: lnrpc.LightningNode - (*NodeAddress)(nil), // 118: lnrpc.NodeAddress - (*RoutingPolicy)(nil), // 119: lnrpc.RoutingPolicy - (*ChannelEdge)(nil), // 120: lnrpc.ChannelEdge - (*ChannelGraphRequest)(nil), // 121: lnrpc.ChannelGraphRequest - (*ChannelGraph)(nil), // 122: lnrpc.ChannelGraph - (*NodeMetricsRequest)(nil), // 123: lnrpc.NodeMetricsRequest - (*NodeMetricsResponse)(nil), // 124: lnrpc.NodeMetricsResponse - (*FloatMetric)(nil), // 125: lnrpc.FloatMetric - (*ChanInfoRequest)(nil), // 126: lnrpc.ChanInfoRequest - (*NetworkInfoRequest)(nil), // 127: lnrpc.NetworkInfoRequest - (*NetworkInfo)(nil), // 128: lnrpc.NetworkInfo - (*StopRequest)(nil), // 129: lnrpc.StopRequest - (*StopResponse)(nil), // 130: lnrpc.StopResponse - (*GraphTopologySubscription)(nil), // 131: lnrpc.GraphTopologySubscription - (*GraphTopologyUpdate)(nil), // 132: lnrpc.GraphTopologyUpdate - (*NodeUpdate)(nil), // 133: lnrpc.NodeUpdate - (*ChannelEdgeUpdate)(nil), // 134: lnrpc.ChannelEdgeUpdate - (*ClosedChannelUpdate)(nil), // 135: lnrpc.ClosedChannelUpdate - (*HopHint)(nil), // 136: lnrpc.HopHint - (*SetID)(nil), // 137: lnrpc.SetID - (*RouteHint)(nil), // 138: lnrpc.RouteHint - (*AMPInvoiceState)(nil), // 139: lnrpc.AMPInvoiceState - (*Invoice)(nil), // 140: lnrpc.Invoice - (*InvoiceHTLC)(nil), // 141: lnrpc.InvoiceHTLC - (*AMP)(nil), // 142: lnrpc.AMP - (*AddInvoiceResponse)(nil), // 143: lnrpc.AddInvoiceResponse - (*PaymentHash)(nil), // 144: lnrpc.PaymentHash - (*ListInvoiceRequest)(nil), // 145: lnrpc.ListInvoiceRequest - (*ListInvoiceResponse)(nil), // 146: lnrpc.ListInvoiceResponse - (*InvoiceSubscription)(nil), // 147: lnrpc.InvoiceSubscription - (*Payment)(nil), // 148: lnrpc.Payment - (*HTLCAttempt)(nil), // 149: lnrpc.HTLCAttempt - (*ListPaymentsRequest)(nil), // 150: lnrpc.ListPaymentsRequest - (*ListPaymentsResponse)(nil), // 151: lnrpc.ListPaymentsResponse - (*DeletePaymentRequest)(nil), // 152: lnrpc.DeletePaymentRequest - (*DeleteAllPaymentsRequest)(nil), // 153: lnrpc.DeleteAllPaymentsRequest - (*DeletePaymentResponse)(nil), // 154: lnrpc.DeletePaymentResponse - (*DeleteAllPaymentsResponse)(nil), // 155: lnrpc.DeleteAllPaymentsResponse - (*AbandonChannelRequest)(nil), // 156: lnrpc.AbandonChannelRequest - (*AbandonChannelResponse)(nil), // 157: lnrpc.AbandonChannelResponse - (*DebugLevelRequest)(nil), // 158: lnrpc.DebugLevelRequest - (*DebugLevelResponse)(nil), // 159: lnrpc.DebugLevelResponse - (*PayReqString)(nil), // 160: lnrpc.PayReqString - (*PayReq)(nil), // 161: lnrpc.PayReq - (*Feature)(nil), // 162: lnrpc.Feature - (*FeeReportRequest)(nil), // 163: lnrpc.FeeReportRequest - (*ChannelFeeReport)(nil), // 164: lnrpc.ChannelFeeReport - (*FeeReportResponse)(nil), // 165: lnrpc.FeeReportResponse - (*PolicyUpdateRequest)(nil), // 166: lnrpc.PolicyUpdateRequest - (*FailedUpdate)(nil), // 167: lnrpc.FailedUpdate - (*PolicyUpdateResponse)(nil), // 168: lnrpc.PolicyUpdateResponse - (*ForwardingHistoryRequest)(nil), // 169: lnrpc.ForwardingHistoryRequest - (*ForwardingEvent)(nil), // 170: lnrpc.ForwardingEvent - (*ForwardingHistoryResponse)(nil), // 171: lnrpc.ForwardingHistoryResponse - (*ExportChannelBackupRequest)(nil), // 172: lnrpc.ExportChannelBackupRequest - (*ChannelBackup)(nil), // 173: lnrpc.ChannelBackup - (*MultiChanBackup)(nil), // 174: lnrpc.MultiChanBackup - (*ChanBackupExportRequest)(nil), // 175: lnrpc.ChanBackupExportRequest - (*ChanBackupSnapshot)(nil), // 176: lnrpc.ChanBackupSnapshot - (*ChannelBackups)(nil), // 177: lnrpc.ChannelBackups - (*RestoreChanBackupRequest)(nil), // 178: lnrpc.RestoreChanBackupRequest - (*RestoreBackupResponse)(nil), // 179: lnrpc.RestoreBackupResponse - (*ChannelBackupSubscription)(nil), // 180: lnrpc.ChannelBackupSubscription - (*VerifyChanBackupResponse)(nil), // 181: lnrpc.VerifyChanBackupResponse - (*MacaroonPermission)(nil), // 182: lnrpc.MacaroonPermission - (*BakeMacaroonRequest)(nil), // 183: lnrpc.BakeMacaroonRequest - (*BakeMacaroonResponse)(nil), // 184: lnrpc.BakeMacaroonResponse - (*ListMacaroonIDsRequest)(nil), // 185: lnrpc.ListMacaroonIDsRequest - (*ListMacaroonIDsResponse)(nil), // 186: lnrpc.ListMacaroonIDsResponse - (*DeleteMacaroonIDRequest)(nil), // 187: lnrpc.DeleteMacaroonIDRequest - (*DeleteMacaroonIDResponse)(nil), // 188: lnrpc.DeleteMacaroonIDResponse - (*MacaroonPermissionList)(nil), // 189: lnrpc.MacaroonPermissionList - (*ListPermissionsRequest)(nil), // 190: lnrpc.ListPermissionsRequest - (*ListPermissionsResponse)(nil), // 191: lnrpc.ListPermissionsResponse - (*Failure)(nil), // 192: lnrpc.Failure - (*ChannelUpdate)(nil), // 193: lnrpc.ChannelUpdate - (*MacaroonId)(nil), // 194: lnrpc.MacaroonId - (*Op)(nil), // 195: lnrpc.Op - (*CheckMacPermRequest)(nil), // 196: lnrpc.CheckMacPermRequest - (*CheckMacPermResponse)(nil), // 197: lnrpc.CheckMacPermResponse - (*RPCMiddlewareRequest)(nil), // 198: lnrpc.RPCMiddlewareRequest - (*StreamAuth)(nil), // 199: lnrpc.StreamAuth - (*RPCMessage)(nil), // 200: lnrpc.RPCMessage - (*RPCMiddlewareResponse)(nil), // 201: lnrpc.RPCMiddlewareResponse - (*MiddlewareRegistration)(nil), // 202: lnrpc.MiddlewareRegistration - (*InterceptFeedback)(nil), // 203: lnrpc.InterceptFeedback - nil, // 204: lnrpc.SendRequest.DestCustomRecordsEntry - nil, // 205: lnrpc.EstimateFeeRequest.AddrToAmountEntry - nil, // 206: lnrpc.SendManyRequest.AddrToAmountEntry - nil, // 207: lnrpc.Peer.FeaturesEntry - nil, // 208: lnrpc.GetInfoResponse.FeaturesEntry - (*PendingChannelsResponse_PendingChannel)(nil), // 209: lnrpc.PendingChannelsResponse.PendingChannel - (*PendingChannelsResponse_PendingOpenChannel)(nil), // 210: lnrpc.PendingChannelsResponse.PendingOpenChannel - (*PendingChannelsResponse_WaitingCloseChannel)(nil), // 211: lnrpc.PendingChannelsResponse.WaitingCloseChannel - (*PendingChannelsResponse_Commitments)(nil), // 212: lnrpc.PendingChannelsResponse.Commitments - (*PendingChannelsResponse_ClosedChannel)(nil), // 213: lnrpc.PendingChannelsResponse.ClosedChannel - (*PendingChannelsResponse_ForceClosedChannel)(nil), // 214: lnrpc.PendingChannelsResponse.ForceClosedChannel - nil, // 215: lnrpc.WalletBalanceResponse.AccountBalanceEntry - nil, // 216: lnrpc.QueryRoutesRequest.DestCustomRecordsEntry - nil, // 217: lnrpc.Hop.CustomRecordsEntry - nil, // 218: lnrpc.LightningNode.FeaturesEntry - nil, // 219: lnrpc.NodeMetricsResponse.BetweennessCentralityEntry - nil, // 220: lnrpc.NodeUpdate.FeaturesEntry - nil, // 221: lnrpc.Invoice.FeaturesEntry - nil, // 222: lnrpc.Invoice.AmpInvoiceStateEntry - nil, // 223: lnrpc.InvoiceHTLC.CustomRecordsEntry - nil, // 224: lnrpc.PayReq.FeaturesEntry - nil, // 225: lnrpc.ListPermissionsResponse.MethodPermissionsEntry + (OutputScriptType)(0), // 0: lnrpc.OutputScriptType + (AddressType)(0), // 1: lnrpc.AddressType + (CommitmentType)(0), // 2: lnrpc.CommitmentType + (Initiator)(0), // 3: lnrpc.Initiator + (ResolutionType)(0), // 4: lnrpc.ResolutionType + (ResolutionOutcome)(0), // 5: lnrpc.ResolutionOutcome + (NodeMetricType)(0), // 6: lnrpc.NodeMetricType + (InvoiceHTLCState)(0), // 7: lnrpc.InvoiceHTLCState + (PaymentFailureReason)(0), // 8: lnrpc.PaymentFailureReason + (FeatureBit)(0), // 9: lnrpc.FeatureBit + (UpdateFailure)(0), // 10: lnrpc.UpdateFailure + (ChannelCloseSummary_ClosureType)(0), // 11: lnrpc.ChannelCloseSummary.ClosureType + (Peer_SyncType)(0), // 12: lnrpc.Peer.SyncType + (PeerEvent_EventType)(0), // 13: lnrpc.PeerEvent.EventType + (PendingChannelsResponse_ForceClosedChannel_AnchorState)(0), // 14: lnrpc.PendingChannelsResponse.ForceClosedChannel.AnchorState + (ChannelEventUpdate_UpdateType)(0), // 15: lnrpc.ChannelEventUpdate.UpdateType + (Invoice_InvoiceState)(0), // 16: lnrpc.Invoice.InvoiceState + (Payment_PaymentStatus)(0), // 17: lnrpc.Payment.PaymentStatus + (HTLCAttempt_HTLCStatus)(0), // 18: lnrpc.HTLCAttempt.HTLCStatus + (Failure_FailureCode)(0), // 19: lnrpc.Failure.FailureCode + (*SubscribeCustomMessagesRequest)(nil), // 20: lnrpc.SubscribeCustomMessagesRequest + (*CustomMessage)(nil), // 21: lnrpc.CustomMessage + (*SendCustomMessageRequest)(nil), // 22: lnrpc.SendCustomMessageRequest + (*SendCustomMessageResponse)(nil), // 23: lnrpc.SendCustomMessageResponse + (*Utxo)(nil), // 24: lnrpc.Utxo + (*OutputDetail)(nil), // 25: lnrpc.OutputDetail + (*Transaction)(nil), // 26: lnrpc.Transaction + (*GetTransactionsRequest)(nil), // 27: lnrpc.GetTransactionsRequest + (*TransactionDetails)(nil), // 28: lnrpc.TransactionDetails + (*FeeLimit)(nil), // 29: lnrpc.FeeLimit + (*SendRequest)(nil), // 30: lnrpc.SendRequest + (*SendResponse)(nil), // 31: lnrpc.SendResponse + (*SendToRouteRequest)(nil), // 32: lnrpc.SendToRouteRequest + (*ChannelAcceptRequest)(nil), // 33: lnrpc.ChannelAcceptRequest + (*ChannelAcceptResponse)(nil), // 34: lnrpc.ChannelAcceptResponse + (*ChannelPoint)(nil), // 35: lnrpc.ChannelPoint + (*OutPoint)(nil), // 36: lnrpc.OutPoint + (*LightningAddress)(nil), // 37: lnrpc.LightningAddress + (*EstimateFeeRequest)(nil), // 38: lnrpc.EstimateFeeRequest + (*EstimateFeeResponse)(nil), // 39: lnrpc.EstimateFeeResponse + (*SendManyRequest)(nil), // 40: lnrpc.SendManyRequest + (*SendManyResponse)(nil), // 41: lnrpc.SendManyResponse + (*SendCoinsRequest)(nil), // 42: lnrpc.SendCoinsRequest + (*SendCoinsResponse)(nil), // 43: lnrpc.SendCoinsResponse + (*ListUnspentRequest)(nil), // 44: lnrpc.ListUnspentRequest + (*ListUnspentResponse)(nil), // 45: lnrpc.ListUnspentResponse + (*NewAddressRequest)(nil), // 46: lnrpc.NewAddressRequest + (*NewAddressResponse)(nil), // 47: lnrpc.NewAddressResponse + (*SignMessageRequest)(nil), // 48: lnrpc.SignMessageRequest + (*SignMessageResponse)(nil), // 49: lnrpc.SignMessageResponse + (*VerifyMessageRequest)(nil), // 50: lnrpc.VerifyMessageRequest + (*VerifyMessageResponse)(nil), // 51: lnrpc.VerifyMessageResponse + (*ConnectPeerRequest)(nil), // 52: lnrpc.ConnectPeerRequest + (*ConnectPeerResponse)(nil), // 53: lnrpc.ConnectPeerResponse + (*DisconnectPeerRequest)(nil), // 54: lnrpc.DisconnectPeerRequest + (*DisconnectPeerResponse)(nil), // 55: lnrpc.DisconnectPeerResponse + (*HTLC)(nil), // 56: lnrpc.HTLC + (*ChannelConstraints)(nil), // 57: lnrpc.ChannelConstraints + (*Channel)(nil), // 58: lnrpc.Channel + (*ListChannelsRequest)(nil), // 59: lnrpc.ListChannelsRequest + (*ListChannelsResponse)(nil), // 60: lnrpc.ListChannelsResponse + (*ChannelCloseSummary)(nil), // 61: lnrpc.ChannelCloseSummary + (*Resolution)(nil), // 62: lnrpc.Resolution + (*ClosedChannelsRequest)(nil), // 63: lnrpc.ClosedChannelsRequest + (*ClosedChannelsResponse)(nil), // 64: lnrpc.ClosedChannelsResponse + (*Peer)(nil), // 65: lnrpc.Peer + (*TimestampedError)(nil), // 66: lnrpc.TimestampedError + (*ListPeersRequest)(nil), // 67: lnrpc.ListPeersRequest + (*ListPeersResponse)(nil), // 68: lnrpc.ListPeersResponse + (*PeerEventSubscription)(nil), // 69: lnrpc.PeerEventSubscription + (*PeerEvent)(nil), // 70: lnrpc.PeerEvent + (*GetInfoRequest)(nil), // 71: lnrpc.GetInfoRequest + (*GetInfoResponse)(nil), // 72: lnrpc.GetInfoResponse + (*GetRecoveryInfoRequest)(nil), // 73: lnrpc.GetRecoveryInfoRequest + (*GetRecoveryInfoResponse)(nil), // 74: lnrpc.GetRecoveryInfoResponse + (*Chain)(nil), // 75: lnrpc.Chain + (*ConfirmationUpdate)(nil), // 76: lnrpc.ConfirmationUpdate + (*ChannelOpenUpdate)(nil), // 77: lnrpc.ChannelOpenUpdate + (*ChannelCloseUpdate)(nil), // 78: lnrpc.ChannelCloseUpdate + (*CloseChannelRequest)(nil), // 79: lnrpc.CloseChannelRequest + (*CloseStatusUpdate)(nil), // 80: lnrpc.CloseStatusUpdate + (*PendingUpdate)(nil), // 81: lnrpc.PendingUpdate + (*ReadyForPsbtFunding)(nil), // 82: lnrpc.ReadyForPsbtFunding + (*BatchOpenChannelRequest)(nil), // 83: lnrpc.BatchOpenChannelRequest + (*BatchOpenChannel)(nil), // 84: lnrpc.BatchOpenChannel + (*BatchOpenChannelResponse)(nil), // 85: lnrpc.BatchOpenChannelResponse + (*OpenChannelRequest)(nil), // 86: lnrpc.OpenChannelRequest + (*OpenStatusUpdate)(nil), // 87: lnrpc.OpenStatusUpdate + (*KeyLocator)(nil), // 88: lnrpc.KeyLocator + (*KeyDescriptor)(nil), // 89: lnrpc.KeyDescriptor + (*ChanPointShim)(nil), // 90: lnrpc.ChanPointShim + (*PsbtShim)(nil), // 91: lnrpc.PsbtShim + (*FundingShim)(nil), // 92: lnrpc.FundingShim + (*FundingShimCancel)(nil), // 93: lnrpc.FundingShimCancel + (*FundingPsbtVerify)(nil), // 94: lnrpc.FundingPsbtVerify + (*FundingPsbtFinalize)(nil), // 95: lnrpc.FundingPsbtFinalize + (*FundingTransitionMsg)(nil), // 96: lnrpc.FundingTransitionMsg + (*FundingStateStepResp)(nil), // 97: lnrpc.FundingStateStepResp + (*PendingHTLC)(nil), // 98: lnrpc.PendingHTLC + (*PendingChannelsRequest)(nil), // 99: lnrpc.PendingChannelsRequest + (*PendingChannelsResponse)(nil), // 100: lnrpc.PendingChannelsResponse + (*ChannelEventSubscription)(nil), // 101: lnrpc.ChannelEventSubscription + (*ChannelEventUpdate)(nil), // 102: lnrpc.ChannelEventUpdate + (*WalletAccountBalance)(nil), // 103: lnrpc.WalletAccountBalance + (*WalletBalanceRequest)(nil), // 104: lnrpc.WalletBalanceRequest + (*WalletBalanceResponse)(nil), // 105: lnrpc.WalletBalanceResponse + (*Amount)(nil), // 106: lnrpc.Amount + (*ChannelBalanceRequest)(nil), // 107: lnrpc.ChannelBalanceRequest + (*ChannelBalanceResponse)(nil), // 108: lnrpc.ChannelBalanceResponse + (*QueryRoutesRequest)(nil), // 109: lnrpc.QueryRoutesRequest + (*NodePair)(nil), // 110: lnrpc.NodePair + (*EdgeLocator)(nil), // 111: lnrpc.EdgeLocator + (*QueryRoutesResponse)(nil), // 112: lnrpc.QueryRoutesResponse + (*Hop)(nil), // 113: lnrpc.Hop + (*MPPRecord)(nil), // 114: lnrpc.MPPRecord + (*AMPRecord)(nil), // 115: lnrpc.AMPRecord + (*Route)(nil), // 116: lnrpc.Route + (*NodeInfoRequest)(nil), // 117: lnrpc.NodeInfoRequest + (*NodeInfo)(nil), // 118: lnrpc.NodeInfo + (*LightningNode)(nil), // 119: lnrpc.LightningNode + (*NodeAddress)(nil), // 120: lnrpc.NodeAddress + (*RoutingPolicy)(nil), // 121: lnrpc.RoutingPolicy + (*ChannelEdge)(nil), // 122: lnrpc.ChannelEdge + (*ChannelGraphRequest)(nil), // 123: lnrpc.ChannelGraphRequest + (*ChannelGraph)(nil), // 124: lnrpc.ChannelGraph + (*NodeMetricsRequest)(nil), // 125: lnrpc.NodeMetricsRequest + (*NodeMetricsResponse)(nil), // 126: lnrpc.NodeMetricsResponse + (*FloatMetric)(nil), // 127: lnrpc.FloatMetric + (*ChanInfoRequest)(nil), // 128: lnrpc.ChanInfoRequest + (*NetworkInfoRequest)(nil), // 129: lnrpc.NetworkInfoRequest + (*NetworkInfo)(nil), // 130: lnrpc.NetworkInfo + (*StopRequest)(nil), // 131: lnrpc.StopRequest + (*StopResponse)(nil), // 132: lnrpc.StopResponse + (*GraphTopologySubscription)(nil), // 133: lnrpc.GraphTopologySubscription + (*GraphTopologyUpdate)(nil), // 134: lnrpc.GraphTopologyUpdate + (*NodeUpdate)(nil), // 135: lnrpc.NodeUpdate + (*ChannelEdgeUpdate)(nil), // 136: lnrpc.ChannelEdgeUpdate + (*ClosedChannelUpdate)(nil), // 137: lnrpc.ClosedChannelUpdate + (*HopHint)(nil), // 138: lnrpc.HopHint + (*SetID)(nil), // 139: lnrpc.SetID + (*RouteHint)(nil), // 140: lnrpc.RouteHint + (*AMPInvoiceState)(nil), // 141: lnrpc.AMPInvoiceState + (*Invoice)(nil), // 142: lnrpc.Invoice + (*InvoiceHTLC)(nil), // 143: lnrpc.InvoiceHTLC + (*AMP)(nil), // 144: lnrpc.AMP + (*AddInvoiceResponse)(nil), // 145: lnrpc.AddInvoiceResponse + (*PaymentHash)(nil), // 146: lnrpc.PaymentHash + (*ListInvoiceRequest)(nil), // 147: lnrpc.ListInvoiceRequest + (*ListInvoiceResponse)(nil), // 148: lnrpc.ListInvoiceResponse + (*InvoiceSubscription)(nil), // 149: lnrpc.InvoiceSubscription + (*Payment)(nil), // 150: lnrpc.Payment + (*HTLCAttempt)(nil), // 151: lnrpc.HTLCAttempt + (*ListPaymentsRequest)(nil), // 152: lnrpc.ListPaymentsRequest + (*ListPaymentsResponse)(nil), // 153: lnrpc.ListPaymentsResponse + (*DeletePaymentRequest)(nil), // 154: lnrpc.DeletePaymentRequest + (*DeleteAllPaymentsRequest)(nil), // 155: lnrpc.DeleteAllPaymentsRequest + (*DeletePaymentResponse)(nil), // 156: lnrpc.DeletePaymentResponse + (*DeleteAllPaymentsResponse)(nil), // 157: lnrpc.DeleteAllPaymentsResponse + (*AbandonChannelRequest)(nil), // 158: lnrpc.AbandonChannelRequest + (*AbandonChannelResponse)(nil), // 159: lnrpc.AbandonChannelResponse + (*DebugLevelRequest)(nil), // 160: lnrpc.DebugLevelRequest + (*DebugLevelResponse)(nil), // 161: lnrpc.DebugLevelResponse + (*PayReqString)(nil), // 162: lnrpc.PayReqString + (*PayReq)(nil), // 163: lnrpc.PayReq + (*Feature)(nil), // 164: lnrpc.Feature + (*FeeReportRequest)(nil), // 165: lnrpc.FeeReportRequest + (*ChannelFeeReport)(nil), // 166: lnrpc.ChannelFeeReport + (*FeeReportResponse)(nil), // 167: lnrpc.FeeReportResponse + (*PolicyUpdateRequest)(nil), // 168: lnrpc.PolicyUpdateRequest + (*FailedUpdate)(nil), // 169: lnrpc.FailedUpdate + (*PolicyUpdateResponse)(nil), // 170: lnrpc.PolicyUpdateResponse + (*ForwardingHistoryRequest)(nil), // 171: lnrpc.ForwardingHistoryRequest + (*ForwardingEvent)(nil), // 172: lnrpc.ForwardingEvent + (*ForwardingHistoryResponse)(nil), // 173: lnrpc.ForwardingHistoryResponse + (*ExportChannelBackupRequest)(nil), // 174: lnrpc.ExportChannelBackupRequest + (*ChannelBackup)(nil), // 175: lnrpc.ChannelBackup + (*MultiChanBackup)(nil), // 176: lnrpc.MultiChanBackup + (*ChanBackupExportRequest)(nil), // 177: lnrpc.ChanBackupExportRequest + (*ChanBackupSnapshot)(nil), // 178: lnrpc.ChanBackupSnapshot + (*ChannelBackups)(nil), // 179: lnrpc.ChannelBackups + (*RestoreChanBackupRequest)(nil), // 180: lnrpc.RestoreChanBackupRequest + (*RestoreBackupResponse)(nil), // 181: lnrpc.RestoreBackupResponse + (*ChannelBackupSubscription)(nil), // 182: lnrpc.ChannelBackupSubscription + (*VerifyChanBackupResponse)(nil), // 183: lnrpc.VerifyChanBackupResponse + (*MacaroonPermission)(nil), // 184: lnrpc.MacaroonPermission + (*BakeMacaroonRequest)(nil), // 185: lnrpc.BakeMacaroonRequest + (*BakeMacaroonResponse)(nil), // 186: lnrpc.BakeMacaroonResponse + (*ListMacaroonIDsRequest)(nil), // 187: lnrpc.ListMacaroonIDsRequest + (*ListMacaroonIDsResponse)(nil), // 188: lnrpc.ListMacaroonIDsResponse + (*DeleteMacaroonIDRequest)(nil), // 189: lnrpc.DeleteMacaroonIDRequest + (*DeleteMacaroonIDResponse)(nil), // 190: lnrpc.DeleteMacaroonIDResponse + (*MacaroonPermissionList)(nil), // 191: lnrpc.MacaroonPermissionList + (*ListPermissionsRequest)(nil), // 192: lnrpc.ListPermissionsRequest + (*ListPermissionsResponse)(nil), // 193: lnrpc.ListPermissionsResponse + (*Failure)(nil), // 194: lnrpc.Failure + (*ChannelUpdate)(nil), // 195: lnrpc.ChannelUpdate + (*MacaroonId)(nil), // 196: lnrpc.MacaroonId + (*Op)(nil), // 197: lnrpc.Op + (*CheckMacPermRequest)(nil), // 198: lnrpc.CheckMacPermRequest + (*CheckMacPermResponse)(nil), // 199: lnrpc.CheckMacPermResponse + (*RPCMiddlewareRequest)(nil), // 200: lnrpc.RPCMiddlewareRequest + (*StreamAuth)(nil), // 201: lnrpc.StreamAuth + (*RPCMessage)(nil), // 202: lnrpc.RPCMessage + (*RPCMiddlewareResponse)(nil), // 203: lnrpc.RPCMiddlewareResponse + (*MiddlewareRegistration)(nil), // 204: lnrpc.MiddlewareRegistration + (*InterceptFeedback)(nil), // 205: lnrpc.InterceptFeedback + nil, // 206: lnrpc.SendRequest.DestCustomRecordsEntry + nil, // 207: lnrpc.EstimateFeeRequest.AddrToAmountEntry + nil, // 208: lnrpc.SendManyRequest.AddrToAmountEntry + nil, // 209: lnrpc.Peer.FeaturesEntry + nil, // 210: lnrpc.GetInfoResponse.FeaturesEntry + (*PendingChannelsResponse_PendingChannel)(nil), // 211: lnrpc.PendingChannelsResponse.PendingChannel + (*PendingChannelsResponse_PendingOpenChannel)(nil), // 212: lnrpc.PendingChannelsResponse.PendingOpenChannel + (*PendingChannelsResponse_WaitingCloseChannel)(nil), // 213: lnrpc.PendingChannelsResponse.WaitingCloseChannel + (*PendingChannelsResponse_Commitments)(nil), // 214: lnrpc.PendingChannelsResponse.Commitments + (*PendingChannelsResponse_ClosedChannel)(nil), // 215: lnrpc.PendingChannelsResponse.ClosedChannel + (*PendingChannelsResponse_ForceClosedChannel)(nil), // 216: lnrpc.PendingChannelsResponse.ForceClosedChannel + nil, // 217: lnrpc.WalletBalanceResponse.AccountBalanceEntry + nil, // 218: lnrpc.QueryRoutesRequest.DestCustomRecordsEntry + nil, // 219: lnrpc.Hop.CustomRecordsEntry + nil, // 220: lnrpc.LightningNode.FeaturesEntry + nil, // 221: lnrpc.NodeMetricsResponse.BetweennessCentralityEntry + nil, // 222: lnrpc.NodeUpdate.FeaturesEntry + nil, // 223: lnrpc.Invoice.FeaturesEntry + nil, // 224: lnrpc.Invoice.AmpInvoiceStateEntry + nil, // 225: lnrpc.InvoiceHTLC.CustomRecordsEntry + nil, // 226: lnrpc.PayReq.FeaturesEntry + nil, // 227: lnrpc.ListPermissionsResponse.MethodPermissionsEntry } var file_lightning_proto_depIdxs = []int32{ - 0, // 0: lnrpc.Utxo.address_type:type_name -> lnrpc.AddressType - 34, // 1: lnrpc.Utxo.outpoint:type_name -> lnrpc.OutPoint - 24, // 2: lnrpc.TransactionDetails.transactions:type_name -> lnrpc.Transaction - 27, // 3: lnrpc.SendRequest.fee_limit:type_name -> lnrpc.FeeLimit - 204, // 4: lnrpc.SendRequest.dest_custom_records:type_name -> lnrpc.SendRequest.DestCustomRecordsEntry - 8, // 5: lnrpc.SendRequest.dest_features:type_name -> lnrpc.FeatureBit - 114, // 6: lnrpc.SendResponse.payment_route:type_name -> lnrpc.Route - 114, // 7: lnrpc.SendToRouteRequest.route:type_name -> lnrpc.Route - 1, // 8: lnrpc.ChannelAcceptRequest.commitment_type:type_name -> lnrpc.CommitmentType - 205, // 9: lnrpc.EstimateFeeRequest.AddrToAmount:type_name -> lnrpc.EstimateFeeRequest.AddrToAmountEntry - 206, // 10: lnrpc.SendManyRequest.AddrToAmount:type_name -> lnrpc.SendManyRequest.AddrToAmountEntry - 23, // 11: lnrpc.ListUnspentResponse.utxos:type_name -> lnrpc.Utxo - 0, // 12: lnrpc.NewAddressRequest.type:type_name -> lnrpc.AddressType - 35, // 13: lnrpc.ConnectPeerRequest.addr:type_name -> lnrpc.LightningAddress - 54, // 14: lnrpc.Channel.pending_htlcs:type_name -> lnrpc.HTLC - 1, // 15: lnrpc.Channel.commitment_type:type_name -> lnrpc.CommitmentType - 55, // 16: lnrpc.Channel.local_constraints:type_name -> lnrpc.ChannelConstraints - 55, // 17: lnrpc.Channel.remote_constraints:type_name -> lnrpc.ChannelConstraints - 56, // 18: lnrpc.ListChannelsResponse.channels:type_name -> lnrpc.Channel - 10, // 19: lnrpc.ChannelCloseSummary.close_type:type_name -> lnrpc.ChannelCloseSummary.ClosureType - 2, // 20: lnrpc.ChannelCloseSummary.open_initiator:type_name -> lnrpc.Initiator - 2, // 21: lnrpc.ChannelCloseSummary.close_initiator:type_name -> lnrpc.Initiator - 60, // 22: lnrpc.ChannelCloseSummary.resolutions:type_name -> lnrpc.Resolution - 3, // 23: lnrpc.Resolution.resolution_type:type_name -> lnrpc.ResolutionType - 4, // 24: lnrpc.Resolution.outcome:type_name -> lnrpc.ResolutionOutcome - 34, // 25: lnrpc.Resolution.outpoint:type_name -> lnrpc.OutPoint - 59, // 26: lnrpc.ClosedChannelsResponse.channels:type_name -> lnrpc.ChannelCloseSummary - 11, // 27: lnrpc.Peer.sync_type:type_name -> lnrpc.Peer.SyncType - 207, // 28: lnrpc.Peer.features:type_name -> lnrpc.Peer.FeaturesEntry - 64, // 29: lnrpc.Peer.errors:type_name -> lnrpc.TimestampedError - 63, // 30: lnrpc.ListPeersResponse.peers:type_name -> lnrpc.Peer - 12, // 31: lnrpc.PeerEvent.type:type_name -> lnrpc.PeerEvent.EventType - 73, // 32: lnrpc.GetInfoResponse.chains:type_name -> lnrpc.Chain - 208, // 33: lnrpc.GetInfoResponse.features:type_name -> lnrpc.GetInfoResponse.FeaturesEntry - 33, // 34: lnrpc.ChannelOpenUpdate.channel_point:type_name -> lnrpc.ChannelPoint - 33, // 35: lnrpc.CloseChannelRequest.channel_point:type_name -> lnrpc.ChannelPoint - 79, // 36: lnrpc.CloseStatusUpdate.close_pending:type_name -> lnrpc.PendingUpdate - 76, // 37: lnrpc.CloseStatusUpdate.chan_close:type_name -> lnrpc.ChannelCloseUpdate - 82, // 38: lnrpc.BatchOpenChannelRequest.channels:type_name -> lnrpc.BatchOpenChannel - 1, // 39: lnrpc.BatchOpenChannel.commitment_type:type_name -> lnrpc.CommitmentType - 79, // 40: lnrpc.BatchOpenChannelResponse.pending_channels:type_name -> lnrpc.PendingUpdate - 90, // 41: lnrpc.OpenChannelRequest.funding_shim:type_name -> lnrpc.FundingShim - 1, // 42: lnrpc.OpenChannelRequest.commitment_type:type_name -> lnrpc.CommitmentType - 79, // 43: lnrpc.OpenStatusUpdate.chan_pending:type_name -> lnrpc.PendingUpdate - 75, // 44: lnrpc.OpenStatusUpdate.chan_open:type_name -> lnrpc.ChannelOpenUpdate - 80, // 45: lnrpc.OpenStatusUpdate.psbt_fund:type_name -> lnrpc.ReadyForPsbtFunding - 86, // 46: lnrpc.KeyDescriptor.key_loc:type_name -> lnrpc.KeyLocator - 33, // 47: lnrpc.ChanPointShim.chan_point:type_name -> lnrpc.ChannelPoint - 87, // 48: lnrpc.ChanPointShim.local_key:type_name -> lnrpc.KeyDescriptor - 88, // 49: lnrpc.FundingShim.chan_point_shim:type_name -> lnrpc.ChanPointShim - 89, // 50: lnrpc.FundingShim.psbt_shim:type_name -> lnrpc.PsbtShim - 90, // 51: lnrpc.FundingTransitionMsg.shim_register:type_name -> lnrpc.FundingShim - 91, // 52: lnrpc.FundingTransitionMsg.shim_cancel:type_name -> lnrpc.FundingShimCancel - 92, // 53: lnrpc.FundingTransitionMsg.psbt_verify:type_name -> lnrpc.FundingPsbtVerify - 93, // 54: lnrpc.FundingTransitionMsg.psbt_finalize:type_name -> lnrpc.FundingPsbtFinalize - 210, // 55: lnrpc.PendingChannelsResponse.pending_open_channels:type_name -> lnrpc.PendingChannelsResponse.PendingOpenChannel - 213, // 56: lnrpc.PendingChannelsResponse.pending_closing_channels:type_name -> lnrpc.PendingChannelsResponse.ClosedChannel - 214, // 57: lnrpc.PendingChannelsResponse.pending_force_closing_channels:type_name -> lnrpc.PendingChannelsResponse.ForceClosedChannel - 211, // 58: lnrpc.PendingChannelsResponse.waiting_close_channels:type_name -> lnrpc.PendingChannelsResponse.WaitingCloseChannel - 56, // 59: lnrpc.ChannelEventUpdate.open_channel:type_name -> lnrpc.Channel - 59, // 60: lnrpc.ChannelEventUpdate.closed_channel:type_name -> lnrpc.ChannelCloseSummary - 33, // 61: lnrpc.ChannelEventUpdate.active_channel:type_name -> lnrpc.ChannelPoint - 33, // 62: lnrpc.ChannelEventUpdate.inactive_channel:type_name -> lnrpc.ChannelPoint - 79, // 63: lnrpc.ChannelEventUpdate.pending_open_channel:type_name -> lnrpc.PendingUpdate - 33, // 64: lnrpc.ChannelEventUpdate.fully_resolved_channel:type_name -> lnrpc.ChannelPoint - 14, // 65: lnrpc.ChannelEventUpdate.type:type_name -> lnrpc.ChannelEventUpdate.UpdateType - 215, // 66: lnrpc.WalletBalanceResponse.account_balance:type_name -> lnrpc.WalletBalanceResponse.AccountBalanceEntry - 104, // 67: lnrpc.ChannelBalanceResponse.local_balance:type_name -> lnrpc.Amount - 104, // 68: lnrpc.ChannelBalanceResponse.remote_balance:type_name -> lnrpc.Amount - 104, // 69: lnrpc.ChannelBalanceResponse.unsettled_local_balance:type_name -> lnrpc.Amount - 104, // 70: lnrpc.ChannelBalanceResponse.unsettled_remote_balance:type_name -> lnrpc.Amount - 104, // 71: lnrpc.ChannelBalanceResponse.pending_open_local_balance:type_name -> lnrpc.Amount - 104, // 72: lnrpc.ChannelBalanceResponse.pending_open_remote_balance:type_name -> lnrpc.Amount - 27, // 73: lnrpc.QueryRoutesRequest.fee_limit:type_name -> lnrpc.FeeLimit - 109, // 74: lnrpc.QueryRoutesRequest.ignored_edges:type_name -> lnrpc.EdgeLocator - 108, // 75: lnrpc.QueryRoutesRequest.ignored_pairs:type_name -> lnrpc.NodePair - 216, // 76: lnrpc.QueryRoutesRequest.dest_custom_records:type_name -> lnrpc.QueryRoutesRequest.DestCustomRecordsEntry - 138, // 77: lnrpc.QueryRoutesRequest.route_hints:type_name -> lnrpc.RouteHint - 8, // 78: lnrpc.QueryRoutesRequest.dest_features:type_name -> lnrpc.FeatureBit - 114, // 79: lnrpc.QueryRoutesResponse.routes:type_name -> lnrpc.Route - 112, // 80: lnrpc.Hop.mpp_record:type_name -> lnrpc.MPPRecord - 113, // 81: lnrpc.Hop.amp_record:type_name -> lnrpc.AMPRecord - 217, // 82: lnrpc.Hop.custom_records:type_name -> lnrpc.Hop.CustomRecordsEntry - 111, // 83: lnrpc.Route.hops:type_name -> lnrpc.Hop - 117, // 84: lnrpc.NodeInfo.node:type_name -> lnrpc.LightningNode - 120, // 85: lnrpc.NodeInfo.channels:type_name -> lnrpc.ChannelEdge - 118, // 86: lnrpc.LightningNode.addresses:type_name -> lnrpc.NodeAddress - 218, // 87: lnrpc.LightningNode.features:type_name -> lnrpc.LightningNode.FeaturesEntry - 119, // 88: lnrpc.ChannelEdge.node1_policy:type_name -> lnrpc.RoutingPolicy - 119, // 89: lnrpc.ChannelEdge.node2_policy:type_name -> lnrpc.RoutingPolicy - 117, // 90: lnrpc.ChannelGraph.nodes:type_name -> lnrpc.LightningNode - 120, // 91: lnrpc.ChannelGraph.edges:type_name -> lnrpc.ChannelEdge - 5, // 92: lnrpc.NodeMetricsRequest.types:type_name -> lnrpc.NodeMetricType - 219, // 93: lnrpc.NodeMetricsResponse.betweenness_centrality:type_name -> lnrpc.NodeMetricsResponse.BetweennessCentralityEntry - 133, // 94: lnrpc.GraphTopologyUpdate.node_updates:type_name -> lnrpc.NodeUpdate - 134, // 95: lnrpc.GraphTopologyUpdate.channel_updates:type_name -> lnrpc.ChannelEdgeUpdate - 135, // 96: lnrpc.GraphTopologyUpdate.closed_chans:type_name -> lnrpc.ClosedChannelUpdate - 118, // 97: lnrpc.NodeUpdate.node_addresses:type_name -> lnrpc.NodeAddress - 220, // 98: lnrpc.NodeUpdate.features:type_name -> lnrpc.NodeUpdate.FeaturesEntry - 33, // 99: lnrpc.ChannelEdgeUpdate.chan_point:type_name -> lnrpc.ChannelPoint - 119, // 100: lnrpc.ChannelEdgeUpdate.routing_policy:type_name -> lnrpc.RoutingPolicy - 33, // 101: lnrpc.ClosedChannelUpdate.chan_point:type_name -> lnrpc.ChannelPoint - 136, // 102: lnrpc.RouteHint.hop_hints:type_name -> lnrpc.HopHint - 6, // 103: lnrpc.AMPInvoiceState.state:type_name -> lnrpc.InvoiceHTLCState - 138, // 104: lnrpc.Invoice.route_hints:type_name -> lnrpc.RouteHint - 15, // 105: lnrpc.Invoice.state:type_name -> lnrpc.Invoice.InvoiceState - 141, // 106: lnrpc.Invoice.htlcs:type_name -> lnrpc.InvoiceHTLC - 221, // 107: lnrpc.Invoice.features:type_name -> lnrpc.Invoice.FeaturesEntry - 222, // 108: lnrpc.Invoice.amp_invoice_state:type_name -> lnrpc.Invoice.AmpInvoiceStateEntry - 6, // 109: lnrpc.InvoiceHTLC.state:type_name -> lnrpc.InvoiceHTLCState - 223, // 110: lnrpc.InvoiceHTLC.custom_records:type_name -> lnrpc.InvoiceHTLC.CustomRecordsEntry - 142, // 111: lnrpc.InvoiceHTLC.amp:type_name -> lnrpc.AMP - 140, // 112: lnrpc.ListInvoiceResponse.invoices:type_name -> lnrpc.Invoice - 16, // 113: lnrpc.Payment.status:type_name -> lnrpc.Payment.PaymentStatus - 149, // 114: lnrpc.Payment.htlcs:type_name -> lnrpc.HTLCAttempt - 7, // 115: lnrpc.Payment.failure_reason:type_name -> lnrpc.PaymentFailureReason - 17, // 116: lnrpc.HTLCAttempt.status:type_name -> lnrpc.HTLCAttempt.HTLCStatus - 114, // 117: lnrpc.HTLCAttempt.route:type_name -> lnrpc.Route - 192, // 118: lnrpc.HTLCAttempt.failure:type_name -> lnrpc.Failure - 148, // 119: lnrpc.ListPaymentsResponse.payments:type_name -> lnrpc.Payment - 33, // 120: lnrpc.AbandonChannelRequest.channel_point:type_name -> lnrpc.ChannelPoint - 138, // 121: lnrpc.PayReq.route_hints:type_name -> lnrpc.RouteHint - 224, // 122: lnrpc.PayReq.features:type_name -> lnrpc.PayReq.FeaturesEntry - 164, // 123: lnrpc.FeeReportResponse.channel_fees:type_name -> lnrpc.ChannelFeeReport - 33, // 124: lnrpc.PolicyUpdateRequest.chan_point:type_name -> lnrpc.ChannelPoint - 34, // 125: lnrpc.FailedUpdate.outpoint:type_name -> lnrpc.OutPoint - 9, // 126: lnrpc.FailedUpdate.reason:type_name -> lnrpc.UpdateFailure - 167, // 127: lnrpc.PolicyUpdateResponse.failed_updates:type_name -> lnrpc.FailedUpdate - 170, // 128: lnrpc.ForwardingHistoryResponse.forwarding_events:type_name -> lnrpc.ForwardingEvent - 33, // 129: lnrpc.ExportChannelBackupRequest.chan_point:type_name -> lnrpc.ChannelPoint - 33, // 130: lnrpc.ChannelBackup.chan_point:type_name -> lnrpc.ChannelPoint - 33, // 131: lnrpc.MultiChanBackup.chan_points:type_name -> lnrpc.ChannelPoint - 177, // 132: lnrpc.ChanBackupSnapshot.single_chan_backups:type_name -> lnrpc.ChannelBackups - 174, // 133: lnrpc.ChanBackupSnapshot.multi_chan_backup:type_name -> lnrpc.MultiChanBackup - 173, // 134: lnrpc.ChannelBackups.chan_backups:type_name -> lnrpc.ChannelBackup - 177, // 135: lnrpc.RestoreChanBackupRequest.chan_backups:type_name -> lnrpc.ChannelBackups - 182, // 136: lnrpc.BakeMacaroonRequest.permissions:type_name -> lnrpc.MacaroonPermission - 182, // 137: lnrpc.MacaroonPermissionList.permissions:type_name -> lnrpc.MacaroonPermission - 225, // 138: lnrpc.ListPermissionsResponse.method_permissions:type_name -> lnrpc.ListPermissionsResponse.MethodPermissionsEntry - 18, // 139: lnrpc.Failure.code:type_name -> lnrpc.Failure.FailureCode - 193, // 140: lnrpc.Failure.channel_update:type_name -> lnrpc.ChannelUpdate - 195, // 141: lnrpc.MacaroonId.ops:type_name -> lnrpc.Op - 182, // 142: lnrpc.CheckMacPermRequest.permissions:type_name -> lnrpc.MacaroonPermission - 199, // 143: lnrpc.RPCMiddlewareRequest.stream_auth:type_name -> lnrpc.StreamAuth - 200, // 144: lnrpc.RPCMiddlewareRequest.request:type_name -> lnrpc.RPCMessage - 200, // 145: lnrpc.RPCMiddlewareRequest.response:type_name -> lnrpc.RPCMessage - 202, // 146: lnrpc.RPCMiddlewareResponse.register:type_name -> lnrpc.MiddlewareRegistration - 203, // 147: lnrpc.RPCMiddlewareResponse.feedback:type_name -> lnrpc.InterceptFeedback - 162, // 148: lnrpc.Peer.FeaturesEntry.value:type_name -> lnrpc.Feature - 162, // 149: lnrpc.GetInfoResponse.FeaturesEntry.value:type_name -> lnrpc.Feature - 2, // 150: lnrpc.PendingChannelsResponse.PendingChannel.initiator:type_name -> lnrpc.Initiator - 1, // 151: lnrpc.PendingChannelsResponse.PendingChannel.commitment_type:type_name -> lnrpc.CommitmentType - 209, // 152: lnrpc.PendingChannelsResponse.PendingOpenChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel - 209, // 153: lnrpc.PendingChannelsResponse.WaitingCloseChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel - 212, // 154: lnrpc.PendingChannelsResponse.WaitingCloseChannel.commitments:type_name -> lnrpc.PendingChannelsResponse.Commitments - 209, // 155: lnrpc.PendingChannelsResponse.ClosedChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel - 209, // 156: lnrpc.PendingChannelsResponse.ForceClosedChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel - 96, // 157: lnrpc.PendingChannelsResponse.ForceClosedChannel.pending_htlcs:type_name -> lnrpc.PendingHTLC - 13, // 158: lnrpc.PendingChannelsResponse.ForceClosedChannel.anchor:type_name -> lnrpc.PendingChannelsResponse.ForceClosedChannel.AnchorState - 101, // 159: lnrpc.WalletBalanceResponse.AccountBalanceEntry.value:type_name -> lnrpc.WalletAccountBalance - 162, // 160: lnrpc.LightningNode.FeaturesEntry.value:type_name -> lnrpc.Feature - 125, // 161: lnrpc.NodeMetricsResponse.BetweennessCentralityEntry.value:type_name -> lnrpc.FloatMetric - 162, // 162: lnrpc.NodeUpdate.FeaturesEntry.value:type_name -> lnrpc.Feature - 162, // 163: lnrpc.Invoice.FeaturesEntry.value:type_name -> lnrpc.Feature - 139, // 164: lnrpc.Invoice.AmpInvoiceStateEntry.value:type_name -> lnrpc.AMPInvoiceState - 162, // 165: lnrpc.PayReq.FeaturesEntry.value:type_name -> lnrpc.Feature - 189, // 166: lnrpc.ListPermissionsResponse.MethodPermissionsEntry.value:type_name -> lnrpc.MacaroonPermissionList - 102, // 167: lnrpc.Lightning.WalletBalance:input_type -> lnrpc.WalletBalanceRequest - 105, // 168: lnrpc.Lightning.ChannelBalance:input_type -> lnrpc.ChannelBalanceRequest - 25, // 169: lnrpc.Lightning.GetTransactions:input_type -> lnrpc.GetTransactionsRequest - 36, // 170: lnrpc.Lightning.EstimateFee:input_type -> lnrpc.EstimateFeeRequest - 40, // 171: lnrpc.Lightning.SendCoins:input_type -> lnrpc.SendCoinsRequest - 42, // 172: lnrpc.Lightning.ListUnspent:input_type -> lnrpc.ListUnspentRequest - 25, // 173: lnrpc.Lightning.SubscribeTransactions:input_type -> lnrpc.GetTransactionsRequest - 38, // 174: lnrpc.Lightning.SendMany:input_type -> lnrpc.SendManyRequest - 44, // 175: lnrpc.Lightning.NewAddress:input_type -> lnrpc.NewAddressRequest - 46, // 176: lnrpc.Lightning.SignMessage:input_type -> lnrpc.SignMessageRequest - 48, // 177: lnrpc.Lightning.VerifyMessage:input_type -> lnrpc.VerifyMessageRequest - 50, // 178: lnrpc.Lightning.ConnectPeer:input_type -> lnrpc.ConnectPeerRequest - 52, // 179: lnrpc.Lightning.DisconnectPeer:input_type -> lnrpc.DisconnectPeerRequest - 65, // 180: lnrpc.Lightning.ListPeers:input_type -> lnrpc.ListPeersRequest - 67, // 181: lnrpc.Lightning.SubscribePeerEvents:input_type -> lnrpc.PeerEventSubscription - 69, // 182: lnrpc.Lightning.GetInfo:input_type -> lnrpc.GetInfoRequest - 71, // 183: lnrpc.Lightning.GetRecoveryInfo:input_type -> lnrpc.GetRecoveryInfoRequest - 97, // 184: lnrpc.Lightning.PendingChannels:input_type -> lnrpc.PendingChannelsRequest - 57, // 185: lnrpc.Lightning.ListChannels:input_type -> lnrpc.ListChannelsRequest - 99, // 186: lnrpc.Lightning.SubscribeChannelEvents:input_type -> lnrpc.ChannelEventSubscription - 61, // 187: lnrpc.Lightning.ClosedChannels:input_type -> lnrpc.ClosedChannelsRequest - 84, // 188: lnrpc.Lightning.OpenChannelSync:input_type -> lnrpc.OpenChannelRequest - 84, // 189: lnrpc.Lightning.OpenChannel:input_type -> lnrpc.OpenChannelRequest - 81, // 190: lnrpc.Lightning.BatchOpenChannel:input_type -> lnrpc.BatchOpenChannelRequest - 94, // 191: lnrpc.Lightning.FundingStateStep:input_type -> lnrpc.FundingTransitionMsg - 32, // 192: lnrpc.Lightning.ChannelAcceptor:input_type -> lnrpc.ChannelAcceptResponse - 77, // 193: lnrpc.Lightning.CloseChannel:input_type -> lnrpc.CloseChannelRequest - 156, // 194: lnrpc.Lightning.AbandonChannel:input_type -> lnrpc.AbandonChannelRequest - 28, // 195: lnrpc.Lightning.SendPayment:input_type -> lnrpc.SendRequest - 28, // 196: lnrpc.Lightning.SendPaymentSync:input_type -> lnrpc.SendRequest - 30, // 197: lnrpc.Lightning.SendToRoute:input_type -> lnrpc.SendToRouteRequest - 30, // 198: lnrpc.Lightning.SendToRouteSync:input_type -> lnrpc.SendToRouteRequest - 140, // 199: lnrpc.Lightning.AddInvoice:input_type -> lnrpc.Invoice - 145, // 200: lnrpc.Lightning.ListInvoices:input_type -> lnrpc.ListInvoiceRequest - 144, // 201: lnrpc.Lightning.LookupInvoice:input_type -> lnrpc.PaymentHash - 147, // 202: lnrpc.Lightning.SubscribeInvoices:input_type -> lnrpc.InvoiceSubscription - 160, // 203: lnrpc.Lightning.DecodePayReq:input_type -> lnrpc.PayReqString - 150, // 204: lnrpc.Lightning.ListPayments:input_type -> lnrpc.ListPaymentsRequest - 152, // 205: lnrpc.Lightning.DeletePayment:input_type -> lnrpc.DeletePaymentRequest - 153, // 206: lnrpc.Lightning.DeleteAllPayments:input_type -> lnrpc.DeleteAllPaymentsRequest - 121, // 207: lnrpc.Lightning.DescribeGraph:input_type -> lnrpc.ChannelGraphRequest - 123, // 208: lnrpc.Lightning.GetNodeMetrics:input_type -> lnrpc.NodeMetricsRequest - 126, // 209: lnrpc.Lightning.GetChanInfo:input_type -> lnrpc.ChanInfoRequest - 115, // 210: lnrpc.Lightning.GetNodeInfo:input_type -> lnrpc.NodeInfoRequest - 107, // 211: lnrpc.Lightning.QueryRoutes:input_type -> lnrpc.QueryRoutesRequest - 127, // 212: lnrpc.Lightning.GetNetworkInfo:input_type -> lnrpc.NetworkInfoRequest - 129, // 213: lnrpc.Lightning.StopDaemon:input_type -> lnrpc.StopRequest - 131, // 214: lnrpc.Lightning.SubscribeChannelGraph:input_type -> lnrpc.GraphTopologySubscription - 158, // 215: lnrpc.Lightning.DebugLevel:input_type -> lnrpc.DebugLevelRequest - 163, // 216: lnrpc.Lightning.FeeReport:input_type -> lnrpc.FeeReportRequest - 166, // 217: lnrpc.Lightning.UpdateChannelPolicy:input_type -> lnrpc.PolicyUpdateRequest - 169, // 218: lnrpc.Lightning.ForwardingHistory:input_type -> lnrpc.ForwardingHistoryRequest - 172, // 219: lnrpc.Lightning.ExportChannelBackup:input_type -> lnrpc.ExportChannelBackupRequest - 175, // 220: lnrpc.Lightning.ExportAllChannelBackups:input_type -> lnrpc.ChanBackupExportRequest - 176, // 221: lnrpc.Lightning.VerifyChanBackup:input_type -> lnrpc.ChanBackupSnapshot - 178, // 222: lnrpc.Lightning.RestoreChannelBackups:input_type -> lnrpc.RestoreChanBackupRequest - 180, // 223: lnrpc.Lightning.SubscribeChannelBackups:input_type -> lnrpc.ChannelBackupSubscription - 183, // 224: lnrpc.Lightning.BakeMacaroon:input_type -> lnrpc.BakeMacaroonRequest - 185, // 225: lnrpc.Lightning.ListMacaroonIDs:input_type -> lnrpc.ListMacaroonIDsRequest - 187, // 226: lnrpc.Lightning.DeleteMacaroonID:input_type -> lnrpc.DeleteMacaroonIDRequest - 190, // 227: lnrpc.Lightning.ListPermissions:input_type -> lnrpc.ListPermissionsRequest - 196, // 228: lnrpc.Lightning.CheckMacaroonPermissions:input_type -> lnrpc.CheckMacPermRequest - 201, // 229: lnrpc.Lightning.RegisterRPCMiddleware:input_type -> lnrpc.RPCMiddlewareResponse - 21, // 230: lnrpc.Lightning.SendCustomMessage:input_type -> lnrpc.SendCustomMessageRequest - 19, // 231: lnrpc.Lightning.SubscribeCustomMessages:input_type -> lnrpc.SubscribeCustomMessagesRequest - 103, // 232: lnrpc.Lightning.WalletBalance:output_type -> lnrpc.WalletBalanceResponse - 106, // 233: lnrpc.Lightning.ChannelBalance:output_type -> lnrpc.ChannelBalanceResponse - 26, // 234: lnrpc.Lightning.GetTransactions:output_type -> lnrpc.TransactionDetails - 37, // 235: lnrpc.Lightning.EstimateFee:output_type -> lnrpc.EstimateFeeResponse - 41, // 236: lnrpc.Lightning.SendCoins:output_type -> lnrpc.SendCoinsResponse - 43, // 237: lnrpc.Lightning.ListUnspent:output_type -> lnrpc.ListUnspentResponse - 24, // 238: lnrpc.Lightning.SubscribeTransactions:output_type -> lnrpc.Transaction - 39, // 239: lnrpc.Lightning.SendMany:output_type -> lnrpc.SendManyResponse - 45, // 240: lnrpc.Lightning.NewAddress:output_type -> lnrpc.NewAddressResponse - 47, // 241: lnrpc.Lightning.SignMessage:output_type -> lnrpc.SignMessageResponse - 49, // 242: lnrpc.Lightning.VerifyMessage:output_type -> lnrpc.VerifyMessageResponse - 51, // 243: lnrpc.Lightning.ConnectPeer:output_type -> lnrpc.ConnectPeerResponse - 53, // 244: lnrpc.Lightning.DisconnectPeer:output_type -> lnrpc.DisconnectPeerResponse - 66, // 245: lnrpc.Lightning.ListPeers:output_type -> lnrpc.ListPeersResponse - 68, // 246: lnrpc.Lightning.SubscribePeerEvents:output_type -> lnrpc.PeerEvent - 70, // 247: lnrpc.Lightning.GetInfo:output_type -> lnrpc.GetInfoResponse - 72, // 248: lnrpc.Lightning.GetRecoveryInfo:output_type -> lnrpc.GetRecoveryInfoResponse - 98, // 249: lnrpc.Lightning.PendingChannels:output_type -> lnrpc.PendingChannelsResponse - 58, // 250: lnrpc.Lightning.ListChannels:output_type -> lnrpc.ListChannelsResponse - 100, // 251: lnrpc.Lightning.SubscribeChannelEvents:output_type -> lnrpc.ChannelEventUpdate - 62, // 252: lnrpc.Lightning.ClosedChannels:output_type -> lnrpc.ClosedChannelsResponse - 33, // 253: lnrpc.Lightning.OpenChannelSync:output_type -> lnrpc.ChannelPoint - 85, // 254: lnrpc.Lightning.OpenChannel:output_type -> lnrpc.OpenStatusUpdate - 83, // 255: lnrpc.Lightning.BatchOpenChannel:output_type -> lnrpc.BatchOpenChannelResponse - 95, // 256: lnrpc.Lightning.FundingStateStep:output_type -> lnrpc.FundingStateStepResp - 31, // 257: lnrpc.Lightning.ChannelAcceptor:output_type -> lnrpc.ChannelAcceptRequest - 78, // 258: lnrpc.Lightning.CloseChannel:output_type -> lnrpc.CloseStatusUpdate - 157, // 259: lnrpc.Lightning.AbandonChannel:output_type -> lnrpc.AbandonChannelResponse - 29, // 260: lnrpc.Lightning.SendPayment:output_type -> lnrpc.SendResponse - 29, // 261: lnrpc.Lightning.SendPaymentSync:output_type -> lnrpc.SendResponse - 29, // 262: lnrpc.Lightning.SendToRoute:output_type -> lnrpc.SendResponse - 29, // 263: lnrpc.Lightning.SendToRouteSync:output_type -> lnrpc.SendResponse - 143, // 264: lnrpc.Lightning.AddInvoice:output_type -> lnrpc.AddInvoiceResponse - 146, // 265: lnrpc.Lightning.ListInvoices:output_type -> lnrpc.ListInvoiceResponse - 140, // 266: lnrpc.Lightning.LookupInvoice:output_type -> lnrpc.Invoice - 140, // 267: lnrpc.Lightning.SubscribeInvoices:output_type -> lnrpc.Invoice - 161, // 268: lnrpc.Lightning.DecodePayReq:output_type -> lnrpc.PayReq - 151, // 269: lnrpc.Lightning.ListPayments:output_type -> lnrpc.ListPaymentsResponse - 154, // 270: lnrpc.Lightning.DeletePayment:output_type -> lnrpc.DeletePaymentResponse - 155, // 271: lnrpc.Lightning.DeleteAllPayments:output_type -> lnrpc.DeleteAllPaymentsResponse - 122, // 272: lnrpc.Lightning.DescribeGraph:output_type -> lnrpc.ChannelGraph - 124, // 273: lnrpc.Lightning.GetNodeMetrics:output_type -> lnrpc.NodeMetricsResponse - 120, // 274: lnrpc.Lightning.GetChanInfo:output_type -> lnrpc.ChannelEdge - 116, // 275: lnrpc.Lightning.GetNodeInfo:output_type -> lnrpc.NodeInfo - 110, // 276: lnrpc.Lightning.QueryRoutes:output_type -> lnrpc.QueryRoutesResponse - 128, // 277: lnrpc.Lightning.GetNetworkInfo:output_type -> lnrpc.NetworkInfo - 130, // 278: lnrpc.Lightning.StopDaemon:output_type -> lnrpc.StopResponse - 132, // 279: lnrpc.Lightning.SubscribeChannelGraph:output_type -> lnrpc.GraphTopologyUpdate - 159, // 280: lnrpc.Lightning.DebugLevel:output_type -> lnrpc.DebugLevelResponse - 165, // 281: lnrpc.Lightning.FeeReport:output_type -> lnrpc.FeeReportResponse - 168, // 282: lnrpc.Lightning.UpdateChannelPolicy:output_type -> lnrpc.PolicyUpdateResponse - 171, // 283: lnrpc.Lightning.ForwardingHistory:output_type -> lnrpc.ForwardingHistoryResponse - 173, // 284: lnrpc.Lightning.ExportChannelBackup:output_type -> lnrpc.ChannelBackup - 176, // 285: lnrpc.Lightning.ExportAllChannelBackups:output_type -> lnrpc.ChanBackupSnapshot - 181, // 286: lnrpc.Lightning.VerifyChanBackup:output_type -> lnrpc.VerifyChanBackupResponse - 179, // 287: lnrpc.Lightning.RestoreChannelBackups:output_type -> lnrpc.RestoreBackupResponse - 176, // 288: lnrpc.Lightning.SubscribeChannelBackups:output_type -> lnrpc.ChanBackupSnapshot - 184, // 289: lnrpc.Lightning.BakeMacaroon:output_type -> lnrpc.BakeMacaroonResponse - 186, // 290: lnrpc.Lightning.ListMacaroonIDs:output_type -> lnrpc.ListMacaroonIDsResponse - 188, // 291: lnrpc.Lightning.DeleteMacaroonID:output_type -> lnrpc.DeleteMacaroonIDResponse - 191, // 292: lnrpc.Lightning.ListPermissions:output_type -> lnrpc.ListPermissionsResponse - 197, // 293: lnrpc.Lightning.CheckMacaroonPermissions:output_type -> lnrpc.CheckMacPermResponse - 198, // 294: lnrpc.Lightning.RegisterRPCMiddleware:output_type -> lnrpc.RPCMiddlewareRequest - 22, // 295: lnrpc.Lightning.SendCustomMessage:output_type -> lnrpc.SendCustomMessageResponse - 20, // 296: lnrpc.Lightning.SubscribeCustomMessages:output_type -> lnrpc.CustomMessage - 232, // [232:297] is the sub-list for method output_type - 167, // [167:232] is the sub-list for method input_type - 167, // [167:167] is the sub-list for extension type_name - 167, // [167:167] is the sub-list for extension extendee - 0, // [0:167] is the sub-list for field type_name + 1, // 0: lnrpc.Utxo.address_type:type_name -> lnrpc.AddressType + 36, // 1: lnrpc.Utxo.outpoint:type_name -> lnrpc.OutPoint + 0, // 2: lnrpc.OutputDetail.output_type:type_name -> lnrpc.OutputScriptType + 25, // 3: lnrpc.Transaction.output_details:type_name -> lnrpc.OutputDetail + 26, // 4: lnrpc.TransactionDetails.transactions:type_name -> lnrpc.Transaction + 29, // 5: lnrpc.SendRequest.fee_limit:type_name -> lnrpc.FeeLimit + 206, // 6: lnrpc.SendRequest.dest_custom_records:type_name -> lnrpc.SendRequest.DestCustomRecordsEntry + 9, // 7: lnrpc.SendRequest.dest_features:type_name -> lnrpc.FeatureBit + 116, // 8: lnrpc.SendResponse.payment_route:type_name -> lnrpc.Route + 116, // 9: lnrpc.SendToRouteRequest.route:type_name -> lnrpc.Route + 2, // 10: lnrpc.ChannelAcceptRequest.commitment_type:type_name -> lnrpc.CommitmentType + 207, // 11: lnrpc.EstimateFeeRequest.AddrToAmount:type_name -> lnrpc.EstimateFeeRequest.AddrToAmountEntry + 208, // 12: lnrpc.SendManyRequest.AddrToAmount:type_name -> lnrpc.SendManyRequest.AddrToAmountEntry + 24, // 13: lnrpc.ListUnspentResponse.utxos:type_name -> lnrpc.Utxo + 1, // 14: lnrpc.NewAddressRequest.type:type_name -> lnrpc.AddressType + 37, // 15: lnrpc.ConnectPeerRequest.addr:type_name -> lnrpc.LightningAddress + 56, // 16: lnrpc.Channel.pending_htlcs:type_name -> lnrpc.HTLC + 2, // 17: lnrpc.Channel.commitment_type:type_name -> lnrpc.CommitmentType + 57, // 18: lnrpc.Channel.local_constraints:type_name -> lnrpc.ChannelConstraints + 57, // 19: lnrpc.Channel.remote_constraints:type_name -> lnrpc.ChannelConstraints + 58, // 20: lnrpc.ListChannelsResponse.channels:type_name -> lnrpc.Channel + 11, // 21: lnrpc.ChannelCloseSummary.close_type:type_name -> lnrpc.ChannelCloseSummary.ClosureType + 3, // 22: lnrpc.ChannelCloseSummary.open_initiator:type_name -> lnrpc.Initiator + 3, // 23: lnrpc.ChannelCloseSummary.close_initiator:type_name -> lnrpc.Initiator + 62, // 24: lnrpc.ChannelCloseSummary.resolutions:type_name -> lnrpc.Resolution + 4, // 25: lnrpc.Resolution.resolution_type:type_name -> lnrpc.ResolutionType + 5, // 26: lnrpc.Resolution.outcome:type_name -> lnrpc.ResolutionOutcome + 36, // 27: lnrpc.Resolution.outpoint:type_name -> lnrpc.OutPoint + 61, // 28: lnrpc.ClosedChannelsResponse.channels:type_name -> lnrpc.ChannelCloseSummary + 12, // 29: lnrpc.Peer.sync_type:type_name -> lnrpc.Peer.SyncType + 209, // 30: lnrpc.Peer.features:type_name -> lnrpc.Peer.FeaturesEntry + 66, // 31: lnrpc.Peer.errors:type_name -> lnrpc.TimestampedError + 65, // 32: lnrpc.ListPeersResponse.peers:type_name -> lnrpc.Peer + 13, // 33: lnrpc.PeerEvent.type:type_name -> lnrpc.PeerEvent.EventType + 75, // 34: lnrpc.GetInfoResponse.chains:type_name -> lnrpc.Chain + 210, // 35: lnrpc.GetInfoResponse.features:type_name -> lnrpc.GetInfoResponse.FeaturesEntry + 35, // 36: lnrpc.ChannelOpenUpdate.channel_point:type_name -> lnrpc.ChannelPoint + 35, // 37: lnrpc.CloseChannelRequest.channel_point:type_name -> lnrpc.ChannelPoint + 81, // 38: lnrpc.CloseStatusUpdate.close_pending:type_name -> lnrpc.PendingUpdate + 78, // 39: lnrpc.CloseStatusUpdate.chan_close:type_name -> lnrpc.ChannelCloseUpdate + 84, // 40: lnrpc.BatchOpenChannelRequest.channels:type_name -> lnrpc.BatchOpenChannel + 2, // 41: lnrpc.BatchOpenChannel.commitment_type:type_name -> lnrpc.CommitmentType + 81, // 42: lnrpc.BatchOpenChannelResponse.pending_channels:type_name -> lnrpc.PendingUpdate + 92, // 43: lnrpc.OpenChannelRequest.funding_shim:type_name -> lnrpc.FundingShim + 2, // 44: lnrpc.OpenChannelRequest.commitment_type:type_name -> lnrpc.CommitmentType + 81, // 45: lnrpc.OpenStatusUpdate.chan_pending:type_name -> lnrpc.PendingUpdate + 77, // 46: lnrpc.OpenStatusUpdate.chan_open:type_name -> lnrpc.ChannelOpenUpdate + 82, // 47: lnrpc.OpenStatusUpdate.psbt_fund:type_name -> lnrpc.ReadyForPsbtFunding + 88, // 48: lnrpc.KeyDescriptor.key_loc:type_name -> lnrpc.KeyLocator + 35, // 49: lnrpc.ChanPointShim.chan_point:type_name -> lnrpc.ChannelPoint + 89, // 50: lnrpc.ChanPointShim.local_key:type_name -> lnrpc.KeyDescriptor + 90, // 51: lnrpc.FundingShim.chan_point_shim:type_name -> lnrpc.ChanPointShim + 91, // 52: lnrpc.FundingShim.psbt_shim:type_name -> lnrpc.PsbtShim + 92, // 53: lnrpc.FundingTransitionMsg.shim_register:type_name -> lnrpc.FundingShim + 93, // 54: lnrpc.FundingTransitionMsg.shim_cancel:type_name -> lnrpc.FundingShimCancel + 94, // 55: lnrpc.FundingTransitionMsg.psbt_verify:type_name -> lnrpc.FundingPsbtVerify + 95, // 56: lnrpc.FundingTransitionMsg.psbt_finalize:type_name -> lnrpc.FundingPsbtFinalize + 212, // 57: lnrpc.PendingChannelsResponse.pending_open_channels:type_name -> lnrpc.PendingChannelsResponse.PendingOpenChannel + 215, // 58: lnrpc.PendingChannelsResponse.pending_closing_channels:type_name -> lnrpc.PendingChannelsResponse.ClosedChannel + 216, // 59: lnrpc.PendingChannelsResponse.pending_force_closing_channels:type_name -> lnrpc.PendingChannelsResponse.ForceClosedChannel + 213, // 60: lnrpc.PendingChannelsResponse.waiting_close_channels:type_name -> lnrpc.PendingChannelsResponse.WaitingCloseChannel + 58, // 61: lnrpc.ChannelEventUpdate.open_channel:type_name -> lnrpc.Channel + 61, // 62: lnrpc.ChannelEventUpdate.closed_channel:type_name -> lnrpc.ChannelCloseSummary + 35, // 63: lnrpc.ChannelEventUpdate.active_channel:type_name -> lnrpc.ChannelPoint + 35, // 64: lnrpc.ChannelEventUpdate.inactive_channel:type_name -> lnrpc.ChannelPoint + 81, // 65: lnrpc.ChannelEventUpdate.pending_open_channel:type_name -> lnrpc.PendingUpdate + 35, // 66: lnrpc.ChannelEventUpdate.fully_resolved_channel:type_name -> lnrpc.ChannelPoint + 15, // 67: lnrpc.ChannelEventUpdate.type:type_name -> lnrpc.ChannelEventUpdate.UpdateType + 217, // 68: lnrpc.WalletBalanceResponse.account_balance:type_name -> lnrpc.WalletBalanceResponse.AccountBalanceEntry + 106, // 69: lnrpc.ChannelBalanceResponse.local_balance:type_name -> lnrpc.Amount + 106, // 70: lnrpc.ChannelBalanceResponse.remote_balance:type_name -> lnrpc.Amount + 106, // 71: lnrpc.ChannelBalanceResponse.unsettled_local_balance:type_name -> lnrpc.Amount + 106, // 72: lnrpc.ChannelBalanceResponse.unsettled_remote_balance:type_name -> lnrpc.Amount + 106, // 73: lnrpc.ChannelBalanceResponse.pending_open_local_balance:type_name -> lnrpc.Amount + 106, // 74: lnrpc.ChannelBalanceResponse.pending_open_remote_balance:type_name -> lnrpc.Amount + 29, // 75: lnrpc.QueryRoutesRequest.fee_limit:type_name -> lnrpc.FeeLimit + 111, // 76: lnrpc.QueryRoutesRequest.ignored_edges:type_name -> lnrpc.EdgeLocator + 110, // 77: lnrpc.QueryRoutesRequest.ignored_pairs:type_name -> lnrpc.NodePair + 218, // 78: lnrpc.QueryRoutesRequest.dest_custom_records:type_name -> lnrpc.QueryRoutesRequest.DestCustomRecordsEntry + 140, // 79: lnrpc.QueryRoutesRequest.route_hints:type_name -> lnrpc.RouteHint + 9, // 80: lnrpc.QueryRoutesRequest.dest_features:type_name -> lnrpc.FeatureBit + 116, // 81: lnrpc.QueryRoutesResponse.routes:type_name -> lnrpc.Route + 114, // 82: lnrpc.Hop.mpp_record:type_name -> lnrpc.MPPRecord + 115, // 83: lnrpc.Hop.amp_record:type_name -> lnrpc.AMPRecord + 219, // 84: lnrpc.Hop.custom_records:type_name -> lnrpc.Hop.CustomRecordsEntry + 113, // 85: lnrpc.Route.hops:type_name -> lnrpc.Hop + 119, // 86: lnrpc.NodeInfo.node:type_name -> lnrpc.LightningNode + 122, // 87: lnrpc.NodeInfo.channels:type_name -> lnrpc.ChannelEdge + 120, // 88: lnrpc.LightningNode.addresses:type_name -> lnrpc.NodeAddress + 220, // 89: lnrpc.LightningNode.features:type_name -> lnrpc.LightningNode.FeaturesEntry + 121, // 90: lnrpc.ChannelEdge.node1_policy:type_name -> lnrpc.RoutingPolicy + 121, // 91: lnrpc.ChannelEdge.node2_policy:type_name -> lnrpc.RoutingPolicy + 119, // 92: lnrpc.ChannelGraph.nodes:type_name -> lnrpc.LightningNode + 122, // 93: lnrpc.ChannelGraph.edges:type_name -> lnrpc.ChannelEdge + 6, // 94: lnrpc.NodeMetricsRequest.types:type_name -> lnrpc.NodeMetricType + 221, // 95: lnrpc.NodeMetricsResponse.betweenness_centrality:type_name -> lnrpc.NodeMetricsResponse.BetweennessCentralityEntry + 135, // 96: lnrpc.GraphTopologyUpdate.node_updates:type_name -> lnrpc.NodeUpdate + 136, // 97: lnrpc.GraphTopologyUpdate.channel_updates:type_name -> lnrpc.ChannelEdgeUpdate + 137, // 98: lnrpc.GraphTopologyUpdate.closed_chans:type_name -> lnrpc.ClosedChannelUpdate + 120, // 99: lnrpc.NodeUpdate.node_addresses:type_name -> lnrpc.NodeAddress + 222, // 100: lnrpc.NodeUpdate.features:type_name -> lnrpc.NodeUpdate.FeaturesEntry + 35, // 101: lnrpc.ChannelEdgeUpdate.chan_point:type_name -> lnrpc.ChannelPoint + 121, // 102: lnrpc.ChannelEdgeUpdate.routing_policy:type_name -> lnrpc.RoutingPolicy + 35, // 103: lnrpc.ClosedChannelUpdate.chan_point:type_name -> lnrpc.ChannelPoint + 138, // 104: lnrpc.RouteHint.hop_hints:type_name -> lnrpc.HopHint + 7, // 105: lnrpc.AMPInvoiceState.state:type_name -> lnrpc.InvoiceHTLCState + 140, // 106: lnrpc.Invoice.route_hints:type_name -> lnrpc.RouteHint + 16, // 107: lnrpc.Invoice.state:type_name -> lnrpc.Invoice.InvoiceState + 143, // 108: lnrpc.Invoice.htlcs:type_name -> lnrpc.InvoiceHTLC + 223, // 109: lnrpc.Invoice.features:type_name -> lnrpc.Invoice.FeaturesEntry + 224, // 110: lnrpc.Invoice.amp_invoice_state:type_name -> lnrpc.Invoice.AmpInvoiceStateEntry + 7, // 111: lnrpc.InvoiceHTLC.state:type_name -> lnrpc.InvoiceHTLCState + 225, // 112: lnrpc.InvoiceHTLC.custom_records:type_name -> lnrpc.InvoiceHTLC.CustomRecordsEntry + 144, // 113: lnrpc.InvoiceHTLC.amp:type_name -> lnrpc.AMP + 142, // 114: lnrpc.ListInvoiceResponse.invoices:type_name -> lnrpc.Invoice + 17, // 115: lnrpc.Payment.status:type_name -> lnrpc.Payment.PaymentStatus + 151, // 116: lnrpc.Payment.htlcs:type_name -> lnrpc.HTLCAttempt + 8, // 117: lnrpc.Payment.failure_reason:type_name -> lnrpc.PaymentFailureReason + 18, // 118: lnrpc.HTLCAttempt.status:type_name -> lnrpc.HTLCAttempt.HTLCStatus + 116, // 119: lnrpc.HTLCAttempt.route:type_name -> lnrpc.Route + 194, // 120: lnrpc.HTLCAttempt.failure:type_name -> lnrpc.Failure + 150, // 121: lnrpc.ListPaymentsResponse.payments:type_name -> lnrpc.Payment + 35, // 122: lnrpc.AbandonChannelRequest.channel_point:type_name -> lnrpc.ChannelPoint + 140, // 123: lnrpc.PayReq.route_hints:type_name -> lnrpc.RouteHint + 226, // 124: lnrpc.PayReq.features:type_name -> lnrpc.PayReq.FeaturesEntry + 166, // 125: lnrpc.FeeReportResponse.channel_fees:type_name -> lnrpc.ChannelFeeReport + 35, // 126: lnrpc.PolicyUpdateRequest.chan_point:type_name -> lnrpc.ChannelPoint + 36, // 127: lnrpc.FailedUpdate.outpoint:type_name -> lnrpc.OutPoint + 10, // 128: lnrpc.FailedUpdate.reason:type_name -> lnrpc.UpdateFailure + 169, // 129: lnrpc.PolicyUpdateResponse.failed_updates:type_name -> lnrpc.FailedUpdate + 172, // 130: lnrpc.ForwardingHistoryResponse.forwarding_events:type_name -> lnrpc.ForwardingEvent + 35, // 131: lnrpc.ExportChannelBackupRequest.chan_point:type_name -> lnrpc.ChannelPoint + 35, // 132: lnrpc.ChannelBackup.chan_point:type_name -> lnrpc.ChannelPoint + 35, // 133: lnrpc.MultiChanBackup.chan_points:type_name -> lnrpc.ChannelPoint + 179, // 134: lnrpc.ChanBackupSnapshot.single_chan_backups:type_name -> lnrpc.ChannelBackups + 176, // 135: lnrpc.ChanBackupSnapshot.multi_chan_backup:type_name -> lnrpc.MultiChanBackup + 175, // 136: lnrpc.ChannelBackups.chan_backups:type_name -> lnrpc.ChannelBackup + 179, // 137: lnrpc.RestoreChanBackupRequest.chan_backups:type_name -> lnrpc.ChannelBackups + 184, // 138: lnrpc.BakeMacaroonRequest.permissions:type_name -> lnrpc.MacaroonPermission + 184, // 139: lnrpc.MacaroonPermissionList.permissions:type_name -> lnrpc.MacaroonPermission + 227, // 140: lnrpc.ListPermissionsResponse.method_permissions:type_name -> lnrpc.ListPermissionsResponse.MethodPermissionsEntry + 19, // 141: lnrpc.Failure.code:type_name -> lnrpc.Failure.FailureCode + 195, // 142: lnrpc.Failure.channel_update:type_name -> lnrpc.ChannelUpdate + 197, // 143: lnrpc.MacaroonId.ops:type_name -> lnrpc.Op + 184, // 144: lnrpc.CheckMacPermRequest.permissions:type_name -> lnrpc.MacaroonPermission + 201, // 145: lnrpc.RPCMiddlewareRequest.stream_auth:type_name -> lnrpc.StreamAuth + 202, // 146: lnrpc.RPCMiddlewareRequest.request:type_name -> lnrpc.RPCMessage + 202, // 147: lnrpc.RPCMiddlewareRequest.response:type_name -> lnrpc.RPCMessage + 204, // 148: lnrpc.RPCMiddlewareResponse.register:type_name -> lnrpc.MiddlewareRegistration + 205, // 149: lnrpc.RPCMiddlewareResponse.feedback:type_name -> lnrpc.InterceptFeedback + 164, // 150: lnrpc.Peer.FeaturesEntry.value:type_name -> lnrpc.Feature + 164, // 151: lnrpc.GetInfoResponse.FeaturesEntry.value:type_name -> lnrpc.Feature + 3, // 152: lnrpc.PendingChannelsResponse.PendingChannel.initiator:type_name -> lnrpc.Initiator + 2, // 153: lnrpc.PendingChannelsResponse.PendingChannel.commitment_type:type_name -> lnrpc.CommitmentType + 211, // 154: lnrpc.PendingChannelsResponse.PendingOpenChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel + 211, // 155: lnrpc.PendingChannelsResponse.WaitingCloseChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel + 214, // 156: lnrpc.PendingChannelsResponse.WaitingCloseChannel.commitments:type_name -> lnrpc.PendingChannelsResponse.Commitments + 211, // 157: lnrpc.PendingChannelsResponse.ClosedChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel + 211, // 158: lnrpc.PendingChannelsResponse.ForceClosedChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel + 98, // 159: lnrpc.PendingChannelsResponse.ForceClosedChannel.pending_htlcs:type_name -> lnrpc.PendingHTLC + 14, // 160: lnrpc.PendingChannelsResponse.ForceClosedChannel.anchor:type_name -> lnrpc.PendingChannelsResponse.ForceClosedChannel.AnchorState + 103, // 161: lnrpc.WalletBalanceResponse.AccountBalanceEntry.value:type_name -> lnrpc.WalletAccountBalance + 164, // 162: lnrpc.LightningNode.FeaturesEntry.value:type_name -> lnrpc.Feature + 127, // 163: lnrpc.NodeMetricsResponse.BetweennessCentralityEntry.value:type_name -> lnrpc.FloatMetric + 164, // 164: lnrpc.NodeUpdate.FeaturesEntry.value:type_name -> lnrpc.Feature + 164, // 165: lnrpc.Invoice.FeaturesEntry.value:type_name -> lnrpc.Feature + 141, // 166: lnrpc.Invoice.AmpInvoiceStateEntry.value:type_name -> lnrpc.AMPInvoiceState + 164, // 167: lnrpc.PayReq.FeaturesEntry.value:type_name -> lnrpc.Feature + 191, // 168: lnrpc.ListPermissionsResponse.MethodPermissionsEntry.value:type_name -> lnrpc.MacaroonPermissionList + 104, // 169: lnrpc.Lightning.WalletBalance:input_type -> lnrpc.WalletBalanceRequest + 107, // 170: lnrpc.Lightning.ChannelBalance:input_type -> lnrpc.ChannelBalanceRequest + 27, // 171: lnrpc.Lightning.GetTransactions:input_type -> lnrpc.GetTransactionsRequest + 38, // 172: lnrpc.Lightning.EstimateFee:input_type -> lnrpc.EstimateFeeRequest + 42, // 173: lnrpc.Lightning.SendCoins:input_type -> lnrpc.SendCoinsRequest + 44, // 174: lnrpc.Lightning.ListUnspent:input_type -> lnrpc.ListUnspentRequest + 27, // 175: lnrpc.Lightning.SubscribeTransactions:input_type -> lnrpc.GetTransactionsRequest + 40, // 176: lnrpc.Lightning.SendMany:input_type -> lnrpc.SendManyRequest + 46, // 177: lnrpc.Lightning.NewAddress:input_type -> lnrpc.NewAddressRequest + 48, // 178: lnrpc.Lightning.SignMessage:input_type -> lnrpc.SignMessageRequest + 50, // 179: lnrpc.Lightning.VerifyMessage:input_type -> lnrpc.VerifyMessageRequest + 52, // 180: lnrpc.Lightning.ConnectPeer:input_type -> lnrpc.ConnectPeerRequest + 54, // 181: lnrpc.Lightning.DisconnectPeer:input_type -> lnrpc.DisconnectPeerRequest + 67, // 182: lnrpc.Lightning.ListPeers:input_type -> lnrpc.ListPeersRequest + 69, // 183: lnrpc.Lightning.SubscribePeerEvents:input_type -> lnrpc.PeerEventSubscription + 71, // 184: lnrpc.Lightning.GetInfo:input_type -> lnrpc.GetInfoRequest + 73, // 185: lnrpc.Lightning.GetRecoveryInfo:input_type -> lnrpc.GetRecoveryInfoRequest + 99, // 186: lnrpc.Lightning.PendingChannels:input_type -> lnrpc.PendingChannelsRequest + 59, // 187: lnrpc.Lightning.ListChannels:input_type -> lnrpc.ListChannelsRequest + 101, // 188: lnrpc.Lightning.SubscribeChannelEvents:input_type -> lnrpc.ChannelEventSubscription + 63, // 189: lnrpc.Lightning.ClosedChannels:input_type -> lnrpc.ClosedChannelsRequest + 86, // 190: lnrpc.Lightning.OpenChannelSync:input_type -> lnrpc.OpenChannelRequest + 86, // 191: lnrpc.Lightning.OpenChannel:input_type -> lnrpc.OpenChannelRequest + 83, // 192: lnrpc.Lightning.BatchOpenChannel:input_type -> lnrpc.BatchOpenChannelRequest + 96, // 193: lnrpc.Lightning.FundingStateStep:input_type -> lnrpc.FundingTransitionMsg + 34, // 194: lnrpc.Lightning.ChannelAcceptor:input_type -> lnrpc.ChannelAcceptResponse + 79, // 195: lnrpc.Lightning.CloseChannel:input_type -> lnrpc.CloseChannelRequest + 158, // 196: lnrpc.Lightning.AbandonChannel:input_type -> lnrpc.AbandonChannelRequest + 30, // 197: lnrpc.Lightning.SendPayment:input_type -> lnrpc.SendRequest + 30, // 198: lnrpc.Lightning.SendPaymentSync:input_type -> lnrpc.SendRequest + 32, // 199: lnrpc.Lightning.SendToRoute:input_type -> lnrpc.SendToRouteRequest + 32, // 200: lnrpc.Lightning.SendToRouteSync:input_type -> lnrpc.SendToRouteRequest + 142, // 201: lnrpc.Lightning.AddInvoice:input_type -> lnrpc.Invoice + 147, // 202: lnrpc.Lightning.ListInvoices:input_type -> lnrpc.ListInvoiceRequest + 146, // 203: lnrpc.Lightning.LookupInvoice:input_type -> lnrpc.PaymentHash + 149, // 204: lnrpc.Lightning.SubscribeInvoices:input_type -> lnrpc.InvoiceSubscription + 162, // 205: lnrpc.Lightning.DecodePayReq:input_type -> lnrpc.PayReqString + 152, // 206: lnrpc.Lightning.ListPayments:input_type -> lnrpc.ListPaymentsRequest + 154, // 207: lnrpc.Lightning.DeletePayment:input_type -> lnrpc.DeletePaymentRequest + 155, // 208: lnrpc.Lightning.DeleteAllPayments:input_type -> lnrpc.DeleteAllPaymentsRequest + 123, // 209: lnrpc.Lightning.DescribeGraph:input_type -> lnrpc.ChannelGraphRequest + 125, // 210: lnrpc.Lightning.GetNodeMetrics:input_type -> lnrpc.NodeMetricsRequest + 128, // 211: lnrpc.Lightning.GetChanInfo:input_type -> lnrpc.ChanInfoRequest + 117, // 212: lnrpc.Lightning.GetNodeInfo:input_type -> lnrpc.NodeInfoRequest + 109, // 213: lnrpc.Lightning.QueryRoutes:input_type -> lnrpc.QueryRoutesRequest + 129, // 214: lnrpc.Lightning.GetNetworkInfo:input_type -> lnrpc.NetworkInfoRequest + 131, // 215: lnrpc.Lightning.StopDaemon:input_type -> lnrpc.StopRequest + 133, // 216: lnrpc.Lightning.SubscribeChannelGraph:input_type -> lnrpc.GraphTopologySubscription + 160, // 217: lnrpc.Lightning.DebugLevel:input_type -> lnrpc.DebugLevelRequest + 165, // 218: lnrpc.Lightning.FeeReport:input_type -> lnrpc.FeeReportRequest + 168, // 219: lnrpc.Lightning.UpdateChannelPolicy:input_type -> lnrpc.PolicyUpdateRequest + 171, // 220: lnrpc.Lightning.ForwardingHistory:input_type -> lnrpc.ForwardingHistoryRequest + 174, // 221: lnrpc.Lightning.ExportChannelBackup:input_type -> lnrpc.ExportChannelBackupRequest + 177, // 222: lnrpc.Lightning.ExportAllChannelBackups:input_type -> lnrpc.ChanBackupExportRequest + 178, // 223: lnrpc.Lightning.VerifyChanBackup:input_type -> lnrpc.ChanBackupSnapshot + 180, // 224: lnrpc.Lightning.RestoreChannelBackups:input_type -> lnrpc.RestoreChanBackupRequest + 182, // 225: lnrpc.Lightning.SubscribeChannelBackups:input_type -> lnrpc.ChannelBackupSubscription + 185, // 226: lnrpc.Lightning.BakeMacaroon:input_type -> lnrpc.BakeMacaroonRequest + 187, // 227: lnrpc.Lightning.ListMacaroonIDs:input_type -> lnrpc.ListMacaroonIDsRequest + 189, // 228: lnrpc.Lightning.DeleteMacaroonID:input_type -> lnrpc.DeleteMacaroonIDRequest + 192, // 229: lnrpc.Lightning.ListPermissions:input_type -> lnrpc.ListPermissionsRequest + 198, // 230: lnrpc.Lightning.CheckMacaroonPermissions:input_type -> lnrpc.CheckMacPermRequest + 203, // 231: lnrpc.Lightning.RegisterRPCMiddleware:input_type -> lnrpc.RPCMiddlewareResponse + 22, // 232: lnrpc.Lightning.SendCustomMessage:input_type -> lnrpc.SendCustomMessageRequest + 20, // 233: lnrpc.Lightning.SubscribeCustomMessages:input_type -> lnrpc.SubscribeCustomMessagesRequest + 105, // 234: lnrpc.Lightning.WalletBalance:output_type -> lnrpc.WalletBalanceResponse + 108, // 235: lnrpc.Lightning.ChannelBalance:output_type -> lnrpc.ChannelBalanceResponse + 28, // 236: lnrpc.Lightning.GetTransactions:output_type -> lnrpc.TransactionDetails + 39, // 237: lnrpc.Lightning.EstimateFee:output_type -> lnrpc.EstimateFeeResponse + 43, // 238: lnrpc.Lightning.SendCoins:output_type -> lnrpc.SendCoinsResponse + 45, // 239: lnrpc.Lightning.ListUnspent:output_type -> lnrpc.ListUnspentResponse + 26, // 240: lnrpc.Lightning.SubscribeTransactions:output_type -> lnrpc.Transaction + 41, // 241: lnrpc.Lightning.SendMany:output_type -> lnrpc.SendManyResponse + 47, // 242: lnrpc.Lightning.NewAddress:output_type -> lnrpc.NewAddressResponse + 49, // 243: lnrpc.Lightning.SignMessage:output_type -> lnrpc.SignMessageResponse + 51, // 244: lnrpc.Lightning.VerifyMessage:output_type -> lnrpc.VerifyMessageResponse + 53, // 245: lnrpc.Lightning.ConnectPeer:output_type -> lnrpc.ConnectPeerResponse + 55, // 246: lnrpc.Lightning.DisconnectPeer:output_type -> lnrpc.DisconnectPeerResponse + 68, // 247: lnrpc.Lightning.ListPeers:output_type -> lnrpc.ListPeersResponse + 70, // 248: lnrpc.Lightning.SubscribePeerEvents:output_type -> lnrpc.PeerEvent + 72, // 249: lnrpc.Lightning.GetInfo:output_type -> lnrpc.GetInfoResponse + 74, // 250: lnrpc.Lightning.GetRecoveryInfo:output_type -> lnrpc.GetRecoveryInfoResponse + 100, // 251: lnrpc.Lightning.PendingChannels:output_type -> lnrpc.PendingChannelsResponse + 60, // 252: lnrpc.Lightning.ListChannels:output_type -> lnrpc.ListChannelsResponse + 102, // 253: lnrpc.Lightning.SubscribeChannelEvents:output_type -> lnrpc.ChannelEventUpdate + 64, // 254: lnrpc.Lightning.ClosedChannels:output_type -> lnrpc.ClosedChannelsResponse + 35, // 255: lnrpc.Lightning.OpenChannelSync:output_type -> lnrpc.ChannelPoint + 87, // 256: lnrpc.Lightning.OpenChannel:output_type -> lnrpc.OpenStatusUpdate + 85, // 257: lnrpc.Lightning.BatchOpenChannel:output_type -> lnrpc.BatchOpenChannelResponse + 97, // 258: lnrpc.Lightning.FundingStateStep:output_type -> lnrpc.FundingStateStepResp + 33, // 259: lnrpc.Lightning.ChannelAcceptor:output_type -> lnrpc.ChannelAcceptRequest + 80, // 260: lnrpc.Lightning.CloseChannel:output_type -> lnrpc.CloseStatusUpdate + 159, // 261: lnrpc.Lightning.AbandonChannel:output_type -> lnrpc.AbandonChannelResponse + 31, // 262: lnrpc.Lightning.SendPayment:output_type -> lnrpc.SendResponse + 31, // 263: lnrpc.Lightning.SendPaymentSync:output_type -> lnrpc.SendResponse + 31, // 264: lnrpc.Lightning.SendToRoute:output_type -> lnrpc.SendResponse + 31, // 265: lnrpc.Lightning.SendToRouteSync:output_type -> lnrpc.SendResponse + 145, // 266: lnrpc.Lightning.AddInvoice:output_type -> lnrpc.AddInvoiceResponse + 148, // 267: lnrpc.Lightning.ListInvoices:output_type -> lnrpc.ListInvoiceResponse + 142, // 268: lnrpc.Lightning.LookupInvoice:output_type -> lnrpc.Invoice + 142, // 269: lnrpc.Lightning.SubscribeInvoices:output_type -> lnrpc.Invoice + 163, // 270: lnrpc.Lightning.DecodePayReq:output_type -> lnrpc.PayReq + 153, // 271: lnrpc.Lightning.ListPayments:output_type -> lnrpc.ListPaymentsResponse + 156, // 272: lnrpc.Lightning.DeletePayment:output_type -> lnrpc.DeletePaymentResponse + 157, // 273: lnrpc.Lightning.DeleteAllPayments:output_type -> lnrpc.DeleteAllPaymentsResponse + 124, // 274: lnrpc.Lightning.DescribeGraph:output_type -> lnrpc.ChannelGraph + 126, // 275: lnrpc.Lightning.GetNodeMetrics:output_type -> lnrpc.NodeMetricsResponse + 122, // 276: lnrpc.Lightning.GetChanInfo:output_type -> lnrpc.ChannelEdge + 118, // 277: lnrpc.Lightning.GetNodeInfo:output_type -> lnrpc.NodeInfo + 112, // 278: lnrpc.Lightning.QueryRoutes:output_type -> lnrpc.QueryRoutesResponse + 130, // 279: lnrpc.Lightning.GetNetworkInfo:output_type -> lnrpc.NetworkInfo + 132, // 280: lnrpc.Lightning.StopDaemon:output_type -> lnrpc.StopResponse + 134, // 281: lnrpc.Lightning.SubscribeChannelGraph:output_type -> lnrpc.GraphTopologyUpdate + 161, // 282: lnrpc.Lightning.DebugLevel:output_type -> lnrpc.DebugLevelResponse + 167, // 283: lnrpc.Lightning.FeeReport:output_type -> lnrpc.FeeReportResponse + 170, // 284: lnrpc.Lightning.UpdateChannelPolicy:output_type -> lnrpc.PolicyUpdateResponse + 173, // 285: lnrpc.Lightning.ForwardingHistory:output_type -> lnrpc.ForwardingHistoryResponse + 175, // 286: lnrpc.Lightning.ExportChannelBackup:output_type -> lnrpc.ChannelBackup + 178, // 287: lnrpc.Lightning.ExportAllChannelBackups:output_type -> lnrpc.ChanBackupSnapshot + 183, // 288: lnrpc.Lightning.VerifyChanBackup:output_type -> lnrpc.VerifyChanBackupResponse + 181, // 289: lnrpc.Lightning.RestoreChannelBackups:output_type -> lnrpc.RestoreBackupResponse + 178, // 290: lnrpc.Lightning.SubscribeChannelBackups:output_type -> lnrpc.ChanBackupSnapshot + 186, // 291: lnrpc.Lightning.BakeMacaroon:output_type -> lnrpc.BakeMacaroonResponse + 188, // 292: lnrpc.Lightning.ListMacaroonIDs:output_type -> lnrpc.ListMacaroonIDsResponse + 190, // 293: lnrpc.Lightning.DeleteMacaroonID:output_type -> lnrpc.DeleteMacaroonIDResponse + 193, // 294: lnrpc.Lightning.ListPermissions:output_type -> lnrpc.ListPermissionsResponse + 199, // 295: lnrpc.Lightning.CheckMacaroonPermissions:output_type -> lnrpc.CheckMacPermResponse + 200, // 296: lnrpc.Lightning.RegisterRPCMiddleware:output_type -> lnrpc.RPCMiddlewareRequest + 23, // 297: lnrpc.Lightning.SendCustomMessage:output_type -> lnrpc.SendCustomMessageResponse + 21, // 298: lnrpc.Lightning.SubscribeCustomMessages:output_type -> lnrpc.CustomMessage + 234, // [234:299] is the sub-list for method output_type + 169, // [169:234] is the sub-list for method input_type + 169, // [169:169] is the sub-list for extension type_name + 169, // [169:169] is the sub-list for extension extendee + 0, // [0:169] is the sub-list for field type_name } func init() { file_lightning_proto_init() } @@ -19702,7 +20064,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Transaction); i { + switch v := v.(*OutputDetail); i { case 0: return &v.state case 1: @@ -19714,7 +20076,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetTransactionsRequest); i { + switch v := v.(*Transaction); i { case 0: return &v.state case 1: @@ -19726,7 +20088,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TransactionDetails); i { + switch v := v.(*GetTransactionsRequest); i { case 0: return &v.state case 1: @@ -19738,7 +20100,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FeeLimit); i { + switch v := v.(*TransactionDetails); i { case 0: return &v.state case 1: @@ -19750,7 +20112,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SendRequest); i { + switch v := v.(*FeeLimit); i { case 0: return &v.state case 1: @@ -19762,7 +20124,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SendResponse); i { + switch v := v.(*SendRequest); i { case 0: return &v.state case 1: @@ -19774,7 +20136,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SendToRouteRequest); i { + switch v := v.(*SendResponse); i { case 0: return &v.state case 1: @@ -19786,7 +20148,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChannelAcceptRequest); i { + switch v := v.(*SendToRouteRequest); i { case 0: return &v.state case 1: @@ -19798,7 +20160,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChannelAcceptResponse); i { + switch v := v.(*ChannelAcceptRequest); i { case 0: return &v.state case 1: @@ -19810,7 +20172,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChannelPoint); i { + switch v := v.(*ChannelAcceptResponse); i { case 0: return &v.state case 1: @@ -19822,7 +20184,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*OutPoint); i { + switch v := v.(*ChannelPoint); i { case 0: return &v.state case 1: @@ -19834,7 +20196,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*LightningAddress); i { + switch v := v.(*OutPoint); i { case 0: return &v.state case 1: @@ -19846,7 +20208,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*EstimateFeeRequest); i { + switch v := v.(*LightningAddress); i { case 0: return &v.state case 1: @@ -19858,7 +20220,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*EstimateFeeResponse); i { + switch v := v.(*EstimateFeeRequest); i { case 0: return &v.state case 1: @@ -19870,7 +20232,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SendManyRequest); i { + switch v := v.(*EstimateFeeResponse); i { case 0: return &v.state case 1: @@ -19882,7 +20244,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SendManyResponse); i { + switch v := v.(*SendManyRequest); i { case 0: return &v.state case 1: @@ -19894,7 +20256,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SendCoinsRequest); i { + switch v := v.(*SendManyResponse); i { case 0: return &v.state case 1: @@ -19906,7 +20268,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SendCoinsResponse); i { + switch v := v.(*SendCoinsRequest); i { case 0: return &v.state case 1: @@ -19918,7 +20280,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListUnspentRequest); i { + switch v := v.(*SendCoinsResponse); i { case 0: return &v.state case 1: @@ -19930,7 +20292,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListUnspentResponse); i { + switch v := v.(*ListUnspentRequest); i { case 0: return &v.state case 1: @@ -19942,7 +20304,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NewAddressRequest); i { + switch v := v.(*ListUnspentResponse); i { case 0: return &v.state case 1: @@ -19954,7 +20316,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NewAddressResponse); i { + switch v := v.(*NewAddressRequest); i { case 0: return &v.state case 1: @@ -19966,7 +20328,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SignMessageRequest); i { + switch v := v.(*NewAddressResponse); i { case 0: return &v.state case 1: @@ -19978,7 +20340,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SignMessageResponse); i { + switch v := v.(*SignMessageRequest); i { case 0: return &v.state case 1: @@ -19990,7 +20352,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*VerifyMessageRequest); i { + switch v := v.(*SignMessageResponse); i { case 0: return &v.state case 1: @@ -20002,7 +20364,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*VerifyMessageResponse); i { + switch v := v.(*VerifyMessageRequest); i { case 0: return &v.state case 1: @@ -20014,7 +20376,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ConnectPeerRequest); i { + switch v := v.(*VerifyMessageResponse); i { case 0: return &v.state case 1: @@ -20026,7 +20388,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ConnectPeerResponse); i { + switch v := v.(*ConnectPeerRequest); i { case 0: return &v.state case 1: @@ -20038,7 +20400,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DisconnectPeerRequest); i { + switch v := v.(*ConnectPeerResponse); i { case 0: return &v.state case 1: @@ -20050,7 +20412,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[34].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DisconnectPeerResponse); i { + switch v := v.(*DisconnectPeerRequest); i { case 0: return &v.state case 1: @@ -20062,7 +20424,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[35].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*HTLC); i { + switch v := v.(*DisconnectPeerResponse); i { case 0: return &v.state case 1: @@ -20074,7 +20436,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[36].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChannelConstraints); i { + switch v := v.(*HTLC); i { case 0: return &v.state case 1: @@ -20086,7 +20448,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[37].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Channel); i { + switch v := v.(*ChannelConstraints); i { case 0: return &v.state case 1: @@ -20098,7 +20460,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[38].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListChannelsRequest); i { + switch v := v.(*Channel); i { case 0: return &v.state case 1: @@ -20110,7 +20472,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[39].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListChannelsResponse); i { + switch v := v.(*ListChannelsRequest); i { case 0: return &v.state case 1: @@ -20122,7 +20484,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[40].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChannelCloseSummary); i { + switch v := v.(*ListChannelsResponse); i { case 0: return &v.state case 1: @@ -20134,7 +20496,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[41].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Resolution); i { + switch v := v.(*ChannelCloseSummary); i { case 0: return &v.state case 1: @@ -20146,7 +20508,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[42].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ClosedChannelsRequest); i { + switch v := v.(*Resolution); i { case 0: return &v.state case 1: @@ -20158,7 +20520,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[43].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ClosedChannelsResponse); i { + switch v := v.(*ClosedChannelsRequest); i { case 0: return &v.state case 1: @@ -20170,7 +20532,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[44].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Peer); i { + switch v := v.(*ClosedChannelsResponse); i { case 0: return &v.state case 1: @@ -20182,7 +20544,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[45].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TimestampedError); i { + switch v := v.(*Peer); i { case 0: return &v.state case 1: @@ -20194,7 +20556,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[46].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListPeersRequest); i { + switch v := v.(*TimestampedError); i { case 0: return &v.state case 1: @@ -20206,7 +20568,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[47].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListPeersResponse); i { + switch v := v.(*ListPeersRequest); i { case 0: return &v.state case 1: @@ -20218,7 +20580,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[48].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PeerEventSubscription); i { + switch v := v.(*ListPeersResponse); i { case 0: return &v.state case 1: @@ -20230,7 +20592,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[49].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PeerEvent); i { + switch v := v.(*PeerEventSubscription); i { case 0: return &v.state case 1: @@ -20242,7 +20604,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[50].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetInfoRequest); i { + switch v := v.(*PeerEvent); i { case 0: return &v.state case 1: @@ -20254,7 +20616,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[51].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetInfoResponse); i { + switch v := v.(*GetInfoRequest); i { case 0: return &v.state case 1: @@ -20266,7 +20628,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[52].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetRecoveryInfoRequest); i { + switch v := v.(*GetInfoResponse); i { case 0: return &v.state case 1: @@ -20278,7 +20640,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[53].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetRecoveryInfoResponse); i { + switch v := v.(*GetRecoveryInfoRequest); i { case 0: return &v.state case 1: @@ -20290,7 +20652,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[54].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Chain); i { + switch v := v.(*GetRecoveryInfoResponse); i { case 0: return &v.state case 1: @@ -20302,7 +20664,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[55].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ConfirmationUpdate); i { + switch v := v.(*Chain); i { case 0: return &v.state case 1: @@ -20314,7 +20676,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[56].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChannelOpenUpdate); i { + switch v := v.(*ConfirmationUpdate); i { case 0: return &v.state case 1: @@ -20326,7 +20688,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[57].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChannelCloseUpdate); i { + switch v := v.(*ChannelOpenUpdate); i { case 0: return &v.state case 1: @@ -20338,7 +20700,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[58].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CloseChannelRequest); i { + switch v := v.(*ChannelCloseUpdate); i { case 0: return &v.state case 1: @@ -20350,7 +20712,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[59].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CloseStatusUpdate); i { + switch v := v.(*CloseChannelRequest); i { case 0: return &v.state case 1: @@ -20362,7 +20724,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[60].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PendingUpdate); i { + switch v := v.(*CloseStatusUpdate); i { case 0: return &v.state case 1: @@ -20374,7 +20736,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[61].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ReadyForPsbtFunding); i { + switch v := v.(*PendingUpdate); i { case 0: return &v.state case 1: @@ -20386,7 +20748,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[62].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BatchOpenChannelRequest); i { + switch v := v.(*ReadyForPsbtFunding); i { case 0: return &v.state case 1: @@ -20398,7 +20760,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[63].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BatchOpenChannel); i { + switch v := v.(*BatchOpenChannelRequest); i { case 0: return &v.state case 1: @@ -20410,7 +20772,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[64].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BatchOpenChannelResponse); i { + switch v := v.(*BatchOpenChannel); i { case 0: return &v.state case 1: @@ -20422,7 +20784,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[65].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*OpenChannelRequest); i { + switch v := v.(*BatchOpenChannelResponse); i { case 0: return &v.state case 1: @@ -20434,7 +20796,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[66].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*OpenStatusUpdate); i { + switch v := v.(*OpenChannelRequest); i { case 0: return &v.state case 1: @@ -20446,7 +20808,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[67].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*KeyLocator); i { + switch v := v.(*OpenStatusUpdate); i { case 0: return &v.state case 1: @@ -20458,7 +20820,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[68].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*KeyDescriptor); i { + switch v := v.(*KeyLocator); i { case 0: return &v.state case 1: @@ -20470,7 +20832,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[69].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChanPointShim); i { + switch v := v.(*KeyDescriptor); i { case 0: return &v.state case 1: @@ -20482,7 +20844,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[70].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PsbtShim); i { + switch v := v.(*ChanPointShim); i { case 0: return &v.state case 1: @@ -20494,7 +20856,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[71].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FundingShim); i { + switch v := v.(*PsbtShim); i { case 0: return &v.state case 1: @@ -20506,7 +20868,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[72].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FundingShimCancel); i { + switch v := v.(*FundingShim); i { case 0: return &v.state case 1: @@ -20518,7 +20880,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[73].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FundingPsbtVerify); i { + switch v := v.(*FundingShimCancel); i { case 0: return &v.state case 1: @@ -20530,7 +20892,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[74].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FundingPsbtFinalize); i { + switch v := v.(*FundingPsbtVerify); i { case 0: return &v.state case 1: @@ -20542,7 +20904,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[75].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FundingTransitionMsg); i { + switch v := v.(*FundingPsbtFinalize); i { case 0: return &v.state case 1: @@ -20554,7 +20916,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[76].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FundingStateStepResp); i { + switch v := v.(*FundingTransitionMsg); i { case 0: return &v.state case 1: @@ -20566,7 +20928,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[77].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PendingHTLC); i { + switch v := v.(*FundingStateStepResp); i { case 0: return &v.state case 1: @@ -20578,7 +20940,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[78].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PendingChannelsRequest); i { + switch v := v.(*PendingHTLC); i { case 0: return &v.state case 1: @@ -20590,7 +20952,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[79].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PendingChannelsResponse); i { + switch v := v.(*PendingChannelsRequest); i { case 0: return &v.state case 1: @@ -20602,7 +20964,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[80].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChannelEventSubscription); i { + switch v := v.(*PendingChannelsResponse); i { case 0: return &v.state case 1: @@ -20614,7 +20976,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[81].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChannelEventUpdate); i { + switch v := v.(*ChannelEventSubscription); i { case 0: return &v.state case 1: @@ -20626,7 +20988,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[82].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WalletAccountBalance); i { + switch v := v.(*ChannelEventUpdate); i { case 0: return &v.state case 1: @@ -20638,7 +21000,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[83].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WalletBalanceRequest); i { + switch v := v.(*WalletAccountBalance); i { case 0: return &v.state case 1: @@ -20650,7 +21012,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[84].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WalletBalanceResponse); i { + switch v := v.(*WalletBalanceRequest); i { case 0: return &v.state case 1: @@ -20662,7 +21024,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[85].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Amount); i { + switch v := v.(*WalletBalanceResponse); i { case 0: return &v.state case 1: @@ -20674,7 +21036,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[86].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChannelBalanceRequest); i { + switch v := v.(*Amount); i { case 0: return &v.state case 1: @@ -20686,7 +21048,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[87].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChannelBalanceResponse); i { + switch v := v.(*ChannelBalanceRequest); i { case 0: return &v.state case 1: @@ -20698,7 +21060,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[88].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*QueryRoutesRequest); i { + switch v := v.(*ChannelBalanceResponse); i { case 0: return &v.state case 1: @@ -20710,7 +21072,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[89].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NodePair); i { + switch v := v.(*QueryRoutesRequest); i { case 0: return &v.state case 1: @@ -20722,7 +21084,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[90].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*EdgeLocator); i { + switch v := v.(*NodePair); i { case 0: return &v.state case 1: @@ -20734,7 +21096,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[91].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*QueryRoutesResponse); i { + switch v := v.(*EdgeLocator); i { case 0: return &v.state case 1: @@ -20746,7 +21108,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[92].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Hop); i { + switch v := v.(*QueryRoutesResponse); i { case 0: return &v.state case 1: @@ -20758,7 +21120,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[93].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*MPPRecord); i { + switch v := v.(*Hop); i { case 0: return &v.state case 1: @@ -20770,7 +21132,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[94].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AMPRecord); i { + switch v := v.(*MPPRecord); i { case 0: return &v.state case 1: @@ -20782,7 +21144,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[95].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Route); i { + switch v := v.(*AMPRecord); i { case 0: return &v.state case 1: @@ -20794,7 +21156,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[96].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NodeInfoRequest); i { + switch v := v.(*Route); i { case 0: return &v.state case 1: @@ -20806,7 +21168,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[97].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NodeInfo); i { + switch v := v.(*NodeInfoRequest); i { case 0: return &v.state case 1: @@ -20818,7 +21180,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[98].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*LightningNode); i { + switch v := v.(*NodeInfo); i { case 0: return &v.state case 1: @@ -20830,7 +21192,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[99].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NodeAddress); i { + switch v := v.(*LightningNode); i { case 0: return &v.state case 1: @@ -20842,7 +21204,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[100].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RoutingPolicy); i { + switch v := v.(*NodeAddress); i { case 0: return &v.state case 1: @@ -20854,7 +21216,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[101].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChannelEdge); i { + switch v := v.(*RoutingPolicy); i { case 0: return &v.state case 1: @@ -20866,7 +21228,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[102].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChannelGraphRequest); i { + switch v := v.(*ChannelEdge); i { case 0: return &v.state case 1: @@ -20878,7 +21240,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[103].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChannelGraph); i { + switch v := v.(*ChannelGraphRequest); i { case 0: return &v.state case 1: @@ -20890,7 +21252,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[104].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NodeMetricsRequest); i { + switch v := v.(*ChannelGraph); i { case 0: return &v.state case 1: @@ -20902,7 +21264,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[105].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NodeMetricsResponse); i { + switch v := v.(*NodeMetricsRequest); i { case 0: return &v.state case 1: @@ -20914,7 +21276,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[106].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FloatMetric); i { + switch v := v.(*NodeMetricsResponse); i { case 0: return &v.state case 1: @@ -20926,7 +21288,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[107].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChanInfoRequest); i { + switch v := v.(*FloatMetric); i { case 0: return &v.state case 1: @@ -20938,7 +21300,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[108].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NetworkInfoRequest); i { + switch v := v.(*ChanInfoRequest); i { case 0: return &v.state case 1: @@ -20950,7 +21312,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[109].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NetworkInfo); i { + switch v := v.(*NetworkInfoRequest); i { case 0: return &v.state case 1: @@ -20962,7 +21324,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[110].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StopRequest); i { + switch v := v.(*NetworkInfo); i { case 0: return &v.state case 1: @@ -20974,7 +21336,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[111].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StopResponse); i { + switch v := v.(*StopRequest); i { case 0: return &v.state case 1: @@ -20986,7 +21348,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[112].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GraphTopologySubscription); i { + switch v := v.(*StopResponse); i { case 0: return &v.state case 1: @@ -20998,7 +21360,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[113].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GraphTopologyUpdate); i { + switch v := v.(*GraphTopologySubscription); i { case 0: return &v.state case 1: @@ -21010,7 +21372,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[114].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NodeUpdate); i { + switch v := v.(*GraphTopologyUpdate); i { case 0: return &v.state case 1: @@ -21022,7 +21384,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[115].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChannelEdgeUpdate); i { + switch v := v.(*NodeUpdate); i { case 0: return &v.state case 1: @@ -21034,7 +21396,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[116].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ClosedChannelUpdate); i { + switch v := v.(*ChannelEdgeUpdate); i { case 0: return &v.state case 1: @@ -21046,7 +21408,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[117].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*HopHint); i { + switch v := v.(*ClosedChannelUpdate); i { case 0: return &v.state case 1: @@ -21058,7 +21420,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[118].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SetID); i { + switch v := v.(*HopHint); i { case 0: return &v.state case 1: @@ -21070,7 +21432,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[119].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RouteHint); i { + switch v := v.(*SetID); i { case 0: return &v.state case 1: @@ -21082,7 +21444,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[120].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AMPInvoiceState); i { + switch v := v.(*RouteHint); i { case 0: return &v.state case 1: @@ -21094,7 +21456,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[121].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Invoice); i { + switch v := v.(*AMPInvoiceState); i { case 0: return &v.state case 1: @@ -21106,7 +21468,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[122].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*InvoiceHTLC); i { + switch v := v.(*Invoice); i { case 0: return &v.state case 1: @@ -21118,7 +21480,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[123].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AMP); i { + switch v := v.(*InvoiceHTLC); i { case 0: return &v.state case 1: @@ -21130,7 +21492,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[124].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AddInvoiceResponse); i { + switch v := v.(*AMP); i { case 0: return &v.state case 1: @@ -21142,7 +21504,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[125].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PaymentHash); i { + switch v := v.(*AddInvoiceResponse); i { case 0: return &v.state case 1: @@ -21154,7 +21516,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[126].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListInvoiceRequest); i { + switch v := v.(*PaymentHash); i { case 0: return &v.state case 1: @@ -21166,7 +21528,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[127].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListInvoiceResponse); i { + switch v := v.(*ListInvoiceRequest); i { case 0: return &v.state case 1: @@ -21178,7 +21540,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[128].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*InvoiceSubscription); i { + switch v := v.(*ListInvoiceResponse); i { case 0: return &v.state case 1: @@ -21190,7 +21552,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[129].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Payment); i { + switch v := v.(*InvoiceSubscription); i { case 0: return &v.state case 1: @@ -21202,7 +21564,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[130].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*HTLCAttempt); i { + switch v := v.(*Payment); i { case 0: return &v.state case 1: @@ -21214,7 +21576,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[131].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListPaymentsRequest); i { + switch v := v.(*HTLCAttempt); i { case 0: return &v.state case 1: @@ -21226,7 +21588,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[132].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListPaymentsResponse); i { + switch v := v.(*ListPaymentsRequest); i { case 0: return &v.state case 1: @@ -21238,7 +21600,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[133].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DeletePaymentRequest); i { + switch v := v.(*ListPaymentsResponse); i { case 0: return &v.state case 1: @@ -21250,7 +21612,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[134].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DeleteAllPaymentsRequest); i { + switch v := v.(*DeletePaymentRequest); i { case 0: return &v.state case 1: @@ -21262,7 +21624,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[135].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DeletePaymentResponse); i { + switch v := v.(*DeleteAllPaymentsRequest); i { case 0: return &v.state case 1: @@ -21274,7 +21636,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[136].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DeleteAllPaymentsResponse); i { + switch v := v.(*DeletePaymentResponse); i { case 0: return &v.state case 1: @@ -21286,7 +21648,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[137].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AbandonChannelRequest); i { + switch v := v.(*DeleteAllPaymentsResponse); i { case 0: return &v.state case 1: @@ -21298,7 +21660,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[138].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AbandonChannelResponse); i { + switch v := v.(*AbandonChannelRequest); i { case 0: return &v.state case 1: @@ -21310,7 +21672,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[139].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DebugLevelRequest); i { + switch v := v.(*AbandonChannelResponse); i { case 0: return &v.state case 1: @@ -21322,7 +21684,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[140].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DebugLevelResponse); i { + switch v := v.(*DebugLevelRequest); i { case 0: return &v.state case 1: @@ -21334,7 +21696,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[141].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PayReqString); i { + switch v := v.(*DebugLevelResponse); i { case 0: return &v.state case 1: @@ -21346,7 +21708,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[142].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PayReq); i { + switch v := v.(*PayReqString); i { case 0: return &v.state case 1: @@ -21358,7 +21720,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[143].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Feature); i { + switch v := v.(*PayReq); i { case 0: return &v.state case 1: @@ -21370,7 +21732,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[144].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FeeReportRequest); i { + switch v := v.(*Feature); i { case 0: return &v.state case 1: @@ -21382,7 +21744,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[145].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChannelFeeReport); i { + switch v := v.(*FeeReportRequest); i { case 0: return &v.state case 1: @@ -21394,7 +21756,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[146].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FeeReportResponse); i { + switch v := v.(*ChannelFeeReport); i { case 0: return &v.state case 1: @@ -21406,7 +21768,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[147].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PolicyUpdateRequest); i { + switch v := v.(*FeeReportResponse); i { case 0: return &v.state case 1: @@ -21418,7 +21780,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[148].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FailedUpdate); i { + switch v := v.(*PolicyUpdateRequest); i { case 0: return &v.state case 1: @@ -21430,7 +21792,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[149].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PolicyUpdateResponse); i { + switch v := v.(*FailedUpdate); i { case 0: return &v.state case 1: @@ -21442,7 +21804,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[150].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ForwardingHistoryRequest); i { + switch v := v.(*PolicyUpdateResponse); i { case 0: return &v.state case 1: @@ -21454,7 +21816,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[151].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ForwardingEvent); i { + switch v := v.(*ForwardingHistoryRequest); i { case 0: return &v.state case 1: @@ -21466,7 +21828,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[152].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ForwardingHistoryResponse); i { + switch v := v.(*ForwardingEvent); i { case 0: return &v.state case 1: @@ -21478,7 +21840,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[153].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ExportChannelBackupRequest); i { + switch v := v.(*ForwardingHistoryResponse); i { case 0: return &v.state case 1: @@ -21490,7 +21852,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[154].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChannelBackup); i { + switch v := v.(*ExportChannelBackupRequest); i { case 0: return &v.state case 1: @@ -21502,7 +21864,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[155].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*MultiChanBackup); i { + switch v := v.(*ChannelBackup); i { case 0: return &v.state case 1: @@ -21514,7 +21876,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[156].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChanBackupExportRequest); i { + switch v := v.(*MultiChanBackup); i { case 0: return &v.state case 1: @@ -21526,7 +21888,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[157].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChanBackupSnapshot); i { + switch v := v.(*ChanBackupExportRequest); i { case 0: return &v.state case 1: @@ -21538,7 +21900,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[158].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChannelBackups); i { + switch v := v.(*ChanBackupSnapshot); i { case 0: return &v.state case 1: @@ -21550,7 +21912,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[159].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RestoreChanBackupRequest); i { + switch v := v.(*ChannelBackups); i { case 0: return &v.state case 1: @@ -21562,7 +21924,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[160].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RestoreBackupResponse); i { + switch v := v.(*RestoreChanBackupRequest); i { case 0: return &v.state case 1: @@ -21574,7 +21936,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[161].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChannelBackupSubscription); i { + switch v := v.(*RestoreBackupResponse); i { case 0: return &v.state case 1: @@ -21586,7 +21948,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[162].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*VerifyChanBackupResponse); i { + switch v := v.(*ChannelBackupSubscription); i { case 0: return &v.state case 1: @@ -21598,7 +21960,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[163].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*MacaroonPermission); i { + switch v := v.(*VerifyChanBackupResponse); i { case 0: return &v.state case 1: @@ -21610,7 +21972,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[164].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BakeMacaroonRequest); i { + switch v := v.(*MacaroonPermission); i { case 0: return &v.state case 1: @@ -21622,7 +21984,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[165].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BakeMacaroonResponse); i { + switch v := v.(*BakeMacaroonRequest); i { case 0: return &v.state case 1: @@ -21634,7 +21996,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[166].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListMacaroonIDsRequest); i { + switch v := v.(*BakeMacaroonResponse); i { case 0: return &v.state case 1: @@ -21646,7 +22008,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[167].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListMacaroonIDsResponse); i { + switch v := v.(*ListMacaroonIDsRequest); i { case 0: return &v.state case 1: @@ -21658,7 +22020,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[168].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DeleteMacaroonIDRequest); i { + switch v := v.(*ListMacaroonIDsResponse); i { case 0: return &v.state case 1: @@ -21670,7 +22032,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[169].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DeleteMacaroonIDResponse); i { + switch v := v.(*DeleteMacaroonIDRequest); i { case 0: return &v.state case 1: @@ -21682,7 +22044,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[170].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*MacaroonPermissionList); i { + switch v := v.(*DeleteMacaroonIDResponse); i { case 0: return &v.state case 1: @@ -21694,7 +22056,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[171].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListPermissionsRequest); i { + switch v := v.(*MacaroonPermissionList); i { case 0: return &v.state case 1: @@ -21706,7 +22068,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[172].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListPermissionsResponse); i { + switch v := v.(*ListPermissionsRequest); i { case 0: return &v.state case 1: @@ -21718,7 +22080,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[173].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Failure); i { + switch v := v.(*ListPermissionsResponse); i { case 0: return &v.state case 1: @@ -21730,7 +22092,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[174].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChannelUpdate); i { + switch v := v.(*Failure); i { case 0: return &v.state case 1: @@ -21742,7 +22104,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[175].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*MacaroonId); i { + switch v := v.(*ChannelUpdate); i { case 0: return &v.state case 1: @@ -21754,7 +22116,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[176].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Op); i { + switch v := v.(*MacaroonId); i { case 0: return &v.state case 1: @@ -21766,7 +22128,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[177].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CheckMacPermRequest); i { + switch v := v.(*Op); i { case 0: return &v.state case 1: @@ -21778,7 +22140,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[178].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CheckMacPermResponse); i { + switch v := v.(*CheckMacPermRequest); i { case 0: return &v.state case 1: @@ -21790,7 +22152,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[179].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RPCMiddlewareRequest); i { + switch v := v.(*CheckMacPermResponse); i { case 0: return &v.state case 1: @@ -21802,7 +22164,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[180].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StreamAuth); i { + switch v := v.(*RPCMiddlewareRequest); i { case 0: return &v.state case 1: @@ -21814,7 +22176,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[181].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RPCMessage); i { + switch v := v.(*StreamAuth); i { case 0: return &v.state case 1: @@ -21826,7 +22188,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[182].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RPCMiddlewareResponse); i { + switch v := v.(*RPCMessage); i { case 0: return &v.state case 1: @@ -21838,7 +22200,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[183].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*MiddlewareRegistration); i { + switch v := v.(*RPCMiddlewareResponse); i { case 0: return &v.state case 1: @@ -21850,6 +22212,18 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[184].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MiddlewareRegistration); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[185].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*InterceptFeedback); i { case 0: return &v.state @@ -21861,7 +22235,7 @@ func file_lightning_proto_init() { return nil } } - file_lightning_proto_msgTypes[190].Exporter = func(v interface{}, i int) interface{} { + file_lightning_proto_msgTypes[191].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PendingChannelsResponse_PendingChannel); i { case 0: return &v.state @@ -21873,7 +22247,7 @@ func file_lightning_proto_init() { return nil } } - file_lightning_proto_msgTypes[191].Exporter = func(v interface{}, i int) interface{} { + file_lightning_proto_msgTypes[192].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PendingChannelsResponse_PendingOpenChannel); i { case 0: return &v.state @@ -21885,7 +22259,7 @@ func file_lightning_proto_init() { return nil } } - file_lightning_proto_msgTypes[192].Exporter = func(v interface{}, i int) interface{} { + file_lightning_proto_msgTypes[193].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PendingChannelsResponse_WaitingCloseChannel); i { case 0: return &v.state @@ -21897,7 +22271,7 @@ func file_lightning_proto_init() { return nil } } - file_lightning_proto_msgTypes[193].Exporter = func(v interface{}, i int) interface{} { + file_lightning_proto_msgTypes[194].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PendingChannelsResponse_Commitments); i { case 0: return &v.state @@ -21909,7 +22283,7 @@ func file_lightning_proto_init() { return nil } } - file_lightning_proto_msgTypes[194].Exporter = func(v interface{}, i int) interface{} { + file_lightning_proto_msgTypes[195].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PendingChannelsResponse_ClosedChannel); i { case 0: return &v.state @@ -21921,7 +22295,7 @@ func file_lightning_proto_init() { return nil } } - file_lightning_proto_msgTypes[195].Exporter = func(v interface{}, i int) interface{} { + file_lightning_proto_msgTypes[196].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PendingChannelsResponse_ForceClosedChannel); i { case 0: return &v.state @@ -21934,35 +22308,35 @@ func file_lightning_proto_init() { } } } - file_lightning_proto_msgTypes[8].OneofWrappers = []interface{}{ + file_lightning_proto_msgTypes[9].OneofWrappers = []interface{}{ (*FeeLimit_Fixed)(nil), (*FeeLimit_FixedMsat)(nil), (*FeeLimit_Percent)(nil), } - file_lightning_proto_msgTypes[14].OneofWrappers = []interface{}{ + file_lightning_proto_msgTypes[15].OneofWrappers = []interface{}{ (*ChannelPoint_FundingTxidBytes)(nil), (*ChannelPoint_FundingTxidStr)(nil), } - file_lightning_proto_msgTypes[59].OneofWrappers = []interface{}{ + file_lightning_proto_msgTypes[60].OneofWrappers = []interface{}{ (*CloseStatusUpdate_ClosePending)(nil), (*CloseStatusUpdate_ChanClose)(nil), } - file_lightning_proto_msgTypes[66].OneofWrappers = []interface{}{ + file_lightning_proto_msgTypes[67].OneofWrappers = []interface{}{ (*OpenStatusUpdate_ChanPending)(nil), (*OpenStatusUpdate_ChanOpen)(nil), (*OpenStatusUpdate_PsbtFund)(nil), } - file_lightning_proto_msgTypes[71].OneofWrappers = []interface{}{ + file_lightning_proto_msgTypes[72].OneofWrappers = []interface{}{ (*FundingShim_ChanPointShim)(nil), (*FundingShim_PsbtShim)(nil), } - file_lightning_proto_msgTypes[75].OneofWrappers = []interface{}{ + file_lightning_proto_msgTypes[76].OneofWrappers = []interface{}{ (*FundingTransitionMsg_ShimRegister)(nil), (*FundingTransitionMsg_ShimCancel)(nil), (*FundingTransitionMsg_PsbtVerify)(nil), (*FundingTransitionMsg_PsbtFinalize)(nil), } - file_lightning_proto_msgTypes[81].OneofWrappers = []interface{}{ + file_lightning_proto_msgTypes[82].OneofWrappers = []interface{}{ (*ChannelEventUpdate_OpenChannel)(nil), (*ChannelEventUpdate_ClosedChannel)(nil), (*ChannelEventUpdate_ActiveChannel)(nil), @@ -21970,20 +22344,20 @@ func file_lightning_proto_init() { (*ChannelEventUpdate_PendingOpenChannel)(nil), (*ChannelEventUpdate_FullyResolvedChannel)(nil), } - file_lightning_proto_msgTypes[147].OneofWrappers = []interface{}{ + file_lightning_proto_msgTypes[148].OneofWrappers = []interface{}{ (*PolicyUpdateRequest_Global)(nil), (*PolicyUpdateRequest_ChanPoint)(nil), } - file_lightning_proto_msgTypes[159].OneofWrappers = []interface{}{ + file_lightning_proto_msgTypes[160].OneofWrappers = []interface{}{ (*RestoreChanBackupRequest_ChanBackups)(nil), (*RestoreChanBackupRequest_MultiChanBackup)(nil), } - file_lightning_proto_msgTypes[179].OneofWrappers = []interface{}{ + file_lightning_proto_msgTypes[180].OneofWrappers = []interface{}{ (*RPCMiddlewareRequest_StreamAuth)(nil), (*RPCMiddlewareRequest_Request)(nil), (*RPCMiddlewareRequest_Response)(nil), } - file_lightning_proto_msgTypes[182].OneofWrappers = []interface{}{ + file_lightning_proto_msgTypes[183].OneofWrappers = []interface{}{ (*RPCMiddlewareResponse_Register)(nil), (*RPCMiddlewareResponse_Feedback)(nil), } @@ -21992,8 +22366,8 @@ func file_lightning_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_lightning_proto_rawDesc, - NumEnums: 19, - NumMessages: 207, + NumEnums: 20, + NumMessages: 208, NumExtensions: 0, NumServices: 1, }, diff --git a/lnrpc/lightning.proto b/lnrpc/lightning.proto index ddf39ec12f..beaa6683dc 100644 --- a/lnrpc/lightning.proto +++ b/lnrpc/lightning.proto @@ -620,6 +620,38 @@ message Utxo { int64 confirmations = 6; } +enum OutputScriptType { + SCRIPT_TYPE_PUBKEY_HASH = 0; + SCRIPT_TYPE_SCRIPT_HASH = 1; + SCRIPT_TYPE_WITNESS_V0_PUBKEY_HASH = 2; + SCRIPT_TYPE_WITNESS_V0_SCRIPT_HASH = 3; + SCRIPT_TYPE_PUBKEY = 4; + SCRIPT_TYPE_MULTISIG = 5; + SCRIPT_TYPE_NULLDATA = 6; + SCRIPT_TYPE_NON_STANDARD = 7; + SCRIPT_TYPE_WITNESS_UNKNOWN = 8; +} + +message OutputDetail { + // The type of the output + OutputScriptType output_type = 1; + + // The address + string address = 2; + + // The pkscript in hex + string pk_script = 3; + + // The output index used in the raw transaction + int64 output_index = 4; + + // The value of the output coin in satoshis + int64 amount = 5; + + // Denotes if the output is controlled by the internal wallet + bool is_our_address = 6; +} + message Transaction { // The transaction hash string tx_hash = 1; @@ -642,8 +674,12 @@ message Transaction { // Fees paid for this transaction int64 total_fees = 7; - // Addresses that received funds for this transaction - repeated string dest_addresses = 8; + // Addresses that received funds for this transaction. Deprecated as it is + // now incorporated in the output_details field. + repeated string dest_addresses = 8 [deprecated = true]; + + // Outputs that received funds for this transaction + repeated OutputDetail output_details = 11; // The raw transaction hex. string raw_tx_hex = 9; @@ -753,7 +789,8 @@ message SendRequest { The maximum number of satoshis that will be paid as a fee of the payment. This value can be represented either as a percentage of the amount being sent, or as a fixed amount of the maximum fee the user is willing the pay to - send the payment. + send the payment. If not specified, lnd will use a default value of 100% + fees for small amounts (<=1k sat) or 5% fees for larger amounts. */ FeeLimit fee_limit = 8; @@ -1101,12 +1138,15 @@ message ListUnspentResponse { - `p2wkh`: Pay to witness key hash (`WITNESS_PUBKEY_HASH` = 0) - `np2wkh`: Pay to nested witness key hash (`NESTED_PUBKEY_HASH` = 1) +- `p2tr`: Pay to taproot pubkey (`TAPROOT_PUBKEY` = 4) */ enum AddressType { WITNESS_PUBKEY_HASH = 0; NESTED_PUBKEY_HASH = 1; UNUSED_WITNESS_PUBKEY_HASH = 2; UNUSED_NESTED_PUBKEY_HASH = 3; + TAPROOT_PUBKEY = 4; + UNUSED_TAPROOT_PUBKEY = 5; } message NewAddressRequest { @@ -1769,6 +1809,11 @@ message GetInfoResponse { announcements and invoices. */ map features = 19; + + /* + Indicates whether the HTLC interceptor API is in always-on mode. + */ + bool require_htlc_interceptor = 21; } message GetRecoveryInfoRequest { @@ -2312,15 +2357,18 @@ message PendingChannelsResponse { // Total number of forwarding packages created in this channel. int64 num_forwarding_packages = 10; + + // A set of flags showing the current state of the channel. + string chan_status_flags = 11; + + // Whether this channel is advertised to the network or not. + bool private = 12; } message PendingOpenChannel { // The pending channel PendingChannel channel = 1; - // The height at which this channel will be confirmed - uint32 confirmation_height = 2; - /* The amount calculated to be paid in fees for the current set of commitment transactions. The fee amount is persisted with the channel @@ -2339,6 +2387,9 @@ message PendingChannelsResponse { transaction. This value can later be updated once the channel is open. */ int64 fee_per_kw = 6; + + // Previously used for confirmation_height. Do not reuse. + reserved 2; } message WaitingCloseChannel { @@ -2353,6 +2404,9 @@ message PendingChannelsResponse { this point. */ Commitments commitments = 3; + + // The transaction id of the closing transaction + string closing_txid = 4; } message Commitments { @@ -2492,6 +2546,10 @@ message WalletBalanceResponse { // The unconfirmed balance of a wallet(with 0 confirmations) int64 unconfirmed_balance = 3; + // The total amount of wallet UTXOs held in outputs that are locked for + // other usage. + int64 locked_balance = 5; + // A mapping of each wallet account's name to its balance. map account_balance = 4; } @@ -2565,7 +2623,8 @@ message QueryRoutesRequest { The maximum number of satoshis that will be paid as a fee of the payment. This value can be represented either as a percentage of the amount being sent, or as a fixed amount of the maximum fee the user is willing the pay to - send the payment. + send the payment. If not specified, lnd will use a default value of 100% + fees for small amounts (<=1k sat) or 5% fees for larger amounts. */ FeeLimit fee_limit = 5; @@ -2638,6 +2697,12 @@ message QueryRoutesRequest { fallback. */ repeated lnrpc.FeatureBit dest_features = 17; + + /* + The time preference for this payment. Set to -1 to optimize for fees + only, to 1 to optimize for reliability only or a value inbetween for a mix. + */ + double time_pref = 18; } message NodePair { @@ -2706,7 +2771,7 @@ message Hop { TLV format. Note that if any custom tlv_records below are specified, then this field MUST be set to true for them to be encoded properly. */ - bool tlv_payload = 9; + bool tlv_payload = 9 [deprecated = true]; /* An optional TLV record that signals the use of an MPP payment. If present, @@ -2732,6 +2797,9 @@ message Hop { to drop off at each hop within the onion. */ map custom_records = 11; + + // The payment metadata to send along with the payment to the payee. + bytes metadata = 13; } message MPPRecord { @@ -3565,6 +3633,14 @@ message ListPaymentsRequest { of the returned payments is always oldest first (ascending index order). */ bool reversed = 4; + + /* + If set, all payments (complete and incomplete, independent of the + max_payments parameter) will be counted. Note that setting this to true will + increase the run time of the call significantly on systems that have a lot + of payments, as all of them have to be iterated through to be counted. + */ + bool count_total_payments = 5; } message ListPaymentsResponse { @@ -3582,6 +3658,14 @@ message ListPaymentsResponse { as the index_offset to continue seeking forwards in the next request. */ uint64 last_index_offset = 3; + + /* + Will only be set if count_total_payments in the request was set. Represents + the total number of payments (complete and incomplete, independent of the + number of payments requested in the query) currently present in the payments + database. + */ + uint64 total_num_payments = 4; } message DeletePaymentRequest { @@ -3742,6 +3826,9 @@ message PolicyUpdateRequest { // goes up to 6 decimal places, so 1e-6. double fee_rate = 4; + // The effective fee rate in micro-satoshis (parts per million). + uint32 fee_rate_ppm = 9; + // The required timelock delta for HTLCs forwarded over the channel. uint32 time_lock_delta = 5; @@ -4175,8 +4262,10 @@ message CheckMacPermResponse { message RPCMiddlewareRequest { /* - The unique ID of the intercepted request. Useful for mapping request to - response when implementing full duplex message interception. + The unique ID of the intercepted original gRPC request. Useful for mapping + request to response when implementing full duplex message interception. For + streaming requests, this will be the same ID for all incoming and outgoing + middleware intercept messages of the _same_ stream. */ uint64 request_id = 1; @@ -4231,6 +4320,16 @@ message RPCMiddlewareRequest { */ RPCMessage response = 6; } + + /* + The unique message ID of this middleware intercept message. There can be + multiple middleware intercept messages per single gRPC request (one for the + incoming request and one for the outgoing response) or gRPC stream (one for + each incoming message and one for each outgoing response). This message ID + must be referenced when responding (accepting/rejecting/modifying) to an + intercept message. + */ + uint64 msg_id = 7; } message StreamAuth { @@ -4270,11 +4369,11 @@ message RPCMessage { message RPCMiddlewareResponse { /* - The unique ID of the intercepted request that this response refers to. Must - always be set when giving feedback to an intercept but is ignored for the - initial registration message. + The request message ID this response refers to. Must always be set when + giving feedback to an intercept but is ignored for the initial registration + message. */ - uint64 request_id = 1; + uint64 ref_msg_id = 1; /* The middleware can only send two types of messages to lnd: The initial diff --git a/lnrpc/lightning.swagger.json b/lnrpc/lightning.swagger.json index 3eb2c2946f..91cb54b115 100644 --- a/lnrpc/lightning.swagger.json +++ b/lnrpc/lightning.swagger.json @@ -1380,6 +1380,14 @@ ] }, "collectionFormat": "multi" + }, + { + "name": "time_pref", + "description": "The time preference for this payment. Set to -1 to optimize for fees\nonly, to 1 to optimize for reliability only or a value inbetween for a mix.", + "in": "query", + "required": false, + "type": "number", + "format": "double" } ], "tags": [ @@ -1801,7 +1809,9 @@ "WITNESS_PUBKEY_HASH", "NESTED_PUBKEY_HASH", "UNUSED_WITNESS_PUBKEY_HASH", - "UNUSED_NESTED_PUBKEY_HASH" + "UNUSED_NESTED_PUBKEY_HASH", + "TAPROOT_PUBKEY", + "UNUSED_TAPROOT_PUBKEY" ], "default": "WITNESS_PUBKEY_HASH" }, @@ -1906,6 +1916,13 @@ "in": "query", "required": false, "type": "boolean" + }, + { + "name": "count_total_payments", + "description": "If set, all payments (complete and incomplete, independent of the\nmax_payments parameter) will be counted. Note that setting this to true will\nincrease the run time of the call significantly on systems that have a lot\nof payments, as all of them have to be iterated through to be counted.", + "in": "query", + "required": false, + "type": "boolean" } ], "tags": [ @@ -2757,6 +2774,14 @@ "type": "string", "format": "int64", "description": "Total number of forwarding packages created in this channel." + }, + "chan_status_flags": { + "type": "string", + "description": "A set of flags showing the current state of the channel." + }, + "private": { + "type": "boolean", + "description": "Whether this channel is advertised to the network or not." } } }, @@ -2767,11 +2792,6 @@ "$ref": "#/definitions/PendingChannelsResponsePendingChannel", "title": "The pending channel" }, - "confirmation_height": { - "type": "integer", - "format": "int64", - "title": "The height at which this channel will be confirmed" - }, "commit_fee": { "type": "string", "format": "int64", @@ -2804,6 +2824,10 @@ "commitments": { "$ref": "#/definitions/PendingChannelsResponseCommitments", "description": "A list of valid commitment transactions. Any of these can confirm at\nthis point." + }, + "closing_txid": { + "type": "string", + "title": "The transaction id of the closing transaction" } } }, @@ -2911,10 +2935,12 @@ "WITNESS_PUBKEY_HASH", "NESTED_PUBKEY_HASH", "UNUSED_WITNESS_PUBKEY_HASH", - "UNUSED_NESTED_PUBKEY_HASH" + "UNUSED_NESTED_PUBKEY_HASH", + "TAPROOT_PUBKEY", + "UNUSED_TAPROOT_PUBKEY" ], "default": "WITNESS_PUBKEY_HASH", - "description": "- `p2wkh`: Pay to witness key hash (`WITNESS_PUBKEY_HASH` = 0)\n- `np2wkh`: Pay to nested witness key hash (`NESTED_PUBKEY_HASH` = 1)", + "description": "- `p2wkh`: Pay to witness key hash (`WITNESS_PUBKEY_HASH` = 0)\n- `np2wkh`: Pay to nested witness key hash (`NESTED_PUBKEY_HASH` = 1)\n- `p2tr`: Pay to taproot pubkey (`TAPROOT_PUBKEY` = 4)", "title": "`AddressType` has to be one of:" }, "lnrpcAmount": { @@ -4421,6 +4447,10 @@ "$ref": "#/definitions/lnrpcFeature" }, "description": "Features that our node has advertised in our init message, node\nannouncements and invoices." + }, + "require_htlc_interceptor": { + "type": "boolean", + "description": "Indicates whether the HTLC interceptor API is in always-on mode." } } }, @@ -4592,6 +4622,11 @@ "format": "byte" }, "description": "An optional set of key-value TLV records. This is useful within the context\nof the SendToRoute call as it allows callers to specify arbitrary K-V pairs\nto drop off at each hop within the onion." + }, + "metadata": { + "type": "string", + "format": "byte", + "description": "The payment metadata to send along with the payment to the payee." } } }, @@ -5005,6 +5040,11 @@ "type": "string", "format": "uint64", "description": "The index of the last item in the set of returned payments. This can be used\nas the index_offset to continue seeking forwards in the next request." + }, + "total_num_payments": { + "type": "string", + "format": "uint64", + "description": "Will only be set if count_total_payments in the request was set. Represents\nthe total number of payments (complete and incomplete, independent of the\nnumber of payments requested in the query) currently present in the payments\ndatabase." } } }, @@ -5418,6 +5458,52 @@ } } }, + "lnrpcOutputDetail": { + "type": "object", + "properties": { + "output_type": { + "$ref": "#/definitions/lnrpcOutputScriptType", + "title": "The type of the output" + }, + "address": { + "type": "string", + "title": "The address" + }, + "pk_script": { + "type": "string", + "title": "The pkscript in hex" + }, + "output_index": { + "type": "string", + "format": "int64", + "title": "The output index used in the raw transaction" + }, + "amount": { + "type": "string", + "format": "int64", + "title": "The value of the output coin in satoshis" + }, + "is_our_address": { + "type": "boolean", + "title": "Denotes if the output is controlled by the internal wallet" + } + } + }, + "lnrpcOutputScriptType": { + "type": "string", + "enum": [ + "SCRIPT_TYPE_PUBKEY_HASH", + "SCRIPT_TYPE_SCRIPT_HASH", + "SCRIPT_TYPE_WITNESS_V0_PUBKEY_HASH", + "SCRIPT_TYPE_WITNESS_V0_SCRIPT_HASH", + "SCRIPT_TYPE_PUBKEY", + "SCRIPT_TYPE_MULTISIG", + "SCRIPT_TYPE_NULLDATA", + "SCRIPT_TYPE_NON_STANDARD", + "SCRIPT_TYPE_WITNESS_UNKNOWN" + ], + "default": "SCRIPT_TYPE_PUBKEY_HASH" + }, "lnrpcPayReq": { "type": "object", "properties": { @@ -5755,6 +5841,11 @@ "format": "double", "description": "The effective fee rate in milli-satoshis. The precision of this value\ngoes up to 6 decimal places, so 1e-6." }, + "fee_rate_ppm": { + "type": "integer", + "format": "int64", + "description": "The effective fee rate in micro-satoshis (parts per million)." + }, "time_lock_delta": { "type": "integer", "format": "int64", @@ -5852,7 +5943,7 @@ "request_id": { "type": "string", "format": "uint64", - "description": "The unique ID of the intercepted request. Useful for mapping request to\nresponse when implementing full duplex message interception." + "description": "The unique ID of the intercepted original gRPC request. Useful for mapping\nrequest to response when implementing full duplex message interception. For\nstreaming requests, this will be the same ID for all incoming and outgoing\nmiddleware intercept messages of the _same_ stream." }, "raw_macaroon": { "type": "string", @@ -5874,6 +5965,11 @@ "response": { "$ref": "#/definitions/lnrpcRPCMessage", "description": "Intercept outgoing gRPC response message: all outgoing messages, both on\nstreaming and unary RPCs, are forwarded to the middleware for inspection\nand amendment. The response in this message is the original response as\nit was generated by the main RPC server. It can either be accepted\n(=forwarded to the client), replaced/overwritten with a new message of\nthe same type, or replaced by an error message." + }, + "msg_id": { + "type": "string", + "format": "uint64", + "description": "The unique message ID of this middleware intercept message. There can be\nmultiple middleware intercept messages per single gRPC request (one for the\nincoming request and one for the outgoing response) or gRPC stream (one for\neach incoming message and one for each outgoing response). This message ID\nmust be referenced when responding (accepting/rejecting/modifying) to an\nintercept message." } } }, @@ -6216,7 +6312,7 @@ }, "fee_limit": { "$ref": "#/definitions/lnrpcFeeLimit", - "description": "The maximum number of satoshis that will be paid as a fee of the payment.\nThis value can be represented either as a percentage of the amount being\nsent, or as a fixed amount of the maximum fee the user is willing the pay to\nsend the payment." + "description": "The maximum number of satoshis that will be paid as a fee of the payment.\nThis value can be represented either as a percentage of the amount being\nsent, or as a fixed amount of the maximum fee the user is willing the pay to\nsend the payment. If not specified, lnd will use a default value of 100%\nfees for small amounts (\u003c=1k sat) or 5% fees for larger amounts." }, "outgoing_chan_id": { "type": "string", @@ -6389,7 +6485,14 @@ "items": { "type": "string" }, - "title": "Addresses that received funds for this transaction" + "description": "Addresses that received funds for this transaction. Deprecated as it is\nnow incorporated in the output_details field." + }, + "output_details": { + "type": "array", + "items": { + "$ref": "#/definitions/lnrpcOutputDetail" + }, + "title": "Outputs that received funds for this transaction" }, "raw_tx_hex": { "type": "string", @@ -6518,6 +6621,11 @@ "format": "int64", "title": "The unconfirmed balance of a wallet(with 0 confirmations)" }, + "locked_balance": { + "type": "string", + "format": "int64", + "description": "The total amount of wallet UTXOs held in outputs that are locked for\nother usage." + }, "account_balance": { "type": "object", "additionalProperties": { diff --git a/lnrpc/marshall_utils.go b/lnrpc/marshall_utils.go index d0e5011600..4e79f7dea8 100644 --- a/lnrpc/marshall_utils.go +++ b/lnrpc/marshall_utils.go @@ -5,9 +5,9 @@ import ( "errors" fmt "fmt" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/txscript" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" ) @@ -27,7 +27,6 @@ func CalculateFeeLimit(feeLimit *FeeLimit, amount lnwire.MilliSatoshi) lnwire.MilliSatoshi { switch feeLimit.GetLimit().(type) { - case *FeeLimit_Fixed: return lnwire.NewMSatFromSatoshis( btcutil.Amount(feeLimit.GetFixed()), @@ -40,10 +39,9 @@ func CalculateFeeLimit(feeLimit *FeeLimit, return amount * lnwire.MilliSatoshi(feeLimit.GetPercent()) / 100 default: - // If a fee limit was not specified, we'll use the payment's - // amount as an upper bound in order to avoid payment attempts - // from incurring fees higher than the payment amount itself. - return amount + // Fall back to a sane default value that is based on the amount + // itself. + return lnwallet.DefaultRoutingFeeLimitForAmount(amount) } } @@ -90,13 +88,15 @@ func MarshalUtxos(utxos []*lnwallet.Utxo, activeNetParams *chaincfg.Params) ( // address type. var addrType AddressType switch utxo.AddressType { - case lnwallet.WitnessPubKey: addrType = AddressType_WITNESS_PUBKEY_HASH case lnwallet.NestedWitnessPubKey: addrType = AddressType_NESTED_PUBKEY_HASH + case lnwallet.TaprootPubkey: + addrType = AddressType_TAPROOT_PUBKEY + case lnwallet.UnknownAddressType: continue @@ -144,3 +144,31 @@ func MarshalUtxos(utxos []*lnwallet.Utxo, activeNetParams *chaincfg.Params) ( return res, nil } + +// MarshallOutputType translates a txscript.ScriptClass into a +// lnrpc.OutputScriptType. +func MarshallOutputType(o txscript.ScriptClass) (ret OutputScriptType) { + // Translate txscript ScriptClass type to the proper gRPC proto + // output script type. + switch o { + case txscript.PubKeyHashTy: + ret = OutputScriptType_SCRIPT_TYPE_PUBKEY_HASH + case txscript.ScriptHashTy: + ret = OutputScriptType_SCRIPT_TYPE_SCRIPT_HASH + case txscript.WitnessV0PubKeyHashTy: + ret = OutputScriptType_SCRIPT_TYPE_WITNESS_V0_PUBKEY_HASH + case txscript.WitnessV0ScriptHashTy: + ret = OutputScriptType_SCRIPT_TYPE_WITNESS_V0_SCRIPT_HASH + case txscript.PubKeyTy: + ret = OutputScriptType_SCRIPT_TYPE_PUBKEY + case txscript.MultiSigTy: + ret = OutputScriptType_SCRIPT_TYPE_MULTISIG + case txscript.NullDataTy: + ret = OutputScriptType_SCRIPT_TYPE_NULLDATA + case txscript.NonStandardTy: + ret = OutputScriptType_SCRIPT_TYPE_NON_STANDARD + case txscript.WitnessUnknownTy: + ret = OutputScriptType_SCRIPT_TYPE_WITNESS_UNKNOWN + } + return +} diff --git a/lnrpc/metadata.go b/lnrpc/metadata.go index fc54560b17..a8c1f57a53 100644 --- a/lnrpc/metadata.go +++ b/lnrpc/metadata.go @@ -14,4 +14,8 @@ var ( regexp.MustCompile("^/v1/channels/transaction-stream$"), regexp.MustCompile("^/v2/router/htlcinterceptor$"), } + + // MaxGrpcMsgSize is used when we configure both server and clients to + // allow sending/receiving at most 200 MiB GRPC messages. + MaxGrpcMsgSize = 200 * 1024 * 1024 ) diff --git a/lnrpc/neutrinorpc/config_active.go b/lnrpc/neutrinorpc/config_active.go new file mode 100644 index 0000000000..c1b50239fa --- /dev/null +++ b/lnrpc/neutrinorpc/config_active.go @@ -0,0 +1,18 @@ +//go:build neutrinorpc +// +build neutrinorpc + +package neutrinorpc + +import ( + "github.com/lightninglabs/neutrino" +) + +// Config is the primary configuration struct for the neutrino RPC server. It +// contains all the items required for the rpc server to carry out its +// duties. The fields with struct tags are meant to be parsed as normal +// configuration options, while if able to be populated, the latter fields MUST +// also be specified. +type Config struct { + // ChainService is required to handle neutrino chain service requests. + NeutrinoCS *neutrino.ChainService +} diff --git a/lnrpc/neutrinorpc/config_default.go b/lnrpc/neutrinorpc/config_default.go new file mode 100644 index 0000000000..13e84aad69 --- /dev/null +++ b/lnrpc/neutrinorpc/config_default.go @@ -0,0 +1,7 @@ +//go:build !neutrinorpc +// +build !neutrinorpc + +package neutrinorpc + +// Config is empty for non-neutrinorpc builds. +type Config struct{} diff --git a/lnrpc/neutrinorpc/driver.go b/lnrpc/neutrinorpc/driver.go new file mode 100644 index 0000000000..9bfe7ccd13 --- /dev/null +++ b/lnrpc/neutrinorpc/driver.go @@ -0,0 +1,55 @@ +//go:build neutrinorpc +// +build neutrinorpc + +package neutrinorpc + +import ( + "fmt" + + "github.com/lightningnetwork/lnd/lnrpc" +) + +// createNewSubServer is a helper method that will create the new sub server +// given the main config dispatcher method. If we're unable to find the config +// that is meant for us in the config dispatcher, then we'll exit with an +// error. +func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( + *Server, lnrpc.MacaroonPerms, error) { + + // We'll attempt to look up the config that we expect, according to our + // subServerName name. If we can't find this, then we'll exit with an + // error, as we're unable to properly initialize ourselves without this + // config. + subServerConf, ok := configRegistry.FetchConfig(subServerName) + if !ok { + return nil, nil, fmt.Errorf("unable to find config for "+ + "subserver type %s", subServerName) + } + + // Now that we've found an object mapping to our service name, we'll + // ensure that it's the type we need. + config, ok := subServerConf.(*Config) + if !ok { + return nil, nil, fmt.Errorf("wrong type of config for "+ + "subserver %s, expected %T got %T", subServerName, + &Config{}, subServerConf) + } + + return New(config) +} + +func init() { + subServer := &lnrpc.SubServerDriver{ + SubServerName: subServerName, + NewGrpcHandler: func() lnrpc.GrpcHandler { + return &ServerShell{} + }, + } + + // If the build tag is active, then we'll register ourselves as a + // sub-RPC server within the global lnrpc package namespace. + if err := lnrpc.RegisterSubServer(subServer); err != nil { + panic(fmt.Sprintf("failed to register sub server driver "+ + "'%s': %v", subServerName, err)) + } +} diff --git a/lnrpc/neutrinorpc/log.go b/lnrpc/neutrinorpc/log.go new file mode 100644 index 0000000000..fb2cd79ecf --- /dev/null +++ b/lnrpc/neutrinorpc/log.go @@ -0,0 +1,32 @@ +package neutrinorpc + +import ( + "github.com/btcsuite/btclog" + "github.com/lightningnetwork/lnd/build" +) + +// log is a logger that is initialized with no output filters. This +// means the package will not perform any logging by default until the caller +// requests it. +var log btclog.Logger + +// Subsystem defines the logging code for this subsystem. +const Subsystem = "NRPC" + +// The default amount of logging is none. +func init() { + UseLogger(build.NewSubLogger(Subsystem, nil)) +} + +// DisableLog disables all library log output. Logging output is disabled +// by default until UseLogger is called. +func DisableLog() { + UseLogger(btclog.Disabled) +} + +// UseLogger uses a specified Logger to output package logging info. +// This should be used in preference to SetLogWriter if the caller is also +// using btclog. +func UseLogger(logger btclog.Logger) { + log = logger +} diff --git a/lnrpc/neutrinorpc/neutrino.pb.go b/lnrpc/neutrinorpc/neutrino.pb.go new file mode 100644 index 0000000000..7395197cee --- /dev/null +++ b/lnrpc/neutrinorpc/neutrino.pb.go @@ -0,0 +1,1343 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.26.0 +// protoc v3.6.1 +// source: neutrinorpc/neutrino.proto + +package neutrinorpc + +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) +) + +type StatusRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *StatusRequest) Reset() { + *x = StatusRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_neutrinorpc_neutrino_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StatusRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StatusRequest) ProtoMessage() {} + +func (x *StatusRequest) ProtoReflect() protoreflect.Message { + mi := &file_neutrinorpc_neutrino_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StatusRequest.ProtoReflect.Descriptor instead. +func (*StatusRequest) Descriptor() ([]byte, []int) { + return file_neutrinorpc_neutrino_proto_rawDescGZIP(), []int{0} +} + +type StatusResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Indicates whether the neutrino backend is active or not. + Active bool `protobuf:"varint,1,opt,name=active,proto3" json:"active,omitempty"` + // Is fully synced. + Synced bool `protobuf:"varint,2,opt,name=synced,proto3" json:"synced,omitempty"` + // Best block height. + BlockHeight int32 `protobuf:"varint,3,opt,name=block_height,json=blockHeight,proto3" json:"block_height,omitempty"` + // Best block hash. + BlockHash string `protobuf:"bytes,4,opt,name=block_hash,json=blockHash,proto3" json:"block_hash,omitempty"` + // Connected peers. + Peers []string `protobuf:"bytes,5,rep,name=peers,proto3" json:"peers,omitempty"` +} + +func (x *StatusResponse) Reset() { + *x = StatusResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_neutrinorpc_neutrino_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StatusResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StatusResponse) ProtoMessage() {} + +func (x *StatusResponse) ProtoReflect() protoreflect.Message { + mi := &file_neutrinorpc_neutrino_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StatusResponse.ProtoReflect.Descriptor instead. +func (*StatusResponse) Descriptor() ([]byte, []int) { + return file_neutrinorpc_neutrino_proto_rawDescGZIP(), []int{1} +} + +func (x *StatusResponse) GetActive() bool { + if x != nil { + return x.Active + } + return false +} + +func (x *StatusResponse) GetSynced() bool { + if x != nil { + return x.Synced + } + return false +} + +func (x *StatusResponse) GetBlockHeight() int32 { + if x != nil { + return x.BlockHeight + } + return 0 +} + +func (x *StatusResponse) GetBlockHash() string { + if x != nil { + return x.BlockHash + } + return "" +} + +func (x *StatusResponse) GetPeers() []string { + if x != nil { + return x.Peers + } + return nil +} + +type AddPeerRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Peer to add. + PeerAddrs string `protobuf:"bytes,1,opt,name=peer_addrs,json=peerAddrs,proto3" json:"peer_addrs,omitempty"` +} + +func (x *AddPeerRequest) Reset() { + *x = AddPeerRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_neutrinorpc_neutrino_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AddPeerRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AddPeerRequest) ProtoMessage() {} + +func (x *AddPeerRequest) ProtoReflect() protoreflect.Message { + mi := &file_neutrinorpc_neutrino_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AddPeerRequest.ProtoReflect.Descriptor instead. +func (*AddPeerRequest) Descriptor() ([]byte, []int) { + return file_neutrinorpc_neutrino_proto_rawDescGZIP(), []int{2} +} + +func (x *AddPeerRequest) GetPeerAddrs() string { + if x != nil { + return x.PeerAddrs + } + return "" +} + +type AddPeerResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *AddPeerResponse) Reset() { + *x = AddPeerResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_neutrinorpc_neutrino_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AddPeerResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AddPeerResponse) ProtoMessage() {} + +func (x *AddPeerResponse) ProtoReflect() protoreflect.Message { + mi := &file_neutrinorpc_neutrino_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AddPeerResponse.ProtoReflect.Descriptor instead. +func (*AddPeerResponse) Descriptor() ([]byte, []int) { + return file_neutrinorpc_neutrino_proto_rawDescGZIP(), []int{3} +} + +type DisconnectPeerRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Peer to disconnect. + PeerAddrs string `protobuf:"bytes,1,opt,name=peer_addrs,json=peerAddrs,proto3" json:"peer_addrs,omitempty"` +} + +func (x *DisconnectPeerRequest) Reset() { + *x = DisconnectPeerRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_neutrinorpc_neutrino_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DisconnectPeerRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DisconnectPeerRequest) ProtoMessage() {} + +func (x *DisconnectPeerRequest) ProtoReflect() protoreflect.Message { + mi := &file_neutrinorpc_neutrino_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DisconnectPeerRequest.ProtoReflect.Descriptor instead. +func (*DisconnectPeerRequest) Descriptor() ([]byte, []int) { + return file_neutrinorpc_neutrino_proto_rawDescGZIP(), []int{4} +} + +func (x *DisconnectPeerRequest) GetPeerAddrs() string { + if x != nil { + return x.PeerAddrs + } + return "" +} + +type DisconnectPeerResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *DisconnectPeerResponse) Reset() { + *x = DisconnectPeerResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_neutrinorpc_neutrino_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DisconnectPeerResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DisconnectPeerResponse) ProtoMessage() {} + +func (x *DisconnectPeerResponse) ProtoReflect() protoreflect.Message { + mi := &file_neutrinorpc_neutrino_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DisconnectPeerResponse.ProtoReflect.Descriptor instead. +func (*DisconnectPeerResponse) Descriptor() ([]byte, []int) { + return file_neutrinorpc_neutrino_proto_rawDescGZIP(), []int{5} +} + +type IsBannedRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Peer to lookup. + PeerAddrs string `protobuf:"bytes,1,opt,name=peer_addrs,json=peerAddrs,proto3" json:"peer_addrs,omitempty"` +} + +func (x *IsBannedRequest) Reset() { + *x = IsBannedRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_neutrinorpc_neutrino_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *IsBannedRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*IsBannedRequest) ProtoMessage() {} + +func (x *IsBannedRequest) ProtoReflect() protoreflect.Message { + mi := &file_neutrinorpc_neutrino_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use IsBannedRequest.ProtoReflect.Descriptor instead. +func (*IsBannedRequest) Descriptor() ([]byte, []int) { + return file_neutrinorpc_neutrino_proto_rawDescGZIP(), []int{6} +} + +func (x *IsBannedRequest) GetPeerAddrs() string { + if x != nil { + return x.PeerAddrs + } + return "" +} + +type IsBannedResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Banned bool `protobuf:"varint,1,opt,name=banned,proto3" json:"banned,omitempty"` +} + +func (x *IsBannedResponse) Reset() { + *x = IsBannedResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_neutrinorpc_neutrino_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *IsBannedResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*IsBannedResponse) ProtoMessage() {} + +func (x *IsBannedResponse) ProtoReflect() protoreflect.Message { + mi := &file_neutrinorpc_neutrino_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use IsBannedResponse.ProtoReflect.Descriptor instead. +func (*IsBannedResponse) Descriptor() ([]byte, []int) { + return file_neutrinorpc_neutrino_proto_rawDescGZIP(), []int{7} +} + +func (x *IsBannedResponse) GetBanned() bool { + if x != nil { + return x.Banned + } + return false +} + +type GetBlockHeaderRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Block hash in hex notation. + Hash string `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` +} + +func (x *GetBlockHeaderRequest) Reset() { + *x = GetBlockHeaderRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_neutrinorpc_neutrino_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetBlockHeaderRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetBlockHeaderRequest) ProtoMessage() {} + +func (x *GetBlockHeaderRequest) ProtoReflect() protoreflect.Message { + mi := &file_neutrinorpc_neutrino_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetBlockHeaderRequest.ProtoReflect.Descriptor instead. +func (*GetBlockHeaderRequest) Descriptor() ([]byte, []int) { + return file_neutrinorpc_neutrino_proto_rawDescGZIP(), []int{8} +} + +func (x *GetBlockHeaderRequest) GetHash() string { + if x != nil { + return x.Hash + } + return "" +} + +type GetBlockHeaderResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The block hash (same as provided). + Hash string `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` + // The number of confirmations. + Confirmations int64 `protobuf:"varint,2,opt,name=confirmations,proto3" json:"confirmations,omitempty"` + // The block size excluding witness data. + StrippedSize int64 `protobuf:"varint,3,opt,name=stripped_size,json=strippedSize,proto3" json:"stripped_size,omitempty"` + // The block size (bytes). + Size int64 `protobuf:"varint,4,opt,name=size,proto3" json:"size,omitempty"` + // The block weight as defined in BIP 141. + Weight int64 `protobuf:"varint,5,opt,name=weight,proto3" json:"weight,omitempty"` + // The block height or index. + Height int32 `protobuf:"varint,6,opt,name=height,proto3" json:"height,omitempty"` + // The block version. + Version int32 `protobuf:"varint,7,opt,name=version,proto3" json:"version,omitempty"` + // The block version. + VersionHex string `protobuf:"bytes,8,opt,name=version_hex,json=versionHex,proto3" json:"version_hex,omitempty"` + // The merkle root. + Merkleroot string `protobuf:"bytes,9,opt,name=merkleroot,proto3" json:"merkleroot,omitempty"` + // The block time in seconds since epoch (Jan 1 1970 GMT). + Time int64 `protobuf:"varint,10,opt,name=time,proto3" json:"time,omitempty"` + // The nonce. + Nonce uint32 `protobuf:"varint,11,opt,name=nonce,proto3" json:"nonce,omitempty"` + // The bits in hex notation. + Bits string `protobuf:"bytes,12,opt,name=bits,proto3" json:"bits,omitempty"` + // The number of transactions in the block. + Ntx int32 `protobuf:"varint,13,opt,name=ntx,proto3" json:"ntx,omitempty"` + // The hash of the previous block. + PreviousBlockHash string `protobuf:"bytes,14,opt,name=previous_block_hash,json=previousBlockHash,proto3" json:"previous_block_hash,omitempty"` + // The raw hex of the block. + RawHex []byte `protobuf:"bytes,15,opt,name=raw_hex,json=rawHex,proto3" json:"raw_hex,omitempty"` +} + +func (x *GetBlockHeaderResponse) Reset() { + *x = GetBlockHeaderResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_neutrinorpc_neutrino_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetBlockHeaderResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetBlockHeaderResponse) ProtoMessage() {} + +func (x *GetBlockHeaderResponse) ProtoReflect() protoreflect.Message { + mi := &file_neutrinorpc_neutrino_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetBlockHeaderResponse.ProtoReflect.Descriptor instead. +func (*GetBlockHeaderResponse) Descriptor() ([]byte, []int) { + return file_neutrinorpc_neutrino_proto_rawDescGZIP(), []int{9} +} + +func (x *GetBlockHeaderResponse) GetHash() string { + if x != nil { + return x.Hash + } + return "" +} + +func (x *GetBlockHeaderResponse) GetConfirmations() int64 { + if x != nil { + return x.Confirmations + } + return 0 +} + +func (x *GetBlockHeaderResponse) GetStrippedSize() int64 { + if x != nil { + return x.StrippedSize + } + return 0 +} + +func (x *GetBlockHeaderResponse) GetSize() int64 { + if x != nil { + return x.Size + } + return 0 +} + +func (x *GetBlockHeaderResponse) GetWeight() int64 { + if x != nil { + return x.Weight + } + return 0 +} + +func (x *GetBlockHeaderResponse) GetHeight() int32 { + if x != nil { + return x.Height + } + return 0 +} + +func (x *GetBlockHeaderResponse) GetVersion() int32 { + if x != nil { + return x.Version + } + return 0 +} + +func (x *GetBlockHeaderResponse) GetVersionHex() string { + if x != nil { + return x.VersionHex + } + return "" +} + +func (x *GetBlockHeaderResponse) GetMerkleroot() string { + if x != nil { + return x.Merkleroot + } + return "" +} + +func (x *GetBlockHeaderResponse) GetTime() int64 { + if x != nil { + return x.Time + } + return 0 +} + +func (x *GetBlockHeaderResponse) GetNonce() uint32 { + if x != nil { + return x.Nonce + } + return 0 +} + +func (x *GetBlockHeaderResponse) GetBits() string { + if x != nil { + return x.Bits + } + return "" +} + +func (x *GetBlockHeaderResponse) GetNtx() int32 { + if x != nil { + return x.Ntx + } + return 0 +} + +func (x *GetBlockHeaderResponse) GetPreviousBlockHash() string { + if x != nil { + return x.PreviousBlockHash + } + return "" +} + +func (x *GetBlockHeaderResponse) GetRawHex() []byte { + if x != nil { + return x.RawHex + } + return nil +} + +type GetBlockRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Block hash in hex notation. + Hash string `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` +} + +func (x *GetBlockRequest) Reset() { + *x = GetBlockRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_neutrinorpc_neutrino_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetBlockRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetBlockRequest) ProtoMessage() {} + +func (x *GetBlockRequest) ProtoReflect() protoreflect.Message { + mi := &file_neutrinorpc_neutrino_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetBlockRequest.ProtoReflect.Descriptor instead. +func (*GetBlockRequest) Descriptor() ([]byte, []int) { + return file_neutrinorpc_neutrino_proto_rawDescGZIP(), []int{10} +} + +func (x *GetBlockRequest) GetHash() string { + if x != nil { + return x.Hash + } + return "" +} + +type GetBlockResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The block hash (same as provided). + Hash string `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` + // The number of confirmations. + Confirmations int64 `protobuf:"varint,2,opt,name=confirmations,proto3" json:"confirmations,omitempty"` + // The block size excluding witness data. + StrippedSize int64 `protobuf:"varint,3,opt,name=stripped_size,json=strippedSize,proto3" json:"stripped_size,omitempty"` + // The block size (bytes). + Size int64 `protobuf:"varint,4,opt,name=size,proto3" json:"size,omitempty"` + // The block weight as defined in BIP 141. + Weight int64 `protobuf:"varint,5,opt,name=weight,proto3" json:"weight,omitempty"` + // The block height or index. + Height int32 `protobuf:"varint,6,opt,name=height,proto3" json:"height,omitempty"` + // The block version. + Version int32 `protobuf:"varint,7,opt,name=version,proto3" json:"version,omitempty"` + // The block version. + VersionHex string `protobuf:"bytes,8,opt,name=version_hex,json=versionHex,proto3" json:"version_hex,omitempty"` + // The merkle root. + Merkleroot string `protobuf:"bytes,9,opt,name=merkleroot,proto3" json:"merkleroot,omitempty"` + // List of transaction ids. + Tx []string `protobuf:"bytes,10,rep,name=tx,proto3" json:"tx,omitempty"` + // The block time in seconds since epoch (Jan 1 1970 GMT). + Time int64 `protobuf:"varint,11,opt,name=time,proto3" json:"time,omitempty"` + // The nonce. + Nonce uint32 `protobuf:"varint,12,opt,name=nonce,proto3" json:"nonce,omitempty"` + // The bits in hex notation. + Bits string `protobuf:"bytes,13,opt,name=bits,proto3" json:"bits,omitempty"` + // The number of transactions in the block. + Ntx int32 `protobuf:"varint,14,opt,name=ntx,proto3" json:"ntx,omitempty"` + // The hash of the previous block. + PreviousBlockHash string `protobuf:"bytes,15,opt,name=previous_block_hash,json=previousBlockHash,proto3" json:"previous_block_hash,omitempty"` + // The raw hex of the block. + RawHex []byte `protobuf:"bytes,16,opt,name=raw_hex,json=rawHex,proto3" json:"raw_hex,omitempty"` +} + +func (x *GetBlockResponse) Reset() { + *x = GetBlockResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_neutrinorpc_neutrino_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetBlockResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetBlockResponse) ProtoMessage() {} + +func (x *GetBlockResponse) ProtoReflect() protoreflect.Message { + mi := &file_neutrinorpc_neutrino_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetBlockResponse.ProtoReflect.Descriptor instead. +func (*GetBlockResponse) Descriptor() ([]byte, []int) { + return file_neutrinorpc_neutrino_proto_rawDescGZIP(), []int{11} +} + +func (x *GetBlockResponse) GetHash() string { + if x != nil { + return x.Hash + } + return "" +} + +func (x *GetBlockResponse) GetConfirmations() int64 { + if x != nil { + return x.Confirmations + } + return 0 +} + +func (x *GetBlockResponse) GetStrippedSize() int64 { + if x != nil { + return x.StrippedSize + } + return 0 +} + +func (x *GetBlockResponse) GetSize() int64 { + if x != nil { + return x.Size + } + return 0 +} + +func (x *GetBlockResponse) GetWeight() int64 { + if x != nil { + return x.Weight + } + return 0 +} + +func (x *GetBlockResponse) GetHeight() int32 { + if x != nil { + return x.Height + } + return 0 +} + +func (x *GetBlockResponse) GetVersion() int32 { + if x != nil { + return x.Version + } + return 0 +} + +func (x *GetBlockResponse) GetVersionHex() string { + if x != nil { + return x.VersionHex + } + return "" +} + +func (x *GetBlockResponse) GetMerkleroot() string { + if x != nil { + return x.Merkleroot + } + return "" +} + +func (x *GetBlockResponse) GetTx() []string { + if x != nil { + return x.Tx + } + return nil +} + +func (x *GetBlockResponse) GetTime() int64 { + if x != nil { + return x.Time + } + return 0 +} + +func (x *GetBlockResponse) GetNonce() uint32 { + if x != nil { + return x.Nonce + } + return 0 +} + +func (x *GetBlockResponse) GetBits() string { + if x != nil { + return x.Bits + } + return "" +} + +func (x *GetBlockResponse) GetNtx() int32 { + if x != nil { + return x.Ntx + } + return 0 +} + +func (x *GetBlockResponse) GetPreviousBlockHash() string { + if x != nil { + return x.PreviousBlockHash + } + return "" +} + +func (x *GetBlockResponse) GetRawHex() []byte { + if x != nil { + return x.RawHex + } + return nil +} + +type GetCFilterRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Block hash in hex notation. + Hash string `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` +} + +func (x *GetCFilterRequest) Reset() { + *x = GetCFilterRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_neutrinorpc_neutrino_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetCFilterRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetCFilterRequest) ProtoMessage() {} + +func (x *GetCFilterRequest) ProtoReflect() protoreflect.Message { + mi := &file_neutrinorpc_neutrino_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetCFilterRequest.ProtoReflect.Descriptor instead. +func (*GetCFilterRequest) Descriptor() ([]byte, []int) { + return file_neutrinorpc_neutrino_proto_rawDescGZIP(), []int{12} +} + +func (x *GetCFilterRequest) GetHash() string { + if x != nil { + return x.Hash + } + return "" +} + +type GetCFilterResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // GCS filter. + Filter []byte `protobuf:"bytes,1,opt,name=filter,proto3" json:"filter,omitempty"` +} + +func (x *GetCFilterResponse) Reset() { + *x = GetCFilterResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_neutrinorpc_neutrino_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetCFilterResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetCFilterResponse) ProtoMessage() {} + +func (x *GetCFilterResponse) ProtoReflect() protoreflect.Message { + mi := &file_neutrinorpc_neutrino_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetCFilterResponse.ProtoReflect.Descriptor instead. +func (*GetCFilterResponse) Descriptor() ([]byte, []int) { + return file_neutrinorpc_neutrino_proto_rawDescGZIP(), []int{13} +} + +func (x *GetCFilterResponse) GetFilter() []byte { + if x != nil { + return x.Filter + } + return nil +} + +var File_neutrinorpc_neutrino_proto protoreflect.FileDescriptor + +var file_neutrinorpc_neutrino_proto_rawDesc = []byte{ + 0x0a, 0x1a, 0x6e, 0x65, 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x72, 0x70, 0x63, 0x2f, 0x6e, 0x65, + 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x6e, 0x65, + 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x72, 0x70, 0x63, 0x22, 0x0f, 0x0a, 0x0d, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x98, 0x01, 0x0a, 0x0e, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, + 0x06, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x61, + 0x63, 0x74, 0x69, 0x76, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x79, 0x6e, 0x63, 0x65, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x73, 0x79, 0x6e, 0x63, 0x65, 0x64, 0x12, 0x21, 0x0a, + 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, + 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x12, + 0x14, 0x0a, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, + 0x70, 0x65, 0x65, 0x72, 0x73, 0x22, 0x2f, 0x0a, 0x0e, 0x41, 0x64, 0x64, 0x50, 0x65, 0x65, 0x72, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x5f, + 0x61, 0x64, 0x64, 0x72, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x65, 0x65, + 0x72, 0x41, 0x64, 0x64, 0x72, 0x73, 0x22, 0x11, 0x0a, 0x0f, 0x41, 0x64, 0x64, 0x50, 0x65, 0x65, + 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x36, 0x0a, 0x15, 0x44, 0x69, 0x73, + 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x73, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x65, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, + 0x73, 0x22, 0x18, 0x0a, 0x16, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, + 0x65, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x30, 0x0a, 0x0f, 0x49, + 0x73, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, + 0x0a, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x73, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x70, 0x65, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x73, 0x22, 0x2a, 0x0a, + 0x10, 0x49, 0x73, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x16, 0x0a, 0x06, 0x62, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x06, 0x62, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x22, 0x2b, 0x0a, 0x15, 0x47, 0x65, 0x74, + 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x22, 0xaf, 0x03, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x42, 0x6c, + 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x24, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x73, + 0x74, 0x72, 0x69, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0c, 0x73, 0x74, 0x72, 0x69, 0x70, 0x70, 0x65, 0x64, 0x53, 0x69, 0x7a, 0x65, + 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, + 0x73, 0x69, 0x7a, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x16, 0x0a, 0x06, + 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x68, 0x65, + 0x69, 0x67, 0x68, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f, + 0x0a, 0x0b, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x68, 0x65, 0x78, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0a, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x48, 0x65, 0x78, 0x12, + 0x1e, 0x0a, 0x0a, 0x6d, 0x65, 0x72, 0x6b, 0x6c, 0x65, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x09, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x65, 0x72, 0x6b, 0x6c, 0x65, 0x72, 0x6f, 0x6f, 0x74, 0x12, + 0x12, 0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x74, + 0x69, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x0b, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x69, 0x74, + 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x62, 0x69, 0x74, 0x73, 0x12, 0x10, 0x0a, + 0x03, 0x6e, 0x74, 0x78, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x6e, 0x74, 0x78, 0x12, + 0x2e, 0x0a, 0x13, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x5f, 0x62, 0x6c, 0x6f, 0x63, + 0x6b, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x70, 0x72, + 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x12, + 0x17, 0x0a, 0x07, 0x72, 0x61, 0x77, 0x5f, 0x68, 0x65, 0x78, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x06, 0x72, 0x61, 0x77, 0x48, 0x65, 0x78, 0x22, 0x25, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x42, + 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x68, + 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x22, + 0xb9, 0x03, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x24, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x23, + 0x0a, 0x0d, 0x73, 0x74, 0x72, 0x69, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x74, 0x72, 0x69, 0x70, 0x70, 0x65, 0x64, 0x53, + 0x69, 0x7a, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x77, 0x65, 0x69, 0x67, 0x68, + 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, + 0x16, 0x0a, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x68, 0x65, 0x78, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x48, + 0x65, 0x78, 0x12, 0x1e, 0x0a, 0x0a, 0x6d, 0x65, 0x72, 0x6b, 0x6c, 0x65, 0x72, 0x6f, 0x6f, 0x74, + 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x65, 0x72, 0x6b, 0x6c, 0x65, 0x72, 0x6f, + 0x6f, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x78, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x02, + 0x74, 0x78, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x18, + 0x0c, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, + 0x62, 0x69, 0x74, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x62, 0x69, 0x74, 0x73, + 0x12, 0x10, 0x0a, 0x03, 0x6e, 0x74, 0x78, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x6e, + 0x74, 0x78, 0x12, 0x2e, 0x0a, 0x13, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x5f, 0x62, + 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x11, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, + 0x73, 0x68, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x61, 0x77, 0x5f, 0x68, 0x65, 0x78, 0x18, 0x10, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x06, 0x72, 0x61, 0x77, 0x48, 0x65, 0x78, 0x22, 0x27, 0x0a, 0x11, 0x47, + 0x65, 0x74, 0x43, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x68, 0x61, 0x73, 0x68, 0x22, 0x2c, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x43, 0x46, 0x69, 0x6c, 0x74, + 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x69, + 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, + 0x65, 0x72, 0x32, 0xad, 0x04, 0x0a, 0x0b, 0x4e, 0x65, 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x4b, + 0x69, 0x74, 0x12, 0x41, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1a, 0x2e, 0x6e, + 0x65, 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6e, 0x65, 0x75, 0x74, 0x72, + 0x69, 0x6e, 0x6f, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x07, 0x41, 0x64, 0x64, 0x50, 0x65, 0x65, 0x72, + 0x12, 0x1b, 0x2e, 0x6e, 0x65, 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x72, 0x70, 0x63, 0x2e, 0x41, + 0x64, 0x64, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, + 0x6e, 0x65, 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x50, + 0x65, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x59, 0x0a, 0x0e, 0x44, + 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x12, 0x22, 0x2e, + 0x6e, 0x65, 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x69, 0x73, 0x63, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x23, 0x2e, 0x6e, 0x65, 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x72, 0x70, 0x63, 0x2e, + 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x08, 0x49, 0x73, 0x42, 0x61, 0x6e, 0x6e, + 0x65, 0x64, 0x12, 0x1c, 0x2e, 0x6e, 0x65, 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x72, 0x70, 0x63, + 0x2e, 0x49, 0x73, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1d, 0x2e, 0x6e, 0x65, 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x72, 0x70, 0x63, 0x2e, 0x49, + 0x73, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x59, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, + 0x72, 0x12, 0x22, 0x2e, 0x6e, 0x65, 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x72, 0x70, 0x63, 0x2e, + 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x6e, 0x65, 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, + 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x64, + 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x08, 0x47, 0x65, + 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x1c, 0x2e, 0x6e, 0x65, 0x75, 0x74, 0x72, 0x69, 0x6e, + 0x6f, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6e, 0x65, 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x72, + 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x43, 0x46, 0x69, 0x6c, 0x74, 0x65, + 0x72, 0x12, 0x1e, 0x2e, 0x6e, 0x65, 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x72, 0x70, 0x63, 0x2e, + 0x47, 0x65, 0x74, 0x43, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1f, 0x2e, 0x6e, 0x65, 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x72, 0x70, 0x63, 0x2e, + 0x47, 0x65, 0x74, 0x43, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, + 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2f, 0x6e, 0x65, 0x75, 0x74, + 0x72, 0x69, 0x6e, 0x6f, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_neutrinorpc_neutrino_proto_rawDescOnce sync.Once + file_neutrinorpc_neutrino_proto_rawDescData = file_neutrinorpc_neutrino_proto_rawDesc +) + +func file_neutrinorpc_neutrino_proto_rawDescGZIP() []byte { + file_neutrinorpc_neutrino_proto_rawDescOnce.Do(func() { + file_neutrinorpc_neutrino_proto_rawDescData = protoimpl.X.CompressGZIP(file_neutrinorpc_neutrino_proto_rawDescData) + }) + return file_neutrinorpc_neutrino_proto_rawDescData +} + +var file_neutrinorpc_neutrino_proto_msgTypes = make([]protoimpl.MessageInfo, 14) +var file_neutrinorpc_neutrino_proto_goTypes = []interface{}{ + (*StatusRequest)(nil), // 0: neutrinorpc.StatusRequest + (*StatusResponse)(nil), // 1: neutrinorpc.StatusResponse + (*AddPeerRequest)(nil), // 2: neutrinorpc.AddPeerRequest + (*AddPeerResponse)(nil), // 3: neutrinorpc.AddPeerResponse + (*DisconnectPeerRequest)(nil), // 4: neutrinorpc.DisconnectPeerRequest + (*DisconnectPeerResponse)(nil), // 5: neutrinorpc.DisconnectPeerResponse + (*IsBannedRequest)(nil), // 6: neutrinorpc.IsBannedRequest + (*IsBannedResponse)(nil), // 7: neutrinorpc.IsBannedResponse + (*GetBlockHeaderRequest)(nil), // 8: neutrinorpc.GetBlockHeaderRequest + (*GetBlockHeaderResponse)(nil), // 9: neutrinorpc.GetBlockHeaderResponse + (*GetBlockRequest)(nil), // 10: neutrinorpc.GetBlockRequest + (*GetBlockResponse)(nil), // 11: neutrinorpc.GetBlockResponse + (*GetCFilterRequest)(nil), // 12: neutrinorpc.GetCFilterRequest + (*GetCFilterResponse)(nil), // 13: neutrinorpc.GetCFilterResponse +} +var file_neutrinorpc_neutrino_proto_depIdxs = []int32{ + 0, // 0: neutrinorpc.NeutrinoKit.Status:input_type -> neutrinorpc.StatusRequest + 2, // 1: neutrinorpc.NeutrinoKit.AddPeer:input_type -> neutrinorpc.AddPeerRequest + 4, // 2: neutrinorpc.NeutrinoKit.DisconnectPeer:input_type -> neutrinorpc.DisconnectPeerRequest + 6, // 3: neutrinorpc.NeutrinoKit.IsBanned:input_type -> neutrinorpc.IsBannedRequest + 8, // 4: neutrinorpc.NeutrinoKit.GetBlockHeader:input_type -> neutrinorpc.GetBlockHeaderRequest + 10, // 5: neutrinorpc.NeutrinoKit.GetBlock:input_type -> neutrinorpc.GetBlockRequest + 12, // 6: neutrinorpc.NeutrinoKit.GetCFilter:input_type -> neutrinorpc.GetCFilterRequest + 1, // 7: neutrinorpc.NeutrinoKit.Status:output_type -> neutrinorpc.StatusResponse + 3, // 8: neutrinorpc.NeutrinoKit.AddPeer:output_type -> neutrinorpc.AddPeerResponse + 5, // 9: neutrinorpc.NeutrinoKit.DisconnectPeer:output_type -> neutrinorpc.DisconnectPeerResponse + 7, // 10: neutrinorpc.NeutrinoKit.IsBanned:output_type -> neutrinorpc.IsBannedResponse + 9, // 11: neutrinorpc.NeutrinoKit.GetBlockHeader:output_type -> neutrinorpc.GetBlockHeaderResponse + 11, // 12: neutrinorpc.NeutrinoKit.GetBlock:output_type -> neutrinorpc.GetBlockResponse + 13, // 13: neutrinorpc.NeutrinoKit.GetCFilter:output_type -> neutrinorpc.GetCFilterResponse + 7, // [7:14] is the sub-list for method output_type + 0, // [0:7] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_neutrinorpc_neutrino_proto_init() } +func file_neutrinorpc_neutrino_proto_init() { + if File_neutrinorpc_neutrino_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_neutrinorpc_neutrino_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StatusRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_neutrinorpc_neutrino_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StatusResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_neutrinorpc_neutrino_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AddPeerRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_neutrinorpc_neutrino_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AddPeerResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_neutrinorpc_neutrino_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DisconnectPeerRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_neutrinorpc_neutrino_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DisconnectPeerResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_neutrinorpc_neutrino_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*IsBannedRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_neutrinorpc_neutrino_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*IsBannedResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_neutrinorpc_neutrino_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetBlockHeaderRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_neutrinorpc_neutrino_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetBlockHeaderResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_neutrinorpc_neutrino_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetBlockRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_neutrinorpc_neutrino_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetBlockResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_neutrinorpc_neutrino_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetCFilterRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_neutrinorpc_neutrino_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetCFilterResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_neutrinorpc_neutrino_proto_rawDesc, + NumEnums: 0, + NumMessages: 14, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_neutrinorpc_neutrino_proto_goTypes, + DependencyIndexes: file_neutrinorpc_neutrino_proto_depIdxs, + MessageInfos: file_neutrinorpc_neutrino_proto_msgTypes, + }.Build() + File_neutrinorpc_neutrino_proto = out.File + file_neutrinorpc_neutrino_proto_rawDesc = nil + file_neutrinorpc_neutrino_proto_goTypes = nil + file_neutrinorpc_neutrino_proto_depIdxs = nil +} diff --git a/lnrpc/neutrinorpc/neutrino.pb.gw.go b/lnrpc/neutrinorpc/neutrino.pb.gw.go new file mode 100644 index 0000000000..7c10a3a030 --- /dev/null +++ b/lnrpc/neutrinorpc/neutrino.pb.gw.go @@ -0,0 +1,693 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: neutrinorpc/neutrino.proto + +/* +Package neutrinorpc is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package neutrinorpc + +import ( + "context" + "io" + "net/http" + + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/grpc-ecosystem/grpc-gateway/v2/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = metadata.Join + +func request_NeutrinoKit_Status_0(ctx context.Context, marshaler runtime.Marshaler, client NeutrinoKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq StatusRequest + var metadata runtime.ServerMetadata + + msg, err := client.Status(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_NeutrinoKit_Status_0(ctx context.Context, marshaler runtime.Marshaler, server NeutrinoKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq StatusRequest + var metadata runtime.ServerMetadata + + msg, err := server.Status(ctx, &protoReq) + return msg, metadata, err + +} + +func request_NeutrinoKit_AddPeer_0(ctx context.Context, marshaler runtime.Marshaler, client NeutrinoKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq AddPeerRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.AddPeer(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_NeutrinoKit_AddPeer_0(ctx context.Context, marshaler runtime.Marshaler, server NeutrinoKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq AddPeerRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.AddPeer(ctx, &protoReq) + return msg, metadata, err + +} + +func request_NeutrinoKit_DisconnectPeer_0(ctx context.Context, marshaler runtime.Marshaler, client NeutrinoKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq DisconnectPeerRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.DisconnectPeer(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_NeutrinoKit_DisconnectPeer_0(ctx context.Context, marshaler runtime.Marshaler, server NeutrinoKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq DisconnectPeerRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.DisconnectPeer(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_NeutrinoKit_IsBanned_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_NeutrinoKit_IsBanned_0(ctx context.Context, marshaler runtime.Marshaler, client NeutrinoKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq IsBannedRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_NeutrinoKit_IsBanned_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.IsBanned(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_NeutrinoKit_IsBanned_0(ctx context.Context, marshaler runtime.Marshaler, server NeutrinoKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq IsBannedRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_NeutrinoKit_IsBanned_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.IsBanned(ctx, &protoReq) + return msg, metadata, err + +} + +func request_NeutrinoKit_GetBlockHeader_0(ctx context.Context, marshaler runtime.Marshaler, client NeutrinoKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetBlockHeaderRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["hash"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "hash") + } + + protoReq.Hash, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "hash", err) + } + + msg, err := client.GetBlockHeader(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_NeutrinoKit_GetBlockHeader_0(ctx context.Context, marshaler runtime.Marshaler, server NeutrinoKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetBlockHeaderRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["hash"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "hash") + } + + protoReq.Hash, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "hash", err) + } + + msg, err := server.GetBlockHeader(ctx, &protoReq) + return msg, metadata, err + +} + +func request_NeutrinoKit_GetBlock_0(ctx context.Context, marshaler runtime.Marshaler, client NeutrinoKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetBlockRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["hash"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "hash") + } + + protoReq.Hash, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "hash", err) + } + + msg, err := client.GetBlock(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_NeutrinoKit_GetBlock_0(ctx context.Context, marshaler runtime.Marshaler, server NeutrinoKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetBlockRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["hash"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "hash") + } + + protoReq.Hash, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "hash", err) + } + + msg, err := server.GetBlock(ctx, &protoReq) + return msg, metadata, err + +} + +func request_NeutrinoKit_GetCFilter_0(ctx context.Context, marshaler runtime.Marshaler, client NeutrinoKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetCFilterRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["hash"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "hash") + } + + protoReq.Hash, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "hash", err) + } + + msg, err := client.GetCFilter(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_NeutrinoKit_GetCFilter_0(ctx context.Context, marshaler runtime.Marshaler, server NeutrinoKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetCFilterRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["hash"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "hash") + } + + protoReq.Hash, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "hash", err) + } + + msg, err := server.GetCFilter(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterNeutrinoKitHandlerServer registers the http handlers for service NeutrinoKit to "mux". +// UnaryRPC :call NeutrinoKitServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterNeutrinoKitHandlerFromEndpoint instead. +func RegisterNeutrinoKitHandlerServer(ctx context.Context, mux *runtime.ServeMux, server NeutrinoKitServer) error { + + mux.Handle("GET", pattern_NeutrinoKit_Status_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/neutrinorpc.NeutrinoKit/Status", runtime.WithHTTPPathPattern("/v2/neutrino/status")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_NeutrinoKit_Status_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_NeutrinoKit_Status_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_NeutrinoKit_AddPeer_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/neutrinorpc.NeutrinoKit/AddPeer", runtime.WithHTTPPathPattern("/v2/neutrino/addpeer")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_NeutrinoKit_AddPeer_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_NeutrinoKit_AddPeer_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_NeutrinoKit_DisconnectPeer_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/neutrinorpc.NeutrinoKit/DisconnectPeer", runtime.WithHTTPPathPattern("/v2/neutrino/disconnect")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_NeutrinoKit_DisconnectPeer_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_NeutrinoKit_DisconnectPeer_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_NeutrinoKit_IsBanned_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/neutrinorpc.NeutrinoKit/IsBanned", runtime.WithHTTPPathPattern("/v2/neutrino/isbanned")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_NeutrinoKit_IsBanned_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_NeutrinoKit_IsBanned_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_NeutrinoKit_GetBlockHeader_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/neutrinorpc.NeutrinoKit/GetBlockHeader", runtime.WithHTTPPathPattern("/v2/neutrino/blockheader/{hash}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_NeutrinoKit_GetBlockHeader_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_NeutrinoKit_GetBlockHeader_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_NeutrinoKit_GetBlock_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/neutrinorpc.NeutrinoKit/GetBlock", runtime.WithHTTPPathPattern("/v2/neutrino/block/{hash}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_NeutrinoKit_GetBlock_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_NeutrinoKit_GetBlock_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_NeutrinoKit_GetCFilter_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/neutrinorpc.NeutrinoKit/GetCFilter", runtime.WithHTTPPathPattern("/v2/neutrino/cfilter/{hash}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_NeutrinoKit_GetCFilter_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_NeutrinoKit_GetCFilter_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterNeutrinoKitHandlerFromEndpoint is same as RegisterNeutrinoKitHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterNeutrinoKitHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterNeutrinoKitHandler(ctx, mux, conn) +} + +// RegisterNeutrinoKitHandler registers the http handlers for service NeutrinoKit to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterNeutrinoKitHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterNeutrinoKitHandlerClient(ctx, mux, NewNeutrinoKitClient(conn)) +} + +// RegisterNeutrinoKitHandlerClient registers the http handlers for service NeutrinoKit +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "NeutrinoKitClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "NeutrinoKitClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "NeutrinoKitClient" to call the correct interceptors. +func RegisterNeutrinoKitHandlerClient(ctx context.Context, mux *runtime.ServeMux, client NeutrinoKitClient) error { + + mux.Handle("GET", pattern_NeutrinoKit_Status_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req, "/neutrinorpc.NeutrinoKit/Status", runtime.WithHTTPPathPattern("/v2/neutrino/status")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_NeutrinoKit_Status_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_NeutrinoKit_Status_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_NeutrinoKit_AddPeer_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req, "/neutrinorpc.NeutrinoKit/AddPeer", runtime.WithHTTPPathPattern("/v2/neutrino/addpeer")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_NeutrinoKit_AddPeer_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_NeutrinoKit_AddPeer_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_NeutrinoKit_DisconnectPeer_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req, "/neutrinorpc.NeutrinoKit/DisconnectPeer", runtime.WithHTTPPathPattern("/v2/neutrino/disconnect")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_NeutrinoKit_DisconnectPeer_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_NeutrinoKit_DisconnectPeer_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_NeutrinoKit_IsBanned_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req, "/neutrinorpc.NeutrinoKit/IsBanned", runtime.WithHTTPPathPattern("/v2/neutrino/isbanned")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_NeutrinoKit_IsBanned_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_NeutrinoKit_IsBanned_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_NeutrinoKit_GetBlockHeader_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req, "/neutrinorpc.NeutrinoKit/GetBlockHeader", runtime.WithHTTPPathPattern("/v2/neutrino/blockheader/{hash}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_NeutrinoKit_GetBlockHeader_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_NeutrinoKit_GetBlockHeader_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_NeutrinoKit_GetBlock_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req, "/neutrinorpc.NeutrinoKit/GetBlock", runtime.WithHTTPPathPattern("/v2/neutrino/block/{hash}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_NeutrinoKit_GetBlock_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_NeutrinoKit_GetBlock_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_NeutrinoKit_GetCFilter_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req, "/neutrinorpc.NeutrinoKit/GetCFilter", runtime.WithHTTPPathPattern("/v2/neutrino/cfilter/{hash}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_NeutrinoKit_GetCFilter_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_NeutrinoKit_GetCFilter_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_NeutrinoKit_Status_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "neutrino", "status"}, "")) + + pattern_NeutrinoKit_AddPeer_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "neutrino", "addpeer"}, "")) + + pattern_NeutrinoKit_DisconnectPeer_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "neutrino", "disconnect"}, "")) + + pattern_NeutrinoKit_IsBanned_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "neutrino", "isbanned"}, "")) + + pattern_NeutrinoKit_GetBlockHeader_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"v2", "neutrino", "blockheader", "hash"}, "")) + + pattern_NeutrinoKit_GetBlock_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"v2", "neutrino", "block", "hash"}, "")) + + pattern_NeutrinoKit_GetCFilter_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"v2", "neutrino", "cfilter", "hash"}, "")) +) + +var ( + forward_NeutrinoKit_Status_0 = runtime.ForwardResponseMessage + + forward_NeutrinoKit_AddPeer_0 = runtime.ForwardResponseMessage + + forward_NeutrinoKit_DisconnectPeer_0 = runtime.ForwardResponseMessage + + forward_NeutrinoKit_IsBanned_0 = runtime.ForwardResponseMessage + + forward_NeutrinoKit_GetBlockHeader_0 = runtime.ForwardResponseMessage + + forward_NeutrinoKit_GetBlock_0 = runtime.ForwardResponseMessage + + forward_NeutrinoKit_GetCFilter_0 = runtime.ForwardResponseMessage +) diff --git a/lnrpc/neutrinorpc/neutrino.proto b/lnrpc/neutrinorpc/neutrino.proto new file mode 100644 index 0000000000..388714813e --- /dev/null +++ b/lnrpc/neutrinorpc/neutrino.proto @@ -0,0 +1,210 @@ +syntax = "proto3"; + +package neutrinorpc; + +option go_package = "github.com/lightningnetwork/lnd/lnrpc/neutrinorpc"; + +// NeutrinoKit is a service that can be used to get information about the +// current state of the neutrino instance, fetch blocks and add/remove peers. +service NeutrinoKit { + /* + Status returns the status of the light client neutrino instance, + along with height and hash of the best block, and a list of connected + peers. + */ + rpc Status (StatusRequest) returns (StatusResponse); + + /* + AddPeer adds a new peer that has already been connected to the server. + */ + rpc AddPeer (AddPeerRequest) returns (AddPeerResponse); + + /* + DisconnectPeer disconnects a peer by target address. Both outbound and + inbound nodes will be searched for the target node. An error message will + be returned if the peer was not found. + */ + rpc DisconnectPeer (DisconnectPeerRequest) returns (DisconnectPeerResponse); + + /* + IsBanned returns true if the peer is banned, otherwise false. + */ + rpc IsBanned (IsBannedRequest) returns (IsBannedResponse); + + /* + GetBlockHeader returns a block header with a particular block hash. + */ + rpc GetBlockHeader (GetBlockHeaderRequest) returns (GetBlockHeaderResponse); + + /* + GetBlock returns a block with a particular block hash. + */ + rpc GetBlock (GetBlockRequest) returns (GetBlockResponse); + + /* + GetCFilter returns a compact filter from a block. + */ + rpc GetCFilter (GetCFilterRequest) returns (GetCFilterResponse); +} + +message StatusRequest { +} + +message StatusResponse { + // Indicates whether the neutrino backend is active or not. + bool active = 1; + + // Is fully synced. + bool synced = 2; + + // Best block height. + int32 block_height = 3; + + // Best block hash. + string block_hash = 4; + + // Connected peers. + repeated string peers = 5; +} + +message AddPeerRequest { + // Peer to add. + string peer_addrs = 1; +} + +message AddPeerResponse { +} + +message DisconnectPeerRequest { + // Peer to disconnect. + string peer_addrs = 1; +} + +message DisconnectPeerResponse { +} + +message IsBannedRequest { + // Peer to lookup. + string peer_addrs = 1; +} + +message IsBannedResponse { + bool banned = 1; +} + +message GetBlockHeaderRequest { + // Block hash in hex notation. + string hash = 1; +} + +message GetBlockHeaderResponse { + // The block hash (same as provided). + string hash = 1; + + // The number of confirmations. + int64 confirmations = 2; + + // The block size excluding witness data. + int64 stripped_size = 3; + + // The block size (bytes). + int64 size = 4; + + // The block weight as defined in BIP 141. + int64 weight = 5; + + // The block height or index. + int32 height = 6; + + // The block version. + int32 version = 7; + + // The block version. + string version_hex = 8; + + // The merkle root. + string merkleroot = 9; + + // The block time in seconds since epoch (Jan 1 1970 GMT). + int64 time = 10; + + // The nonce. + uint32 nonce = 11; + + // The bits in hex notation. + string bits = 12; + + // The number of transactions in the block. + int32 ntx = 13; + + // The hash of the previous block. + string previous_block_hash = 14; + + // The raw hex of the block. + bytes raw_hex = 15; +} + +message GetBlockRequest { + // Block hash in hex notation. + string hash = 1; +} + +message GetBlockResponse { + // The block hash (same as provided). + string hash = 1; + + // The number of confirmations. + int64 confirmations = 2; + + // The block size excluding witness data. + int64 stripped_size = 3; + + // The block size (bytes). + int64 size = 4; + + // The block weight as defined in BIP 141. + int64 weight = 5; + + // The block height or index. + int32 height = 6; + + // The block version. + int32 version = 7; + + // The block version. + string version_hex = 8; + + // The merkle root. + string merkleroot = 9; + + // List of transaction ids. + repeated string tx = 10; + + // The block time in seconds since epoch (Jan 1 1970 GMT). + int64 time = 11; + + // The nonce. + uint32 nonce = 12; + + // The bits in hex notation. + string bits = 13; + + // The number of transactions in the block. + int32 ntx = 14; + + // The hash of the previous block. + string previous_block_hash = 15; + + // The raw hex of the block. + bytes raw_hex = 16; +} + +message GetCFilterRequest { + // Block hash in hex notation. + string hash = 1; +} + +message GetCFilterResponse { + // GCS filter. + bytes filter = 1; +} \ No newline at end of file diff --git a/lnrpc/neutrinorpc/neutrino.swagger.json b/lnrpc/neutrinorpc/neutrino.swagger.json new file mode 100644 index 0000000000..5be08ef010 --- /dev/null +++ b/lnrpc/neutrinorpc/neutrino.swagger.json @@ -0,0 +1,497 @@ +{ + "swagger": "2.0", + "info": { + "title": "neutrinorpc/neutrino.proto", + "version": "version not set" + }, + "tags": [ + { + "name": "NeutrinoKit" + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": { + "/v2/neutrino/addpeer": { + "post": { + "summary": "AddPeer adds a new peer that has already been connected to the server.", + "operationId": "NeutrinoKit_AddPeer", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/neutrinorpcAddPeerResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/neutrinorpcAddPeerRequest" + } + } + ], + "tags": [ + "NeutrinoKit" + ] + } + }, + "/v2/neutrino/block/{hash}": { + "get": { + "summary": "GetBlock returns a block with a particular block hash.", + "operationId": "NeutrinoKit_GetBlock", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/neutrinorpcGetBlockResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "hash", + "description": "Block hash in hex notation.", + "in": "path", + "required": true, + "type": "string" + } + ], + "tags": [ + "NeutrinoKit" + ] + } + }, + "/v2/neutrino/blockheader/{hash}": { + "get": { + "summary": "GetBlockHeader returns a block header with a particular block hash.", + "operationId": "NeutrinoKit_GetBlockHeader", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/neutrinorpcGetBlockHeaderResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "hash", + "description": "Block hash in hex notation.", + "in": "path", + "required": true, + "type": "string" + } + ], + "tags": [ + "NeutrinoKit" + ] + } + }, + "/v2/neutrino/cfilter/{hash}": { + "get": { + "summary": "GetCFilter returns a compact filter from a block.", + "operationId": "NeutrinoKit_GetCFilter", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/neutrinorpcGetCFilterResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "hash", + "description": "Block hash in hex notation.", + "in": "path", + "required": true, + "type": "string" + } + ], + "tags": [ + "NeutrinoKit" + ] + } + }, + "/v2/neutrino/disconnect": { + "post": { + "summary": "DisconnectPeer disconnects a peer by target address. Both outbound and\ninbound nodes will be searched for the target node. An error message will\nbe returned if the peer was not found.", + "operationId": "NeutrinoKit_DisconnectPeer", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/neutrinorpcDisconnectPeerResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/neutrinorpcDisconnectPeerRequest" + } + } + ], + "tags": [ + "NeutrinoKit" + ] + } + }, + "/v2/neutrino/isbanned": { + "get": { + "summary": "IsBanned returns true if the peer is banned, otherwise false.", + "operationId": "NeutrinoKit_IsBanned", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/neutrinorpcIsBannedResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "peer_addrs", + "description": "Peer to lookup.", + "in": "query", + "required": false, + "type": "string" + } + ], + "tags": [ + "NeutrinoKit" + ] + } + }, + "/v2/neutrino/status": { + "get": { + "summary": "Status returns the status of the light client neutrino instance,\nalong with height and hash of the best block, and a list of connected\npeers.", + "operationId": "NeutrinoKit_Status", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/neutrinorpcStatusResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "tags": [ + "NeutrinoKit" + ] + } + } + }, + "definitions": { + "neutrinorpcAddPeerRequest": { + "type": "object", + "properties": { + "peer_addrs": { + "type": "string", + "description": "Peer to add." + } + } + }, + "neutrinorpcAddPeerResponse": { + "type": "object" + }, + "neutrinorpcDisconnectPeerRequest": { + "type": "object", + "properties": { + "peer_addrs": { + "type": "string", + "description": "Peer to disconnect." + } + } + }, + "neutrinorpcDisconnectPeerResponse": { + "type": "object" + }, + "neutrinorpcGetBlockHeaderResponse": { + "type": "object", + "properties": { + "hash": { + "type": "string", + "description": "The block hash (same as provided)." + }, + "confirmations": { + "type": "string", + "format": "int64", + "description": "The number of confirmations." + }, + "stripped_size": { + "type": "string", + "format": "int64", + "description": "The block size excluding witness data." + }, + "size": { + "type": "string", + "format": "int64", + "description": "The block size (bytes)." + }, + "weight": { + "type": "string", + "format": "int64", + "description": "The block weight as defined in BIP 141." + }, + "height": { + "type": "integer", + "format": "int32", + "description": "The block height or index." + }, + "version": { + "type": "integer", + "format": "int32", + "description": "The block version." + }, + "version_hex": { + "type": "string", + "description": "The block version." + }, + "merkleroot": { + "type": "string", + "description": "The merkle root." + }, + "time": { + "type": "string", + "format": "int64", + "description": "The block time in seconds since epoch (Jan 1 1970 GMT)." + }, + "nonce": { + "type": "integer", + "format": "int64", + "description": "The nonce." + }, + "bits": { + "type": "string", + "description": "The bits in hex notation." + }, + "ntx": { + "type": "integer", + "format": "int32", + "description": "The number of transactions in the block." + }, + "previous_block_hash": { + "type": "string", + "description": "The hash of the previous block." + }, + "raw_hex": { + "type": "string", + "format": "byte", + "description": "The raw hex of the block." + } + } + }, + "neutrinorpcGetBlockResponse": { + "type": "object", + "properties": { + "hash": { + "type": "string", + "description": "The block hash (same as provided)." + }, + "confirmations": { + "type": "string", + "format": "int64", + "description": "The number of confirmations." + }, + "stripped_size": { + "type": "string", + "format": "int64", + "description": "The block size excluding witness data." + }, + "size": { + "type": "string", + "format": "int64", + "description": "The block size (bytes)." + }, + "weight": { + "type": "string", + "format": "int64", + "description": "The block weight as defined in BIP 141." + }, + "height": { + "type": "integer", + "format": "int32", + "description": "The block height or index." + }, + "version": { + "type": "integer", + "format": "int32", + "description": "The block version." + }, + "version_hex": { + "type": "string", + "description": "The block version." + }, + "merkleroot": { + "type": "string", + "description": "The merkle root." + }, + "tx": { + "type": "array", + "items": { + "type": "string" + }, + "description": "List of transaction ids." + }, + "time": { + "type": "string", + "format": "int64", + "description": "The block time in seconds since epoch (Jan 1 1970 GMT)." + }, + "nonce": { + "type": "integer", + "format": "int64", + "description": "The nonce." + }, + "bits": { + "type": "string", + "description": "The bits in hex notation." + }, + "ntx": { + "type": "integer", + "format": "int32", + "description": "The number of transactions in the block." + }, + "previous_block_hash": { + "type": "string", + "description": "The hash of the previous block." + }, + "raw_hex": { + "type": "string", + "format": "byte", + "description": "The raw hex of the block." + } + } + }, + "neutrinorpcGetCFilterResponse": { + "type": "object", + "properties": { + "filter": { + "type": "string", + "format": "byte", + "description": "GCS filter." + } + } + }, + "neutrinorpcIsBannedResponse": { + "type": "object", + "properties": { + "banned": { + "type": "boolean" + } + } + }, + "neutrinorpcStatusResponse": { + "type": "object", + "properties": { + "active": { + "type": "boolean", + "description": "Indicates whether the neutrino backend is active or not." + }, + "synced": { + "type": "boolean", + "description": "Is fully synced." + }, + "block_height": { + "type": "integer", + "format": "int32", + "description": "Best block height." + }, + "block_hash": { + "type": "string", + "description": "Best block hash." + }, + "peers": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Connected peers." + } + } + }, + "protobufAny": { + "type": "object", + "properties": { + "type_url": { + "type": "string" + }, + "value": { + "type": "string", + "format": "byte" + } + } + }, + "rpcStatus": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "$ref": "#/definitions/protobufAny" + } + } + } + } + } +} diff --git a/lnrpc/neutrinorpc/neutrino.yaml b/lnrpc/neutrinorpc/neutrino.yaml new file mode 100644 index 0000000000..d8547b7e57 --- /dev/null +++ b/lnrpc/neutrinorpc/neutrino.yaml @@ -0,0 +1,21 @@ +type: google.api.Service +config_version: 3 + +http: + rules: + - selector: neutrinorpc.NeutrinoKit.Status + get: "/v2/neutrino/status" + - selector: neutrinorpc.NeutrinoKit.AddPeer + post: "/v2/neutrino/addpeer" + body: "*" + - selector: neutrinorpc.NeutrinoKit.DisconnectPeer + post: "/v2/neutrino/disconnect" + body: "*" + - selector: neutrinorpc.NeutrinoKit.IsBanned + get: "/v2/neutrino/isbanned" + - selector: neutrinorpc.NeutrinoKit.GetBlock + get: "/v2/neutrino/block/{hash}" + - selector: neutrinorpc.NeutrinoKit.GetBlockHeader + get: "/v2/neutrino/blockheader/{hash}" + - selector: neutrinorpc.NeutrinoKit.GetCFilter + get: "/v2/neutrino/cfilter/{hash}" \ No newline at end of file diff --git a/lnrpc/neutrinorpc/neutrino_grpc.pb.go b/lnrpc/neutrinorpc/neutrino_grpc.pb.go new file mode 100644 index 0000000000..2003cfb528 --- /dev/null +++ b/lnrpc/neutrinorpc/neutrino_grpc.pb.go @@ -0,0 +1,353 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. + +package neutrinorpc + +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 + +// NeutrinoKitClient is the client API for NeutrinoKit 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 NeutrinoKitClient interface { + // + //Status returns the status of the light client neutrino instance, + //along with height and hash of the best block, and a list of connected + //peers. + Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error) + // + //AddPeer adds a new peer that has already been connected to the server. + AddPeer(ctx context.Context, in *AddPeerRequest, opts ...grpc.CallOption) (*AddPeerResponse, error) + // + //DisconnectPeer disconnects a peer by target address. Both outbound and + //inbound nodes will be searched for the target node. An error message will + //be returned if the peer was not found. + DisconnectPeer(ctx context.Context, in *DisconnectPeerRequest, opts ...grpc.CallOption) (*DisconnectPeerResponse, error) + // + //IsBanned returns true if the peer is banned, otherwise false. + IsBanned(ctx context.Context, in *IsBannedRequest, opts ...grpc.CallOption) (*IsBannedResponse, error) + // + //GetBlockHeader returns a block header with a particular block hash. + GetBlockHeader(ctx context.Context, in *GetBlockHeaderRequest, opts ...grpc.CallOption) (*GetBlockHeaderResponse, error) + // + //GetBlock returns a block with a particular block hash. + GetBlock(ctx context.Context, in *GetBlockRequest, opts ...grpc.CallOption) (*GetBlockResponse, error) + // + //GetCFilter returns a compact filter from a block. + GetCFilter(ctx context.Context, in *GetCFilterRequest, opts ...grpc.CallOption) (*GetCFilterResponse, error) +} + +type neutrinoKitClient struct { + cc grpc.ClientConnInterface +} + +func NewNeutrinoKitClient(cc grpc.ClientConnInterface) NeutrinoKitClient { + return &neutrinoKitClient{cc} +} + +func (c *neutrinoKitClient) Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error) { + out := new(StatusResponse) + err := c.cc.Invoke(ctx, "/neutrinorpc.NeutrinoKit/Status", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *neutrinoKitClient) AddPeer(ctx context.Context, in *AddPeerRequest, opts ...grpc.CallOption) (*AddPeerResponse, error) { + out := new(AddPeerResponse) + err := c.cc.Invoke(ctx, "/neutrinorpc.NeutrinoKit/AddPeer", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *neutrinoKitClient) DisconnectPeer(ctx context.Context, in *DisconnectPeerRequest, opts ...grpc.CallOption) (*DisconnectPeerResponse, error) { + out := new(DisconnectPeerResponse) + err := c.cc.Invoke(ctx, "/neutrinorpc.NeutrinoKit/DisconnectPeer", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *neutrinoKitClient) IsBanned(ctx context.Context, in *IsBannedRequest, opts ...grpc.CallOption) (*IsBannedResponse, error) { + out := new(IsBannedResponse) + err := c.cc.Invoke(ctx, "/neutrinorpc.NeutrinoKit/IsBanned", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *neutrinoKitClient) GetBlockHeader(ctx context.Context, in *GetBlockHeaderRequest, opts ...grpc.CallOption) (*GetBlockHeaderResponse, error) { + out := new(GetBlockHeaderResponse) + err := c.cc.Invoke(ctx, "/neutrinorpc.NeutrinoKit/GetBlockHeader", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *neutrinoKitClient) GetBlock(ctx context.Context, in *GetBlockRequest, opts ...grpc.CallOption) (*GetBlockResponse, error) { + out := new(GetBlockResponse) + err := c.cc.Invoke(ctx, "/neutrinorpc.NeutrinoKit/GetBlock", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *neutrinoKitClient) GetCFilter(ctx context.Context, in *GetCFilterRequest, opts ...grpc.CallOption) (*GetCFilterResponse, error) { + out := new(GetCFilterResponse) + err := c.cc.Invoke(ctx, "/neutrinorpc.NeutrinoKit/GetCFilter", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// NeutrinoKitServer is the server API for NeutrinoKit service. +// All implementations must embed UnimplementedNeutrinoKitServer +// for forward compatibility +type NeutrinoKitServer interface { + // + //Status returns the status of the light client neutrino instance, + //along with height and hash of the best block, and a list of connected + //peers. + Status(context.Context, *StatusRequest) (*StatusResponse, error) + // + //AddPeer adds a new peer that has already been connected to the server. + AddPeer(context.Context, *AddPeerRequest) (*AddPeerResponse, error) + // + //DisconnectPeer disconnects a peer by target address. Both outbound and + //inbound nodes will be searched for the target node. An error message will + //be returned if the peer was not found. + DisconnectPeer(context.Context, *DisconnectPeerRequest) (*DisconnectPeerResponse, error) + // + //IsBanned returns true if the peer is banned, otherwise false. + IsBanned(context.Context, *IsBannedRequest) (*IsBannedResponse, error) + // + //GetBlockHeader returns a block header with a particular block hash. + GetBlockHeader(context.Context, *GetBlockHeaderRequest) (*GetBlockHeaderResponse, error) + // + //GetBlock returns a block with a particular block hash. + GetBlock(context.Context, *GetBlockRequest) (*GetBlockResponse, error) + // + //GetCFilter returns a compact filter from a block. + GetCFilter(context.Context, *GetCFilterRequest) (*GetCFilterResponse, error) + mustEmbedUnimplementedNeutrinoKitServer() +} + +// UnimplementedNeutrinoKitServer must be embedded to have forward compatible implementations. +type UnimplementedNeutrinoKitServer struct { +} + +func (UnimplementedNeutrinoKitServer) Status(context.Context, *StatusRequest) (*StatusResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Status not implemented") +} +func (UnimplementedNeutrinoKitServer) AddPeer(context.Context, *AddPeerRequest) (*AddPeerResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method AddPeer not implemented") +} +func (UnimplementedNeutrinoKitServer) DisconnectPeer(context.Context, *DisconnectPeerRequest) (*DisconnectPeerResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method DisconnectPeer not implemented") +} +func (UnimplementedNeutrinoKitServer) IsBanned(context.Context, *IsBannedRequest) (*IsBannedResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method IsBanned not implemented") +} +func (UnimplementedNeutrinoKitServer) GetBlockHeader(context.Context, *GetBlockHeaderRequest) (*GetBlockHeaderResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetBlockHeader not implemented") +} +func (UnimplementedNeutrinoKitServer) GetBlock(context.Context, *GetBlockRequest) (*GetBlockResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetBlock not implemented") +} +func (UnimplementedNeutrinoKitServer) GetCFilter(context.Context, *GetCFilterRequest) (*GetCFilterResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetCFilter not implemented") +} +func (UnimplementedNeutrinoKitServer) mustEmbedUnimplementedNeutrinoKitServer() {} + +// UnsafeNeutrinoKitServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to NeutrinoKitServer will +// result in compilation errors. +type UnsafeNeutrinoKitServer interface { + mustEmbedUnimplementedNeutrinoKitServer() +} + +func RegisterNeutrinoKitServer(s grpc.ServiceRegistrar, srv NeutrinoKitServer) { + s.RegisterService(&NeutrinoKit_ServiceDesc, srv) +} + +func _NeutrinoKit_Status_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(StatusRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NeutrinoKitServer).Status(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/neutrinorpc.NeutrinoKit/Status", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NeutrinoKitServer).Status(ctx, req.(*StatusRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _NeutrinoKit_AddPeer_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AddPeerRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NeutrinoKitServer).AddPeer(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/neutrinorpc.NeutrinoKit/AddPeer", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NeutrinoKitServer).AddPeer(ctx, req.(*AddPeerRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _NeutrinoKit_DisconnectPeer_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DisconnectPeerRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NeutrinoKitServer).DisconnectPeer(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/neutrinorpc.NeutrinoKit/DisconnectPeer", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NeutrinoKitServer).DisconnectPeer(ctx, req.(*DisconnectPeerRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _NeutrinoKit_IsBanned_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(IsBannedRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NeutrinoKitServer).IsBanned(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/neutrinorpc.NeutrinoKit/IsBanned", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NeutrinoKitServer).IsBanned(ctx, req.(*IsBannedRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _NeutrinoKit_GetBlockHeader_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetBlockHeaderRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NeutrinoKitServer).GetBlockHeader(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/neutrinorpc.NeutrinoKit/GetBlockHeader", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NeutrinoKitServer).GetBlockHeader(ctx, req.(*GetBlockHeaderRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _NeutrinoKit_GetBlock_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetBlockRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NeutrinoKitServer).GetBlock(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/neutrinorpc.NeutrinoKit/GetBlock", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NeutrinoKitServer).GetBlock(ctx, req.(*GetBlockRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _NeutrinoKit_GetCFilter_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetCFilterRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NeutrinoKitServer).GetCFilter(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/neutrinorpc.NeutrinoKit/GetCFilter", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NeutrinoKitServer).GetCFilter(ctx, req.(*GetCFilterRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// NeutrinoKit_ServiceDesc is the grpc.ServiceDesc for NeutrinoKit service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var NeutrinoKit_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "neutrinorpc.NeutrinoKit", + HandlerType: (*NeutrinoKitServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Status", + Handler: _NeutrinoKit_Status_Handler, + }, + { + MethodName: "AddPeer", + Handler: _NeutrinoKit_AddPeer_Handler, + }, + { + MethodName: "DisconnectPeer", + Handler: _NeutrinoKit_DisconnectPeer_Handler, + }, + { + MethodName: "IsBanned", + Handler: _NeutrinoKit_IsBanned_Handler, + }, + { + MethodName: "GetBlockHeader", + Handler: _NeutrinoKit_GetBlockHeader_Handler, + }, + { + MethodName: "GetBlock", + Handler: _NeutrinoKit_GetBlock_Handler, + }, + { + MethodName: "GetCFilter", + Handler: _NeutrinoKit_GetCFilter_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "neutrinorpc/neutrino.proto", +} diff --git a/lnrpc/neutrinorpc/neutrino_server.go b/lnrpc/neutrinorpc/neutrino_server.go new file mode 100644 index 0000000000..fdfbe64c5f --- /dev/null +++ b/lnrpc/neutrinorpc/neutrino_server.go @@ -0,0 +1,425 @@ +//go:build neutrinorpc +// +build neutrinorpc + +package neutrinorpc + +import ( + "context" + "errors" + "fmt" + + "google.golang.org/grpc" + "gopkg.in/macaroon-bakery.v2/bakery" + + "github.com/btcsuite/btcd/chaincfg/chainhash" + + "github.com/btcsuite/btcd/blockchain" + "github.com/btcsuite/btcd/wire" + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + + "github.com/lightningnetwork/lnd/lnrpc" +) + +const ( + // subServerName is the name of the sub rpc server. We'll use this name + // to register ourselves, and we also require that the main + // SubServerConfigDispatcher instance recognize it as the name of our + // RPC service. + subServerName = "NeutrinoKitRPC" +) + +var ( + // macPermissions maps RPC calls to the permissions they require. + macPermissions = map[string][]bakery.Op{ + "/neutrinorpc.NeutrinoKit/Status": {{ + Entity: "info", + Action: "read", + }}, + "/neutrinorpc.NeutrinoKit/AddPeer": {{ + Entity: "peers", + Action: "write", + }}, + "/neutrinorpc.NeutrinoKit/DisconnectPeer": {{ + Entity: "peers", + Action: "write", + }}, + "/neutrinorpc.NeutrinoKit/IsBanned": {{ + Entity: "info", + Action: "read", + }}, + "/neutrinorpc.NeutrinoKit/GetBlock": {{ + Entity: "onchain", + Action: "read", + }}, + "/neutrinorpc.NeutrinoKit/GetBlockHeader": {{ + Entity: "onchain", + Action: "read", + }}, + "/neutrinorpc.NeutrinoKit/GetCFilter": {{ + Entity: "onchain", + Action: "read", + }}, + } + + // ErrNeutrinoNotActive is an error returned when there is no running + // neutrino light client instance. + ErrNeutrinoNotActive = errors.New("no active neutrino instance") +) + +// ServerShell is a shell struct holding a reference to the actual sub-server. +// It is used to register the gRPC sub-server with the root server before we +// have the necessary dependencies to populate the actual sub-server. +type ServerShell struct { + NeutrinoKitServer +} + +// Server is a sub-server of the main RPC server: the neutrino RPC. This sub +// RPC server allows external callers to access the status of the neutrino +// currently active within lnd, as well as configuring it at runtime. +type Server struct { + // Required by the grpc-gateway/v2 library for forward compatibility. + // Must be after the atomically used variables to not break struct + // alignment. + UnimplementedNeutrinoKitServer + + cfg *Config +} + +// A compile time check to ensure that NeutrinoKit fully implements the +// NeutrinoServer gRPC service. +var _ NeutrinoKitServer = (*Server)(nil) + +// New returns a new instance of the neutrinorpc Neutrino sub-server. We also +// return the set of permissions for the macaroons that we may create within +// this method. If the macaroons we need aren't found in the filepath, then +// we'll create them on start up. If we're unable to locate, or create the +// macaroons we need, then we'll return with an error. +func New(cfg *Config) (*Server, lnrpc.MacaroonPerms, error) { + // We don't create any new macaroons for this subserver, instead reuse + // existing onchain/offchain permissions. + server := &Server{ + cfg: cfg, + } + + return server, macPermissions, nil +} + +// Start launches any helper goroutines required for the Server to function. +// +// NOTE: This is part of the lnrpc.SubServer interface. +func (s *Server) Start() error { + return nil +} + +// Stop signals any active goroutines for a graceful closure. +// +// NOTE: This is part of the lnrpc.SubServer interface. +func (s *Server) Stop() error { + return nil +} + +// Name returns a unique string representation of the sub-server. This can be +// used to identify the sub-server and also de-duplicate them. +// +// NOTE: This is part of the lnrpc.SubServer interface. +func (s *Server) Name() string { + return subServerName +} + +// RegisterWithRootServer will be called by the root gRPC server to direct a +// sub RPC server to register itself with the main gRPC root server. Until this +// is called, each sub-server won't be able to have +// requests routed towards it. +// +// NOTE: This is part of the lnrpc.GrpcHandler interface. +func (r *ServerShell) RegisterWithRootServer(grpcServer *grpc.Server) error { + // We make sure that we register it with the main gRPC server to ensure + // all our methods are routed properly. + RegisterNeutrinoKitServer(grpcServer, r) + + log.Debugf("Neutrino RPC server successfully register with root " + + "gRPC server") + + return nil +} + +// RegisterWithRestServer will be called by the root REST mux to direct a sub +// RPC server to register itself with the main REST mux server. Until this is +// called, each sub-server won't be able to have requests routed towards it. +// +// NOTE: This is part of the lnrpc.GrpcHandler interface. +func (r *ServerShell) RegisterWithRestServer(ctx context.Context, + mux *runtime.ServeMux, dest string, opts []grpc.DialOption) error { + + // We make sure that we register it with the main REST server to ensure + // all our methods are routed properly. + err := RegisterNeutrinoKitHandlerFromEndpoint(ctx, mux, dest, opts) + if err != nil { + log.Errorf("Could not register Neutrino REST server "+ + "with root REST server: %v", err) + return err + } + + log.Debugf("Neutrino REST server successfully registered with " + + "root REST server") + return nil +} + +// CreateSubServer populates the subserver's dependencies using the passed +// SubServerConfigDispatcher. This method should fully initialize the +// sub-server instance, making it ready for action. It returns the macaroon +// permissions that the sub-server wishes to pass on to the root server for all +// methods routed towards it. +// +// NOTE: This is part of the lnrpc.GrpcHandler interface. +func (r *ServerShell) CreateSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( + lnrpc.SubServer, lnrpc.MacaroonPerms, error) { + + subServer, macPermissions, err := createNewSubServer(configRegistry) + if err != nil { + return nil, nil, err + } + + r.NeutrinoKitServer = subServer + return subServer, macPermissions, nil +} + +// Status returns the current status, best block height and connected peers +// of the neutrino node. +// +// NOTE: Part of the NeutrinoServer interface. +func (s *Server) Status(ctx context.Context, + in *StatusRequest) (*StatusResponse, error) { + + if s.cfg.NeutrinoCS == nil { + return &StatusResponse{}, nil + } + + bestBlock, err := s.cfg.NeutrinoCS.BestBlock() + if err != nil { + return &StatusResponse{}, + fmt.Errorf("could not get best block: %v", err) + } + + peers := s.cfg.NeutrinoCS.Peers() + var Peers = make([]string, len(peers)) + for i, p := range peers { + Peers[i] = p.Addr() + } + + return &StatusResponse{ + Active: s.cfg.NeutrinoCS != nil, + BlockHeight: bestBlock.Height, + BlockHash: bestBlock.Hash.String(), + Synced: s.cfg.NeutrinoCS.IsCurrent(), + Peers: Peers, + }, nil +} + +// AddPeer adds a new peer that has already been connected to the server. +// +// NOTE: Part of the NeutrinoKitServer interface. +func (s *Server) AddPeer(ctx context.Context, + in *AddPeerRequest) (*AddPeerResponse, error) { + + if s.cfg.NeutrinoCS == nil { + return &AddPeerResponse{}, ErrNeutrinoNotActive + } + + peer := s.cfg.NeutrinoCS.PeerByAddr(in.PeerAddrs) + if peer == nil { + return &AddPeerResponse{}, + fmt.Errorf("could not found peer: %s", in.PeerAddrs) + } + s.cfg.NeutrinoCS.AddPeer(peer) + + return &AddPeerResponse{}, nil +} + +// DisconnectPeer disconnects a peer by target address. Both outbound and +// inbound nodes will be searched for the target node. An error message will +// be returned if the peer was not found. +// +// NOTE: Part of the NeutrinoKitServer interface. +func (s *Server) DisconnectPeer(ctx context.Context, + in *DisconnectPeerRequest) (*DisconnectPeerResponse, error) { + + if s.cfg.NeutrinoCS == nil { + return &DisconnectPeerResponse{}, ErrNeutrinoNotActive + } + + peer := s.cfg.NeutrinoCS.PeerByAddr(in.PeerAddrs) + if peer == nil { + return &DisconnectPeerResponse{}, + fmt.Errorf("could not found peer: %s", in.PeerAddrs) + } + + err := s.cfg.NeutrinoCS.DisconnectNodeByAddr(peer.Addr()) + if err != nil { + return &DisconnectPeerResponse{}, err + } + + return &DisconnectPeerResponse{}, nil +} + +// IsBanned returns true if the peer is banned, otherwise false. +// +// NOTE: Part of the NeutrinoKitServer interface. +func (s *Server) IsBanned(ctx context.Context, + in *IsBannedRequest) (*IsBannedResponse, error) { + + if s.cfg.NeutrinoCS == nil { + return &IsBannedResponse{}, ErrNeutrinoNotActive + } + + return &IsBannedResponse{ + Banned: s.cfg.NeutrinoCS.IsBanned(in.PeerAddrs), + }, nil +} + +// GetBlockHeader returns a block header with a particular block hash. If the +// block header is found in the cache, it will be returned immediately. +// Otherwise a block will be requested from the network, one peer at a time, +// until one answers. +// +// NOTE: Part of the NeutrinoKitServer interface. +func (s *Server) GetBlockHeader(ctx context.Context, + in *GetBlockHeaderRequest) (*GetBlockHeaderResponse, error) { + + if s.cfg.NeutrinoCS == nil { + return &GetBlockHeaderResponse{}, ErrNeutrinoNotActive + } + + var hash chainhash.Hash + if err := chainhash.Decode(&hash, in.Hash); err != nil { + return &GetBlockHeaderResponse{}, err + } + + resp, err := s.getBlock(hash) + if err != nil { + return &GetBlockHeaderResponse{}, err + } + + return &GetBlockHeaderResponse{ + Hash: resp.Hash, + Confirmations: resp.Confirmations, + StrippedSize: resp.StrippedSize, + Size: resp.Size, + Weight: resp.Weight, + Height: resp.Height, + Version: resp.Version, + VersionHex: resp.VersionHex, + Merkleroot: resp.Merkleroot, + Time: resp.Time, + Nonce: resp.Nonce, + Bits: resp.Bits, + Ntx: resp.Ntx, + PreviousBlockHash: resp.PreviousBlockHash, + RawHex: resp.RawHex, + }, nil +} + +// GetBlock returns a block with a particular block hash. If the block is +// found in the cache, it will be returned immediately. Otherwise a block will +// be requested from the network, one peer at a time, until one answers. +// +// NOTE: Part of the NeutrinoKitServer interface. +func (s *Server) GetBlock(ctx context.Context, + in *GetBlockRequest) (*GetBlockResponse, error) { + + if s.cfg.NeutrinoCS == nil { + return &GetBlockResponse{}, ErrNeutrinoNotActive + } + + var hash chainhash.Hash + if err := chainhash.Decode(&hash, in.Hash); err != nil { + return &GetBlockResponse{}, err + } + + return s.getBlock(hash) +} + +// GetCFilter returns a compact filter of a particular block. +// If found, only regular filters will be returned. +// +// NOTE: Part of the NeutrinoKitServer interface. +func (s *Server) GetCFilter(ctx context.Context, + in *GetCFilterRequest) (*GetCFilterResponse, error) { + + if s.cfg.NeutrinoCS == nil { + return &GetCFilterResponse{}, ErrNeutrinoNotActive + } + + var hash chainhash.Hash + if err := chainhash.Decode(&hash, in.Hash); err != nil { + return &GetCFilterResponse{}, err + } + + // GetCFilter returns a compact filter from the database. If it is missing, + // it requests the compact filter from the network. + filter, err := s.cfg.NeutrinoCS.GetCFilter(hash, wire.GCSFilterRegular) + if err != nil { + return &GetCFilterResponse{}, err + } + + filterlBytes, err := filter.Bytes() + if err != nil { + return &GetCFilterResponse{}, err + } + + return &GetCFilterResponse{Filter: filterlBytes}, nil +} + +func (s *Server) getBlock(hash chainhash.Hash) (*GetBlockResponse, error) { + block, err := s.cfg.NeutrinoCS.GetBlock(hash) + if err != nil { + return &GetBlockResponse{}, err + } + + header, _, err := s.cfg.NeutrinoCS.BlockHeaders.FetchHeader(&hash) + if err != nil { + return &GetBlockResponse{}, err + } + + blockData, err := block.Bytes() + if err != nil { + return &GetBlockResponse{}, err + } + + strippedData, err := block.BytesNoWitness() + if err != nil { + return &GetBlockResponse{}, err + } + + bestBlock, err := s.cfg.NeutrinoCS.BestBlock() + if err != nil { + return &GetBlockResponse{}, err + } + + // Convert txids to a string array. + transactions := block.Transactions() + tx := make([]string, len(transactions)) + for i := range transactions { + tx[i] = transactions[i].Hash().String() + } + + return &GetBlockResponse{ + Hash: block.Hash().String(), + Confirmations: int64(1 + bestBlock.Height - block.Height()), + StrippedSize: int64(len(strippedData)), + Size: int64(len(blockData)), + Weight: blockchain.GetBlockWeight(block), + Height: block.Height(), + Version: header.Version, + VersionHex: fmt.Sprintf("%0x", header.Version), + Merkleroot: header.MerkleRoot.String(), + Tx: tx, + Time: header.Timestamp.Unix(), + Nonce: header.Nonce, + // Format bits as a hex. + Bits: fmt.Sprintf("%0x", header.Bits), + Ntx: int32(len(block.Transactions())), + PreviousBlockHash: header.PrevBlock.String(), + RawHex: blockData, + }, nil +} diff --git a/lnrpc/neutrinorpc/neutrinokit.pb.json.go b/lnrpc/neutrinorpc/neutrinokit.pb.json.go new file mode 100644 index 0000000000..2b9e2664ea --- /dev/null +++ b/lnrpc/neutrinorpc/neutrinokit.pb.json.go @@ -0,0 +1,200 @@ +// Code generated by falafel 0.9.1. DO NOT EDIT. +// source: neutrino.proto + +// +build js + +package neutrinorpc + +import ( + "context" + + gateway "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "google.golang.org/grpc" + "google.golang.org/protobuf/encoding/protojson" +) + +func RegisterNeutrinoKitJSONCallbacks(registry map[string]func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error))) { + + marshaler := &gateway.JSONPb{ + MarshalOptions: protojson.MarshalOptions{ + UseProtoNames: true, + EmitUnpopulated: true, + }, + } + + registry["neutrinorpc.NeutrinoKit.Status"] = func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { + + req := &StatusRequest{} + err := marshaler.Unmarshal([]byte(reqJSON), req) + if err != nil { + callback("", err) + return + } + + client := NewNeutrinoKitClient(conn) + resp, err := client.Status(ctx, req) + if err != nil { + callback("", err) + return + } + + respBytes, err := marshaler.Marshal(resp) + if err != nil { + callback("", err) + return + } + callback(string(respBytes), nil) + } + + registry["neutrinorpc.NeutrinoKit.AddPeer"] = func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { + + req := &AddPeerRequest{} + err := marshaler.Unmarshal([]byte(reqJSON), req) + if err != nil { + callback("", err) + return + } + + client := NewNeutrinoKitClient(conn) + resp, err := client.AddPeer(ctx, req) + if err != nil { + callback("", err) + return + } + + respBytes, err := marshaler.Marshal(resp) + if err != nil { + callback("", err) + return + } + callback(string(respBytes), nil) + } + + registry["neutrinorpc.NeutrinoKit.DisconnectPeer"] = func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { + + req := &DisconnectPeerRequest{} + err := marshaler.Unmarshal([]byte(reqJSON), req) + if err != nil { + callback("", err) + return + } + + client := NewNeutrinoKitClient(conn) + resp, err := client.DisconnectPeer(ctx, req) + if err != nil { + callback("", err) + return + } + + respBytes, err := marshaler.Marshal(resp) + if err != nil { + callback("", err) + return + } + callback(string(respBytes), nil) + } + + registry["neutrinorpc.NeutrinoKit.IsBanned"] = func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { + + req := &IsBannedRequest{} + err := marshaler.Unmarshal([]byte(reqJSON), req) + if err != nil { + callback("", err) + return + } + + client := NewNeutrinoKitClient(conn) + resp, err := client.IsBanned(ctx, req) + if err != nil { + callback("", err) + return + } + + respBytes, err := marshaler.Marshal(resp) + if err != nil { + callback("", err) + return + } + callback(string(respBytes), nil) + } + + registry["neutrinorpc.NeutrinoKit.GetBlockHeader"] = func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { + + req := &GetBlockHeaderRequest{} + err := marshaler.Unmarshal([]byte(reqJSON), req) + if err != nil { + callback("", err) + return + } + + client := NewNeutrinoKitClient(conn) + resp, err := client.GetBlockHeader(ctx, req) + if err != nil { + callback("", err) + return + } + + respBytes, err := marshaler.Marshal(resp) + if err != nil { + callback("", err) + return + } + callback(string(respBytes), nil) + } + + registry["neutrinorpc.NeutrinoKit.GetBlock"] = func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { + + req := &GetBlockRequest{} + err := marshaler.Unmarshal([]byte(reqJSON), req) + if err != nil { + callback("", err) + return + } + + client := NewNeutrinoKitClient(conn) + resp, err := client.GetBlock(ctx, req) + if err != nil { + callback("", err) + return + } + + respBytes, err := marshaler.Marshal(resp) + if err != nil { + callback("", err) + return + } + callback(string(respBytes), nil) + } + + registry["neutrinorpc.NeutrinoKit.GetCFilter"] = func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { + + req := &GetCFilterRequest{} + err := marshaler.Unmarshal([]byte(reqJSON), req) + if err != nil { + callback("", err) + return + } + + client := NewNeutrinoKitClient(conn) + resp, err := client.GetCFilter(ctx, req) + if err != nil { + callback("", err) + return + } + + respBytes, err := marshaler.Marshal(resp) + if err != nil { + callback("", err) + return + } + callback(string(respBytes), nil) + } +} diff --git a/lnrpc/peersrpc/config_active.go b/lnrpc/peersrpc/config_active.go new file mode 100644 index 0000000000..860e7627e1 --- /dev/null +++ b/lnrpc/peersrpc/config_active.go @@ -0,0 +1,29 @@ +//go:build peersrpc +// +build peersrpc + +package peersrpc + +import ( + "net" + + "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/netann" +) + +// Config is the primary configuration struct for the peers RPC subserver. +// It contains all the items required for the server to carry out its duties. +// The fields with struct tags are meant to be parsed as normal configuration +// options, while if able to be populated, the latter fields MUST also be +// specified. +type Config struct { + // GetNodeAnnouncement is used to send our retrieve the current + // node announcement information. + GetNodeAnnouncement func() (lnwire.NodeAnnouncement, error) + + // ParseAddr parses an address from its string format to a net.Addr. + ParseAddr func(addr string) (net.Addr, error) + + // UpdateNodeAnnouncement updates our node announcement applying the + // given NodeAnnModifiers and broadcasts the new version to the network. + UpdateNodeAnnouncement func(...netann.NodeAnnModifier) error +} diff --git a/lnrpc/peersrpc/config_default.go b/lnrpc/peersrpc/config_default.go new file mode 100644 index 0000000000..8b359a85c9 --- /dev/null +++ b/lnrpc/peersrpc/config_default.go @@ -0,0 +1,7 @@ +//go:build !peersrpc +// +build !peersrpc + +package peersrpc + +// Config is empty for non-peersrpc builds. +type Config struct{} diff --git a/lnrpc/peersrpc/driver.go b/lnrpc/peersrpc/driver.go new file mode 100644 index 0000000000..8abf3ed924 --- /dev/null +++ b/lnrpc/peersrpc/driver.go @@ -0,0 +1,55 @@ +//go:build peersrpc +// +build peersrpc + +package peersrpc + +import ( + "fmt" + + "github.com/lightningnetwork/lnd/lnrpc" +) + +// createNewSubServer is a helper method that will create the new sub server +// given the main config dispatcher method. If we're unable to find the config +// that is meant for us in the config dispatcher, then we'll exit with an +// error. +func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( + *Server, lnrpc.MacaroonPerms, error) { + + // We'll attempt to look up the config that we expect, according to our + // subServerName name. If we can't find this, then we'll exit with an + // error, as we're unable to properly initialize ourselves without this + // config. + subServerConf, ok := configRegistry.FetchConfig(subServerName) + if !ok { + return nil, nil, fmt.Errorf("unable to find config for "+ + "subserver type %s", subServerName) + } + + // Now that we've found an object mapping to our service name, we'll + // ensure that it's the type we need. + config, ok := subServerConf.(*Config) + if !ok { + return nil, nil, fmt.Errorf("wrong type of config for "+ + "subserver %s, expected %T got %T", subServerName, + &Config{}, subServerConf) + } + + return New(config) +} + +func init() { + subServer := &lnrpc.SubServerDriver{ + SubServerName: subServerName, + NewGrpcHandler: func() lnrpc.GrpcHandler { + return &ServerShell{} + }, + } + + // If the build tag is active, then we'll register ourselves as a + // sub-RPC server within the global lnrpc package namespace. + if err := lnrpc.RegisterSubServer(subServer); err != nil { + panic(fmt.Sprintf("failed to register sub server driver "+ + "'%s': %v", subServerName, err)) + } +} diff --git a/lnrpc/peersrpc/log.go b/lnrpc/peersrpc/log.go new file mode 100644 index 0000000000..3fd59789cb --- /dev/null +++ b/lnrpc/peersrpc/log.go @@ -0,0 +1,32 @@ +package peersrpc + +import ( + "github.com/btcsuite/btclog" + "github.com/lightningnetwork/lnd/build" +) + +// log is a logger that is initialized with no output filters. This means the +// package will not perform any logging by default until the caller requests +// it. +var log btclog.Logger + +// Subsystem defines the logging code for this subsystem. +const Subsystem = "PRPC" + +// The default amount of logging is none. +func init() { + UseLogger(build.NewSubLogger(Subsystem, nil)) +} + +// DisableLog disables all library log output. Logging output is disabled by +// by default until UseLogger is called. +func DisableLog() { + UseLogger(btclog.Disabled) +} + +// UseLogger uses a specified Logger to output package logging info. This +// should be used in preference to SetLogWriter if the caller is also using +// btclog. +func UseLogger(logger btclog.Logger) { + log = logger +} diff --git a/lnrpc/peersrpc/peers.pb.go b/lnrpc/peersrpc/peers.pb.go new file mode 100644 index 0000000000..a0efe7974b --- /dev/null +++ b/lnrpc/peersrpc/peers.pb.go @@ -0,0 +1,554 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.26.0 +// protoc v3.6.1 +// source: peersrpc/peers.proto + +package peersrpc + +import ( + lnrpc "github.com/lightningnetwork/lnd/lnrpc" + 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) +) + +// UpdateAction is used to determine the kind of action we are referring to. +type UpdateAction int32 + +const ( + // ADD indicates this is an "insertion" kind of action. + UpdateAction_ADD UpdateAction = 0 + // REMOVE indicates this is a "deletion" kind of action. + UpdateAction_REMOVE UpdateAction = 1 +) + +// Enum value maps for UpdateAction. +var ( + UpdateAction_name = map[int32]string{ + 0: "ADD", + 1: "REMOVE", + } + UpdateAction_value = map[string]int32{ + "ADD": 0, + "REMOVE": 1, + } +) + +func (x UpdateAction) Enum() *UpdateAction { + p := new(UpdateAction) + *p = x + return p +} + +func (x UpdateAction) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (UpdateAction) Descriptor() protoreflect.EnumDescriptor { + return file_peersrpc_peers_proto_enumTypes[0].Descriptor() +} + +func (UpdateAction) Type() protoreflect.EnumType { + return &file_peersrpc_peers_proto_enumTypes[0] +} + +func (x UpdateAction) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use UpdateAction.Descriptor instead. +func (UpdateAction) EnumDescriptor() ([]byte, []int) { + return file_peersrpc_peers_proto_rawDescGZIP(), []int{0} +} + +type FeatureSet int32 + +const ( + // + //SET_INIT identifies features that should be sent in an Init message to + //a remote peer. + FeatureSet_SET_INIT FeatureSet = 0 + // + //SET_LEGACY_GLOBAL identifies features that should be set in the legacy + //GlobalFeatures field of an Init message, which maintains backwards + //compatibility with nodes that haven't implemented flat features. + FeatureSet_SET_LEGACY_GLOBAL FeatureSet = 1 + // + //SET_NODE_ANN identifies features that should be advertised on node + //announcements. + FeatureSet_SET_NODE_ANN FeatureSet = 2 + // + //SET_INVOICE identifies features that should be advertised on invoices + //generated by the daemon. + FeatureSet_SET_INVOICE FeatureSet = 3 + // + //SET_INVOICE_AMP identifies the features that should be advertised on + //AMP invoices generated by the daemon. + FeatureSet_SET_INVOICE_AMP FeatureSet = 4 +) + +// Enum value maps for FeatureSet. +var ( + FeatureSet_name = map[int32]string{ + 0: "SET_INIT", + 1: "SET_LEGACY_GLOBAL", + 2: "SET_NODE_ANN", + 3: "SET_INVOICE", + 4: "SET_INVOICE_AMP", + } + FeatureSet_value = map[string]int32{ + "SET_INIT": 0, + "SET_LEGACY_GLOBAL": 1, + "SET_NODE_ANN": 2, + "SET_INVOICE": 3, + "SET_INVOICE_AMP": 4, + } +) + +func (x FeatureSet) Enum() *FeatureSet { + p := new(FeatureSet) + *p = x + return p +} + +func (x FeatureSet) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (FeatureSet) Descriptor() protoreflect.EnumDescriptor { + return file_peersrpc_peers_proto_enumTypes[1].Descriptor() +} + +func (FeatureSet) Type() protoreflect.EnumType { + return &file_peersrpc_peers_proto_enumTypes[1] +} + +func (x FeatureSet) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use FeatureSet.Descriptor instead. +func (FeatureSet) EnumDescriptor() ([]byte, []int) { + return file_peersrpc_peers_proto_rawDescGZIP(), []int{1} +} + +type UpdateAddressAction struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Determines the kind of action. + Action UpdateAction `protobuf:"varint,1,opt,name=action,proto3,enum=peersrpc.UpdateAction" json:"action,omitempty"` + // The address used to apply the update action. + Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"` +} + +func (x *UpdateAddressAction) Reset() { + *x = UpdateAddressAction{} + if protoimpl.UnsafeEnabled { + mi := &file_peersrpc_peers_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateAddressAction) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateAddressAction) ProtoMessage() {} + +func (x *UpdateAddressAction) ProtoReflect() protoreflect.Message { + mi := &file_peersrpc_peers_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateAddressAction.ProtoReflect.Descriptor instead. +func (*UpdateAddressAction) Descriptor() ([]byte, []int) { + return file_peersrpc_peers_proto_rawDescGZIP(), []int{0} +} + +func (x *UpdateAddressAction) GetAction() UpdateAction { + if x != nil { + return x.Action + } + return UpdateAction_ADD +} + +func (x *UpdateAddressAction) GetAddress() string { + if x != nil { + return x.Address + } + return "" +} + +type UpdateFeatureAction struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Determines the kind of action. + Action UpdateAction `protobuf:"varint,1,opt,name=action,proto3,enum=peersrpc.UpdateAction" json:"action,omitempty"` + // The feature bit used to apply the update action. + FeatureBit lnrpc.FeatureBit `protobuf:"varint,2,opt,name=feature_bit,json=featureBit,proto3,enum=lnrpc.FeatureBit" json:"feature_bit,omitempty"` +} + +func (x *UpdateFeatureAction) Reset() { + *x = UpdateFeatureAction{} + if protoimpl.UnsafeEnabled { + mi := &file_peersrpc_peers_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateFeatureAction) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateFeatureAction) ProtoMessage() {} + +func (x *UpdateFeatureAction) ProtoReflect() protoreflect.Message { + mi := &file_peersrpc_peers_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateFeatureAction.ProtoReflect.Descriptor instead. +func (*UpdateFeatureAction) Descriptor() ([]byte, []int) { + return file_peersrpc_peers_proto_rawDescGZIP(), []int{1} +} + +func (x *UpdateFeatureAction) GetAction() UpdateAction { + if x != nil { + return x.Action + } + return UpdateAction_ADD +} + +func (x *UpdateFeatureAction) GetFeatureBit() lnrpc.FeatureBit { + if x != nil { + return x.FeatureBit + } + return lnrpc.FeatureBit_DATALOSS_PROTECT_REQ +} + +type NodeAnnouncementUpdateRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Set of changes for the features that the node supports. + FeatureUpdates []*UpdateFeatureAction `protobuf:"bytes,1,rep,name=feature_updates,json=featureUpdates,proto3" json:"feature_updates,omitempty"` + // Color is the node's color in hex code format. + Color string `protobuf:"bytes,2,opt,name=color,proto3" json:"color,omitempty"` + // Alias or nick name of the node. + Alias string `protobuf:"bytes,3,opt,name=alias,proto3" json:"alias,omitempty"` + // Set of changes for the node's known addresses. + AddressUpdates []*UpdateAddressAction `protobuf:"bytes,4,rep,name=address_updates,json=addressUpdates,proto3" json:"address_updates,omitempty"` +} + +func (x *NodeAnnouncementUpdateRequest) Reset() { + *x = NodeAnnouncementUpdateRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_peersrpc_peers_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *NodeAnnouncementUpdateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NodeAnnouncementUpdateRequest) ProtoMessage() {} + +func (x *NodeAnnouncementUpdateRequest) ProtoReflect() protoreflect.Message { + mi := &file_peersrpc_peers_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NodeAnnouncementUpdateRequest.ProtoReflect.Descriptor instead. +func (*NodeAnnouncementUpdateRequest) Descriptor() ([]byte, []int) { + return file_peersrpc_peers_proto_rawDescGZIP(), []int{2} +} + +func (x *NodeAnnouncementUpdateRequest) GetFeatureUpdates() []*UpdateFeatureAction { + if x != nil { + return x.FeatureUpdates + } + return nil +} + +func (x *NodeAnnouncementUpdateRequest) GetColor() string { + if x != nil { + return x.Color + } + return "" +} + +func (x *NodeAnnouncementUpdateRequest) GetAlias() string { + if x != nil { + return x.Alias + } + return "" +} + +func (x *NodeAnnouncementUpdateRequest) GetAddressUpdates() []*UpdateAddressAction { + if x != nil { + return x.AddressUpdates + } + return nil +} + +type NodeAnnouncementUpdateResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Ops []*lnrpc.Op `protobuf:"bytes,1,rep,name=ops,proto3" json:"ops,omitempty"` +} + +func (x *NodeAnnouncementUpdateResponse) Reset() { + *x = NodeAnnouncementUpdateResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_peersrpc_peers_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *NodeAnnouncementUpdateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NodeAnnouncementUpdateResponse) ProtoMessage() {} + +func (x *NodeAnnouncementUpdateResponse) ProtoReflect() protoreflect.Message { + mi := &file_peersrpc_peers_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NodeAnnouncementUpdateResponse.ProtoReflect.Descriptor instead. +func (*NodeAnnouncementUpdateResponse) Descriptor() ([]byte, []int) { + return file_peersrpc_peers_proto_rawDescGZIP(), []int{3} +} + +func (x *NodeAnnouncementUpdateResponse) GetOps() []*lnrpc.Op { + if x != nil { + return x.Ops + } + return nil +} + +var File_peersrpc_peers_proto protoreflect.FileDescriptor + +var file_peersrpc_peers_proto_rawDesc = []byte{ + 0x0a, 0x14, 0x70, 0x65, 0x65, 0x72, 0x73, 0x72, 0x70, 0x63, 0x2f, 0x70, 0x65, 0x65, 0x72, 0x73, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x70, 0x65, 0x65, 0x72, 0x73, 0x72, 0x70, 0x63, + 0x1a, 0x0f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x22, 0x5f, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2e, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x70, 0x65, 0x65, 0x72, 0x73, + 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x22, 0x79, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x65, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2e, 0x0a, 0x06, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x70, 0x65, 0x65, 0x72, + 0x73, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x32, 0x0a, 0x0b, 0x66, 0x65, 0x61, + 0x74, 0x75, 0x72, 0x65, 0x5f, 0x62, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x11, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x42, 0x69, + 0x74, 0x52, 0x0a, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x42, 0x69, 0x74, 0x22, 0xdb, 0x01, + 0x0a, 0x1d, 0x4e, 0x6f, 0x64, 0x65, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x46, 0x0a, 0x0f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x65, 0x65, 0x72, 0x73, + 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0e, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x6c, 0x6f, 0x72, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x12, 0x14, 0x0a, + 0x05, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x6c, + 0x69, 0x61, 0x73, 0x12, 0x46, 0x0a, 0x0f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x75, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, + 0x65, 0x65, 0x72, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0e, 0x61, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x22, 0x3d, 0x0a, 0x1e, 0x4e, + 0x6f, 0x64, 0x65, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1b, 0x0a, + 0x03, 0x6f, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x09, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x4f, 0x70, 0x52, 0x03, 0x6f, 0x70, 0x73, 0x2a, 0x23, 0x0a, 0x0c, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x44, + 0x44, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x52, 0x45, 0x4d, 0x4f, 0x56, 0x45, 0x10, 0x01, 0x2a, + 0x69, 0x0a, 0x0a, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x12, 0x0c, 0x0a, + 0x08, 0x53, 0x45, 0x54, 0x5f, 0x49, 0x4e, 0x49, 0x54, 0x10, 0x00, 0x12, 0x15, 0x0a, 0x11, 0x53, + 0x45, 0x54, 0x5f, 0x4c, 0x45, 0x47, 0x41, 0x43, 0x59, 0x5f, 0x47, 0x4c, 0x4f, 0x42, 0x41, 0x4c, + 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x53, 0x45, 0x54, 0x5f, 0x4e, 0x4f, 0x44, 0x45, 0x5f, 0x41, + 0x4e, 0x4e, 0x10, 0x02, 0x12, 0x0f, 0x0a, 0x0b, 0x53, 0x45, 0x54, 0x5f, 0x49, 0x4e, 0x56, 0x4f, + 0x49, 0x43, 0x45, 0x10, 0x03, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x45, 0x54, 0x5f, 0x49, 0x4e, 0x56, + 0x4f, 0x49, 0x43, 0x45, 0x5f, 0x41, 0x4d, 0x50, 0x10, 0x04, 0x32, 0x74, 0x0a, 0x05, 0x50, 0x65, + 0x65, 0x72, 0x73, 0x12, 0x6b, 0x0a, 0x16, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4e, 0x6f, 0x64, + 0x65, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x27, 0x2e, + 0x70, 0x65, 0x65, 0x72, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x41, 0x6e, 0x6e, + 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x70, 0x65, 0x65, 0x72, 0x73, 0x72, 0x70, + 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 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, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, + 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2f, 0x70, 0x65, 0x65, 0x72, 0x73, 0x72, + 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_peersrpc_peers_proto_rawDescOnce sync.Once + file_peersrpc_peers_proto_rawDescData = file_peersrpc_peers_proto_rawDesc +) + +func file_peersrpc_peers_proto_rawDescGZIP() []byte { + file_peersrpc_peers_proto_rawDescOnce.Do(func() { + file_peersrpc_peers_proto_rawDescData = protoimpl.X.CompressGZIP(file_peersrpc_peers_proto_rawDescData) + }) + return file_peersrpc_peers_proto_rawDescData +} + +var file_peersrpc_peers_proto_enumTypes = make([]protoimpl.EnumInfo, 2) +var file_peersrpc_peers_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_peersrpc_peers_proto_goTypes = []interface{}{ + (UpdateAction)(0), // 0: peersrpc.UpdateAction + (FeatureSet)(0), // 1: peersrpc.FeatureSet + (*UpdateAddressAction)(nil), // 2: peersrpc.UpdateAddressAction + (*UpdateFeatureAction)(nil), // 3: peersrpc.UpdateFeatureAction + (*NodeAnnouncementUpdateRequest)(nil), // 4: peersrpc.NodeAnnouncementUpdateRequest + (*NodeAnnouncementUpdateResponse)(nil), // 5: peersrpc.NodeAnnouncementUpdateResponse + (lnrpc.FeatureBit)(0), // 6: lnrpc.FeatureBit + (*lnrpc.Op)(nil), // 7: lnrpc.Op +} +var file_peersrpc_peers_proto_depIdxs = []int32{ + 0, // 0: peersrpc.UpdateAddressAction.action:type_name -> peersrpc.UpdateAction + 0, // 1: peersrpc.UpdateFeatureAction.action:type_name -> peersrpc.UpdateAction + 6, // 2: peersrpc.UpdateFeatureAction.feature_bit:type_name -> lnrpc.FeatureBit + 3, // 3: peersrpc.NodeAnnouncementUpdateRequest.feature_updates:type_name -> peersrpc.UpdateFeatureAction + 2, // 4: peersrpc.NodeAnnouncementUpdateRequest.address_updates:type_name -> peersrpc.UpdateAddressAction + 7, // 5: peersrpc.NodeAnnouncementUpdateResponse.ops:type_name -> lnrpc.Op + 4, // 6: peersrpc.Peers.UpdateNodeAnnouncement:input_type -> peersrpc.NodeAnnouncementUpdateRequest + 5, // 7: peersrpc.Peers.UpdateNodeAnnouncement:output_type -> peersrpc.NodeAnnouncementUpdateResponse + 7, // [7:8] is the sub-list for method output_type + 6, // [6:7] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name +} + +func init() { file_peersrpc_peers_proto_init() } +func file_peersrpc_peers_proto_init() { + if File_peersrpc_peers_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_peersrpc_peers_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateAddressAction); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_peersrpc_peers_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateFeatureAction); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_peersrpc_peers_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*NodeAnnouncementUpdateRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_peersrpc_peers_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*NodeAnnouncementUpdateResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_peersrpc_peers_proto_rawDesc, + NumEnums: 2, + NumMessages: 4, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_peersrpc_peers_proto_goTypes, + DependencyIndexes: file_peersrpc_peers_proto_depIdxs, + EnumInfos: file_peersrpc_peers_proto_enumTypes, + MessageInfos: file_peersrpc_peers_proto_msgTypes, + }.Build() + File_peersrpc_peers_proto = out.File + file_peersrpc_peers_proto_rawDesc = nil + file_peersrpc_peers_proto_goTypes = nil + file_peersrpc_peers_proto_depIdxs = nil +} diff --git a/lnrpc/peersrpc/peers.pb.gw.go b/lnrpc/peersrpc/peers.pb.gw.go new file mode 100644 index 0000000000..f45a8db693 --- /dev/null +++ b/lnrpc/peersrpc/peers.pb.gw.go @@ -0,0 +1,167 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: peersrpc/peers.proto + +/* +Package peersrpc is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package peersrpc + +import ( + "context" + "io" + "net/http" + + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/grpc-ecosystem/grpc-gateway/v2/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = metadata.Join + +func request_Peers_UpdateNodeAnnouncement_0(ctx context.Context, marshaler runtime.Marshaler, client PeersClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq NodeAnnouncementUpdateRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.UpdateNodeAnnouncement(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Peers_UpdateNodeAnnouncement_0(ctx context.Context, marshaler runtime.Marshaler, server PeersServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq NodeAnnouncementUpdateRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.UpdateNodeAnnouncement(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterPeersHandlerServer registers the http handlers for service Peers to "mux". +// UnaryRPC :call PeersServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterPeersHandlerFromEndpoint instead. +func RegisterPeersHandlerServer(ctx context.Context, mux *runtime.ServeMux, server PeersServer) error { + + mux.Handle("POST", pattern_Peers_UpdateNodeAnnouncement_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/peersrpc.Peers/UpdateNodeAnnouncement", runtime.WithHTTPPathPattern("/v2/peers/nodeannouncement")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Peers_UpdateNodeAnnouncement_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Peers_UpdateNodeAnnouncement_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterPeersHandlerFromEndpoint is same as RegisterPeersHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterPeersHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterPeersHandler(ctx, mux, conn) +} + +// RegisterPeersHandler registers the http handlers for service Peers to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterPeersHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterPeersHandlerClient(ctx, mux, NewPeersClient(conn)) +} + +// RegisterPeersHandlerClient registers the http handlers for service Peers +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "PeersClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "PeersClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "PeersClient" to call the correct interceptors. +func RegisterPeersHandlerClient(ctx context.Context, mux *runtime.ServeMux, client PeersClient) error { + + mux.Handle("POST", pattern_Peers_UpdateNodeAnnouncement_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req, "/peersrpc.Peers/UpdateNodeAnnouncement", runtime.WithHTTPPathPattern("/v2/peers/nodeannouncement")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Peers_UpdateNodeAnnouncement_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Peers_UpdateNodeAnnouncement_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_Peers_UpdateNodeAnnouncement_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "peers", "nodeannouncement"}, "")) +) + +var ( + forward_Peers_UpdateNodeAnnouncement_0 = runtime.ForwardResponseMessage +) diff --git a/lnrpc/peersrpc/peers.pb.json.go b/lnrpc/peersrpc/peers.pb.json.go new file mode 100644 index 0000000000..8efa8585ae --- /dev/null +++ b/lnrpc/peersrpc/peers.pb.json.go @@ -0,0 +1,50 @@ +// Code generated by falafel 0.9.1. DO NOT EDIT. +// source: peers.proto + +// +build js + +package peersrpc + +import ( + "context" + + gateway "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "google.golang.org/grpc" + "google.golang.org/protobuf/encoding/protojson" +) + +func RegisterPeersJSONCallbacks(registry map[string]func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error))) { + + marshaler := &gateway.JSONPb{ + MarshalOptions: protojson.MarshalOptions{ + UseProtoNames: true, + EmitUnpopulated: true, + }, + } + + registry["peersrpc.Peers.UpdateNodeAnnouncement"] = func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { + + req := &NodeAnnouncementUpdateRequest{} + err := marshaler.Unmarshal([]byte(reqJSON), req) + if err != nil { + callback("", err) + return + } + + client := NewPeersClient(conn) + resp, err := client.UpdateNodeAnnouncement(ctx, req) + if err != nil { + callback("", err) + return + } + + respBytes, err := marshaler.Marshal(resp) + if err != nil { + callback("", err) + return + } + callback(string(respBytes), nil) + } +} diff --git a/lnrpc/peersrpc/peers.proto b/lnrpc/peersrpc/peers.proto new file mode 100644 index 0000000000..7af9f0a7f3 --- /dev/null +++ b/lnrpc/peersrpc/peers.proto @@ -0,0 +1,94 @@ +syntax = "proto3"; + +import "lightning.proto"; + +package peersrpc; + +option go_package = "github.com/lightningnetwork/lnd/lnrpc/peersrpc"; + +// Peers is a service that can be used to get information and interact +// with the other nodes of the newtwork. +service Peers { + /* lncli: peers updatenodeannouncement + UpdateNodeAnnouncement allows the caller to update the node parameters + and broadcasts a new version of the node announcement to its peers. + */ + rpc UpdateNodeAnnouncement (NodeAnnouncementUpdateRequest) + returns (NodeAnnouncementUpdateResponse); +} + +// UpdateAction is used to determine the kind of action we are referring to. +enum UpdateAction { + // ADD indicates this is an "insertion" kind of action. + ADD = 0; + + // REMOVE indicates this is a "deletion" kind of action. + REMOVE = 1; +} + +enum FeatureSet { + /* + SET_INIT identifies features that should be sent in an Init message to + a remote peer. + */ + SET_INIT = 0; + + /* + SET_LEGACY_GLOBAL identifies features that should be set in the legacy + GlobalFeatures field of an Init message, which maintains backwards + compatibility with nodes that haven't implemented flat features. + */ + SET_LEGACY_GLOBAL = 1; + + /* + SET_NODE_ANN identifies features that should be advertised on node + announcements. + */ + SET_NODE_ANN = 2; + + /* + SET_INVOICE identifies features that should be advertised on invoices + generated by the daemon. + */ + SET_INVOICE = 3; + + /* + SET_INVOICE_AMP identifies the features that should be advertised on + AMP invoices generated by the daemon. + */ + SET_INVOICE_AMP = 4; +} + +message UpdateAddressAction { + // Determines the kind of action. + UpdateAction action = 1; + + // The address used to apply the update action. + string address = 2; +} + +message UpdateFeatureAction { + // Determines the kind of action. + UpdateAction action = 1; + + // The feature bit used to apply the update action. + lnrpc.FeatureBit feature_bit = 2; +} + +message NodeAnnouncementUpdateRequest { + // Set of changes for the features that the node supports. + repeated UpdateFeatureAction feature_updates = 1; + + // Color is the node's color in hex code format. + string color = 2; + + // Alias or nick name of the node. + string alias = 3; + + // Set of changes for the node's known addresses. + repeated UpdateAddressAction address_updates = 4; +} + +message NodeAnnouncementUpdateResponse { + repeated lnrpc.Op ops = 1; +} diff --git a/lnrpc/peersrpc/peers.swagger.json b/lnrpc/peersrpc/peers.swagger.json new file mode 100644 index 0000000000..c7b9970cf6 --- /dev/null +++ b/lnrpc/peersrpc/peers.swagger.json @@ -0,0 +1,203 @@ +{ + "swagger": "2.0", + "info": { + "title": "peersrpc/peers.proto", + "version": "version not set" + }, + "tags": [ + { + "name": "Peers" + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": { + "/v2/peers/nodeannouncement": { + "post": { + "summary": "lncli: peers updatenodeannouncement\nUpdateNodeAnnouncement allows the caller to update the node parameters\nand broadcasts a new version of the node announcement to its peers.", + "operationId": "Peers_UpdateNodeAnnouncement", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/peersrpcNodeAnnouncementUpdateResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/peersrpcNodeAnnouncementUpdateRequest" + } + } + ], + "tags": [ + "Peers" + ] + } + } + }, + "definitions": { + "lnrpcFeatureBit": { + "type": "string", + "enum": [ + "DATALOSS_PROTECT_REQ", + "DATALOSS_PROTECT_OPT", + "INITIAL_ROUING_SYNC", + "UPFRONT_SHUTDOWN_SCRIPT_REQ", + "UPFRONT_SHUTDOWN_SCRIPT_OPT", + "GOSSIP_QUERIES_REQ", + "GOSSIP_QUERIES_OPT", + "TLV_ONION_REQ", + "TLV_ONION_OPT", + "EXT_GOSSIP_QUERIES_REQ", + "EXT_GOSSIP_QUERIES_OPT", + "STATIC_REMOTE_KEY_REQ", + "STATIC_REMOTE_KEY_OPT", + "PAYMENT_ADDR_REQ", + "PAYMENT_ADDR_OPT", + "MPP_REQ", + "MPP_OPT", + "WUMBO_CHANNELS_REQ", + "WUMBO_CHANNELS_OPT", + "ANCHORS_REQ", + "ANCHORS_OPT", + "ANCHORS_ZERO_FEE_HTLC_REQ", + "ANCHORS_ZERO_FEE_HTLC_OPT", + "AMP_REQ", + "AMP_OPT" + ], + "default": "DATALOSS_PROTECT_REQ" + }, + "lnrpcOp": { + "type": "object", + "properties": { + "entity": { + "type": "string" + }, + "actions": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "peersrpcNodeAnnouncementUpdateRequest": { + "type": "object", + "properties": { + "feature_updates": { + "type": "array", + "items": { + "$ref": "#/definitions/peersrpcUpdateFeatureAction" + }, + "description": "Set of changes for the features that the node supports." + }, + "color": { + "type": "string", + "description": "Color is the node's color in hex code format." + }, + "alias": { + "type": "string", + "description": "Alias or nick name of the node." + }, + "address_updates": { + "type": "array", + "items": { + "$ref": "#/definitions/peersrpcUpdateAddressAction" + }, + "description": "Set of changes for the node's known addresses." + } + } + }, + "peersrpcNodeAnnouncementUpdateResponse": { + "type": "object", + "properties": { + "ops": { + "type": "array", + "items": { + "$ref": "#/definitions/lnrpcOp" + } + } + } + }, + "peersrpcUpdateAction": { + "type": "string", + "enum": [ + "ADD", + "REMOVE" + ], + "default": "ADD", + "description": "UpdateAction is used to determine the kind of action we are referring to.\n\n - ADD: ADD indicates this is an \"insertion\" kind of action.\n - REMOVE: REMOVE indicates this is a \"deletion\" kind of action." + }, + "peersrpcUpdateAddressAction": { + "type": "object", + "properties": { + "action": { + "$ref": "#/definitions/peersrpcUpdateAction", + "description": "Determines the kind of action." + }, + "address": { + "type": "string", + "description": "The address used to apply the update action." + } + } + }, + "peersrpcUpdateFeatureAction": { + "type": "object", + "properties": { + "action": { + "$ref": "#/definitions/peersrpcUpdateAction", + "description": "Determines the kind of action." + }, + "feature_bit": { + "$ref": "#/definitions/lnrpcFeatureBit", + "description": "The feature bit used to apply the update action." + } + } + }, + "protobufAny": { + "type": "object", + "properties": { + "type_url": { + "type": "string" + }, + "value": { + "type": "string", + "format": "byte" + } + } + }, + "rpcStatus": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "$ref": "#/definitions/protobufAny" + } + } + } + } + } +} diff --git a/lnrpc/peersrpc/peers.yaml b/lnrpc/peersrpc/peers.yaml new file mode 100644 index 0000000000..b1b9544f18 --- /dev/null +++ b/lnrpc/peersrpc/peers.yaml @@ -0,0 +1,8 @@ +type: google.api.Service +config_version: 3 + +http: + rules: + - selector: peersrpc.Peers.UpdateNodeAnnouncement + post: "/v2/peers/nodeannouncement" + body: "*" diff --git a/lnrpc/peersrpc/peers_grpc.pb.go b/lnrpc/peersrpc/peers_grpc.pb.go new file mode 100644 index 0000000000..437fe45b28 --- /dev/null +++ b/lnrpc/peersrpc/peers_grpc.pb.go @@ -0,0 +1,107 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. + +package peersrpc + +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 + +// PeersClient is the client API for Peers 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 PeersClient interface { + // lncli: peers updatenodeannouncement + //UpdateNodeAnnouncement allows the caller to update the node parameters + //and broadcasts a new version of the node announcement to its peers. + UpdateNodeAnnouncement(ctx context.Context, in *NodeAnnouncementUpdateRequest, opts ...grpc.CallOption) (*NodeAnnouncementUpdateResponse, error) +} + +type peersClient struct { + cc grpc.ClientConnInterface +} + +func NewPeersClient(cc grpc.ClientConnInterface) PeersClient { + return &peersClient{cc} +} + +func (c *peersClient) UpdateNodeAnnouncement(ctx context.Context, in *NodeAnnouncementUpdateRequest, opts ...grpc.CallOption) (*NodeAnnouncementUpdateResponse, error) { + out := new(NodeAnnouncementUpdateResponse) + err := c.cc.Invoke(ctx, "/peersrpc.Peers/UpdateNodeAnnouncement", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// PeersServer is the server API for Peers service. +// All implementations must embed UnimplementedPeersServer +// for forward compatibility +type PeersServer interface { + // lncli: peers updatenodeannouncement + //UpdateNodeAnnouncement allows the caller to update the node parameters + //and broadcasts a new version of the node announcement to its peers. + UpdateNodeAnnouncement(context.Context, *NodeAnnouncementUpdateRequest) (*NodeAnnouncementUpdateResponse, error) + mustEmbedUnimplementedPeersServer() +} + +// UnimplementedPeersServer must be embedded to have forward compatible implementations. +type UnimplementedPeersServer struct { +} + +func (UnimplementedPeersServer) UpdateNodeAnnouncement(context.Context, *NodeAnnouncementUpdateRequest) (*NodeAnnouncementUpdateResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateNodeAnnouncement not implemented") +} +func (UnimplementedPeersServer) mustEmbedUnimplementedPeersServer() {} + +// UnsafePeersServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to PeersServer will +// result in compilation errors. +type UnsafePeersServer interface { + mustEmbedUnimplementedPeersServer() +} + +func RegisterPeersServer(s grpc.ServiceRegistrar, srv PeersServer) { + s.RegisterService(&Peers_ServiceDesc, srv) +} + +func _Peers_UpdateNodeAnnouncement_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(NodeAnnouncementUpdateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PeersServer).UpdateNodeAnnouncement(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/peersrpc.Peers/UpdateNodeAnnouncement", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PeersServer).UpdateNodeAnnouncement(ctx, req.(*NodeAnnouncementUpdateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// Peers_ServiceDesc is the grpc.ServiceDesc for Peers service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Peers_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "peersrpc.Peers", + HandlerType: (*PeersServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "UpdateNodeAnnouncement", + Handler: _Peers_UpdateNodeAnnouncement_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "peersrpc/peers.proto", +} diff --git a/lnrpc/peersrpc/peers_server.go b/lnrpc/peersrpc/peers_server.go new file mode 100644 index 0000000000..84516f7dca --- /dev/null +++ b/lnrpc/peersrpc/peers_server.go @@ -0,0 +1,403 @@ +//go:build peersrpc +// +build peersrpc + +package peersrpc + +import ( + "context" + "fmt" + "net" + "sync/atomic" + + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/lightningnetwork/lnd/feature" + "github.com/lightningnetwork/lnd/lncfg" + "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/netann" + "google.golang.org/grpc" + "gopkg.in/macaroon-bakery.v2/bakery" +) + +const ( + // subServerName is the name of the sub rpc server. We'll use this name + // to register ourselves, and we also require that the main + // SubServerConfigDispatcher instance recognize tt as the name of our + // RPC service. + subServerName = "PeersRPC" +) + +var ( + // macPermissions maps RPC calls to the permissions they require. + macPermissions = map[string][]bakery.Op{ + "/peersrpc.Peers/UpdateNodeAnnouncement": {{ + Entity: "peers", + Action: "write", + }}, + } +) + +// ServerShell is a shell struct holding a reference to the actual sub-server. +// It is used to register the gRPC sub-server with the root server before we +// have the necessary dependencies to populate the actual sub-server. +type ServerShell struct { + PeersServer +} + +// Server is a sub-server of the main RPC server: the peers RPC. This sub +// RPC server allows to intereact with our Peers in the Lightning Network. +type Server struct { + started int32 // To be used atomically. + shutdown int32 // To be used atomically. + + // Required by the grpc-gateway/v2 library for forward compatibility. + // Must be after the atomically used variables to not break struct + // alignment. + UnimplementedPeersServer + + cfg *Config +} + +// A compile time check to ensure that Server fully implements the PeersServer +// gRPC service. +var _ PeersServer = (*Server)(nil) + +// New returns a new instance of the peersrpc Peers sub-server. We also +// return the set of permissions for the macaroons that we may create within +// this method. If the macaroons we need aren't found in the filepath, then +// we'll create them on start up. If we're unable to locate, or create the +// macaroons we need, then we'll return with an error. +func New(cfg *Config) (*Server, lnrpc.MacaroonPerms, error) { + server := &Server{ + cfg: cfg, + } + + return server, macPermissions, nil +} + +// Start launches any helper goroutines required for the Server to function. +// +// NOTE: This is part of the lnrpc.SubServer interface. +func (s *Server) Start() error { + if atomic.AddInt32(&s.started, 1) != 1 { + return nil + } + + return nil +} + +// Stop signals any active goroutines for a graceful closure. +// +// NOTE: This is part of the lnrpc.SubServer interface. +func (s *Server) Stop() error { + if atomic.AddInt32(&s.shutdown, 1) != 1 { + return nil + } + + return nil +} + +// Name returns a unique string representation of the sub-server. This can be +// used to identify the sub-server and also de-duplicate them. +// +// NOTE: This is part of the lnrpc.SubServer interface. +func (s *Server) Name() string { + return subServerName +} + +// RegisterWithRootServer will be called by the root gRPC server to direct a +// sub RPC server to register itself with the main gRPC root server. Until this +// is called, each sub-server won't be able to have +// requests routed towards it. +// +// NOTE: This is part of the lnrpc.GrpcHandler interface. +func (r *ServerShell) RegisterWithRootServer(grpcServer *grpc.Server) error { + // We make sure that we register it with the main gRPC server to ensure + // all our methods are routed properly. + RegisterPeersServer(grpcServer, r) + + log.Debugf("Peers RPC server successfully register with root " + + "gRPC server") + + return nil +} + +// RegisterWithRestServer will be called by the root REST mux to direct a sub +// RPC server to register itself with the main REST mux server. Until this is +// called, each sub-server won't be able to have requests routed towards it. +// +// NOTE: This is part of the lnrpc.GrpcHandler interface. +func (r *ServerShell) RegisterWithRestServer(ctx context.Context, + mux *runtime.ServeMux, dest string, opts []grpc.DialOption) error { + + // We make sure that we register it with the main REST server to ensure + // all our methods are routed properly. + err := RegisterPeersHandlerFromEndpoint(ctx, mux, dest, opts) + if err != nil { + log.Errorf("Could not register Peers REST server "+ + "with root REST server: %v", err) + return err + } + + log.Debugf("Peers REST server successfully registered with " + + "root REST server") + return nil +} + +// CreateSubServer populates the subserver's dependencies using the passed +// SubServerConfigDispatcher. This method should fully initialize the +// sub-server instance, making it ready for action. It returns the macaroon +// permissions that the sub-server wishes to pass on to the root server for all +// methods routed towards it. +// +// NOTE: This is part of the lnrpc.GrpcHandler interface. +func (r *ServerShell) CreateSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( + lnrpc.SubServer, lnrpc.MacaroonPerms, error) { + + subServer, macPermissions, err := createNewSubServer(configRegistry) + if err != nil { + return nil, nil, err + } + + r.PeersServer = subServer + return subServer, macPermissions, nil +} + +// updateAddresses computes the new address set after executing the update +// actions. +func (s *Server) updateAddresses(currentAddresses []net.Addr, + updates []*UpdateAddressAction) ([]net.Addr, *lnrpc.Op, error) { + + // net.Addr is not comparable so we cannot use the default map + // (map[net.Addr]struct{}) so we have to use arrays and a helping + // function. + findAddr := func(addr net.Addr, slice []net.Addr) bool { + for _, sAddr := range slice { + if sAddr.Network() != addr.Network() { + continue + } + + if sAddr.String() == addr.String() { + return true + } + } + return false + } + + // Preallocate enough memory for both arrays. + removeAddr := make([]net.Addr, 0, len(updates)) + addAddr := make([]net.Addr, 0, len(updates)) + for _, update := range updates { + addr, err := s.cfg.ParseAddr(update.Address) + if err != nil { + return nil, nil, fmt.Errorf("unable to resolve "+ + "address %v: %v", update.Address, err) + } + + switch update.Action { + case UpdateAction_ADD: + addAddr = append(addAddr, addr) + case UpdateAction_REMOVE: + removeAddr = append(removeAddr, addr) + default: + return nil, nil, fmt.Errorf("invalid address update "+ + "action: %v", update.Action) + } + } + + // Look for any inconsistency trying to add AND remove the same address. + for _, addr := range removeAddr { + if findAddr(addr, addAddr) { + return nil, nil, fmt.Errorf("invalid updates for "+ + "removing AND adding %v", addr) + } + } + + ops := &lnrpc.Op{Entity: "addresses"} + newAddrs := make([]net.Addr, 0, len(updates)+len(currentAddresses)) + + // Copy current addresses excluding the ones that need to be removed. + for _, addr := range currentAddresses { + if findAddr(addr, removeAddr) { + ops.Actions = append( + ops.Actions, + fmt.Sprintf("%s removed", addr.String()), + ) + continue + } + newAddrs = append(newAddrs, addr) + } + + // Add new adresses if needed. + for _, addr := range addAddr { + if !findAddr(addr, newAddrs) { + ops.Actions = append( + ops.Actions, + fmt.Sprintf("%s added", addr.String()), + ) + newAddrs = append(newAddrs, addr) + } + } + + return newAddrs, ops, nil +} + +// updateFeatures computes the new raw SetNodeAnn after executing the update +// actions. +func (s *Server) updateFeatures(currentfeatures *lnwire.RawFeatureVector, + updates []*UpdateFeatureAction) (*lnwire.RawFeatureVector, + *lnrpc.Op, error) { + + ops := &lnrpc.Op{Entity: "features"} + raw := currentfeatures.Clone() + + for _, update := range updates { + bit := lnwire.FeatureBit(update.FeatureBit) + + switch update.Action { + case UpdateAction_ADD: + if raw.IsSet(bit) { + return nil, nil, fmt.Errorf( + "invalid add action for bit %v, "+ + "bit is already set", + update.FeatureBit, + ) + } + raw.Set(bit) + ops.Actions = append( + ops.Actions, + fmt.Sprintf("%s set", lnwire.Features[bit]), + ) + + case UpdateAction_REMOVE: + if !raw.IsSet(bit) { + return nil, nil, fmt.Errorf( + "invalid remove action for bit %v, "+ + "bit is already unset", + update.FeatureBit, + ) + } + raw.Unset(bit) + ops.Actions = append( + ops.Actions, + fmt.Sprintf("%s unset", lnwire.Features[bit]), + ) + + default: + return nil, nil, fmt.Errorf( + "invalid update action (%v) for bit %v", + update.Action, + update.FeatureBit, + ) + } + } + + // Validate our new SetNodeAnn. + fv := lnwire.NewFeatureVector(raw, lnwire.Features) + if err := feature.ValidateDeps(fv); err != nil { + return nil, nil, fmt.Errorf( + "invalid feature set (SetNodeAnn): %v", + err, + ) + } + + return raw, ops, nil +} + +// UpdateNodeAnnouncement allows the caller to update the node parameters +// and broadcasts a new version of the node announcement to its peers. +func (s *Server) UpdateNodeAnnouncement(_ context.Context, + req *NodeAnnouncementUpdateRequest) ( + *NodeAnnouncementUpdateResponse, error) { + + resp := &NodeAnnouncementUpdateResponse{} + nodeModifiers := make([]netann.NodeAnnModifier, 0) + + currentNodeAnn, err := s.cfg.GetNodeAnnouncement() + if err != nil { + return nil, fmt.Errorf("unable to get current node "+ + "announcement: %v", err) + } + + if len(req.FeatureUpdates) > 0 { + features, ops, err := s.updateFeatures( + currentNodeAnn.Features, + req.FeatureUpdates, + ) + if err != nil { + return nil, fmt.Errorf("error trying to update node "+ + "features: %w", err) + } + resp.Ops = append(resp.Ops, ops) + nodeModifiers = append( + nodeModifiers, + netann.NodeAnnSetFeatures(features), + ) + } + + if req.Color != "" { + color, err := lncfg.ParseHexColor(req.Color) + if err != nil { + return nil, fmt.Errorf("unable to parse color: %v", err) + } + + if color != currentNodeAnn.RGBColor { + resp.Ops = append(resp.Ops, &lnrpc.Op{ + Entity: "color", + Actions: []string{ + fmt.Sprintf("changed to %v", color), + }, + }) + nodeModifiers = append( + nodeModifiers, + netann.NodeAnnSetColor(color), + ) + } + } + + if req.Alias != "" { + alias, err := lnwire.NewNodeAlias(req.Alias) + if err != nil { + return nil, fmt.Errorf("invalid alias value: %v", err) + } + if alias != currentNodeAnn.Alias { + resp.Ops = append(resp.Ops, &lnrpc.Op{ + Entity: "alias", + Actions: []string{ + fmt.Sprintf("changed to %v", alias), + }, + }) + nodeModifiers = append( + nodeModifiers, + netann.NodeAnnSetAlias(alias), + ) + } + } + + if len(req.AddressUpdates) > 0 { + newAddrs, ops, err := s.updateAddresses( + currentNodeAnn.Addresses, + req.AddressUpdates, + ) + if err != nil { + return nil, fmt.Errorf("error trying to update node "+ + "addresses: %w", err) + } + resp.Ops = append(resp.Ops, ops) + nodeModifiers = append( + nodeModifiers, + netann.NodeAnnSetAddrs(newAddrs), + ) + } + + if len(nodeModifiers) == 0 { + return nil, fmt.Errorf("unable detect any new values to " + + "update the node announcement") + } + + if err := s.cfg.UpdateNodeAnnouncement(nodeModifiers...); err != nil { + return nil, err + } + + return resp, nil +} diff --git a/lnrpc/routerrpc/driver.go b/lnrpc/routerrpc/driver.go index 0899464cbc..4e9cea6577 100644 --- a/lnrpc/routerrpc/driver.go +++ b/lnrpc/routerrpc/driver.go @@ -33,7 +33,7 @@ func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( } // Before we try to make the new router service instance, we'll perform - // some sanity checks on the arguments to ensure that they're useable. + // some sanity checks on the arguments to ensure that they're usable. switch { case config.Router == nil: return nil, nil, fmt.Errorf("Router must be set to create " + diff --git a/lnrpc/routerrpc/forward_interceptor.go b/lnrpc/routerrpc/forward_interceptor.go index e0b0d4ca8a..9fba60f1f1 100644 --- a/lnrpc/routerrpc/forward_interceptor.go +++ b/lnrpc/routerrpc/forward_interceptor.go @@ -2,13 +2,14 @@ package routerrpc import ( "errors" - "fmt" - "sync" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/htlcswitch" + "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwire" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) var ( @@ -25,82 +26,41 @@ var ( // interceptor streaming session. // It is created when the stream opens and disconnects when the stream closes. type forwardInterceptor struct { - // server is the Server reference - server *Server - - // holdForwards is a map of current hold forwards and their corresponding - // ForwardResolver. - holdForwards map[channeldb.CircuitKey]htlcswitch.InterceptedForward - // stream is the bidirectional RPC stream stream Router_HtlcInterceptorServer - // quit is a channel that is closed when this forwardInterceptor is shutting - // down. - quit chan struct{} - - // intercepted is where we stream all intercepted packets coming from - // the switch. - intercepted chan htlcswitch.InterceptedForward - - wg sync.WaitGroup + htlcSwitch htlcswitch.InterceptableHtlcForwarder } // newForwardInterceptor creates a new forwardInterceptor. -func newForwardInterceptor(server *Server, stream Router_HtlcInterceptorServer) *forwardInterceptor { +func newForwardInterceptor(htlcSwitch htlcswitch.InterceptableHtlcForwarder, + stream Router_HtlcInterceptorServer) *forwardInterceptor { + return &forwardInterceptor{ - server: server, - stream: stream, - holdForwards: make( - map[channeldb.CircuitKey]htlcswitch.InterceptedForward), - quit: make(chan struct{}), - intercepted: make(chan htlcswitch.InterceptedForward), + htlcSwitch: htlcSwitch, + stream: stream, } } // run sends the intercepted packets to the client and receives the -// corersponding responses. On one hand it regsitered itself as an interceptor +// corersponding responses. On one hand it registered itself as an interceptor // that receives the switch packets and on the other hand launches a go routine // to read from the client stream. // To coordinate all this and make sure it is safe for concurrent access all // packets are sent to the main where they are handled. func (r *forwardInterceptor) run() error { - // make sure we disconnect and resolves all remaining packets if any. - defer r.onDisconnect() - // Register our interceptor so we receive all forwarded packets. - interceptableForwarder := r.server.cfg.RouterBackend.InterceptableForwarder - interceptableForwarder.SetInterceptor(r.onIntercept) - defer interceptableForwarder.SetInterceptor(nil) - - // start a go routine that reads client resolutions. - errChan := make(chan error) - resolutionRequests := make(chan *ForwardHtlcInterceptResponse) - r.wg.Add(1) - go r.readClientResponses(resolutionRequests, errChan) + r.htlcSwitch.SetInterceptor(r.onIntercept) + defer r.htlcSwitch.SetInterceptor(nil) - // run the main loop that synchronizes both sides input into one go routine. for { - select { - case intercepted := <-r.intercepted: - log.Tracef("sending intercepted packet to client %v", intercepted) - // in case we couldn't forward we exit the loop and drain the - // current interceptor as this indicates on a connection problem. - if err := r.holdAndForwardToClient(intercepted); err != nil { - return err - } - case resolution := <-resolutionRequests: - log.Tracef("resolving intercepted packet %v", resolution) - // in case we couldn't resolve we just add a log line since this - // does not indicate on any connection problem. - if err := r.resolveFromClient(resolution); err != nil { - log.Warnf("client resolution of intercepted "+ - "packet failed %v", err) - } - case err := <-errChan: + resp, err := r.stream.Recv() + if err != nil { + return err + } + + if err := r.resolveFromClient(resp); err != nil { return err - case <-r.server.quit: - return nil } } } @@ -109,54 +69,14 @@ func (r *forwardInterceptor) run() error { // packet. Our interceptor makes sure we hold the packet and then signal to the // main loop to handle the packet. We only return true if we were able // to deliver the packet to the main loop. -func (r *forwardInterceptor) onIntercept(p htlcswitch.InterceptedForward) bool { - select { - case r.intercepted <- p: - return true - case <-r.quit: - return false - case <-r.server.quit: - return false - } -} - -func (r *forwardInterceptor) readClientResponses( - resolutionChan chan *ForwardHtlcInterceptResponse, errChan chan error) { - - defer r.wg.Done() - for { - resp, err := r.stream.Recv() - if err != nil { - errChan <- err - return - } - - // Now that we have the response from the RPC client, send it to - // the responses chan. - select { - case resolutionChan <- resp: - case <-r.quit: - return - case <-r.server.quit: - return - } - } -} +func (r *forwardInterceptor) onIntercept( + htlc htlcswitch.InterceptedPacket) error { -// holdAndForwardToClient forwards the intercepted htlc to the client. -func (r *forwardInterceptor) holdAndForwardToClient( - forward htlcswitch.InterceptedForward) error { + log.Tracef("Sending intercepted packet to client %v", htlc) - htlc := forward.Packet() inKey := htlc.IncomingCircuit - // Ignore already held htlcs. - if _, ok := r.holdForwards[inKey]; ok { - return nil - } - // First hold the forward, then send to client. - r.holdForwards[inKey] = forward interceptionRequest := &ForwardHtlcInterceptRequest{ IncomingCircuitKey: &CircuitKey{ ChanId: inKey.ChanID.ToUint64(), @@ -179,22 +99,78 @@ func (r *forwardInterceptor) holdAndForwardToClient( func (r *forwardInterceptor) resolveFromClient( in *ForwardHtlcInterceptResponse) error { + log.Tracef("Resolving intercepted packet %v", in) + circuitKey := channeldb.CircuitKey{ ChanID: lnwire.NewShortChanIDFromInt(in.IncomingCircuitKey.ChanId), HtlcID: in.IncomingCircuitKey.HtlcId, } - var interceptedForward htlcswitch.InterceptedForward - interceptedForward, ok := r.holdForwards[circuitKey] - if !ok { - return ErrFwdNotExists - } - delete(r.holdForwards, circuitKey) switch in.Action { case ResolveHoldForwardAction_RESUME: - return interceptedForward.Resume() + return r.htlcSwitch.Resolve(&htlcswitch.FwdResolution{ + Key: circuitKey, + Action: htlcswitch.FwdActionResume, + }) + case ResolveHoldForwardAction_FAIL: - return interceptedForward.Fail() + // Fail with an encrypted reason. + if in.FailureMessage != nil { + if in.FailureCode != 0 { + return status.Errorf( + codes.InvalidArgument, + "failure message and failure code "+ + "are mutually exclusive", + ) + } + + // Verify that the size is equal to the fixed failure + // message size + hmac + two uint16 lengths. See BOLT + // #4. + if len(in.FailureMessage) != + lnwire.FailureMessageLength+32+2+2 { + + return status.Errorf( + codes.InvalidArgument, + "failure message length invalid", + ) + } + + return r.htlcSwitch.Resolve(&htlcswitch.FwdResolution{ + Key: circuitKey, + Action: htlcswitch.FwdActionFail, + FailureMessage: in.FailureMessage, + }) + } + + var code lnwire.FailCode + switch in.FailureCode { + case lnrpc.Failure_INVALID_ONION_HMAC: + code = lnwire.CodeInvalidOnionHmac + + case lnrpc.Failure_INVALID_ONION_KEY: + code = lnwire.CodeInvalidOnionKey + + case lnrpc.Failure_INVALID_ONION_VERSION: + code = lnwire.CodeInvalidOnionVersion + + // Default to TemporaryChannelFailure. + case 0, lnrpc.Failure_TEMPORARY_CHANNEL_FAILURE: + code = lnwire.CodeTemporaryChannelFailure + + default: + return status.Errorf( + codes.InvalidArgument, + "unsupported failure code: %v", in.FailureCode, + ) + } + + return r.htlcSwitch.Resolve(&htlcswitch.FwdResolution{ + Key: circuitKey, + Action: htlcswitch.FwdActionFail, + FailureCode: code, + }) + case ResolveHoldForwardAction_SETTLE: if in.Preimage == nil { return ErrMissingPreimage @@ -203,25 +179,17 @@ func (r *forwardInterceptor) resolveFromClient( if err != nil { return err } - return interceptedForward.Settle(preimage) - default: - return fmt.Errorf("unrecognized resolve action %v", in.Action) - } -} -// onDisconnect removes all previousely held forwards from -// the store. Before they are removed it ensure to resume as the default -// behavior. -func (r *forwardInterceptor) onDisconnect() { - // Then close the channel so all go routine will exit. - close(r.quit) - - log.Infof("RPC interceptor disconnected, resolving held packets") - for key, forward := range r.holdForwards { - if err := forward.Resume(); err != nil { - log.Errorf("failed to resume hold forward %v", err) - } - delete(r.holdForwards, key) + return r.htlcSwitch.Resolve(&htlcswitch.FwdResolution{ + Key: circuitKey, + Action: htlcswitch.FwdActionSettle, + Preimage: preimage, + }) + + default: + return status.Errorf( + codes.InvalidArgument, + "unrecognized resolve action %v", in.Action, + ) } - r.wg.Wait() } diff --git a/lnrpc/routerrpc/log.go b/lnrpc/routerrpc/log.go index 6cbd8bea33..5c4ad01d83 100644 --- a/lnrpc/routerrpc/log.go +++ b/lnrpc/routerrpc/log.go @@ -5,7 +5,7 @@ import ( "github.com/lightningnetwork/lnd/build" ) -// log is a logger that is initialized with no output filters. This +// log is a logger that is initialized with no output filters. This // means the package will not perform any logging by default until the caller // requests it. var log btclog.Logger @@ -30,19 +30,3 @@ func DisableLog() { func UseLogger(logger btclog.Logger) { log = logger } - -// logClosure is used to provide a closure over expensive logging operations so -// don't have to be performed when the logging level doesn't warrant it. -type logClosure func() string // nolint:unused - -// String invokes the underlying function and returns the result. -func (c logClosure) String() string { - return c() -} - -// newLogClosure returns a new closure over a function that returns a string -// which itself provides a Stringer interface so that it can be used with the -// logging system. -func newLogClosure(c func() string) logClosure { // nolint:unused - return logClosure(c) -} diff --git a/lnrpc/routerrpc/router.pb.go b/lnrpc/routerrpc/router.pb.go index fcbecbe64a..e620a77cb8 100644 --- a/lnrpc/routerrpc/router.pb.go +++ b/lnrpc/routerrpc/router.pb.go @@ -467,6 +467,10 @@ type SendPaymentRequest struct { // //If set, an AMP-payment will be attempted. Amp bool `protobuf:"varint,22,opt,name=amp,proto3" json:"amp,omitempty"` + // + //The time preference for this payment. Set to -1 to optimize for fees + //only, to 1 to optimize for reliability only or a value inbetween for a mix. + TimePref float64 `protobuf:"fixed64,23,opt,name=time_pref,json=timePref,proto3" json:"time_pref,omitempty"` } func (x *SendPaymentRequest) Reset() { @@ -656,6 +660,13 @@ func (x *SendPaymentRequest) GetAmp() bool { return false } +func (x *SendPaymentRequest) GetTimePref() float64 { + if x != nil { + return x.TimePref + } + return 0 +} + type TrackPaymentRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1120,6 +1131,10 @@ type XImportMissionControlRequest struct { // Node pair-level mission control state to be imported. Pairs []*PairHistory `protobuf:"bytes,1,rep,name=pairs,proto3" json:"pairs,omitempty"` + // Whether to force override MC pair history. Note that even with force + // override the failure pair is imported before the success pair and both + // still clamp existing failure/success amounts. + Force bool `protobuf:"varint,2,opt,name=force,proto3" json:"force,omitempty"` } func (x *XImportMissionControlRequest) Reset() { @@ -1161,6 +1176,13 @@ func (x *XImportMissionControlRequest) GetPairs() []*PairHistory { return nil } +func (x *XImportMissionControlRequest) GetForce() bool { + if x != nil { + return x.Force + } + return false +} + type XImportMissionControlResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -2679,6 +2701,19 @@ type ForwardHtlcInterceptResponse struct { Action ResolveHoldForwardAction `protobuf:"varint,2,opt,name=action,proto3,enum=routerrpc.ResolveHoldForwardAction" json:"action,omitempty"` // The preimage in case the resolve action is Settle. Preimage []byte `protobuf:"bytes,3,opt,name=preimage,proto3" json:"preimage,omitempty"` + // Encrypted failure message in case the resolve action is Fail. + // + // If failure_message is specified, the failure_code field must be set + // to zero. + FailureMessage []byte `protobuf:"bytes,4,opt,name=failure_message,json=failureMessage,proto3" json:"failure_message,omitempty"` + // Return the specified failure code in case the resolve action is Fail. The + // message data fields are populated automatically. + // + // If a non-zero failure_code is specified, failure_message must not be set. + // + // For backwards-compatibility reasons, TEMPORARY_CHANNEL_FAILURE is the + // default value for this field. + FailureCode lnrpc.Failure_FailureCode `protobuf:"varint,5,opt,name=failure_code,json=failureCode,proto3,enum=lnrpc.Failure_FailureCode" json:"failure_code,omitempty"` } func (x *ForwardHtlcInterceptResponse) Reset() { @@ -2734,6 +2769,20 @@ func (x *ForwardHtlcInterceptResponse) GetPreimage() []byte { return nil } +func (x *ForwardHtlcInterceptResponse) GetFailureMessage() []byte { + if x != nil { + return x.FailureMessage + } + return nil +} + +func (x *ForwardHtlcInterceptResponse) GetFailureCode() lnrpc.Failure_FailureCode { + if x != nil { + return x.FailureCode + } + return lnrpc.Failure_RESERVED +} + type UpdateChanStatusRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -2833,7 +2882,7 @@ var file_routerrpc_router_proto_rawDesc = []byte{ 0x0a, 0x16, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x1a, 0x0f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xd7, 0x07, 0x0a, 0x12, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, + 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xf4, 0x07, 0x0a, 0x12, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x6d, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x61, 0x6d, @@ -2890,439 +2939,449 @@ var file_routerrpc_router_proto_rawDesc = []byte{ 0x68, 0x61, 0x72, 0x64, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x15, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x6d, 0x61, 0x78, 0x53, 0x68, 0x61, 0x72, 0x64, 0x53, 0x69, 0x7a, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x6d, 0x70, 0x18, 0x16, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x03, 0x61, 0x6d, 0x70, 0x1a, 0x44, 0x0a, 0x16, 0x44, 0x65, 0x73, 0x74, - 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, - 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, - 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x68, - 0x0a, 0x13, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, - 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, - 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x2e, 0x0a, 0x13, 0x6e, 0x6f, 0x5f, 0x69, - 0x6e, 0x66, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x6e, 0x6f, 0x49, 0x6e, 0x66, 0x6c, 0x69, 0x67, 0x68, - 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x22, 0x3e, 0x0a, 0x0f, 0x52, 0x6f, 0x75, 0x74, - 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x64, - 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x65, 0x73, 0x74, 0x12, - 0x17, 0x0a, 0x07, 0x61, 0x6d, 0x74, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x06, 0x61, 0x6d, 0x74, 0x53, 0x61, 0x74, 0x22, 0x64, 0x0a, 0x10, 0x52, 0x6f, 0x75, 0x74, - 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x10, - 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x46, - 0x65, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6c, - 0x6f, 0x63, 0x6b, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0d, 0x74, 0x69, 0x6d, 0x65, 0x4c, 0x6f, 0x63, 0x6b, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x22, 0x5b, - 0x0a, 0x12, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, - 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, - 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x22, 0x0a, 0x05, 0x72, 0x6f, 0x75, 0x74, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, - 0x6f, 0x75, 0x74, 0x65, 0x52, 0x05, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x22, 0x5b, 0x0a, 0x13, 0x53, - 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x28, - 0x0a, 0x07, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, - 0x07, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x22, 0x1c, 0x0a, 0x1a, 0x52, 0x65, 0x73, 0x65, - 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x1d, 0x0a, 0x1b, 0x52, 0x65, 0x73, 0x65, 0x74, 0x4d, - 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1c, 0x0a, 0x1a, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4d, 0x69, - 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x22, 0x51, 0x0a, 0x1b, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4d, 0x69, 0x73, 0x73, - 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x2c, 0x0a, 0x05, 0x70, 0x61, 0x69, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x16, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, - 0x69, 0x72, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x05, 0x70, 0x61, 0x69, 0x72, 0x73, - 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x22, 0x4c, 0x0a, 0x1c, 0x58, 0x49, 0x6d, 0x70, 0x6f, 0x72, - 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x05, 0x70, 0x61, 0x69, 0x72, 0x73, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, - 0x63, 0x2e, 0x50, 0x61, 0x69, 0x72, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x05, 0x70, - 0x61, 0x69, 0x72, 0x73, 0x22, 0x1f, 0x0a, 0x1d, 0x58, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x4d, - 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x8a, 0x01, 0x0a, 0x0b, 0x50, 0x61, 0x69, 0x72, 0x48, 0x69, - 0x73, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x66, 0x72, - 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x46, 0x72, - 0x6f, 0x6d, 0x12, 0x17, 0x0a, 0x07, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x74, 0x6f, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x06, 0x6e, 0x6f, 0x64, 0x65, 0x54, 0x6f, 0x12, 0x2d, 0x0a, 0x07, 0x68, - 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x72, - 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x69, 0x72, 0x44, 0x61, 0x74, - 0x61, 0x52, 0x07, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, - 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x4a, 0x04, 0x08, 0x05, 0x10, 0x06, 0x4a, 0x04, 0x08, 0x06, - 0x10, 0x07, 0x22, 0xe8, 0x01, 0x0a, 0x08, 0x50, 0x61, 0x69, 0x72, 0x44, 0x61, 0x74, 0x61, 0x12, - 0x1b, 0x0a, 0x09, 0x66, 0x61, 0x69, 0x6c, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x08, 0x66, 0x61, 0x69, 0x6c, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0c, - 0x66, 0x61, 0x69, 0x6c, 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0a, 0x66, 0x61, 0x69, 0x6c, 0x41, 0x6d, 0x74, 0x53, 0x61, 0x74, 0x12, 0x22, - 0x0a, 0x0d, 0x66, 0x61, 0x69, 0x6c, 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x66, 0x61, 0x69, 0x6c, 0x41, 0x6d, 0x74, 0x4d, 0x73, - 0x61, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x69, - 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, - 0x73, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, - 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, - 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x41, 0x6d, 0x74, 0x53, 0x61, 0x74, 0x12, 0x28, 0x0a, - 0x10, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, - 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, - 0x41, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x22, 0x20, 0x0a, - 0x1e, 0x47, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, - 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, - 0x5a, 0x0a, 0x1f, 0x47, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, - 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x37, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x4d, - 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x59, 0x0a, 0x1e, 0x53, - 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x37, 0x0a, - 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, - 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, - 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x21, 0x0a, 0x1f, 0x53, 0x65, 0x74, 0x4d, 0x69, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x80, 0x02, 0x0a, 0x14, 0x4d, 0x69, - 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x12, 0x2a, 0x0a, 0x11, 0x68, 0x61, 0x6c, 0x66, 0x5f, 0x6c, 0x69, 0x66, 0x65, 0x5f, - 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x68, - 0x61, 0x6c, 0x66, 0x4c, 0x69, 0x66, 0x65, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x27, - 0x0a, 0x0f, 0x68, 0x6f, 0x70, 0x5f, 0x70, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, - 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x02, 0x52, 0x0e, 0x68, 0x6f, 0x70, 0x50, 0x72, 0x6f, 0x62, - 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x77, 0x65, 0x69, 0x67, 0x68, - 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x02, 0x52, 0x06, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, - 0x36, 0x0a, 0x17, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x70, 0x61, 0x79, 0x6d, 0x65, - 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, - 0x52, 0x15, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, - 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x43, 0x0a, 0x1e, 0x6d, 0x69, 0x6e, 0x69, 0x6d, - 0x75, 0x6d, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x78, - 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, - 0x1b, 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, - 0x65, 0x6c, 0x61, 0x78, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x22, 0x6a, 0x0a, 0x17, - 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x72, 0x6f, 0x6d, 0x5f, - 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x66, 0x72, 0x6f, 0x6d, - 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x6f, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x74, 0x6f, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x19, 0x0a, - 0x08, 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x07, 0x61, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x22, 0x6b, 0x0a, 0x18, 0x51, 0x75, 0x65, 0x72, - 0x79, 0x50, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, - 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x62, 0x61, - 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x2d, 0x0a, 0x07, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, - 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, - 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x69, 0x72, 0x44, 0x61, 0x74, 0x61, 0x52, 0x07, 0x68, 0x69, - 0x73, 0x74, 0x6f, 0x72, 0x79, 0x22, 0xca, 0x01, 0x0a, 0x11, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x52, - 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x61, - 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x61, - 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x5f, - 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x0e, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x43, 0x6c, 0x74, 0x76, 0x44, 0x65, 0x6c, 0x74, 0x61, - 0x12, 0x2c, 0x0a, 0x10, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, - 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x0e, - 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1f, - 0x0a, 0x0b, 0x68, 0x6f, 0x70, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x04, 0x20, - 0x03, 0x28, 0x0c, 0x52, 0x0a, 0x68, 0x6f, 0x70, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x73, 0x12, - 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x41, 0x64, - 0x64, 0x72, 0x22, 0x38, 0x0a, 0x12, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x22, 0x0a, 0x05, 0x72, 0x6f, 0x75, 0x74, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x05, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x22, 0x1c, 0x0a, 0x1a, - 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x48, 0x74, 0x6c, 0x63, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xf6, 0x04, 0x0a, 0x09, 0x48, - 0x74, 0x6c, 0x63, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x69, 0x6e, 0x63, 0x6f, - 0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x11, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x43, - 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x64, 0x12, 0x2e, 0x0a, 0x13, 0x6f, 0x75, 0x74, 0x67, - 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x69, 0x64, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x11, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x43, - 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x69, 0x6e, 0x63, 0x6f, - 0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x04, 0x52, 0x0e, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x48, 0x74, 0x6c, 0x63, - 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x68, - 0x74, 0x6c, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x6f, 0x75, - 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, - 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x5f, 0x6e, 0x73, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x04, 0x52, 0x0b, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x4e, 0x73, 0x12, - 0x3d, 0x0a, 0x0a, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x1e, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, - 0x48, 0x74, 0x6c, 0x63, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, - 0x79, 0x70, 0x65, 0x52, 0x09, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x3e, - 0x0a, 0x0d, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, - 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, - 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, - 0x52, 0x0c, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x4b, - 0x0a, 0x12, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x5f, 0x65, - 0x76, 0x65, 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x72, 0x6f, 0x75, - 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x46, 0x61, - 0x69, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x10, 0x66, 0x6f, 0x72, 0x77, 0x61, - 0x72, 0x64, 0x46, 0x61, 0x69, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x3b, 0x0a, 0x0c, 0x73, - 0x65, 0x74, 0x74, 0x6c, 0x65, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x16, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, - 0x74, 0x74, 0x6c, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0b, 0x73, 0x65, 0x74, - 0x74, 0x6c, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x42, 0x0a, 0x0f, 0x6c, 0x69, 0x6e, 0x6b, - 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x18, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, - 0x6e, 0x6b, 0x46, 0x61, 0x69, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0d, 0x6c, - 0x69, 0x6e, 0x6b, 0x46, 0x61, 0x69, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x3c, 0x0a, 0x09, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, - 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x45, 0x4e, 0x44, 0x10, 0x01, - 0x12, 0x0b, 0x0a, 0x07, 0x52, 0x45, 0x43, 0x45, 0x49, 0x56, 0x45, 0x10, 0x02, 0x12, 0x0b, 0x0a, - 0x07, 0x46, 0x4f, 0x52, 0x57, 0x41, 0x52, 0x44, 0x10, 0x03, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, - 0x65, 0x6e, 0x74, 0x22, 0xbc, 0x01, 0x0a, 0x08, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x66, 0x6f, - 0x12, 0x2b, 0x0a, 0x11, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x69, 0x6d, - 0x65, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x69, 0x6e, 0x63, - 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x54, 0x69, 0x6d, 0x65, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x2b, 0x0a, - 0x11, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6c, 0x6f, - 0x63, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, - 0x6e, 0x67, 0x54, 0x69, 0x6d, 0x65, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x2a, 0x0a, 0x11, 0x69, 0x6e, - 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x41, - 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x2a, 0x0a, 0x11, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, - 0x6e, 0x67, 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x0f, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x41, 0x6d, 0x74, 0x4d, 0x73, - 0x61, 0x74, 0x22, 0x37, 0x0a, 0x0c, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x12, 0x27, 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x13, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x74, 0x6c, - 0x63, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x22, 0x12, 0x0a, 0x10, 0x46, - 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x46, 0x61, 0x69, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, - 0x29, 0x0a, 0x0b, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1a, - 0x0a, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x22, 0xdf, 0x01, 0x0a, 0x0d, 0x4c, - 0x69, 0x6e, 0x6b, 0x46, 0x61, 0x69, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x27, 0x0a, 0x04, - 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x72, 0x6f, 0x75, - 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x66, 0x6f, 0x52, - 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x3d, 0x0a, 0x0c, 0x77, 0x69, 0x72, 0x65, 0x5f, 0x66, 0x61, - 0x69, 0x6c, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x46, 0x61, 0x69, 0x6c, - 0x75, 0x72, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x0b, 0x77, 0x69, 0x72, 0x65, 0x46, 0x61, 0x69, - 0x6c, 0x75, 0x72, 0x65, 0x12, 0x3f, 0x0a, 0x0e, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, - 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x72, - 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, - 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x52, 0x0d, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x44, - 0x65, 0x74, 0x61, 0x69, 0x6c, 0x12, 0x25, 0x0a, 0x0e, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, - 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x66, - 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x22, 0x8a, 0x01, 0x0a, - 0x0d, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2d, - 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, - 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, - 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x1a, 0x0a, - 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x28, 0x0a, 0x05, 0x68, 0x74, 0x6c, - 0x63, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x48, 0x54, 0x4c, 0x43, 0x41, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x52, 0x05, 0x68, 0x74, - 0x6c, 0x63, 0x73, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x22, 0x3e, 0x0a, 0x0a, 0x43, 0x69, 0x72, - 0x63, 0x75, 0x69, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x17, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, - 0x12, 0x17, 0x0a, 0x07, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x06, 0x68, 0x74, 0x6c, 0x63, 0x49, 0x64, 0x22, 0xbf, 0x04, 0x0a, 0x1b, 0x46, 0x6f, - 0x72, 0x77, 0x61, 0x72, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, - 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x47, 0x0a, 0x14, 0x69, 0x6e, 0x63, - 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x5f, 0x6b, 0x65, - 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, - 0x72, 0x70, 0x63, 0x2e, 0x43, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x4b, 0x65, 0x79, 0x52, 0x12, - 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x43, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x4b, - 0x65, 0x79, 0x12, 0x30, 0x0a, 0x14, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x61, - 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x12, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, - 0x4d, 0x73, 0x61, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, - 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x69, - 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x21, 0x0a, - 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, - 0x12, 0x3b, 0x0a, 0x1a, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x17, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x30, 0x0a, - 0x14, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, - 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x12, 0x6f, 0x75, 0x74, - 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, - 0x27, 0x0a, 0x0f, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x65, 0x78, 0x70, 0x69, - 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, - 0x6e, 0x67, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x60, 0x0a, 0x0e, 0x63, 0x75, 0x73, 0x74, - 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x39, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, - 0x77, 0x61, 0x72, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, - 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x63, 0x75, 0x73, - 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x6f, 0x6e, - 0x69, 0x6f, 0x6e, 0x5f, 0x62, 0x6c, 0x6f, 0x62, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, - 0x6f, 0x6e, 0x69, 0x6f, 0x6e, 0x42, 0x6c, 0x6f, 0x62, 0x1a, 0x40, 0x0a, 0x12, 0x43, 0x75, 0x73, + 0x01, 0x28, 0x08, 0x52, 0x03, 0x61, 0x6d, 0x70, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, + 0x5f, 0x70, 0x72, 0x65, 0x66, 0x18, 0x17, 0x20, 0x01, 0x28, 0x01, 0x52, 0x08, 0x74, 0x69, 0x6d, + 0x65, 0x50, 0x72, 0x65, 0x66, 0x1a, 0x44, 0x0a, 0x16, 0x44, 0x65, 0x73, 0x74, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xc0, 0x01, 0x0a, 0x1c, - 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, - 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x14, + 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x68, 0x0a, 0x13, 0x54, + 0x72, 0x61, 0x63, 0x6b, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, + 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, + 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x2e, 0x0a, 0x13, 0x6e, 0x6f, 0x5f, 0x69, 0x6e, 0x66, 0x6c, + 0x69, 0x67, 0x68, 0x74, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x11, 0x6e, 0x6f, 0x49, 0x6e, 0x66, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x73, 0x22, 0x3e, 0x0a, 0x0f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x46, 0x65, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x74, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, + 0x61, 0x6d, 0x74, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, + 0x6d, 0x74, 0x53, 0x61, 0x74, 0x22, 0x64, 0x0a, 0x10, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x46, 0x65, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x72, 0x6f, 0x75, + 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x46, 0x65, 0x65, 0x4d, + 0x73, 0x61, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, + 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x74, 0x69, + 0x6d, 0x65, 0x4c, 0x6f, 0x63, 0x6b, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x22, 0x5b, 0x0a, 0x12, 0x53, + 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, + 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x48, 0x61, 0x73, 0x68, 0x12, 0x22, 0x0a, 0x05, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, + 0x65, 0x52, 0x05, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x22, 0x5b, 0x0a, 0x13, 0x53, 0x65, 0x6e, 0x64, + 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x28, 0x0a, 0x07, 0x66, + 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x07, 0x66, 0x61, + 0x69, 0x6c, 0x75, 0x72, 0x65, 0x22, 0x1c, 0x0a, 0x1a, 0x52, 0x65, 0x73, 0x65, 0x74, 0x4d, 0x69, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x22, 0x1d, 0x0a, 0x1b, 0x52, 0x65, 0x73, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x1c, 0x0a, 0x1a, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4d, 0x69, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x22, 0x51, 0x0a, 0x1b, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x2c, 0x0a, 0x05, 0x70, 0x61, 0x69, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, + 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x69, 0x72, 0x48, + 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x05, 0x70, 0x61, 0x69, 0x72, 0x73, 0x4a, 0x04, 0x08, + 0x01, 0x10, 0x02, 0x22, 0x62, 0x0a, 0x1c, 0x58, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x4d, 0x69, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x05, 0x70, 0x61, 0x69, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x50, + 0x61, 0x69, 0x72, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x05, 0x70, 0x61, 0x69, 0x72, + 0x73, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x22, 0x1f, 0x0a, 0x1d, 0x58, 0x49, 0x6d, 0x70, 0x6f, + 0x72, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x8a, 0x01, 0x0a, 0x0b, 0x50, 0x61, 0x69, + 0x72, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x64, 0x65, + 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x6e, 0x6f, 0x64, + 0x65, 0x46, 0x72, 0x6f, 0x6d, 0x12, 0x17, 0x0a, 0x07, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x74, 0x6f, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x6e, 0x6f, 0x64, 0x65, 0x54, 0x6f, 0x12, 0x2d, + 0x0a, 0x07, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x13, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x69, 0x72, + 0x44, 0x61, 0x74, 0x61, 0x52, 0x07, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x04, 0x08, + 0x03, 0x10, 0x04, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x4a, 0x04, 0x08, 0x05, 0x10, 0x06, 0x4a, + 0x04, 0x08, 0x06, 0x10, 0x07, 0x22, 0xe8, 0x01, 0x0a, 0x08, 0x50, 0x61, 0x69, 0x72, 0x44, 0x61, + 0x74, 0x61, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x61, 0x69, 0x6c, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x66, 0x61, 0x69, 0x6c, 0x54, 0x69, 0x6d, 0x65, 0x12, + 0x20, 0x0a, 0x0c, 0x66, 0x61, 0x69, 0x6c, 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x73, 0x61, 0x74, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x66, 0x61, 0x69, 0x6c, 0x41, 0x6d, 0x74, 0x53, 0x61, + 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x66, 0x61, 0x69, 0x6c, 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, + 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x66, 0x61, 0x69, 0x6c, 0x41, 0x6d, + 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, + 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x73, 0x75, 0x63, + 0x63, 0x65, 0x73, 0x73, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x73, 0x75, 0x63, 0x63, + 0x65, 0x73, 0x73, 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0d, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x41, 0x6d, 0x74, 0x53, 0x61, 0x74, + 0x12, 0x28, 0x0a, 0x10, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x61, 0x6d, 0x74, 0x5f, + 0x6d, 0x73, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x73, 0x75, 0x63, 0x63, + 0x65, 0x73, 0x73, 0x41, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, + 0x22, 0x20, 0x0a, 0x1e, 0x47, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, + 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x22, 0x5a, 0x0a, 0x1f, 0x47, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, + 0x63, 0x2e, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x59, + 0x0a, 0x1e, 0x53, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, + 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x37, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1f, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x69, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x21, 0x0a, 0x1f, 0x53, 0x65, 0x74, + 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x80, 0x02, 0x0a, + 0x14, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2a, 0x0a, 0x11, 0x68, 0x61, 0x6c, 0x66, 0x5f, 0x6c, 0x69, + 0x66, 0x65, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x0f, 0x68, 0x61, 0x6c, 0x66, 0x4c, 0x69, 0x66, 0x65, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, + 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x68, 0x6f, 0x70, 0x5f, 0x70, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, + 0x6c, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x02, 0x52, 0x0e, 0x68, 0x6f, 0x70, 0x50, + 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x77, 0x65, + 0x69, 0x67, 0x68, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x02, 0x52, 0x06, 0x77, 0x65, 0x69, 0x67, + 0x68, 0x74, 0x12, 0x36, 0x0a, 0x17, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x70, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x15, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x50, 0x61, 0x79, 0x6d, + 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x43, 0x0a, 0x1e, 0x6d, 0x69, + 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x72, 0x65, + 0x6c, 0x61, 0x78, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x1b, 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x46, 0x61, 0x69, 0x6c, 0x75, + 0x72, 0x65, 0x52, 0x65, 0x6c, 0x61, 0x78, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x22, + 0x6a, 0x0a, 0x17, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, + 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x72, + 0x6f, 0x6d, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x66, + 0x72, 0x6f, 0x6d, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x6f, 0x5f, 0x6e, 0x6f, + 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x74, 0x6f, 0x4e, 0x6f, 0x64, 0x65, + 0x12, 0x19, 0x0a, 0x08, 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x07, 0x61, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x22, 0x6b, 0x0a, 0x18, 0x51, + 0x75, 0x65, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x62, 0x61, + 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0b, 0x70, 0x72, + 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x2d, 0x0a, 0x07, 0x68, 0x69, 0x73, + 0x74, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x72, 0x6f, 0x75, + 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x69, 0x72, 0x44, 0x61, 0x74, 0x61, 0x52, + 0x07, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x22, 0xca, 0x01, 0x0a, 0x11, 0x42, 0x75, 0x69, + 0x6c, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, + 0x0a, 0x08, 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x07, 0x61, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x66, 0x69, 0x6e, + 0x61, 0x6c, 0x5f, 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x0e, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x43, 0x6c, 0x74, 0x76, 0x44, 0x65, + 0x6c, 0x74, 0x61, 0x12, 0x2c, 0x0a, 0x10, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, + 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, + 0x01, 0x52, 0x0e, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, + 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x68, 0x6f, 0x70, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x73, + 0x18, 0x04, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0a, 0x68, 0x6f, 0x70, 0x50, 0x75, 0x62, 0x6b, 0x65, + 0x79, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x64, + 0x64, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, + 0x74, 0x41, 0x64, 0x64, 0x72, 0x22, 0x38, 0x0a, 0x12, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x52, 0x6f, + 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x22, 0x0a, 0x05, 0x72, + 0x6f, 0x75, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x05, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x22, + 0x1c, 0x0a, 0x1a, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x48, 0x74, 0x6c, 0x63, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xf6, 0x04, + 0x0a, 0x09, 0x48, 0x74, 0x6c, 0x63, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x69, + 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x11, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, + 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x64, 0x12, 0x2e, 0x0a, 0x13, 0x6f, + 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, + 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x11, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, + 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x69, + 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x69, 0x64, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x48, + 0x74, 0x6c, 0x63, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, + 0x67, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x0e, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x64, 0x12, + 0x21, 0x0a, 0x0c, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x5f, 0x6e, 0x73, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x4e, 0x73, 0x12, 0x3d, 0x0a, 0x0a, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1e, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, + 0x70, 0x63, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x09, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, + 0x65, 0x12, 0x3e, 0x0a, 0x0d, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x65, 0x76, 0x65, + 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, + 0x72, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x48, 0x00, 0x52, 0x0c, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x12, 0x4b, 0x0a, 0x12, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x66, 0x61, 0x69, + 0x6c, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, + 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, + 0x64, 0x46, 0x61, 0x69, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x10, 0x66, 0x6f, + 0x72, 0x77, 0x61, 0x72, 0x64, 0x46, 0x61, 0x69, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x3b, + 0x0a, 0x0c, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x09, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, + 0x2e, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0b, + 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x42, 0x0a, 0x0f, 0x6c, + 0x69, 0x6e, 0x6b, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x0a, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, + 0x2e, 0x4c, 0x69, 0x6e, 0x6b, 0x46, 0x61, 0x69, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, + 0x52, 0x0d, 0x6c, 0x69, 0x6e, 0x6b, 0x46, 0x61, 0x69, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, + 0x3c, 0x0a, 0x09, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, + 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x45, 0x4e, + 0x44, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x52, 0x45, 0x43, 0x45, 0x49, 0x56, 0x45, 0x10, 0x02, + 0x12, 0x0b, 0x0a, 0x07, 0x46, 0x4f, 0x52, 0x57, 0x41, 0x52, 0x44, 0x10, 0x03, 0x42, 0x07, 0x0a, + 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0xbc, 0x01, 0x0a, 0x08, 0x48, 0x74, 0x6c, 0x63, 0x49, + 0x6e, 0x66, 0x6f, 0x12, 0x2b, 0x0a, 0x11, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, + 0x74, 0x69, 0x6d, 0x65, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, + 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x54, 0x69, 0x6d, 0x65, 0x6c, 0x6f, 0x63, 0x6b, + 0x12, 0x2b, 0x0a, 0x11, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x69, 0x6d, + 0x65, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x6f, 0x75, 0x74, + 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x54, 0x69, 0x6d, 0x65, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x2a, 0x0a, + 0x11, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, + 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, + 0x6e, 0x67, 0x41, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x2a, 0x0a, 0x11, 0x6f, 0x75, 0x74, + 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x41, 0x6d, + 0x74, 0x4d, 0x73, 0x61, 0x74, 0x22, 0x37, 0x0a, 0x0c, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x27, 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, + 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x22, 0x12, + 0x0a, 0x10, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x46, 0x61, 0x69, 0x6c, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x22, 0x29, 0x0a, 0x0b, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x22, 0xdf, 0x01, + 0x0a, 0x0d, 0x4c, 0x69, 0x6e, 0x6b, 0x46, 0x61, 0x69, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, + 0x27, 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, + 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, + 0x66, 0x6f, 0x52, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x3d, 0x0a, 0x0c, 0x77, 0x69, 0x72, 0x65, + 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x46, + 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x0b, 0x77, 0x69, 0x72, 0x65, + 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x12, 0x3f, 0x0a, 0x0e, 0x66, 0x61, 0x69, 0x6c, 0x75, + 0x72, 0x65, 0x5f, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x18, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x61, 0x69, 0x6c, + 0x75, 0x72, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x52, 0x0d, 0x66, 0x61, 0x69, 0x6c, 0x75, + 0x72, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x12, 0x25, 0x0a, 0x0e, 0x66, 0x61, 0x69, 0x6c, + 0x75, 0x72, 0x65, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0d, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x22, + 0x8a, 0x01, 0x0a, 0x0d, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x12, 0x2d, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x17, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, + 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, + 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x28, 0x0a, 0x05, + 0x68, 0x74, 0x6c, 0x63, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x48, 0x54, 0x4c, 0x43, 0x41, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x52, + 0x05, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x22, 0x3e, 0x0a, 0x0a, + 0x43, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x17, 0x0a, 0x07, 0x63, 0x68, + 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x63, 0x68, 0x61, + 0x6e, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x68, 0x74, 0x6c, 0x63, 0x49, 0x64, 0x22, 0xbf, 0x04, 0x0a, + 0x1b, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x74, 0x65, + 0x72, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x47, 0x0a, 0x14, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x4b, 0x65, 0x79, 0x52, 0x12, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x43, 0x69, 0x72, 0x63, 0x75, - 0x69, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x3b, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, - 0x63, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x48, 0x6f, 0x6c, 0x64, 0x46, 0x6f, 0x72, - 0x77, 0x61, 0x72, 0x64, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x22, 0x82, - 0x01, 0x0a, 0x17, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x32, 0x0a, 0x0a, 0x63, 0x68, - 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, - 0x69, 0x6e, 0x74, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x33, - 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, - 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x61, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x22, 0x1a, 0x0a, 0x18, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, - 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2a, - 0x81, 0x04, 0x0a, 0x0d, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, - 0x6c, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0d, - 0x0a, 0x09, 0x4e, 0x4f, 0x5f, 0x44, 0x45, 0x54, 0x41, 0x49, 0x4c, 0x10, 0x01, 0x12, 0x10, 0x0a, - 0x0c, 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x44, 0x45, 0x43, 0x4f, 0x44, 0x45, 0x10, 0x02, 0x12, - 0x15, 0x0a, 0x11, 0x4c, 0x49, 0x4e, 0x4b, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x45, 0x4c, 0x49, 0x47, - 0x49, 0x42, 0x4c, 0x45, 0x10, 0x03, 0x12, 0x14, 0x0a, 0x10, 0x4f, 0x4e, 0x5f, 0x43, 0x48, 0x41, - 0x49, 0x4e, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x04, 0x12, 0x14, 0x0a, 0x10, - 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x45, 0x58, 0x43, 0x45, 0x45, 0x44, 0x53, 0x5f, 0x4d, 0x41, 0x58, - 0x10, 0x05, 0x12, 0x18, 0x0a, 0x14, 0x49, 0x4e, 0x53, 0x55, 0x46, 0x46, 0x49, 0x43, 0x49, 0x45, - 0x4e, 0x54, 0x5f, 0x42, 0x41, 0x4c, 0x41, 0x4e, 0x43, 0x45, 0x10, 0x06, 0x12, 0x16, 0x0a, 0x12, - 0x49, 0x4e, 0x43, 0x4f, 0x4d, 0x50, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x46, 0x4f, 0x52, 0x57, 0x41, - 0x52, 0x44, 0x10, 0x07, 0x12, 0x13, 0x0a, 0x0f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x44, 0x44, - 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x08, 0x12, 0x15, 0x0a, 0x11, 0x46, 0x4f, 0x52, - 0x57, 0x41, 0x52, 0x44, 0x53, 0x5f, 0x44, 0x49, 0x53, 0x41, 0x42, 0x4c, 0x45, 0x44, 0x10, 0x09, - 0x12, 0x14, 0x0a, 0x10, 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, 0x5f, 0x43, 0x41, 0x4e, 0x43, - 0x45, 0x4c, 0x45, 0x44, 0x10, 0x0a, 0x12, 0x15, 0x0a, 0x11, 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, - 0x45, 0x5f, 0x55, 0x4e, 0x44, 0x45, 0x52, 0x50, 0x41, 0x49, 0x44, 0x10, 0x0b, 0x12, 0x1b, 0x0a, - 0x17, 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, 0x5f, 0x45, 0x58, 0x50, 0x49, 0x52, 0x59, 0x5f, - 0x54, 0x4f, 0x4f, 0x5f, 0x53, 0x4f, 0x4f, 0x4e, 0x10, 0x0c, 0x12, 0x14, 0x0a, 0x10, 0x49, 0x4e, - 0x56, 0x4f, 0x49, 0x43, 0x45, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x4f, 0x50, 0x45, 0x4e, 0x10, 0x0d, - 0x12, 0x17, 0x0a, 0x13, 0x4d, 0x50, 0x50, 0x5f, 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, 0x5f, - 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x0e, 0x12, 0x14, 0x0a, 0x10, 0x41, 0x44, 0x44, - 0x52, 0x45, 0x53, 0x53, 0x5f, 0x4d, 0x49, 0x53, 0x4d, 0x41, 0x54, 0x43, 0x48, 0x10, 0x0f, 0x12, - 0x16, 0x0a, 0x12, 0x53, 0x45, 0x54, 0x5f, 0x54, 0x4f, 0x54, 0x41, 0x4c, 0x5f, 0x4d, 0x49, 0x53, - 0x4d, 0x41, 0x54, 0x43, 0x48, 0x10, 0x10, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x45, 0x54, 0x5f, 0x54, - 0x4f, 0x54, 0x41, 0x4c, 0x5f, 0x54, 0x4f, 0x4f, 0x5f, 0x4c, 0x4f, 0x57, 0x10, 0x11, 0x12, 0x10, - 0x0a, 0x0c, 0x53, 0x45, 0x54, 0x5f, 0x4f, 0x56, 0x45, 0x52, 0x50, 0x41, 0x49, 0x44, 0x10, 0x12, - 0x12, 0x13, 0x0a, 0x0f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x49, 0x4e, 0x56, 0x4f, - 0x49, 0x43, 0x45, 0x10, 0x13, 0x12, 0x13, 0x0a, 0x0f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, - 0x5f, 0x4b, 0x45, 0x59, 0x53, 0x45, 0x4e, 0x44, 0x10, 0x14, 0x12, 0x13, 0x0a, 0x0f, 0x4d, 0x50, - 0x50, 0x5f, 0x49, 0x4e, 0x5f, 0x50, 0x52, 0x4f, 0x47, 0x52, 0x45, 0x53, 0x53, 0x10, 0x15, 0x12, - 0x12, 0x0a, 0x0e, 0x43, 0x49, 0x52, 0x43, 0x55, 0x4c, 0x41, 0x52, 0x5f, 0x52, 0x4f, 0x55, 0x54, - 0x45, 0x10, 0x16, 0x2a, 0xae, 0x01, 0x0a, 0x0c, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, - 0x74, 0x61, 0x74, 0x65, 0x12, 0x0d, 0x0a, 0x09, 0x49, 0x4e, 0x5f, 0x46, 0x4c, 0x49, 0x47, 0x48, - 0x54, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x55, 0x43, 0x43, 0x45, 0x45, 0x44, 0x45, 0x44, - 0x10, 0x01, 0x12, 0x12, 0x0a, 0x0e, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x54, 0x49, 0x4d, - 0x45, 0x4f, 0x55, 0x54, 0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, - 0x5f, 0x4e, 0x4f, 0x5f, 0x52, 0x4f, 0x55, 0x54, 0x45, 0x10, 0x03, 0x12, 0x10, 0x0a, 0x0c, 0x46, - 0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x12, 0x24, 0x0a, - 0x20, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, - 0x54, 0x5f, 0x50, 0x41, 0x59, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x44, 0x45, 0x54, 0x41, 0x49, 0x4c, - 0x53, 0x10, 0x05, 0x12, 0x1f, 0x0a, 0x1b, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x49, 0x4e, - 0x53, 0x55, 0x46, 0x46, 0x49, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x5f, 0x42, 0x41, 0x4c, 0x41, 0x4e, - 0x43, 0x45, 0x10, 0x06, 0x2a, 0x3c, 0x0a, 0x18, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x48, - 0x6f, 0x6c, 0x64, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x45, 0x54, 0x54, 0x4c, 0x45, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, - 0x46, 0x41, 0x49, 0x4c, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x52, 0x45, 0x53, 0x55, 0x4d, 0x45, - 0x10, 0x02, 0x2a, 0x35, 0x0a, 0x10, 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0a, 0x0a, 0x06, 0x45, 0x4e, 0x41, 0x42, 0x4c, 0x45, - 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x49, 0x53, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x01, 0x12, - 0x08, 0x0a, 0x04, 0x41, 0x55, 0x54, 0x4f, 0x10, 0x02, 0x32, 0xf1, 0x0b, 0x0a, 0x06, 0x52, 0x6f, - 0x75, 0x74, 0x65, 0x72, 0x12, 0x40, 0x0a, 0x0d, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, - 0x65, 0x6e, 0x74, 0x56, 0x32, 0x12, 0x1d, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, - 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, - 0x6d, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x42, 0x0a, 0x0e, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x50, - 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x56, 0x32, 0x12, 0x1e, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, - 0x72, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x4b, 0x0a, 0x10, 0x45, 0x73, - 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x46, 0x65, 0x65, 0x12, 0x1a, - 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, - 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x72, 0x6f, 0x75, - 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x51, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x54, - 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x1d, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, + 0x69, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x14, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, + 0x67, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x12, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x41, 0x6d, 0x6f, + 0x75, 0x6e, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x69, 0x6e, 0x63, 0x6f, 0x6d, + 0x69, 0x6e, 0x67, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x0e, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, + 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, + 0x61, 0x73, 0x68, 0x12, 0x3b, 0x0a, 0x1a, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, + 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x17, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, + 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, + 0x12, 0x30, 0x0a, 0x14, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x6d, 0x6f, + 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x12, + 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x73, + 0x61, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x65, + 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x6f, 0x75, 0x74, + 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x60, 0x0a, 0x0e, 0x63, + 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x08, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, + 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, + 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x43, 0x75, 0x73, 0x74, + 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, + 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x1d, 0x0a, + 0x0a, 0x6f, 0x6e, 0x69, 0x6f, 0x6e, 0x5f, 0x62, 0x6c, 0x6f, 0x62, 0x18, 0x09, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x09, 0x6f, 0x6e, 0x69, 0x6f, 0x6e, 0x42, 0x6c, 0x6f, 0x62, 0x1a, 0x40, 0x0a, 0x12, + 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xa8, + 0x02, 0x0a, 0x1c, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, + 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x47, 0x0a, 0x14, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x69, 0x72, 0x63, + 0x75, 0x69, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, + 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x69, 0x72, 0x63, 0x75, 0x69, + 0x74, 0x4b, 0x65, 0x79, 0x52, 0x12, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x43, 0x69, + 0x72, 0x63, 0x75, 0x69, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x3b, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, + 0x72, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x48, 0x6f, 0x6c, 0x64, + 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x61, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, + 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x6d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x66, 0x61, 0x69, 0x6c, + 0x75, 0x72, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x3d, 0x0a, 0x0c, 0x66, 0x61, + 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, + 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x0b, 0x66, 0x61, + 0x69, 0x6c, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x82, 0x01, 0x0a, 0x17, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x32, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, + 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x09, + 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x33, 0x0a, 0x06, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x72, 0x6f, 0x75, 0x74, + 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x1a, + 0x0a, 0x18, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2a, 0x81, 0x04, 0x0a, 0x0d, 0x46, + 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x12, 0x0b, 0x0a, 0x07, + 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x4e, 0x4f, 0x5f, + 0x44, 0x45, 0x54, 0x41, 0x49, 0x4c, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x4f, 0x4e, 0x49, 0x4f, + 0x4e, 0x5f, 0x44, 0x45, 0x43, 0x4f, 0x44, 0x45, 0x10, 0x02, 0x12, 0x15, 0x0a, 0x11, 0x4c, 0x49, + 0x4e, 0x4b, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x45, 0x4c, 0x49, 0x47, 0x49, 0x42, 0x4c, 0x45, 0x10, + 0x03, 0x12, 0x14, 0x0a, 0x10, 0x4f, 0x4e, 0x5f, 0x43, 0x48, 0x41, 0x49, 0x4e, 0x5f, 0x54, 0x49, + 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x04, 0x12, 0x14, 0x0a, 0x10, 0x48, 0x54, 0x4c, 0x43, 0x5f, + 0x45, 0x58, 0x43, 0x45, 0x45, 0x44, 0x53, 0x5f, 0x4d, 0x41, 0x58, 0x10, 0x05, 0x12, 0x18, 0x0a, + 0x14, 0x49, 0x4e, 0x53, 0x55, 0x46, 0x46, 0x49, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x5f, 0x42, 0x41, + 0x4c, 0x41, 0x4e, 0x43, 0x45, 0x10, 0x06, 0x12, 0x16, 0x0a, 0x12, 0x49, 0x4e, 0x43, 0x4f, 0x4d, + 0x50, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x46, 0x4f, 0x52, 0x57, 0x41, 0x52, 0x44, 0x10, 0x07, 0x12, + 0x13, 0x0a, 0x0f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x44, 0x44, 0x5f, 0x46, 0x41, 0x49, 0x4c, + 0x45, 0x44, 0x10, 0x08, 0x12, 0x15, 0x0a, 0x11, 0x46, 0x4f, 0x52, 0x57, 0x41, 0x52, 0x44, 0x53, + 0x5f, 0x44, 0x49, 0x53, 0x41, 0x42, 0x4c, 0x45, 0x44, 0x10, 0x09, 0x12, 0x14, 0x0a, 0x10, 0x49, + 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, 0x5f, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x45, 0x44, 0x10, + 0x0a, 0x12, 0x15, 0x0a, 0x11, 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, 0x5f, 0x55, 0x4e, 0x44, + 0x45, 0x52, 0x50, 0x41, 0x49, 0x44, 0x10, 0x0b, 0x12, 0x1b, 0x0a, 0x17, 0x49, 0x4e, 0x56, 0x4f, + 0x49, 0x43, 0x45, 0x5f, 0x45, 0x58, 0x50, 0x49, 0x52, 0x59, 0x5f, 0x54, 0x4f, 0x4f, 0x5f, 0x53, + 0x4f, 0x4f, 0x4e, 0x10, 0x0c, 0x12, 0x14, 0x0a, 0x10, 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, + 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x4f, 0x50, 0x45, 0x4e, 0x10, 0x0d, 0x12, 0x17, 0x0a, 0x13, 0x4d, + 0x50, 0x50, 0x5f, 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, + 0x55, 0x54, 0x10, 0x0e, 0x12, 0x14, 0x0a, 0x10, 0x41, 0x44, 0x44, 0x52, 0x45, 0x53, 0x53, 0x5f, + 0x4d, 0x49, 0x53, 0x4d, 0x41, 0x54, 0x43, 0x48, 0x10, 0x0f, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x45, + 0x54, 0x5f, 0x54, 0x4f, 0x54, 0x41, 0x4c, 0x5f, 0x4d, 0x49, 0x53, 0x4d, 0x41, 0x54, 0x43, 0x48, + 0x10, 0x10, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x45, 0x54, 0x5f, 0x54, 0x4f, 0x54, 0x41, 0x4c, 0x5f, + 0x54, 0x4f, 0x4f, 0x5f, 0x4c, 0x4f, 0x57, 0x10, 0x11, 0x12, 0x10, 0x0a, 0x0c, 0x53, 0x45, 0x54, + 0x5f, 0x4f, 0x56, 0x45, 0x52, 0x50, 0x41, 0x49, 0x44, 0x10, 0x12, 0x12, 0x13, 0x0a, 0x0f, 0x55, + 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, 0x10, 0x13, + 0x12, 0x13, 0x0a, 0x0f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x4b, 0x45, 0x59, 0x53, + 0x45, 0x4e, 0x44, 0x10, 0x14, 0x12, 0x13, 0x0a, 0x0f, 0x4d, 0x50, 0x50, 0x5f, 0x49, 0x4e, 0x5f, + 0x50, 0x52, 0x4f, 0x47, 0x52, 0x45, 0x53, 0x53, 0x10, 0x15, 0x12, 0x12, 0x0a, 0x0e, 0x43, 0x49, + 0x52, 0x43, 0x55, 0x4c, 0x41, 0x52, 0x5f, 0x52, 0x4f, 0x55, 0x54, 0x45, 0x10, 0x16, 0x2a, 0xae, + 0x01, 0x0a, 0x0c, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, + 0x0d, 0x0a, 0x09, 0x49, 0x4e, 0x5f, 0x46, 0x4c, 0x49, 0x47, 0x48, 0x54, 0x10, 0x00, 0x12, 0x0d, + 0x0a, 0x09, 0x53, 0x55, 0x43, 0x43, 0x45, 0x45, 0x44, 0x45, 0x44, 0x10, 0x01, 0x12, 0x12, 0x0a, + 0x0e, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, + 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x4e, 0x4f, 0x5f, 0x52, + 0x4f, 0x55, 0x54, 0x45, 0x10, 0x03, 0x12, 0x10, 0x0a, 0x0c, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, + 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x12, 0x24, 0x0a, 0x20, 0x46, 0x41, 0x49, 0x4c, + 0x45, 0x44, 0x5f, 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x50, 0x41, 0x59, + 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x44, 0x45, 0x54, 0x41, 0x49, 0x4c, 0x53, 0x10, 0x05, 0x12, 0x1f, + 0x0a, 0x1b, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x49, 0x4e, 0x53, 0x55, 0x46, 0x46, 0x49, + 0x43, 0x49, 0x45, 0x4e, 0x54, 0x5f, 0x42, 0x41, 0x4c, 0x41, 0x4e, 0x43, 0x45, 0x10, 0x06, 0x2a, + 0x3c, 0x0a, 0x18, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x48, 0x6f, 0x6c, 0x64, 0x46, 0x6f, + 0x72, 0x77, 0x61, 0x72, 0x64, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0a, 0x0a, 0x06, 0x53, + 0x45, 0x54, 0x54, 0x4c, 0x45, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x46, 0x41, 0x49, 0x4c, 0x10, + 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x52, 0x45, 0x53, 0x55, 0x4d, 0x45, 0x10, 0x02, 0x2a, 0x35, 0x0a, + 0x10, 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x41, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x0a, 0x0a, 0x06, 0x45, 0x4e, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x00, 0x12, 0x0b, 0x0a, + 0x07, 0x44, 0x49, 0x53, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x41, 0x55, + 0x54, 0x4f, 0x10, 0x02, 0x32, 0xf1, 0x0b, 0x0a, 0x06, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x12, + 0x40, 0x0a, 0x0d, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x56, 0x32, + 0x12, 0x1d, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, + 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x30, + 0x01, 0x12, 0x42, 0x0a, 0x0e, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, + 0x74, 0x56, 0x32, 0x12, 0x1e, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, + 0x54, 0x72, 0x61, 0x63, 0x6b, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, + 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x4b, 0x0a, 0x10, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, + 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x46, 0x65, 0x65, 0x12, 0x1a, 0x2e, 0x72, 0x6f, 0x75, 0x74, + 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, + 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x51, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, + 0x65, 0x12, 0x1d, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, + 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1e, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, + 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x03, 0x88, 0x02, 0x01, 0x12, 0x42, 0x0a, 0x0d, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, + 0x6f, 0x75, 0x74, 0x65, 0x56, 0x32, 0x12, 0x1d, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, - 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x88, 0x02, 0x01, 0x12, 0x42, 0x0a, 0x0d, 0x53, 0x65, - 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x56, 0x32, 0x12, 0x1d, 0x2e, 0x72, 0x6f, - 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, - 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x48, 0x54, 0x4c, 0x43, 0x41, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x12, 0x64, - 0x0a, 0x13, 0x52, 0x65, 0x73, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, - 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x12, 0x25, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, - 0x63, 0x2e, 0x52, 0x65, 0x73, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, - 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x72, - 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x65, 0x74, 0x4d, 0x69, - 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x64, 0x0a, 0x13, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4d, 0x69, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x12, 0x25, 0x2e, 0x72, 0x6f, - 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4d, 0x69, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x51, - 0x75, 0x65, 0x72, 0x79, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, - 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6a, 0x0a, 0x15, 0x58, 0x49, - 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, - 0x72, 0x6f, 0x6c, 0x12, 0x27, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, - 0x58, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, - 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x72, - 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x58, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, - 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x70, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x4d, 0x69, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x12, 0x29, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, - 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x72, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x54, + 0x4c, 0x43, 0x41, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x12, 0x64, 0x0a, 0x13, 0x52, 0x65, 0x73, + 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x12, 0x25, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, + 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, + 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x64, 0x0a, 0x13, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, + 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x12, 0x25, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, + 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, + 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, + 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4d, + 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6a, 0x0a, 0x15, 0x58, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, + 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x12, 0x27, + 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x58, 0x49, 0x6d, 0x70, 0x6f, + 0x72, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, + 0x72, 0x70, 0x63, 0x2e, 0x58, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x70, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, + 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x29, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x70, 0x0a, 0x17, 0x53, 0x65, 0x74, 0x4d, - 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x12, 0x29, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, - 0x53, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, - 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, + 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, + 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x70, 0x0a, 0x17, 0x53, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x29, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5b, 0x0a, 0x10, 0x51, 0x75, - 0x65, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x22, - 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, - 0x50, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x51, - 0x75, 0x65, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0a, 0x42, 0x75, 0x69, 0x6c, 0x64, - 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x1c, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, - 0x63, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, - 0x42, 0x75, 0x69, 0x6c, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x54, 0x0a, 0x13, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x48, - 0x74, 0x6c, 0x63, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x25, 0x2e, 0x72, 0x6f, 0x75, 0x74, - 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x48, - 0x74, 0x6c, 0x63, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x14, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x74, 0x6c, - 0x63, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x4d, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, - 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1d, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, - 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, - 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x22, 0x03, 0x88, 0x02, 0x01, 0x30, 0x01, 0x12, 0x4f, 0x0a, 0x0c, 0x54, 0x72, 0x61, 0x63, 0x6b, - 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1e, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, - 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, - 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x22, 0x03, 0x88, 0x02, 0x01, 0x30, 0x01, 0x12, 0x66, 0x0a, 0x0f, 0x48, 0x74, 0x6c, 0x63, - 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x27, 0x2e, 0x72, 0x6f, - 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x48, - 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x1a, 0x26, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, - 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x74, 0x65, - 0x72, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x28, 0x01, 0x30, 0x01, - 0x12, 0x5b, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x12, 0x22, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, - 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, - 0x72, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x31, 0x5a, - 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, - 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, - 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x72, 0x6f, 0x75, 0x74, + 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5b, 0x0a, 0x10, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x72, + 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x22, 0x2e, 0x72, 0x6f, 0x75, 0x74, + 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x62, 0x61, + 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, + 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, + 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0a, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x65, + 0x12, 0x1c, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x75, 0x69, + 0x6c, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, + 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, + 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, + 0x13, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x48, 0x74, 0x6c, 0x63, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x73, 0x12, 0x25, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, + 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x48, 0x74, 0x6c, 0x63, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x72, 0x6f, + 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x30, 0x01, 0x12, 0x4d, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, + 0x6e, 0x74, 0x12, 0x1d, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, + 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x03, 0x88, 0x02, 0x01, + 0x30, 0x01, 0x12, 0x4f, 0x0a, 0x0c, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x50, 0x61, 0x79, 0x6d, 0x65, + 0x6e, 0x74, 0x12, 0x1e, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x54, + 0x72, 0x61, 0x63, 0x6b, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x50, + 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x03, 0x88, 0x02, + 0x01, 0x30, 0x01, 0x12, 0x66, 0x0a, 0x0f, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, + 0x63, 0x65, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x27, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, + 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, + 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, + 0x26, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, + 0x61, 0x72, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x28, 0x01, 0x30, 0x01, 0x12, 0x5b, 0x0a, 0x10, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, + 0x22, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x31, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, + 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, } var ( @@ -3420,47 +3479,48 @@ var file_routerrpc_router_proto_depIdxs = []int32{ 42, // 24: routerrpc.ForwardHtlcInterceptRequest.custom_records:type_name -> routerrpc.ForwardHtlcInterceptRequest.CustomRecordsEntry 36, // 25: routerrpc.ForwardHtlcInterceptResponse.incoming_circuit_key:type_name -> routerrpc.CircuitKey 2, // 26: routerrpc.ForwardHtlcInterceptResponse.action:type_name -> routerrpc.ResolveHoldForwardAction - 49, // 27: routerrpc.UpdateChanStatusRequest.chan_point:type_name -> lnrpc.ChannelPoint - 3, // 28: routerrpc.UpdateChanStatusRequest.action:type_name -> routerrpc.ChanStatusAction - 5, // 29: routerrpc.Router.SendPaymentV2:input_type -> routerrpc.SendPaymentRequest - 6, // 30: routerrpc.Router.TrackPaymentV2:input_type -> routerrpc.TrackPaymentRequest - 7, // 31: routerrpc.Router.EstimateRouteFee:input_type -> routerrpc.RouteFeeRequest - 9, // 32: routerrpc.Router.SendToRoute:input_type -> routerrpc.SendToRouteRequest - 9, // 33: routerrpc.Router.SendToRouteV2:input_type -> routerrpc.SendToRouteRequest - 11, // 34: routerrpc.Router.ResetMissionControl:input_type -> routerrpc.ResetMissionControlRequest - 13, // 35: routerrpc.Router.QueryMissionControl:input_type -> routerrpc.QueryMissionControlRequest - 15, // 36: routerrpc.Router.XImportMissionControl:input_type -> routerrpc.XImportMissionControlRequest - 19, // 37: routerrpc.Router.GetMissionControlConfig:input_type -> routerrpc.GetMissionControlConfigRequest - 21, // 38: routerrpc.Router.SetMissionControlConfig:input_type -> routerrpc.SetMissionControlConfigRequest - 24, // 39: routerrpc.Router.QueryProbability:input_type -> routerrpc.QueryProbabilityRequest - 26, // 40: routerrpc.Router.BuildRoute:input_type -> routerrpc.BuildRouteRequest - 28, // 41: routerrpc.Router.SubscribeHtlcEvents:input_type -> routerrpc.SubscribeHtlcEventsRequest - 5, // 42: routerrpc.Router.SendPayment:input_type -> routerrpc.SendPaymentRequest - 6, // 43: routerrpc.Router.TrackPayment:input_type -> routerrpc.TrackPaymentRequest - 38, // 44: routerrpc.Router.HtlcInterceptor:input_type -> routerrpc.ForwardHtlcInterceptResponse - 39, // 45: routerrpc.Router.UpdateChanStatus:input_type -> routerrpc.UpdateChanStatusRequest - 50, // 46: routerrpc.Router.SendPaymentV2:output_type -> lnrpc.Payment - 50, // 47: routerrpc.Router.TrackPaymentV2:output_type -> lnrpc.Payment - 8, // 48: routerrpc.Router.EstimateRouteFee:output_type -> routerrpc.RouteFeeResponse - 10, // 49: routerrpc.Router.SendToRoute:output_type -> routerrpc.SendToRouteResponse - 48, // 50: routerrpc.Router.SendToRouteV2:output_type -> lnrpc.HTLCAttempt - 12, // 51: routerrpc.Router.ResetMissionControl:output_type -> routerrpc.ResetMissionControlResponse - 14, // 52: routerrpc.Router.QueryMissionControl:output_type -> routerrpc.QueryMissionControlResponse - 16, // 53: routerrpc.Router.XImportMissionControl:output_type -> routerrpc.XImportMissionControlResponse - 20, // 54: routerrpc.Router.GetMissionControlConfig:output_type -> routerrpc.GetMissionControlConfigResponse - 22, // 55: routerrpc.Router.SetMissionControlConfig:output_type -> routerrpc.SetMissionControlConfigResponse - 25, // 56: routerrpc.Router.QueryProbability:output_type -> routerrpc.QueryProbabilityResponse - 27, // 57: routerrpc.Router.BuildRoute:output_type -> routerrpc.BuildRouteResponse - 29, // 58: routerrpc.Router.SubscribeHtlcEvents:output_type -> routerrpc.HtlcEvent - 35, // 59: routerrpc.Router.SendPayment:output_type -> routerrpc.PaymentStatus - 35, // 60: routerrpc.Router.TrackPayment:output_type -> routerrpc.PaymentStatus - 37, // 61: routerrpc.Router.HtlcInterceptor:output_type -> routerrpc.ForwardHtlcInterceptRequest - 40, // 62: routerrpc.Router.UpdateChanStatus:output_type -> routerrpc.UpdateChanStatusResponse - 46, // [46:63] is the sub-list for method output_type - 29, // [29:46] is the sub-list for method input_type - 29, // [29:29] is the sub-list for extension type_name - 29, // [29:29] is the sub-list for extension extendee - 0, // [0:29] is the sub-list for field type_name + 47, // 27: routerrpc.ForwardHtlcInterceptResponse.failure_code:type_name -> lnrpc.Failure.FailureCode + 49, // 28: routerrpc.UpdateChanStatusRequest.chan_point:type_name -> lnrpc.ChannelPoint + 3, // 29: routerrpc.UpdateChanStatusRequest.action:type_name -> routerrpc.ChanStatusAction + 5, // 30: routerrpc.Router.SendPaymentV2:input_type -> routerrpc.SendPaymentRequest + 6, // 31: routerrpc.Router.TrackPaymentV2:input_type -> routerrpc.TrackPaymentRequest + 7, // 32: routerrpc.Router.EstimateRouteFee:input_type -> routerrpc.RouteFeeRequest + 9, // 33: routerrpc.Router.SendToRoute:input_type -> routerrpc.SendToRouteRequest + 9, // 34: routerrpc.Router.SendToRouteV2:input_type -> routerrpc.SendToRouteRequest + 11, // 35: routerrpc.Router.ResetMissionControl:input_type -> routerrpc.ResetMissionControlRequest + 13, // 36: routerrpc.Router.QueryMissionControl:input_type -> routerrpc.QueryMissionControlRequest + 15, // 37: routerrpc.Router.XImportMissionControl:input_type -> routerrpc.XImportMissionControlRequest + 19, // 38: routerrpc.Router.GetMissionControlConfig:input_type -> routerrpc.GetMissionControlConfigRequest + 21, // 39: routerrpc.Router.SetMissionControlConfig:input_type -> routerrpc.SetMissionControlConfigRequest + 24, // 40: routerrpc.Router.QueryProbability:input_type -> routerrpc.QueryProbabilityRequest + 26, // 41: routerrpc.Router.BuildRoute:input_type -> routerrpc.BuildRouteRequest + 28, // 42: routerrpc.Router.SubscribeHtlcEvents:input_type -> routerrpc.SubscribeHtlcEventsRequest + 5, // 43: routerrpc.Router.SendPayment:input_type -> routerrpc.SendPaymentRequest + 6, // 44: routerrpc.Router.TrackPayment:input_type -> routerrpc.TrackPaymentRequest + 38, // 45: routerrpc.Router.HtlcInterceptor:input_type -> routerrpc.ForwardHtlcInterceptResponse + 39, // 46: routerrpc.Router.UpdateChanStatus:input_type -> routerrpc.UpdateChanStatusRequest + 50, // 47: routerrpc.Router.SendPaymentV2:output_type -> lnrpc.Payment + 50, // 48: routerrpc.Router.TrackPaymentV2:output_type -> lnrpc.Payment + 8, // 49: routerrpc.Router.EstimateRouteFee:output_type -> routerrpc.RouteFeeResponse + 10, // 50: routerrpc.Router.SendToRoute:output_type -> routerrpc.SendToRouteResponse + 48, // 51: routerrpc.Router.SendToRouteV2:output_type -> lnrpc.HTLCAttempt + 12, // 52: routerrpc.Router.ResetMissionControl:output_type -> routerrpc.ResetMissionControlResponse + 14, // 53: routerrpc.Router.QueryMissionControl:output_type -> routerrpc.QueryMissionControlResponse + 16, // 54: routerrpc.Router.XImportMissionControl:output_type -> routerrpc.XImportMissionControlResponse + 20, // 55: routerrpc.Router.GetMissionControlConfig:output_type -> routerrpc.GetMissionControlConfigResponse + 22, // 56: routerrpc.Router.SetMissionControlConfig:output_type -> routerrpc.SetMissionControlConfigResponse + 25, // 57: routerrpc.Router.QueryProbability:output_type -> routerrpc.QueryProbabilityResponse + 27, // 58: routerrpc.Router.BuildRoute:output_type -> routerrpc.BuildRouteResponse + 29, // 59: routerrpc.Router.SubscribeHtlcEvents:output_type -> routerrpc.HtlcEvent + 35, // 60: routerrpc.Router.SendPayment:output_type -> routerrpc.PaymentStatus + 35, // 61: routerrpc.Router.TrackPayment:output_type -> routerrpc.PaymentStatus + 37, // 62: routerrpc.Router.HtlcInterceptor:output_type -> routerrpc.ForwardHtlcInterceptRequest + 40, // 63: routerrpc.Router.UpdateChanStatus:output_type -> routerrpc.UpdateChanStatusResponse + 47, // [47:64] is the sub-list for method output_type + 30, // [30:47] is the sub-list for method input_type + 30, // [30:30] is the sub-list for extension type_name + 30, // [30:30] is the sub-list for extension extendee + 0, // [0:30] is the sub-list for field type_name } func init() { file_routerrpc_router_proto_init() } diff --git a/lnrpc/routerrpc/router.proto b/lnrpc/routerrpc/router.proto index 79becfdf20..edb3fb4dfb 100644 --- a/lnrpc/routerrpc/router.proto +++ b/lnrpc/routerrpc/router.proto @@ -284,6 +284,12 @@ message SendPaymentRequest { If set, an AMP-payment will be attempted. */ bool amp = 22; + + /* + The time preference for this payment. Set to -1 to optimize for fees + only, to 1 to optimize for reliability only or a value inbetween for a mix. + */ + double time_pref = 23; } message TrackPaymentRequest { @@ -360,6 +366,11 @@ message QueryMissionControlResponse { message XImportMissionControlRequest { // Node pair-level mission control state to be imported. repeated PairHistory pairs = 1; + + // Whether to force override MC pair history. Note that even with force + // override the failure pair is imported before the success pair and both + // still clamp existing failure/success amounts. + bool force = 2; } message XImportMissionControlResponse { @@ -776,6 +787,21 @@ message ForwardHtlcInterceptResponse { // The preimage in case the resolve action is Settle. bytes preimage = 3; + + // Encrypted failure message in case the resolve action is Fail. + // + // If failure_message is specified, the failure_code field must be set + // to zero. + bytes failure_message = 4; + + // Return the specified failure code in case the resolve action is Fail. The + // message data fields are populated automatically. + // + // If a non-zero failure_code is specified, failure_message must not be set. + // + // For backwards-compatibility reasons, TEMPORARY_CHANNEL_FAILURE is the + // default value for this field. + lnrpc.Failure.FailureCode failure_code = 5; } enum ResolveHoldForwardAction { diff --git a/lnrpc/routerrpc/router.swagger.json b/lnrpc/routerrpc/router.swagger.json index 98f5689132..82ec213f0e 100644 --- a/lnrpc/routerrpc/router.swagger.json +++ b/lnrpc/routerrpc/router.swagger.json @@ -819,6 +819,11 @@ "format": "byte" }, "description": "An optional set of key-value TLV records. This is useful within the context\nof the SendToRoute call as it allows callers to specify arbitrary K-V pairs\nto drop off at each hop within the onion." + }, + "metadata": { + "type": "string", + "format": "byte", + "description": "The payment metadata to send along with the payment to the payee." } } }, @@ -1201,6 +1206,15 @@ "type": "string", "format": "byte", "description": "The preimage in case the resolve action is Settle." + }, + "failure_message": { + "type": "string", + "format": "byte", + "description": "Encrypted failure message in case the resolve action is Fail.\n\nIf failure_message is specified, the failure_code field must be set\nto zero." + }, + "failure_code": { + "$ref": "#/definitions/FailureFailureCode", + "description": "Return the specified failure code in case the resolve action is Fail. The\nmessage data fields are populated automatically.\n\nIf a non-zero failure_code is specified, failure_message must not be set.\n\nFor backwards-compatibility reasons, TEMPORARY_CHANNEL_FAILURE is the\ndefault value for this field." } }, "description": "*\nForwardHtlcInterceptResponse enables the caller to resolve a previously hold\nforward. The caller can choose either to:\n- `Resume`: Execute the default behavior (usually forward).\n- `Reject`: Fail the htlc backwards.\n- `Settle`: Settle this htlc with a given preimage." @@ -1626,6 +1640,11 @@ "amp": { "type": "boolean", "description": "If set, an AMP-payment will be attempted." + }, + "time_pref": { + "type": "number", + "format": "double", + "description": "The time preference for this payment. Set to -1 to optimize for fees\nonly, to 1 to optimize for reliability only or a value inbetween for a mix." } } }, @@ -1702,6 +1721,10 @@ "$ref": "#/definitions/routerrpcPairHistory" }, "description": "Node pair-level mission control state to be imported." + }, + "force": { + "type": "boolean", + "description": "Whether to force override MC pair history. Note that even with force\noverride the failure pair is imported before the success pair and both\nstill clamp existing failure/success amounts." } } }, diff --git a/lnrpc/routerrpc/router_backend.go b/lnrpc/routerrpc/router_backend.go index d39ff7a7d5..a372af521c 100644 --- a/lnrpc/routerrpc/router_backend.go +++ b/lnrpc/routerrpc/router_backend.go @@ -9,10 +9,10 @@ import ( math "math" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/feature" "github.com/lightningnetwork/lnd/htlcswitch" @@ -31,7 +31,7 @@ const ( // for MPP when the user is attempting to send a payment. // // TODO(roasbeef): make this value dynamic based on expected number of - // attempts for given amount + // attempts for given amount. DefaultMaxParts = 16 ) @@ -53,7 +53,8 @@ type RouterBackend struct { // FindRoutes is a closure that abstracts away how we locate/query for // routes. FindRoute func(source, target route.Vertex, - amt lnwire.MilliSatoshi, restrictions *routing.RestrictParams, + amt lnwire.MilliSatoshi, timePref float64, + restrictions *routing.RestrictParams, destCustomRecords record.CustomSet, routeHints map[route.Vertex][]*channeldb.CachedEdgePolicy, finalExpiry uint16) (*route.Route, error) @@ -115,7 +116,7 @@ type MissionControl interface { // ImportHistory imports the mission control snapshot to our internal // state. This import will only be applied in-memory, and will not be // persisted across restarts. - ImportHistory(*routing.MissionControlSnapshot) error + ImportHistory(snapshot *routing.MissionControlSnapshot, force bool) error // GetPairHistorySnapshot returns the stored history for a given node // pair. @@ -132,7 +133,7 @@ type MissionControl interface { // QueryRoutes attempts to query the daemons' Channel Router for a possible // route to a target destination capable of carrying a specific amount of -// satoshis within the route's flow. The retuned route contains the full +// satoshis within the route's flow. The returned route contains the full // details required to craft and send an HTLC, also including the necessary // information that should be present within the Sphinx packet encapsulated // within the HTLC. @@ -324,7 +325,7 @@ func (r *RouterBackend) QueryRoutes(ctx context.Context, // can carry `in.Amt` satoshis _including_ the total fee required on // the route. route, err := r.FindRoute( - sourcePubKey, targetPubKey, amt, restrictions, + sourcePubKey, targetPubKey, amt, in.TimePref, restrictions, customRecords, routeHintEdges, finalCLTVDelta, ) if err != nil { @@ -443,6 +444,7 @@ func (r *RouterBackend) MarshallRoute(route *route.Route) (*lnrpc.Route, error) CustomRecords: hop.CustomRecords, TlvPayload: !hop.LegacyPayload, MppRecord: mpp, + Metadata: hop.Metadata, } incomingAmt = hop.AmtToForward } @@ -476,7 +478,7 @@ func UnmarshallHopWithPubkey(rpcHop *lnrpc.Hop, pubkey route.Vertex) (*route.Hop PubKeyBytes: pubkey, ChannelID: rpcHop.ChanId, CustomRecords: customRecords, - LegacyPayload: !rpcHop.TlvPayload, + LegacyPayload: false, MPP: mpp, AMP: amp, }, nil @@ -559,6 +561,12 @@ func (r *RouterBackend) extractIntentFromSendRequest( payIntent := &routing.LightningPayment{} + // Pass along time preference. + if rpcPayReq.TimePref < -1 || rpcPayReq.TimePref > 1 { + return nil, errors.New("time preference out of range") + } + payIntent.TimePref = rpcPayReq.TimePref + // Pass along restrictions on the outgoing channels that may be used. payIntent.OutgoingChannelIDs = rpcPayReq.OutgoingChanIds @@ -661,11 +669,11 @@ func (r *RouterBackend) extractIntentFromSendRequest( "cannot appear together") case len(rpcPayReq.PaymentHash) > 0: - return nil, errors.New("dest and payment_hash " + + return nil, errors.New("payment_hash and payment_request " + "cannot appear together") case rpcPayReq.FinalCltvDelta != 0: - return nil, errors.New("dest and final_cltv_delta " + + return nil, errors.New("final_cltv_delta and payment_request " + "cannot appear together") } @@ -759,6 +767,7 @@ func (r *RouterBackend) extractIntentFromSendRequest( payIntent.DestFeatures = payReq.Features payIntent.PaymentAddr = payAddr payIntent.PaymentRequest = []byte(rpcPayReq.PaymentRequest) + payIntent.Metadata = payReq.Metadata } else { // Otherwise, If the payment request field was not specified // (and a custom route wasn't specified), construct the payment @@ -927,7 +936,7 @@ func unmarshallHopHint(rpcHint *lnrpc.HopHint) (zpay32.HopHint, error) { return zpay32.HopHint{}, err } - pubkey, err := btcec.ParsePubKey(pubBytes, btcec.S256()) + pubkey, err := btcec.ParsePubKey(pubBytes) if err != nil { return zpay32.HopHint{}, err } @@ -1008,7 +1017,6 @@ func UnmarshalMPP(reqMPP *lnrpc.MPPRecord) (*record.MPP, error) { reqAddr := reqMPP.PaymentAddr switch { - // No MPP fields were provided. case reqTotal == 0 && len(reqAddr) == 0: return nil, fmt.Errorf("missing total_msat and payment_addr") @@ -1110,7 +1118,6 @@ func marshallHtlcFailure(failure *channeldb.HTLCFailInfo) (*lnrpc.Failure, } switch failure.Reason { - case channeldb.HTLCFailUnknown: rpcFailure.Code = lnrpc.Failure_UNKNOWN_FAILURE @@ -1191,7 +1198,6 @@ func marshallWireError(msg lnwire.FailureMessage, response *lnrpc.Failure) error { switch onionErr := msg.(type) { - case *lnwire.FailIncorrectDetails: response.Code = lnrpc.Failure_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS response.Height = onionErr.Height() @@ -1412,7 +1418,6 @@ func marshallPaymentFailureReason(reason *channeldb.FailureReason) ( } switch *reason { - case channeldb.FailureReasonTimeout: return lnrpc.PaymentFailureReason_FAILURE_REASON_TIMEOUT, nil diff --git a/lnrpc/routerrpc/router_backend_test.go b/lnrpc/routerrpc/router_backend_test.go index 1b05d5f81e..a5b629fe20 100644 --- a/lnrpc/routerrpc/router_backend_test.go +++ b/lnrpc/routerrpc/router_backend_test.go @@ -6,15 +6,14 @@ import ( "encoding/hex" "testing" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/record" "github.com/lightningnetwork/lnd/routing" "github.com/lightningnetwork/lnd/routing/route" "github.com/stretchr/testify/require" - - "github.com/lightningnetwork/lnd/lnrpc" ) const ( @@ -124,8 +123,8 @@ func testQueryRoutes(t *testing.T, useMissionControl bool, useMsat bool, } findRoute := func(source, target route.Vertex, - amt lnwire.MilliSatoshi, restrictions *routing.RestrictParams, - _ record.CustomSet, + amt lnwire.MilliSatoshi, _ float64, + restrictions *routing.RestrictParams, _ record.CustomSet, routeHints map[route.Vertex][]*channeldb.CachedEdgePolicy, finalExpiry uint16) (*route.Route, error) { @@ -204,7 +203,7 @@ func testQueryRoutes(t *testing.T, useMissionControl bool, useMsat bool, route.Vertex, error) { if chanID != 555 { - t.Fatal("expected endpoints to be fetched for "+ + t.Fatalf("expected endpoints to be fetched for "+ "channel 555, but got %v instead", chanID) } @@ -337,7 +336,6 @@ func TestUnmarshalMPP(t *testing.T) { func testUnmarshalMPP(t *testing.T, test unmarshalMPPTest) { mpp, err := UnmarshalMPP(test.mpp) switch test.outcome { - // Valid arguments should result in no error, a non-nil MPP record, and // the fields should be set correctly. case valid: diff --git a/lnrpc/routerrpc/router_server.go b/lnrpc/routerrpc/router_server.go index 4aef7ef253..3d5a7e8f45 100644 --- a/lnrpc/routerrpc/router_server.go +++ b/lnrpc/routerrpc/router_server.go @@ -10,8 +10,8 @@ import ( "sync/atomic" "time" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/lnrpc" @@ -20,7 +20,6 @@ import ( "github.com/lightningnetwork/lnd/macaroons" "github.com/lightningnetwork/lnd/routing" "github.com/lightningnetwork/lnd/routing/route" - "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -364,7 +363,7 @@ func (s *Server) EstimateRouteFee(ctx context.Context, // that exceeds it and is useless to us. mc := s.cfg.RouterBackend.MissionControl route, err := s.cfg.Router.FindRoute( - s.cfg.RouterBackend.SelfNode, destNode, amtMsat, + s.cfg.RouterBackend.SelfNode, destNode, amtMsat, 0, &routing.RestrictParams{ FeeLimit: feeLimit, CltvLimit: s.cfg.RouterBackend.MaxTotalTimelock, @@ -553,7 +552,9 @@ func (s *Server) XImportMissionControl(ctx context.Context, snapshot.Pairs[i] = *pairSnapshot } - err := s.cfg.RouterBackend.MissionControl.ImportHistory(snapshot) + err := s.cfg.RouterBackend.MissionControl.ImportHistory( + snapshot, req.Force, + ) if err != nil { return nil, err } @@ -664,7 +665,7 @@ func getMsatPairValue(msatValue lnwire.MilliSatoshi, return msatValue, nil } - // If we have no msatValue, we can just return our sate value even if + // If we have no msatValue, we can just return our state value even if // it is zero, because it's impossible that we have mismatched values. if msatValue == 0 { return lnwire.MilliSatoshi(satValue * 1000), nil @@ -877,7 +878,7 @@ func (s *Server) SubscribeHtlcEvents(req *SubscribeHtlcEventsRequest, // requests to the caller. // Upon connection it does the following: // 1. Check if there is already a live stream, if yes it rejects the request. -// 2. Regsitered a ForwardInterceptor +// 2. Registered a ForwardInterceptor // 3. Delivers to the caller every √√ and detect his answer. // It uses a local implementation of holdForwardsStore to keep all the hold // forwards and find them when manual resolution is later needed. @@ -889,7 +890,9 @@ func (s *Server) HtlcInterceptor(stream Router_HtlcInterceptorServer) error { defer atomic.CompareAndSwapInt32(&s.forwardInterceptorActive, 1, 0) // run the forward interceptor. - return newForwardInterceptor(s, stream).run() + return newForwardInterceptor( + s.cfg.RouterBackend.InterceptableForwarder, stream, + ).run() } func extractOutPoint(req *UpdateChanStatusRequest) (*wire.OutPoint, error) { diff --git a/lnrpc/routerrpc/routing_config.go b/lnrpc/routerrpc/routing_config.go index 2c93cb28d8..2a53ba2e4e 100644 --- a/lnrpc/routerrpc/routing_config.go +++ b/lnrpc/routerrpc/routing_config.go @@ -3,7 +3,7 @@ package routerrpc import ( "time" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) // RoutingConfig contains the configurable parameters that control routing. diff --git a/lnrpc/routerrpc/subscribe_events.go b/lnrpc/routerrpc/subscribe_events.go index d0969dc5e2..f738cf38c0 100644 --- a/lnrpc/routerrpc/subscribe_events.go +++ b/lnrpc/routerrpc/subscribe_events.go @@ -141,9 +141,7 @@ func rpcFailReason(linkErr *htlcswitch.LinkError) (lnrpc.Failure_FailureCode, default: return 0, 0, fmt.Errorf("unknown failure "+ "detail type: %T", linkErr.FailureDetail) - } - } // rpcFailureResolution maps an invoice failure resolution to a rpc failure diff --git a/lnrpc/rpc_utils.go b/lnrpc/rpc_utils.go index 0f2d9bcd6e..26182838dd 100644 --- a/lnrpc/rpc_utils.go +++ b/lnrpc/rpc_utils.go @@ -16,6 +16,56 @@ const ( RegisterRPCMiddlewareURI = "/lnrpc.Lightning/RegisterRPCMiddleware" ) +// RPCTransaction returns a rpc transaction. +func RPCTransaction(tx *lnwallet.TransactionDetail) *Transaction { + var destAddresses []string + // Re-package destination output information. + var outputDetails []*OutputDetail + for _, o := range tx.OutputDetails { + // Note: DestAddresses is deprecated but we keep + // populating it with addresses for backwards + // compatibility. + for _, a := range o.Addresses { + destAddresses = append(destAddresses, + a.EncodeAddress()) + } + + var address string + if len(o.Addresses) == 1 { + address = o.Addresses[0].EncodeAddress() + } + + outputDetails = append(outputDetails, &OutputDetail{ + OutputType: MarshallOutputType(o.OutputType), + Address: address, + PkScript: hex.EncodeToString(o.PkScript), + OutputIndex: int64(o.OutputIndex), + Amount: int64(o.Value), + IsOurAddress: o.IsOurAddress, + }) + } + + // We also get unconfirmed transactions, so BlockHash can be nil. + blockHash := "" + if tx.BlockHash != nil { + blockHash = tx.BlockHash.String() + } + + return &Transaction{ + TxHash: tx.Hash.String(), + Amount: int64(tx.Value), + NumConfirmations: tx.NumConfirmations, + BlockHash: blockHash, + BlockHeight: tx.BlockHeight, + TimeStamp: tx.Timestamp, + TotalFees: tx.TotalFees, + DestAddresses: destAddresses, + OutputDetails: outputDetails, + RawTxHex: hex.EncodeToString(tx.RawTx), + Label: tx.Label, + } +} + // RPCTransactionDetails returns a set of rpc transaction details. func RPCTransactionDetails(txns []*lnwallet.TransactionDetail) *TransactionDetails { txDetails := &TransactionDetails{ @@ -23,30 +73,7 @@ func RPCTransactionDetails(txns []*lnwallet.TransactionDetail) *TransactionDetai } for i, tx := range txns { - var destAddresses []string - for _, destAddress := range tx.DestAddresses { - destAddresses = append(destAddresses, destAddress.EncodeAddress()) - } - - // We also get unconfirmed transactions, so BlockHash can be - // nil. - blockHash := "" - if tx.BlockHash != nil { - blockHash = tx.BlockHash.String() - } - - txDetails.Transactions[i] = &Transaction{ - TxHash: tx.Hash.String(), - Amount: int64(tx.Value), - NumConfirmations: tx.NumConfirmations, - BlockHash: blockHash, - BlockHeight: tx.BlockHeight, - TimeStamp: tx.Timestamp, - TotalFees: tx.TotalFees, - DestAddresses: destAddresses, - RawTxHex: hex.EncodeToString(tx.RawTx), - Label: tx.Label, - } + txDetails.Transactions[i] = RPCTransaction(tx) } // Sort transactions by number of confirmations rather than height so diff --git a/lnrpc/signrpc/driver.go b/lnrpc/signrpc/driver.go index e000c5635e..01b07d60cf 100644 --- a/lnrpc/signrpc/driver.go +++ b/lnrpc/signrpc/driver.go @@ -36,7 +36,7 @@ func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( } // Before we try to make the new signer service instance, we'll perform - // some sanity checks on the arguments to ensure that they're useable. + // some sanity checks on the arguments to ensure that they're usable. switch { // If the macaroon service is set (we should use macaroons), then diff --git a/lnrpc/signrpc/log.go b/lnrpc/signrpc/log.go index b5ff742a08..2dc6533435 100644 --- a/lnrpc/signrpc/log.go +++ b/lnrpc/signrpc/log.go @@ -5,7 +5,7 @@ import ( "github.com/lightningnetwork/lnd/build" ) -// log is a logger that is initialized with no output filters. This +// log is a logger that is initialized with no output filters. This // means the package will not perform any logging by default until the caller // requests it. var log btclog.Logger @@ -27,19 +27,3 @@ func DisableLog() { func UseLogger(logger btclog.Logger) { log = logger } - -// logClosure is used to provide a closure over expensive logging operations so -// don't have to be performed when the logging level doesn't warrant it. -type logClosure func() string // nolint:unused - -// String invokes the underlying function and returns the result. -func (c logClosure) String() string { - return c() -} - -// newLogClosure returns a new closure over a function that returns a string -// which itself provides a Stringer interface so that it can be used with the -// logging system. -func newLogClosure(c func() string) logClosure { // nolint:unused - return logClosure(c) -} diff --git a/lnrpc/signrpc/signer.pb.go b/lnrpc/signrpc/signer.pb.go index f2096c949c..a51c671ade 100644 --- a/lnrpc/signrpc/signer.pb.go +++ b/lnrpc/signrpc/signer.pb.go @@ -20,6 +20,70 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +type SignMethod int32 + +const ( + // + //Specifies that a SegWit v0 (p2wkh, np2wkh, p2wsh) input script should be + //signed. + SignMethod_SIGN_METHOD_WITNESS_V0 SignMethod = 0 + // + //Specifies that a SegWit v1 (p2tr) input should be signed by using the + //BIP0086 method (commit to internal key only). + SignMethod_SIGN_METHOD_TAPROOT_KEY_SPEND_BIP0086 SignMethod = 1 + // + //Specifies that a SegWit v1 (p2tr) input should be signed by using a given + //taproot hash to commit to in addition to the internal key. + SignMethod_SIGN_METHOD_TAPROOT_KEY_SPEND SignMethod = 2 + // + //Specifies that a SegWit v1 (p2tr) input should be spent using the script + //path and that a specific leaf script should be signed for. + SignMethod_SIGN_METHOD_TAPROOT_SCRIPT_SPEND SignMethod = 3 +) + +// Enum value maps for SignMethod. +var ( + SignMethod_name = map[int32]string{ + 0: "SIGN_METHOD_WITNESS_V0", + 1: "SIGN_METHOD_TAPROOT_KEY_SPEND_BIP0086", + 2: "SIGN_METHOD_TAPROOT_KEY_SPEND", + 3: "SIGN_METHOD_TAPROOT_SCRIPT_SPEND", + } + SignMethod_value = map[string]int32{ + "SIGN_METHOD_WITNESS_V0": 0, + "SIGN_METHOD_TAPROOT_KEY_SPEND_BIP0086": 1, + "SIGN_METHOD_TAPROOT_KEY_SPEND": 2, + "SIGN_METHOD_TAPROOT_SCRIPT_SPEND": 3, + } +) + +func (x SignMethod) Enum() *SignMethod { + p := new(SignMethod) + *p = x + return p +} + +func (x SignMethod) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (SignMethod) Descriptor() protoreflect.EnumDescriptor { + return file_signrpc_signer_proto_enumTypes[0].Descriptor() +} + +func (SignMethod) Type() protoreflect.EnumType { + return &file_signrpc_signer_proto_enumTypes[0] +} + +func (x SignMethod) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use SignMethod.Descriptor instead. +func (SignMethod) EnumDescriptor() ([]byte, []int) { + return file_signrpc_signer_proto_rawDescGZIP(), []int{0} +} + type KeyLocator struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -83,12 +147,12 @@ type KeyDescriptor struct { unknownFields protoimpl.UnknownFields // - //The raw bytes of the key being identified. Either this or the KeyLocator - //must be specified. + //The raw bytes of the public key in the key pair being identified. Either + //this or the KeyLocator must be specified. RawKeyBytes []byte `protobuf:"bytes,1,opt,name=raw_key_bytes,json=rawKeyBytes,proto3" json:"raw_key_bytes,omitempty"` // - //The key locator that identifies which key to use for signing. Either this - //or the raw bytes of the target key must be specified. + //The key locator that identifies which private key to use for signing. + //Either this or the raw bytes of the target public key must be specified. KeyLoc *KeyLocator `protobuf:"bytes,2,opt,name=key_loc,json=keyLoc,proto3" json:"key_loc,omitempty"` } @@ -228,8 +292,20 @@ type SignDescriptor struct { //tweakPriv*sha256(tweakPub || pubKey)) mod N DoubleTweak []byte `protobuf:"bytes,3,opt,name=double_tweak,json=doubleTweak,proto3" json:"double_tweak,omitempty"` // - //The full script required to properly redeem the output. This field will - //only be populated if a p2wsh or a p2sh output is being signed. + //The 32 byte input to the taproot tweak derivation that is used to derive + //the output key from an internal key: outputKey = internalKey + + //tagged_hash("tapTweak", internalKey || tapTweak). + // + //When doing a BIP 86 spend, this field can be an empty byte slice. + // + //When doing a normal key path spend, with the output key committing to an + //actual script root, then this field should be: the tapscript root hash. + TapTweak []byte `protobuf:"bytes,10,opt,name=tap_tweak,json=tapTweak,proto3" json:"tap_tweak,omitempty"` + // + //The full script required to properly redeem the output. This field will + //only be populated if a p2tr, p2wsh or a p2sh output is being signed. If a + //taproot script path spend is being attempted, then this should be the raw + //leaf script. WitnessScript []byte `protobuf:"bytes,4,opt,name=witness_script,json=witnessScript,proto3" json:"witness_script,omitempty"` // //A description of the output being spent. The value and script MUST be @@ -242,6 +318,12 @@ type SignDescriptor struct { // //The target input within the transaction that should be signed. InputIndex int32 `protobuf:"varint,8,opt,name=input_index,json=inputIndex,proto3" json:"input_index,omitempty"` + // + //The sign method specifies how the input should be signed. Depending on the + //method, either the tap_tweak, witness_script or both need to be specified. + //Defaults to SegWit v0 signing to be backward compatible with older RPC + //clients. + SignMethod SignMethod `protobuf:"varint,9,opt,name=sign_method,json=signMethod,proto3,enum=signrpc.SignMethod" json:"sign_method,omitempty"` } func (x *SignDescriptor) Reset() { @@ -297,6 +379,13 @@ func (x *SignDescriptor) GetDoubleTweak() []byte { return nil } +func (x *SignDescriptor) GetTapTweak() []byte { + if x != nil { + return x.TapTweak + } + return nil +} + func (x *SignDescriptor) GetWitnessScript() []byte { if x != nil { return x.WitnessScript @@ -325,6 +414,13 @@ func (x *SignDescriptor) GetInputIndex() int32 { return 0 } +func (x *SignDescriptor) GetSignMethod() SignMethod { + if x != nil { + return x.SignMethod + } + return SignMethod_SIGN_METHOD_WITNESS_V0 +} + type SignReq struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -334,6 +430,10 @@ type SignReq struct { RawTxBytes []byte `protobuf:"bytes,1,opt,name=raw_tx_bytes,json=rawTxBytes,proto3" json:"raw_tx_bytes,omitempty"` // A set of sign descriptors, for each input to be signed. SignDescs []*SignDescriptor `protobuf:"bytes,2,rep,name=sign_descs,json=signDescs,proto3" json:"sign_descs,omitempty"` + // + //The full list of UTXO information for each of the inputs being spent. This + //is required when spending one or more taproot (SegWit v1) outputs. + PrevOutputs []*TxOut `protobuf:"bytes,3,rep,name=prev_outputs,json=prevOutputs,proto3" json:"prev_outputs,omitempty"` } func (x *SignReq) Reset() { @@ -382,6 +482,13 @@ func (x *SignReq) GetSignDescs() []*SignDescriptor { return nil } +func (x *SignReq) GetPrevOutputs() []*TxOut { + if x != nil { + return x.PrevOutputs + } + return nil +} + type SignResp struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -904,262 +1011,1351 @@ func (x *SharedKeyResponse) GetSharedKey() []byte { return nil } -var File_signrpc_signer_proto protoreflect.FileDescriptor +type TweakDesc struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields -var file_signrpc_signer_proto_rawDesc = []byte{ - 0x0a, 0x14, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2f, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x07, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x22, - 0x48, 0x0a, 0x0a, 0x4b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x1d, 0x0a, - 0x0a, 0x6b, 0x65, 0x79, 0x5f, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x09, 0x6b, 0x65, 0x79, 0x46, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x12, 0x1b, 0x0a, 0x09, - 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, - 0x08, 0x6b, 0x65, 0x79, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x61, 0x0a, 0x0d, 0x4b, 0x65, 0x79, - 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x22, 0x0a, 0x0d, 0x72, 0x61, - 0x77, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x0b, 0x72, 0x61, 0x77, 0x4b, 0x65, 0x79, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x2c, - 0x0a, 0x07, 0x6b, 0x65, 0x79, 0x5f, 0x6c, 0x6f, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x13, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x4c, 0x6f, 0x63, - 0x61, 0x74, 0x6f, 0x72, 0x52, 0x06, 0x6b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x22, 0x3a, 0x0a, 0x05, - 0x54, 0x78, 0x4f, 0x75, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, - 0x6b, 0x5f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, - 0x70, 0x6b, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x22, 0x93, 0x02, 0x0a, 0x0e, 0x53, 0x69, 0x67, - 0x6e, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x31, 0x0a, 0x08, 0x6b, - 0x65, 0x79, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, - 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x6f, 0x72, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x44, 0x65, 0x73, 0x63, 0x12, 0x21, - 0x0a, 0x0c, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x5f, 0x74, 0x77, 0x65, 0x61, 0x6b, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x54, 0x77, 0x65, 0x61, - 0x6b, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x5f, 0x74, 0x77, 0x65, 0x61, - 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x54, - 0x77, 0x65, 0x61, 0x6b, 0x12, 0x25, 0x0a, 0x0e, 0x77, 0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x5f, - 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x77, 0x69, - 0x74, 0x6e, 0x65, 0x73, 0x73, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x26, 0x0a, 0x06, 0x6f, - 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x73, 0x69, - 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x78, 0x4f, 0x75, 0x74, 0x52, 0x06, 0x6f, 0x75, 0x74, - 0x70, 0x75, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x69, 0x67, 0x68, 0x61, 0x73, 0x68, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x73, 0x69, 0x67, 0x68, 0x61, 0x73, 0x68, 0x12, 0x1f, 0x0a, - 0x0b, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x08, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x0a, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x63, - 0x0a, 0x07, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x71, 0x12, 0x20, 0x0a, 0x0c, 0x72, 0x61, 0x77, - 0x5f, 0x74, 0x78, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x0a, 0x72, 0x61, 0x77, 0x54, 0x78, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x36, 0x0a, 0x0a, 0x73, - 0x69, 0x67, 0x6e, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x17, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x44, 0x65, - 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x44, 0x65, - 0x73, 0x63, 0x73, 0x22, 0x25, 0x0a, 0x08, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x12, - 0x19, 0x0a, 0x08, 0x72, 0x61, 0x77, 0x5f, 0x73, 0x69, 0x67, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x0c, 0x52, 0x07, 0x72, 0x61, 0x77, 0x53, 0x69, 0x67, 0x73, 0x22, 0x46, 0x0a, 0x0b, 0x49, 0x6e, - 0x70, 0x75, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x77, 0x69, 0x74, - 0x6e, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x07, 0x77, 0x69, 0x74, 0x6e, - 0x65, 0x73, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x69, 0x67, 0x5f, 0x73, 0x63, 0x72, 0x69, 0x70, - 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x53, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x22, 0x4c, 0x0a, 0x0f, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, - 0x74, 0x52, 0x65, 0x73, 0x70, 0x12, 0x39, 0x0a, 0x0d, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x73, - 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x53, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x52, 0x0c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x73, - 0x22, 0x92, 0x01, 0x0a, 0x0e, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, - 0x52, 0x65, 0x71, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x03, 0x6d, 0x73, 0x67, 0x12, 0x2c, 0x0a, 0x07, 0x6b, 0x65, 0x79, 0x5f, 0x6c, 0x6f, 0x63, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x4b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x06, 0x6b, 0x65, 0x79, - 0x4c, 0x6f, 0x63, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x5f, 0x68, 0x61, - 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, - 0x48, 0x61, 0x73, 0x68, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x5f, - 0x73, 0x69, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x63, 0x6f, 0x6d, 0x70, 0x61, - 0x63, 0x74, 0x53, 0x69, 0x67, 0x22, 0x2f, 0x0a, 0x0f, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, - 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, - 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x5a, 0x0a, 0x10, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, - 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, - 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x12, 0x1c, 0x0a, 0x09, - 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, - 0x62, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b, - 0x65, 0x79, 0x22, 0x29, 0x0a, 0x11, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x22, 0xa2, 0x01, - 0x0a, 0x10, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x65, 0x70, 0x68, 0x65, 0x6d, 0x65, 0x72, 0x61, 0x6c, 0x5f, - 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x65, 0x70, - 0x68, 0x65, 0x6d, 0x65, 0x72, 0x61, 0x6c, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, - 0x07, 0x6b, 0x65, 0x79, 0x5f, 0x6c, 0x6f, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, - 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x61, - 0x74, 0x6f, 0x72, 0x42, 0x02, 0x18, 0x01, 0x52, 0x06, 0x6b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x12, - 0x31, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x16, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x44, - 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x44, 0x65, - 0x73, 0x63, 0x22, 0x32, 0x0a, 0x11, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x68, 0x61, 0x72, 0x65, - 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x68, 0x61, - 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x32, 0xd4, 0x02, 0x0a, 0x06, 0x53, 0x69, 0x67, 0x6e, 0x65, - 0x72, 0x12, 0x34, 0x0a, 0x0d, 0x53, 0x69, 0x67, 0x6e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, - 0x61, 0x77, 0x12, 0x10, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, - 0x6e, 0x52, 0x65, 0x71, 0x1a, 0x11, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, - 0x69, 0x67, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x12, 0x40, 0x0a, 0x12, 0x43, 0x6f, 0x6d, 0x70, 0x75, - 0x74, 0x65, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x10, 0x2e, - 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x71, 0x1a, - 0x18, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x53, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x12, 0x40, 0x0a, 0x0b, 0x53, 0x69, 0x67, - 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x17, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, - 0x71, 0x1a, 0x18, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, - 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x12, 0x46, 0x0a, 0x0d, 0x56, - 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x19, 0x2e, 0x73, - 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x1a, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, - 0x65, 0x73, 0x70, 0x12, 0x48, 0x0a, 0x0f, 0x44, 0x65, 0x72, 0x69, 0x76, 0x65, 0x53, 0x68, 0x61, - 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x12, 0x19, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x1a, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x68, 0x61, 0x72, - 0x65, 0x64, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x2f, 0x5a, - 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, - 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, - 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2f, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x62, 0x06, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + // + //Tweak is the 32-byte value that will modify the public key. + Tweak []byte `protobuf:"bytes,1,opt,name=tweak,proto3" json:"tweak,omitempty"` + // + //Specifies if the target key should be converted to an x-only public key + //before tweaking. If true, then the public key will be mapped to an x-only + //key before the tweaking operation is applied. + IsXOnly bool `protobuf:"varint,2,opt,name=is_x_only,json=isXOnly,proto3" json:"is_x_only,omitempty"` } -var ( - file_signrpc_signer_proto_rawDescOnce sync.Once - file_signrpc_signer_proto_rawDescData = file_signrpc_signer_proto_rawDesc -) +func (x *TweakDesc) Reset() { + *x = TweakDesc{} + if protoimpl.UnsafeEnabled { + mi := &file_signrpc_signer_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} -func file_signrpc_signer_proto_rawDescGZIP() []byte { - file_signrpc_signer_proto_rawDescOnce.Do(func() { - file_signrpc_signer_proto_rawDescData = protoimpl.X.CompressGZIP(file_signrpc_signer_proto_rawDescData) - }) - return file_signrpc_signer_proto_rawDescData +func (x *TweakDesc) String() string { + return protoimpl.X.MessageStringOf(x) } -var file_signrpc_signer_proto_msgTypes = make([]protoimpl.MessageInfo, 14) -var file_signrpc_signer_proto_goTypes = []interface{}{ - (*KeyLocator)(nil), // 0: signrpc.KeyLocator - (*KeyDescriptor)(nil), // 1: signrpc.KeyDescriptor - (*TxOut)(nil), // 2: signrpc.TxOut - (*SignDescriptor)(nil), // 3: signrpc.SignDescriptor - (*SignReq)(nil), // 4: signrpc.SignReq - (*SignResp)(nil), // 5: signrpc.SignResp - (*InputScript)(nil), // 6: signrpc.InputScript - (*InputScriptResp)(nil), // 7: signrpc.InputScriptResp - (*SignMessageReq)(nil), // 8: signrpc.SignMessageReq - (*SignMessageResp)(nil), // 9: signrpc.SignMessageResp - (*VerifyMessageReq)(nil), // 10: signrpc.VerifyMessageReq - (*VerifyMessageResp)(nil), // 11: signrpc.VerifyMessageResp - (*SharedKeyRequest)(nil), // 12: signrpc.SharedKeyRequest - (*SharedKeyResponse)(nil), // 13: signrpc.SharedKeyResponse +func (*TweakDesc) ProtoMessage() {} + +func (x *TweakDesc) ProtoReflect() protoreflect.Message { + mi := &file_signrpc_signer_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -var file_signrpc_signer_proto_depIdxs = []int32{ - 0, // 0: signrpc.KeyDescriptor.key_loc:type_name -> signrpc.KeyLocator - 1, // 1: signrpc.SignDescriptor.key_desc:type_name -> signrpc.KeyDescriptor - 2, // 2: signrpc.SignDescriptor.output:type_name -> signrpc.TxOut - 3, // 3: signrpc.SignReq.sign_descs:type_name -> signrpc.SignDescriptor - 6, // 4: signrpc.InputScriptResp.input_scripts:type_name -> signrpc.InputScript - 0, // 5: signrpc.SignMessageReq.key_loc:type_name -> signrpc.KeyLocator - 0, // 6: signrpc.SharedKeyRequest.key_loc:type_name -> signrpc.KeyLocator - 1, // 7: signrpc.SharedKeyRequest.key_desc:type_name -> signrpc.KeyDescriptor - 4, // 8: signrpc.Signer.SignOutputRaw:input_type -> signrpc.SignReq - 4, // 9: signrpc.Signer.ComputeInputScript:input_type -> signrpc.SignReq - 8, // 10: signrpc.Signer.SignMessage:input_type -> signrpc.SignMessageReq - 10, // 11: signrpc.Signer.VerifyMessage:input_type -> signrpc.VerifyMessageReq - 12, // 12: signrpc.Signer.DeriveSharedKey:input_type -> signrpc.SharedKeyRequest - 5, // 13: signrpc.Signer.SignOutputRaw:output_type -> signrpc.SignResp - 7, // 14: signrpc.Signer.ComputeInputScript:output_type -> signrpc.InputScriptResp - 9, // 15: signrpc.Signer.SignMessage:output_type -> signrpc.SignMessageResp - 11, // 16: signrpc.Signer.VerifyMessage:output_type -> signrpc.VerifyMessageResp - 13, // 17: signrpc.Signer.DeriveSharedKey:output_type -> signrpc.SharedKeyResponse - 13, // [13:18] is the sub-list for method output_type - 8, // [8:13] is the sub-list for method input_type - 8, // [8:8] is the sub-list for extension type_name - 8, // [8:8] is the sub-list for extension extendee - 0, // [0:8] is the sub-list for field type_name + +// Deprecated: Use TweakDesc.ProtoReflect.Descriptor instead. +func (*TweakDesc) Descriptor() ([]byte, []int) { + return file_signrpc_signer_proto_rawDescGZIP(), []int{14} } -func init() { file_signrpc_signer_proto_init() } -func file_signrpc_signer_proto_init() { - if File_signrpc_signer_proto != nil { - return +func (x *TweakDesc) GetTweak() []byte { + if x != nil { + return x.Tweak } - if !protoimpl.UnsafeEnabled { - file_signrpc_signer_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*KeyLocator); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_signrpc_signer_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*KeyDescriptor); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_signrpc_signer_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TxOut); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_signrpc_signer_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SignDescriptor); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_signrpc_signer_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SignReq); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } + return nil +} + +func (x *TweakDesc) GetIsXOnly() bool { + if x != nil { + return x.IsXOnly + } + return false +} + +type TaprootTweakDesc struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // + //The root hash of the tapscript tree if a script path is committed to. If + //the MuSig2 key put on chain doesn't also commit to a script path (BIP-0086 + //key spend only), then this needs to be empty and the key_spend_only field + //below must be set to true. This is required because gRPC cannot + //differentiate between a zero-size byte slice and a nil byte slice (both + //would be serialized the same way). So the extra boolean is required. + ScriptRoot []byte `protobuf:"bytes,1,opt,name=script_root,json=scriptRoot,proto3" json:"script_root,omitempty"` + // + //Indicates that the above script_root is expected to be empty because this + //is a BIP-0086 key spend only commitment where only the internal key is + //committed to instead of also including a script root hash. + KeySpendOnly bool `protobuf:"varint,2,opt,name=key_spend_only,json=keySpendOnly,proto3" json:"key_spend_only,omitempty"` +} + +func (x *TaprootTweakDesc) Reset() { + *x = TaprootTweakDesc{} + if protoimpl.UnsafeEnabled { + mi := &file_signrpc_signer_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TaprootTweakDesc) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TaprootTweakDesc) ProtoMessage() {} + +func (x *TaprootTweakDesc) ProtoReflect() protoreflect.Message { + mi := &file_signrpc_signer_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) } - file_signrpc_signer_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SignResp); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TaprootTweakDesc.ProtoReflect.Descriptor instead. +func (*TaprootTweakDesc) Descriptor() ([]byte, []int) { + return file_signrpc_signer_proto_rawDescGZIP(), []int{15} +} + +func (x *TaprootTweakDesc) GetScriptRoot() []byte { + if x != nil { + return x.ScriptRoot + } + return nil +} + +func (x *TaprootTweakDesc) GetKeySpendOnly() bool { + if x != nil { + return x.KeySpendOnly + } + return false +} + +type MuSig2CombineKeysRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // + //A list of all public keys (serialized in 32-byte x-only format!) + //participating in the signing session. The list will always be sorted + //lexicographically internally. This must include the local key which is + //described by the above key_loc. + AllSignerPubkeys [][]byte `protobuf:"bytes,1,rep,name=all_signer_pubkeys,json=allSignerPubkeys,proto3" json:"all_signer_pubkeys,omitempty"` + // + //A series of optional generic tweaks to be applied to the the aggregated + //public key. + Tweaks []*TweakDesc `protobuf:"bytes,2,rep,name=tweaks,proto3" json:"tweaks,omitempty"` + // + //An optional taproot specific tweak that must be specified if the MuSig2 + //combined key will be used as the main taproot key of a taproot output + //on-chain. + TaprootTweak *TaprootTweakDesc `protobuf:"bytes,3,opt,name=taproot_tweak,json=taprootTweak,proto3" json:"taproot_tweak,omitempty"` +} + +func (x *MuSig2CombineKeysRequest) Reset() { + *x = MuSig2CombineKeysRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_signrpc_signer_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MuSig2CombineKeysRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MuSig2CombineKeysRequest) ProtoMessage() {} + +func (x *MuSig2CombineKeysRequest) ProtoReflect() protoreflect.Message { + mi := &file_signrpc_signer_proto_msgTypes[16] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) } - file_signrpc_signer_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*InputScript); i { - case 0: - return &v.state - case 1: - return &v.sizeCache + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MuSig2CombineKeysRequest.ProtoReflect.Descriptor instead. +func (*MuSig2CombineKeysRequest) Descriptor() ([]byte, []int) { + return file_signrpc_signer_proto_rawDescGZIP(), []int{16} +} + +func (x *MuSig2CombineKeysRequest) GetAllSignerPubkeys() [][]byte { + if x != nil { + return x.AllSignerPubkeys + } + return nil +} + +func (x *MuSig2CombineKeysRequest) GetTweaks() []*TweakDesc { + if x != nil { + return x.Tweaks + } + return nil +} + +func (x *MuSig2CombineKeysRequest) GetTaprootTweak() *TaprootTweakDesc { + if x != nil { + return x.TaprootTweak + } + return nil +} + +type MuSig2CombineKeysResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // + //The combined public key (in the 32-byte x-only format) with all tweaks + //applied to it. If a taproot tweak is specified, this corresponds to the + //taproot key that can be put into the on-chain output. + CombinedKey []byte `protobuf:"bytes,1,opt,name=combined_key,json=combinedKey,proto3" json:"combined_key,omitempty"` + // + //The raw combined public key (in the 32-byte x-only format) before any tweaks + //are applied to it. If a taproot tweak is specified, this corresponds to the + //internal key that needs to be put into the witness if the script spend path + //is used. + TaprootInternalKey []byte `protobuf:"bytes,2,opt,name=taproot_internal_key,json=taprootInternalKey,proto3" json:"taproot_internal_key,omitempty"` +} + +func (x *MuSig2CombineKeysResponse) Reset() { + *x = MuSig2CombineKeysResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_signrpc_signer_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MuSig2CombineKeysResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MuSig2CombineKeysResponse) ProtoMessage() {} + +func (x *MuSig2CombineKeysResponse) ProtoReflect() protoreflect.Message { + mi := &file_signrpc_signer_proto_msgTypes[17] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MuSig2CombineKeysResponse.ProtoReflect.Descriptor instead. +func (*MuSig2CombineKeysResponse) Descriptor() ([]byte, []int) { + return file_signrpc_signer_proto_rawDescGZIP(), []int{17} +} + +func (x *MuSig2CombineKeysResponse) GetCombinedKey() []byte { + if x != nil { + return x.CombinedKey + } + return nil +} + +func (x *MuSig2CombineKeysResponse) GetTaprootInternalKey() []byte { + if x != nil { + return x.TaprootInternalKey + } + return nil +} + +type MuSig2SessionRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // + //The key locator that identifies which key to use for signing. + KeyLoc *KeyLocator `protobuf:"bytes,1,opt,name=key_loc,json=keyLoc,proto3" json:"key_loc,omitempty"` + // + //A list of all public keys (serialized in 32-byte x-only format!) + //participating in the signing session. The list will always be sorted + //lexicographically internally. This must include the local key which is + //described by the above key_loc. + AllSignerPubkeys [][]byte `protobuf:"bytes,2,rep,name=all_signer_pubkeys,json=allSignerPubkeys,proto3" json:"all_signer_pubkeys,omitempty"` + // + //An optional list of all public nonces of other signing participants that + //might already be known. + OtherSignerPublicNonces [][]byte `protobuf:"bytes,3,rep,name=other_signer_public_nonces,json=otherSignerPublicNonces,proto3" json:"other_signer_public_nonces,omitempty"` + // + //A series of optional generic tweaks to be applied to the the aggregated + //public key. + Tweaks []*TweakDesc `protobuf:"bytes,4,rep,name=tweaks,proto3" json:"tweaks,omitempty"` + // + //An optional taproot specific tweak that must be specified if the MuSig2 + //combined key will be used as the main taproot key of a taproot output + //on-chain. + TaprootTweak *TaprootTweakDesc `protobuf:"bytes,5,opt,name=taproot_tweak,json=taprootTweak,proto3" json:"taproot_tweak,omitempty"` +} + +func (x *MuSig2SessionRequest) Reset() { + *x = MuSig2SessionRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_signrpc_signer_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MuSig2SessionRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MuSig2SessionRequest) ProtoMessage() {} + +func (x *MuSig2SessionRequest) ProtoReflect() protoreflect.Message { + mi := &file_signrpc_signer_proto_msgTypes[18] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MuSig2SessionRequest.ProtoReflect.Descriptor instead. +func (*MuSig2SessionRequest) Descriptor() ([]byte, []int) { + return file_signrpc_signer_proto_rawDescGZIP(), []int{18} +} + +func (x *MuSig2SessionRequest) GetKeyLoc() *KeyLocator { + if x != nil { + return x.KeyLoc + } + return nil +} + +func (x *MuSig2SessionRequest) GetAllSignerPubkeys() [][]byte { + if x != nil { + return x.AllSignerPubkeys + } + return nil +} + +func (x *MuSig2SessionRequest) GetOtherSignerPublicNonces() [][]byte { + if x != nil { + return x.OtherSignerPublicNonces + } + return nil +} + +func (x *MuSig2SessionRequest) GetTweaks() []*TweakDesc { + if x != nil { + return x.Tweaks + } + return nil +} + +func (x *MuSig2SessionRequest) GetTaprootTweak() *TaprootTweakDesc { + if x != nil { + return x.TaprootTweak + } + return nil +} + +type MuSig2SessionResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // + //The unique ID that represents this signing session. A session can be used + //for producing a signature a single time. If the signing fails for any + //reason, a new session with the same participants needs to be created. + SessionId []byte `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` + // + //The combined public key (in the 32-byte x-only format) with all tweaks + //applied to it. If a taproot tweak is specified, this corresponds to the + //taproot key that can be put into the on-chain output. + CombinedKey []byte `protobuf:"bytes,2,opt,name=combined_key,json=combinedKey,proto3" json:"combined_key,omitempty"` + // + //The raw combined public key (in the 32-byte x-only format) before any tweaks + //are applied to it. If a taproot tweak is specified, this corresponds to the + //internal key that needs to be put into the witness if the script spend path + //is used. + TaprootInternalKey []byte `protobuf:"bytes,3,opt,name=taproot_internal_key,json=taprootInternalKey,proto3" json:"taproot_internal_key,omitempty"` + // + //The two public nonces the local signer uses, combined into a single value + //of 66 bytes. Can be split into the two 33-byte points to get the individual + //nonces. + LocalPublicNonces []byte `protobuf:"bytes,4,opt,name=local_public_nonces,json=localPublicNonces,proto3" json:"local_public_nonces,omitempty"` + // + //Indicates whether all nonces required to start the signing process are known + //now. + HaveAllNonces bool `protobuf:"varint,5,opt,name=have_all_nonces,json=haveAllNonces,proto3" json:"have_all_nonces,omitempty"` +} + +func (x *MuSig2SessionResponse) Reset() { + *x = MuSig2SessionResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_signrpc_signer_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MuSig2SessionResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MuSig2SessionResponse) ProtoMessage() {} + +func (x *MuSig2SessionResponse) ProtoReflect() protoreflect.Message { + mi := &file_signrpc_signer_proto_msgTypes[19] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MuSig2SessionResponse.ProtoReflect.Descriptor instead. +func (*MuSig2SessionResponse) Descriptor() ([]byte, []int) { + return file_signrpc_signer_proto_rawDescGZIP(), []int{19} +} + +func (x *MuSig2SessionResponse) GetSessionId() []byte { + if x != nil { + return x.SessionId + } + return nil +} + +func (x *MuSig2SessionResponse) GetCombinedKey() []byte { + if x != nil { + return x.CombinedKey + } + return nil +} + +func (x *MuSig2SessionResponse) GetTaprootInternalKey() []byte { + if x != nil { + return x.TaprootInternalKey + } + return nil +} + +func (x *MuSig2SessionResponse) GetLocalPublicNonces() []byte { + if x != nil { + return x.LocalPublicNonces + } + return nil +} + +func (x *MuSig2SessionResponse) GetHaveAllNonces() bool { + if x != nil { + return x.HaveAllNonces + } + return false +} + +type MuSig2RegisterNoncesRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // + //The unique ID of the signing session those nonces should be registered with. + SessionId []byte `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` + // + //A list of all public nonces of other signing participants that should be + //registered. + OtherSignerPublicNonces [][]byte `protobuf:"bytes,3,rep,name=other_signer_public_nonces,json=otherSignerPublicNonces,proto3" json:"other_signer_public_nonces,omitempty"` +} + +func (x *MuSig2RegisterNoncesRequest) Reset() { + *x = MuSig2RegisterNoncesRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_signrpc_signer_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MuSig2RegisterNoncesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MuSig2RegisterNoncesRequest) ProtoMessage() {} + +func (x *MuSig2RegisterNoncesRequest) ProtoReflect() protoreflect.Message { + mi := &file_signrpc_signer_proto_msgTypes[20] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MuSig2RegisterNoncesRequest.ProtoReflect.Descriptor instead. +func (*MuSig2RegisterNoncesRequest) Descriptor() ([]byte, []int) { + return file_signrpc_signer_proto_rawDescGZIP(), []int{20} +} + +func (x *MuSig2RegisterNoncesRequest) GetSessionId() []byte { + if x != nil { + return x.SessionId + } + return nil +} + +func (x *MuSig2RegisterNoncesRequest) GetOtherSignerPublicNonces() [][]byte { + if x != nil { + return x.OtherSignerPublicNonces + } + return nil +} + +type MuSig2RegisterNoncesResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // + //Indicates whether all nonces required to start the signing process are known + //now. + HaveAllNonces bool `protobuf:"varint,1,opt,name=have_all_nonces,json=haveAllNonces,proto3" json:"have_all_nonces,omitempty"` +} + +func (x *MuSig2RegisterNoncesResponse) Reset() { + *x = MuSig2RegisterNoncesResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_signrpc_signer_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MuSig2RegisterNoncesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MuSig2RegisterNoncesResponse) ProtoMessage() {} + +func (x *MuSig2RegisterNoncesResponse) ProtoReflect() protoreflect.Message { + mi := &file_signrpc_signer_proto_msgTypes[21] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MuSig2RegisterNoncesResponse.ProtoReflect.Descriptor instead. +func (*MuSig2RegisterNoncesResponse) Descriptor() ([]byte, []int) { + return file_signrpc_signer_proto_rawDescGZIP(), []int{21} +} + +func (x *MuSig2RegisterNoncesResponse) GetHaveAllNonces() bool { + if x != nil { + return x.HaveAllNonces + } + return false +} + +type MuSig2SignRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // + //The unique ID of the signing session to use for signing. + SessionId []byte `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` + // + //The 32-byte SHA256 digest of the message to sign. + MessageDigest []byte `protobuf:"bytes,2,opt,name=message_digest,json=messageDigest,proto3" json:"message_digest,omitempty"` + // + //Cleanup indicates that after signing, the session state can be cleaned up, + //since another participant is going to be responsible for combining the + //partial signatures. + Cleanup bool `protobuf:"varint,3,opt,name=cleanup,proto3" json:"cleanup,omitempty"` +} + +func (x *MuSig2SignRequest) Reset() { + *x = MuSig2SignRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_signrpc_signer_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MuSig2SignRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MuSig2SignRequest) ProtoMessage() {} + +func (x *MuSig2SignRequest) ProtoReflect() protoreflect.Message { + mi := &file_signrpc_signer_proto_msgTypes[22] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MuSig2SignRequest.ProtoReflect.Descriptor instead. +func (*MuSig2SignRequest) Descriptor() ([]byte, []int) { + return file_signrpc_signer_proto_rawDescGZIP(), []int{22} +} + +func (x *MuSig2SignRequest) GetSessionId() []byte { + if x != nil { + return x.SessionId + } + return nil +} + +func (x *MuSig2SignRequest) GetMessageDigest() []byte { + if x != nil { + return x.MessageDigest + } + return nil +} + +func (x *MuSig2SignRequest) GetCleanup() bool { + if x != nil { + return x.Cleanup + } + return false +} + +type MuSig2SignResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // + //The partial signature created by the local signer. + LocalPartialSignature []byte `protobuf:"bytes,1,opt,name=local_partial_signature,json=localPartialSignature,proto3" json:"local_partial_signature,omitempty"` +} + +func (x *MuSig2SignResponse) Reset() { + *x = MuSig2SignResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_signrpc_signer_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MuSig2SignResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MuSig2SignResponse) ProtoMessage() {} + +func (x *MuSig2SignResponse) ProtoReflect() protoreflect.Message { + mi := &file_signrpc_signer_proto_msgTypes[23] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MuSig2SignResponse.ProtoReflect.Descriptor instead. +func (*MuSig2SignResponse) Descriptor() ([]byte, []int) { + return file_signrpc_signer_proto_rawDescGZIP(), []int{23} +} + +func (x *MuSig2SignResponse) GetLocalPartialSignature() []byte { + if x != nil { + return x.LocalPartialSignature + } + return nil +} + +type MuSig2CombineSigRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // + //The unique ID of the signing session to combine the signatures for. + SessionId []byte `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` + // + //The list of all other participants' partial signatures to add to the current + //session. + OtherPartialSignatures [][]byte `protobuf:"bytes,2,rep,name=other_partial_signatures,json=otherPartialSignatures,proto3" json:"other_partial_signatures,omitempty"` +} + +func (x *MuSig2CombineSigRequest) Reset() { + *x = MuSig2CombineSigRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_signrpc_signer_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MuSig2CombineSigRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MuSig2CombineSigRequest) ProtoMessage() {} + +func (x *MuSig2CombineSigRequest) ProtoReflect() protoreflect.Message { + mi := &file_signrpc_signer_proto_msgTypes[24] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MuSig2CombineSigRequest.ProtoReflect.Descriptor instead. +func (*MuSig2CombineSigRequest) Descriptor() ([]byte, []int) { + return file_signrpc_signer_proto_rawDescGZIP(), []int{24} +} + +func (x *MuSig2CombineSigRequest) GetSessionId() []byte { + if x != nil { + return x.SessionId + } + return nil +} + +func (x *MuSig2CombineSigRequest) GetOtherPartialSignatures() [][]byte { + if x != nil { + return x.OtherPartialSignatures + } + return nil +} + +type MuSig2CombineSigResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // + //Indicates whether all partial signatures required to create a final, full + //signature are known yet. If this is true, then the final_signature field is + //set, otherwise it is empty. + HaveAllSignatures bool `protobuf:"varint,1,opt,name=have_all_signatures,json=haveAllSignatures,proto3" json:"have_all_signatures,omitempty"` + // + //The final, full signature that is valid for the combined public key. + FinalSignature []byte `protobuf:"bytes,2,opt,name=final_signature,json=finalSignature,proto3" json:"final_signature,omitempty"` +} + +func (x *MuSig2CombineSigResponse) Reset() { + *x = MuSig2CombineSigResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_signrpc_signer_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MuSig2CombineSigResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MuSig2CombineSigResponse) ProtoMessage() {} + +func (x *MuSig2CombineSigResponse) ProtoReflect() protoreflect.Message { + mi := &file_signrpc_signer_proto_msgTypes[25] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MuSig2CombineSigResponse.ProtoReflect.Descriptor instead. +func (*MuSig2CombineSigResponse) Descriptor() ([]byte, []int) { + return file_signrpc_signer_proto_rawDescGZIP(), []int{25} +} + +func (x *MuSig2CombineSigResponse) GetHaveAllSignatures() bool { + if x != nil { + return x.HaveAllSignatures + } + return false +} + +func (x *MuSig2CombineSigResponse) GetFinalSignature() []byte { + if x != nil { + return x.FinalSignature + } + return nil +} + +type MuSig2CleanupRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // + //The unique ID of the signing session that should be removed/cleaned up. + SessionId []byte `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` +} + +func (x *MuSig2CleanupRequest) Reset() { + *x = MuSig2CleanupRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_signrpc_signer_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MuSig2CleanupRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MuSig2CleanupRequest) ProtoMessage() {} + +func (x *MuSig2CleanupRequest) ProtoReflect() protoreflect.Message { + mi := &file_signrpc_signer_proto_msgTypes[26] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MuSig2CleanupRequest.ProtoReflect.Descriptor instead. +func (*MuSig2CleanupRequest) Descriptor() ([]byte, []int) { + return file_signrpc_signer_proto_rawDescGZIP(), []int{26} +} + +func (x *MuSig2CleanupRequest) GetSessionId() []byte { + if x != nil { + return x.SessionId + } + return nil +} + +type MuSig2CleanupResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *MuSig2CleanupResponse) Reset() { + *x = MuSig2CleanupResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_signrpc_signer_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MuSig2CleanupResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MuSig2CleanupResponse) ProtoMessage() {} + +func (x *MuSig2CleanupResponse) ProtoReflect() protoreflect.Message { + mi := &file_signrpc_signer_proto_msgTypes[27] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MuSig2CleanupResponse.ProtoReflect.Descriptor instead. +func (*MuSig2CleanupResponse) Descriptor() ([]byte, []int) { + return file_signrpc_signer_proto_rawDescGZIP(), []int{27} +} + +var File_signrpc_signer_proto protoreflect.FileDescriptor + +var file_signrpc_signer_proto_rawDesc = []byte{ + 0x0a, 0x14, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2f, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x07, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x22, + 0x48, 0x0a, 0x0a, 0x4b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x1d, 0x0a, + 0x0a, 0x6b, 0x65, 0x79, 0x5f, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x09, 0x6b, 0x65, 0x79, 0x46, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x12, 0x1b, 0x0a, 0x09, + 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x08, 0x6b, 0x65, 0x79, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x61, 0x0a, 0x0d, 0x4b, 0x65, 0x79, + 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x22, 0x0a, 0x0d, 0x72, 0x61, + 0x77, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x0b, 0x72, 0x61, 0x77, 0x4b, 0x65, 0x79, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x2c, + 0x0a, 0x07, 0x6b, 0x65, 0x79, 0x5f, 0x6c, 0x6f, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x13, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x4c, 0x6f, 0x63, + 0x61, 0x74, 0x6f, 0x72, 0x52, 0x06, 0x6b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x22, 0x3a, 0x0a, 0x05, + 0x54, 0x78, 0x4f, 0x75, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, + 0x6b, 0x5f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, + 0x70, 0x6b, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x22, 0xe6, 0x02, 0x0a, 0x0e, 0x53, 0x69, 0x67, + 0x6e, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x31, 0x0a, 0x08, 0x6b, + 0x65, 0x79, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, + 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x6f, 0x72, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x44, 0x65, 0x73, 0x63, 0x12, 0x21, + 0x0a, 0x0c, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x5f, 0x74, 0x77, 0x65, 0x61, 0x6b, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x54, 0x77, 0x65, 0x61, + 0x6b, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x5f, 0x74, 0x77, 0x65, 0x61, + 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x54, + 0x77, 0x65, 0x61, 0x6b, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x61, 0x70, 0x5f, 0x74, 0x77, 0x65, 0x61, + 0x6b, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x74, 0x61, 0x70, 0x54, 0x77, 0x65, 0x61, + 0x6b, 0x12, 0x25, 0x0a, 0x0e, 0x77, 0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x5f, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x77, 0x69, 0x74, 0x6e, 0x65, + 0x73, 0x73, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x26, 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, + 0x75, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x54, 0x78, 0x4f, 0x75, 0x74, 0x52, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, + 0x12, 0x18, 0x0a, 0x07, 0x73, 0x69, 0x67, 0x68, 0x61, 0x73, 0x68, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x07, 0x73, 0x69, 0x67, 0x68, 0x61, 0x73, 0x68, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, + 0x70, 0x75, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x0a, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x34, 0x0a, 0x0b, 0x73, + 0x69, 0x67, 0x6e, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x13, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x4d, + 0x65, 0x74, 0x68, 0x6f, 0x64, 0x52, 0x0a, 0x73, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x74, 0x68, 0x6f, + 0x64, 0x22, 0x96, 0x01, 0x0a, 0x07, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x71, 0x12, 0x20, 0x0a, + 0x0c, 0x72, 0x61, 0x77, 0x5f, 0x74, 0x78, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x72, 0x61, 0x77, 0x54, 0x78, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, + 0x36, 0x0a, 0x0a, 0x73, 0x69, 0x67, 0x6e, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x73, 0x18, 0x02, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, + 0x67, 0x6e, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x52, 0x09, 0x73, 0x69, + 0x67, 0x6e, 0x44, 0x65, 0x73, 0x63, 0x73, 0x12, 0x31, 0x0a, 0x0c, 0x70, 0x72, 0x65, 0x76, 0x5f, + 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, + 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x78, 0x4f, 0x75, 0x74, 0x52, 0x0b, 0x70, + 0x72, 0x65, 0x76, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x22, 0x25, 0x0a, 0x08, 0x53, 0x69, + 0x67, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x61, 0x77, 0x5f, 0x73, 0x69, + 0x67, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x07, 0x72, 0x61, 0x77, 0x53, 0x69, 0x67, + 0x73, 0x22, 0x46, 0x0a, 0x0b, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x12, 0x18, 0x0a, 0x07, 0x77, 0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0c, 0x52, 0x07, 0x77, 0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x69, + 0x67, 0x5f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, + 0x73, 0x69, 0x67, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x22, 0x4c, 0x0a, 0x0f, 0x49, 0x6e, 0x70, + 0x75, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x12, 0x39, 0x0a, 0x0d, + 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, + 0x70, 0x75, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x0c, 0x69, 0x6e, 0x70, 0x75, 0x74, + 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x73, 0x22, 0x92, 0x01, 0x0a, 0x0e, 0x53, 0x69, 0x67, 0x6e, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, + 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x12, 0x2c, 0x0a, 0x07, + 0x6b, 0x65, 0x79, 0x5f, 0x6c, 0x6f, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, + 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x61, 0x74, + 0x6f, 0x72, 0x52, 0x06, 0x6b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x6f, + 0x75, 0x62, 0x6c, 0x65, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x0a, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x48, 0x61, 0x73, 0x68, 0x12, 0x1f, 0x0a, 0x0b, 0x63, + 0x6f, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x5f, 0x73, 0x69, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x0a, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x53, 0x69, 0x67, 0x22, 0x2f, 0x0a, 0x0f, + 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x12, + 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x5a, 0x0a, + 0x10, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, + 0x71, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, + 0x6d, 0x73, 0x67, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x22, 0x29, 0x0a, 0x11, 0x56, 0x65, 0x72, + 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x12, 0x14, + 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x76, + 0x61, 0x6c, 0x69, 0x64, 0x22, 0xa2, 0x01, 0x0a, 0x10, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, + 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x65, 0x70, 0x68, + 0x65, 0x6d, 0x65, 0x72, 0x61, 0x6c, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x65, 0x70, 0x68, 0x65, 0x6d, 0x65, 0x72, 0x61, 0x6c, 0x50, 0x75, + 0x62, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x07, 0x6b, 0x65, 0x79, 0x5f, 0x6c, 0x6f, 0x63, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x4b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x42, 0x02, 0x18, 0x01, 0x52, 0x06, + 0x6b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x12, 0x31, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x5f, 0x64, 0x65, + 0x73, 0x63, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, + 0x52, 0x07, 0x6b, 0x65, 0x79, 0x44, 0x65, 0x73, 0x63, 0x22, 0x32, 0x0a, 0x11, 0x53, 0x68, 0x61, + 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1d, + 0x0a, 0x0a, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x09, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x22, 0x3d, 0x0a, + 0x09, 0x54, 0x77, 0x65, 0x61, 0x6b, 0x44, 0x65, 0x73, 0x63, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x77, + 0x65, 0x61, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x74, 0x77, 0x65, 0x61, 0x6b, + 0x12, 0x1a, 0x0a, 0x09, 0x69, 0x73, 0x5f, 0x78, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x73, 0x58, 0x4f, 0x6e, 0x6c, 0x79, 0x22, 0x59, 0x0a, 0x10, + 0x54, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x54, 0x77, 0x65, 0x61, 0x6b, 0x44, 0x65, 0x73, 0x63, + 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x6f, 0x6f, + 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x6b, 0x65, 0x79, 0x5f, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x5f, 0x6f, + 0x6e, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x6b, 0x65, 0x79, 0x53, 0x70, + 0x65, 0x6e, 0x64, 0x4f, 0x6e, 0x6c, 0x79, 0x22, 0xb4, 0x01, 0x0a, 0x18, 0x4d, 0x75, 0x53, 0x69, + 0x67, 0x32, 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x61, 0x6c, 0x6c, 0x5f, 0x73, 0x69, 0x67, 0x6e, + 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, + 0x52, 0x10, 0x61, 0x6c, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x50, 0x75, 0x62, 0x6b, 0x65, + 0x79, 0x73, 0x12, 0x2a, 0x0a, 0x06, 0x74, 0x77, 0x65, 0x61, 0x6b, 0x73, 0x18, 0x02, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x77, 0x65, + 0x61, 0x6b, 0x44, 0x65, 0x73, 0x63, 0x52, 0x06, 0x74, 0x77, 0x65, 0x61, 0x6b, 0x73, 0x12, 0x3e, + 0x0a, 0x0d, 0x74, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x74, 0x77, 0x65, 0x61, 0x6b, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x54, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x54, 0x77, 0x65, 0x61, 0x6b, 0x44, 0x65, 0x73, 0x63, + 0x52, 0x0c, 0x74, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x54, 0x77, 0x65, 0x61, 0x6b, 0x22, 0x70, + 0x0a, 0x19, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x4b, + 0x65, 0x79, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, + 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x0b, 0x63, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x12, 0x30, + 0x0a, 0x14, 0x74, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, + 0x61, 0x6c, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x12, 0x74, 0x61, + 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x4b, 0x65, 0x79, + 0x22, 0x9b, 0x02, 0x0a, 0x14, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x53, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x07, 0x6b, 0x65, 0x79, + 0x5f, 0x6c, 0x6f, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x73, 0x69, 0x67, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x52, + 0x06, 0x6b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x12, 0x2c, 0x0a, 0x12, 0x61, 0x6c, 0x6c, 0x5f, 0x73, + 0x69, 0x67, 0x6e, 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x02, 0x20, + 0x03, 0x28, 0x0c, 0x52, 0x10, 0x61, 0x6c, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x50, 0x75, + 0x62, 0x6b, 0x65, 0x79, 0x73, 0x12, 0x3b, 0x0a, 0x1a, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x5f, 0x73, + 0x69, 0x67, 0x6e, 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6e, 0x6f, 0x6e, + 0x63, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x17, 0x6f, 0x74, 0x68, 0x65, 0x72, + 0x53, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4e, 0x6f, 0x6e, 0x63, + 0x65, 0x73, 0x12, 0x2a, 0x0a, 0x06, 0x74, 0x77, 0x65, 0x61, 0x6b, 0x73, 0x18, 0x04, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x77, 0x65, + 0x61, 0x6b, 0x44, 0x65, 0x73, 0x63, 0x52, 0x06, 0x74, 0x77, 0x65, 0x61, 0x6b, 0x73, 0x12, 0x3e, + 0x0a, 0x0d, 0x74, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x74, 0x77, 0x65, 0x61, 0x6b, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x54, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x54, 0x77, 0x65, 0x61, 0x6b, 0x44, 0x65, 0x73, 0x63, + 0x52, 0x0c, 0x74, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x54, 0x77, 0x65, 0x61, 0x6b, 0x22, 0xe3, + 0x01, 0x0a, 0x15, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 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, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x62, 0x69, + 0x6e, 0x65, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x63, + 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x14, 0x74, 0x61, + 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x6b, + 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x12, 0x74, 0x61, 0x70, 0x72, 0x6f, 0x6f, + 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x4b, 0x65, 0x79, 0x12, 0x2e, 0x0a, 0x13, + 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6e, 0x6f, 0x6e, + 0x63, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x11, 0x6c, 0x6f, 0x63, 0x61, 0x6c, + 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x26, 0x0a, 0x0f, + 0x68, 0x61, 0x76, 0x65, 0x5f, 0x61, 0x6c, 0x6c, 0x5f, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x68, 0x61, 0x76, 0x65, 0x41, 0x6c, 0x6c, 0x4e, 0x6f, + 0x6e, 0x63, 0x65, 0x73, 0x22, 0x79, 0x0a, 0x1b, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x52, 0x65, + 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 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, 0x3b, 0x0a, 0x1a, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x5f, 0x73, 0x69, 0x67, 0x6e, + 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x73, + 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x17, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x53, 0x69, 0x67, + 0x6e, 0x65, 0x72, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x22, + 0x46, 0x0a, 0x1c, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, + 0x72, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x26, 0x0a, 0x0f, 0x68, 0x61, 0x76, 0x65, 0x5f, 0x61, 0x6c, 0x6c, 0x5f, 0x6e, 0x6f, 0x6e, 0x63, + 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x68, 0x61, 0x76, 0x65, 0x41, 0x6c, + 0x6c, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x22, 0x73, 0x0a, 0x11, 0x4d, 0x75, 0x53, 0x69, 0x67, + 0x32, 0x53, 0x69, 0x67, 0x6e, 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, 0x25, 0x0a, 0x0e, 0x6d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x44, 0x69, 0x67, 0x65, + 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x07, 0x63, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70, 0x22, 0x4c, 0x0a, 0x12, + 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x36, 0x0a, 0x17, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x70, 0x61, 0x72, 0x74, + 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x15, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x61, 0x72, 0x74, 0x69, 0x61, + 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x72, 0x0a, 0x17, 0x4d, 0x75, + 0x53, 0x69, 0x67, 0x32, 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x53, 0x69, 0x67, 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, 0x38, 0x0a, 0x18, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x5f, 0x70, 0x61, + 0x72, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, + 0x18, 0x02, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x16, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x50, 0x61, 0x72, + 0x74, 0x69, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x22, 0x73, + 0x0a, 0x18, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x53, + 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x68, 0x61, + 0x76, 0x65, 0x5f, 0x61, 0x6c, 0x6c, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x68, 0x61, 0x76, 0x65, 0x41, 0x6c, 0x6c, + 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x66, 0x69, + 0x6e, 0x61, 0x6c, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x22, 0x35, 0x0a, 0x14, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6c, 0x65, + 0x61, 0x6e, 0x75, 0x70, 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, 0x22, 0x17, 0x0a, 0x15, 0x4d, 0x75, + 0x53, 0x69, 0x67, 0x32, 0x43, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x2a, 0x9c, 0x01, 0x0a, 0x0a, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x74, 0x68, + 0x6f, 0x64, 0x12, 0x1a, 0x0a, 0x16, 0x53, 0x49, 0x47, 0x4e, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, + 0x44, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x56, 0x30, 0x10, 0x00, 0x12, 0x29, + 0x0a, 0x25, 0x53, 0x49, 0x47, 0x4e, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x54, 0x41, + 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x53, 0x50, 0x45, 0x4e, 0x44, 0x5f, + 0x42, 0x49, 0x50, 0x30, 0x30, 0x38, 0x36, 0x10, 0x01, 0x12, 0x21, 0x0a, 0x1d, 0x53, 0x49, 0x47, + 0x4e, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, + 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x53, 0x50, 0x45, 0x4e, 0x44, 0x10, 0x02, 0x12, 0x24, 0x0a, 0x20, + 0x53, 0x49, 0x47, 0x4e, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x54, 0x41, 0x50, 0x52, + 0x4f, 0x4f, 0x54, 0x5f, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x53, 0x50, 0x45, 0x4e, 0x44, + 0x10, 0x03, 0x32, 0xdb, 0x06, 0x0a, 0x06, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x12, 0x34, 0x0a, + 0x0d, 0x53, 0x69, 0x67, 0x6e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x61, 0x77, 0x12, 0x10, + 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x71, + 0x1a, 0x11, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x52, + 0x65, 0x73, 0x70, 0x12, 0x40, 0x0a, 0x12, 0x43, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x65, 0x49, 0x6e, + 0x70, 0x75, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x10, 0x2e, 0x73, 0x69, 0x67, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x71, 0x1a, 0x18, 0x2e, 0x73, 0x69, + 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x12, 0x40, 0x0a, 0x0b, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x12, 0x17, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, + 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x18, 0x2e, + 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x12, 0x46, 0x0a, 0x0d, 0x56, 0x65, 0x72, 0x69, 0x66, + 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x19, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x52, 0x65, 0x71, 0x1a, 0x1a, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, + 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x12, + 0x48, 0x0a, 0x0f, 0x44, 0x65, 0x72, 0x69, 0x76, 0x65, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, + 0x65, 0x79, 0x12, 0x19, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x68, 0x61, + 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, + 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, + 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x11, 0x4d, 0x75, 0x53, + 0x69, 0x67, 0x32, 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x21, + 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, + 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x22, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, + 0x67, 0x32, 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x13, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x2e, 0x73, + 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x53, 0x65, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x73, 0x69, + 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x53, 0x65, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x63, 0x0a, 0x14, 0x4d, + 0x75, 0x53, 0x69, 0x67, 0x32, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x6f, 0x6e, + 0x63, 0x65, 0x73, 0x12, 0x24, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, + 0x53, 0x69, 0x67, 0x32, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x6f, 0x6e, 0x63, + 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x73, 0x69, 0x67, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, + 0x65, 0x72, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x45, 0x0a, 0x0a, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x53, 0x69, 0x67, 0x6e, 0x12, 0x1a, + 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x53, + 0x69, 0x67, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x73, 0x69, 0x67, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x53, 0x69, 0x67, 0x6e, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x10, 0x4d, 0x75, 0x53, 0x69, 0x67, + 0x32, 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x53, 0x69, 0x67, 0x12, 0x20, 0x2e, 0x73, 0x69, + 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6f, 0x6d, 0x62, + 0x69, 0x6e, 0x65, 0x53, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, + 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6f, + 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x53, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x4e, 0x0a, 0x0d, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6c, 0x65, 0x61, 0x6e, 0x75, + 0x70, 0x12, 0x1d, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, + 0x67, 0x32, 0x43, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1e, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, + 0x32, 0x43, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x42, 0x2f, 0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, + 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, + 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2f, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, + 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_signrpc_signer_proto_rawDescOnce sync.Once + file_signrpc_signer_proto_rawDescData = file_signrpc_signer_proto_rawDesc +) + +func file_signrpc_signer_proto_rawDescGZIP() []byte { + file_signrpc_signer_proto_rawDescOnce.Do(func() { + file_signrpc_signer_proto_rawDescData = protoimpl.X.CompressGZIP(file_signrpc_signer_proto_rawDescData) + }) + return file_signrpc_signer_proto_rawDescData +} + +var file_signrpc_signer_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_signrpc_signer_proto_msgTypes = make([]protoimpl.MessageInfo, 28) +var file_signrpc_signer_proto_goTypes = []interface{}{ + (SignMethod)(0), // 0: signrpc.SignMethod + (*KeyLocator)(nil), // 1: signrpc.KeyLocator + (*KeyDescriptor)(nil), // 2: signrpc.KeyDescriptor + (*TxOut)(nil), // 3: signrpc.TxOut + (*SignDescriptor)(nil), // 4: signrpc.SignDescriptor + (*SignReq)(nil), // 5: signrpc.SignReq + (*SignResp)(nil), // 6: signrpc.SignResp + (*InputScript)(nil), // 7: signrpc.InputScript + (*InputScriptResp)(nil), // 8: signrpc.InputScriptResp + (*SignMessageReq)(nil), // 9: signrpc.SignMessageReq + (*SignMessageResp)(nil), // 10: signrpc.SignMessageResp + (*VerifyMessageReq)(nil), // 11: signrpc.VerifyMessageReq + (*VerifyMessageResp)(nil), // 12: signrpc.VerifyMessageResp + (*SharedKeyRequest)(nil), // 13: signrpc.SharedKeyRequest + (*SharedKeyResponse)(nil), // 14: signrpc.SharedKeyResponse + (*TweakDesc)(nil), // 15: signrpc.TweakDesc + (*TaprootTweakDesc)(nil), // 16: signrpc.TaprootTweakDesc + (*MuSig2CombineKeysRequest)(nil), // 17: signrpc.MuSig2CombineKeysRequest + (*MuSig2CombineKeysResponse)(nil), // 18: signrpc.MuSig2CombineKeysResponse + (*MuSig2SessionRequest)(nil), // 19: signrpc.MuSig2SessionRequest + (*MuSig2SessionResponse)(nil), // 20: signrpc.MuSig2SessionResponse + (*MuSig2RegisterNoncesRequest)(nil), // 21: signrpc.MuSig2RegisterNoncesRequest + (*MuSig2RegisterNoncesResponse)(nil), // 22: signrpc.MuSig2RegisterNoncesResponse + (*MuSig2SignRequest)(nil), // 23: signrpc.MuSig2SignRequest + (*MuSig2SignResponse)(nil), // 24: signrpc.MuSig2SignResponse + (*MuSig2CombineSigRequest)(nil), // 25: signrpc.MuSig2CombineSigRequest + (*MuSig2CombineSigResponse)(nil), // 26: signrpc.MuSig2CombineSigResponse + (*MuSig2CleanupRequest)(nil), // 27: signrpc.MuSig2CleanupRequest + (*MuSig2CleanupResponse)(nil), // 28: signrpc.MuSig2CleanupResponse +} +var file_signrpc_signer_proto_depIdxs = []int32{ + 1, // 0: signrpc.KeyDescriptor.key_loc:type_name -> signrpc.KeyLocator + 2, // 1: signrpc.SignDescriptor.key_desc:type_name -> signrpc.KeyDescriptor + 3, // 2: signrpc.SignDescriptor.output:type_name -> signrpc.TxOut + 0, // 3: signrpc.SignDescriptor.sign_method:type_name -> signrpc.SignMethod + 4, // 4: signrpc.SignReq.sign_descs:type_name -> signrpc.SignDescriptor + 3, // 5: signrpc.SignReq.prev_outputs:type_name -> signrpc.TxOut + 7, // 6: signrpc.InputScriptResp.input_scripts:type_name -> signrpc.InputScript + 1, // 7: signrpc.SignMessageReq.key_loc:type_name -> signrpc.KeyLocator + 1, // 8: signrpc.SharedKeyRequest.key_loc:type_name -> signrpc.KeyLocator + 2, // 9: signrpc.SharedKeyRequest.key_desc:type_name -> signrpc.KeyDescriptor + 15, // 10: signrpc.MuSig2CombineKeysRequest.tweaks:type_name -> signrpc.TweakDesc + 16, // 11: signrpc.MuSig2CombineKeysRequest.taproot_tweak:type_name -> signrpc.TaprootTweakDesc + 1, // 12: signrpc.MuSig2SessionRequest.key_loc:type_name -> signrpc.KeyLocator + 15, // 13: signrpc.MuSig2SessionRequest.tweaks:type_name -> signrpc.TweakDesc + 16, // 14: signrpc.MuSig2SessionRequest.taproot_tweak:type_name -> signrpc.TaprootTweakDesc + 5, // 15: signrpc.Signer.SignOutputRaw:input_type -> signrpc.SignReq + 5, // 16: signrpc.Signer.ComputeInputScript:input_type -> signrpc.SignReq + 9, // 17: signrpc.Signer.SignMessage:input_type -> signrpc.SignMessageReq + 11, // 18: signrpc.Signer.VerifyMessage:input_type -> signrpc.VerifyMessageReq + 13, // 19: signrpc.Signer.DeriveSharedKey:input_type -> signrpc.SharedKeyRequest + 17, // 20: signrpc.Signer.MuSig2CombineKeys:input_type -> signrpc.MuSig2CombineKeysRequest + 19, // 21: signrpc.Signer.MuSig2CreateSession:input_type -> signrpc.MuSig2SessionRequest + 21, // 22: signrpc.Signer.MuSig2RegisterNonces:input_type -> signrpc.MuSig2RegisterNoncesRequest + 23, // 23: signrpc.Signer.MuSig2Sign:input_type -> signrpc.MuSig2SignRequest + 25, // 24: signrpc.Signer.MuSig2CombineSig:input_type -> signrpc.MuSig2CombineSigRequest + 27, // 25: signrpc.Signer.MuSig2Cleanup:input_type -> signrpc.MuSig2CleanupRequest + 6, // 26: signrpc.Signer.SignOutputRaw:output_type -> signrpc.SignResp + 8, // 27: signrpc.Signer.ComputeInputScript:output_type -> signrpc.InputScriptResp + 10, // 28: signrpc.Signer.SignMessage:output_type -> signrpc.SignMessageResp + 12, // 29: signrpc.Signer.VerifyMessage:output_type -> signrpc.VerifyMessageResp + 14, // 30: signrpc.Signer.DeriveSharedKey:output_type -> signrpc.SharedKeyResponse + 18, // 31: signrpc.Signer.MuSig2CombineKeys:output_type -> signrpc.MuSig2CombineKeysResponse + 20, // 32: signrpc.Signer.MuSig2CreateSession:output_type -> signrpc.MuSig2SessionResponse + 22, // 33: signrpc.Signer.MuSig2RegisterNonces:output_type -> signrpc.MuSig2RegisterNoncesResponse + 24, // 34: signrpc.Signer.MuSig2Sign:output_type -> signrpc.MuSig2SignResponse + 26, // 35: signrpc.Signer.MuSig2CombineSig:output_type -> signrpc.MuSig2CombineSigResponse + 28, // 36: signrpc.Signer.MuSig2Cleanup:output_type -> signrpc.MuSig2CleanupResponse + 26, // [26:37] is the sub-list for method output_type + 15, // [15:26] is the sub-list for method input_type + 15, // [15:15] is the sub-list for extension type_name + 15, // [15:15] is the sub-list for extension extendee + 0, // [0:15] is the sub-list for field type_name +} + +func init() { file_signrpc_signer_proto_init() } +func file_signrpc_signer_proto_init() { + if File_signrpc_signer_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_signrpc_signer_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*KeyLocator); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_signrpc_signer_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*KeyDescriptor); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_signrpc_signer_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TxOut); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_signrpc_signer_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SignDescriptor); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_signrpc_signer_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SignReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_signrpc_signer_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SignResp); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_signrpc_signer_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*InputScript); i { + case 0: + return &v.state + case 1: + return &v.sizeCache case 2: return &v.unknownFields default: @@ -1250,19 +2446,188 @@ func file_signrpc_signer_proto_init() { return nil } } + file_signrpc_signer_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TweakDesc); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_signrpc_signer_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TaprootTweakDesc); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_signrpc_signer_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MuSig2CombineKeysRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_signrpc_signer_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MuSig2CombineKeysResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_signrpc_signer_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MuSig2SessionRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_signrpc_signer_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MuSig2SessionResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_signrpc_signer_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MuSig2RegisterNoncesRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_signrpc_signer_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MuSig2RegisterNoncesResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_signrpc_signer_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MuSig2SignRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_signrpc_signer_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MuSig2SignResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_signrpc_signer_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MuSig2CombineSigRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_signrpc_signer_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MuSig2CombineSigResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_signrpc_signer_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MuSig2CleanupRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_signrpc_signer_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MuSig2CleanupResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_signrpc_signer_proto_rawDesc, - NumEnums: 0, - NumMessages: 14, + NumEnums: 1, + NumMessages: 28, NumExtensions: 0, NumServices: 1, }, GoTypes: file_signrpc_signer_proto_goTypes, DependencyIndexes: file_signrpc_signer_proto_depIdxs, + EnumInfos: file_signrpc_signer_proto_enumTypes, MessageInfos: file_signrpc_signer_proto_msgTypes, }.Build() File_signrpc_signer_proto = out.File diff --git a/lnrpc/signrpc/signer.pb.gw.go b/lnrpc/signrpc/signer.pb.gw.go index 9e97159170..670241f8eb 100644 --- a/lnrpc/signrpc/signer.pb.gw.go +++ b/lnrpc/signrpc/signer.pb.gw.go @@ -201,6 +201,210 @@ func local_request_Signer_DeriveSharedKey_0(ctx context.Context, marshaler runti } +func request_Signer_MuSig2CombineKeys_0(ctx context.Context, marshaler runtime.Marshaler, client SignerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq MuSig2CombineKeysRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.MuSig2CombineKeys(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Signer_MuSig2CombineKeys_0(ctx context.Context, marshaler runtime.Marshaler, server SignerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq MuSig2CombineKeysRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.MuSig2CombineKeys(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Signer_MuSig2CreateSession_0(ctx context.Context, marshaler runtime.Marshaler, client SignerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq MuSig2SessionRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.MuSig2CreateSession(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Signer_MuSig2CreateSession_0(ctx context.Context, marshaler runtime.Marshaler, server SignerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq MuSig2SessionRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.MuSig2CreateSession(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Signer_MuSig2RegisterNonces_0(ctx context.Context, marshaler runtime.Marshaler, client SignerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq MuSig2RegisterNoncesRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.MuSig2RegisterNonces(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Signer_MuSig2RegisterNonces_0(ctx context.Context, marshaler runtime.Marshaler, server SignerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq MuSig2RegisterNoncesRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.MuSig2RegisterNonces(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Signer_MuSig2Sign_0(ctx context.Context, marshaler runtime.Marshaler, client SignerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq MuSig2SignRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.MuSig2Sign(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Signer_MuSig2Sign_0(ctx context.Context, marshaler runtime.Marshaler, server SignerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq MuSig2SignRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.MuSig2Sign(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Signer_MuSig2CombineSig_0(ctx context.Context, marshaler runtime.Marshaler, client SignerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq MuSig2CombineSigRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.MuSig2CombineSig(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Signer_MuSig2CombineSig_0(ctx context.Context, marshaler runtime.Marshaler, server SignerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq MuSig2CombineSigRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.MuSig2CombineSig(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Signer_MuSig2Cleanup_0(ctx context.Context, marshaler runtime.Marshaler, client SignerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq MuSig2CleanupRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.MuSig2Cleanup(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Signer_MuSig2Cleanup_0(ctx context.Context, marshaler runtime.Marshaler, server SignerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq MuSig2CleanupRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.MuSig2Cleanup(ctx, &protoReq) + return msg, metadata, err + +} + // RegisterSignerHandlerServer registers the http handlers for service Signer to "mux". // UnaryRPC :call SignerServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -322,6 +526,144 @@ func RegisterSignerHandlerServer(ctx context.Context, mux *runtime.ServeMux, ser }) + mux.Handle("POST", pattern_Signer_MuSig2CombineKeys_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/signrpc.Signer/MuSig2CombineKeys", runtime.WithHTTPPathPattern("/v2/signer/musig2/combinekeys")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Signer_MuSig2CombineKeys_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Signer_MuSig2CombineKeys_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_Signer_MuSig2CreateSession_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/signrpc.Signer/MuSig2CreateSession", runtime.WithHTTPPathPattern("/v2/signer/musig2/createsession")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Signer_MuSig2CreateSession_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Signer_MuSig2CreateSession_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_Signer_MuSig2RegisterNonces_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/signrpc.Signer/MuSig2RegisterNonces", runtime.WithHTTPPathPattern("/v2/signer/musig2/registernonces")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Signer_MuSig2RegisterNonces_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Signer_MuSig2RegisterNonces_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_Signer_MuSig2Sign_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/signrpc.Signer/MuSig2Sign", runtime.WithHTTPPathPattern("/v2/signer/musig2/sign")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Signer_MuSig2Sign_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Signer_MuSig2Sign_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_Signer_MuSig2CombineSig_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/signrpc.Signer/MuSig2CombineSig", runtime.WithHTTPPathPattern("/v2/signer/musig2/combinesig")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Signer_MuSig2CombineSig_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Signer_MuSig2CombineSig_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_Signer_MuSig2Cleanup_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/signrpc.Signer/MuSig2Cleanup", runtime.WithHTTPPathPattern("/v2/signer/musig2/cleanup")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Signer_MuSig2Cleanup_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Signer_MuSig2Cleanup_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -463,6 +805,126 @@ func RegisterSignerHandlerClient(ctx context.Context, mux *runtime.ServeMux, cli }) + mux.Handle("POST", pattern_Signer_MuSig2CombineKeys_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req, "/signrpc.Signer/MuSig2CombineKeys", runtime.WithHTTPPathPattern("/v2/signer/musig2/combinekeys")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Signer_MuSig2CombineKeys_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Signer_MuSig2CombineKeys_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_Signer_MuSig2CreateSession_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req, "/signrpc.Signer/MuSig2CreateSession", runtime.WithHTTPPathPattern("/v2/signer/musig2/createsession")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Signer_MuSig2CreateSession_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Signer_MuSig2CreateSession_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_Signer_MuSig2RegisterNonces_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req, "/signrpc.Signer/MuSig2RegisterNonces", runtime.WithHTTPPathPattern("/v2/signer/musig2/registernonces")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Signer_MuSig2RegisterNonces_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Signer_MuSig2RegisterNonces_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_Signer_MuSig2Sign_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req, "/signrpc.Signer/MuSig2Sign", runtime.WithHTTPPathPattern("/v2/signer/musig2/sign")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Signer_MuSig2Sign_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Signer_MuSig2Sign_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_Signer_MuSig2CombineSig_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req, "/signrpc.Signer/MuSig2CombineSig", runtime.WithHTTPPathPattern("/v2/signer/musig2/combinesig")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Signer_MuSig2CombineSig_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Signer_MuSig2CombineSig_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_Signer_MuSig2Cleanup_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req, "/signrpc.Signer/MuSig2Cleanup", runtime.WithHTTPPathPattern("/v2/signer/musig2/cleanup")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Signer_MuSig2Cleanup_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Signer_MuSig2Cleanup_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -476,6 +938,18 @@ var ( pattern_Signer_VerifyMessage_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "signer", "verifymessage"}, "")) pattern_Signer_DeriveSharedKey_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "signer", "sharedkey"}, "")) + + pattern_Signer_MuSig2CombineKeys_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "signer", "musig2", "combinekeys"}, "")) + + pattern_Signer_MuSig2CreateSession_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "signer", "musig2", "createsession"}, "")) + + pattern_Signer_MuSig2RegisterNonces_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "signer", "musig2", "registernonces"}, "")) + + pattern_Signer_MuSig2Sign_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "signer", "musig2", "sign"}, "")) + + pattern_Signer_MuSig2CombineSig_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "signer", "musig2", "combinesig"}, "")) + + pattern_Signer_MuSig2Cleanup_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "signer", "musig2", "cleanup"}, "")) ) var ( @@ -488,4 +962,16 @@ var ( forward_Signer_VerifyMessage_0 = runtime.ForwardResponseMessage forward_Signer_DeriveSharedKey_0 = runtime.ForwardResponseMessage + + forward_Signer_MuSig2CombineKeys_0 = runtime.ForwardResponseMessage + + forward_Signer_MuSig2CreateSession_0 = runtime.ForwardResponseMessage + + forward_Signer_MuSig2RegisterNonces_0 = runtime.ForwardResponseMessage + + forward_Signer_MuSig2Sign_0 = runtime.ForwardResponseMessage + + forward_Signer_MuSig2CombineSig_0 = runtime.ForwardResponseMessage + + forward_Signer_MuSig2Cleanup_0 = runtime.ForwardResponseMessage ) diff --git a/lnrpc/signrpc/signer.pb.json.go b/lnrpc/signrpc/signer.pb.json.go index 8f2e6ae2ef..1fcafc1e40 100644 --- a/lnrpc/signrpc/signer.pb.json.go +++ b/lnrpc/signrpc/signer.pb.json.go @@ -147,4 +147,154 @@ func RegisterSignerJSONCallbacks(registry map[string]func(ctx context.Context, } callback(string(respBytes), nil) } + + registry["signrpc.Signer.MuSig2CombineKeys"] = func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { + + req := &MuSig2CombineKeysRequest{} + err := marshaler.Unmarshal([]byte(reqJSON), req) + if err != nil { + callback("", err) + return + } + + client := NewSignerClient(conn) + resp, err := client.MuSig2CombineKeys(ctx, req) + if err != nil { + callback("", err) + return + } + + respBytes, err := marshaler.Marshal(resp) + if err != nil { + callback("", err) + return + } + callback(string(respBytes), nil) + } + + registry["signrpc.Signer.MuSig2CreateSession"] = func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { + + req := &MuSig2SessionRequest{} + err := marshaler.Unmarshal([]byte(reqJSON), req) + if err != nil { + callback("", err) + return + } + + client := NewSignerClient(conn) + resp, err := client.MuSig2CreateSession(ctx, req) + if err != nil { + callback("", err) + return + } + + respBytes, err := marshaler.Marshal(resp) + if err != nil { + callback("", err) + return + } + callback(string(respBytes), nil) + } + + registry["signrpc.Signer.MuSig2RegisterNonces"] = func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { + + req := &MuSig2RegisterNoncesRequest{} + err := marshaler.Unmarshal([]byte(reqJSON), req) + if err != nil { + callback("", err) + return + } + + client := NewSignerClient(conn) + resp, err := client.MuSig2RegisterNonces(ctx, req) + if err != nil { + callback("", err) + return + } + + respBytes, err := marshaler.Marshal(resp) + if err != nil { + callback("", err) + return + } + callback(string(respBytes), nil) + } + + registry["signrpc.Signer.MuSig2Sign"] = func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { + + req := &MuSig2SignRequest{} + err := marshaler.Unmarshal([]byte(reqJSON), req) + if err != nil { + callback("", err) + return + } + + client := NewSignerClient(conn) + resp, err := client.MuSig2Sign(ctx, req) + if err != nil { + callback("", err) + return + } + + respBytes, err := marshaler.Marshal(resp) + if err != nil { + callback("", err) + return + } + callback(string(respBytes), nil) + } + + registry["signrpc.Signer.MuSig2CombineSig"] = func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { + + req := &MuSig2CombineSigRequest{} + err := marshaler.Unmarshal([]byte(reqJSON), req) + if err != nil { + callback("", err) + return + } + + client := NewSignerClient(conn) + resp, err := client.MuSig2CombineSig(ctx, req) + if err != nil { + callback("", err) + return + } + + respBytes, err := marshaler.Marshal(resp) + if err != nil { + callback("", err) + return + } + callback(string(respBytes), nil) + } + + registry["signrpc.Signer.MuSig2Cleanup"] = func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { + + req := &MuSig2CleanupRequest{} + err := marshaler.Unmarshal([]byte(reqJSON), req) + if err != nil { + callback("", err) + return + } + + client := NewSignerClient(conn) + resp, err := client.MuSig2Cleanup(ctx, req) + if err != nil { + callback("", err) + return + } + + respBytes, err := marshaler.Marshal(resp) + if err != nil { + callback("", err) + return + } + callback(string(respBytes), nil) + } } diff --git a/lnrpc/signrpc/signer.proto b/lnrpc/signrpc/signer.proto index 74c38f7e3e..9085c6f674 100644 --- a/lnrpc/signrpc/signer.proto +++ b/lnrpc/signrpc/signer.proto @@ -62,6 +62,87 @@ service Signer { hashed with sha256, resulting in the final key length of 256bit. */ rpc DeriveSharedKey (SharedKeyRequest) returns (SharedKeyResponse); + + /* + MuSig2CombineKeys (experimental!) is a stateless helper RPC that can be used + to calculate the combined MuSig2 public key from a list of all participating + signers' public keys. This RPC is completely stateless and deterministic and + does not create any signing session. It can be used to determine the Taproot + public key that should be put in an on-chain output once all public keys are + known. A signing session is only needed later when that output should be + _spent_ again. + + NOTE: The MuSig2 BIP is not final yet and therefore this API must be + considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming + releases. Backward compatibility is not guaranteed! + */ + rpc MuSig2CombineKeys (MuSig2CombineKeysRequest) + returns (MuSig2CombineKeysResponse); + + /* + MuSig2CreateSession (experimental!) creates a new MuSig2 signing session + using the local key identified by the key locator. The complete list of all + public keys of all signing parties must be provided, including the public + key of the local signing key. If nonces of other parties are already known, + they can be submitted as well to reduce the number of RPC calls necessary + later on. + + NOTE: The MuSig2 BIP is not final yet and therefore this API must be + considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming + releases. Backward compatibility is not guaranteed! + */ + rpc MuSig2CreateSession (MuSig2SessionRequest) + returns (MuSig2SessionResponse); + + /* + MuSig2RegisterNonces (experimental!) registers one or more public nonces of + other signing participants for a session identified by its ID. This RPC can + be called multiple times until all nonces are registered. + + NOTE: The MuSig2 BIP is not final yet and therefore this API must be + considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming + releases. Backward compatibility is not guaranteed! + */ + rpc MuSig2RegisterNonces (MuSig2RegisterNoncesRequest) + returns (MuSig2RegisterNoncesResponse); + + /* + MuSig2Sign (experimental!) creates a partial signature using the local + signing key that was specified when the session was created. This can only + be called when all public nonces of all participants are known and have been + registered with the session. If this node isn't responsible for combining + all the partial signatures, then the cleanup flag should be set, indicating + that the session can be removed from memory once the signature was produced. + + NOTE: The MuSig2 BIP is not final yet and therefore this API must be + considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming + releases. Backward compatibility is not guaranteed! + */ + rpc MuSig2Sign (MuSig2SignRequest) returns (MuSig2SignResponse); + + /* + MuSig2CombineSig (experimental!) combines the given partial signature(s) + with the local one, if it already exists. Once a partial signature of all + participants is registered, the final signature will be combined and + returned. + + NOTE: The MuSig2 BIP is not final yet and therefore this API must be + considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming + releases. Backward compatibility is not guaranteed! + */ + rpc MuSig2CombineSig (MuSig2CombineSigRequest) + returns (MuSig2CombineSigResponse); + + /* + MuSig2Cleanup (experimental!) allows a caller to clean up a session early in + cases where it's obvious that the signing session won't succeed and the + resources can be released. + + NOTE: The MuSig2 BIP is not final yet and therefore this API must be + considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming + releases. Backward compatibility is not guaranteed! + */ + rpc MuSig2Cleanup (MuSig2CleanupRequest) returns (MuSig2CleanupResponse); } message KeyLocator { @@ -74,14 +155,14 @@ message KeyLocator { message KeyDescriptor { /* - The raw bytes of the key being identified. Either this or the KeyLocator - must be specified. + The raw bytes of the public key in the key pair being identified. Either + this or the KeyLocator must be specified. */ bytes raw_key_bytes = 1; /* - The key locator that identifies which key to use for signing. Either this - or the raw bytes of the target key must be specified. + The key locator that identifies which private key to use for signing. + Either this or the raw bytes of the target public key must be specified. */ KeyLocator key_loc = 2; } @@ -94,6 +175,32 @@ message TxOut { bytes pk_script = 2; } +enum SignMethod { + /* + Specifies that a SegWit v0 (p2wkh, np2wkh, p2wsh) input script should be + signed. + */ + SIGN_METHOD_WITNESS_V0 = 0; + + /* + Specifies that a SegWit v1 (p2tr) input should be signed by using the + BIP0086 method (commit to internal key only). + */ + SIGN_METHOD_TAPROOT_KEY_SPEND_BIP0086 = 1; + + /* + Specifies that a SegWit v1 (p2tr) input should be signed by using a given + taproot hash to commit to in addition to the internal key. + */ + SIGN_METHOD_TAPROOT_KEY_SPEND = 2; + + /* + Specifies that a SegWit v1 (p2tr) input should be spent using the script + path and that a specific leaf script should be signed for. + */ + SIGN_METHOD_TAPROOT_SCRIPT_SPEND = 3; +} + message SignDescriptor { /* A descriptor that precisely describes *which* key to use for signing. This @@ -129,8 +236,22 @@ message SignDescriptor { bytes double_tweak = 3; /* - The full script required to properly redeem the output. This field will - only be populated if a p2wsh or a p2sh output is being signed. + The 32 byte input to the taproot tweak derivation that is used to derive + the output key from an internal key: outputKey = internalKey + + tagged_hash("tapTweak", internalKey || tapTweak). + + When doing a BIP 86 spend, this field can be an empty byte slice. + + When doing a normal key path spend, with the output key committing to an + actual script root, then this field should be: the tapscript root hash. + */ + bytes tap_tweak = 10; + + /* + The full script required to properly redeem the output. This field will + only be populated if a p2tr, p2wsh or a p2sh output is being signed. If a + taproot script path spend is being attempted, then this should be the raw + leaf script. */ bytes witness_script = 4; @@ -150,6 +271,14 @@ message SignDescriptor { The target input within the transaction that should be signed. */ int32 input_index = 8; + + /* + The sign method specifies how the input should be signed. Depending on the + method, either the tap_tweak, witness_script or both need to be specified. + Defaults to SegWit v0 signing to be backward compatible with older RPC + clients. + */ + SignMethod sign_method = 9; } message SignReq { @@ -158,6 +287,12 @@ message SignReq { // A set of sign descriptors, for each input to be signed. repeated SignDescriptor sign_descs = 2; + + /* + The full list of UTXO information for each of the inputs being spent. This + is required when spending one or more taproot (SegWit v1) outputs. + */ + repeated TxOut prev_outputs = 3; } message SignResp { @@ -248,3 +383,231 @@ message SharedKeyResponse { // The shared public key, hashed with sha256. bytes shared_key = 1; } + +message TweakDesc { + /* + Tweak is the 32-byte value that will modify the public key. + */ + bytes tweak = 1; + + /* + Specifies if the target key should be converted to an x-only public key + before tweaking. If true, then the public key will be mapped to an x-only + key before the tweaking operation is applied. + */ + bool is_x_only = 2; +} + +message TaprootTweakDesc { + /* + The root hash of the tapscript tree if a script path is committed to. If + the MuSig2 key put on chain doesn't also commit to a script path (BIP-0086 + key spend only), then this needs to be empty and the key_spend_only field + below must be set to true. This is required because gRPC cannot + differentiate between a zero-size byte slice and a nil byte slice (both + would be serialized the same way). So the extra boolean is required. + */ + bytes script_root = 1; + + /* + Indicates that the above script_root is expected to be empty because this + is a BIP-0086 key spend only commitment where only the internal key is + committed to instead of also including a script root hash. + */ + bool key_spend_only = 2; +} + +message MuSig2CombineKeysRequest { + /* + A list of all public keys (serialized in 32-byte x-only format!) + participating in the signing session. The list will always be sorted + lexicographically internally. This must include the local key which is + described by the above key_loc. + */ + repeated bytes all_signer_pubkeys = 1; + + /* + A series of optional generic tweaks to be applied to the the aggregated + public key. + */ + repeated TweakDesc tweaks = 2; + + /* + An optional taproot specific tweak that must be specified if the MuSig2 + combined key will be used as the main taproot key of a taproot output + on-chain. + */ + TaprootTweakDesc taproot_tweak = 3; +} + +message MuSig2CombineKeysResponse { + /* + The combined public key (in the 32-byte x-only format) with all tweaks + applied to it. If a taproot tweak is specified, this corresponds to the + taproot key that can be put into the on-chain output. + */ + bytes combined_key = 1; + + /* + The raw combined public key (in the 32-byte x-only format) before any tweaks + are applied to it. If a taproot tweak is specified, this corresponds to the + internal key that needs to be put into the witness if the script spend path + is used. + */ + bytes taproot_internal_key = 2; +} + +message MuSig2SessionRequest { + /* + The key locator that identifies which key to use for signing. + */ + KeyLocator key_loc = 1; + + /* + A list of all public keys (serialized in 32-byte x-only format!) + participating in the signing session. The list will always be sorted + lexicographically internally. This must include the local key which is + described by the above key_loc. + */ + repeated bytes all_signer_pubkeys = 2; + + /* + An optional list of all public nonces of other signing participants that + might already be known. + */ + repeated bytes other_signer_public_nonces = 3; + + /* + A series of optional generic tweaks to be applied to the the aggregated + public key. + */ + repeated TweakDesc tweaks = 4; + + /* + An optional taproot specific tweak that must be specified if the MuSig2 + combined key will be used as the main taproot key of a taproot output + on-chain. + */ + TaprootTweakDesc taproot_tweak = 5; +} + +message MuSig2SessionResponse { + /* + The unique ID that represents this signing session. A session can be used + for producing a signature a single time. If the signing fails for any + reason, a new session with the same participants needs to be created. + */ + bytes session_id = 1; + + /* + The combined public key (in the 32-byte x-only format) with all tweaks + applied to it. If a taproot tweak is specified, this corresponds to the + taproot key that can be put into the on-chain output. + */ + bytes combined_key = 2; + + /* + The raw combined public key (in the 32-byte x-only format) before any tweaks + are applied to it. If a taproot tweak is specified, this corresponds to the + internal key that needs to be put into the witness if the script spend path + is used. + */ + bytes taproot_internal_key = 3; + + /* + The two public nonces the local signer uses, combined into a single value + of 66 bytes. Can be split into the two 33-byte points to get the individual + nonces. + */ + bytes local_public_nonces = 4; + + /* + Indicates whether all nonces required to start the signing process are known + now. + */ + bool have_all_nonces = 5; +} + +message MuSig2RegisterNoncesRequest { + /* + The unique ID of the signing session those nonces should be registered with. + */ + bytes session_id = 1; + + /* + A list of all public nonces of other signing participants that should be + registered. + */ + repeated bytes other_signer_public_nonces = 3; +} + +message MuSig2RegisterNoncesResponse { + /* + Indicates whether all nonces required to start the signing process are known + now. + */ + bool have_all_nonces = 1; +} + +message MuSig2SignRequest { + /* + The unique ID of the signing session to use for signing. + */ + bytes session_id = 1; + + /* + The 32-byte SHA256 digest of the message to sign. + */ + bytes message_digest = 2; + + /* + Cleanup indicates that after signing, the session state can be cleaned up, + since another participant is going to be responsible for combining the + partial signatures. + */ + bool cleanup = 3; +} + +message MuSig2SignResponse { + /* + The partial signature created by the local signer. + */ + bytes local_partial_signature = 1; +} + +message MuSig2CombineSigRequest { + /* + The unique ID of the signing session to combine the signatures for. + */ + bytes session_id = 1; + + /* + The list of all other participants' partial signatures to add to the current + session. + */ + repeated bytes other_partial_signatures = 2; +} + +message MuSig2CombineSigResponse { + /* + Indicates whether all partial signatures required to create a final, full + signature are known yet. If this is true, then the final_signature field is + set, otherwise it is empty. + */ + bool have_all_signatures = 1; + + /* + The final, full signature that is valid for the combined public key. + */ + bytes final_signature = 2; +} + +message MuSig2CleanupRequest { + /* + The unique ID of the signing session that should be removed/cleaned up. + */ + bytes session_id = 1; +} + +message MuSig2CleanupResponse { +} \ No newline at end of file diff --git a/lnrpc/signrpc/signer.swagger.json b/lnrpc/signrpc/signer.swagger.json index 45db7ee8e7..edd0afa2e8 100644 --- a/lnrpc/signrpc/signer.swagger.json +++ b/lnrpc/signrpc/signer.swagger.json @@ -50,6 +50,210 @@ ] } }, + "/v2/signer/musig2/cleanup": { + "post": { + "summary": "MuSig2Cleanup (experimental!) allows a caller to clean up a session early in\ncases where it's obvious that the signing session won't succeed and the\nresources can be released.", + "description": "NOTE: The MuSig2 BIP is not final yet and therefore this API must be\nconsidered to be HIGHLY EXPERIMENTAL and subject to change in upcoming\nreleases. Backward compatibility is not guaranteed!", + "operationId": "Signer_MuSig2Cleanup", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/signrpcMuSig2CleanupResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/signrpcMuSig2CleanupRequest" + } + } + ], + "tags": [ + "Signer" + ] + } + }, + "/v2/signer/musig2/combinekeys": { + "post": { + "summary": "MuSig2CombineKeys (experimental!) is a stateless helper RPC that can be used\nto calculate the combined MuSig2 public key from a list of all participating\nsigners' public keys. This RPC is completely stateless and deterministic and\ndoes not create any signing session. It can be used to determine the Taproot\npublic key that should be put in an on-chain output once all public keys are\nknown. A signing session is only needed later when that output should be\n_spent_ again.", + "description": "NOTE: The MuSig2 BIP is not final yet and therefore this API must be\nconsidered to be HIGHLY EXPERIMENTAL and subject to change in upcoming\nreleases. Backward compatibility is not guaranteed!", + "operationId": "Signer_MuSig2CombineKeys", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/signrpcMuSig2CombineKeysResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/signrpcMuSig2CombineKeysRequest" + } + } + ], + "tags": [ + "Signer" + ] + } + }, + "/v2/signer/musig2/combinesig": { + "post": { + "summary": "MuSig2CombineSig (experimental!) combines the given partial signature(s)\nwith the local one, if it already exists. Once a partial signature of all\nparticipants is registered, the final signature will be combined and\nreturned.", + "description": "NOTE: The MuSig2 BIP is not final yet and therefore this API must be\nconsidered to be HIGHLY EXPERIMENTAL and subject to change in upcoming\nreleases. Backward compatibility is not guaranteed!", + "operationId": "Signer_MuSig2CombineSig", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/signrpcMuSig2CombineSigResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/signrpcMuSig2CombineSigRequest" + } + } + ], + "tags": [ + "Signer" + ] + } + }, + "/v2/signer/musig2/createsession": { + "post": { + "summary": "MuSig2CreateSession (experimental!) creates a new MuSig2 signing session\nusing the local key identified by the key locator. The complete list of all\npublic keys of all signing parties must be provided, including the public\nkey of the local signing key. If nonces of other parties are already known,\nthey can be submitted as well to reduce the number of RPC calls necessary\nlater on.", + "description": "NOTE: The MuSig2 BIP is not final yet and therefore this API must be\nconsidered to be HIGHLY EXPERIMENTAL and subject to change in upcoming\nreleases. Backward compatibility is not guaranteed!", + "operationId": "Signer_MuSig2CreateSession", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/signrpcMuSig2SessionResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/signrpcMuSig2SessionRequest" + } + } + ], + "tags": [ + "Signer" + ] + } + }, + "/v2/signer/musig2/registernonces": { + "post": { + "summary": "MuSig2RegisterNonces (experimental!) registers one or more public nonces of\nother signing participants for a session identified by its ID. This RPC can\nbe called multiple times until all nonces are registered.", + "description": "NOTE: The MuSig2 BIP is not final yet and therefore this API must be\nconsidered to be HIGHLY EXPERIMENTAL and subject to change in upcoming\nreleases. Backward compatibility is not guaranteed!", + "operationId": "Signer_MuSig2RegisterNonces", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/signrpcMuSig2RegisterNoncesResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/signrpcMuSig2RegisterNoncesRequest" + } + } + ], + "tags": [ + "Signer" + ] + } + }, + "/v2/signer/musig2/sign": { + "post": { + "summary": "MuSig2Sign (experimental!) creates a partial signature using the local\nsigning key that was specified when the session was created. This can only\nbe called when all public nonces of all participants are known and have been\nregistered with the session. If this node isn't responsible for combining\nall the partial signatures, then the cleanup flag should be set, indicating\nthat the session can be removed from memory once the signature was produced.", + "description": "NOTE: The MuSig2 BIP is not final yet and therefore this API must be\nconsidered to be HIGHLY EXPERIMENTAL and subject to change in upcoming\nreleases. Backward compatibility is not guaranteed!", + "operationId": "Signer_MuSig2Sign", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/signrpcMuSig2SignResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/signrpcMuSig2SignRequest" + } + } + ], + "tags": [ + "Signer" + ] + } + }, "/v2/signer/sharedkey": { "post": { "summary": "DeriveSharedKey returns a shared secret key by performing Diffie-Hellman key\nderivation between the ephemeral public key in the request and the node's\nkey specified in the key_desc parameter. Either a key locator or a raw\npublic key is expected in the key_desc, if neither is supplied, defaults to\nthe node's identity private key:\nP_shared = privKeyNode * ephemeralPubkey\nThe resulting shared public key is serialized in the compressed format and\nhashed with sha256, resulting in the final key length of 256bit.", @@ -253,11 +457,11 @@ "raw_key_bytes": { "type": "string", "format": "byte", - "description": "The raw bytes of the key being identified. Either this or the KeyLocator\nmust be specified." + "description": "The raw bytes of the public key in the key pair being identified. Either\nthis or the KeyLocator must be specified." }, "key_loc": { "$ref": "#/definitions/signrpcKeyLocator", - "description": "The key locator that identifies which key to use for signing. Either this\nor the raw bytes of the target key must be specified." + "description": "The key locator that identifies which private key to use for signing.\nEither this or the raw bytes of the target public key must be specified." } } }, @@ -276,6 +480,211 @@ } } }, + "signrpcMuSig2CleanupRequest": { + "type": "object", + "properties": { + "session_id": { + "type": "string", + "format": "byte", + "description": "The unique ID of the signing session that should be removed/cleaned up." + } + } + }, + "signrpcMuSig2CleanupResponse": { + "type": "object" + }, + "signrpcMuSig2CombineKeysRequest": { + "type": "object", + "properties": { + "all_signer_pubkeys": { + "type": "array", + "items": { + "type": "string", + "format": "byte" + }, + "description": "A list of all public keys (serialized in 32-byte x-only format!)\nparticipating in the signing session. The list will always be sorted\nlexicographically internally. This must include the local key which is\ndescribed by the above key_loc." + }, + "tweaks": { + "type": "array", + "items": { + "$ref": "#/definitions/signrpcTweakDesc" + }, + "description": "A series of optional generic tweaks to be applied to the the aggregated\npublic key." + }, + "taproot_tweak": { + "$ref": "#/definitions/signrpcTaprootTweakDesc", + "description": "An optional taproot specific tweak that must be specified if the MuSig2\ncombined key will be used as the main taproot key of a taproot output\non-chain." + } + } + }, + "signrpcMuSig2CombineKeysResponse": { + "type": "object", + "properties": { + "combined_key": { + "type": "string", + "format": "byte", + "description": "The combined public key (in the 32-byte x-only format) with all tweaks\napplied to it. If a taproot tweak is specified, this corresponds to the\ntaproot key that can be put into the on-chain output." + }, + "taproot_internal_key": { + "type": "string", + "format": "byte", + "description": "The raw combined public key (in the 32-byte x-only format) before any tweaks\nare applied to it. If a taproot tweak is specified, this corresponds to the\ninternal key that needs to be put into the witness if the script spend path\nis used." + } + } + }, + "signrpcMuSig2CombineSigRequest": { + "type": "object", + "properties": { + "session_id": { + "type": "string", + "format": "byte", + "description": "The unique ID of the signing session to combine the signatures for." + }, + "other_partial_signatures": { + "type": "array", + "items": { + "type": "string", + "format": "byte" + }, + "description": "The list of all other participants' partial signatures to add to the current\nsession." + } + } + }, + "signrpcMuSig2CombineSigResponse": { + "type": "object", + "properties": { + "have_all_signatures": { + "type": "boolean", + "description": "Indicates whether all partial signatures required to create a final, full\nsignature are known yet. If this is true, then the final_signature field is\nset, otherwise it is empty." + }, + "final_signature": { + "type": "string", + "format": "byte", + "description": "The final, full signature that is valid for the combined public key." + } + } + }, + "signrpcMuSig2RegisterNoncesRequest": { + "type": "object", + "properties": { + "session_id": { + "type": "string", + "format": "byte", + "description": "The unique ID of the signing session those nonces should be registered with." + }, + "other_signer_public_nonces": { + "type": "array", + "items": { + "type": "string", + "format": "byte" + }, + "description": "A list of all public nonces of other signing participants that should be\nregistered." + } + } + }, + "signrpcMuSig2RegisterNoncesResponse": { + "type": "object", + "properties": { + "have_all_nonces": { + "type": "boolean", + "description": "Indicates whether all nonces required to start the signing process are known\nnow." + } + } + }, + "signrpcMuSig2SessionRequest": { + "type": "object", + "properties": { + "key_loc": { + "$ref": "#/definitions/signrpcKeyLocator", + "description": "The key locator that identifies which key to use for signing." + }, + "all_signer_pubkeys": { + "type": "array", + "items": { + "type": "string", + "format": "byte" + }, + "description": "A list of all public keys (serialized in 32-byte x-only format!)\nparticipating in the signing session. The list will always be sorted\nlexicographically internally. This must include the local key which is\ndescribed by the above key_loc." + }, + "other_signer_public_nonces": { + "type": "array", + "items": { + "type": "string", + "format": "byte" + }, + "description": "An optional list of all public nonces of other signing participants that\nmight already be known." + }, + "tweaks": { + "type": "array", + "items": { + "$ref": "#/definitions/signrpcTweakDesc" + }, + "description": "A series of optional generic tweaks to be applied to the the aggregated\npublic key." + }, + "taproot_tweak": { + "$ref": "#/definitions/signrpcTaprootTweakDesc", + "description": "An optional taproot specific tweak that must be specified if the MuSig2\ncombined key will be used as the main taproot key of a taproot output\non-chain." + } + } + }, + "signrpcMuSig2SessionResponse": { + "type": "object", + "properties": { + "session_id": { + "type": "string", + "format": "byte", + "description": "The unique ID that represents this signing session. A session can be used\nfor producing a signature a single time. If the signing fails for any\nreason, a new session with the same participants needs to be created." + }, + "combined_key": { + "type": "string", + "format": "byte", + "description": "The combined public key (in the 32-byte x-only format) with all tweaks\napplied to it. If a taproot tweak is specified, this corresponds to the\ntaproot key that can be put into the on-chain output." + }, + "taproot_internal_key": { + "type": "string", + "format": "byte", + "description": "The raw combined public key (in the 32-byte x-only format) before any tweaks\nare applied to it. If a taproot tweak is specified, this corresponds to the\ninternal key that needs to be put into the witness if the script spend path\nis used." + }, + "local_public_nonces": { + "type": "string", + "format": "byte", + "description": "The two public nonces the local signer uses, combined into a single value\nof 66 bytes. Can be split into the two 33-byte points to get the individual\nnonces." + }, + "have_all_nonces": { + "type": "boolean", + "description": "Indicates whether all nonces required to start the signing process are known\nnow." + } + } + }, + "signrpcMuSig2SignRequest": { + "type": "object", + "properties": { + "session_id": { + "type": "string", + "format": "byte", + "description": "The unique ID of the signing session to use for signing." + }, + "message_digest": { + "type": "string", + "format": "byte", + "description": "The 32-byte SHA256 digest of the message to sign." + }, + "cleanup": { + "type": "boolean", + "description": "Cleanup indicates that after signing, the session state can be cleaned up,\nsince another participant is going to be responsible for combining the\npartial signatures." + } + } + }, + "signrpcMuSig2SignResponse": { + "type": "object", + "properties": { + "local_partial_signature": { + "type": "string", + "format": "byte", + "description": "The partial signature created by the local signer." + } + } + }, "signrpcSharedKeyRequest": { "type": "object", "properties": { @@ -322,10 +731,15 @@ "format": "byte", "description": "A private key that will be used in combination with its corresponding\nprivate key to derive the private key that is to be used to sign the target\ninput. Within the Lightning protocol, this value is typically the\ncommitment secret from a previously revoked commitment transaction. This\nvalue is in combination with two hash values, and the original private key\nto derive the private key to be used when signing.\n\nk = (privKey*sha256(pubKey || tweakPub) +\ntweakPriv*sha256(tweakPub || pubKey)) mod N" }, + "tap_tweak": { + "type": "string", + "format": "byte", + "description": "The 32 byte input to the taproot tweak derivation that is used to derive\nthe output key from an internal key: outputKey = internalKey +\ntagged_hash(\"tapTweak\", internalKey || tapTweak).\n\nWhen doing a BIP 86 spend, this field can be an empty byte slice.\n\nWhen doing a normal key path spend, with the output key committing to an\nactual script root, then this field should be: the tapscript root hash." + }, "witness_script": { "type": "string", "format": "byte", - "description": "The full script required to properly redeem the output. This field will\nonly be populated if a p2wsh or a p2sh output is being signed." + "description": "The full script required to properly redeem the output. This field will\nonly be populated if a p2tr, p2wsh or a p2sh output is being signed. If a\ntaproot script path spend is being attempted, then this should be the raw\nleaf script." }, "output": { "$ref": "#/definitions/signrpcTxOut", @@ -340,6 +754,10 @@ "type": "integer", "format": "int32", "description": "The target input within the transaction that should be signed." + }, + "sign_method": { + "$ref": "#/definitions/signrpcSignMethod", + "description": "The sign method specifies how the input should be signed. Depending on the\nmethod, either the tap_tweak, witness_script or both need to be specified.\nDefaults to SegWit v0 signing to be backward compatible with older RPC\nclients." } } }, @@ -375,6 +793,17 @@ } } }, + "signrpcSignMethod": { + "type": "string", + "enum": [ + "SIGN_METHOD_WITNESS_V0", + "SIGN_METHOD_TAPROOT_KEY_SPEND_BIP0086", + "SIGN_METHOD_TAPROOT_KEY_SPEND", + "SIGN_METHOD_TAPROOT_SCRIPT_SPEND" + ], + "default": "SIGN_METHOD_WITNESS_V0", + "description": " - SIGN_METHOD_WITNESS_V0: Specifies that a SegWit v0 (p2wkh, np2wkh, p2wsh) input script should be\nsigned.\n - SIGN_METHOD_TAPROOT_KEY_SPEND_BIP0086: Specifies that a SegWit v1 (p2tr) input should be signed by using the\nBIP0086 method (commit to internal key only).\n - SIGN_METHOD_TAPROOT_KEY_SPEND: Specifies that a SegWit v1 (p2tr) input should be signed by using a given\ntaproot hash to commit to in addition to the internal key.\n - SIGN_METHOD_TAPROOT_SCRIPT_SPEND: Specifies that a SegWit v1 (p2tr) input should be spent using the script\npath and that a specific leaf script should be signed for." + }, "signrpcSignReq": { "type": "object", "properties": { @@ -389,6 +818,13 @@ "$ref": "#/definitions/signrpcSignDescriptor" }, "description": "A set of sign descriptors, for each input to be signed." + }, + "prev_outputs": { + "type": "array", + "items": { + "$ref": "#/definitions/signrpcTxOut" + }, + "description": "The full list of UTXO information for each of the inputs being spent. This\nis required when spending one or more taproot (SegWit v1) outputs." } } }, @@ -405,6 +841,34 @@ } } }, + "signrpcTaprootTweakDesc": { + "type": "object", + "properties": { + "script_root": { + "type": "string", + "format": "byte", + "description": "The root hash of the tapscript tree if a script path is committed to. If\nthe MuSig2 key put on chain doesn't also commit to a script path (BIP-0086\nkey spend only), then this needs to be empty and the key_spend_only field\nbelow must be set to true. This is required because gRPC cannot\ndifferentiate between a zero-size byte slice and a nil byte slice (both\nwould be serialized the same way). So the extra boolean is required." + }, + "key_spend_only": { + "type": "boolean", + "description": "Indicates that the above script_root is expected to be empty because this\nis a BIP-0086 key spend only commitment where only the internal key is\ncommitted to instead of also including a script root hash." + } + } + }, + "signrpcTweakDesc": { + "type": "object", + "properties": { + "tweak": { + "type": "string", + "format": "byte", + "description": "Tweak is the 32-byte value that will modify the public key." + }, + "is_x_only": { + "type": "boolean", + "description": "Specifies if the target key should be converted to an x-only public key\nbefore tweaking. If true, then the public key will be mapped to an x-only\nkey before the tweaking operation is applied." + } + } + }, "signrpcTxOut": { "type": "object", "properties": { diff --git a/lnrpc/signrpc/signer.yaml b/lnrpc/signrpc/signer.yaml index 295f8b1605..57699de544 100644 --- a/lnrpc/signrpc/signer.yaml +++ b/lnrpc/signrpc/signer.yaml @@ -18,3 +18,21 @@ http: - selector: signrpc.Signer.DeriveSharedKey post: "/v2/signer/sharedkey" body: "*" + - selector: signrpc.Signer.MuSig2CombineKeys + post: "/v2/signer/musig2/combinekeys" + body: "*" + - selector: signrpc.Signer.MuSig2CreateSession + post: "/v2/signer/musig2/createsession" + body: "*" + - selector: signrpc.Signer.MuSig2RegisterNonces + post: "/v2/signer/musig2/registernonces" + body: "*" + - selector: signrpc.Signer.MuSig2Sign + post: "/v2/signer/musig2/sign" + body: "*" + - selector: signrpc.Signer.MuSig2CombineSig + post: "/v2/signer/musig2/combinesig" + body: "*" + - selector: signrpc.Signer.MuSig2Cleanup + post: "/v2/signer/musig2/cleanup" + body: "*" diff --git a/lnrpc/signrpc/signer_grpc.pb.go b/lnrpc/signrpc/signer_grpc.pb.go index 4a6724fff4..2c0a018991 100644 --- a/lnrpc/signrpc/signer_grpc.pb.go +++ b/lnrpc/signrpc/signer_grpc.pb.go @@ -64,6 +64,71 @@ type SignerClient interface { //The resulting shared public key is serialized in the compressed format and //hashed with sha256, resulting in the final key length of 256bit. DeriveSharedKey(ctx context.Context, in *SharedKeyRequest, opts ...grpc.CallOption) (*SharedKeyResponse, error) + // + //MuSig2CombineKeys (experimental!) is a stateless helper RPC that can be used + //to calculate the combined MuSig2 public key from a list of all participating + //signers' public keys. This RPC is completely stateless and deterministic and + //does not create any signing session. It can be used to determine the Taproot + //public key that should be put in an on-chain output once all public keys are + //known. A signing session is only needed later when that output should be + //_spent_ again. + // + //NOTE: The MuSig2 BIP is not final yet and therefore this API must be + //considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming + //releases. Backward compatibility is not guaranteed! + MuSig2CombineKeys(ctx context.Context, in *MuSig2CombineKeysRequest, opts ...grpc.CallOption) (*MuSig2CombineKeysResponse, error) + // + //MuSig2CreateSession (experimental!) creates a new MuSig2 signing session + //using the local key identified by the key locator. The complete list of all + //public keys of all signing parties must be provided, including the public + //key of the local signing key. If nonces of other parties are already known, + //they can be submitted as well to reduce the number of RPC calls necessary + //later on. + // + //NOTE: The MuSig2 BIP is not final yet and therefore this API must be + //considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming + //releases. Backward compatibility is not guaranteed! + MuSig2CreateSession(ctx context.Context, in *MuSig2SessionRequest, opts ...grpc.CallOption) (*MuSig2SessionResponse, error) + // + //MuSig2RegisterNonces (experimental!) registers one or more public nonces of + //other signing participants for a session identified by its ID. This RPC can + //be called multiple times until all nonces are registered. + // + //NOTE: The MuSig2 BIP is not final yet and therefore this API must be + //considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming + //releases. Backward compatibility is not guaranteed! + MuSig2RegisterNonces(ctx context.Context, in *MuSig2RegisterNoncesRequest, opts ...grpc.CallOption) (*MuSig2RegisterNoncesResponse, error) + // + //MuSig2Sign (experimental!) creates a partial signature using the local + //signing key that was specified when the session was created. This can only + //be called when all public nonces of all participants are known and have been + //registered with the session. If this node isn't responsible for combining + //all the partial signatures, then the cleanup flag should be set, indicating + //that the session can be removed from memory once the signature was produced. + // + //NOTE: The MuSig2 BIP is not final yet and therefore this API must be + //considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming + //releases. Backward compatibility is not guaranteed! + MuSig2Sign(ctx context.Context, in *MuSig2SignRequest, opts ...grpc.CallOption) (*MuSig2SignResponse, error) + // + //MuSig2CombineSig (experimental!) combines the given partial signature(s) + //with the local one, if it already exists. Once a partial signature of all + //participants is registered, the final signature will be combined and + //returned. + // + //NOTE: The MuSig2 BIP is not final yet and therefore this API must be + //considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming + //releases. Backward compatibility is not guaranteed! + MuSig2CombineSig(ctx context.Context, in *MuSig2CombineSigRequest, opts ...grpc.CallOption) (*MuSig2CombineSigResponse, error) + // + //MuSig2Cleanup (experimental!) allows a caller to clean up a session early in + //cases where it's obvious that the signing session won't succeed and the + //resources can be released. + // + //NOTE: The MuSig2 BIP is not final yet and therefore this API must be + //considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming + //releases. Backward compatibility is not guaranteed! + MuSig2Cleanup(ctx context.Context, in *MuSig2CleanupRequest, opts ...grpc.CallOption) (*MuSig2CleanupResponse, error) } type signerClient struct { @@ -119,6 +184,60 @@ func (c *signerClient) DeriveSharedKey(ctx context.Context, in *SharedKeyRequest return out, nil } +func (c *signerClient) MuSig2CombineKeys(ctx context.Context, in *MuSig2CombineKeysRequest, opts ...grpc.CallOption) (*MuSig2CombineKeysResponse, error) { + out := new(MuSig2CombineKeysResponse) + err := c.cc.Invoke(ctx, "/signrpc.Signer/MuSig2CombineKeys", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *signerClient) MuSig2CreateSession(ctx context.Context, in *MuSig2SessionRequest, opts ...grpc.CallOption) (*MuSig2SessionResponse, error) { + out := new(MuSig2SessionResponse) + err := c.cc.Invoke(ctx, "/signrpc.Signer/MuSig2CreateSession", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *signerClient) MuSig2RegisterNonces(ctx context.Context, in *MuSig2RegisterNoncesRequest, opts ...grpc.CallOption) (*MuSig2RegisterNoncesResponse, error) { + out := new(MuSig2RegisterNoncesResponse) + err := c.cc.Invoke(ctx, "/signrpc.Signer/MuSig2RegisterNonces", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *signerClient) MuSig2Sign(ctx context.Context, in *MuSig2SignRequest, opts ...grpc.CallOption) (*MuSig2SignResponse, error) { + out := new(MuSig2SignResponse) + err := c.cc.Invoke(ctx, "/signrpc.Signer/MuSig2Sign", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *signerClient) MuSig2CombineSig(ctx context.Context, in *MuSig2CombineSigRequest, opts ...grpc.CallOption) (*MuSig2CombineSigResponse, error) { + out := new(MuSig2CombineSigResponse) + err := c.cc.Invoke(ctx, "/signrpc.Signer/MuSig2CombineSig", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *signerClient) MuSig2Cleanup(ctx context.Context, in *MuSig2CleanupRequest, opts ...grpc.CallOption) (*MuSig2CleanupResponse, error) { + out := new(MuSig2CleanupResponse) + err := c.cc.Invoke(ctx, "/signrpc.Signer/MuSig2Cleanup", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // SignerServer is the server API for Signer service. // All implementations must embed UnimplementedSignerServer // for forward compatibility @@ -169,6 +288,71 @@ type SignerServer interface { //The resulting shared public key is serialized in the compressed format and //hashed with sha256, resulting in the final key length of 256bit. DeriveSharedKey(context.Context, *SharedKeyRequest) (*SharedKeyResponse, error) + // + //MuSig2CombineKeys (experimental!) is a stateless helper RPC that can be used + //to calculate the combined MuSig2 public key from a list of all participating + //signers' public keys. This RPC is completely stateless and deterministic and + //does not create any signing session. It can be used to determine the Taproot + //public key that should be put in an on-chain output once all public keys are + //known. A signing session is only needed later when that output should be + //_spent_ again. + // + //NOTE: The MuSig2 BIP is not final yet and therefore this API must be + //considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming + //releases. Backward compatibility is not guaranteed! + MuSig2CombineKeys(context.Context, *MuSig2CombineKeysRequest) (*MuSig2CombineKeysResponse, error) + // + //MuSig2CreateSession (experimental!) creates a new MuSig2 signing session + //using the local key identified by the key locator. The complete list of all + //public keys of all signing parties must be provided, including the public + //key of the local signing key. If nonces of other parties are already known, + //they can be submitted as well to reduce the number of RPC calls necessary + //later on. + // + //NOTE: The MuSig2 BIP is not final yet and therefore this API must be + //considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming + //releases. Backward compatibility is not guaranteed! + MuSig2CreateSession(context.Context, *MuSig2SessionRequest) (*MuSig2SessionResponse, error) + // + //MuSig2RegisterNonces (experimental!) registers one or more public nonces of + //other signing participants for a session identified by its ID. This RPC can + //be called multiple times until all nonces are registered. + // + //NOTE: The MuSig2 BIP is not final yet and therefore this API must be + //considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming + //releases. Backward compatibility is not guaranteed! + MuSig2RegisterNonces(context.Context, *MuSig2RegisterNoncesRequest) (*MuSig2RegisterNoncesResponse, error) + // + //MuSig2Sign (experimental!) creates a partial signature using the local + //signing key that was specified when the session was created. This can only + //be called when all public nonces of all participants are known and have been + //registered with the session. If this node isn't responsible for combining + //all the partial signatures, then the cleanup flag should be set, indicating + //that the session can be removed from memory once the signature was produced. + // + //NOTE: The MuSig2 BIP is not final yet and therefore this API must be + //considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming + //releases. Backward compatibility is not guaranteed! + MuSig2Sign(context.Context, *MuSig2SignRequest) (*MuSig2SignResponse, error) + // + //MuSig2CombineSig (experimental!) combines the given partial signature(s) + //with the local one, if it already exists. Once a partial signature of all + //participants is registered, the final signature will be combined and + //returned. + // + //NOTE: The MuSig2 BIP is not final yet and therefore this API must be + //considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming + //releases. Backward compatibility is not guaranteed! + MuSig2CombineSig(context.Context, *MuSig2CombineSigRequest) (*MuSig2CombineSigResponse, error) + // + //MuSig2Cleanup (experimental!) allows a caller to clean up a session early in + //cases where it's obvious that the signing session won't succeed and the + //resources can be released. + // + //NOTE: The MuSig2 BIP is not final yet and therefore this API must be + //considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming + //releases. Backward compatibility is not guaranteed! + MuSig2Cleanup(context.Context, *MuSig2CleanupRequest) (*MuSig2CleanupResponse, error) mustEmbedUnimplementedSignerServer() } @@ -191,6 +375,24 @@ func (UnimplementedSignerServer) VerifyMessage(context.Context, *VerifyMessageRe func (UnimplementedSignerServer) DeriveSharedKey(context.Context, *SharedKeyRequest) (*SharedKeyResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method DeriveSharedKey not implemented") } +func (UnimplementedSignerServer) MuSig2CombineKeys(context.Context, *MuSig2CombineKeysRequest) (*MuSig2CombineKeysResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method MuSig2CombineKeys not implemented") +} +func (UnimplementedSignerServer) MuSig2CreateSession(context.Context, *MuSig2SessionRequest) (*MuSig2SessionResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method MuSig2CreateSession not implemented") +} +func (UnimplementedSignerServer) MuSig2RegisterNonces(context.Context, *MuSig2RegisterNoncesRequest) (*MuSig2RegisterNoncesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method MuSig2RegisterNonces not implemented") +} +func (UnimplementedSignerServer) MuSig2Sign(context.Context, *MuSig2SignRequest) (*MuSig2SignResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method MuSig2Sign not implemented") +} +func (UnimplementedSignerServer) MuSig2CombineSig(context.Context, *MuSig2CombineSigRequest) (*MuSig2CombineSigResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method MuSig2CombineSig not implemented") +} +func (UnimplementedSignerServer) MuSig2Cleanup(context.Context, *MuSig2CleanupRequest) (*MuSig2CleanupResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method MuSig2Cleanup not implemented") +} func (UnimplementedSignerServer) mustEmbedUnimplementedSignerServer() {} // UnsafeSignerServer may be embedded to opt out of forward compatibility for this service. @@ -294,6 +496,114 @@ func _Signer_DeriveSharedKey_Handler(srv interface{}, ctx context.Context, dec f return interceptor(ctx, in, info, handler) } +func _Signer_MuSig2CombineKeys_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MuSig2CombineKeysRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SignerServer).MuSig2CombineKeys(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/signrpc.Signer/MuSig2CombineKeys", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SignerServer).MuSig2CombineKeys(ctx, req.(*MuSig2CombineKeysRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Signer_MuSig2CreateSession_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MuSig2SessionRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SignerServer).MuSig2CreateSession(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/signrpc.Signer/MuSig2CreateSession", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SignerServer).MuSig2CreateSession(ctx, req.(*MuSig2SessionRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Signer_MuSig2RegisterNonces_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MuSig2RegisterNoncesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SignerServer).MuSig2RegisterNonces(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/signrpc.Signer/MuSig2RegisterNonces", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SignerServer).MuSig2RegisterNonces(ctx, req.(*MuSig2RegisterNoncesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Signer_MuSig2Sign_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MuSig2SignRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SignerServer).MuSig2Sign(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/signrpc.Signer/MuSig2Sign", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SignerServer).MuSig2Sign(ctx, req.(*MuSig2SignRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Signer_MuSig2CombineSig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MuSig2CombineSigRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SignerServer).MuSig2CombineSig(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/signrpc.Signer/MuSig2CombineSig", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SignerServer).MuSig2CombineSig(ctx, req.(*MuSig2CombineSigRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Signer_MuSig2Cleanup_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MuSig2CleanupRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SignerServer).MuSig2Cleanup(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/signrpc.Signer/MuSig2Cleanup", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SignerServer).MuSig2Cleanup(ctx, req.(*MuSig2CleanupRequest)) + } + return interceptor(ctx, in, info, handler) +} + // Signer_ServiceDesc is the grpc.ServiceDesc for Signer service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -321,6 +631,30 @@ var Signer_ServiceDesc = grpc.ServiceDesc{ MethodName: "DeriveSharedKey", Handler: _Signer_DeriveSharedKey_Handler, }, + { + MethodName: "MuSig2CombineKeys", + Handler: _Signer_MuSig2CombineKeys_Handler, + }, + { + MethodName: "MuSig2CreateSession", + Handler: _Signer_MuSig2CreateSession_Handler, + }, + { + MethodName: "MuSig2RegisterNonces", + Handler: _Signer_MuSig2RegisterNonces_Handler, + }, + { + MethodName: "MuSig2Sign", + Handler: _Signer_MuSig2Sign_Handler, + }, + { + MethodName: "MuSig2CombineSig", + Handler: _Signer_MuSig2CombineSig_Handler, + }, + { + MethodName: "MuSig2Cleanup", + Handler: _Signer_MuSig2Cleanup_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "signrpc/signer.proto", diff --git a/lnrpc/signrpc/signer_server.go b/lnrpc/signrpc/signer_server.go index 51c2e55ede..7dbdece22a 100644 --- a/lnrpc/signrpc/signer_server.go +++ b/lnrpc/signrpc/signer_server.go @@ -6,12 +6,15 @@ package signrpc import ( "bytes" "context" + "crypto/sha256" "fmt" "io/ioutil" "os" "path/filepath" - "github.com/btcsuite/btcd/btcec" + "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/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" @@ -69,6 +72,30 @@ var ( Entity: "signer", Action: "generate", }}, + "/signrpc.Signer/MuSig2CombineKeys": {{ + Entity: "signer", + Action: "read", + }}, + "/signrpc.Signer/MuSig2CreateSession": {{ + Entity: "signer", + Action: "generate", + }}, + "/signrpc.Signer/MuSig2RegisterNonces": {{ + Entity: "signer", + Action: "generate", + }}, + "/signrpc.Signer/MuSig2Sign": {{ + Entity: "signer", + Action: "generate", + }}, + "/signrpc.Signer/MuSig2CombineSig": {{ + Entity: "signer", + Action: "generate", + }}, + "/signrpc.Signer/MuSig2Cleanup": {{ + Entity: "signer", + Action: "generate", + }}, } // DefaultSignerMacFilename is the default name of the signer macaroon @@ -238,7 +265,8 @@ func (r *ServerShell) CreateSubServer(configRegistry lnrpc.SubServerConfigDispat // provides an invalid transaction, then we'll return with an error. // // NOTE: The resulting signature should be void of a sighash byte. -func (s *Server) SignOutputRaw(ctx context.Context, in *SignReq) (*SignResp, error) { +func (s *Server) SignOutputRaw(_ context.Context, in *SignReq) (*SignResp, + error) { switch { // If the client doesn't specify a transaction, then there's nothing to @@ -265,7 +293,33 @@ func (s *Server) SignOutputRaw(ctx context.Context, in *SignReq) (*SignResp, err return nil, fmt.Errorf("unable to decode tx: %v", err) } - sigHashCache := txscript.NewTxSigHashes(&txToSign) + var ( + sigHashCache = input.NewTxSigHashesV0Only(&txToSign) + prevOutputFetcher = txscript.NewMultiPrevOutFetcher(nil) + ) + + // If we're spending one or more SegWit v1 (Taproot) inputs, then we + // need the full UTXO information available. + if len(in.PrevOutputs) > 0 { + if len(in.PrevOutputs) != len(txToSign.TxIn) { + return nil, fmt.Errorf("provided previous outputs " + + "doesn't match number of transaction inputs") + } + + // Add all previous inputs to our sighash prev out fetcher so we + // can calculate the sighash correctly. + for idx, txIn := range txToSign.TxIn { + prevOutputFetcher.AddPrevOut( + txIn.PreviousOutPoint, &wire.TxOut{ + Value: in.PrevOutputs[idx].Value, + PkScript: in.PrevOutputs[idx].PkScript, + }, + ) + } + sigHashCache = txscript.NewTxSigHashes( + &txToSign, prevOutputFetcher, + ) + } log.Debugf("Generating sigs for %v inputs: ", len(in.SignDescs)) @@ -307,13 +361,56 @@ func (s *Server) SignOutputRaw(ctx context.Context, in *SignReq) (*SignResp, err } } - // If a witness script isn't passed, then we can't proceed, as - // in the p2wsh case, we can't properly generate the sighash. - if len(signDesc.WitnessScript) == 0 { - // TODO(roasbeef): if regualr p2wkh, then at times - // internally we allow script to go by - return nil, fmt.Errorf("witness script MUST be " + - "specified") + // Check what sign method was selected by the user so, we know + // exactly what we're expecting and can prevent some of the more + // obvious usage errors. + signMethod, err := UnmarshalSignMethod(signDesc.SignMethod) + if err != nil { + return nil, fmt.Errorf("unable to unmarshal sign "+ + "method: %v", err) + } + if !signMethod.PkScriptCompatible(signDesc.Output.PkScript) { + return nil, fmt.Errorf("selected sign method %v is "+ + "not compatible with given pk script %x", + signMethod, signDesc.Output.PkScript) + } + + // Perform input validation according to the sign method. Not + // all methods require the same fields to be provided. + switch signMethod { + case input.WitnessV0SignMethod: + // If a witness script isn't passed, then we can't + // proceed, as in the p2wsh case, we can't properly + // generate the sighash. A P2WKH doesn't need a witness + // script. But SignOutputRaw still needs to know the PK + // script that was used for the output. We'll send it in + // the WitnessScript field, the SignOutputRaw RPC will + // know what to do with it when creating the sighash. + if len(signDesc.WitnessScript) == 0 { + return nil, fmt.Errorf("witness script MUST " + + "be specified for segwit v0 sign " + + "method") + } + + case input.TaprootKeySpendBIP0086SignMethod: + if len(signDesc.TapTweak) > 0 { + return nil, fmt.Errorf("tap tweak must be " + + "empty for BIP0086 key spend") + } + + case input.TaprootKeySpendSignMethod: + if len(signDesc.TapTweak) != sha256.Size { + return nil, fmt.Errorf("tap tweak must be " + + "specified for key spend with root " + + "hash") + } + + case input.TaprootScriptSpendSignMethod: + if len(signDesc.WitnessScript) == 0 { + return nil, fmt.Errorf("witness script MUST " + + "be specified for taproot script " + + "spend method") + } } // If the users provided a double tweak, then we'll need to @@ -321,7 +418,7 @@ func (s *Server) SignOutputRaw(ctx context.Context, in *SignReq) (*SignResp, err var tweakPrivKey *btcec.PrivateKey if len(signDesc.DoubleTweak) != 0 { tweakPrivKey, _ = btcec.PrivKeyFromBytes( - btcec.S256(), signDesc.DoubleTweak, + signDesc.DoubleTweak, ) } @@ -335,15 +432,36 @@ func (s *Server) SignOutputRaw(ctx context.Context, in *SignReq) (*SignResp, err }, SingleTweak: signDesc.SingleTweak, DoubleTweak: tweakPrivKey, + TapTweak: signDesc.TapTweak, WitnessScript: signDesc.WitnessScript, + SignMethod: signMethod, Output: &wire.TxOut{ Value: signDesc.Output.Value, PkScript: signDesc.Output.PkScript, }, - HashType: txscript.SigHashType(signDesc.Sighash), - SigHashes: sigHashCache, - InputIndex: int(signDesc.InputIndex), + HashType: txscript.SigHashType(signDesc.Sighash), + SigHashes: sigHashCache, + InputIndex: int(signDesc.InputIndex), + PrevOutputFetcher: prevOutputFetcher, }) + + // Are we trying to sign for a Taproot output? Then we need all + // previous outputs being declared, otherwise we'd run into a + // panic later on. + if txscript.IsPayToTaproot(signDesc.Output.PkScript) { + for idx, txIn := range txToSign.TxIn { + utxo := prevOutputFetcher.FetchPrevOutput( + txIn.PreviousOutPoint, + ) + if utxo == nil { + return nil, fmt.Errorf("error signing "+ + "taproot output, transaction "+ + "input %d is missing its "+ + "previous outpoint information", + idx) + } + } + } } // Now that we've mapped all the proper sign descriptors, we can @@ -401,7 +519,7 @@ func (s *Server) ComputeInputScript(ctx context.Context, return nil, fmt.Errorf("unable to decode tx: %v", err) } - sigHashCache := txscript.NewTxSigHashes(&txToSign) + sigHashCache := input.NewTxSigHashesV0Only(&txToSign) signDescs := make([]*input.SignDescriptor, 0, len(in.SignDescs)) for _, signDesc := range in.SignDescs { @@ -511,7 +629,7 @@ func (s *Server) VerifyMessage(ctx context.Context, if in.Pubkey == nil { return nil, fmt.Errorf("a pubkey to verify MUST be passed in") } - pubkey, err := btcec.ParsePubKey(in.Pubkey, btcec.S256()) + pubkey, err := btcec.ParsePubKey(in.Pubkey) if err != nil { return nil, fmt.Errorf("unable to parse pubkey: %v", err) } @@ -623,18 +741,282 @@ func (s *Server) DeriveSharedKey(_ context.Context, in *SharedKeyRequest) ( return &SharedKeyResponse{SharedKey: sharedKeyHash[:]}, nil } +// MuSig2CombineKeys combines the given set of public keys into a single +// combined MuSig2 combined public key, applying the given tweaks. +func (s *Server) MuSig2CombineKeys(_ context.Context, + in *MuSig2CombineKeysRequest) (*MuSig2CombineKeysResponse, error) { + + // Parse the public keys of all signing participants. This must also + // include our own, local key. + allSignerPubKeys := make([]*btcec.PublicKey, len(in.AllSignerPubkeys)) + if len(in.AllSignerPubkeys) < 2 { + return nil, fmt.Errorf("need at least two signing public keys") + } + + for idx, pubKeyBytes := range in.AllSignerPubkeys { + pubKey, err := schnorr.ParsePubKey(pubKeyBytes) + if err != nil { + return nil, fmt.Errorf("error parsing signer public "+ + "key %d: %v", idx, err) + } + allSignerPubKeys[idx] = pubKey + } + + // Are there any tweaks to apply to the combined public key? + tweaks, err := UnmarshalTweaks(in.Tweaks, in.TaprootTweak) + if err != nil { + return nil, fmt.Errorf("error unmarshaling tweak options: %v", + err) + } + + // Combine the keys now without creating a session in memory. + combinedKey, err := input.MuSig2CombineKeys(allSignerPubKeys, tweaks) + if err != nil { + return nil, fmt.Errorf("error combining keys: %v", err) + } + + var internalKeyBytes []byte + if combinedKey.PreTweakedKey != nil { + internalKeyBytes = schnorr.SerializePubKey( + combinedKey.PreTweakedKey, + ) + } + + return &MuSig2CombineKeysResponse{ + CombinedKey: schnorr.SerializePubKey( + combinedKey.FinalKey, + ), + TaprootInternalKey: internalKeyBytes, + }, nil +} + +// MuSig2CreateSession creates a new MuSig2 signing session using the local +// key identified by the key locator. The complete list of all public keys of +// all signing parties must be provided, including the public key of the local +// signing key. If nonces of other parties are already known, they can be +// submitted as well to reduce the number of RPC calls necessary later on. +func (s *Server) MuSig2CreateSession(_ context.Context, + in *MuSig2SessionRequest) (*MuSig2SessionResponse, error) { + + // A key locator is always mandatory. + if in.KeyLoc == nil { + return nil, fmt.Errorf("missing key_loc") + } + keyLoc := keychain.KeyLocator{ + Family: keychain.KeyFamily(in.KeyLoc.KeyFamily), + Index: uint32(in.KeyLoc.KeyIndex), + } + + // Parse the public keys of all signing participants. This must also + // include our own, local key. + allSignerPubKeys := make([]*btcec.PublicKey, len(in.AllSignerPubkeys)) + if len(in.AllSignerPubkeys) < 2 { + return nil, fmt.Errorf("need at least two signing public keys") + } + + for idx, pubKeyBytes := range in.AllSignerPubkeys { + pubKey, err := schnorr.ParsePubKey(pubKeyBytes) + if err != nil { + return nil, fmt.Errorf("error parsing signer public "+ + "key %d: %v", idx, err) + } + allSignerPubKeys[idx] = pubKey + } + + // We participate a nonce ourselves, so we can't have more nonces than + // the total number of participants minus ourselves. + maxNonces := len(in.AllSignerPubkeys) - 1 + if len(in.OtherSignerPublicNonces) > maxNonces { + return nil, fmt.Errorf("too many other signer public nonces, "+ + "got %d but expected a maximum of %d", + len(in.OtherSignerPublicNonces), maxNonces) + } + + // Parse all other nonces we might already know. + otherSignerNonces, err := parseMuSig2PublicNonces( + in.OtherSignerPublicNonces, true, + ) + if err != nil { + return nil, fmt.Errorf("error parsing other nonces: %v", err) + } + + // Are there any tweaks to apply to the combined public key? + tweaks, err := UnmarshalTweaks(in.Tweaks, in.TaprootTweak) + if err != nil { + return nil, fmt.Errorf("error unmarshaling tweak options: %v", + err) + } + + // Register the session with the internal wallet/signer now. + session, err := s.cfg.Signer.MuSig2CreateSession( + keyLoc, allSignerPubKeys, tweaks, otherSignerNonces, + ) + if err != nil { + return nil, fmt.Errorf("error registering session: %v", err) + } + + var internalKeyBytes []byte + if session.TaprootTweak { + internalKeyBytes = schnorr.SerializePubKey( + session.TaprootInternalKey, + ) + } + + return &MuSig2SessionResponse{ + SessionId: session.SessionID[:], + CombinedKey: schnorr.SerializePubKey( + session.CombinedKey, + ), + TaprootInternalKey: internalKeyBytes, + LocalPublicNonces: session.PublicNonce[:], + HaveAllNonces: session.HaveAllNonces, + }, nil +} + +// MuSig2RegisterNonces registers one or more public nonces of other signing +// participants for a session identified by its ID. +func (s *Server) MuSig2RegisterNonces(_ context.Context, + in *MuSig2RegisterNoncesRequest) (*MuSig2RegisterNoncesResponse, error) { + + // Check session ID length. + sessionID, err := parseMuSig2SessionID(in.SessionId) + if err != nil { + return nil, fmt.Errorf("error parsing session ID: %v", err) + } + + // Parse the other signing participants' nonces. We can't validate the + // number of nonces here because we don't have access to the session in + // this context. But the signer will be able to make sure we don't + // register more nonces than there are signers (which would mean + // something is wrong in the signing setup). But we want at least a + // single nonce for each call. + otherSignerNonces, err := parseMuSig2PublicNonces( + in.OtherSignerPublicNonces, false, + ) + if err != nil { + return nil, fmt.Errorf("error parsing other nonces: %v", err) + } + + // Register the nonces now. + haveAllNonces, err := s.cfg.Signer.MuSig2RegisterNonces( + sessionID, otherSignerNonces, + ) + if err != nil { + return nil, fmt.Errorf("error registering nonces: %v", err) + } + + return &MuSig2RegisterNoncesResponse{HaveAllNonces: haveAllNonces}, nil +} + +// MuSig2Sign creates a partial signature using the local signing key that was +// specified when the session was created. This can only be called when all +// public nonces of all participants are known and have been registered with +// the session. If this node isn't responsible for combining all the partial +// signatures, then the cleanup flag should be set, indicating that the session +// can be removed from memory once the signature was produced. +func (s *Server) MuSig2Sign(_ context.Context, + in *MuSig2SignRequest) (*MuSig2SignResponse, error) { + + // Check session ID length. + sessionID, err := parseMuSig2SessionID(in.SessionId) + if err != nil { + return nil, fmt.Errorf("error parsing session ID: %v", err) + } + + // Schnorr signatures only work reliably if the message is 32 bytes. + msg := [sha256.Size]byte{} + if len(in.MessageDigest) != sha256.Size { + return nil, fmt.Errorf("invalid message digest size, got %d "+ + "but expected %d", len(in.MessageDigest), sha256.Size) + } + copy(msg[:], in.MessageDigest) + + // Create our own partial signature with the local signing key. + partialSig, err := s.cfg.Signer.MuSig2Sign(sessionID, msg, in.Cleanup) + if err != nil { + return nil, fmt.Errorf("error signing: %v", err) + } + + serializedPartialSig, err := input.SerializePartialSignature(partialSig) + if err != nil { + return nil, fmt.Errorf("error serializing sig: %v", err) + } + + return &MuSig2SignResponse{ + LocalPartialSignature: serializedPartialSig[:], + }, nil +} + +// MuSig2CombineSig combines the given partial signature(s) with the local one, +// if it already exists. Once a partial signature of all participants is +// registered, the final signature will be combined and returned. +func (s *Server) MuSig2CombineSig(_ context.Context, + in *MuSig2CombineSigRequest) (*MuSig2CombineSigResponse, error) { + + // Check session ID length. + sessionID, err := parseMuSig2SessionID(in.SessionId) + if err != nil { + return nil, fmt.Errorf("error parsing session ID: %v", err) + } + + // Parse all other signatures. This can be called multiple times, so we + // can't really sanity check how many we already have vs. how many the + // user supplied in this call. + partialSigs, err := parseMuSig2PartialSignatures( + in.OtherPartialSignatures, + ) + if err != nil { + return nil, fmt.Errorf("error parsing partial signatures: %v", + err) + } + + // Combine the signatures now, potentially getting the final, full + // signature if we've already got all partial ones. + finalSig, haveAllSigs, err := s.cfg.Signer.MuSig2CombineSig( + sessionID, partialSigs, + ) + if err != nil { + return nil, fmt.Errorf("error combining signatures: %v", err) + } + + resp := &MuSig2CombineSigResponse{ + HaveAllSignatures: haveAllSigs, + } + + if haveAllSigs { + resp.FinalSignature = finalSig.Serialize() + } + + return resp, err +} + +// MuSig2Cleanup removes a session from memory to free up resources. +func (s *Server) MuSig2Cleanup(_ context.Context, + in *MuSig2CleanupRequest) (*MuSig2CleanupResponse, error) { + + // Check session ID length. + sessionID, err := parseMuSig2SessionID(in.SessionId) + if err != nil { + return nil, fmt.Errorf("error parsing session ID: %v", err) + } + + err = s.cfg.Signer.MuSig2Cleanup(sessionID) + if err != nil { + return nil, fmt.Errorf("error cleaning up session: %v", err) + } + + return &MuSig2CleanupResponse{}, nil +} + // parseRawKeyBytes checks that the provided raw public key is valid and returns // the public key. A nil public key is returned if the length of the rawKeyBytes // is zero. func parseRawKeyBytes(rawKeyBytes []byte) (*btcec.PublicKey, error) { switch { - case len(rawKeyBytes) == 33: // If a proper raw key was provided, then we'll attempt // to decode and parse it. - return btcec.ParsePubKey( - rawKeyBytes, btcec.S256(), - ) + return btcec.ParsePubKey(rawKeyBytes) case len(rawKeyBytes) == 0: // No key is provided, return nil. @@ -648,3 +1030,132 @@ func parseRawKeyBytes(rawKeyBytes []byte) (*btcec.PublicKey, error) { "specified") } } + +// parseMuSig2SessionID parses a MuSig2 session ID from a raw byte slice. +func parseMuSig2SessionID(rawID []byte) (input.MuSig2SessionID, error) { + sessionID := input.MuSig2SessionID{} + + // The session ID must be exact in its length. + if len(rawID) != sha256.Size { + return sessionID, fmt.Errorf("invalid session ID size, got "+ + "%d but expected %d", len(rawID), sha256.Size) + } + copy(sessionID[:], rawID) + + return sessionID, nil +} + +// parseMuSig2PublicNonces sanity checks and parses the other signers' public +// nonces. +func parseMuSig2PublicNonces(pubNonces [][]byte, + emptyAllowed bool) ([][musig2.PubNonceSize]byte, error) { + + // For some calls the nonces are optional while for others it doesn't + // make any sense to not specify them (for example for the explicit + // nonce registration call there should be at least one nonce). + if !emptyAllowed && len(pubNonces) == 0 { + return nil, fmt.Errorf("at least one other signer public " + + "nonce is required") + } + + // Parse all other nonces. This can be called multiple times, so we + // can't really sanity check how many we already have vs. how many the + // user supplied in this call. + otherSignerNonces := make([][musig2.PubNonceSize]byte, len(pubNonces)) + for idx, otherNonceBytes := range pubNonces { + if len(otherNonceBytes) != musig2.PubNonceSize { + return nil, fmt.Errorf("invalid public nonce at "+ + "index %d: invalid length, got %d but "+ + "expected %d", idx, len(otherNonceBytes), + musig2.PubNonceSize) + } + copy(otherSignerNonces[idx][:], otherNonceBytes) + } + + return otherSignerNonces, nil +} + +// parseMuSig2PartialSignatures sanity checks and parses the other signers' +// partial signatures. +func parseMuSig2PartialSignatures( + partialSignatures [][]byte) ([]*musig2.PartialSignature, error) { + + // We always want at least one partial signature. + if len(partialSignatures) == 0 { + return nil, fmt.Errorf("at least one partial signature is " + + "required") + } + + parsedPartialSigs := make( + []*musig2.PartialSignature, len(partialSignatures), + ) + for idx, otherPartialSigBytes := range partialSignatures { + sig, err := input.DeserializePartialSignature( + otherPartialSigBytes, + ) + if err != nil { + return nil, fmt.Errorf("invalid partial signature at "+ + "index %d: %v", idx, err) + } + + parsedPartialSigs[idx] = sig + } + + return parsedPartialSigs, nil +} + +// UnmarshalTweaks parses the RPC tweak descriptions into their native +// counterpart. +func UnmarshalTweaks(rpcTweaks []*TweakDesc, + taprootTweak *TaprootTweakDesc) (*input.MuSig2Tweaks, error) { + + // Parse the generic tweaks first. + tweaks := &input.MuSig2Tweaks{ + GenericTweaks: make([]musig2.KeyTweakDesc, len(rpcTweaks)), + } + for idx, rpcTweak := range rpcTweaks { + if len(rpcTweak.Tweak) == 0 { + return nil, fmt.Errorf("tweak cannot be empty") + } + + copy(tweaks.GenericTweaks[idx].Tweak[:], rpcTweak.Tweak) + tweaks.GenericTweaks[idx].IsXOnly = rpcTweak.IsXOnly + } + + // Now parse the taproot specific tweak. + if taprootTweak != nil { + if taprootTweak.KeySpendOnly { + tweaks.TaprootBIP0086Tweak = true + } else { + if len(taprootTweak.ScriptRoot) == 0 { + return nil, fmt.Errorf("script root cannot " + + "be empty for non-keyspend") + } + + tweaks.TaprootTweak = taprootTweak.ScriptRoot + } + } + + return tweaks, nil +} + +// UnmarshalSignMethod parses the RPC sign method into the native counterpart. +func UnmarshalSignMethod(rpcSignMethod SignMethod) (input.SignMethod, error) { + switch rpcSignMethod { + case SignMethod_SIGN_METHOD_WITNESS_V0: + return input.WitnessV0SignMethod, nil + + case SignMethod_SIGN_METHOD_TAPROOT_KEY_SPEND_BIP0086: + return input.TaprootKeySpendBIP0086SignMethod, nil + + case SignMethod_SIGN_METHOD_TAPROOT_KEY_SPEND: + return input.TaprootKeySpendSignMethod, nil + + case SignMethod_SIGN_METHOD_TAPROOT_SCRIPT_SPEND: + return input.TaprootScriptSpendSignMethod, nil + + default: + return 0, fmt.Errorf("unknown RPC sign method <%d>", + rpcSignMethod) + } +} diff --git a/lnrpc/verrpc/log.go b/lnrpc/verrpc/log.go index fb57daa212..3553b56806 100644 --- a/lnrpc/verrpc/log.go +++ b/lnrpc/verrpc/log.go @@ -5,7 +5,7 @@ import ( "github.com/lightningnetwork/lnd/build" ) -// log is a logger that is initialized with no output filters. This +// log is a logger that is initialized with no output filters. This // means the package will not perform any logging by default until the caller // requests it. var log btclog.Logger diff --git a/lnrpc/walletrpc/config_active.go b/lnrpc/walletrpc/config_active.go index 806dbca489..13cac7f56e 100644 --- a/lnrpc/walletrpc/config_active.go +++ b/lnrpc/walletrpc/config_active.go @@ -15,7 +15,7 @@ import ( const ( // SubServerName is the name of the sub rpc server. We'll use this name // to register ourselves, and we also require that the main - // SubServerConfigDispatcher instance recognize as the name of our + // SubServerConfigDispatcher instance recognize as the name of our. SubServerName = "WalletKitRPC" ) diff --git a/lnrpc/walletrpc/driver.go b/lnrpc/walletrpc/driver.go index 79972f7db6..7446470f1f 100644 --- a/lnrpc/walletrpc/driver.go +++ b/lnrpc/walletrpc/driver.go @@ -37,7 +37,7 @@ func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( // Before we try to make the new WalletKit service instance, we'll // perform some sanity checks on the arguments to ensure that they're - // useable. + // usable. switch { case config.MacService != nil && config.NetworkDir == "": return nil, nil, fmt.Errorf("NetworkDir must be set to " + diff --git a/lnrpc/walletrpc/log.go b/lnrpc/walletrpc/log.go index 5c14879f6f..1916c549f2 100644 --- a/lnrpc/walletrpc/log.go +++ b/lnrpc/walletrpc/log.go @@ -5,7 +5,7 @@ import ( "github.com/lightningnetwork/lnd/build" ) -// log is a logger that is initialized with no output filters. This means the +// log is a logger that is initialized with no output filters. This means the // package will not perform any logging by default until the caller requests // it. var log btclog.Logger @@ -21,25 +21,9 @@ func DisableLog() { UseLogger(btclog.Disabled) } -// UseLogger uses a specified Logger to output package logging info. This +// UseLogger uses a specified Logger to output package logging info. This // should be used in preference to SetLogWriter if the caller is also using // btclog. func UseLogger(logger btclog.Logger) { log = logger } - -// logClosure is used to provide a closure over expensive logging operations so -// don't have to be performed when the logging level doesn't warrant it. -type logClosure func() string // nolint:unused - -// String invokes the underlying function and returns the result. -func (c logClosure) String() string { - return c() -} - -// newLogClosure returns a new closure over a function that returns a string -// which itself provides a Stringer interface so that it can be used with the -// logging system. -func newLogClosure(c func() string) logClosure { // nolint:unused - return logClosure(c) -} diff --git a/lnrpc/walletrpc/psbt.go b/lnrpc/walletrpc/psbt.go index 40deedd08d..2031c3e561 100644 --- a/lnrpc/walletrpc/psbt.go +++ b/lnrpc/walletrpc/psbt.go @@ -8,8 +8,8 @@ import ( "math" "time" + "github.com/btcsuite/btcd/btcutil/psbt" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil/psbt" "github.com/btcsuite/btcwallet/wtxmgr" "github.com/lightningnetwork/lnd/lnwallet" ) @@ -70,7 +70,6 @@ func lockInputs(w lnwallet.WalletController, packet *psbt.Packet) ( if err := w.ReleaseOutput( LndInternalLockID, op, ); err != nil { - log.Errorf("could not release the "+ "lock on %v: %v", op, err) } diff --git a/lnrpc/walletrpc/walletkit.pb.go b/lnrpc/walletrpc/walletkit.pb.go index 0f17d62e2f..9e3daf086a 100644 --- a/lnrpc/walletrpc/walletkit.pb.go +++ b/lnrpc/walletrpc/walletkit.pb.go @@ -29,6 +29,7 @@ const ( AddressType_WITNESS_PUBKEY_HASH AddressType = 1 AddressType_NESTED_WITNESS_PUBKEY_HASH AddressType = 2 AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH AddressType = 3 + AddressType_TAPROOT_PUBKEY AddressType = 4 ) // Enum value maps for AddressType. @@ -38,12 +39,14 @@ var ( 1: "WITNESS_PUBKEY_HASH", 2: "NESTED_WITNESS_PUBKEY_HASH", 3: "HYBRID_NESTED_WITNESS_PUBKEY_HASH", + 4: "TAPROOT_PUBKEY", } AddressType_value = map[string]int32{ "UNKNOWN": 0, "WITNESS_PUBKEY_HASH": 1, "NESTED_WITNESS_PUBKEY_HASH": 2, "HYBRID_NESTED_WITNESS_PUBKEY_HASH": 3, + "TAPROOT_PUBKEY": 4, } ) @@ -214,6 +217,12 @@ type ListUnspentRequest struct { MaxConfs int32 `protobuf:"varint,2,opt,name=max_confs,json=maxConfs,proto3" json:"max_confs,omitempty"` // An optional filter to only include outputs belonging to an account. Account string `protobuf:"bytes,3,opt,name=account,proto3" json:"account,omitempty"` + // + //When min_confs and max_confs are zero, setting false implicitly + //overrides max_confs to be MaxInt32, otherwise max_confs remains + //zero. An error is returned if the value is true and both min_confs + //and max_confs are non-zero. (default: false) + UnconfirmedOnly bool `protobuf:"varint,4,opt,name=unconfirmed_only,json=unconfirmedOnly,proto3" json:"unconfirmed_only,omitempty"` } func (x *ListUnspentRequest) Reset() { @@ -269,6 +278,13 @@ func (x *ListUnspentRequest) GetAccount() string { return "" } +func (x *ListUnspentRequest) GetUnconfirmedOnly() bool { + if x != nil { + return x.UnconfirmedOnly + } + return false +} + type ListUnspentResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -2542,6 +2558,104 @@ func (x *UtxoLease) GetExpiration() uint64 { return 0 } +type SignPsbtRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // + //The PSBT that should be signed. The PSBT must contain all required inputs, + //outputs, UTXO data and custom fields required to identify the signing key. + FundedPsbt []byte `protobuf:"bytes,1,opt,name=funded_psbt,json=fundedPsbt,proto3" json:"funded_psbt,omitempty"` +} + +func (x *SignPsbtRequest) Reset() { + *x = SignPsbtRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[35] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SignPsbtRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SignPsbtRequest) ProtoMessage() {} + +func (x *SignPsbtRequest) ProtoReflect() protoreflect.Message { + mi := &file_walletrpc_walletkit_proto_msgTypes[35] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SignPsbtRequest.ProtoReflect.Descriptor instead. +func (*SignPsbtRequest) Descriptor() ([]byte, []int) { + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{35} +} + +func (x *SignPsbtRequest) GetFundedPsbt() []byte { + if x != nil { + return x.FundedPsbt + } + return nil +} + +type SignPsbtResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The signed transaction in PSBT format. + SignedPsbt []byte `protobuf:"bytes,1,opt,name=signed_psbt,json=signedPsbt,proto3" json:"signed_psbt,omitempty"` +} + +func (x *SignPsbtResponse) Reset() { + *x = SignPsbtResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[36] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SignPsbtResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SignPsbtResponse) ProtoMessage() {} + +func (x *SignPsbtResponse) ProtoReflect() protoreflect.Message { + mi := &file_walletrpc_walletkit_proto_msgTypes[36] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SignPsbtResponse.ProtoReflect.Descriptor instead. +func (*SignPsbtResponse) Descriptor() ([]byte, []int) { + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{36} +} + +func (x *SignPsbtResponse) GetSignedPsbt() []byte { + if x != nil { + return x.SignedPsbt + } + return nil +} + type FinalizePsbtRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -2561,7 +2675,7 @@ type FinalizePsbtRequest struct { func (x *FinalizePsbtRequest) Reset() { *x = FinalizePsbtRequest{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[35] + mi := &file_walletrpc_walletkit_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2574,7 +2688,7 @@ func (x *FinalizePsbtRequest) String() string { func (*FinalizePsbtRequest) ProtoMessage() {} func (x *FinalizePsbtRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[35] + mi := &file_walletrpc_walletkit_proto_msgTypes[37] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2587,7 +2701,7 @@ func (x *FinalizePsbtRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FinalizePsbtRequest.ProtoReflect.Descriptor instead. func (*FinalizePsbtRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{35} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{37} } func (x *FinalizePsbtRequest) GetFundedPsbt() []byte { @@ -2618,7 +2732,7 @@ type FinalizePsbtResponse struct { func (x *FinalizePsbtResponse) Reset() { *x = FinalizePsbtResponse{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[36] + mi := &file_walletrpc_walletkit_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2631,7 +2745,7 @@ func (x *FinalizePsbtResponse) String() string { func (*FinalizePsbtResponse) ProtoMessage() {} func (x *FinalizePsbtResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[36] + mi := &file_walletrpc_walletkit_proto_msgTypes[38] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2644,7 +2758,7 @@ func (x *FinalizePsbtResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FinalizePsbtResponse.ProtoReflect.Descriptor instead. func (*FinalizePsbtResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{36} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{38} } func (x *FinalizePsbtResponse) GetSignedPsbt() []byte { @@ -2670,7 +2784,7 @@ type ListLeasesRequest struct { func (x *ListLeasesRequest) Reset() { *x = ListLeasesRequest{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[37] + mi := &file_walletrpc_walletkit_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2683,7 +2797,7 @@ func (x *ListLeasesRequest) String() string { func (*ListLeasesRequest) ProtoMessage() {} func (x *ListLeasesRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[37] + mi := &file_walletrpc_walletkit_proto_msgTypes[39] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2696,7 +2810,7 @@ func (x *ListLeasesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListLeasesRequest.ProtoReflect.Descriptor instead. func (*ListLeasesRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{37} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{39} } type ListLeasesResponse struct { @@ -2711,7 +2825,7 @@ type ListLeasesResponse struct { func (x *ListLeasesResponse) Reset() { *x = ListLeasesResponse{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[38] + mi := &file_walletrpc_walletkit_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2724,7 +2838,7 @@ func (x *ListLeasesResponse) String() string { func (*ListLeasesResponse) ProtoMessage() {} func (x *ListLeasesResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[38] + mi := &file_walletrpc_walletkit_proto_msgTypes[40] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2737,7 +2851,7 @@ func (x *ListLeasesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListLeasesResponse.ProtoReflect.Descriptor instead. func (*ListLeasesResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{38} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{40} } func (x *ListLeasesResponse) GetLockedUtxos() []*UtxoLease { @@ -2762,7 +2876,7 @@ type ListSweepsResponse_TransactionIDs struct { func (x *ListSweepsResponse_TransactionIDs) Reset() { *x = ListSweepsResponse_TransactionIDs{} if protoimpl.UnsafeEnabled { - mi := &file_walletrpc_walletkit_proto_msgTypes[39] + mi := &file_walletrpc_walletkit_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2775,7 +2889,7 @@ func (x *ListSweepsResponse_TransactionIDs) String() string { func (*ListSweepsResponse_TransactionIDs) ProtoMessage() {} func (x *ListSweepsResponse_TransactionIDs) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[39] + mi := &file_walletrpc_walletkit_proto_msgTypes[41] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2805,417 +2919,432 @@ var file_walletrpc_walletkit_proto_rawDesc = []byte{ 0x65, 0x74, 0x6b, 0x69, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x1a, 0x0f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x14, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, - 0x2f, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x68, 0x0a, - 0x12, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x73, - 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x61, 0x78, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x18, 0x0a, - 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x38, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x55, - 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, - 0x0a, 0x05, 0x75, 0x74, 0x78, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0b, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x74, 0x78, 0x6f, 0x52, 0x05, 0x75, 0x74, 0x78, 0x6f, - 0x73, 0x22, 0x80, 0x01, 0x0a, 0x12, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, - 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, - 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x2d, 0x0a, 0x12, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x11, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x63, - 0x6f, 0x6e, 0x64, 0x73, 0x22, 0x35, 0x0a, 0x13, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, - 0x70, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x65, - 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, - 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x53, 0x0a, 0x14, 0x52, - 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x02, 0x69, 0x64, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, - 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, - 0x22, 0x17, 0x0a, 0x15, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, - 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x51, 0x0a, 0x06, 0x4b, 0x65, 0x79, - 0x52, 0x65, 0x71, 0x12, 0x28, 0x0a, 0x10, 0x6b, 0x65, 0x79, 0x5f, 0x66, 0x69, 0x6e, 0x67, 0x65, - 0x72, 0x5f, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x6b, - 0x65, 0x79, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x50, 0x72, 0x69, 0x6e, 0x74, 0x12, 0x1d, 0x0a, - 0x0a, 0x6b, 0x65, 0x79, 0x5f, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x09, 0x6b, 0x65, 0x79, 0x46, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x22, 0x6b, 0x0a, 0x0b, - 0x41, 0x64, 0x64, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, - 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x63, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2a, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, - 0x65, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x22, 0x22, 0x0a, 0x0c, 0x41, 0x64, 0x64, - 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x64, 0x64, - 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, 0x64, 0x72, 0x22, 0xe2, 0x02, - 0x0a, 0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x39, 0x0a, - 0x0c, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x61, 0x64, 0x64, - 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x65, 0x78, 0x74, 0x65, - 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x50, - 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x34, 0x0a, 0x16, 0x6d, 0x61, 0x73, 0x74, - 0x65, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, - 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x14, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, - 0x4b, 0x65, 0x79, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x12, 0x27, - 0x0a, 0x0f, 0x64, 0x65, 0x72, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x61, 0x74, - 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x64, 0x65, 0x72, 0x69, 0x76, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x50, 0x61, 0x74, 0x68, 0x12, 0x2c, 0x0a, 0x12, 0x65, 0x78, 0x74, 0x65, 0x72, - 0x6e, 0x61, 0x6c, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x0d, 0x52, 0x10, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x4b, 0x65, 0x79, - 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, - 0x6c, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x0d, 0x52, 0x10, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x4b, 0x65, 0x79, 0x43, 0x6f, - 0x75, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x77, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x6f, 0x6e, 0x6c, - 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x77, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x6e, - 0x6c, 0x79, 0x22, 0x64, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x39, 0x0a, - 0x0c, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x61, 0x64, 0x64, - 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x22, 0x46, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, - 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x2e, 0x0a, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, - 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, - 0x22, 0xe4, 0x01, 0x0a, 0x14, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2e, 0x0a, - 0x13, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, - 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x65, 0x78, 0x74, 0x65, - 0x6e, 0x64, 0x65, 0x64, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x34, 0x0a, - 0x16, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x66, 0x69, 0x6e, 0x67, - 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x14, 0x6d, - 0x61, 0x73, 0x74, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, - 0x69, 0x6e, 0x74, 0x12, 0x39, 0x0a, 0x0c, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x74, - 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, - 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, - 0x65, 0x52, 0x0b, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x17, - 0x0a, 0x07, 0x64, 0x72, 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x06, 0x64, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x22, 0xaf, 0x01, 0x0a, 0x15, 0x49, 0x6d, 0x70, 0x6f, - 0x72, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x2c, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, - 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, - 0x33, 0x0a, 0x16, 0x64, 0x72, 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, - 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x13, 0x64, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, - 0x64, 0x64, 0x72, 0x73, 0x12, 0x33, 0x0a, 0x16, 0x64, 0x72, 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x5f, - 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x73, 0x18, 0x03, - 0x20, 0x03, 0x28, 0x09, 0x52, 0x13, 0x64, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x49, 0x6e, 0x74, 0x65, - 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x64, 0x64, 0x72, 0x73, 0x22, 0x72, 0x0a, 0x16, 0x49, 0x6d, 0x70, - 0x6f, 0x72, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, - 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, - 0x65, 0x79, 0x12, 0x39, 0x0a, 0x0c, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x79, - 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, - 0x52, 0x0b, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x22, 0x19, 0x0a, - 0x17, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3a, 0x0a, 0x0b, 0x54, 0x72, 0x61, 0x6e, - 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x15, 0x0a, 0x06, 0x74, 0x78, 0x5f, 0x68, 0x65, - 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x74, 0x78, 0x48, 0x65, 0x78, 0x12, 0x14, - 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, - 0x61, 0x62, 0x65, 0x6c, 0x22, 0x36, 0x0a, 0x0f, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x75, 0x62, 0x6c, 0x69, - 0x73, 0x68, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, - 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x22, 0xbc, 0x01, 0x0a, - 0x12, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x0a, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x6b, - 0x77, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x4b, - 0x77, 0x12, 0x28, 0x0a, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x78, 0x4f, - 0x75, 0x74, 0x52, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6c, - 0x61, 0x62, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, - 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x2b, - 0x0a, 0x11, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x5f, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, - 0x6d, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x73, 0x70, 0x65, 0x6e, 0x64, - 0x55, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x22, 0x2c, 0x0a, 0x13, 0x53, - 0x65, 0x6e, 0x64, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x72, 0x61, 0x77, 0x5f, 0x74, 0x78, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x05, 0x72, 0x61, 0x77, 0x54, 0x78, 0x22, 0x35, 0x0a, 0x12, 0x45, 0x73, 0x74, - 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x1f, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x66, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, - 0x22, 0x33, 0x0a, 0x13, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x0a, 0x73, 0x61, 0x74, 0x5f, 0x70, - 0x65, 0x72, 0x5f, 0x6b, 0x77, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x73, 0x61, 0x74, - 0x50, 0x65, 0x72, 0x4b, 0x77, 0x22, 0xfc, 0x03, 0x0a, 0x0c, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, - 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, - 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, - 0x69, 0x6e, 0x74, 0x12, 0x39, 0x0a, 0x0c, 0x77, 0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x5f, 0x74, - 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, - 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, - 0x65, 0x52, 0x0b, 0x77, 0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, - 0x0a, 0x0a, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x0d, 0x52, 0x09, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x61, 0x74, 0x12, 0x24, 0x0a, - 0x0c, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, - 0x79, 0x74, 0x65, 0x12, 0x2d, 0x0a, 0x12, 0x62, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, - 0x5f, 0x61, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, - 0x11, 0x62, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x41, 0x74, 0x74, 0x65, 0x6d, 0x70, - 0x74, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x62, 0x72, 0x6f, 0x61, 0x64, - 0x63, 0x61, 0x73, 0x74, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x0d, 0x52, 0x13, 0x6e, 0x65, 0x78, 0x74, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, - 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, - 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x13, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, - 0x43, 0x6f, 0x6e, 0x66, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x37, 0x0a, 0x16, 0x72, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, - 0x62, 0x79, 0x74, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x13, - 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, - 0x79, 0x74, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, - 0x62, 0x79, 0x74, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x61, 0x74, 0x50, - 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x12, 0x35, 0x0a, 0x17, 0x72, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x65, 0x64, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, - 0x74, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52, 0x14, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x65, 0x64, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x12, 0x14, - 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x66, - 0x6f, 0x72, 0x63, 0x65, 0x22, 0x16, 0x0a, 0x14, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, - 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x57, 0x0a, 0x15, - 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x0e, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, - 0x5f, 0x73, 0x77, 0x65, 0x65, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, - 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, - 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, 0x52, 0x0d, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, - 0x77, 0x65, 0x65, 0x70, 0x73, 0x22, 0xbe, 0x01, 0x0a, 0x0e, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x65, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, - 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, - 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, - 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, - 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x24, 0x0a, 0x0c, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, - 0x72, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, - 0x52, 0x0a, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, - 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x66, 0x6f, 0x72, - 0x63, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, - 0x79, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x61, 0x74, 0x50, 0x65, - 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x22, 0x11, 0x0a, 0x0f, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x65, - 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2d, 0x0a, 0x11, 0x4c, 0x69, 0x73, - 0x74, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, - 0x0a, 0x07, 0x76, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x07, 0x76, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x65, 0x22, 0x80, 0x02, 0x0a, 0x12, 0x4c, 0x69, 0x73, - 0x74, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x4c, 0x0a, 0x13, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, - 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x48, 0x00, 0x52, 0x12, 0x74, 0x72, 0x61, 0x6e, 0x73, - 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x57, 0x0a, - 0x0f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, - 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x49, 0x44, 0x73, 0x48, 0x00, 0x52, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x73, 0x1a, 0x39, 0x0a, 0x0e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x74, 0x72, 0x61, 0x6e, - 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x09, 0x52, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, - 0x73, 0x42, 0x08, 0x0a, 0x06, 0x73, 0x77, 0x65, 0x65, 0x70, 0x73, 0x22, 0x61, 0x0a, 0x17, 0x4c, - 0x61, 0x62, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, + 0x2f, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x93, 0x01, + 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, + 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, + 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x61, 0x78, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x18, + 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x75, 0x6e, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x0f, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x4f, + 0x6e, 0x6c, 0x79, 0x22, 0x38, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, + 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x05, 0x75, 0x74, + 0x78, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x55, 0x74, 0x78, 0x6f, 0x52, 0x05, 0x75, 0x74, 0x78, 0x6f, 0x73, 0x22, 0x80, 0x01, + 0x0a, 0x12, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x02, 0x69, 0x64, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, + 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, + 0x74, 0x12, 0x2d, 0x0a, 0x12, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, + 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x11, 0x65, + 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, + 0x22, 0x35, 0x0a, 0x13, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x65, 0x78, 0x70, + 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x53, 0x0a, 0x14, 0x52, 0x65, 0x6c, 0x65, 0x61, + 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, + 0x2b, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, + 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x22, 0x17, 0x0a, 0x15, + 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x51, 0x0a, 0x06, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x12, + 0x28, 0x0a, 0x10, 0x6b, 0x65, 0x79, 0x5f, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x5f, 0x70, 0x72, + 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x6b, 0x65, 0x79, 0x46, 0x69, + 0x6e, 0x67, 0x65, 0x72, 0x50, 0x72, 0x69, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x6b, 0x65, 0x79, + 0x5f, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x6b, + 0x65, 0x79, 0x46, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x22, 0x6b, 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x72, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x12, 0x2a, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, + 0x06, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x63, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x22, 0x22, 0x0a, 0x0c, 0x41, 0x64, 0x64, 0x72, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x64, 0x64, 0x72, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, 0x64, 0x72, 0x22, 0xe2, 0x02, 0x0a, 0x07, 0x41, 0x63, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x39, 0x0a, 0x0c, 0x61, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x54, 0x79, 0x70, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, + 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x11, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x75, 0x62, 0x6c, 0x69, + 0x63, 0x4b, 0x65, 0x79, 0x12, 0x34, 0x0a, 0x16, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6b, + 0x65, 0x79, 0x5f, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x14, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x46, + 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x64, 0x65, + 0x72, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0e, 0x64, 0x65, 0x72, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, + 0x61, 0x74, 0x68, 0x12, 0x2c, 0x0a, 0x12, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, + 0x6b, 0x65, 0x79, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x10, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x4b, 0x65, 0x79, 0x43, 0x6f, 0x75, 0x6e, + 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x6b, 0x65, + 0x79, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x69, + 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x4b, 0x65, 0x79, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, + 0x1d, 0x0a, 0x0a, 0x77, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x09, 0x77, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x6e, 0x6c, 0x79, 0x22, 0x64, + 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x39, 0x0a, 0x0c, 0x61, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x54, 0x79, 0x70, 0x65, 0x22, 0x46, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x08, + 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x52, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x22, 0xe4, 0x01, 0x0a, + 0x14, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x65, 0x78, 0x74, + 0x65, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, + 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x34, 0x0a, 0x16, 0x6d, 0x61, 0x73, + 0x74, 0x65, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, + 0x69, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x14, 0x6d, 0x61, 0x73, 0x74, 0x65, + 0x72, 0x4b, 0x65, 0x79, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x12, + 0x39, 0x0a, 0x0c, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x61, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x72, + 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x64, 0x72, 0x79, + 0x52, 0x75, 0x6e, 0x22, 0xaf, 0x01, 0x0a, 0x15, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x63, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2c, 0x0a, + 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x33, 0x0a, 0x16, 0x64, + 0x72, 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, + 0x61, 0x64, 0x64, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x13, 0x64, 0x72, 0x79, + 0x52, 0x75, 0x6e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x64, 0x64, 0x72, 0x73, + 0x12, 0x33, 0x0a, 0x16, 0x64, 0x72, 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x13, 0x64, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, + 0x41, 0x64, 0x64, 0x72, 0x73, 0x22, 0x72, 0x0a, 0x16, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50, + 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x39, + 0x0a, 0x0c, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, + 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x61, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x22, 0x19, 0x0a, 0x17, 0x49, 0x6d, 0x70, + 0x6f, 0x72, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3a, 0x0a, 0x0b, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x15, 0x0a, 0x06, 0x74, 0x78, 0x5f, 0x68, 0x65, 0x78, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x05, 0x74, 0x78, 0x48, 0x65, 0x78, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, - 0x12, 0x1c, 0x0a, 0x09, 0x6f, 0x76, 0x65, 0x72, 0x77, 0x72, 0x69, 0x74, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x09, 0x6f, 0x76, 0x65, 0x72, 0x77, 0x72, 0x69, 0x74, 0x65, 0x22, 0x1a, - 0x0a, 0x18, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x93, 0x02, 0x0a, 0x0f, 0x46, - 0x75, 0x6e, 0x64, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, - 0x0a, 0x04, 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x04, - 0x70, 0x73, 0x62, 0x74, 0x12, 0x29, 0x0a, 0x03, 0x72, 0x61, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x15, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x78, - 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x48, 0x00, 0x52, 0x03, 0x72, 0x61, 0x77, 0x12, - 0x21, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x0d, 0x48, 0x01, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x6f, - 0x6e, 0x66, 0x12, 0x24, 0x0a, 0x0d, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, - 0x79, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x48, 0x01, 0x52, 0x0b, 0x73, 0x61, 0x74, - 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, + 0x22, 0x36, 0x0a, 0x0f, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x5f, 0x65, + 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x75, 0x62, 0x6c, + 0x69, 0x73, 0x68, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x22, 0xbc, 0x01, 0x0a, 0x12, 0x53, 0x65, 0x6e, + 0x64, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x1c, 0x0a, 0x0a, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x6b, 0x77, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x08, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x4b, 0x77, 0x12, 0x28, 0x0a, + 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, + 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x78, 0x4f, 0x75, 0x74, 0x52, 0x07, + 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x1b, 0x0a, + 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x73, 0x70, + 0x65, 0x6e, 0x64, 0x5f, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x55, 0x6e, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x22, 0x2c, 0x0a, 0x13, 0x53, 0x65, 0x6e, 0x64, 0x4f, + 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x15, + 0x0a, 0x06, 0x72, 0x61, 0x77, 0x5f, 0x74, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, + 0x72, 0x61, 0x77, 0x54, 0x78, 0x22, 0x35, 0x0a, 0x12, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, + 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x63, + 0x6f, 0x6e, 0x66, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x22, 0x33, 0x0a, 0x13, + 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x0a, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x6b, + 0x77, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x4b, + 0x77, 0x22, 0xfc, 0x03, 0x0a, 0x0c, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, + 0x65, 0x70, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, + 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, + 0x39, 0x0a, 0x0c, 0x77, 0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x57, 0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x77, + 0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x6d, + 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, + 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x61, 0x74, 0x12, 0x24, 0x0a, 0x0c, 0x73, 0x61, 0x74, + 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x42, + 0x02, 0x18, 0x01, 0x52, 0x0a, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x12, + 0x2d, 0x0a, 0x12, 0x62, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x5f, 0x61, 0x74, 0x74, + 0x65, 0x6d, 0x70, 0x74, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11, 0x62, 0x72, 0x6f, + 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x41, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x73, 0x12, 0x32, + 0x0a, 0x15, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x62, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, + 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x13, 0x6e, + 0x65, 0x78, 0x74, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x48, 0x65, 0x69, 0x67, + 0x68, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x5f, + 0x63, 0x6f, 0x6e, 0x66, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x13, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x43, 0x6f, 0x6e, 0x66, + 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x37, 0x0a, 0x16, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x65, 0x64, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x74, 0x65, + 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x13, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x65, 0x64, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x12, + 0x22, 0x0a, 0x0d, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, + 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, + 0x79, 0x74, 0x65, 0x12, 0x35, 0x0a, 0x17, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, + 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x0b, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x14, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x53, + 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f, + 0x72, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, + 0x22, 0x16, 0x0a, 0x14, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x57, 0x0a, 0x15, 0x50, 0x65, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x3e, 0x0a, 0x0e, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x77, 0x65, + 0x65, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, + 0x65, 0x70, 0x52, 0x0d, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, + 0x73, 0x22, 0xbe, 0x01, 0x0a, 0x0e, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, + 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, + 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x6f, + 0x6e, 0x66, 0x12, 0x24, 0x0a, 0x0c, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x62, 0x79, + 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, 0x73, 0x61, + 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, + 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x12, 0x22, + 0x0a, 0x0d, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, + 0x74, 0x65, 0x22, 0x11, 0x0a, 0x0f, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2d, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x77, 0x65, + 0x65, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, + 0x72, 0x62, 0x6f, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x76, 0x65, 0x72, + 0x62, 0x6f, 0x73, 0x65, 0x22, 0x80, 0x02, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x77, 0x65, + 0x65, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x13, 0x74, + 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x65, 0x74, 0x61, 0x69, + 0x6c, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x74, 0x61, + 0x69, 0x6c, 0x73, 0x48, 0x00, 0x52, 0x12, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x57, 0x0a, 0x0f, 0x74, 0x72, 0x61, + 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x73, + 0x48, 0x00, 0x52, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, + 0x64, 0x73, 0x1a, 0x39, 0x0a, 0x0e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x49, 0x44, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x74, + 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x73, 0x42, 0x08, 0x0a, + 0x06, 0x73, 0x77, 0x65, 0x65, 0x70, 0x73, 0x22, 0x61, 0x0a, 0x17, 0x4c, 0x61, 0x62, 0x65, 0x6c, + 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x1c, 0x0a, 0x09, + 0x6f, 0x76, 0x65, 0x72, 0x77, 0x72, 0x69, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x09, 0x6f, 0x76, 0x65, 0x72, 0x77, 0x72, 0x69, 0x74, 0x65, 0x22, 0x1a, 0x0a, 0x18, 0x4c, 0x61, + 0x62, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x93, 0x02, 0x0a, 0x0f, 0x46, 0x75, 0x6e, 0x64, 0x50, + 0x73, 0x62, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x04, 0x70, 0x73, + 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x04, 0x70, 0x73, 0x62, 0x74, + 0x12, 0x29, 0x0a, 0x03, 0x72, 0x61, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, + 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x78, 0x54, 0x65, 0x6d, 0x70, + 0x6c, 0x61, 0x74, 0x65, 0x48, 0x00, 0x52, 0x03, 0x72, 0x61, 0x77, 0x12, 0x21, 0x0a, 0x0b, 0x74, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, + 0x48, 0x01, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x24, + 0x0a, 0x0d, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x04, 0x48, 0x01, 0x52, 0x0b, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, + 0x62, 0x79, 0x74, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1b, + 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x73, + 0x70, 0x65, 0x6e, 0x64, 0x5f, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x55, 0x6e, 0x63, + 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x42, 0x0a, 0x0a, 0x08, 0x74, 0x65, 0x6d, 0x70, + 0x6c, 0x61, 0x74, 0x65, 0x42, 0x06, 0x0a, 0x04, 0x66, 0x65, 0x65, 0x73, 0x22, 0x9c, 0x01, 0x0a, + 0x10, 0x46, 0x75, 0x6e, 0x64, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x73, 0x62, 0x74, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x73, + 0x62, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x5f, 0x6f, 0x75, 0x74, + 0x70, 0x75, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x11, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x49, 0x6e, 0x64, + 0x65, 0x78, 0x12, 0x37, 0x0a, 0x0c, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x75, 0x74, 0x78, + 0x6f, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x74, 0x78, 0x6f, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x52, 0x0b, + 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x55, 0x74, 0x78, 0x6f, 0x73, 0x22, 0xaf, 0x01, 0x0a, 0x0a, + 0x54, 0x78, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x27, 0x0a, 0x06, 0x69, 0x6e, + 0x70, 0x75, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x06, 0x69, 0x6e, 0x70, + 0x75, 0x74, 0x73, 0x12, 0x3c, 0x0a, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x18, 0x02, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, + 0x2e, 0x54, 0x78, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x2e, 0x4f, 0x75, 0x74, 0x70, + 0x75, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, + 0x73, 0x1a, 0x3a, 0x0a, 0x0c, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, + 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x68, 0x0a, + 0x09, 0x55, 0x74, 0x78, 0x6f, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75, + 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, + 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x65, 0x78, 0x70, + 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x32, 0x0a, 0x0f, 0x53, 0x69, 0x67, 0x6e, 0x50, + 0x73, 0x62, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x75, + 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x0a, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x73, 0x62, 0x74, 0x22, 0x33, 0x0a, 0x10, 0x53, + 0x69, 0x67, 0x6e, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x1f, 0x0a, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x50, 0x73, 0x62, 0x74, + 0x22, 0x50, 0x0a, 0x13, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x73, 0x62, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x75, 0x6e, 0x64, 0x65, + 0x64, 0x5f, 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x66, 0x75, + 0x6e, 0x64, 0x65, 0x64, 0x50, 0x73, 0x62, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, - 0x2b, 0x0a, 0x11, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x5f, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, - 0x72, 0x6d, 0x65, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x73, 0x70, 0x65, 0x6e, - 0x64, 0x55, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x42, 0x0a, 0x0a, 0x08, - 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x06, 0x0a, 0x04, 0x66, 0x65, 0x65, 0x73, - 0x22, 0x9c, 0x01, 0x0a, 0x10, 0x46, 0x75, 0x6e, 0x64, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x5f, - 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x66, 0x75, 0x6e, 0x64, - 0x65, 0x64, 0x50, 0x73, 0x62, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, - 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x11, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, - 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x37, 0x0a, 0x0c, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, - 0x5f, 0x75, 0x74, 0x78, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x77, - 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x74, 0x78, 0x6f, 0x4c, 0x65, 0x61, - 0x73, 0x65, 0x52, 0x0b, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x55, 0x74, 0x78, 0x6f, 0x73, 0x22, - 0xaf, 0x01, 0x0a, 0x0a, 0x54, 0x78, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x27, - 0x0a, 0x06, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, - 0x06, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x12, 0x3c, 0x0a, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, - 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x78, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x2e, - 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x6f, 0x75, - 0x74, 0x70, 0x75, 0x74, 0x73, 0x1a, 0x3a, 0x0a, 0x0c, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, - 0x01, 0x22, 0x68, 0x0a, 0x09, 0x55, 0x74, 0x78, 0x6f, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x12, 0x0e, - 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x2b, - 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, - 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x65, - 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, - 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x50, 0x0a, 0x13, 0x46, - 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x73, 0x62, - 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x50, - 0x73, 0x62, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x59, 0x0a, - 0x14, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, - 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x73, 0x69, 0x67, 0x6e, - 0x65, 0x64, 0x50, 0x73, 0x62, 0x74, 0x12, 0x20, 0x0a, 0x0c, 0x72, 0x61, 0x77, 0x5f, 0x66, 0x69, - 0x6e, 0x61, 0x6c, 0x5f, 0x74, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x72, 0x61, - 0x77, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x54, 0x78, 0x22, 0x13, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, - 0x4c, 0x65, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x4d, 0x0a, - 0x12, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x0c, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x75, 0x74, - 0x78, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x77, 0x61, 0x6c, 0x6c, - 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x74, 0x78, 0x6f, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x52, - 0x0b, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x55, 0x74, 0x78, 0x6f, 0x73, 0x2a, 0x7a, 0x0a, 0x0b, - 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, - 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x57, 0x49, 0x54, 0x4e, - 0x45, 0x53, 0x53, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, - 0x01, 0x12, 0x1e, 0x0a, 0x1a, 0x4e, 0x45, 0x53, 0x54, 0x45, 0x44, 0x5f, 0x57, 0x49, 0x54, 0x4e, - 0x45, 0x53, 0x53, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, - 0x02, 0x12, 0x25, 0x0a, 0x21, 0x48, 0x59, 0x42, 0x52, 0x49, 0x44, 0x5f, 0x4e, 0x45, 0x53, 0x54, - 0x45, 0x44, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, - 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x03, 0x2a, 0x99, 0x03, 0x0a, 0x0b, 0x57, 0x69, 0x74, - 0x6e, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x13, 0x0a, 0x0f, 0x55, 0x4e, 0x4b, 0x4e, - 0x4f, 0x57, 0x4e, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x10, 0x00, 0x12, 0x18, 0x0a, - 0x14, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x49, 0x4d, 0x45, - 0x5f, 0x4c, 0x4f, 0x43, 0x4b, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x43, 0x4f, 0x4d, 0x4d, 0x49, - 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x4e, 0x4f, 0x5f, 0x44, 0x45, 0x4c, 0x41, 0x59, 0x10, 0x02, - 0x12, 0x15, 0x0a, 0x11, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x52, - 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x03, 0x12, 0x17, 0x0a, 0x13, 0x48, 0x54, 0x4c, 0x43, 0x5f, - 0x4f, 0x46, 0x46, 0x45, 0x52, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x04, - 0x12, 0x18, 0x0a, 0x14, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, - 0x44, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x05, 0x12, 0x25, 0x0a, 0x21, 0x48, 0x54, - 0x4c, 0x43, 0x5f, 0x4f, 0x46, 0x46, 0x45, 0x52, 0x45, 0x44, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, - 0x55, 0x54, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x10, - 0x06, 0x12, 0x26, 0x0a, 0x22, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, - 0x45, 0x44, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, - 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x10, 0x07, 0x12, 0x1f, 0x0a, 0x1b, 0x48, 0x54, 0x4c, - 0x43, 0x5f, 0x4f, 0x46, 0x46, 0x45, 0x52, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, - 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x08, 0x12, 0x20, 0x0a, 0x1c, 0x48, 0x54, - 0x4c, 0x43, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x4d, 0x4f, - 0x54, 0x45, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x09, 0x12, 0x1c, 0x0a, 0x18, - 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, - 0x4c, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x0a, 0x12, 0x14, 0x0a, 0x10, 0x57, 0x49, - 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x0b, - 0x12, 0x1b, 0x0a, 0x17, 0x4e, 0x45, 0x53, 0x54, 0x45, 0x44, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, - 0x53, 0x53, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x0c, 0x12, 0x15, 0x0a, - 0x11, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x41, 0x4e, 0x43, 0x48, - 0x4f, 0x52, 0x10, 0x0d, 0x32, 0xb2, 0x0b, 0x0a, 0x09, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x4b, - 0x69, 0x74, 0x12, 0x4c, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, - 0x74, 0x12, 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, - 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x4c, 0x0a, 0x0b, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, - 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x65, 0x61, 0x73, - 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, - 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x65, 0x61, 0x73, 0x65, - 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, - 0x0a, 0x0d, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, - 0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x6c, 0x65, - 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x20, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x6c, - 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x73, - 0x12, 0x1c, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, - 0x74, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, - 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4c, - 0x65, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, - 0x0d, 0x44, 0x65, 0x72, 0x69, 0x76, 0x65, 0x4e, 0x65, 0x78, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x11, - 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x52, 0x65, - 0x71, 0x1a, 0x16, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x44, - 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x38, 0x0a, 0x09, 0x44, 0x65, 0x72, - 0x69, 0x76, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x13, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x4b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x1a, 0x16, 0x2e, 0x73, 0x69, + 0x6e, 0x74, 0x22, 0x59, 0x0a, 0x14, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x73, + 0x62, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x69, + 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x0a, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x50, 0x73, 0x62, 0x74, 0x12, 0x20, 0x0a, 0x0c, 0x72, + 0x61, 0x77, 0x5f, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x74, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x0a, 0x72, 0x61, 0x77, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x54, 0x78, 0x22, 0x13, 0x0a, + 0x11, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x22, 0x4d, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x0c, 0x6c, 0x6f, 0x63, 0x6b, + 0x65, 0x64, 0x5f, 0x75, 0x74, 0x78, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x74, 0x78, 0x6f, 0x4c, + 0x65, 0x61, 0x73, 0x65, 0x52, 0x0b, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x55, 0x74, 0x78, 0x6f, + 0x73, 0x2a, 0x8e, 0x01, 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, + 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x17, + 0x0a, 0x13, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, + 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x01, 0x12, 0x1e, 0x0a, 0x1a, 0x4e, 0x45, 0x53, 0x54, 0x45, + 0x44, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, + 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x02, 0x12, 0x25, 0x0a, 0x21, 0x48, 0x59, 0x42, 0x52, 0x49, + 0x44, 0x5f, 0x4e, 0x45, 0x53, 0x54, 0x45, 0x44, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, + 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x03, 0x12, 0x12, + 0x0a, 0x0e, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, + 0x10, 0x04, 0x2a, 0x99, 0x03, 0x0a, 0x0b, 0x57, 0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x54, 0x79, + 0x70, 0x65, 0x12, 0x13, 0x0a, 0x0f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x57, 0x49, + 0x54, 0x4e, 0x45, 0x53, 0x53, 0x10, 0x00, 0x12, 0x18, 0x0a, 0x14, 0x43, 0x4f, 0x4d, 0x4d, 0x49, + 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x5f, 0x4c, 0x4f, 0x43, 0x4b, 0x10, + 0x01, 0x12, 0x17, 0x0a, 0x13, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, + 0x4e, 0x4f, 0x5f, 0x44, 0x45, 0x4c, 0x41, 0x59, 0x10, 0x02, 0x12, 0x15, 0x0a, 0x11, 0x43, 0x4f, + 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, + 0x03, 0x12, 0x17, 0x0a, 0x13, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4f, 0x46, 0x46, 0x45, 0x52, 0x45, + 0x44, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x04, 0x12, 0x18, 0x0a, 0x14, 0x48, 0x54, + 0x4c, 0x43, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x56, 0x4f, + 0x4b, 0x45, 0x10, 0x05, 0x12, 0x25, 0x0a, 0x21, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4f, 0x46, 0x46, + 0x45, 0x52, 0x45, 0x44, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x5f, 0x53, 0x45, 0x43, + 0x4f, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x10, 0x06, 0x12, 0x26, 0x0a, 0x22, 0x48, + 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x5f, 0x53, 0x55, 0x43, + 0x43, 0x45, 0x53, 0x53, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, + 0x4c, 0x10, 0x07, 0x12, 0x1f, 0x0a, 0x1b, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4f, 0x46, 0x46, 0x45, + 0x52, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, + 0x55, 0x54, 0x10, 0x08, 0x12, 0x20, 0x0a, 0x1c, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x43, 0x43, + 0x45, 0x50, 0x54, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x53, 0x55, 0x43, + 0x43, 0x45, 0x53, 0x53, 0x10, 0x09, 0x12, 0x1c, 0x0a, 0x18, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x53, + 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, 0x52, 0x45, 0x56, 0x4f, + 0x4b, 0x45, 0x10, 0x0a, 0x12, 0x14, 0x0a, 0x10, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, + 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x0b, 0x12, 0x1b, 0x0a, 0x17, 0x4e, 0x45, + 0x53, 0x54, 0x45, 0x44, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x4b, 0x45, 0x59, + 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x0c, 0x12, 0x15, 0x0a, 0x11, 0x43, 0x4f, 0x4d, 0x4d, 0x49, + 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x10, 0x0d, 0x32, 0xf7, + 0x0b, 0x0a, 0x09, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x4b, 0x69, 0x74, 0x12, 0x4c, 0x0a, 0x0b, + 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x12, 0x1d, 0x2e, 0x77, 0x61, + 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, + 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x77, 0x61, 0x6c, + 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, + 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x0b, 0x4c, 0x65, + 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0d, 0x52, 0x65, 0x6c, 0x65, + 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, + 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x77, 0x61, 0x6c, + 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, + 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0a, + 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x73, 0x12, 0x1c, 0x2e, 0x77, 0x61, 0x6c, + 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, 0x61, 0x73, 0x65, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x0d, 0x44, 0x65, 0x72, 0x69, 0x76, + 0x65, 0x4e, 0x65, 0x78, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x11, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x16, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, - 0x74, 0x6f, 0x72, 0x12, 0x3b, 0x0a, 0x08, 0x4e, 0x65, 0x78, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, - 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x4f, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, - 0x12, 0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, - 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, - 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x52, 0x0a, 0x0d, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x12, 0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x49, - 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x58, 0x0a, 0x0f, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50, - 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x21, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, - 0x63, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x77, 0x61, - 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x75, - 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x48, 0x0a, 0x12, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, - 0x63, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x1a, 0x2e, - 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, - 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x0b, 0x53, 0x65, 0x6e, - 0x64, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x12, 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x0b, 0x45, 0x73, 0x74, 0x69, 0x6d, - 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x12, 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, - 0x70, 0x63, 0x2e, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, - 0x63, 0x2e, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0d, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, - 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x12, 0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, - 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x07, 0x42, 0x75, 0x6d, - 0x70, 0x46, 0x65, 0x65, 0x12, 0x19, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1a, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x75, 0x6d, 0x70, - 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0a, 0x4c, - 0x69, 0x73, 0x74, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x12, 0x1c, 0x2e, 0x77, 0x61, 0x6c, 0x6c, - 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5b, 0x0a, 0x10, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x54, - 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x22, 0x2e, 0x77, 0x61, 0x6c, - 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x6e, - 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, - 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, - 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x08, 0x46, 0x75, 0x6e, 0x64, 0x50, 0x73, 0x62, 0x74, 0x12, - 0x1a, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, - 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x77, 0x61, - 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x50, 0x73, 0x62, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x0c, 0x46, 0x69, 0x6e, 0x61, - 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x73, 0x62, 0x74, 0x12, 0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x73, 0x62, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x73, 0x62, - 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x31, 0x5a, 0x2f, 0x67, 0x69, 0x74, - 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, - 0x67, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2f, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x33, + 0x74, 0x6f, 0x72, 0x12, 0x38, 0x0a, 0x09, 0x44, 0x65, 0x72, 0x69, 0x76, 0x65, 0x4b, 0x65, 0x79, + 0x12, 0x13, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x4c, 0x6f, + 0x63, 0x61, 0x74, 0x6f, 0x72, 0x1a, 0x16, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x4b, 0x65, 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x3b, 0x0a, + 0x08, 0x4e, 0x65, 0x78, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x17, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, + 0x64, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x0c, 0x4c, 0x69, + 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x1e, 0x2e, 0x77, 0x61, 0x6c, + 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x77, 0x61, 0x6c, + 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0d, 0x49, + 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1f, 0x2e, 0x77, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x41, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, + 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, + 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x58, 0x0a, 0x0f, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, + 0x65, 0x79, 0x12, 0x21, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x49, + 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, + 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x48, 0x0a, 0x12, 0x50, 0x75, 0x62, + 0x6c, 0x69, 0x73, 0x68, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x61, 0x6e, + 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x1a, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x75, 0x74, 0x70, 0x75, + 0x74, 0x73, 0x12, 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, + 0x65, 0x6e, 0x64, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, + 0x6e, 0x64, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x4c, 0x0a, 0x0b, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, + 0x12, 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x73, 0x74, + 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x73, 0x74, 0x69, + 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x52, 0x0a, 0x0d, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, + 0x12, 0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x20, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, + 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x07, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x65, 0x65, 0x12, 0x19, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x75, 0x6d, 0x70, 0x46, + 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x77, 0x65, + 0x65, 0x70, 0x73, 0x12, 0x1c, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x5b, 0x0a, 0x10, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x22, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, + 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, + 0x08, 0x46, 0x75, 0x6e, 0x64, 0x50, 0x73, 0x62, 0x74, 0x12, 0x1a, 0x2e, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x43, 0x0a, 0x08, 0x53, 0x69, 0x67, 0x6e, 0x50, 0x73, 0x62, 0x74, 0x12, 0x1a, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x50, + 0x73, 0x62, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x77, 0x61, 0x6c, + 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x50, 0x73, 0x62, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x0c, 0x46, 0x69, 0x6e, 0x61, 0x6c, + 0x69, 0x7a, 0x65, 0x50, 0x73, 0x62, 0x74, 0x12, 0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x73, 0x62, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x73, 0x62, 0x74, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x31, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, + 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2f, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, } var ( @@ -3231,7 +3360,7 @@ func file_walletrpc_walletkit_proto_rawDescGZIP() []byte { } var file_walletrpc_walletkit_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_walletrpc_walletkit_proto_msgTypes = make([]protoimpl.MessageInfo, 41) +var file_walletrpc_walletkit_proto_msgTypes = make([]protoimpl.MessageInfo, 43) var file_walletrpc_walletkit_proto_goTypes = []interface{}{ (AddressType)(0), // 0: walletrpc.AddressType (WitnessType)(0), // 1: walletrpc.WitnessType @@ -3270,23 +3399,25 @@ var file_walletrpc_walletkit_proto_goTypes = []interface{}{ (*FundPsbtResponse)(nil), // 34: walletrpc.FundPsbtResponse (*TxTemplate)(nil), // 35: walletrpc.TxTemplate (*UtxoLease)(nil), // 36: walletrpc.UtxoLease - (*FinalizePsbtRequest)(nil), // 37: walletrpc.FinalizePsbtRequest - (*FinalizePsbtResponse)(nil), // 38: walletrpc.FinalizePsbtResponse - (*ListLeasesRequest)(nil), // 39: walletrpc.ListLeasesRequest - (*ListLeasesResponse)(nil), // 40: walletrpc.ListLeasesResponse - (*ListSweepsResponse_TransactionIDs)(nil), // 41: walletrpc.ListSweepsResponse.TransactionIDs - nil, // 42: walletrpc.TxTemplate.OutputsEntry - (*lnrpc.Utxo)(nil), // 43: lnrpc.Utxo - (*lnrpc.OutPoint)(nil), // 44: lnrpc.OutPoint - (*signrpc.TxOut)(nil), // 45: signrpc.TxOut - (*lnrpc.TransactionDetails)(nil), // 46: lnrpc.TransactionDetails - (*signrpc.KeyLocator)(nil), // 47: signrpc.KeyLocator - (*signrpc.KeyDescriptor)(nil), // 48: signrpc.KeyDescriptor + (*SignPsbtRequest)(nil), // 37: walletrpc.SignPsbtRequest + (*SignPsbtResponse)(nil), // 38: walletrpc.SignPsbtResponse + (*FinalizePsbtRequest)(nil), // 39: walletrpc.FinalizePsbtRequest + (*FinalizePsbtResponse)(nil), // 40: walletrpc.FinalizePsbtResponse + (*ListLeasesRequest)(nil), // 41: walletrpc.ListLeasesRequest + (*ListLeasesResponse)(nil), // 42: walletrpc.ListLeasesResponse + (*ListSweepsResponse_TransactionIDs)(nil), // 43: walletrpc.ListSweepsResponse.TransactionIDs + nil, // 44: walletrpc.TxTemplate.OutputsEntry + (*lnrpc.Utxo)(nil), // 45: lnrpc.Utxo + (*lnrpc.OutPoint)(nil), // 46: lnrpc.OutPoint + (*signrpc.TxOut)(nil), // 47: signrpc.TxOut + (*lnrpc.TransactionDetails)(nil), // 48: lnrpc.TransactionDetails + (*signrpc.KeyLocator)(nil), // 49: signrpc.KeyLocator + (*signrpc.KeyDescriptor)(nil), // 50: signrpc.KeyDescriptor } var file_walletrpc_walletkit_proto_depIdxs = []int32{ - 43, // 0: walletrpc.ListUnspentResponse.utxos:type_name -> lnrpc.Utxo - 44, // 1: walletrpc.LeaseOutputRequest.outpoint:type_name -> lnrpc.OutPoint - 44, // 2: walletrpc.ReleaseOutputRequest.outpoint:type_name -> lnrpc.OutPoint + 45, // 0: walletrpc.ListUnspentResponse.utxos:type_name -> lnrpc.Utxo + 46, // 1: walletrpc.LeaseOutputRequest.outpoint:type_name -> lnrpc.OutPoint + 46, // 2: walletrpc.ReleaseOutputRequest.outpoint:type_name -> lnrpc.OutPoint 0, // 3: walletrpc.AddrRequest.type:type_name -> walletrpc.AddressType 0, // 4: walletrpc.Account.address_type:type_name -> walletrpc.AddressType 0, // 5: walletrpc.ListAccountsRequest.address_type:type_name -> walletrpc.AddressType @@ -3294,25 +3425,25 @@ var file_walletrpc_walletkit_proto_depIdxs = []int32{ 0, // 7: walletrpc.ImportAccountRequest.address_type:type_name -> walletrpc.AddressType 11, // 8: walletrpc.ImportAccountResponse.account:type_name -> walletrpc.Account 0, // 9: walletrpc.ImportPublicKeyRequest.address_type:type_name -> walletrpc.AddressType - 45, // 10: walletrpc.SendOutputsRequest.outputs:type_name -> signrpc.TxOut - 44, // 11: walletrpc.PendingSweep.outpoint:type_name -> lnrpc.OutPoint + 47, // 10: walletrpc.SendOutputsRequest.outputs:type_name -> signrpc.TxOut + 46, // 11: walletrpc.PendingSweep.outpoint:type_name -> lnrpc.OutPoint 1, // 12: walletrpc.PendingSweep.witness_type:type_name -> walletrpc.WitnessType 24, // 13: walletrpc.PendingSweepsResponse.pending_sweeps:type_name -> walletrpc.PendingSweep - 44, // 14: walletrpc.BumpFeeRequest.outpoint:type_name -> lnrpc.OutPoint - 46, // 15: walletrpc.ListSweepsResponse.transaction_details:type_name -> lnrpc.TransactionDetails - 41, // 16: walletrpc.ListSweepsResponse.transaction_ids:type_name -> walletrpc.ListSweepsResponse.TransactionIDs + 46, // 14: walletrpc.BumpFeeRequest.outpoint:type_name -> lnrpc.OutPoint + 48, // 15: walletrpc.ListSweepsResponse.transaction_details:type_name -> lnrpc.TransactionDetails + 43, // 16: walletrpc.ListSweepsResponse.transaction_ids:type_name -> walletrpc.ListSweepsResponse.TransactionIDs 35, // 17: walletrpc.FundPsbtRequest.raw:type_name -> walletrpc.TxTemplate 36, // 18: walletrpc.FundPsbtResponse.locked_utxos:type_name -> walletrpc.UtxoLease - 44, // 19: walletrpc.TxTemplate.inputs:type_name -> lnrpc.OutPoint - 42, // 20: walletrpc.TxTemplate.outputs:type_name -> walletrpc.TxTemplate.OutputsEntry - 44, // 21: walletrpc.UtxoLease.outpoint:type_name -> lnrpc.OutPoint + 46, // 19: walletrpc.TxTemplate.inputs:type_name -> lnrpc.OutPoint + 44, // 20: walletrpc.TxTemplate.outputs:type_name -> walletrpc.TxTemplate.OutputsEntry + 46, // 21: walletrpc.UtxoLease.outpoint:type_name -> lnrpc.OutPoint 36, // 22: walletrpc.ListLeasesResponse.locked_utxos:type_name -> walletrpc.UtxoLease 2, // 23: walletrpc.WalletKit.ListUnspent:input_type -> walletrpc.ListUnspentRequest 4, // 24: walletrpc.WalletKit.LeaseOutput:input_type -> walletrpc.LeaseOutputRequest 6, // 25: walletrpc.WalletKit.ReleaseOutput:input_type -> walletrpc.ReleaseOutputRequest - 39, // 26: walletrpc.WalletKit.ListLeases:input_type -> walletrpc.ListLeasesRequest + 41, // 26: walletrpc.WalletKit.ListLeases:input_type -> walletrpc.ListLeasesRequest 8, // 27: walletrpc.WalletKit.DeriveNextKey:input_type -> walletrpc.KeyReq - 47, // 28: walletrpc.WalletKit.DeriveKey:input_type -> signrpc.KeyLocator + 49, // 28: walletrpc.WalletKit.DeriveKey:input_type -> signrpc.KeyLocator 9, // 29: walletrpc.WalletKit.NextAddr:input_type -> walletrpc.AddrRequest 12, // 30: walletrpc.WalletKit.ListAccounts:input_type -> walletrpc.ListAccountsRequest 14, // 31: walletrpc.WalletKit.ImportAccount:input_type -> walletrpc.ImportAccountRequest @@ -3325,28 +3456,30 @@ var file_walletrpc_walletkit_proto_depIdxs = []int32{ 29, // 38: walletrpc.WalletKit.ListSweeps:input_type -> walletrpc.ListSweepsRequest 31, // 39: walletrpc.WalletKit.LabelTransaction:input_type -> walletrpc.LabelTransactionRequest 33, // 40: walletrpc.WalletKit.FundPsbt:input_type -> walletrpc.FundPsbtRequest - 37, // 41: walletrpc.WalletKit.FinalizePsbt:input_type -> walletrpc.FinalizePsbtRequest - 3, // 42: walletrpc.WalletKit.ListUnspent:output_type -> walletrpc.ListUnspentResponse - 5, // 43: walletrpc.WalletKit.LeaseOutput:output_type -> walletrpc.LeaseOutputResponse - 7, // 44: walletrpc.WalletKit.ReleaseOutput:output_type -> walletrpc.ReleaseOutputResponse - 40, // 45: walletrpc.WalletKit.ListLeases:output_type -> walletrpc.ListLeasesResponse - 48, // 46: walletrpc.WalletKit.DeriveNextKey:output_type -> signrpc.KeyDescriptor - 48, // 47: walletrpc.WalletKit.DeriveKey:output_type -> signrpc.KeyDescriptor - 10, // 48: walletrpc.WalletKit.NextAddr:output_type -> walletrpc.AddrResponse - 13, // 49: walletrpc.WalletKit.ListAccounts:output_type -> walletrpc.ListAccountsResponse - 15, // 50: walletrpc.WalletKit.ImportAccount:output_type -> walletrpc.ImportAccountResponse - 17, // 51: walletrpc.WalletKit.ImportPublicKey:output_type -> walletrpc.ImportPublicKeyResponse - 19, // 52: walletrpc.WalletKit.PublishTransaction:output_type -> walletrpc.PublishResponse - 21, // 53: walletrpc.WalletKit.SendOutputs:output_type -> walletrpc.SendOutputsResponse - 23, // 54: walletrpc.WalletKit.EstimateFee:output_type -> walletrpc.EstimateFeeResponse - 26, // 55: walletrpc.WalletKit.PendingSweeps:output_type -> walletrpc.PendingSweepsResponse - 28, // 56: walletrpc.WalletKit.BumpFee:output_type -> walletrpc.BumpFeeResponse - 30, // 57: walletrpc.WalletKit.ListSweeps:output_type -> walletrpc.ListSweepsResponse - 32, // 58: walletrpc.WalletKit.LabelTransaction:output_type -> walletrpc.LabelTransactionResponse - 34, // 59: walletrpc.WalletKit.FundPsbt:output_type -> walletrpc.FundPsbtResponse - 38, // 60: walletrpc.WalletKit.FinalizePsbt:output_type -> walletrpc.FinalizePsbtResponse - 42, // [42:61] is the sub-list for method output_type - 23, // [23:42] is the sub-list for method input_type + 37, // 41: walletrpc.WalletKit.SignPsbt:input_type -> walletrpc.SignPsbtRequest + 39, // 42: walletrpc.WalletKit.FinalizePsbt:input_type -> walletrpc.FinalizePsbtRequest + 3, // 43: walletrpc.WalletKit.ListUnspent:output_type -> walletrpc.ListUnspentResponse + 5, // 44: walletrpc.WalletKit.LeaseOutput:output_type -> walletrpc.LeaseOutputResponse + 7, // 45: walletrpc.WalletKit.ReleaseOutput:output_type -> walletrpc.ReleaseOutputResponse + 42, // 46: walletrpc.WalletKit.ListLeases:output_type -> walletrpc.ListLeasesResponse + 50, // 47: walletrpc.WalletKit.DeriveNextKey:output_type -> signrpc.KeyDescriptor + 50, // 48: walletrpc.WalletKit.DeriveKey:output_type -> signrpc.KeyDescriptor + 10, // 49: walletrpc.WalletKit.NextAddr:output_type -> walletrpc.AddrResponse + 13, // 50: walletrpc.WalletKit.ListAccounts:output_type -> walletrpc.ListAccountsResponse + 15, // 51: walletrpc.WalletKit.ImportAccount:output_type -> walletrpc.ImportAccountResponse + 17, // 52: walletrpc.WalletKit.ImportPublicKey:output_type -> walletrpc.ImportPublicKeyResponse + 19, // 53: walletrpc.WalletKit.PublishTransaction:output_type -> walletrpc.PublishResponse + 21, // 54: walletrpc.WalletKit.SendOutputs:output_type -> walletrpc.SendOutputsResponse + 23, // 55: walletrpc.WalletKit.EstimateFee:output_type -> walletrpc.EstimateFeeResponse + 26, // 56: walletrpc.WalletKit.PendingSweeps:output_type -> walletrpc.PendingSweepsResponse + 28, // 57: walletrpc.WalletKit.BumpFee:output_type -> walletrpc.BumpFeeResponse + 30, // 58: walletrpc.WalletKit.ListSweeps:output_type -> walletrpc.ListSweepsResponse + 32, // 59: walletrpc.WalletKit.LabelTransaction:output_type -> walletrpc.LabelTransactionResponse + 34, // 60: walletrpc.WalletKit.FundPsbt:output_type -> walletrpc.FundPsbtResponse + 38, // 61: walletrpc.WalletKit.SignPsbt:output_type -> walletrpc.SignPsbtResponse + 40, // 62: walletrpc.WalletKit.FinalizePsbt:output_type -> walletrpc.FinalizePsbtResponse + 43, // [43:63] is the sub-list for method output_type + 23, // [23:43] is the sub-list for method input_type 23, // [23:23] is the sub-list for extension type_name 23, // [23:23] is the sub-list for extension extendee 0, // [0:23] is the sub-list for field type_name @@ -3779,7 +3912,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[35].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FinalizePsbtRequest); i { + switch v := v.(*SignPsbtRequest); i { case 0: return &v.state case 1: @@ -3791,7 +3924,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[36].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FinalizePsbtResponse); i { + switch v := v.(*SignPsbtResponse); i { case 0: return &v.state case 1: @@ -3803,7 +3936,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[37].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListLeasesRequest); i { + switch v := v.(*FinalizePsbtRequest); i { case 0: return &v.state case 1: @@ -3815,7 +3948,7 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[38].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListLeasesResponse); i { + switch v := v.(*FinalizePsbtResponse); i { case 0: return &v.state case 1: @@ -3827,6 +3960,30 @@ func file_walletrpc_walletkit_proto_init() { } } file_walletrpc_walletkit_proto_msgTypes[39].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListLeasesRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[40].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListLeasesResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[41].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ListSweepsResponse_TransactionIDs); i { case 0: return &v.state @@ -3855,7 +4012,7 @@ func file_walletrpc_walletkit_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_walletrpc_walletkit_proto_rawDesc, NumEnums: 2, - NumMessages: 41, + NumMessages: 43, NumExtensions: 0, NumServices: 1, }, diff --git a/lnrpc/walletrpc/walletkit.pb.gw.go b/lnrpc/walletrpc/walletkit.pb.gw.go index f8da00bb8a..08566ef21e 100644 --- a/lnrpc/walletrpc/walletkit.pb.gw.go +++ b/lnrpc/walletrpc/walletkit.pb.gw.go @@ -634,6 +634,40 @@ func local_request_WalletKit_FundPsbt_0(ctx context.Context, marshaler runtime.M } +func request_WalletKit_SignPsbt_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq SignPsbtRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.SignPsbt(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_WalletKit_SignPsbt_0(ctx context.Context, marshaler runtime.Marshaler, server WalletKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq SignPsbtRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.SignPsbt(ctx, &protoReq) + return msg, metadata, err + +} + func request_WalletKit_FinalizePsbt_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq FinalizePsbtRequest var metadata runtime.ServerMetadata @@ -1088,6 +1122,29 @@ func RegisterWalletKitHandlerServer(ctx context.Context, mux *runtime.ServeMux, }) + mux.Handle("POST", pattern_WalletKit_SignPsbt_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/walletrpc.WalletKit/SignPsbt", runtime.WithHTTPPathPattern("/v2/wallet/psbt/sign")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_WalletKit_SignPsbt_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_WalletKit_SignPsbt_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("POST", pattern_WalletKit_FinalizePsbt_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -1512,6 +1569,26 @@ func RegisterWalletKitHandlerClient(ctx context.Context, mux *runtime.ServeMux, }) + mux.Handle("POST", pattern_WalletKit_SignPsbt_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req, "/walletrpc.WalletKit/SignPsbt", runtime.WithHTTPPathPattern("/v2/wallet/psbt/sign")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_WalletKit_SignPsbt_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_WalletKit_SignPsbt_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("POST", pattern_WalletKit_FinalizePsbt_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -1572,6 +1649,8 @@ var ( pattern_WalletKit_FundPsbt_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "wallet", "psbt", "fund"}, "")) + pattern_WalletKit_SignPsbt_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "wallet", "psbt", "sign"}, "")) + pattern_WalletKit_FinalizePsbt_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "wallet", "psbt", "finalize"}, "")) ) @@ -1612,5 +1691,7 @@ var ( forward_WalletKit_FundPsbt_0 = runtime.ForwardResponseMessage + forward_WalletKit_SignPsbt_0 = runtime.ForwardResponseMessage + forward_WalletKit_FinalizePsbt_0 = runtime.ForwardResponseMessage ) diff --git a/lnrpc/walletrpc/walletkit.pb.json.go b/lnrpc/walletrpc/walletkit.pb.json.go index 9eb5c69e4a..8d8b73111d 100644 --- a/lnrpc/walletrpc/walletkit.pb.json.go +++ b/lnrpc/walletrpc/walletkit.pb.json.go @@ -474,6 +474,31 @@ func RegisterWalletKitJSONCallbacks(registry map[string]func(ctx context.Context callback(string(respBytes), nil) } + registry["walletrpc.WalletKit.SignPsbt"] = func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { + + req := &SignPsbtRequest{} + err := marshaler.Unmarshal([]byte(reqJSON), req) + if err != nil { + callback("", err) + return + } + + client := NewWalletKitClient(conn) + resp, err := client.SignPsbt(ctx, req) + if err != nil { + callback("", err) + return + } + + respBytes, err := marshaler.Marshal(resp) + if err != nil { + callback("", err) + return + } + callback(string(respBytes), nil) + } + registry["walletrpc.WalletKit.FinalizePsbt"] = func(ctx context.Context, conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { diff --git a/lnrpc/walletrpc/walletkit.proto b/lnrpc/walletrpc/walletkit.proto index 2d552915e9..d9e7c10650 100644 --- a/lnrpc/walletrpc/walletkit.proto +++ b/lnrpc/walletrpc/walletkit.proto @@ -12,7 +12,9 @@ option go_package = "github.com/lightningnetwork/lnd/lnrpc/walletrpc"; service WalletKit { /* ListUnspent returns a list of all utxos spendable by the wallet with a - number of confirmations between the specified minimum and maximum. + number of confirmations between the specified minimum and maximum. By + default, all utxos are listed. To list only the unconfirmed utxos, set + the unconfirmed_only to true. */ rpc ListUnspent (ListUnspentRequest) returns (ListUnspentResponse); @@ -200,6 +202,21 @@ service WalletKit { */ rpc FundPsbt (FundPsbtRequest) returns (FundPsbtResponse); + /* + SignPsbt expects a partial transaction with all inputs and outputs fully + declared and tries to sign all unsigned inputs that have all required fields + (UTXO information, BIP32 derivation information, witness or sig scripts) + set. + If no error is returned, the PSBT is ready to be given to the next signer or + to be finalized if lnd was the last signer. + + NOTE: This RPC only signs inputs (and only those it can sign), it does not + perform any other tasks (such as coin selection, UTXO locking or + input/output/fee value validation, PSBT finalization). Any input that is + incomplete will be skipped. + */ + rpc SignPsbt (SignPsbtRequest) returns (SignPsbtResponse); + /* FinalizePsbt expects a partial transaction with all inputs and outputs fully declared and tries to sign all inputs that belong to the wallet. Lnd must be @@ -225,6 +242,14 @@ message ListUnspentRequest { // An optional filter to only include outputs belonging to an account. string account = 3; + + /* + When min_confs and max_confs are zero, setting false implicitly + overrides max_confs to be MaxInt32, otherwise max_confs remains + zero. An error is returned if the value is true and both min_confs + and max_confs are non-zero. (default: false) + */ + bool unconfirmed_only = 4; } message ListUnspentResponse { @@ -309,6 +334,7 @@ enum AddressType { WITNESS_PUBKEY_HASH = 1; NESTED_WITNESS_PUBKEY_HASH = 2; HYBRID_NESTED_WITNESS_PUBKEY_HASH = 3; + TAPROOT_PUBKEY = 4; } message Account { // The name used to identify the account. @@ -820,6 +846,19 @@ message UtxoLease { uint64 expiration = 3; } +message SignPsbtRequest { + /* + The PSBT that should be signed. The PSBT must contain all required inputs, + outputs, UTXO data and custom fields required to identify the signing key. + */ + bytes funded_psbt = 1; +} + +message SignPsbtResponse { + // The signed transaction in PSBT format. + bytes signed_psbt = 1; +} + message FinalizePsbtRequest { /* A PSBT that should be signed and finalized. The PSBT must contain all diff --git a/lnrpc/walletrpc/walletkit.swagger.json b/lnrpc/walletrpc/walletkit.swagger.json index 46d2920724..fb91e39c25 100644 --- a/lnrpc/walletrpc/walletkit.swagger.json +++ b/lnrpc/walletrpc/walletkit.swagger.json @@ -52,7 +52,8 @@ "UNKNOWN", "WITNESS_PUBKEY_HASH", "NESTED_WITNESS_PUBKEY_HASH", - "HYBRID_NESTED_WITNESS_PUBKEY_HASH" + "HYBRID_NESTED_WITNESS_PUBKEY_HASH", + "TAPROOT_PUBKEY" ], "default": "UNKNOWN" } @@ -364,6 +365,40 @@ ] } }, + "/v2/wallet/psbt/sign": { + "post": { + "summary": "SignPsbt expects a partial transaction with all inputs and outputs fully\ndeclared and tries to sign all unsigned inputs that have all required fields\n(UTXO information, BIP32 derivation information, witness or sig scripts)\nset.\nIf no error is returned, the PSBT is ready to be given to the next signer or\nto be finalized if lnd was the last signer.", + "description": "NOTE: This RPC only signs inputs (and only those it can sign), it does not\nperform any other tasks (such as coin selection, UTXO locking or\ninput/output/fee value validation, PSBT finalization). Any input that is\nincomplete will be skipped.", + "operationId": "WalletKit_SignPsbt", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/walletrpcSignPsbtResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/walletrpcSignPsbtRequest" + } + } + ], + "tags": [ + "WalletKit" + ] + } + }, "/v2/wallet/send": { "post": { "summary": "SendOutputs is similar to the existing sendmany call in Bitcoind, and\nallows the caller to create a transaction that sends to several outputs at\nonce. This is ideal when wanting to batch create a set of transactions.", @@ -521,7 +556,7 @@ }, "/v2/wallet/utxos": { "post": { - "summary": "ListUnspent returns a list of all utxos spendable by the wallet with a\nnumber of confirmations between the specified minimum and maximum.", + "summary": "ListUnspent returns a list of all utxos spendable by the wallet with a\nnumber of confirmations between the specified minimum and maximum. By\ndefault, all utxos are listed. To list only the unconfirmed utxos, set\nthe unconfirmed_only to true.", "operationId": "WalletKit_ListUnspent", "responses": { "200": { @@ -661,10 +696,12 @@ "WITNESS_PUBKEY_HASH", "NESTED_PUBKEY_HASH", "UNUSED_WITNESS_PUBKEY_HASH", - "UNUSED_NESTED_PUBKEY_HASH" + "UNUSED_NESTED_PUBKEY_HASH", + "TAPROOT_PUBKEY", + "UNUSED_TAPROOT_PUBKEY" ], "default": "WITNESS_PUBKEY_HASH", - "description": "- `p2wkh`: Pay to witness key hash (`WITNESS_PUBKEY_HASH` = 0)\n- `np2wkh`: Pay to nested witness key hash (`NESTED_PUBKEY_HASH` = 1)", + "description": "- `p2wkh`: Pay to witness key hash (`WITNESS_PUBKEY_HASH` = 0)\n- `np2wkh`: Pay to nested witness key hash (`NESTED_PUBKEY_HASH` = 1)\n- `p2tr`: Pay to taproot pubkey (`TAPROOT_PUBKEY` = 4)", "title": "`AddressType` has to be one of:" }, "lnrpcOutPoint": { @@ -686,6 +723,52 @@ } } }, + "lnrpcOutputDetail": { + "type": "object", + "properties": { + "output_type": { + "$ref": "#/definitions/lnrpcOutputScriptType", + "title": "The type of the output" + }, + "address": { + "type": "string", + "title": "The address" + }, + "pk_script": { + "type": "string", + "title": "The pkscript in hex" + }, + "output_index": { + "type": "string", + "format": "int64", + "title": "The output index used in the raw transaction" + }, + "amount": { + "type": "string", + "format": "int64", + "title": "The value of the output coin in satoshis" + }, + "is_our_address": { + "type": "boolean", + "title": "Denotes if the output is controlled by the internal wallet" + } + } + }, + "lnrpcOutputScriptType": { + "type": "string", + "enum": [ + "SCRIPT_TYPE_PUBKEY_HASH", + "SCRIPT_TYPE_SCRIPT_HASH", + "SCRIPT_TYPE_WITNESS_V0_PUBKEY_HASH", + "SCRIPT_TYPE_WITNESS_V0_SCRIPT_HASH", + "SCRIPT_TYPE_PUBKEY", + "SCRIPT_TYPE_MULTISIG", + "SCRIPT_TYPE_NULLDATA", + "SCRIPT_TYPE_NON_STANDARD", + "SCRIPT_TYPE_WITNESS_UNKNOWN" + ], + "default": "SCRIPT_TYPE_PUBKEY_HASH" + }, "lnrpcTransaction": { "type": "object", "properties": { @@ -727,7 +810,14 @@ "items": { "type": "string" }, - "title": "Addresses that received funds for this transaction" + "description": "Addresses that received funds for this transaction. Deprecated as it is\nnow incorporated in the output_details field." + }, + "output_details": { + "type": "array", + "items": { + "$ref": "#/definitions/lnrpcOutputDetail" + }, + "title": "Outputs that received funds for this transaction" }, "raw_tx_hex": { "type": "string", @@ -818,11 +908,11 @@ "raw_key_bytes": { "type": "string", "format": "byte", - "description": "The raw bytes of the key being identified. Either this or the KeyLocator\nmust be specified." + "description": "The raw bytes of the public key in the key pair being identified. Either\nthis or the KeyLocator must be specified." }, "key_loc": { "$ref": "#/definitions/signrpcKeyLocator", - "description": "The key locator that identifies which key to use for signing. Either this\nor the raw bytes of the target key must be specified." + "description": "The key locator that identifies which private key to use for signing.\nEither this or the raw bytes of the target public key must be specified." } } }, @@ -928,7 +1018,8 @@ "UNKNOWN", "WITNESS_PUBKEY_HASH", "NESTED_WITNESS_PUBKEY_HASH", - "HYBRID_NESTED_WITNESS_PUBKEY_HASH" + "HYBRID_NESTED_WITNESS_PUBKEY_HASH", + "TAPROOT_PUBKEY" ], "default": "UNKNOWN" }, @@ -1242,6 +1333,10 @@ "account": { "type": "string", "description": "An optional filter to only include outputs belonging to an account." + }, + "unconfirmed_only": { + "type": "boolean", + "title": "When min_confs and max_confs are zero, setting false implicitly\noverrides max_confs to be MaxInt32, otherwise max_confs remains\nzero. An error is returned if the value is true and both min_confs\nand max_confs are non-zero. (default: false)" } } }, @@ -1392,6 +1487,26 @@ } } }, + "walletrpcSignPsbtRequest": { + "type": "object", + "properties": { + "funded_psbt": { + "type": "string", + "format": "byte", + "description": "The PSBT that should be signed. The PSBT must contain all required inputs,\noutputs, UTXO data and custom fields required to identify the signing key." + } + } + }, + "walletrpcSignPsbtResponse": { + "type": "object", + "properties": { + "signed_psbt": { + "type": "string", + "format": "byte", + "description": "The signed transaction in PSBT format." + } + } + }, "walletrpcTransaction": { "type": "object", "properties": { diff --git a/lnrpc/walletrpc/walletkit.yaml b/lnrpc/walletrpc/walletkit.yaml index 7ad00c0f43..dc9a211fe7 100644 --- a/lnrpc/walletrpc/walletkit.yaml +++ b/lnrpc/walletrpc/walletkit.yaml @@ -47,6 +47,9 @@ http: - selector: walletrpc.WalletKit.FundPsbt post: "/v2/wallet/psbt/fund" body: "*" + - selector: walletrpc.WalletKit.SignPsbt + post: "/v2/wallet/psbt/sign" + body: "*" - selector: walletrpc.WalletKit.FinalizePsbt post: "/v2/wallet/psbt/finalize" body: "*" diff --git a/lnrpc/walletrpc/walletkit_grpc.pb.go b/lnrpc/walletrpc/walletkit_grpc.pb.go index d73d095aca..b037c83e07 100644 --- a/lnrpc/walletrpc/walletkit_grpc.pb.go +++ b/lnrpc/walletrpc/walletkit_grpc.pb.go @@ -21,7 +21,9 @@ const _ = grpc.SupportPackageIsVersion7 type WalletKitClient interface { // //ListUnspent returns a list of all utxos spendable by the wallet with a - //number of confirmations between the specified minimum and maximum. + //number of confirmations between the specified minimum and maximum. By + //default, all utxos are listed. To list only the unconfirmed utxos, set + //the unconfirmed_only to true. ListUnspent(ctx context.Context, in *ListUnspentRequest, opts ...grpc.CallOption) (*ListUnspentResponse, error) // //LeaseOutput locks an output to the given ID, preventing it from being @@ -172,6 +174,19 @@ type WalletKitClient interface { //an error on the caller's side. FundPsbt(ctx context.Context, in *FundPsbtRequest, opts ...grpc.CallOption) (*FundPsbtResponse, error) // + //SignPsbt expects a partial transaction with all inputs and outputs fully + //declared and tries to sign all unsigned inputs that have all required fields + //(UTXO information, BIP32 derivation information, witness or sig scripts) + //set. + //If no error is returned, the PSBT is ready to be given to the next signer or + //to be finalized if lnd was the last signer. + // + //NOTE: This RPC only signs inputs (and only those it can sign), it does not + //perform any other tasks (such as coin selection, UTXO locking or + //input/output/fee value validation, PSBT finalization). Any input that is + //incomplete will be skipped. + SignPsbt(ctx context.Context, in *SignPsbtRequest, opts ...grpc.CallOption) (*SignPsbtResponse, error) + // //FinalizePsbt expects a partial transaction with all inputs and outputs fully //declared and tries to sign all inputs that belong to the wallet. Lnd must be //the last signer of the transaction. That means, if there are any unsigned @@ -356,6 +371,15 @@ func (c *walletKitClient) FundPsbt(ctx context.Context, in *FundPsbtRequest, opt return out, nil } +func (c *walletKitClient) SignPsbt(ctx context.Context, in *SignPsbtRequest, opts ...grpc.CallOption) (*SignPsbtResponse, error) { + out := new(SignPsbtResponse) + err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/SignPsbt", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *walletKitClient) FinalizePsbt(ctx context.Context, in *FinalizePsbtRequest, opts ...grpc.CallOption) (*FinalizePsbtResponse, error) { out := new(FinalizePsbtResponse) err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/FinalizePsbt", in, out, opts...) @@ -371,7 +395,9 @@ func (c *walletKitClient) FinalizePsbt(ctx context.Context, in *FinalizePsbtRequ type WalletKitServer interface { // //ListUnspent returns a list of all utxos spendable by the wallet with a - //number of confirmations between the specified minimum and maximum. + //number of confirmations between the specified minimum and maximum. By + //default, all utxos are listed. To list only the unconfirmed utxos, set + //the unconfirmed_only to true. ListUnspent(context.Context, *ListUnspentRequest) (*ListUnspentResponse, error) // //LeaseOutput locks an output to the given ID, preventing it from being @@ -522,6 +548,19 @@ type WalletKitServer interface { //an error on the caller's side. FundPsbt(context.Context, *FundPsbtRequest) (*FundPsbtResponse, error) // + //SignPsbt expects a partial transaction with all inputs and outputs fully + //declared and tries to sign all unsigned inputs that have all required fields + //(UTXO information, BIP32 derivation information, witness or sig scripts) + //set. + //If no error is returned, the PSBT is ready to be given to the next signer or + //to be finalized if lnd was the last signer. + // + //NOTE: This RPC only signs inputs (and only those it can sign), it does not + //perform any other tasks (such as coin selection, UTXO locking or + //input/output/fee value validation, PSBT finalization). Any input that is + //incomplete will be skipped. + SignPsbt(context.Context, *SignPsbtRequest) (*SignPsbtResponse, error) + // //FinalizePsbt expects a partial transaction with all inputs and outputs fully //declared and tries to sign all inputs that belong to the wallet. Lnd must be //the last signer of the transaction. That means, if there are any unsigned @@ -595,6 +634,9 @@ func (UnimplementedWalletKitServer) LabelTransaction(context.Context, *LabelTran func (UnimplementedWalletKitServer) FundPsbt(context.Context, *FundPsbtRequest) (*FundPsbtResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method FundPsbt not implemented") } +func (UnimplementedWalletKitServer) SignPsbt(context.Context, *SignPsbtRequest) (*SignPsbtResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SignPsbt not implemented") +} func (UnimplementedWalletKitServer) FinalizePsbt(context.Context, *FinalizePsbtRequest) (*FinalizePsbtResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method FinalizePsbt not implemented") } @@ -935,6 +977,24 @@ func _WalletKit_FundPsbt_Handler(srv interface{}, ctx context.Context, dec func( return interceptor(ctx, in, info, handler) } +func _WalletKit_SignPsbt_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SignPsbtRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WalletKitServer).SignPsbt(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/walletrpc.WalletKit/SignPsbt", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WalletKitServer).SignPsbt(ctx, req.(*SignPsbtRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _WalletKit_FinalizePsbt_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(FinalizePsbtRequest) if err := dec(in); err != nil { @@ -1032,6 +1092,10 @@ var WalletKit_ServiceDesc = grpc.ServiceDesc{ MethodName: "FundPsbt", Handler: _WalletKit_FundPsbt_Handler, }, + { + MethodName: "SignPsbt", + Handler: _WalletKit_SignPsbt_Handler, + }, { MethodName: "FinalizePsbt", Handler: _WalletKit_FinalizePsbt_Handler, diff --git a/lnrpc/walletrpc/walletkit_server.go b/lnrpc/walletrpc/walletkit_server.go index 31796972f1..946f1298ff 100644 --- a/lnrpc/walletrpc/walletkit_server.go +++ b/lnrpc/walletrpc/walletkit_server.go @@ -10,17 +10,18 @@ import ( "errors" "fmt" "io/ioutil" + "math" "os" "path/filepath" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/hdkeychain" + "github.com/btcsuite/btcd/btcutil/psbt" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" - "github.com/btcsuite/btcutil/hdkeychain" - "github.com/btcsuite/btcutil/psbt" "github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/wtxmgr" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" @@ -122,6 +123,10 @@ var ( Entity: "onchain", Action: "write", }}, + "/walletrpc.WalletKit/SignPsbt": {{ + Entity: "onchain", + Action: "write", + }}, "/walletrpc.WalletKit/FinalizePsbt": {{ Entity: "onchain", Action: "write", @@ -318,16 +323,29 @@ func (w *WalletKit) internalScope() waddrmgr.KeyScope { } } -// ListUnspent returns useful information about each unspent output owned by the -// wallet, as reported by the underlying `ListUnspentWitness`; the information -// returned is: outpoint, amount in satoshis, address, address type, -// scriptPubKey in hex and number of confirmations. The result is filtered to -// contain outputs whose number of confirmations is between a -// minimum and maximum number of confirmations specified by the user, with 0 -// meaning unconfirmed. +// ListUnspent returns useful information about each unspent output owned by +// the wallet, as reported by the underlying `ListUnspentWitness`; the +// information returned is: outpoint, amount in satoshis, address, address +// type, scriptPubKey in hex and number of confirmations. The result is +// filtered to contain outputs whose number of confirmations is between a +// minimum and maximum number of confirmations specified by the user. func (w *WalletKit) ListUnspent(ctx context.Context, req *ListUnspentRequest) (*ListUnspentResponse, error) { + // Force min_confs and max_confs to be zero if unconfirmed_only is + // true. + if req.UnconfirmedOnly && (req.MinConfs != 0 || req.MaxConfs != 0) { + return nil, fmt.Errorf("min_confs and max_confs must be zero if " + + "unconfirmed_only is true") + } + + // When unconfirmed_only is inactive and max_confs is zero (default + // values), we will override max_confs to be a MaxInt32, in order + // to return all confirmed and unconfirmed utxos as a default response. + if req.MaxConfs == 0 && !req.UnconfirmedOnly { + req.MaxConfs = math.MaxInt32 + } + // Validate the confirmation arguments. minConfs, maxConfs, err := lnrpc.ParseConfs(req.MinConfs, req.MaxConfs) if err != nil { @@ -1188,6 +1206,49 @@ func marshallLeases(locks []*wtxmgr.LockedOutput) []*UtxoLease { return rpcLocks } +// SignPsbt expects a partial transaction with all inputs and outputs fully +// declared and tries to sign all unsigned inputs that have all required fields +// (UTXO information, BIP32 derivation information, witness or sig scripts) +// set. +// If no error is returned, the PSBT is ready to be given to the next signer or +// to be finalized if lnd was the last signer. +// +// NOTE: This RPC only signs inputs (and only those it can sign), it does not +// perform any other tasks (such as coin selection, UTXO locking or +// input/output/fee value validation, PSBT finalization). Any input that is +// incomplete will be skipped. +func (w *WalletKit) SignPsbt(_ context.Context, req *SignPsbtRequest) ( + *SignPsbtResponse, error) { + + packet, err := psbt.NewFromRawBytes( + bytes.NewReader(req.FundedPsbt), false, + ) + if err != nil { + log.Debugf("Error parsing PSBT: %v, raw input: %x", err, + req.FundedPsbt) + return nil, fmt.Errorf("error parsing PSBT: %v", err) + } + + // Let the wallet do the heavy lifting. This will sign all inputs that + // we have the UTXO for. If some inputs can't be signed and don't have + // witness data attached, they will just be skipped. + err = w.cfg.Wallet.SignPsbt(packet) + if err != nil { + return nil, fmt.Errorf("error signing PSBT: %v", err) + } + + // Serialize the signed PSBT in both the packet and wire format. + var signedPsbtBytes bytes.Buffer + err = packet.Serialize(&signedPsbtBytes) + if err != nil { + return nil, fmt.Errorf("error serializing PSBT: %v", err) + } + + return &SignPsbtResponse{ + SignedPsbt: signedPsbtBytes.Bytes(), + }, nil +} + // FinalizePsbt expects a partial transaction with all inputs and outputs fully // declared and tries to sign all inputs that belong to the wallet. Lnd must be // the last signer of the transaction. That means, if there are any unsigned @@ -1209,8 +1270,7 @@ func (w *WalletKit) FinalizePsbt(_ context.Context, account = req.Account } - // Parse the funded PSBT. No additional checks are required at this - // level as the wallet will perform all of them. + // Parse the funded PSBT. packet, err := psbt.NewFromRawBytes( bytes.NewReader(req.FundedPsbt), false, ) @@ -1218,6 +1278,12 @@ func (w *WalletKit) FinalizePsbt(_ context.Context, return nil, fmt.Errorf("error parsing PSBT: %v", err) } + // The only check done at this level is to validate that the PSBT is + // not complete. The wallet performs all other checks. + if packet.IsComplete() { + return nil, fmt.Errorf("PSBT is already fully signed") + } + // Let the wallet do the heavy lifting. This will sign all inputs that // we have the UTXO for. If some inputs can't be signed and don't have // witness data attached, this will fail. @@ -1281,6 +1347,9 @@ func marshalWalletAccount(internalScope waddrmgr.KeyScope, case waddrmgr.KeyScopeBIP0084: addrType = AddressType_WITNESS_PUBKEY_HASH + case waddrmgr.KeyScopeBIP0086: + addrType = AddressType_TAPROOT_PUBKEY + case internalScope: addrType = AddressType_WITNESS_PUBKEY_HASH @@ -1339,6 +1408,10 @@ func (w *WalletKit) ListAccounts(ctx context.Context, keyScope := waddrmgr.KeyScopeBIP0049Plus keyScopeFilter = &keyScope + case AddressType_TAPROOT_PUBKEY: + keyScope := waddrmgr.KeyScopeBIP0086 + keyScopeFilter = &keyScope + default: return nil, fmt.Errorf("unhandled address type %v", req.AddressType) } @@ -1354,6 +1427,7 @@ func (w *WalletKit) ListAccounts(ctx context.Context, // wallet in the response if they don't have any keys imported. if account.AccountName == waddrmgr.ImportedAddrAccountName && account.ImportedKeyCount == 0 { + continue } @@ -1393,6 +1467,10 @@ func parseAddrType(addrType AddressType, addrTyp := waddrmgr.WitnessPubKey return &addrTyp, nil + case AddressType_TAPROOT_PUBKEY: + addrTyp := waddrmgr.TaprootPubKey + return &addrTyp, nil + default: return nil, fmt.Errorf("unhandled address type %v", addrType) } @@ -1480,7 +1558,7 @@ func (w *WalletKit) ImportAccount(ctx context.Context, func (w *WalletKit) ImportPublicKey(ctx context.Context, req *ImportPublicKeyRequest) (*ImportPublicKeyResponse, error) { - pubKey, err := btcec.ParsePubKey(req.PublicKey, btcec.S256()) + pubKey, err := btcec.ParsePubKey(req.PublicKey) if err != nil { return nil, err } diff --git a/lnrpc/watchtowerrpc/handler.go b/lnrpc/watchtowerrpc/handler.go index 4aacea1be7..23e7ac64e6 100644 --- a/lnrpc/watchtowerrpc/handler.go +++ b/lnrpc/watchtowerrpc/handler.go @@ -175,7 +175,7 @@ func (c *Handler) GetInfo(ctx context.Context, } // isActive returns nil if the tower backend is initialized, and the Handler can -// proccess RPC requests. +// process RPC requests. func (c *Handler) isActive() error { if c.cfg.Active { return nil diff --git a/lnrpc/watchtowerrpc/interface.go b/lnrpc/watchtowerrpc/interface.go index aafe3e06f4..adf6cf93a5 100644 --- a/lnrpc/watchtowerrpc/interface.go +++ b/lnrpc/watchtowerrpc/interface.go @@ -3,7 +3,7 @@ package watchtowerrpc import ( "net" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" ) // WatchtowerBackend abstracts access to the watchtower information that is diff --git a/lnrpc/watchtowerrpc/log.go b/lnrpc/watchtowerrpc/log.go index 0ff4e68012..a71b7827fb 100644 --- a/lnrpc/watchtowerrpc/log.go +++ b/lnrpc/watchtowerrpc/log.go @@ -29,19 +29,3 @@ func DisableLog() { func UseLogger(logger btclog.Logger) { log = logger } - -// logClosure is used to provide a closure over expensive logging operations so -// don't have to be performed when the logging level doesn't warrant it. -type logClosure func() string // nolint:unused - -// String invokes the underlying function and returns the result. -func (c logClosure) String() string { - return c() -} - -// newLogClosure returns a new closure over a function that returns a string -// which itself provides a Stringer interface so that it can be used with the -// logging system. -func newLogClosure(c func() string) logClosure { // nolint:unused - return logClosure(c) -} diff --git a/lnrpc/websocket_proxy.go b/lnrpc/websocket_proxy.go index 803cf6c36e..7aaa9b1f28 100644 --- a/lnrpc/websocket_proxy.go +++ b/lnrpc/websocket_proxy.go @@ -253,7 +253,7 @@ func (p *WebsocketProxy) upgradeToWebSocketProxy(w http.ResponseWriter, payload = newPayload } - _, err = requestForwarder.Write(payload) + _, err := requestForwarder.Write(payload) if err != nil { p.logger.Errorf("WS: error writing message "+ "to upstream http server: %v", err) @@ -338,7 +338,7 @@ func (p *WebsocketProxy) upgradeToWebSocketProxy(w http.ResponseWriter, continue } - err = conn.WriteMessage( + err := conn.WriteMessage( websocket.TextMessage, responseForwarder.Bytes(), ) if err != nil { diff --git a/lnrpc/wtclientrpc/driver.go b/lnrpc/wtclientrpc/driver.go index cecbbf4347..62a18cffce 100644 --- a/lnrpc/wtclientrpc/driver.go +++ b/lnrpc/wtclientrpc/driver.go @@ -34,7 +34,7 @@ func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( } // Before we try to make the new service instance, we'll perform - // some sanity checks on the arguments to ensure that they're useable. + // some sanity checks on the arguments to ensure that they're usable. switch { case config.Resolver == nil: return nil, nil, errors.New("a lncfg.TCPResolver is required") diff --git a/lnrpc/wtclientrpc/wtclient.go b/lnrpc/wtclientrpc/wtclient.go index a42f560ace..5864b66088 100644 --- a/lnrpc/wtclientrpc/wtclient.go +++ b/lnrpc/wtclientrpc/wtclient.go @@ -7,7 +7,7 @@ import ( "net" "strconv" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lnrpc" @@ -188,7 +188,7 @@ func (c *WatchtowerClient) AddTower(ctx context.Context, return nil, err } - pubKey, err := btcec.ParsePubKey(req.Pubkey, btcec.S256()) + pubKey, err := btcec.ParsePubKey(req.Pubkey) if err != nil { return nil, err } @@ -227,7 +227,7 @@ func (c *WatchtowerClient) RemoveTower(ctx context.Context, return nil, err } - pubKey, err := btcec.ParsePubKey(req.Pubkey, btcec.S256()) + pubKey, err := btcec.ParsePubKey(req.Pubkey) if err != nil { return nil, err } @@ -301,7 +301,7 @@ func (c *WatchtowerClient) GetTowerInfo(ctx context.Context, return nil, err } - pubKey, err := btcec.ParsePubKey(req.Pubkey, btcec.S256()) + pubKey, err := btcec.ParsePubKey(req.Pubkey) if err != nil { return nil, err } diff --git a/lntest/bitcoind.go b/lntest/bitcoind.go index 37ff5fb5d1..37800169bb 100644 --- a/lntest/bitcoind.go +++ b/lntest/bitcoind.go @@ -1,5 +1,5 @@ -//go:build bitcoind && !notxindex -// +build bitcoind,!notxindex +//go:build bitcoind && !notxindex && !rpcpolling +// +build bitcoind,!notxindex,!rpcpolling package lntest @@ -19,5 +19,5 @@ func NewBackend(miner string, netParams *chaincfg.Params) ( "-disablewallet", } - return newBackend(miner, netParams, extraArgs) + return newBackend(miner, netParams, extraArgs, false) } diff --git a/lntest/bitcoind_common.go b/lntest/bitcoind_common.go index 9fc03756bb..d9bf102f63 100644 --- a/lntest/bitcoind_common.go +++ b/lntest/bitcoind_common.go @@ -29,6 +29,7 @@ type BitcoindBackendConfig struct { zmqTxPath string p2pPort int rpcClient *rpcclient.Client + rpcPolling bool // minerAddr is the p2p address of the miner to connect to. minerAddr string @@ -46,10 +47,19 @@ func (b BitcoindBackendConfig) GenArgs() []string { args = append(args, fmt.Sprintf("--bitcoind.rpchost=%v", b.rpcHost)) args = append(args, fmt.Sprintf("--bitcoind.rpcuser=%v", b.rpcUser)) args = append(args, fmt.Sprintf("--bitcoind.rpcpass=%v", b.rpcPass)) - args = append(args, fmt.Sprintf("--bitcoind.zmqpubrawblock=%v", - b.zmqBlockPath)) - args = append(args, fmt.Sprintf("--bitcoind.zmqpubrawtx=%v", - b.zmqTxPath)) + + if b.rpcPolling { + args = append(args, fmt.Sprintf("--bitcoind.rpcpolling")) + args = append(args, + fmt.Sprintf("--bitcoind.blockpollinginterval=10ms")) + args = append(args, + fmt.Sprintf("--bitcoind.txpollinginterval=10ms")) + } else { + args = append(args, fmt.Sprintf("--bitcoind.zmqpubrawblock=%v", + b.zmqBlockPath)) + args = append(args, fmt.Sprintf("--bitcoind.zmqpubrawtx=%v", + b.zmqTxPath)) + } return args } @@ -64,6 +74,11 @@ func (b BitcoindBackendConfig) DisconnectMiner() error { return b.rpcClient.AddNode(b.minerAddr, rpcclient.ANRemove) } +// Credentials returns the rpc username, password and host for the backend. +func (b BitcoindBackendConfig) Credentials() (string, string, string, error) { + return b.rpcUser, b.rpcPass, b.rpcHost, nil +} + // Name returns the name of the backend type. func (b BitcoindBackendConfig) Name() string { return "bitcoind" @@ -71,8 +86,8 @@ func (b BitcoindBackendConfig) Name() string { // newBackend starts a bitcoind node with the given extra parameters and returns // a BitcoindBackendConfig for that node. -func newBackend(miner string, netParams *chaincfg.Params, extraArgs []string) ( - *BitcoindBackendConfig, func() error, error) { +func newBackend(miner string, netParams *chaincfg.Params, extraArgs []string, + rpcPolling bool) (*BitcoindBackendConfig, func() error, error) { baseLogDir := fmt.Sprintf(logDirPattern, GetLogDir()) if netParams != &chaincfg.RegressionNetParams { @@ -187,6 +202,7 @@ func newBackend(miner string, netParams *chaincfg.Params, extraArgs []string) ( p2pPort: p2pPort, rpcClient: client, minerAddr: miner, + rpcPolling: rpcPolling, } return &bd, cleanUp, nil diff --git a/lntest/bitcoind_notxindex.go b/lntest/bitcoind_notxindex.go index 2aa7e4a631..de7959eba7 100644 --- a/lntest/bitcoind_notxindex.go +++ b/lntest/bitcoind_notxindex.go @@ -1,5 +1,5 @@ -//go:build bitcoind && notxindex -// +build bitcoind,notxindex +//go:build bitcoind && notxindex && !rpcpolling +// +build bitcoind,notxindex,!rpcpolling package lntest @@ -18,5 +18,5 @@ func NewBackend(miner string, netParams *chaincfg.Params) ( "-disablewallet", } - return newBackend(miner, netParams, extraArgs) + return newBackend(miner, netParams, extraArgs, false) } diff --git a/lntest/bitcoind_rpcpolling.go b/lntest/bitcoind_rpcpolling.go new file mode 100644 index 0000000000..ca203ad2c2 --- /dev/null +++ b/lntest/bitcoind_rpcpolling.go @@ -0,0 +1,23 @@ +//go:build bitcoind && rpcpolling +// +build bitcoind,rpcpolling + +package lntest + +import ( + "github.com/btcsuite/btcd/chaincfg" +) + +// NewBackend starts a bitcoind node without the txindex enabled and returns a +// BitoindBackendConfig for that node. +func NewBackend(miner string, netParams *chaincfg.Params) ( + *BitcoindBackendConfig, func() error, error) { + + extraArgs := []string{ + "-debug", + "-regtest", + "-txindex", + "-disablewallet", + } + + return newBackend(miner, netParams, extraArgs, true) +} diff --git a/lntest/btcd.go b/lntest/btcd.go index 1ac281787c..200faa9bf5 100644 --- a/lntest/btcd.go +++ b/lntest/btcd.go @@ -67,6 +67,11 @@ func (b BtcdBackendConfig) DisconnectMiner() error { return b.harness.Client.Node(btcjson.NDisconnect, b.minerAddr, &temp) } +// Credentials returns the rpc username, password and host for the backend. +func (b BtcdBackendConfig) Credentials() (string, string, string, error) { + return b.rpcConfig.User, b.rpcConfig.Pass, b.rpcConfig.Host, nil +} + // Name returns the name of the backend type. func (b BtcdBackendConfig) Name() string { return "btcd" diff --git a/lntest/fee_service.go b/lntest/fee_service.go index bf80fa0db5..e2c1df3617 100644 --- a/lntest/fee_service.go +++ b/lntest/fee_service.go @@ -7,6 +7,7 @@ import ( "io" "net/http" "sync" + "testing" "github.com/lightningnetwork/lnd/lnwallet/chainfee" ) @@ -22,6 +23,8 @@ const ( type feeService struct { feeEstimates + t *testing.T + srv *http.Server wg sync.WaitGroup @@ -36,9 +39,10 @@ type feeEstimates struct { } // startFeeService spins up a go-routine to serve fee estimates. -func startFeeService() *feeService { +func startFeeService(t *testing.T) *feeService { port := NextAvailablePort() f := feeService{ + t: t, url: fmt.Sprintf("http://localhost:%v/fee-estimates.json", port), } @@ -59,7 +63,7 @@ func startFeeService() *feeService { defer f.wg.Done() if err := f.srv.ListenAndServe(); err != http.ErrServerClosed { - fmt.Printf("error: cannot start fee api: %v", err) + f.t.Errorf("error: cannot start fee api: %v", err) } }() @@ -73,23 +77,21 @@ func (f *feeService) handleRequest(w http.ResponseWriter, r *http.Request) { bytes, err := json.Marshal(f.feeEstimates) if err != nil { - fmt.Printf("error: cannot serialize "+ - "estimates: %v", err) + f.t.Errorf("error: cannot serialize estimates: %v", err) return } _, err = io.WriteString(w, string(bytes)) if err != nil { - fmt.Printf("error: cannot send estimates: %v", - err) + f.t.Errorf("error: cannot send estimates: %v", err) } } // stop stops the web server. func (f *feeService) stop() { if err := f.srv.Shutdown(context.Background()); err != nil { - fmt.Printf("error: cannot stop fee api: %v", err) + f.t.Errorf("error: cannot stop fee api: %v", err) } f.wg.Wait() diff --git a/lntest/fee_service_test.go b/lntest/fee_service_test.go index c8c0ca89ed..228d7c30e4 100644 --- a/lntest/fee_service_test.go +++ b/lntest/fee_service_test.go @@ -11,7 +11,7 @@ import ( // TestFeeService tests the itest fee estimating web service. func TestFeeService(t *testing.T) { - service := startFeeService() + service := startFeeService(t) defer service.stop() service.setFee(5000) diff --git a/lntest/harness_miner.go b/lntest/harness_miner.go new file mode 100644 index 0000000000..b9f54619ab --- /dev/null +++ b/lntest/harness_miner.go @@ -0,0 +1,161 @@ +package lntest + +import ( + "context" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + "time" + + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/integration/rpctest" + "github.com/btcsuite/btcd/rpcclient" +) + +const ( + // minerLogFilename is the default log filename for the miner node. + minerLogFilename = "output_btcd_miner.log" + + // minerLogDir is the default log dir for the miner node. + minerLogDir = ".minerlogs" +) + +var harnessNetParams = &chaincfg.RegressionNetParams + +type HarnessMiner struct { + *rpctest.Harness + + // runCtx is a context with cancel method. It's used to signal when the + // node needs to quit, and used as the parent context when spawning + runCtx context.Context + cancel context.CancelFunc + + // logPath is the directory path of the miner's logs. + logPath string + + // logFilename is the saved log filename of the miner node. + logFilename string +} + +// NewMiner creates a new miner using btcd backend with the default log file +// dir and name. +func NewMiner() (*HarnessMiner, error) { + return newMiner(minerLogDir, minerLogFilename) +} + +// NewTempMiner creates a new miner using btcd backend with the specified log +// file dir and name. +func NewTempMiner(tempDir, tempLogFilename string) (*HarnessMiner, error) { + return newMiner(tempDir, tempLogFilename) +} + +// newMiner creates a new miner using btcd's rpctest. +func newMiner(minerDirName, logFilename string) (*HarnessMiner, error) { + handler := &rpcclient.NotificationHandlers{} + btcdBinary := GetBtcdBinary() + baseLogPath := fmt.Sprintf("%s/%s", GetLogDir(), minerDirName) + + args := []string{ + "--rejectnonstd", + "--txindex", + "--nowinservice", + "--nobanning", + "--debuglevel=debug", + "--logdir=" + baseLogPath, + "--trickleinterval=100ms", + // Don't disconnect if a reply takes too long. + "--nostalldetect", + } + + miner, err := rpctest.New(harnessNetParams, handler, args, btcdBinary) + if err != nil { + return nil, fmt.Errorf("unable to create mining node: %v", err) + } + + ctxt, cancel := context.WithCancel(context.Background()) + m := &HarnessMiner{ + Harness: miner, + runCtx: ctxt, + cancel: cancel, + logPath: baseLogPath, + logFilename: logFilename, + } + return m, nil +} + +// Stop shuts down the miner and saves its logs. +func (h *HarnessMiner) Stop() error { + h.cancel() + + if err := h.TearDown(); err != nil { + return fmt.Errorf("tear down miner got error: %s", err) + } + + return h.saveLogs() +} + +// saveLogs copies the node logs and save it to the file specified by +// h.logFilename. +func (h *HarnessMiner) saveLogs() error { + // After shutting down the miner, we'll make a copy of the log files + // before deleting the temporary log dir. + path := fmt.Sprintf("%s/%s", h.logPath, harnessNetParams.Name) + files, err := ioutil.ReadDir(path) + if err != nil { + return fmt.Errorf("unable to read log directory: %v", err) + } + + for _, file := range files { + newFilename := strings.Replace( + file.Name(), "btcd.log", h.logFilename, 1, + ) + copyPath := fmt.Sprintf("%s/../%s", h.logPath, newFilename) + + logFile := fmt.Sprintf("%s/%s", path, file.Name()) + err := CopyFile(filepath.Clean(copyPath), logFile) + if err != nil { + return fmt.Errorf("unable to copy file: %v", err) + } + } + + if err = os.RemoveAll(h.logPath); err != nil { + return fmt.Errorf("cannot remove dir %s: %v", h.logPath, err) + } + + return nil +} + +// waitForTxInMempool blocks until the target txid is seen in the mempool. If +// the transaction isn't seen within the network before the passed timeout, +// then an error is returned. +func (h *HarnessMiner) waitForTxInMempool(txid chainhash.Hash) error { + ticker := time.NewTicker(50 * time.Millisecond) + defer ticker.Stop() + + var mempool []*chainhash.Hash + for { + select { + case <-h.runCtx.Done(): + return fmt.Errorf("NetworkHarness has been torn down") + case <-time.After(DefaultTimeout): + return fmt.Errorf("wanted %v, found %v txs "+ + "in mempool: %v", txid, len(mempool), mempool) + + case <-ticker.C: + var err error + mempool, err = h.Client.GetRawMempool() + if err != nil { + return err + } + + for _, mempoolTx := range mempool { + if *mempoolTx == txid { + return nil + } + } + } + } +} diff --git a/lntest/harness.go b/lntest/harness_net.go similarity index 85% rename from lntest/harness.go rename to lntest/harness_net.go index 3cba2a013b..5fde91419f 100644 --- a/lntest/harness.go +++ b/lntest/harness_net.go @@ -15,12 +15,11 @@ import ( "testing" "time" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcd/integration/rpctest" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd" "github.com/lightningnetwork/lnd/kvdb/etcd" "github.com/lightningnetwork/lnd/lnrpc" @@ -36,10 +35,12 @@ import ( const DefaultCSV = 4 // NodeOption is a function for updating a node's configuration. -type NodeOption func(*NodeConfig) +type NodeOption func(*BaseNodeConfig) // NetworkHarness is an integration testing harness for the lightning network. -// The harness by default is created with two active nodes on the network: +// Building on top of HarnessNode, it is responsible for handling interactions +// among different nodes. The harness by default is created with two active +// nodes on the network: // Alice and Bob. type NetworkHarness struct { netParams *chaincfg.Params @@ -51,9 +52,9 @@ type NetworkHarness struct { // compiled with all required itest flags. lndBinary string - // Miner is a reference to a running full node that can be used to create - // new blocks on the network. - Miner *rpctest.Harness + // Miner is a reference to a running full node that can be used to + // create new blocks on the network. + Miner *HarnessMiner // BackendCfg houses the information necessary to use a node as LND // chain backend, such as rpc configuration, P2P information etc. @@ -79,37 +80,33 @@ type NetworkHarness struct { // lnd. feeService *feeService - quit chan struct{} + // runCtx is a context with cancel method. It's used to signal when the + // node needs to quit, and used as the parent context when spawning + // children contexts for RPC requests. + runCtx context.Context + cancel context.CancelFunc mtx sync.Mutex } -type DatabaseBackend int - -const ( - BackendBbolt DatabaseBackend = iota - BackendEtcd - BackendPostgres -) - // NewNetworkHarness creates a new network test harness. // TODO(roasbeef): add option to use golang's build library to a binary of the // current repo. This will save developers from having to manually `go install` // within the repo each time before changes -func NewNetworkHarness(r *rpctest.Harness, b BackendConfig, lndBinary string, +func NewNetworkHarness(m *HarnessMiner, b BackendConfig, lndBinary string, dbBackend DatabaseBackend) (*NetworkHarness, error) { - feeService := startFeeService() + ctxt, cancel := context.WithCancel(context.Background()) n := NetworkHarness{ activeNodes: make(map[int]*HarnessNode), nodesByPub: make(map[string]*HarnessNode), lndErrorChan: make(chan error), - netParams: r.ActiveNet, - Miner: r, + netParams: m.ActiveNet, + Miner: m, BackendCfg: b, - feeService: feeService, - quit: make(chan struct{}), + runCtx: ctxt, + cancel: cancel, lndBinary: lndBinary, dbBackend: dbBackend, } @@ -117,8 +114,7 @@ func NewNetworkHarness(r *rpctest.Harness, b BackendConfig, lndBinary string, } // LookUpNodeByPub queries the set of active nodes to locate a node according -// to its public key. The second value will be true if the node was found, and -// false otherwise. +// to its public key. The error is returned if the node was not found. func (n *NetworkHarness) LookUpNodeByPub(pubStr string) (*HarnessNode, error) { n.mtx.Lock() defer n.mtx.Unlock() @@ -138,17 +134,6 @@ func (n *NetworkHarness) ProcessErrors() <-chan error { return n.lndErrorChan } -// fakeLogger is a fake grpclog.Logger implementation. This is used to stop -// grpc's logger from printing directly to stdout. -type fakeLogger struct{} - -func (f *fakeLogger) Fatal(args ...interface{}) {} -func (f *fakeLogger) Fatalf(format string, args ...interface{}) {} -func (f *fakeLogger) Fatalln(args ...interface{}) {} -func (f *fakeLogger) Print(args ...interface{}) {} -func (f *fakeLogger) Printf(format string, args ...interface{}) {} -func (f *fakeLogger) Println(args ...interface{}) {} - // SetUp starts the initial seeder nodes within the test harness. The initial // node's wallets will be funded wallets with ten 1 BTC outputs each. Finally // rpc clients capable of communicating with the initial seeder nodes are @@ -159,8 +144,10 @@ func (n *NetworkHarness) SetUp(t *testing.T, // Swap out grpc's default logger with out fake logger which drops the // statements on the floor. - grpclog.SetLogger(&fakeLogger{}) + fakeLogger := grpclog.NewLoggerV2(io.Discard, io.Discard, io.Discard) + grpclog.SetLoggerV2(fakeLogger) n.currentTestCase = testCase + n.feeService = startFeeService(t) // Start the initial seeder nodes within the test network, then connect // their respective RPC clients. @@ -184,7 +171,6 @@ func (n *NetworkHarness) SetUp(t *testing.T, // First, make a connection between the two nodes. This will wait until // both nodes are fully started since the Connect RPC is guarded behind // the server.Started() flag that waits for all subsystems to be ready. - ctxb := context.Background() n.ConnectNodes(t, n.Alice, n.Bob) // Load up the wallets of the seeder nodes with 10 outputs of 1 BTC @@ -195,7 +181,7 @@ func (n *NetworkHarness) SetUp(t *testing.T, clients := []lnrpc.LightningClient{n.Alice, n.Bob} for _, client := range clients { for i := 0; i < 10; i++ { - resp, err := client.NewAddress(ctxb, addrReq) + resp, err := client.NewAddress(n.runCtx, addrReq) if err != nil { return err } @@ -226,12 +212,10 @@ func (n *NetworkHarness) SetUp(t *testing.T, } // Now we want to wait for the nodes to catch up. - ctxt, cancel := context.WithTimeout(ctxb, DefaultTimeout) - defer cancel() - if err := n.Alice.WaitForBlockchainSync(ctxt); err != nil { + if err := n.Alice.WaitForBlockchainSync(); err != nil { return err } - if err := n.Bob.WaitForBlockchainSync(ctxt); err != nil { + if err := n.Bob.WaitForBlockchainSync(); err != nil { return err } @@ -245,11 +229,11 @@ out: for { select { case <-balanceTicker.C: - aliceResp, err := n.Alice.WalletBalance(ctxb, balReq) + aliceResp, err := n.Alice.WalletBalance(n.runCtx, balReq) if err != nil { return err } - bobResp, err := n.Bob.WalletBalance(ctxb, balReq) + bobResp, err := n.Bob.WalletBalance(n.runCtx, balReq) if err != nil { return err } @@ -280,14 +264,20 @@ func (n *NetworkHarness) TearDown() error { // Stop stops the test harness. func (n *NetworkHarness) Stop() { close(n.lndErrorChan) - close(n.quit) + n.cancel() - n.feeService.stop() + // feeService may not be created. For instance, running a non-exist + // test case. + if n.feeService != nil { + n.feeService.stop() + } } // extraArgsEtcd returns extra args for configuring LND to use an external etcd // database (for remote channel DB and wallet DB). -func extraArgsEtcd(etcdCfg *etcd.Config, name string, cluster bool) []string { +func extraArgsEtcd(etcdCfg *etcd.Config, name string, cluster bool, + leaderSessionTTL int) []string { + extraArgs := []string{ "--db.backend=etcd", fmt.Sprintf("--db.etcd.host=%v", etcdCfg.Host), @@ -298,14 +288,16 @@ func extraArgsEtcd(etcdCfg *etcd.Config, name string, cluster bool) []string { if etcdCfg.InsecureSkipVerify { extraArgs = append(extraArgs, "--db.etcd.insecure_skip_verify") - } if cluster { - extraArgs = append(extraArgs, "--cluster.enable-leader-election") - extraArgs = append( - extraArgs, fmt.Sprintf("--cluster.id=%v", name), - ) + clusterArgs := []string{ + "--cluster.enable-leader-election", + fmt.Sprintf("--cluster.id=%v", name), + fmt.Sprintf("--cluster.leader-session-ttl=%v", + leaderSessionTTL), + } + extraArgs = append(extraArgs, clusterArgs...) } return extraArgs @@ -315,13 +307,13 @@ func extraArgsEtcd(etcdCfg *etcd.Config, name string, cluster bool) []string { // etcd database as its (remote) channel and wallet DB. The passsed cluster // flag indicates that we'd like the node to join the cluster leader election. func (n *NetworkHarness) NewNodeWithSeedEtcd(name string, etcdCfg *etcd.Config, - password []byte, entropy []byte, statelessInit, cluster bool) ( - *HarnessNode, []string, []byte, error) { + password []byte, entropy []byte, statelessInit, cluster bool, + leaderSessionTTL int) (*HarnessNode, []string, []byte, error) { // We don't want to use the embedded etcd instance. const dbBackend = BackendBbolt - extraArgs := extraArgsEtcd(etcdCfg, name, cluster) + extraArgs := extraArgsEtcd(etcdCfg, name, cluster, leaderSessionTTL) return n.newNodeWithSeed( name, extraArgs, password, entropy, statelessInit, dbBackend, ) @@ -333,12 +325,13 @@ func (n *NetworkHarness) NewNodeWithSeedEtcd(name string, etcdCfg *etcd.Config, // If the wait flag is false then we won't wait until RPC is available (this is // useful when the node is not expected to become the leader right away). func (n *NetworkHarness) NewNodeEtcd(name string, etcdCfg *etcd.Config, - password []byte, cluster, wait bool) (*HarnessNode, error) { + password []byte, cluster, wait bool, leaderSessionTTL int) ( + *HarnessNode, error) { // We don't want to use the embedded etcd instance. const dbBackend = BackendBbolt - extraArgs := extraArgsEtcd(etcdCfg, name, cluster) + extraArgs := extraArgsEtcd(etcdCfg, name, cluster, leaderSessionTTL) return n.newNode(name, extraArgs, true, password, dbBackend, wait) } @@ -380,8 +373,6 @@ func (n *NetworkHarness) newNodeWithSeed(name string, extraArgs []string, return nil, nil, nil, err } - ctxb := context.Background() - // Create a request to generate a new aezeed. The new seed will have the // same password as the internal wallet. genSeedReq := &lnrpc.GenSeedRequest{ @@ -389,7 +380,7 @@ func (n *NetworkHarness) newNodeWithSeed(name string, extraArgs []string, SeedEntropy: entropy, } - ctxt, cancel := context.WithTimeout(ctxb, DefaultTimeout) + ctxt, cancel := context.WithTimeout(n.runCtx, DefaultTimeout) defer cancel() var genSeedResp *lnrpc.GenSeedResponse @@ -411,7 +402,7 @@ func (n *NetworkHarness) newNodeWithSeed(name string, extraArgs []string, // Pass the init request via rpc to finish unlocking the node. This will // also initialize the macaroon-authenticated LightningClient. - response, err := node.Init(ctxb, initReq) + response, err := node.Init(initReq) if err != nil { return nil, nil, nil, err } @@ -437,8 +428,6 @@ func (n *NetworkHarness) NewNodeRemoteSigner(name string, extraArgs []string, return nil, err } - ctxb := context.Background() - // With the seed created, construct the init request to the node, // including the newly generated seed. initReq := &lnrpc.InitWalletRequest{ @@ -448,7 +437,7 @@ func (n *NetworkHarness) NewNodeRemoteSigner(name string, extraArgs []string, // Pass the init request via rpc to finish unlocking the node. This will // also initialize the macaroon-authenticated LightningClient. - _, err = node.Init(ctxb, initReq) + _, err = node.Init(initReq) if err != nil { return nil, err } @@ -486,7 +475,7 @@ func (n *NetworkHarness) RestoreNodeWithSeed(name string, extraArgs []string, ChannelBackups: chanBackups, } - _, err = node.Init(context.Background(), initReq) + _, err = node.Init(initReq) if err != nil { return nil, err } @@ -506,7 +495,7 @@ func (n *NetworkHarness) newNode(name string, extraArgs []string, hasSeed bool, password []byte, dbBackend DatabaseBackend, wait bool, opts ...NodeOption) ( *HarnessNode, error) { - cfg := &NodeConfig{ + cfg := &BaseNodeConfig{ Name: name, LogFilenamePrefix: n.currentTestCase, HasSeed: hasSeed, @@ -521,7 +510,7 @@ func (n *NetworkHarness) newNode(name string, extraArgs []string, hasSeed bool, opt(cfg) } - node, err := newNode(*cfg) + node, err := newNode(cfg) if err != nil { return nil, err } @@ -590,8 +579,7 @@ tryconnect: // been made, the method will block until the two nodes appear in each other's // peers list, or until the 15s timeout expires. func (n *NetworkHarness) EnsureConnected(t *testing.T, a, b *HarnessNode) { - ctxb := context.Background() - ctx, cancel := context.WithTimeout(ctxb, DefaultTimeout*2) + ctx, cancel := context.WithTimeout(n.runCtx, DefaultTimeout*2) defer cancel() // errConnectionRequested is used to signal that a connection was @@ -637,7 +625,6 @@ func (n *NetworkHarness) EnsureConnected(t *testing.T, a, b *HarnessNode) { predErr = err return false } - }, DefaultTimeout) if err != nil { return fmt.Errorf("connection not succeeded within 15 "+ @@ -728,8 +715,7 @@ func (n *NetworkHarness) ConnectNodesPerm(t *testing.T, func (n *NetworkHarness) connectNodes(t *testing.T, a, b *HarnessNode, perm bool) { - ctxb := context.Background() - ctx, cancel := context.WithTimeout(ctxb, DefaultTimeout) + ctx, cancel := context.WithTimeout(n.runCtx, DefaultTimeout) defer cancel() bobInfo, err := b.GetInfo(ctx, &lnrpc.GetInfoRequest{}) @@ -780,8 +766,7 @@ func (n *NetworkHarness) connectNodes(t *testing.T, a, b *HarnessNode, // DisconnectNodes disconnects node a from node b by sending RPC message // from a node to b node func (n *NetworkHarness) DisconnectNodes(a, b *HarnessNode) error { - ctxb := context.Background() - ctx, cancel := context.WithTimeout(ctxb, DefaultTimeout) + ctx, cancel := context.WithTimeout(n.runCtx, DefaultTimeout) defer cancel() bobInfo, err := b.GetInfo(ctx, &lnrpc.GetInfoRequest{}) @@ -836,15 +821,13 @@ func (n *NetworkHarness) RestartNode(node *HarnessNode, callback func() error, unlockReq.RecoveryWindow = 1000 } - if err := node.Unlock(context.Background(), unlockReq); err != nil { + if err := node.Unlock(unlockReq); err != nil { return err } - // Give the node some time to catch up with the chain before we continue - // with the tests. - ctxc, done := context.WithTimeout(context.Background(), DefaultTimeout) - defer done() - return node.WaitForBlockchainSync(ctxc) + // Give the node some time to catch up with the chain before we + // continue with the tests. + return node.WaitForBlockchainSync() } // RestartNodeNoUnlock attempts to restart a lightning node by shutting it down @@ -970,45 +953,6 @@ func saveProfilesPage(node *HarnessNode) error { return nil } -// waitForTxInMempool blocks until the target txid is seen in the mempool. If -// the transaction isn't seen within the network before the passed timeout, -// then an error is returned. -func (n *NetworkHarness) waitForTxInMempool(ctx context.Context, - txid chainhash.Hash) error { - - // Return immediately if harness has been torn down. - select { - case <-n.quit: - return fmt.Errorf("NetworkHarness has been torn down") - default: - } - - ticker := time.NewTicker(50 * time.Millisecond) - defer ticker.Stop() - - var mempool []*chainhash.Hash - for { - select { - case <-ctx.Done(): - return fmt.Errorf("wanted %v, found %v txs "+ - "in mempool: %v", txid, len(mempool), mempool) - - case <-ticker.C: - var err error - mempool, err = n.Miner.Client.GetRawMempool() - if err != nil { - return err - } - - for _, mempoolTx := range mempool { - if *mempoolTx == txid { - return nil - } - } - } - } -} - // OpenChannelParams houses the params to specify when opening a new channel. type OpenChannelParams struct { // Amt is the local amount being put into the channel. @@ -1055,20 +999,14 @@ type OpenChannelParams struct { func (n *NetworkHarness) OpenChannel(srcNode, destNode *HarnessNode, p OpenChannelParams) (lnrpc.Lightning_OpenChannelClient, error) { - ctxb := context.Background() - // The cancel is intentionally left out here because the returned - // item(open channel client) relies on the context being active. This - // will be fixed once we finish refactoring the NetworkHarness. - ctx, _ := context.WithTimeout(ctxb, ChannelOpenTimeout) // nolint: govet - // Wait until srcNode and destNode have the latest chain synced. // Otherwise, we may run into a check within the funding manager that // prevents any funding workflows from being kicked off if the chain // isn't yet synced. - if err := srcNode.WaitForBlockchainSync(ctx); err != nil { + if err := srcNode.WaitForBlockchainSync(); err != nil { return nil, fmt.Errorf("unable to sync srcNode chain: %v", err) } - if err := destNode.WaitForBlockchainSync(ctx); err != nil { + if err := destNode.WaitForBlockchainSync(); err != nil { return nil, fmt.Errorf("unable to sync destNode chain: %v", err) } @@ -1091,7 +1029,9 @@ func (n *NetworkHarness) OpenChannel(srcNode, destNode *HarnessNode, CommitmentType: p.CommitmentType, } - respStream, err := srcNode.OpenChannel(ctx, openReq) + // We need to use n.runCtx here to keep the response stream alive after + // the function is returned. + respStream, err := srcNode.OpenChannel(n.runCtx, openReq) if err != nil { return nil, fmt.Errorf("unable to open channel between "+ "alice and bob: %v", err) @@ -1100,17 +1040,18 @@ func (n *NetworkHarness) OpenChannel(srcNode, destNode *HarnessNode, chanOpen := make(chan struct{}) errChan := make(chan error) go func() { - // Consume the "channel pending" update. This waits until the node - // notifies us that the final message in the channel funding workflow - // has been sent to the remote node. + // Consume the "channel pending" update. This waits until the + // node notifies us that the final message in the channel + // funding workflow has been sent to the remote node. resp, err := respStream.Recv() if err != nil { errChan <- err return } - if _, ok := resp.Update.(*lnrpc.OpenStatusUpdate_ChanPending); !ok { - errChan <- fmt.Errorf("expected channel pending update, "+ - "instead got %v", resp) + _, ok := resp.Update.(*lnrpc.OpenStatusUpdate_ChanPending) + if !ok { + errChan <- fmt.Errorf("expected channel pending: "+ + "update, instead got %v", resp) return } @@ -1118,7 +1059,7 @@ func (n *NetworkHarness) OpenChannel(srcNode, destNode *HarnessNode, }() select { - case <-ctx.Done(): + case <-time.After(ChannelOpenTimeout): return nil, fmt.Errorf("timeout reached before chan pending "+ "update sent: %v", err) case err := <-errChan: @@ -1128,23 +1069,19 @@ func (n *NetworkHarness) OpenChannel(srcNode, destNode *HarnessNode, } } -// OpenPendingChannel attempts to open a channel between srcNode and destNode with the -// passed channel funding parameters. If the passed context has a timeout, then -// if the timeout is reached before the channel pending notification is -// received, an error is returned. +// OpenPendingChannel attempts to open a channel between srcNode and destNode +// with the passed channel funding parameters. If the passed context has a +// timeout, then if the timeout is reached before the channel pending +// notification is received, an error is returned. func (n *NetworkHarness) OpenPendingChannel(srcNode, destNode *HarnessNode, amt btcutil.Amount, pushAmt btcutil.Amount) (*lnrpc.PendingUpdate, error) { - ctxb := context.Background() - ctx, cancel := context.WithTimeout(ctxb, ChannelOpenTimeout) - defer cancel() - // Wait until srcNode and destNode have blockchain synced - if err := srcNode.WaitForBlockchainSync(ctx); err != nil { + if err := srcNode.WaitForBlockchainSync(); err != nil { return nil, fmt.Errorf("unable to sync srcNode chain: %v", err) } - if err := destNode.WaitForBlockchainSync(ctx); err != nil { + if err := destNode.WaitForBlockchainSync(); err != nil { return nil, fmt.Errorf("unable to sync destNode chain: %v", err) } @@ -1155,7 +1092,9 @@ func (n *NetworkHarness) OpenPendingChannel(srcNode, destNode *HarnessNode, Private: false, } - respStream, err := srcNode.OpenChannel(ctx, openReq) + // We need to use n.runCtx here to keep the response stream alive after + // the function is returned. + respStream, err := srcNode.OpenChannel(n.runCtx, openReq) if err != nil { return nil, fmt.Errorf("unable to open channel between "+ "alice and bob: %v", err) @@ -1164,9 +1103,9 @@ func (n *NetworkHarness) OpenPendingChannel(srcNode, destNode *HarnessNode, chanPending := make(chan *lnrpc.PendingUpdate) errChan := make(chan error) go func() { - // Consume the "channel pending" update. This waits until the node - // notifies us that the final message in the channel funding workflow - // has been sent to the remote node. + // Consume the "channel pending" update. This waits until the + // node notifies us that the final message in the channel + // funding workflow has been sent to the remote node. resp, err := respStream.Recv() if err != nil { errChan <- err @@ -1174,8 +1113,8 @@ func (n *NetworkHarness) OpenPendingChannel(srcNode, destNode *HarnessNode, } pendingResp, ok := resp.Update.(*lnrpc.OpenStatusUpdate_ChanPending) if !ok { - errChan <- fmt.Errorf("expected channel pending update, "+ - "instead got %v", resp) + errChan <- fmt.Errorf("expected channel pending "+ + "update, instead got %v", resp) return } @@ -1183,7 +1122,7 @@ func (n *NetworkHarness) OpenPendingChannel(srcNode, destNode *HarnessNode, }() select { - case <-ctx.Done(): + case <-time.After(ChannelOpenTimeout): return nil, fmt.Errorf("timeout reached before chan pending " + "update sent") case err := <-errChan: @@ -1198,10 +1137,10 @@ func (n *NetworkHarness) OpenPendingChannel(srcNode, destNode *HarnessNode, // has a timeout, then if the timeout is reached before the channel has been // opened, then an error is returned. func (n *NetworkHarness) WaitForChannelOpen( - openChanStream lnrpc.Lightning_OpenChannelClient) (*lnrpc.ChannelPoint, error) { + openChanStream lnrpc.Lightning_OpenChannelClient) ( + *lnrpc.ChannelPoint, error) { - ctxb := context.Background() - ctx, cancel := context.WithTimeout(ctxb, ChannelOpenTimeout) + ctx, cancel := context.WithTimeout(n.runCtx, ChannelOpenTimeout) defer cancel() errChan := make(chan error) @@ -1238,14 +1177,14 @@ func (n *NetworkHarness) WaitForChannelOpen( // has a timeout, an error is returned if that timeout is reached before the // channel close is pending. func (n *NetworkHarness) CloseChannel(lnNode *HarnessNode, - cp *lnrpc.ChannelPoint, - force bool) (lnrpc.Lightning_CloseChannelClient, *chainhash.Hash, error) { + cp *lnrpc.ChannelPoint, force bool) (lnrpc.Lightning_CloseChannelClient, + *chainhash.Hash, error) { - ctxb := context.Background() // The cancel is intentionally left out here because the returned // item(close channel client) relies on the context being active. This // will be fixed once we finish refactoring the NetworkHarness. - ctx, _ := context.WithTimeout(ctxb, ChannelCloseTimeout) // nolint: govet + ctxt, cancel := context.WithTimeout(n.runCtx, ChannelCloseTimeout) + defer cancel() // Create a channel outpoint that we can use to compare to channels // from the ListChannelsResponse. @@ -1273,7 +1212,7 @@ func (n *NetworkHarness) CloseChannel(lnNode *HarnessNode, // not. filterChannel := func(node *HarnessNode, op wire.OutPoint) (*lnrpc.Channel, error) { - listResp, err := node.ListChannels(ctx, listReq) + listResp, err := node.ListChannels(ctxt, listReq) if err != nil { return nil, err } @@ -1298,7 +1237,8 @@ func (n *NetworkHarness) CloseChannel(lnNode *HarnessNode, } // Next, we'll fetch the target channel in order to get the - // harness node that will be receiving the channel close request. + // harness node that will be receiving the channel close + // request. targetChan, err := filterChannel(lnNode, chanPoint) if err != nil { return nil, nil, err @@ -1315,7 +1255,9 @@ func (n *NetworkHarness) CloseChannel(lnNode *HarnessNode, return nil, nil, fmt.Errorf("channel of closing " + "node not active in time") } - err = wait.Predicate(activeChanPredicate(receivingNode), timeout) + err = wait.Predicate( + activeChanPredicate(receivingNode), timeout, + ) if err != nil { return nil, nil, fmt.Errorf("channel of receiving " + "node not active in time") @@ -1331,7 +1273,9 @@ func (n *NetworkHarness) CloseChannel(lnNode *HarnessNode, closeReq := &lnrpc.CloseChannelRequest{ ChannelPoint: cp, Force: force, } - closeRespStream, err = lnNode.CloseChannel(ctx, closeReq) + // We need to use n.runCtx to keep the client stream alive + // after the function has returned. + closeRespStream, err = lnNode.CloseChannel(n.runCtx, closeReq) if err != nil { return fmt.Errorf("unable to close channel: %v", err) } @@ -1357,7 +1301,7 @@ func (n *NetworkHarness) CloseChannel(lnNode *HarnessNode, return fmt.Errorf("unable to decode closeTxid: "+ "%v", err) } - if err := n.waitForTxInMempool(ctx, *closeTxid); err != nil { + if err := n.Miner.waitForTxInMempool(*closeTxid); err != nil { return fmt.Errorf("error while waiting for "+ "broadcast tx: %v", err) } @@ -1375,11 +1319,8 @@ func (n *NetworkHarness) CloseChannel(lnNode *HarnessNode, // passed context has a timeout, then if the timeout is reached before the // notification is received then an error is returned. func (n *NetworkHarness) WaitForChannelClose( - closeChanStream lnrpc.Lightning_CloseChannelClient) (*chainhash.Hash, error) { - - ctxb := context.Background() - ctx, cancel := context.WithTimeout(ctxb, ChannelCloseTimeout) - defer cancel() + closeChanStream lnrpc.Lightning_CloseChannelClient) ( + *chainhash.Hash, error) { errChan := make(chan error) updateChan := make(chan *lnrpc.CloseStatusUpdate_ChanClose) @@ -1403,7 +1344,7 @@ func (n *NetworkHarness) WaitForChannelClose( // Wait until either the deadline for the context expires, an error // occurs, or the channel close update is received. select { - case <-ctx.Done(): + case <-time.After(ChannelCloseTimeout): return nil, fmt.Errorf("timeout reached before update sent") case err := <-errChan: return nil, err @@ -1417,12 +1358,10 @@ func (n *NetworkHarness) WaitForChannelClose( // an optional set of check functions which can be used to make further // assertions using channel's values. These functions are responsible for // failing the test themselves if they do not pass. -// nolint: interfacer func (n *NetworkHarness) AssertChannelExists(node *HarnessNode, chanPoint *wire.OutPoint, checks ...func(*lnrpc.Channel)) error { - ctxb := context.Background() - ctx, cancel := context.WithTimeout(ctxb, ChannelCloseTimeout) + ctx, cancel := context.WithTimeout(n.runCtx, ChannelCloseTimeout) defer cancel() req := &lnrpc.ListChannelsRequest{} @@ -1478,7 +1417,7 @@ func (n *NetworkHarness) DumpLogs(node *HarnessNode) (string, error) { func (n *NetworkHarness) SendCoins(t *testing.T, amt btcutil.Amount, target *HarnessNode) { - err := n.sendCoins( + err := n.SendCoinsOfType( amt, target, lnrpc.AddressType_WITNESS_PUBKEY_HASH, true, ) require.NoErrorf(t, err, "unable to send coins for %s", target.Cfg.Name) @@ -1490,7 +1429,7 @@ func (n *NetworkHarness) SendCoins(t *testing.T, amt btcutil.Amount, func (n *NetworkHarness) SendCoinsUnconfirmed(t *testing.T, amt btcutil.Amount, target *HarnessNode) { - err := n.sendCoins( + err := n.SendCoinsOfType( amt, target, lnrpc.AddressType_WITNESS_PUBKEY_HASH, false, ) require.NoErrorf( @@ -1504,7 +1443,7 @@ func (n *NetworkHarness) SendCoinsUnconfirmed(t *testing.T, amt btcutil.Amount, func (n *NetworkHarness) SendCoinsNP2WKH(t *testing.T, amt btcutil.Amount, target *HarnessNode) { - err := n.sendCoins( + err := n.SendCoinsOfType( amt, target, lnrpc.AddressType_NESTED_PUBKEY_HASH, true, ) require.NoErrorf( @@ -1513,14 +1452,26 @@ func (n *NetworkHarness) SendCoinsNP2WKH(t *testing.T, amt btcutil.Amount, ) } -// sendCoins attempts to send amt satoshis from the internal mining node to the -// targeted lightning node. The confirmed boolean indicates whether the +// SendCoinsP2TR attempts to send amt satoshis from the internal mining node +// to the targeted lightning node using a P2TR address. +func (n *NetworkHarness) SendCoinsP2TR(t *testing.T, amt btcutil.Amount, + target *HarnessNode) { + + err := n.SendCoinsOfType( + amt, target, lnrpc.AddressType_TAPROOT_PUBKEY, true, + ) + require.NoErrorf( + t, err, "unable to send P2TR coins for %s", target.Cfg.Name, + ) +} + +// SendCoinsOfType attempts to send amt satoshis from the internal mining node +// to the targeted lightning node. The confirmed boolean indicates whether the // transaction that pays to the target should confirm. -func (n *NetworkHarness) sendCoins(amt btcutil.Amount, target *HarnessNode, +func (n *NetworkHarness) SendCoinsOfType(amt btcutil.Amount, target *HarnessNode, addrType lnrpc.AddressType, confirmed bool) error { - ctxb := context.Background() - ctx, cancel := context.WithTimeout(ctxb, DefaultTimeout) + ctx, cancel := context.WithTimeout(n.runCtx, DefaultTimeout) defer cancel() balReq := &lnrpc.WalletBalanceRequest{} @@ -1615,7 +1566,9 @@ func (n *NetworkHarness) sendCoins(amt btcutil.Amount, target *HarnessNode, return err } - expectedBalance := btcutil.Amount(initialBalance.ConfirmedBalance) + amt + fullInitialBalance := initialBalance.ConfirmedBalance + + initialBalance.UnconfirmedBalance + expectedBalance := btcutil.Amount(fullInitialBalance) + amt return target.WaitForBalance(expectedBalance, true) } @@ -1629,36 +1582,6 @@ func (n *NetworkHarness) SetFeeEstimateWithConf( n.feeService.setFeeWithConf(fee, conf) } -// CopyFile copies the file src to dest. -func CopyFile(dest, src string) error { - s, err := os.Open(src) - if err != nil { - return err - } - defer s.Close() - - d, err := os.Create(dest) - if err != nil { - return err - } - - if _, err := io.Copy(d, s); err != nil { - d.Close() - return err - } - - return d.Close() -} - -// FileExists returns true if the file at path exists. -func FileExists(path string) bool { - if _, err := os.Stat(path); os.IsNotExist(err) { - return false - } - - return true -} - // copyAll copies all files and directories from srcDir to dstDir recursively. // Note that this function does not support links. func copyAll(dstDir, srcDir string) error { @@ -1774,3 +1697,26 @@ func (n *NetworkHarness) RestoreDb(hn *HarnessNode) error { return nil } + +// getChanPointFundingTxid returns the given channel point's funding txid in +// raw bytes. +func getChanPointFundingTxid(chanPoint *lnrpc.ChannelPoint) ([]byte, error) { + var txid []byte + + // A channel point's funding txid can be get/set as a byte slice or a + // string. In the case it is a string, decode it. + switch chanPoint.GetFundingTxid().(type) { + case *lnrpc.ChannelPoint_FundingTxidBytes: + txid = chanPoint.GetFundingTxidBytes() + case *lnrpc.ChannelPoint_FundingTxidStr: + s := chanPoint.GetFundingTxidStr() + h, err := chainhash.NewHashFromStr(s) + if err != nil { + return nil, err + } + + txid = h[:] + } + + return txid, nil +} diff --git a/lntest/node.go b/lntest/harness_node.go similarity index 64% rename from lntest/node.go rename to lntest/harness_node.go index f164ad070e..477b973078 100644 --- a/lntest/node.go +++ b/lntest/harness_node.go @@ -6,31 +6,26 @@ import ( "crypto/rand" "encoding/hex" "encoding/json" - "flag" "fmt" "io" "io/ioutil" - "net" "os" "os/exec" "path" "path/filepath" "strings" "sync" - "sync/atomic" "time" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcd/integration/rpctest" - "github.com/btcsuite/btcd/rpcclient" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" - "github.com/go-errors/errors" "github.com/jackc/pgx/v4/pgxpool" "github.com/lightningnetwork/lnd/chanbackup" "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lnrpc/chainrpc" "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc" + "github.com/lightningnetwork/lnd/lnrpc/peersrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lnrpc/signrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" @@ -39,16 +34,13 @@ import ( "github.com/lightningnetwork/lnd/lntest/wait" "github.com/lightningnetwork/lnd/macaroons" "google.golang.org/grpc" + "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials" + "google.golang.org/grpc/status" "gopkg.in/macaroon.v2" ) const ( - // defaultNodePort is the start of the range for listening ports of - // harness nodes. Ports are monotonically increasing starting from this - // number and are determined by the results of nextAvailablePort(). - defaultNodePort = 5555 - // logPubKeyBytes is the number of bytes of the node's PubKey that will // be appended to the log file name. The whole PubKey is too long and // not really necessary to quickly identify what node produced which @@ -59,128 +51,24 @@ const ( // release of announcements by AuthenticatedGossiper to the network. trickleDelay = 50 - // listenerFormat is the format string that is used to generate local - // listener addresses. - listenerFormat = "127.0.0.1:%d" - - // NeutrinoBackendName is the name of the neutrino backend. - NeutrinoBackendName = "neutrino" - postgresDsn = "postgres://postgres:postgres@localhost:6432/%s?sslmode=disable" + + // commitInterval specifies the maximum interval the graph database + // will wait between attempting to flush a batch of modifications to + // disk(db.batch-commit-interval). + commitInterval = 10 * time.Millisecond ) var ( // numActiveNodes is the number of active nodes within the test network. numActiveNodes = 0 numActiveNodesMtx sync.Mutex - - // lastPort is the last port determined to be free for use by a new - // node. It should be used atomically. - lastPort uint32 = defaultNodePort - - // logOutput is a flag that can be set to append the output from the - // seed nodes to log files. - logOutput = flag.Bool("logoutput", false, - "log output from node n to file output-n.log") - - // logSubDir is the default directory where the logs are written to if - // logOutput is true. - logSubDir = flag.String("logdir", ".", "default dir to write logs to") - - // goroutineDump is a flag that can be set to dump the active - // goroutines of test nodes on failure. - goroutineDump = flag.Bool("goroutinedump", false, - "write goroutine dump from node n to file pprof-n.log") - - // btcdExecutable is the full path to the btcd binary. - btcdExecutable = flag.String( - "btcdexec", "", "full path to btcd binary", - ) ) func postgresDatabaseDsn(dbName string) string { return fmt.Sprintf(postgresDsn, dbName) } -// NextAvailablePort returns the first port that is available for listening by -// a new node. It panics if no port is found and the maximum available TCP port -// is reached. -func NextAvailablePort() int { - port := atomic.AddUint32(&lastPort, 1) - for port < 65535 { - // If there are no errors while attempting to listen on this - // port, close the socket and return it as available. While it - // could be the case that some other process picks up this port - // between the time the socket is closed and it's reopened in - // the harness node, in practice in CI servers this seems much - // less likely than simply some other process already being - // bound at the start of the tests. - addr := fmt.Sprintf(listenerFormat, port) - l, err := net.Listen("tcp4", addr) - if err == nil { - err := l.Close() - if err == nil { - return int(port) - } - } - port = atomic.AddUint32(&lastPort, 1) - } - - // No ports available? Must be a mistake. - panic("no ports available for listening") -} - -// ApplyPortOffset adds the given offset to the lastPort variable, making it -// possible to run the tests in parallel without colliding on the same ports. -func ApplyPortOffset(offset uint32) { - _ = atomic.AddUint32(&lastPort, offset) -} - -// GetLogDir returns the passed --logdir flag or the default value if it wasn't -// set. -func GetLogDir() string { - if logSubDir != nil && *logSubDir != "" { - return *logSubDir - } - return "." -} - -// GetBtcdBinary returns the full path to the binary of the custom built btcd -// executable or an empty string if none is set. -func GetBtcdBinary() string { - if btcdExecutable != nil { - return *btcdExecutable - } - - return "" -} - -// GenerateBtcdListenerAddresses is a function that returns two listener -// addresses with unique ports and should be used to overwrite rpctest's default -// generator which is prone to use colliding ports. -func GenerateBtcdListenerAddresses() (string, string) { - return fmt.Sprintf(listenerFormat, NextAvailablePort()), - fmt.Sprintf(listenerFormat, NextAvailablePort()) -} - -// generateListeningPorts returns four ints representing ports to listen on -// designated for the current lightning network test. This returns the next -// available ports for the p2p, rpc, rest and profiling services. -func generateListeningPorts(cfg *NodeConfig) { - if cfg.P2PPort == 0 { - cfg.P2PPort = NextAvailablePort() - } - if cfg.RPCPort == 0 { - cfg.RPCPort = NextAvailablePort() - } - if cfg.RESTPort == 0 { - cfg.RESTPort = NextAvailablePort() - } - if cfg.ProfilePort == 0 { - cfg.ProfilePort = NextAvailablePort() - } -} - // BackendConfig is an interface that abstracts away the specific chain backend // node implementation. type BackendConfig interface { @@ -196,12 +84,31 @@ type BackendConfig interface { // Name returns the name of the backend type. Name() string + + // Credentials returns the rpc username, password and host for the + // backend. + Credentials() (string, string, string, error) +} + +// NodeConfig is the basic interface a node configuration must implement. +type NodeConfig interface { + // BaseConfig returns the base node configuration struct. + BaseConfig() *BaseNodeConfig + + // GenerateListeningPorts generates the ports to listen on designated + // for the current lightning network test. + GenerateListeningPorts() + + // GenArgs generates a slice of command line arguments from the + // lightning node config struct. + GenArgs() []string } -type NodeConfig struct { +// BaseNodeConfig is the base node configuration. +type BaseNodeConfig struct { Name string - // LogFilenamePrefix is is used to prefix node log files. Can be used + // LogFilenamePrefix is used to prefix node log files. Can be used // to store the current test case for simpler postmortem debugging. LogFilenamePrefix string @@ -235,28 +142,28 @@ type NodeConfig struct { PostgresDsn string } -func (cfg NodeConfig) P2PAddr() string { - return fmt.Sprintf(listenerFormat, cfg.P2PPort) +func (cfg BaseNodeConfig) P2PAddr() string { + return fmt.Sprintf(ListenerFormat, cfg.P2PPort) } -func (cfg NodeConfig) RPCAddr() string { - return fmt.Sprintf(listenerFormat, cfg.RPCPort) +func (cfg BaseNodeConfig) RPCAddr() string { + return fmt.Sprintf(ListenerFormat, cfg.RPCPort) } -func (cfg NodeConfig) RESTAddr() string { - return fmt.Sprintf(listenerFormat, cfg.RESTPort) +func (cfg BaseNodeConfig) RESTAddr() string { + return fmt.Sprintf(ListenerFormat, cfg.RESTPort) } // DBDir returns the holding directory path of the graph database. -func (cfg NodeConfig) DBDir() string { +func (cfg BaseNodeConfig) DBDir() string { return filepath.Join(cfg.DataDir, "graph", cfg.NetParams.Name) } -func (cfg NodeConfig) DBPath() string { +func (cfg BaseNodeConfig) DBPath() string { return filepath.Join(cfg.DBDir(), "channel.db") } -func (cfg NodeConfig) ChanBackupPath() string { +func (cfg BaseNodeConfig) ChanBackupPath() string { return filepath.Join( cfg.DataDir, "chain", "bitcoin", fmt.Sprintf( @@ -266,9 +173,31 @@ func (cfg NodeConfig) ChanBackupPath() string { ) } -// genArgs generates a slice of command line arguments from the lightning node +// GenerateListeningPorts generates the ports to listen on designated for the +// current lightning network test. +func (cfg *BaseNodeConfig) GenerateListeningPorts() { + if cfg.P2PPort == 0 { + cfg.P2PPort = NextAvailablePort() + } + if cfg.RPCPort == 0 { + cfg.RPCPort = NextAvailablePort() + } + if cfg.RESTPort == 0 { + cfg.RESTPort = NextAvailablePort() + } + if cfg.ProfilePort == 0 { + cfg.ProfilePort = NextAvailablePort() + } +} + +// BaseConfig returns the base node configuration struct. +func (cfg *BaseNodeConfig) BaseConfig() *BaseNodeConfig { + return cfg +} + +// GenArgs generates a slice of command line arguments from the lightning node // config struct. -func (cfg NodeConfig) genArgs() []string { +func (cfg *BaseNodeConfig) GenArgs() []string { var args []string switch cfg.NetParams { @@ -282,27 +211,28 @@ func (cfg NodeConfig) genArgs() []string { backendArgs := cfg.BackendCfg.GenArgs() args = append(args, backendArgs...) - args = append(args, "--bitcoin.active") - args = append(args, "--nobootstrap") - args = append(args, "--debuglevel=debug") - args = append(args, "--bitcoin.defaultchanconfs=1") - args = append(args, fmt.Sprintf("--db.batch-commit-interval=%v", 10*time.Millisecond)) - args = append(args, fmt.Sprintf("--bitcoin.defaultremotedelay=%v", DefaultCSV)) - args = append(args, fmt.Sprintf("--rpclisten=%v", cfg.RPCAddr())) - args = append(args, fmt.Sprintf("--restlisten=%v", cfg.RESTAddr())) - args = append(args, fmt.Sprintf("--restcors=https://%v", cfg.RESTAddr())) - args = append(args, fmt.Sprintf("--listen=%v", cfg.P2PAddr())) - args = append(args, fmt.Sprintf("--externalip=%v", cfg.P2PAddr())) - args = append(args, fmt.Sprintf("--logdir=%v", cfg.LogDir)) - args = append(args, fmt.Sprintf("--datadir=%v", cfg.DataDir)) - args = append(args, fmt.Sprintf("--tlscertpath=%v", cfg.TLSCertPath)) - args = append(args, fmt.Sprintf("--tlskeypath=%v", cfg.TLSKeyPath)) - args = append(args, fmt.Sprintf("--configfile=%v", cfg.DataDir)) - args = append(args, fmt.Sprintf("--adminmacaroonpath=%v", cfg.AdminMacPath)) - args = append(args, fmt.Sprintf("--readonlymacaroonpath=%v", cfg.ReadMacPath)) - args = append(args, fmt.Sprintf("--invoicemacaroonpath=%v", cfg.InvoiceMacPath)) - args = append(args, fmt.Sprintf("--trickledelay=%v", trickleDelay)) - args = append(args, fmt.Sprintf("--profile=%d", cfg.ProfilePort)) + + nodeArgs := []string{ + "--bitcoin.active", + "--nobootstrap", + "--debuglevel=debug", + "--bitcoin.defaultchanconfs=1", + fmt.Sprintf("--db.batch-commit-interval=%v", commitInterval), + fmt.Sprintf("--bitcoin.defaultremotedelay=%v", DefaultCSV), + fmt.Sprintf("--rpclisten=%v", cfg.RPCAddr()), + fmt.Sprintf("--restlisten=%v", cfg.RESTAddr()), + fmt.Sprintf("--restcors=https://%v", cfg.RESTAddr()), + fmt.Sprintf("--listen=%v", cfg.P2PAddr()), + fmt.Sprintf("--externalip=%v", cfg.P2PAddr()), + fmt.Sprintf("--lnddir=%v", cfg.BaseDir), + fmt.Sprintf("--adminmacaroonpath=%v", cfg.AdminMacPath), + fmt.Sprintf("--readonlymacaroonpath=%v", cfg.ReadMacPath), + fmt.Sprintf("--invoicemacaroonpath=%v", cfg.InvoiceMacPath), + fmt.Sprintf("--trickledelay=%v", trickleDelay), + fmt.Sprintf("--profile=%d", cfg.ProfilePort), + fmt.Sprintf("--caches.rpc-graph-cache-duration=%d", 0), + } + args = append(args, nodeArgs...) if !cfg.HasSeed { args = append(args, "--noseedbackup") @@ -367,14 +297,14 @@ func (cfg NodeConfig) genArgs() []string { // ] // }, // "chanPoint2": ... -// } +// }. type policyUpdateMap map[string]map[string][]*lnrpc.RoutingPolicy // HarnessNode represents an instance of lnd running within our test network // harness. Each HarnessNode instance also fully embeds an RPC client in // order to pragmatically drive the node. type HarnessNode struct { - Cfg *NodeConfig + Cfg *BaseNodeConfig // NodeID is a unique identifier for the node within a NetworkHarness. NodeID int @@ -385,14 +315,11 @@ type HarnessNode struct { PubKey [33]byte PubKeyStr string - cmd *exec.Cmd - pidFile string - logFile *os.File - - // processExit is a channel that's closed once it's detected that the - // process this instance of HarnessNode is bound to has exited. - processExit chan struct{} + // rpc holds a list of RPC clients. + rpc *RPCClients + // chanWatchRequests receives a request for watching a particular event + // for a given channel. chanWatchRequests chan *chanWatchRequest // For each outpoint, we'll track an integer which denotes the number of @@ -409,45 +336,73 @@ type HarnessNode struct { // node and the outpoint. policyUpdates policyUpdateMap - quit chan struct{} - wg sync.WaitGroup - - lnrpc.LightningClient - - lnrpc.WalletUnlockerClient + // backupDbDir is the path where a database backup is stored, if any. + backupDbDir string - invoicesrpc.InvoicesClient + // postgresDbName is the name of the postgres database where lnd data is + // stored in. + postgresDbName string - // SignerClient cannot be embedded because the name collisions of the - // methods SignMessage and VerifyMessage. - SignerClient signrpc.SignerClient + // runCtx is a context with cancel method. It's used to signal when the + // node needs to quit, and used as the parent context when spawning + // children contexts for RPC requests. + runCtx context.Context + cancel context.CancelFunc - // conn is the underlying connection to the grpc endpoint of the node. - conn *grpc.ClientConn + wg sync.WaitGroup + cmd *exec.Cmd + logFile *os.File - // RouterClient, WalletKitClient, WatchtowerClient cannot be embedded, - // because a name collision would occur with LightningClient. + // TODO(yy): remove + lnrpc.LightningClient + lnrpc.WalletUnlockerClient + invoicesrpc.InvoicesClient + peersrpc.PeersClient + SignerClient signrpc.SignerClient RouterClient routerrpc.RouterClient WalletKitClient walletrpc.WalletKitClient Watchtower watchtowerrpc.WatchtowerClient WatchtowerClient wtclientrpc.WatchtowerClientClient StateClient lnrpc.StateClient + ChainClient chainrpc.ChainNotifierClient +} - // backupDbDir is the path where a database backup is stored, if any. - backupDbDir string +// RPCClients wraps a list of RPC clients into a single struct for easier +// access. +type RPCClients struct { + // conn is the underlying connection to the grpc endpoint of the node. + conn *grpc.ClientConn - // postgresDbName is the name of the postgres database where lnd data is - // stored in. - postgresDbName string + LN lnrpc.LightningClient + WalletUnlocker lnrpc.WalletUnlockerClient + Invoice invoicesrpc.InvoicesClient + Signer signrpc.SignerClient + Router routerrpc.RouterClient + WalletKit walletrpc.WalletKitClient + Watchtower watchtowerrpc.WatchtowerClient + WatchtowerClient wtclientrpc.WatchtowerClientClient + State lnrpc.StateClient + ChainClient chainrpc.ChainNotifierClient } // Assert *HarnessNode implements the lnrpc.LightningClient interface. var _ lnrpc.LightningClient = (*HarnessNode)(nil) var _ lnrpc.WalletUnlockerClient = (*HarnessNode)(nil) var _ invoicesrpc.InvoicesClient = (*HarnessNode)(nil) +var _ peersrpc.PeersClient = (*HarnessNode)(nil) + +// nextNodeID generates a unique sequence to be used as the node's ID. +func nextNodeID() int { + numActiveNodesMtx.Lock() + defer numActiveNodesMtx.Unlock() + nodeNum := numActiveNodes + numActiveNodes++ + + return nodeNum +} // newNode creates a new test lightning node instance from the passed config. -func newNode(cfg NodeConfig) (*HarnessNode, error) { +func newNode(cfg *BaseNodeConfig) (*HarnessNode, error) { if cfg.BaseDir == "" { var err error cfg.BaseDir, err = ioutil.TempDir("", "lndtest-node") @@ -456,9 +411,9 @@ func newNode(cfg NodeConfig) (*HarnessNode, error) { } } cfg.DataDir = filepath.Join(cfg.BaseDir, "data") - cfg.LogDir = filepath.Join(cfg.BaseDir, "log") - cfg.TLSCertPath = filepath.Join(cfg.DataDir, "tls.cert") - cfg.TLSKeyPath = filepath.Join(cfg.DataDir, "tls.key") + cfg.LogDir = filepath.Join(cfg.BaseDir, "logs") + cfg.TLSCertPath = filepath.Join(cfg.BaseDir, "tls.cert") + cfg.TLSKeyPath = filepath.Join(cfg.BaseDir, "tls.key") networkDir := filepath.Join( cfg.DataDir, "chain", "bitcoin", cfg.NetParams.Name, @@ -467,7 +422,7 @@ func newNode(cfg NodeConfig) (*HarnessNode, error) { cfg.ReadMacPath = filepath.Join(networkDir, "readonly.macaroon") cfg.InvoiceMacPath = filepath.Join(networkDir, "invoice.macaroon") - generateListeningPorts(&cfg) + cfg.GenerateListeningPorts() // Run all tests with accept keysend. The keysend code is very isolated // and it is highly unlikely that it would affect regular itests when @@ -485,14 +440,9 @@ func newNode(cfg NodeConfig) (*HarnessNode, error) { cfg.PostgresDsn = postgresDatabaseDsn(dbName) } - numActiveNodesMtx.Lock() - nodeNum := numActiveNodes - numActiveNodes++ - numActiveNodesMtx.Unlock() - return &HarnessNode{ - Cfg: &cfg, - NodeID: nodeNum, + Cfg: cfg, + NodeID: nextNodeID(), chanWatchRequests: make(chan *chanWatchRequest), openChans: make(map[wire.OutPoint]int), openChanWatchers: make(map[wire.OutPoint][]chan struct{}), @@ -538,74 +488,6 @@ func executePgQuery(query string) error { return err } -// NewMiner creates a new miner using btcd backend. The baseLogDir specifies -// the miner node's log dir. When tests are finished, during clean up, its log -// files, including any compressed log files from logrotate, are copied to -// logDir as logFilename. -func NewMiner(baseLogDir, logFilename string, netParams *chaincfg.Params, - handler *rpcclient.NotificationHandlers, - btcdBinary string) (*rpctest.Harness, func() error, error) { - - args := []string{ - "--rejectnonstd", - "--txindex", - "--nowinservice", - "--nobanning", - "--debuglevel=debug", - "--logdir=" + baseLogDir, - "--trickleinterval=100ms", - // Don't disconnect if a reply takes too long. - "--nostalldetect", - } - - miner, err := rpctest.New(netParams, handler, args, btcdBinary) - if err != nil { - return nil, nil, fmt.Errorf( - "unable to create mining node: %v", err, - ) - } - - cleanUp := func() error { - if err := miner.TearDown(); err != nil { - return fmt.Errorf( - "failed to tear down miner, got error: %s", err, - ) - } - - // After shutting down the miner, we'll make a copy of - // the log files before deleting the temporary log dir. - logDir := fmt.Sprintf("%s/%s", baseLogDir, netParams.Name) - files, err := ioutil.ReadDir(logDir) - if err != nil { - return fmt.Errorf("unable to read log directory: %v", err) - } - - for _, file := range files { - logFile := fmt.Sprintf( - "%s/%s", logDir, file.Name(), - ) - newFilename := strings.Replace(file.Name(), "btcd.log", logFilename, 1) - copyPath := fmt.Sprintf( - "%s/../%s", baseLogDir, newFilename, - ) - - err := CopyFile(filepath.Clean(copyPath), logFile) - if err != nil { - return fmt.Errorf("unable to copy file: %v", err) - } - } - - if err = os.RemoveAll(baseLogDir); err != nil { - return fmt.Errorf( - "cannot remove dir %s: %v", baseLogDir, err, - ) - } - return nil - } - - return miner, cleanUp, nil -} - // String gives the internal state of the node which is useful for debugging. func (hn *HarnessNode) String() string { type nodeCfg struct { @@ -654,12 +536,12 @@ func (hn *HarnessNode) String() string { nodeState.ClosedChans[outpoint.String()] = count } - bytes, err := json.MarshalIndent(nodeState, "", "\t") + stateBytes, err := json.MarshalIndent(nodeState, "", "\t") if err != nil { return fmt.Sprintf("\n encode node state with err: %v", err) } - return fmt.Sprintf("\nnode state: %s", bytes) + return fmt.Sprintf("\nnode state: %s", stateBytes) } // DBPath returns the filepath to the channeldb database file for this node. @@ -709,114 +591,27 @@ func (hn *HarnessNode) InvoiceMacPath() string { return hn.Cfg.InvoiceMacPath } -// renameFile is a helper to rename (log) files created during integration tests. -func renameFile(fromFileName, toFileName string) { - err := os.Rename(fromFileName, toFileName) - if err != nil { - fmt.Printf("could not rename %s to %s: %v\n", - fromFileName, toFileName, err) - } -} - -// Start launches a new process running lnd. Additionally, the PID of the -// launched process is saved in order to possibly kill the process forcibly -// later. -// -// This may not clean up properly if an error is returned, so the caller should -// call shutdown() regardless of the return value. -func (hn *HarnessNode) start(lndBinary string, lndError chan<- error, - wait bool) error { - - hn.quit = make(chan struct{}) - - args := hn.Cfg.genArgs() +// startLnd handles the startup of lnd, creating log files, and possibly kills +// the process when needed. +func (hn *HarnessNode) startLnd(lndBinary string, lndError chan<- error) error { + args := hn.Cfg.GenArgs() hn.cmd = exec.Command(lndBinary, args...) // Redirect stderr output to buffer var errb bytes.Buffer hn.cmd.Stderr = &errb - // Make sure the log file cleanup function is initialized, even - // if no log file is created. - var finalizeLogfile = func() { - if hn.logFile != nil { - hn.logFile.Close() - } - } - - getFinalizedLogFilePrefix := func() string { - pubKeyHex := hex.EncodeToString( - hn.PubKey[:logPubKeyBytes], - ) - - return fmt.Sprintf("%s/%d-%s-%s-%s", - GetLogDir(), hn.NodeID, - hn.Cfg.LogFilenamePrefix, - hn.Cfg.Name, pubKeyHex) - } - - finalizeEtcdLog := func() { - if hn.Cfg.DbBackend != BackendEtcd { - return - } - - etcdLogFileName := fmt.Sprintf("%s/etcd.log", hn.Cfg.LogDir) - newEtcdLogFileName := fmt.Sprintf("%v-etcd.log", - getFinalizedLogFilePrefix(), - ) - - renameFile(etcdLogFileName, newEtcdLogFileName) - } - // If the logoutput flag is passed, redirect output from the nodes to // log files. + var ( + fileName string + err error + ) if *logOutput { - dir := GetLogDir() - fileName := fmt.Sprintf("%s/%d-%s-%s-%s.log", dir, hn.NodeID, - hn.Cfg.LogFilenamePrefix, hn.Cfg.Name, - hex.EncodeToString(hn.PubKey[:logPubKeyBytes])) - - // If the node's PubKey is not yet initialized, create a - // temporary file name. Later, after the PubKey has been - // initialized, the file can be moved to its final name with - // the PubKey included. - if bytes.Equal(hn.PubKey[:4], []byte{0, 0, 0, 0}) { - fileName = fmt.Sprintf("%s/%d-%s-%s-tmp__.log", dir, - hn.NodeID, hn.Cfg.LogFilenamePrefix, - hn.Cfg.Name) - } - - // Once the node has done its work, the log file can be - // renamed. - finalizeLogfile = func() { - if hn.logFile != nil { - hn.logFile.Close() - - newFileName := fmt.Sprintf("%v.log", - getFinalizedLogFilePrefix(), - ) - - renameFile(fileName, newFileName) - } - } - - // Create file if not exists, otherwise append. - file, err := os.OpenFile(fileName, - os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666) + fileName, err = addLogFile(hn) if err != nil { return err } - - // Pass node's stderr to both errb and the file. - w := io.MultiWriter(&errb, file) - hn.cmd.Stderr = w - - // Pass the node's stdout only to the file. - hn.cmd.Stdout = file - - // Let the node keep a reference to this file, such - // that we can add to it if necessary. - hn.logFile = file } if err := hn.cmd.Start(); err != nil { @@ -825,30 +620,42 @@ func (hn *HarnessNode) start(lndBinary string, lndError chan<- error, // Launch a new goroutine which that bubbles up any potential fatal // process errors to the goroutine running the tests. - hn.processExit = make(chan struct{}) hn.wg.Add(1) go func() { defer hn.wg.Done() err := hn.cmd.Wait() if err != nil { - lndError <- errors.Errorf("%v\n%v\n", err, errb.String()) + lndError <- fmt.Errorf("%v\n%v", err, errb.String()) } - // Signal any onlookers that this process has exited. - close(hn.processExit) - // Make sure log file is closed and renamed if necessary. - finalizeLogfile() + finalizeLogfile(hn, fileName) // Rename the etcd.log file if the node was running on embedded // etcd. - finalizeEtcdLog() + finalizeEtcdLog(hn) }() - // Write process ID to a file. - if err := hn.writePidFile(); err != nil { - hn.cmd.Process.Kill() + return nil +} + +// Start launches a new process running lnd. Additionally, the PID of the +// launched process is saved in order to possibly kill the process forcibly +// later. +// +// This may not clean up properly if an error is returned, so the caller should +// call shutdown() regardless of the return value. +func (hn *HarnessNode) start(lndBinary string, lndError chan<- error, + wait bool) error { + + // Init the runCtx. + ctxt, cancel := context.WithCancel(context.Background()) + hn.runCtx = ctxt + hn.cancel = cancel + + // Start lnd and prepare logs. + if err := hn.startLnd(lndBinary, lndError); err != nil { return err } @@ -858,16 +665,24 @@ func (hn *HarnessNode) start(lndBinary string, lndError chan<- error, return nil } - // Since Stop uses the LightningClient to stop the node, if we fail to get a - // connected client, we have to kill the process. + // Since Stop uses the LightningClient to stop the node, if we fail to + // get a connected client, we have to kill the process. useMacaroons := !hn.Cfg.HasSeed conn, err := hn.ConnectRPC(useMacaroons) if err != nil { - hn.cmd.Process.Kill() + err = fmt.Errorf("ConnectRPC err: %w", err) + cmdErr := hn.cmd.Process.Kill() + if cmdErr != nil { + err = fmt.Errorf("kill process got err: %w: %v", + cmdErr, err) + } return err } - if err := hn.WaitUntilStarted(conn, DefaultTimeout); err != nil { + // Init all the RPC clients. + hn.InitRPCClients(conn) + + if err := hn.WaitUntilStarted(); err != nil { return err } @@ -876,98 +691,60 @@ func (hn *HarnessNode) start(lndBinary string, lndError chan<- error, // only use the TLS certs, and can only perform operations necessary to // unlock the daemon. if hn.Cfg.HasSeed { + // TODO(yy): remove hn.WalletUnlockerClient = lnrpc.NewWalletUnlockerClient(conn) return nil } - return hn.initLightningClient(conn) + return hn.initLightningClient() } // WaitUntilStarted waits until the wallet state flips from "WAITING_TO_START". -func (hn *HarnessNode) WaitUntilStarted(conn grpc.ClientConnInterface, - timeout time.Duration) error { - - return hn.waitForState(conn, timeout, func(s lnrpc.WalletState) bool { +func (hn *HarnessNode) WaitUntilStarted() error { + return hn.waitTillServerState(func(s lnrpc.WalletState) bool { return s != lnrpc.WalletState_WAITING_TO_START }) } // WaitUntilStateReached waits until the given wallet state (or one of the // states following it) has been reached. -func (hn *HarnessNode) WaitUntilStateReached(conn grpc.ClientConnInterface, - timeout time.Duration, desiredState lnrpc.WalletState) error { +func (hn *HarnessNode) WaitUntilStateReached( + desiredState lnrpc.WalletState) error { - return hn.waitForState(conn, timeout, func(s lnrpc.WalletState) bool { + return hn.waitTillServerState(func(s lnrpc.WalletState) bool { return s >= desiredState }) } -// waitForState waits until the current node state fulfills the given -// predicate. -func (hn *HarnessNode) waitForState(conn grpc.ClientConnInterface, - timeout time.Duration, - predicate func(state lnrpc.WalletState) bool) error { - - stateClient := lnrpc.NewStateClient(conn) - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - stateStream, err := stateClient.SubscribeState( - ctx, &lnrpc.SubscribeStateRequest{}, - ) - if err != nil { - return err - } - - errChan := make(chan error, 1) - started := make(chan struct{}) - go func() { - for { - resp, err := stateStream.Recv() - if err != nil { - errChan <- err - return - } - - if predicate(resp.State) { - close(started) - return - } - } - }() - - select { - - case <-started: - case err = <-errChan: - - case <-time.After(timeout): - return fmt.Errorf("WaitUntilLeader timed out") - } - - return err +// WaitUntilServerActive waits until the lnd daemon is fully started. +func (hn *HarnessNode) WaitUntilServerActive() error { + return hn.waitTillServerState(func(s lnrpc.WalletState) bool { + return s == lnrpc.WalletState_SERVER_ACTIVE + }) } // WaitUntilLeader attempts to finish the start procedure by initiating an RPC // connection and setting up the wallet unlocker client. This is needed when // a node that has recently been started was waiting to become the leader and -// we're at the point when we expect that it is the leader now (awaiting unlock). +// we're at the point when we expect that it is the leader now (awaiting +// unlock). func (hn *HarnessNode) WaitUntilLeader(timeout time.Duration) error { var ( conn *grpc.ClientConn connErr error ) - startTs := time.Now() if err := wait.NoError(func() error { conn, connErr = hn.ConnectRPC(!hn.Cfg.HasSeed) return connErr }, timeout); err != nil { return err } - timeout -= time.Since(startTs) - if err := hn.WaitUntilStarted(conn, timeout); err != nil { + // Init all the RPC clients. + hn.InitRPCClients(conn) + + if err := hn.WaitUntilStarted(); err != nil { return err } @@ -976,71 +753,79 @@ func (hn *HarnessNode) WaitUntilLeader(timeout time.Duration) error { // only use the TLS certs, and can only perform operations necessary to // unlock the daemon. if hn.Cfg.HasSeed { + // TODO(yy): remove hn.WalletUnlockerClient = lnrpc.NewWalletUnlockerClient(conn) + return nil } - return hn.initLightningClient(conn) + return hn.initLightningClient() } // initClientWhenReady waits until the main gRPC server is detected as active, -// then complete the normal HarnessNode gRPC connection creation. This can be -// used it a node has just been unlocked, or has its wallet state initialized. -func (hn *HarnessNode) initClientWhenReady(timeout time.Duration) error { +// then complete the normal HarnessNode gRPC connection creation. If the node +// is initialized stateless, the macaroon is returned so that the client can +// use it. +func (hn *HarnessNode) initClientWhenReady(stateless bool, + macBytes []byte) error { + + // Wait for the wallet to finish unlocking, such that we can connect to + // it via a macaroon-authenticated rpc connection. var ( - conn *grpc.ClientConn - connErr error + conn *grpc.ClientConn + err error ) - if err := wait.NoError(func() error { - conn, connErr = hn.ConnectRPC(true) - return connErr - }, timeout); err != nil { + if err = wait.NoError(func() error { + // If the node has been initialized stateless, we need to pass + // the macaroon to the client. + if stateless { + adminMac := &macaroon.Macaroon{} + err := adminMac.UnmarshalBinary(macBytes) + if err != nil { + return fmt.Errorf("unmarshal failed: %w", err) + } + conn, err = hn.ConnectRPCWithMacaroon(adminMac) + return err + } + + // Normal initialization, we expect a macaroon to be in the + // file system. + conn, err = hn.ConnectRPC(true) return err + }, DefaultTimeout); err != nil { + return fmt.Errorf("timeout while init client: %w", err) } - return hn.initLightningClient(conn) + // Init all the RPC clients. + hn.InitRPCClients(conn) + + return hn.initLightningClient() } // Init initializes a harness node by passing the init request via rpc. After // the request is submitted, this method will block until a -// macaroon-authenticated RPC connection can be established to the harness node. -// Once established, the new connection is used to initialize the +// macaroon-authenticated RPC connection can be established to the harness +// node. Once established, the new connection is used to initialize the // LightningClient and subscribes the HarnessNode to topology changes. -func (hn *HarnessNode) Init(ctx context.Context, +func (hn *HarnessNode) Init( initReq *lnrpc.InitWalletRequest) (*lnrpc.InitWalletResponse, error) { - ctxt, cancel := context.WithTimeout(ctx, DefaultTimeout) + ctxt, cancel := context.WithTimeout(hn.runCtx, DefaultTimeout) defer cancel() - response, err := hn.InitWallet(ctxt, initReq) + + response, err := hn.rpc.WalletUnlocker.InitWallet(ctxt, initReq) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to init wallet: %w", err) } - // Wait for the wallet to finish unlocking, such that we can connect to - // it via a macaroon-authenticated rpc connection. - var conn *grpc.ClientConn - if err = wait.Predicate(func() bool { - // If the node has been initialized stateless, we need to pass - // the macaroon to the client. - if initReq.StatelessInit { - adminMac := &macaroon.Macaroon{} - err := adminMac.UnmarshalBinary(response.AdminMacaroon) - if err != nil { - return false - } - conn, err = hn.ConnectRPCWithMacaroon(adminMac) - return err == nil - } - - // Normal initialization, we expect a macaroon to be in the - // file system. - conn, err = hn.ConnectRPC(true) - return err == nil - }, DefaultTimeout); err != nil { - return nil, err + err = hn.initClientWhenReady( + initReq.StatelessInit, response.AdminMacaroon, + ) + if err != nil { + return nil, fmt.Errorf("failed to init: %w", err) } - return response, hn.initLightningClient(conn) + return response, nil } // InitChangePassword initializes a harness node by passing the change password @@ -1048,100 +833,121 @@ func (hn *HarnessNode) Init(ctx context.Context, // a macaroon-authenticated RPC connection can be established to the harness // node. Once established, the new connection is used to initialize the // LightningClient and subscribes the HarnessNode to topology changes. -func (hn *HarnessNode) InitChangePassword(ctx context.Context, +func (hn *HarnessNode) InitChangePassword( chngPwReq *lnrpc.ChangePasswordRequest) (*lnrpc.ChangePasswordResponse, error) { - ctxt, cancel := context.WithTimeout(ctx, DefaultTimeout) + ctxt, cancel := context.WithTimeout(hn.runCtx, DefaultTimeout) defer cancel() - response, err := hn.ChangePassword(ctxt, chngPwReq) + + response, err := hn.rpc.WalletUnlocker.ChangePassword(ctxt, chngPwReq) if err != nil { return nil, err } - - // Wait for the wallet to finish unlocking, such that we can connect to - // it via a macaroon-authenticated rpc connection. - var conn *grpc.ClientConn - if err = wait.Predicate(func() bool { - // If the node has been initialized stateless, we need to pass - // the macaroon to the client. - if chngPwReq.StatelessInit { - adminMac := &macaroon.Macaroon{} - err := adminMac.UnmarshalBinary(response.AdminMacaroon) - if err != nil { - return false - } - conn, err = hn.ConnectRPCWithMacaroon(adminMac) - return err == nil - } - - // Normal initialization, we expect a macaroon to be in the - // file system. - conn, err = hn.ConnectRPC(true) - return err == nil - }, DefaultTimeout); err != nil { + err = hn.initClientWhenReady( + chngPwReq.StatelessInit, response.AdminMacaroon, + ) + if err != nil { return nil, err } - return response, hn.initLightningClient(conn) + return response, nil } // Unlock attempts to unlock the wallet of the target HarnessNode. This method // should be called after the restart of a HarnessNode that was created with a // seed+password. Once this method returns, the HarnessNode will be ready to // accept normal gRPC requests and harness command. -func (hn *HarnessNode) Unlock(ctx context.Context, - unlockReq *lnrpc.UnlockWalletRequest) error { - - ctxt, _ := context.WithTimeout(ctx, DefaultTimeout) +func (hn *HarnessNode) Unlock(unlockReq *lnrpc.UnlockWalletRequest) error { + ctxt, cancel := context.WithTimeout(hn.runCtx, DefaultTimeout) + defer cancel() // Otherwise, we'll need to unlock the node before it's able to start // up properly. - if _, err := hn.UnlockWallet(ctxt, unlockReq); err != nil { + _, err := hn.rpc.WalletUnlocker.UnlockWallet(ctxt, unlockReq) + if err != nil { return err } // Now that the wallet has been unlocked, we'll wait for the RPC client // to be ready, then establish the normal gRPC connection. - return hn.initClientWhenReady(DefaultTimeout) + return hn.initClientWhenReady(false, nil) } -// waitTillServerStarted makes a subscription to the server's state change and -// blocks until the server is in state ServerActive. -func (hn *HarnessNode) waitTillServerStarted() error { - ctxb := context.Background() - ctxt, cancel := context.WithTimeout(ctxb, NodeStartTimeout) +// waitTillServerState makes a subscription to the server's state change and +// blocks until the server is in the targeted state. +func (hn *HarnessNode) waitTillServerState( + predicate func(state lnrpc.WalletState) bool) error { + + ctxt, cancel := context.WithTimeout(hn.runCtx, NodeStartTimeout) defer cancel() - client, err := hn.StateClient.SubscribeState( + client, err := hn.rpc.State.SubscribeState( ctxt, &lnrpc.SubscribeStateRequest{}, ) if err != nil { return fmt.Errorf("failed to subscribe to state: %w", err) } - for { - resp, err := client.Recv() - if err != nil { - return fmt.Errorf("failed to receive state "+ - "client stream: %w", err) + errChan := make(chan error, 1) + done := make(chan struct{}) + go func() { + for { + resp, err := client.Recv() + if err != nil { + errChan <- err + return + } + + if predicate(resp.State) { + close(done) + return + } } + }() + + var lastErr error + for { + select { + case err := <-errChan: + lastErr = err - if resp.State == lnrpc.WalletState_SERVER_ACTIVE { + case <-done: return nil + + case <-time.After(NodeStartTimeout): + return fmt.Errorf("timeout waiting for state, "+ + "got err from stream: %v", lastErr) } } +} +// InitRPCClients initializes a list of RPC clients for the node. +func (hn *HarnessNode) InitRPCClients(c *grpc.ClientConn) { + hn.rpc = &RPCClients{ + conn: c, + LN: lnrpc.NewLightningClient(c), + Invoice: invoicesrpc.NewInvoicesClient(c), + Router: routerrpc.NewRouterClient(c), + WalletKit: walletrpc.NewWalletKitClient(c), + WalletUnlocker: lnrpc.NewWalletUnlockerClient(c), + Watchtower: watchtowerrpc.NewWatchtowerClient(c), + WatchtowerClient: wtclientrpc.NewWatchtowerClientClient(c), + Signer: signrpc.NewSignerClient(c), + State: lnrpc.NewStateClient(c), + ChainClient: chainrpc.NewChainNotifierClient(c), + } } -// initLightningClient constructs the grpc LightningClient from the given client -// connection and subscribes the harness node to graph topology updates. -// This method also spawns a lightning network watcher for this node, -// which watches for topology changes. -func (hn *HarnessNode) initLightningClient(conn *grpc.ClientConn) error { +// initLightningClient blocks until the lnd server is fully started and +// subscribes the harness node to graph topology updates. This method also +// spawns a lightning network watcher for this node, which watches for topology +// changes. +func (hn *HarnessNode) initLightningClient() error { + // TODO(yy): remove // Construct the LightningClient that will allow us to use the // HarnessNode directly for normal rpc operations. - hn.conn = conn + conn := hn.rpc.conn hn.LightningClient = lnrpc.NewLightningClient(conn) hn.InvoicesClient = invoicesrpc.NewInvoicesClient(conn) hn.RouterClient = routerrpc.NewRouterClient(conn) @@ -1149,17 +955,18 @@ func (hn *HarnessNode) initLightningClient(conn *grpc.ClientConn) error { hn.Watchtower = watchtowerrpc.NewWatchtowerClient(conn) hn.WatchtowerClient = wtclientrpc.NewWatchtowerClientClient(conn) hn.SignerClient = signrpc.NewSignerClient(conn) + hn.PeersClient = peersrpc.NewPeersClient(conn) hn.StateClient = lnrpc.NewStateClient(conn) + hn.ChainClient = chainrpc.NewChainNotifierClient(conn) // Wait until the server is fully started. - if err := hn.waitTillServerStarted(); err != nil { + if err := hn.WaitUntilServerActive(); err != nil { return err } // Set the harness node's pubkey to what the node claims in GetInfo. - // Since the RPC might not be immediately active, we wrap the call in a - // wait.NoError. - if err := wait.NoError(hn.FetchNodeInfo, DefaultTimeout); err != nil { + // The RPC must have been started at this point. + if err := hn.FetchNodeInfo(); err != nil { return err } @@ -1174,8 +981,7 @@ func (hn *HarnessNode) initLightningClient(conn *grpc.ClientConn) error { // FetchNodeInfo queries an unlocked node to retrieve its public key. func (hn *HarnessNode) FetchNodeInfo() error { // Obtain the lnid of this node for quick identification purposes. - ctxb := context.Background() - info, err := hn.GetInfo(ctxb, &lnrpc.GetInfoRequest{}) + info, err := hn.rpc.LN.GetInfo(hn.runCtx, &lnrpc.GetInfoRequest{}) if err != nil { return err } @@ -1191,9 +997,9 @@ func (hn *HarnessNode) FetchNodeInfo() error { return nil } -// AddToLog adds a line of choice to the node's logfile. This is useful +// AddToLogf adds a line of choice to the node's logfile. This is useful // to interleave test output with output from the node. -func (hn *HarnessNode) AddToLog(format string, a ...interface{}) { +func (hn *HarnessNode) AddToLogf(format string, a ...interface{}) { // If this node was not set up with a log file, just return early. if hn.logFile == nil { return @@ -1201,29 +1007,10 @@ func (hn *HarnessNode) AddToLog(format string, a ...interface{}) { desc := fmt.Sprintf("itest: %s\n", fmt.Sprintf(format, a...)) if _, err := hn.logFile.WriteString(desc); err != nil { - hn.PrintErr("write to log err: %v", err) + hn.PrintErrf("write to log err: %v", err) } } -// writePidFile writes the process ID of the running lnd process to a .pid file. -func (hn *HarnessNode) writePidFile() error { - filePath := filepath.Join(hn.Cfg.BaseDir, fmt.Sprintf("%v.pid", hn.NodeID)) - - pid, err := os.Create(filePath) - if err != nil { - return err - } - defer pid.Close() - - _, err = fmt.Fprintf(pid, "%v\n", hn.cmd.Process.Pid) - if err != nil { - return err - } - - hn.pidFile = filePath - return nil -} - // ReadMacaroon waits a given duration for the macaroon file to be created. If // the file is readable within the timeout, its content is de-serialized as a // macaroon and returned. @@ -1236,7 +1023,8 @@ func (hn *HarnessNode) ReadMacaroon(macPath string, timeout time.Duration) ( err := wait.NoError(func() error { macBytes, err := ioutil.ReadFile(macPath) if err != nil { - return fmt.Errorf("error reading macaroon file: %v", err) + return fmt.Errorf("error reading macaroon file: %v", + err) } newMac := &macaroon.Macaroon{} @@ -1276,7 +1064,7 @@ func (hn *HarnessNode) ConnectRPCWithMacaroon(mac *macaroon.Macaroon) ( grpc.WithTransportCredentials(tlsCreds), } - ctx, cancel := context.WithTimeout(context.Background(), DefaultTimeout) + ctx, cancel := context.WithTimeout(hn.runCtx, DefaultTimeout) defer cancel() if mac == nil { @@ -1320,7 +1108,8 @@ func (hn *HarnessNode) cleanup() error { if hn.backupDbDir != "" { err := os.RemoveAll(hn.backupDbDir) if err != nil { - return fmt.Errorf("unable to remove backup dir: %v", err) + return fmt.Errorf("unable to remove backup dir: %v", + err) } } @@ -1330,26 +1119,27 @@ func (hn *HarnessNode) cleanup() error { // Stop attempts to stop the active lnd process. func (hn *HarnessNode) stop() error { // Do nothing if the process is not running. - if hn.processExit == nil { + if hn.runCtx == nil { return nil } - // If start() failed before creating a client, we will just wait for the + // If start() failed before creating clients, we will just wait for the // child process to die. - if hn.LightningClient != nil { - // Don't watch for error because sometimes the RPC connection gets - // closed before a response is returned. + if hn.rpc != nil && hn.rpc.LN != nil { + // Don't watch for error because sometimes the RPC connection + // gets closed before a response is returned. req := lnrpc.StopRequest{} - ctx := context.Background() err := wait.NoError(func() error { - _, err := hn.LightningClient.StopDaemon(ctx, &req) + _, err := hn.rpc.LN.StopDaemon(hn.runCtx, &req) switch { case err == nil: return nil // Try again if a recovery/rescan is in progress. - case strings.Contains(err.Error(), "recovery in progress"): + case strings.Contains( + err.Error(), "recovery in progress", + ): return err default: @@ -1361,39 +1151,60 @@ func (hn *HarnessNode) stop() error { } } - // Wait for lnd process and other goroutines to exit. - select { - case <-hn.processExit: - case <-time.After(DefaultTimeout * 2): - return fmt.Errorf("process did not exit") - } + // Stop the runCtx and wait for goroutines to finish. + hn.cancel() + + // Wait for lnd process to exit. + err := wait.NoError(func() error { + if hn.cmd.ProcessState == nil { + return fmt.Errorf("process did not exit") + } + + if !hn.cmd.ProcessState.Exited() { + return fmt.Errorf("process did not exit") + } - close(hn.quit) - hn.wg.Wait() + // Wait for goroutines to be finished. + hn.wg.Wait() + + return nil + }, DefaultTimeout*2) + if err != nil { + return err + } - hn.quit = nil - hn.processExit = nil hn.LightningClient = nil hn.WalletUnlockerClient = nil hn.Watchtower = nil hn.WatchtowerClient = nil // Close any attempts at further grpc connections. - if hn.conn != nil { - err := hn.conn.Close() - if err != nil && - !strings.Contains(err.Error(), "connection is closing") { + if hn.rpc.conn != nil { + err := status.Code(hn.rpc.conn.Close()) + switch err { + case codes.OK: + return nil - return fmt.Errorf("error attempting to stop grpc "+ - "client: %v", err) + // When the context is canceled above, we might get the + // following error as the context is no longer active. + case codes.Canceled: + return nil + + case codes.Unknown: + return fmt.Errorf("unknown error attempting to stop "+ + "grpc client: %v", err) + + default: + return fmt.Errorf("error attempting to stop "+ + "grpc client: %v", err) } } return nil } -// shutdown stops the active lnd process and cleans up any temporary directories -// created along the way. +// shutdown stops the active lnd process and cleans up any temporary +// directories created along the way. func (hn *HarnessNode) shutdown() error { if err := hn.stop(); err != nil { return err @@ -1404,7 +1215,7 @@ func (hn *HarnessNode) shutdown() error { return nil } -// kill kills the lnd process +// kill kills the lnd process. func (hn *HarnessNode) kill() error { return hn.cmd.Process.Kill() } @@ -1440,35 +1251,11 @@ type chanWatchRequest struct { includeUnannounced bool } -// getChanPointFundingTxid returns the given channel point's funding txid in -// raw bytes. -func getChanPointFundingTxid(chanPoint *lnrpc.ChannelPoint) ([]byte, error) { - var txid []byte - - // A channel point's funding txid can be get/set as a byte slice or a - // string. In the case it is a string, decode it. - switch chanPoint.GetFundingTxid().(type) { - case *lnrpc.ChannelPoint_FundingTxidBytes: - txid = chanPoint.GetFundingTxidBytes() - case *lnrpc.ChannelPoint_FundingTxidStr: - s := chanPoint.GetFundingTxidStr() - h, err := chainhash.NewHashFromStr(s) - if err != nil { - return nil, err - } - - txid = h[:] - } - - return txid, nil -} - -func checkChanPointInGraph(ctx context.Context, - node *HarnessNode, chanPoint wire.OutPoint) bool { - - ctxt, cancel := context.WithTimeout(ctx, DefaultTimeout) +func (hn *HarnessNode) checkChanPointInGraph(chanPoint wire.OutPoint) bool { + ctxt, cancel := context.WithTimeout(hn.runCtx, DefaultTimeout) defer cancel() - chanGraph, err := node.DescribeGraph(ctxt, &lnrpc.ChannelGraphRequest{}) + + chanGraph, err := hn.DescribeGraph(ctxt, &lnrpc.ChannelGraphRequest{}) if err != nil { return false } @@ -1499,15 +1286,15 @@ func (hn *HarnessNode) lightningNetworkWatcher() { go func() { defer hn.wg.Done() err := hn.receiveTopologyClientStream(graphUpdates) + if err != nil { - hn.PrintErr("receive topology client stream "+ + hn.PrintErrf("receive topology client stream "+ "got err:%v", err) } }() for { select { - // A new graph update has just been received, so we'll examine // the current set of registered clients to see if we can // dispatch any requests. @@ -1533,7 +1320,7 @@ func (hn *HarnessNode) lightningNetworkWatcher() { hn.handlePolicyUpdateWatchRequest(watchRequest) } - case <-hn.quit: + case <-hn.runCtx.Done(): return } } @@ -1543,9 +1330,12 @@ func (hn *HarnessNode) lightningNetworkWatcher() { // outpoint is seen as being fully advertised within the network. A channel is // considered "fully advertised" once both of its directional edges has been // advertised within the test Lightning Network. -func (hn *HarnessNode) WaitForNetworkChannelOpen(ctx context.Context, +func (hn *HarnessNode) WaitForNetworkChannelOpen( chanPoint *lnrpc.ChannelPoint) error { + ctxt, cancel := context.WithTimeout(hn.runCtx, DefaultTimeout) + defer cancel() + eventChan := make(chan struct{}) op, err := MakeOutpoint(chanPoint) @@ -1563,7 +1353,7 @@ func (hn *HarnessNode) WaitForNetworkChannelOpen(ctx context.Context, select { case <-eventChan: return nil - case <-ctx.Done(): + case <-ctxt.Done(): return fmt.Errorf("channel:%s not opened before timeout: %s", op, hn) } @@ -1573,9 +1363,12 @@ func (hn *HarnessNode) WaitForNetworkChannelOpen(ctx context.Context, // outpoint is seen as closed within the network. A channel is considered // closed once a transaction spending the funding outpoint is seen within a // confirmed block. -func (hn *HarnessNode) WaitForNetworkChannelClose(ctx context.Context, +func (hn *HarnessNode) WaitForNetworkChannelClose( chanPoint *lnrpc.ChannelPoint) error { + ctxt, cancel := context.WithTimeout(hn.runCtx, DefaultTimeout) + defer cancel() + eventChan := make(chan struct{}) op, err := MakeOutpoint(chanPoint) @@ -1593,7 +1386,7 @@ func (hn *HarnessNode) WaitForNetworkChannelClose(ctx context.Context, select { case <-eventChan: return nil - case <-ctx.Done(): + case <-ctxt.Done(): return fmt.Errorf("channel:%s not closed before timeout: "+ "%s", op, hn) } @@ -1601,10 +1394,13 @@ func (hn *HarnessNode) WaitForNetworkChannelClose(ctx context.Context, // WaitForChannelPolicyUpdate will block until a channel policy with the target // outpoint and advertisingNode is seen within the network. -func (hn *HarnessNode) WaitForChannelPolicyUpdate(ctx context.Context, +func (hn *HarnessNode) WaitForChannelPolicyUpdate( advertisingNode string, policy *lnrpc.RoutingPolicy, chanPoint *lnrpc.ChannelPoint, includeUnannounced bool) error { + ctxt, cancel := context.WithTimeout(hn.runCtx, DefaultTimeout) + defer cancel() + eventChan := make(chan struct{}) op, err := MakeOutpoint(chanPoint) @@ -1641,7 +1437,7 @@ func (hn *HarnessNode) WaitForChannelPolicyUpdate(ctx context.Context, case <-eventChan: return nil - case <-ctx.Done(): + case <-ctxt.Done(): return fmt.Errorf("channel:%s policy not updated "+ "before timeout: [%s:%v] %s", op, advertisingNode, policy, hn.String()) @@ -1649,16 +1445,19 @@ func (hn *HarnessNode) WaitForChannelPolicyUpdate(ctx context.Context, } } -// WaitForBlockchainSync waits for the target node to be fully synchronized with -// the blockchain. If the passed context object has a set timeout, it will +// WaitForBlockchainSync waits for the target node to be fully synchronized +// with the blockchain. If the passed context object has a set timeout, it will // continually poll until the timeout has elapsed. In the case that the chain // isn't synced before the timeout is up, this function will return an error. -func (hn *HarnessNode) WaitForBlockchainSync(ctx context.Context) error { +func (hn *HarnessNode) WaitForBlockchainSync() error { + ctxt, cancel := context.WithTimeout(hn.runCtx, DefaultTimeout) + defer cancel() + ticker := time.NewTicker(time.Millisecond * 100) defer ticker.Stop() for { - resp, err := hn.GetInfo(ctx, &lnrpc.GetInfoRequest{}) + resp, err := hn.rpc.LN.GetInfo(ctxt, &lnrpc.GetInfoRequest{}) if err != nil { return err } @@ -1667,10 +1466,10 @@ func (hn *HarnessNode) WaitForBlockchainSync(ctx context.Context) error { } select { - case <-ctx.Done(): + case <-ctxt.Done(): return fmt.Errorf("timeout while waiting for " + "blockchain sync") - case <-hn.quit: + case <-hn.runCtx.Done(): return nil case <-ticker.C: } @@ -1679,54 +1478,45 @@ func (hn *HarnessNode) WaitForBlockchainSync(ctx context.Context) error { // WaitForBalance waits until the node sees the expected confirmed/unconfirmed // balance within their wallet. -func (hn *HarnessNode) WaitForBalance(expectedBalance btcutil.Amount, confirmed bool) error { - ctx := context.Background() +func (hn *HarnessNode) WaitForBalance(expectedBalance btcutil.Amount, + confirmed bool) error { + req := &lnrpc.WalletBalanceRequest{} var lastBalance btcutil.Amount doesBalanceMatch := func() bool { - balance, err := hn.WalletBalance(ctx, req) + balance, err := hn.rpc.LN.WalletBalance(hn.runCtx, req) if err != nil { return false } if confirmed { lastBalance = btcutil.Amount(balance.ConfirmedBalance) - return btcutil.Amount(balance.ConfirmedBalance) == expectedBalance + return btcutil.Amount(balance.ConfirmedBalance) == + expectedBalance } lastBalance = btcutil.Amount(balance.UnconfirmedBalance) - return btcutil.Amount(balance.UnconfirmedBalance) == expectedBalance + return btcutil.Amount(balance.UnconfirmedBalance) == + expectedBalance } err := wait.Predicate(doesBalanceMatch, DefaultTimeout) if err != nil { return fmt.Errorf("balances not synced after deadline: "+ - "expected %v, only have %v", expectedBalance, lastBalance) + "expected %v, only have %v", expectedBalance, + lastBalance) } return nil } -// PrintErr prints an error to the console. -func (hn *HarnessNode) PrintErr(format string, a ...interface{}) { - fmt.Printf("itest error from [node:%s]: %s\n", +// PrintErrf prints an error to the console. +func (hn *HarnessNode) PrintErrf(format string, a ...interface{}) { + fmt.Printf("itest error from [node:%s]: %s\n", // nolint:forbidigo hn.Cfg.Name, fmt.Sprintf(format, a...)) } -// MakeOutpoint returns the outpoint of the channel's funding transaction. -func MakeOutpoint(chanPoint *lnrpc.ChannelPoint) (wire.OutPoint, error) { - fundingTxID, err := lnrpc.GetChanPointFundingTxid(chanPoint) - if err != nil { - return wire.OutPoint{}, err - } - - return wire.OutPoint{ - Hash: *fundingTxID, - Index: chanPoint.OutputIndex, - }, nil -} - // handleChannelEdgeUpdates takes a series of channel edge updates, extracts // the outpoints, and saves them to harness node's internal state. func (hn *HarnessNode) handleChannelEdgeUpdates( @@ -1737,7 +1527,7 @@ func (hn *HarnessNode) handleChannelEdgeUpdates( for _, newChan := range updates { op, err := MakeOutpoint(newChan.ChanPoint) if err != nil { - hn.PrintErr("failed to create outpoint for %v "+ + hn.PrintErrf("failed to create outpoint for %v "+ "got err: %v", newChan.ChanPoint, err) return } @@ -1798,7 +1588,7 @@ func (hn *HarnessNode) handleOpenChannelWatchRequest(req *chanWatchRequest) { // node. This lets us handle the case where a node has already seen a // channel before a notification has been requested, causing us to miss // it. - chanFound := checkChanPointInGraph(context.Background(), hn, targetChan) + chanFound := hn.checkChanPointInGraph(targetChan) if chanFound { close(req.eventChan) return @@ -1823,7 +1613,7 @@ func (hn *HarnessNode) handleClosedChannelUpdate( for _, closedChan := range updates { op, err := MakeOutpoint(closedChan.ChanPoint) if err != nil { - hn.PrintErr("failed to create outpoint for %v "+ + hn.PrintErrf("failed to create outpoint for %v "+ "got err: %v", closedChan.ChanPoint, err) return } @@ -1868,7 +1658,7 @@ func (hn *HarnessNode) newTopologyClient( ctx context.Context) (topologyClient, error) { req := &lnrpc.GraphTopologySubscription{} - client, err := hn.SubscribeChannelGraph(ctx, req) + client, err := hn.rpc.LN.SubscribeChannelGraph(ctx, req) if err != nil { return nil, fmt.Errorf("%s(%d): unable to create topology "+ "client: %v (%s)", hn.Name(), hn.NodeID, err, @@ -1888,16 +1678,14 @@ func (hn *HarnessNode) newTopologyClient( func (hn *HarnessNode) receiveTopologyClientStream( receiver chan *lnrpc.GraphTopologyUpdate) error { - ctxb := context.Background() - // Create a topology client to receive graph updates. - client, err := hn.newTopologyClient(ctxb) + client, err := hn.newTopologyClient(hn.runCtx) if err != nil { - return fmt.Errorf("create topologyClient failed: %v", err) + return fmt.Errorf("create topologyClient failed: %w", err) } // We use the context to time out when retrying graph subscription. - ctxt, cancel := context.WithTimeout(ctxb, DefaultTimeout) + ctxt, cancel := context.WithTimeout(hn.runCtx, DefaultTimeout) defer cancel() for { @@ -1916,12 +1704,12 @@ func (hn *HarnessNode) receiveTopologyClientStream( return fmt.Errorf("graph subscription: " + "router not started before timeout") case <-time.After(wait.PollInterval): - case <-hn.quit: + case <-hn.runCtx.Done(): return nil } // Re-create the topology client. - client, err = hn.newTopologyClient(ctxb) + client, err = hn.newTopologyClient(hn.runCtx) if err != nil { return fmt.Errorf("create topologyClient "+ "failed: %v", err) @@ -1933,52 +1721,25 @@ func (hn *HarnessNode) receiveTopologyClientStream( // End of subscription stream. Do nothing and quit. return nil + case strings.Contains(err.Error(), context.Canceled.Error()): + // End of subscription stream. Do nothing and quit. + return nil + default: // An expected error is returned, return and leave it // to be handled by the caller. - return fmt.Errorf("graph subscription err: %v", err) + return fmt.Errorf("graph subscription err: %w", err) } // Send the update or quit. select { case receiver <- update: - case <-hn.quit: + case <-hn.runCtx.Done(): return nil } } } -// CheckChannelPolicy checks that the policy matches the expected one. -func CheckChannelPolicy(policy, expectedPolicy *lnrpc.RoutingPolicy) error { - if policy.FeeBaseMsat != expectedPolicy.FeeBaseMsat { - return fmt.Errorf("expected base fee %v, got %v", - expectedPolicy.FeeBaseMsat, policy.FeeBaseMsat) - } - if policy.FeeRateMilliMsat != expectedPolicy.FeeRateMilliMsat { - return fmt.Errorf("expected fee rate %v, got %v", - expectedPolicy.FeeRateMilliMsat, - policy.FeeRateMilliMsat) - } - if policy.TimeLockDelta != expectedPolicy.TimeLockDelta { - return fmt.Errorf("expected time lock delta %v, got %v", - expectedPolicy.TimeLockDelta, - policy.TimeLockDelta) - } - if policy.MinHtlc != expectedPolicy.MinHtlc { - return fmt.Errorf("expected min htlc %v, got %v", - expectedPolicy.MinHtlc, policy.MinHtlc) - } - if policy.MaxHtlcMsat != expectedPolicy.MaxHtlcMsat { - return fmt.Errorf("expected max htlc %v, got %v", - expectedPolicy.MaxHtlcMsat, policy.MaxHtlcMsat) - } - if policy.Disabled != expectedPolicy.Disabled { - return errors.New("edge should be disabled but isn't") - } - - return nil -} - // handlePolicyUpdateWatchRequest checks that if the expected policy can be // found either in the node's interval state or describe graph response. If // found, it will signal the request by closing the event channel. Otherwise it @@ -2012,24 +1773,20 @@ func (hn *HarnessNode) handlePolicyUpdateWatchRequest(req *chanWatchRequest) { // getChannelPolicies queries the channel graph and formats the policies into // the format defined in type policyUpdateMap. func (hn *HarnessNode) getChannelPolicies(include bool) policyUpdateMap { - - ctxt, cancel := context.WithTimeout( - context.Background(), DefaultTimeout, - ) + ctxt, cancel := context.WithTimeout(hn.runCtx, DefaultTimeout) defer cancel() - graph, err := hn.DescribeGraph(ctxt, &lnrpc.ChannelGraphRequest{ + graph, err := hn.rpc.LN.DescribeGraph(ctxt, &lnrpc.ChannelGraphRequest{ IncludeUnannounced: include, }) if err != nil { - hn.PrintErr("DescribeGraph got err: %v", err) + hn.PrintErrf("DescribeGraph got err: %v", err) return nil } policyUpdates := policyUpdateMap{} for _, e := range graph.Edges { - policies := policyUpdates[e.ChanPoint] // If the map[op] is nil, we need to initialize the map first. @@ -2054,3 +1811,96 @@ func (hn *HarnessNode) getChannelPolicies(include bool) policyUpdateMap { return policyUpdates } + +// renameFile is a helper to rename (log) files created during integration +// tests. +func renameFile(fromFileName, toFileName string) { + err := os.Rename(fromFileName, toFileName) + if err != nil { + fmt.Printf("could not rename %s to %s: %v\n", // nolint:forbidigo + fromFileName, toFileName, err) + } +} + +// getFinalizedLogFilePrefix returns the finalize log filename. +func getFinalizedLogFilePrefix(hn *HarnessNode) string { + pubKeyHex := hex.EncodeToString( + hn.PubKey[:logPubKeyBytes], + ) + + return fmt.Sprintf("%s/%d-%s-%s-%s", + GetLogDir(), hn.NodeID, + hn.Cfg.LogFilenamePrefix, + hn.Cfg.Name, pubKeyHex) +} + +// finalizeLogfile makes sure the log file cleanup function is initialized, +// even if no log file is created. +func finalizeLogfile(hn *HarnessNode, fileName string) { + if hn.logFile != nil { + hn.logFile.Close() + + // If logoutput flag is not set, return early. + if !*logOutput { + return + } + + newFileName := fmt.Sprintf("%v.log", + getFinalizedLogFilePrefix(hn), + ) + + renameFile(fileName, newFileName) + } +} + +func finalizeEtcdLog(hn *HarnessNode) { + if hn.Cfg.DbBackend != BackendEtcd { + return + } + + etcdLogFileName := fmt.Sprintf("%s/etcd.log", hn.Cfg.LogDir) + newEtcdLogFileName := fmt.Sprintf("%v-etcd.log", + getFinalizedLogFilePrefix(hn), + ) + + renameFile(etcdLogFileName, newEtcdLogFileName) +} + +func addLogFile(hn *HarnessNode) (string, error) { + var fileName string + + dir := GetLogDir() + fileName = fmt.Sprintf("%s/%d-%s-%s-%s.log", dir, hn.NodeID, + hn.Cfg.LogFilenamePrefix, hn.Cfg.Name, + hex.EncodeToString(hn.PubKey[:logPubKeyBytes])) + + // If the node's PubKey is not yet initialized, create a + // temporary file name. Later, after the PubKey has been + // initialized, the file can be moved to its final name with + // the PubKey included. + if bytes.Equal(hn.PubKey[:4], []byte{0, 0, 0, 0}) { + fileName = fmt.Sprintf("%s/%d-%s-%s-tmp__.log", dir, + hn.NodeID, hn.Cfg.LogFilenamePrefix, + hn.Cfg.Name) + } + + // Create file if not exists, otherwise append. + file, err := os.OpenFile(fileName, + os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666) + if err != nil { + return fileName, err + } + + // Pass node's stderr to both errb and the file. + w := io.MultiWriter(hn.cmd.Stderr, file) + hn.cmd.Stderr = w + + // Pass the node's stdout only to the file. + hn.cmd.Stdout = file + + // Let the node keep a reference to this file, such + // that we can add to it if necessary. + hn.logFile = file + + return fileName, nil +} diff --git a/lntest/itest/assertions.go b/lntest/itest/assertions.go index 071b07c485..dac88010d5 100644 --- a/lntest/itest/assertions.go +++ b/lntest/itest/assertions.go @@ -9,14 +9,14 @@ import ( "testing" "time" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcd/integration/rpctest" "github.com/btcsuite/btcd/rpcclient" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lnrpc/peersrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" "github.com/lightningnetwork/lnd/lntest" @@ -528,7 +528,6 @@ func assertConnected(t *harnessTest, alice, bob *lntest.HarnessNode) { } return nil - }, defaultTimeout) require.NoError(t.t, err) } @@ -571,7 +570,6 @@ func assertNotConnected(t *harnessTest, alice, bob *lntest.HarnessNode) { } return nil - }, defaultTimeout) require.NoError(t.t, err) } @@ -591,9 +589,8 @@ func shutdownAndAssert(net *lntest.NetworkHarness, t *harnessTest, // assertChannelBalanceResp makes a ChannelBalance request and checks the // returned response matches the expected. -func assertChannelBalanceResp(t *harnessTest, - node *lntest.HarnessNode, - expected *lnrpc.ChannelBalanceResponse) { // nolint:interfacer +func assertChannelBalanceResp(t *harnessTest, node *lntest.HarnessNode, + expected *lnrpc.ChannelBalanceResponse) { resp := getChannelBalance(t, node) require.True(t.t, proto.Equal(expected, resp), "balance is incorrect") @@ -691,7 +688,7 @@ func assertChannelPolicy(t *harnessTest, node *lntest.HarnessNode, // assertMinerBlockHeightDelta ensures that tempMiner is 'delta' blocks ahead // of miner. func assertMinerBlockHeightDelta(t *harnessTest, - miner, tempMiner *rpctest.Harness, delta int32) { + miner, tempMiner *lntest.HarnessMiner, delta int32) { // Ensure the chain lengths are what we expect. var predErr error @@ -1007,6 +1004,7 @@ func assertChannelConstraintsEqual( func assertAmountPaid(t *harnessTest, channelName string, node *lntest.HarnessNode, chanPoint wire.OutPoint, amountSent, amountReceived int64) { + ctxb := context.Background() checkAmountPaid := func() error { @@ -1030,6 +1028,7 @@ func assertAmountPaid(t *harnessTest, channelName string, } if channel.TotalSatoshisReceived != amountReceived { + return fmt.Errorf("%v: incorrect amount"+ " received: %v != %v", channelName, @@ -1071,6 +1070,7 @@ func assertAmountPaid(t *harnessTest, channelName string, // node reports the expected number of pending channels. func assertNumPendingChannels(t *harnessTest, node *lntest.HarnessNode, expWaitingClose, expPendingForceClose int) { + ctxb := context.Background() var predErr error @@ -1397,6 +1397,7 @@ func verifyCloseUpdate(chanUpdate *lnrpc.ChannelEventUpdate, case *lnrpc.ChannelEventUpdate_ClosedChannel: if chanUpdate.Type != lnrpc.ChannelEventUpdate_CLOSED_CHANNEL { + return fmt.Errorf("update type mismatch: expected %v, got %v", lnrpc.ChannelEventUpdate_CLOSED_CHANNEL, chanUpdate.Type) @@ -1435,6 +1436,7 @@ func verifyCloseUpdate(chanUpdate *lnrpc.ChannelEventUpdate, // reaches the desired number of total channels. func assertNodeNumChannels(t *harnessTest, node *lntest.HarnessNode, numChannels int) { + ctxb := context.Background() // Poll node for its list of channels. @@ -1629,9 +1631,7 @@ func getSpendingTxInMempool(t *harnessTest, miner *rpcclient.Client, for _, txIn := range msgTx.TxIn { input := txIn.PreviousOutPoint - if _, ok := inputSet[input]; ok { - delete(inputSet, input) - } + delete(inputSet, input) } if len(inputSet) > 0 { @@ -1783,14 +1783,128 @@ func assertChannelPolicyUpdate(t *testing.T, node *lntest.HarnessNode, advertisingNode string, policy *lnrpc.RoutingPolicy, chanPoint *lnrpc.ChannelPoint, includeUnannounced bool) { - ctxb := context.Background() - ctxt, cancel := context.WithTimeout(ctxb, lntest.DefaultTimeout) - defer cancel() - require.NoError( t, node.WaitForChannelPolicyUpdate( - ctxt, advertisingNode, policy, + advertisingNode, policy, chanPoint, includeUnannounced, ), "error while waiting for channel update", ) } + +func transactionInWallet(node *lntest.HarnessNode, txid chainhash.Hash) bool { + txStr := txid.String() + + txResp, err := node.GetTransactions( + context.Background(), &lnrpc.GetTransactionsRequest{}, + ) + if err != nil { + return false + } + + for _, txn := range txResp.Transactions { + if txn.TxHash == txStr { + return true + } + } + + return false +} + +func assertTransactionInWallet(t *testing.T, node *lntest.HarnessNode, txID chainhash.Hash) { + t.Helper() + + err := wait.Predicate(func() bool { + return transactionInWallet(node, txID) + }, defaultTimeout) + require.NoError( + t, err, fmt.Sprintf("transaction %v not found in wallet", txID), + ) +} + +func assertTransactionNotInWallet(t *testing.T, node *lntest.HarnessNode, + txID chainhash.Hash) { + + t.Helper() + + err := wait.Predicate(func() bool { + return !transactionInWallet(node, txID) + }, defaultTimeout) + require.NoError( + t, err, fmt.Sprintf("transaction %v found in wallet", txID), + ) +} + +func assertAnchorOutputLost(t *harnessTest, node *lntest.HarnessNode, + chanPoint wire.OutPoint) { + + pendingChansRequest := &lnrpc.PendingChannelsRequest{} + err := wait.Predicate(func() bool { + resp, pErr := node.PendingChannels( + context.Background(), pendingChansRequest, + ) + if pErr != nil { + return false + } + + for _, pendingChan := range resp.PendingForceClosingChannels { + if pendingChan.Channel.ChannelPoint == chanPoint.String() { + return (pendingChan.Anchor == + lnrpc.PendingChannelsResponse_ForceClosedChannel_LOST) + } + } + + return false + }, defaultTimeout) + require.NoError(t.t, err, "anchor doesn't show as being lost") +} + +// assertNodeAnnouncement compares that two node announcements match. +func assertNodeAnnouncement(t *harnessTest, n1, n2 *lnrpc.NodeUpdate) { + // Alias should match. + require.Equal(t.t, n1.Alias, n2.Alias, "alias don't match") + + // Color should match. + require.Equal(t.t, n1.Color, n2.Color, "color don't match") + + // NodeAddresses should match. + require.Equal( + t.t, len(n1.NodeAddresses), len(n2.NodeAddresses), + "node addresses don't match", + ) + + addrs := make(map[string]struct{}, len(n1.NodeAddresses)) + for _, nodeAddr := range n1.NodeAddresses { + addrs[nodeAddr.Addr] = struct{}{} + } + + for _, nodeAddr := range n2.NodeAddresses { + if _, ok := addrs[nodeAddr.Addr]; !ok { + t.Fatalf("address %v not found in node announcement", + nodeAddr.Addr) + } + } +} + +// assertUpdateNodeAnnouncementResponse is a helper function to assert +// the response expected values. +func assertUpdateNodeAnnouncementResponse(t *harnessTest, + response *peersrpc.NodeAnnouncementUpdateResponse, + expectedOps map[string]int) { + + require.Equal( + t.t, len(response.Ops), len(expectedOps), + "unexpected number of Ops updating dave's node announcement", + ) + + ops := make(map[string]int, len(response.Ops)) + for _, op := range response.Ops { + ops[op.Entity] = len(op.Actions) + } + + for k, v := range expectedOps { + if v != ops[k] { + t.Fatalf("unexpected number of actions for operation "+ + "%s: got %d wanted %d", k, ops[k], v) + } + } +} diff --git a/lntest/itest/lnd_amp_test.go b/lntest/itest/lnd_amp_test.go index 7568191820..2ed33951af 100644 --- a/lntest/itest/lnd_amp_test.go +++ b/lntest/itest/lnd_amp_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/amp" "github.com/lightningnetwork/lnd/chainreg" "github.com/lightningnetwork/lnd/lnrpc" @@ -163,7 +163,7 @@ func testSendPaymentAMPInvoiceCase(net *lntest.NetworkHarness, t *harnessTest, require.NoError(t.t, err) // Also fetch Bob's invoice from ListInvoices and assert it is equal to - // the one recevied via the subscription. + // the one received via the subscription. invoiceResp, err := ctx.bob.ListInvoices( ctxb, &lnrpc.ListInvoiceRequest{}, ) @@ -245,11 +245,9 @@ func testSendPaymentAMPInvoiceRepeat(net *lntest.NetworkHarness, Amt: chanAmt, }, ) - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - err = carol.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = carol.WaitForNetworkChannelOpen(chanPoint) require.NoError(t.t, err, "carol didn't report channel") - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = dave.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = dave.WaitForNetworkChannelOpen(chanPoint) require.NoError(t.t, err, "dave didn't report channel") // Create an AMP invoice of a trivial amount, that we'll pay repeatedly @@ -697,7 +695,7 @@ func testSendToRouteAMP(net *lntest.NetworkHarness, t *harnessTest) { require.NoError(t.t, err) // Also fetch Bob's invoice from ListInvoices and assert it is equal to - // the one recevied via the subscription. + // the one received via the subscription. invoiceResp, err := ctx.bob.ListInvoices( ctxb, &lnrpc.ListInvoiceRequest{}, ) diff --git a/lntest/itest/lnd_channel_backup_test.go b/lntest/itest/lnd_channel_backup_test.go index 3aa355b2ca..91455ddcc5 100644 --- a/lntest/itest/lnd_channel_backup_test.go +++ b/lntest/itest/lnd_channel_backup_test.go @@ -12,8 +12,8 @@ import ( "testing" "time" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/chanbackup" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" @@ -982,12 +982,11 @@ func testChanRestoreScenario(t *harnessTest, net *lntest.NetworkHarness, ) // Wait for both sides to see the opened channel. - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - err = dave.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = dave.WaitForNetworkChannelOpen(chanPoint) if err != nil { t.Fatalf("dave didn't report channel: %v", err) } - err = carol.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = carol.WaitForNetworkChannelOpen(chanPoint) if err != nil { t.Fatalf("carol didn't report channel: %v", err) } @@ -1327,11 +1326,9 @@ func createLegacyRevocationChannel(net *lntest.NetworkHarness, t *harnessTest, } _ = mineBlocks(t, net, 6, 1) - ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) - defer cancel() - err = from.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = from.WaitForNetworkChannelOpen(chanPoint) require.NoError(t.t, err) - err = to.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = to.WaitForNetworkChannelOpen(chanPoint) require.NoError(t.t, err) } @@ -1375,7 +1372,7 @@ func chanRestoreViaRPC(net *lntest.NetworkHarness, password []byte, // copyPorts returns a node option function that copies the ports of an existing // node over to the newly created one. func copyPorts(oldNode *lntest.HarnessNode) lntest.NodeOption { - return func(cfg *lntest.NodeConfig) { + return func(cfg *lntest.BaseNodeConfig) { cfg.P2PPort = oldNode.Cfg.P2PPort cfg.RPCPort = oldNode.Cfg.RPCPort cfg.RESTPort = oldNode.Cfg.RESTPort diff --git a/lntest/itest/lnd_channel_balance_test.go b/lntest/itest/lnd_channel_balance_test.go index d49a276097..f33b26fdbd 100644 --- a/lntest/itest/lnd_channel_balance_test.go +++ b/lntest/itest/lnd_channel_balance_test.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/chainreg" "github.com/lightningnetwork/lnd/funding" "github.com/lightningnetwork/lnd/lnrpc" @@ -18,8 +18,6 @@ import ( // testChannelBalance creates a new channel between Alice and Bob, then checks // channel balance to be equal amount specified while creation of channel. func testChannelBalance(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() - // Open a channel with 0.16 BTC between Alice and Bob, ensuring the // channel has been opened properly. amount := funding.MaxBtcFundingAmount @@ -61,14 +59,12 @@ func testChannelBalance(net *lntest.NetworkHarness, t *harnessTest) { ) // Wait for both Alice and Bob to recognize this new channel. - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - err := net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint) + err := net.Alice.WaitForNetworkChannelOpen(chanPoint) if err != nil { t.Fatalf("alice didn't advertise channel before "+ "timeout: %v", err) } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.Bob.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = net.Bob.WaitForNetworkChannelOpen(chanPoint) if err != nil { t.Fatalf("bob didn't advertise channel before "+ "timeout: %v", err) @@ -155,15 +151,13 @@ func testChannelUnsettledBalance(net *lntest.NetworkHarness, t *harnessTest) { // Wait for Alice and Carol to receive the channel edge from the // funding manager. - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - err := net.Alice.WaitForNetworkChannelOpen(ctxt, chanPointAlice) + err := net.Alice.WaitForNetworkChannelOpen(chanPointAlice) if err != nil { t.Fatalf("alice didn't see the alice->carol channel before "+ "timeout: %v", err) } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = carol.WaitForNetworkChannelOpen(ctxt, chanPointAlice) + err = carol.WaitForNetworkChannelOpen(chanPointAlice) if err != nil { t.Fatalf("alice didn't see the alice->carol channel before "+ "timeout: %v", err) @@ -191,7 +185,7 @@ func testChannelUnsettledBalance(net *lntest.NetworkHarness, t *harnessTest) { errChan := make(chan error) for i := 0; i < numInvoices; i++ { go func() { - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) _, err := net.Alice.RouterClient.SendPaymentV2(ctxt, &routerrpc.SendPaymentRequest{ Dest: carolPubKey, diff --git a/lntest/itest/lnd_channel_force_close_test.go b/lntest/itest/lnd_channel_force_close_test.go index 7225c4a2c5..d36c505352 100644 --- a/lntest/itest/lnd_channel_force_close_test.go +++ b/lntest/itest/lnd_channel_force_close_test.go @@ -7,9 +7,8 @@ import ( "testing" "github.com/btcsuite/btcd/blockchain" - "github.com/btcsuite/btcd/integration/rpctest" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/go-errors/errors" "github.com/lightningnetwork/lnd" "github.com/lightningnetwork/lnd/chainreg" @@ -212,7 +211,7 @@ func testCommitmentTransactionDeadline(net *lntest.NetworkHarness, // calculateTxnsFeeRate takes a list of transactions and estimates the fee rate // used to sweep them. func calculateTxnsFeeRate(t *testing.T, - miner *rpctest.Harness, txns []*wire.MsgTx) int64 { + miner *lntest.HarnessMiner, txns []*wire.MsgTx) int64 { var totalWeight, totalFee int64 for _, tx := range txns { @@ -335,13 +334,12 @@ func channelForceClosureTest(net *lntest.NetworkHarness, t *harnessTest, // Wait for Alice and Carol to receive the channel edge from the // funding manager. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = alice.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = alice.WaitForNetworkChannelOpen(chanPoint) if err != nil { t.Fatalf("alice didn't see the alice->carol channel before "+ "timeout: %v", err) } - err = carol.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = carol.WaitForNetworkChannelOpen(chanPoint) if err != nil { t.Fatalf("alice didn't see the alice->carol channel before "+ "timeout: %v", err) @@ -528,7 +526,7 @@ func channelForceClosureTest(net *lntest.NetworkHarness, t *harnessTest, aliceReports[aliceAnchor.OutPoint.String()] = &lnrpc.Resolution{ ResolutionType: lnrpc.ResolutionType_ANCHOR, Outcome: lnrpc.ResolutionOutcome_CLAIMED, - SweepTxid: aliceAnchor.SweepTx, + SweepTxid: aliceAnchor.SweepTx.TxHash().String(), Outpoint: &lnrpc.OutPoint{ TxidBytes: aliceAnchor.OutPoint.Hash[:], TxidStr: aliceAnchor.OutPoint.Hash.String(), @@ -634,7 +632,7 @@ func channelForceClosureTest(net *lntest.NetworkHarness, t *harnessTest, carolReports[carolAnchor.OutPoint.String()] = &lnrpc.Resolution{ ResolutionType: lnrpc.ResolutionType_ANCHOR, Outcome: lnrpc.ResolutionOutcome_CLAIMED, - SweepTxid: carolAnchor.SweepTx, + SweepTxid: carolAnchor.SweepTx.TxHash().String(), AmountSat: anchorSize, Outpoint: &lnrpc.OutPoint{ TxidBytes: carolAnchor.OutPoint.Hash[:], @@ -772,7 +770,7 @@ func channelForceClosureTest(net *lntest.NetworkHarness, t *harnessTest, OutputIndex: carolCommit.OutPoint.Index, }, AmountSat: uint64(pushAmt), - SweepTxid: carolCommit.SweepTx, + SweepTxid: carolCommit.SweepTx.TxHash().String(), } // Check that we can find the commitment sweep in our set of known @@ -1087,7 +1085,6 @@ func channelForceClosureTest(net *lntest.NetworkHarness, t *harnessTest, numBlocks := uint32(defaultCSV - 1) if channelType == lnrpc.CommitmentType_ANCHORS { numBlocks = defaultCSV - 2 - } _, err = net.Miner.Client.Generate(numBlocks) if err != nil { @@ -1201,7 +1198,7 @@ func channelForceClosureTest(net *lntest.NetworkHarness, t *harnessTest, } } - // Check that each HTLC output was spent exactly onece. + // Check that each HTLC output was spent exactly once. for op, num := range htlcTxOutpointSet { if num != 1 { t.Fatalf("HTLC outpoint %v was spent %v times", op, num) @@ -1340,7 +1337,7 @@ func padCLTV(cltv uint32) uint32 { type sweptOutput struct { OutPoint wire.OutPoint - SweepTx string + SweepTx *wire.MsgTx } // findCommitAndAnchor looks for a commitment sweep and anchor sweep in the @@ -1367,7 +1364,7 @@ func findCommitAndAnchor(t *harnessTest, net *lntest.NetworkHarness, if len(inputs) == 1 { commitSweep = &sweptOutput{ OutPoint: inputs[0].PreviousOutPoint, - SweepTx: txHash.String(), + SweepTx: tx, } } else { // Since we have more than one input, we run through @@ -1378,7 +1375,7 @@ func findCommitAndAnchor(t *harnessTest, net *lntest.NetworkHarness, if outpointStr == closeTx { anchorSweep = &sweptOutput{ OutPoint: txin.PreviousOutPoint, - SweepTx: txHash.String(), + SweepTx: tx, } } } @@ -1430,8 +1427,7 @@ func testFailingChannel(net *lntest.NetworkHarness, t *harnessTest) { carolPayReqs := []string{resp.PaymentRequest} // Wait for Alice to receive the channel edge from the funding manager. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = net.Alice.WaitForNetworkChannelOpen(chanPoint) if err != nil { t.Fatalf("alice didn't see the alice->carol channel before "+ "timeout: %v", err) diff --git a/lntest/itest/lnd_channel_graph_test.go b/lntest/itest/lnd_channel_graph_test.go index 747f018fd4..c001c5f0ca 100644 --- a/lntest/itest/lnd_channel_graph_test.go +++ b/lntest/itest/lnd_channel_graph_test.go @@ -5,13 +5,15 @@ import ( "context" "fmt" "io" + "strings" "testing" "time" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/chainreg" "github.com/lightningnetwork/lnd/funding" "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lnrpc/peersrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lntest" "github.com/lightningnetwork/lnd/lntest/wait" @@ -58,12 +60,10 @@ func testUpdateChanStatus(net *lntest.NetworkHarness, t *harnessTest) { // Wait for Alice and Bob to receive the channel edge from the // funding manager. - ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) - defer cancel() - err := alice.WaitForNetworkChannelOpen(ctxt, chanPoint) + err := alice.WaitForNetworkChannelOpen(chanPoint) require.NoError(t.t, err, "alice didn't see the alice->bob channel") - err = bob.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = bob.WaitForNetworkChannelOpen(chanPoint) require.NoError(t.t, err, "bob didn't see the alice->bob channel") // Launch a node for Carol which will connect to Alice and Bob in order @@ -83,12 +83,9 @@ func testUpdateChanStatus(net *lntest.NetworkHarness, t *harnessTest) { assertChannelUpdate := func(node *lntest.HarnessNode, policy *lnrpc.RoutingPolicy) { - ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) - defer cancel() - require.NoError( t.t, carol.WaitForChannelPolicyUpdate( - ctxt, node.PubKeyStr, policy, chanPoint, false, + node.PubKeyStr, policy, chanPoint, false, ), "error while waiting for channel update", ) } @@ -398,7 +395,7 @@ func testGraphTopologyNtfns(net *lntest.NetworkHarness, t *harnessTest, pinned b // Bob stimmy. net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, bob) - // Assert that Bob has the correct sync type before proceeeding. + // Assert that Bob has the correct sync type before proceeding. if pinned { assertSyncType(t, alice, bobPubkey, lnrpc.Peer_PINNED_SYNC) } else { @@ -431,7 +428,7 @@ func testGraphTopologyNtfns(net *lntest.NetworkHarness, t *harnessTest, pinned b // Ensure that a new update for both created edges is properly // dispatched to our registered client. case graphUpdate := <-graphSub.updateChan: - // Process all channel updates prsented in this update + // Process all channel updates presented in this update // message. for _, chanUpdate := range graphUpdate.ChannelUpdates { switch chanUpdate.AdvertisingNode { @@ -772,3 +769,263 @@ func subscribeGraphNotifications(ctxb context.Context, t *harnessTest, quit: quit, } } + +// waitForNodeAnnUpdates monitors the nodeAnnUpdates until we get one for +// the expected node and asserts that has the expected information. +func waitForNodeAnnUpdates(graphSub graphSubscription, nodePubKey string, + expectedUpdate *lnrpc.NodeUpdate, t *harnessTest) { + + for { + select { + case graphUpdate := <-graphSub.updateChan: + for _, update := range graphUpdate.NodeUpdates { + if update.IdentityKey == nodePubKey { + assertNodeAnnouncement( + t, update, expectedUpdate, + ) + return + } + } + case err := <-graphSub.errChan: + t.Fatalf("unable to recv graph update: %v", err) + case <-time.After(defaultTimeout): + t.Fatalf("did not receive node ann update") + } + } +} + +// testUpdateNodeAnnouncement ensures that the RPC endpoint validates +// the requests correctly and that the new node announcement is brodcasted +// with the right information after updating our node. +func testUpdateNodeAnnouncement(net *lntest.NetworkHarness, t *harnessTest) { + // context timeout for the whole test. + ctxt, cancel := context.WithTimeout( + context.Background(), defaultTimeout, + ) + defer cancel() + + // Launch notification clients for alice, such that we can + // get notified when there are updates in the graph. + aliceSub := subscribeGraphNotifications(ctxt, t, net.Alice) + defer close(aliceSub.quit) + + var lndArgs []string + + // Add some exta addresses to the default ones. + extraAddrs := []string{ + "192.168.1.1:8333", + "[2001:db8:85a3:8d3:1319:8a2e:370:7348]:8337", + "bkb6azqggsaiskzi.onion:9735", + "fomvuglh6h6vcag73xo5t5gv56ombih3zr2xvplkpbfd7wrog4swjwid.onion:1234", + } + for _, addr := range extraAddrs { + lndArgs = append(lndArgs, "--externalip="+addr) + } + dave := net.NewNode(t.t, "Dave", lndArgs) + defer shutdownAndAssert(net, t, dave) + + // Get dave default information so we can compare + // it lately with the brodcasted updates. + nodeInfoReq := &lnrpc.GetInfoRequest{} + resp, err := dave.GetInfo(ctxt, nodeInfoReq) + require.NoError(t.t, err, "unable to get dave's information") + + defaultAddrs := make([]*lnrpc.NodeAddress, 0, len(resp.Uris)) + for _, uri := range resp.GetUris() { + values := strings.Split(uri, "@") + defaultAddrs = append( + defaultAddrs, &lnrpc.NodeAddress{ + Addr: values[1], + Network: "tcp", + }, + ) + } + + // This feature bit is used to test that our endpoint sets/unsets + // feature bits properly. If the current FeatureBit is set by default + // update this one for another one unset by default at random. + featureBit := lnrpc.FeatureBit_WUMBO_CHANNELS_REQ + featureIdx := uint32(featureBit) + if _, ok := resp.Features[featureIdx]; ok { + t.Fatalf("unexpected feature bit enabled by default") + } + + defaultDaveNodeAnn := &lnrpc.NodeUpdate{ + Alias: resp.Alias, + Color: resp.Color, + NodeAddresses: defaultAddrs, + } + + // Dave must have an open channel before he can send a node + // announcement, so we open a channel with Bob. + net.ConnectNodes(t.t, net.Bob, dave) + + // Go ahead and open a channel between Bob and Dave. This + // ensures that Alice receives the node announcement from Bob as part of + // the announcement broadcast. + chanPoint := openChannelAndAssert( + t, net, net.Bob, dave, + lntest.OpenChannelParams{ + Amt: 1000000, + }, + ) + require.NoError(t.t, err, "unexpected error opening a channel") + + // Wait for Alice to receive dave's node announcement with the default + // values. + waitForNodeAnnUpdates( + aliceSub, dave.PubKeyStr, defaultDaveNodeAnn, t, + ) + + // We cannot differentiate between requests with Alias = "" and requests + // that do not provide that field. If a user sets Alias = "" in the request + // the field will simply be ignored. The request must fail because no + // modifiers are applied. + invalidNodeAnnReq := &peersrpc.NodeAnnouncementUpdateRequest{ + Alias: "", + } + + _, err = dave.UpdateNodeAnnouncement(ctxt, invalidNodeAnnReq) + require.Error(t.t, err, "requests without modifiers should field") + + // Alias too long. + invalidNodeAnnReq = &peersrpc.NodeAnnouncementUpdateRequest{ + Alias: strings.Repeat("a", 50), + } + + _, err = dave.UpdateNodeAnnouncement(ctxt, invalidNodeAnnReq) + require.Error(t.t, err, "failed to validate an invalid alias for an "+ + "update node announcement request") + + // Update Node. + newAlias := "new-alias" + newColor := "#2288ee" + + newAddresses := []string{ + "192.168.1.10:8333", + "192.168.1.11:8333", + } + + updateAddressActions := []*peersrpc.UpdateAddressAction{ + { + Action: peersrpc.UpdateAction_ADD, + Address: newAddresses[0], + }, + { + Action: peersrpc.UpdateAction_ADD, + Address: newAddresses[1], + }, + { + Action: peersrpc.UpdateAction_REMOVE, + Address: defaultAddrs[0].Addr, + }, + } + + updateFeatureActions := []*peersrpc.UpdateFeatureAction{ + { + Action: peersrpc.UpdateAction_ADD, + FeatureBit: featureBit, + }, + } + + nodeAnnReq := &peersrpc.NodeAnnouncementUpdateRequest{ + Alias: newAlias, + Color: newColor, + AddressUpdates: updateAddressActions, + FeatureUpdates: updateFeatureActions, + } + + response, err := dave.UpdateNodeAnnouncement(ctxt, nodeAnnReq) + require.NoError(t.t, err, "unable to update dave's node announcement") + + expectedOps := map[string]int{ + "features": 1, + "color": 1, + "alias": 1, + "addresses": 3, + } + assertUpdateNodeAnnouncementResponse(t, response, expectedOps) + + newNodeAddresses := []*lnrpc.NodeAddress{} + // We removed the first address. + newNodeAddresses = append(newNodeAddresses, defaultAddrs[1:]...) + newNodeAddresses = append( + newNodeAddresses, + &lnrpc.NodeAddress{Addr: newAddresses[0], Network: "tcp"}, + &lnrpc.NodeAddress{Addr: newAddresses[1], Network: "tcp"}, + ) + + // After updating the node we expect the update to contain + // the requested color, requested alias and the new added addresses. + newDaveNodeAnn := &lnrpc.NodeUpdate{ + Alias: newAlias, + Color: newColor, + NodeAddresses: newNodeAddresses, + } + + // We'll then wait for Alice to receive dave's node announcement + // with the new values. + waitForNodeAnnUpdates( + aliceSub, dave.PubKeyStr, newDaveNodeAnn, t, + ) + + // Check that the feature bit was set correctly. + resp, err = dave.GetInfo(ctxt, nodeInfoReq) + require.NoError(t.t, err, "unable to get dave's information") + + if _, ok := resp.Features[featureIdx]; !ok { + t.Fatalf("failed to set feature bit") + } + + // Check that we cannot set a feature bit that is already set. + nodeAnnReq = &peersrpc.NodeAnnouncementUpdateRequest{ + FeatureUpdates: updateFeatureActions, + } + + _, err = dave.UpdateNodeAnnouncement(ctxt, nodeAnnReq) + require.Error( + t.t, err, "missing expected error: cannot set a feature bit "+ + "that is already set", + ) + + // Check that we can unset feature bits. + updateFeatureActions = []*peersrpc.UpdateFeatureAction{ + { + Action: peersrpc.UpdateAction_REMOVE, + FeatureBit: featureBit, + }, + } + + nodeAnnReq = &peersrpc.NodeAnnouncementUpdateRequest{ + FeatureUpdates: updateFeatureActions, + } + + response, err = dave.UpdateNodeAnnouncement(ctxt, nodeAnnReq) + require.NoError(t.t, err, "unable to update dave's node announcement") + + expectedOps = map[string]int{ + "features": 1, + } + assertUpdateNodeAnnouncementResponse(t, response, expectedOps) + + resp, err = dave.GetInfo(ctxt, nodeInfoReq) + require.NoError(t.t, err, "unable to get dave's information") + + if _, ok := resp.Features[featureIdx]; ok { + t.Fatalf("failed to unset feature bit") + } + + // Check that we cannot unset a feature bit that is already unset. + nodeAnnReq = &peersrpc.NodeAnnouncementUpdateRequest{ + FeatureUpdates: updateFeatureActions, + } + + _, err = dave.UpdateNodeAnnouncement(ctxt, nodeAnnReq) + require.Error( + t.t, err, "missing expected error: cannot unset a feature bit "+ + "that is already unset", + ) + + // Close the channel between Bob and Dave. + closeChannelAndAssert(t, net, net.Bob, chanPoint, false) +} diff --git a/lntest/itest/lnd_channel_policy_test.go b/lntest/itest/lnd_channel_policy_test.go index 3bdadf86c7..cc25714bc9 100644 --- a/lntest/itest/lnd_channel_policy_test.go +++ b/lntest/itest/lnd_channel_policy_test.go @@ -2,11 +2,12 @@ package itest import ( "context" + "math" "strings" "time" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/chainreg" "github.com/lightningnetwork/lnd/funding" "github.com/lightningnetwork/lnd/lnrpc" @@ -15,6 +16,19 @@ import ( "github.com/stretchr/testify/require" ) +// assertPolicyUpdate checks that a given policy update has been received by a +// list of given nodes. +func assertPolicyUpdate(t *harnessTest, nodes []*lntest.HarnessNode, + advertisingNode string, policy *lnrpc.RoutingPolicy, + chanPoint *lnrpc.ChannelPoint) { + + for _, node := range nodes { + assertChannelPolicyUpdate( + t.t, node, advertisingNode, policy, chanPoint, false, + ) + } +} + // testUpdateChannelPolicy tests that policy updates made to a channel // gets propagated to other nodes in the network. func testUpdateChannelPolicy(net *lntest.NetworkHarness, t *harnessTest) { @@ -45,21 +59,6 @@ func testUpdateChannelPolicy(net *lntest.NetworkHarness, t *harnessTest) { // make sure they all receive the expected updates. nodes := []*lntest.HarnessNode{net.Alice, net.Bob} - // assertPolicyUpdate checks that a given policy update has been - // received by a list of given nodes. - assertPolicyUpdate := func(nodes []*lntest.HarnessNode, - advertisingNode string, policy *lnrpc.RoutingPolicy, - chanPoint *lnrpc.ChannelPoint) { - - for _, node := range nodes { - assertChannelPolicyUpdate( - t.t, node, advertisingNode, - policy, chanPoint, false, - ) - } - - } - // Alice and Bob should see each other's ChannelUpdates, advertising the // default routing policies. expectedPolicy := &lnrpc.RoutingPolicy{ @@ -71,9 +70,11 @@ func testUpdateChannelPolicy(net *lntest.NetworkHarness, t *harnessTest) { } assertPolicyUpdate( - nodes, net.Alice.PubKeyStr, expectedPolicy, chanPoint, + t, nodes, net.Alice.PubKeyStr, expectedPolicy, chanPoint, + ) + assertPolicyUpdate( + t, nodes, net.Bob.PubKeyStr, expectedPolicy, chanPoint, ) - assertPolicyUpdate(nodes, net.Bob.PubKeyStr, expectedPolicy, chanPoint) // They should now know about the default policies. for _, node := range nodes { @@ -85,12 +86,11 @@ func testUpdateChannelPolicy(net *lntest.NetworkHarness, t *harnessTest) { ) } - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - err := net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint) + err := net.Alice.WaitForNetworkChannelOpen(chanPoint) if err != nil { t.Fatalf("alice didn't report channel: %v", err) } - err = net.Bob.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = net.Bob.WaitForNetworkChannelOpen(chanPoint) if err != nil { t.Fatalf("bob didn't report channel: %v", err) } @@ -145,10 +145,10 @@ func testUpdateChannelPolicy(net *lntest.NetworkHarness, t *harnessTest) { } assertPolicyUpdate( - nodes, net.Bob.PubKeyStr, expectedPolicyBob, chanPoint2, + t, nodes, net.Bob.PubKeyStr, expectedPolicyBob, chanPoint2, ) assertPolicyUpdate( - nodes, carol.PubKeyStr, expectedPolicyCarol, chanPoint2, + t, nodes, carol.PubKeyStr, expectedPolicyCarol, chanPoint2, ) // Check that all nodes now know about the updated policies. @@ -163,18 +163,15 @@ func testUpdateChannelPolicy(net *lntest.NetworkHarness, t *harnessTest) { ) } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint2) + err = net.Alice.WaitForNetworkChannelOpen(chanPoint2) if err != nil { t.Fatalf("alice didn't report channel: %v", err) } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.Bob.WaitForNetworkChannelOpen(ctxt, chanPoint2) + err = net.Bob.WaitForNetworkChannelOpen(chanPoint2) if err != nil { t.Fatalf("bob didn't report channel: %v", err) } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = carol.WaitForNetworkChannelOpen(ctxt, chanPoint2) + err = carol.WaitForNetworkChannelOpen(chanPoint2) if err != nil { t.Fatalf("carol didn't report channel: %v", err) } @@ -187,7 +184,7 @@ func testUpdateChannelPolicy(net *lntest.NetworkHarness, t *harnessTest) { Memo: "testing", Value: int64(payAmt), } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) resp, err := carol.AddInvoice(ctxt, invoice) if err != nil { t.Fatalf("unable to add invoice: %v", err) @@ -341,7 +338,9 @@ func testUpdateChannelPolicy(net *lntest.NetworkHarness, t *harnessTest) { } // Wait for all nodes to have seen the policy update done by Bob. - assertPolicyUpdate(nodes, net.Bob.PubKeyStr, expectedPolicy, chanPoint) + assertPolicyUpdate( + t, nodes, net.Bob.PubKeyStr, expectedPolicy, chanPoint, + ) // Check that all nodes now know about Bob's updated policy. for _, node := range nodes { @@ -387,13 +386,11 @@ func testUpdateChannelPolicy(net *lntest.NetworkHarness, t *harnessTest) { ) defer closeChannelAndAssert(t, net, net.Alice, chanPoint3, false) - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint3) + err = net.Alice.WaitForNetworkChannelOpen(chanPoint3) if err != nil { t.Fatalf("alice didn't report channel: %v", err) } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = carol.WaitForNetworkChannelOpen(ctxt, chanPoint3) + err = carol.WaitForNetworkChannelOpen(chanPoint3) if err != nil { t.Fatalf("bob didn't report channel: %v", err) } @@ -427,10 +424,10 @@ func testUpdateChannelPolicy(net *lntest.NetworkHarness, t *harnessTest) { // Wait for all nodes to have seen the policy updates for both of // Alice's channels. assertPolicyUpdate( - nodes, net.Alice.PubKeyStr, expectedPolicy, chanPoint, + t, nodes, net.Alice.PubKeyStr, expectedPolicy, chanPoint, ) assertPolicyUpdate( - nodes, net.Alice.PubKeyStr, expectedPolicy, chanPoint3, + t, nodes, net.Alice.PubKeyStr, expectedPolicy, chanPoint3, ) // And finally check that all nodes remembers the policy update they @@ -462,11 +459,11 @@ func testUpdateChannelPolicy(net *lntest.NetworkHarness, t *harnessTest) { // of Alice's channels. Carol will not see the last update as // the limit has been reached. assertPolicyUpdate( - []*lntest.HarnessNode{net.Alice, net.Bob}, + t, []*lntest.HarnessNode{net.Alice, net.Bob}, net.Alice.PubKeyStr, expectedPolicy, chanPoint, ) assertPolicyUpdate( - []*lntest.HarnessNode{net.Alice, net.Bob}, + t, []*lntest.HarnessNode{net.Alice, net.Bob}, net.Alice.PubKeyStr, expectedPolicy, chanPoint3, ) // Check that all nodes remembers the policy update @@ -487,11 +484,11 @@ func testUpdateChannelPolicy(net *lntest.NetworkHarness, t *harnessTest) { expectedPolicy = &prevAlicePolicy } assertPolicyUpdate( - []*lntest.HarnessNode{carol}, + t, []*lntest.HarnessNode{carol}, net.Alice.PubKeyStr, expectedPolicy, chanPoint, ) assertPolicyUpdate( - []*lntest.HarnessNode{carol}, + t, []*lntest.HarnessNode{carol}, net.Alice.PubKeyStr, expectedPolicy, chanPoint3, ) assertChannelPolicy( @@ -505,8 +502,6 @@ func testUpdateChannelPolicy(net *lntest.NetworkHarness, t *harnessTest) { // flag set is sent once a channel has been either unilaterally or cooperatively // closed. func testSendUpdateDisableChannel(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() - const ( chanAmt = 100000 ) @@ -588,12 +583,9 @@ func testSendUpdateDisableChannel(net *lntest.NetworkHarness, t *harnessTest) { assertPolicyUpdate := func(node *lntest.HarnessNode, policy *lnrpc.RoutingPolicy, chanPoint *lnrpc.ChannelPoint) { - ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) - defer cancel() - require.NoError( t.t, dave.WaitForChannelPolicyUpdate( - ctxt, node.PubKeyStr, policy, chanPoint, false, + node.PubKeyStr, policy, chanPoint, false, ), "error while waiting for channel update", ) } @@ -616,6 +608,10 @@ func testSendUpdateDisableChannel(net *lntest.NetworkHarness, t *harnessTest) { expectedPolicy.Disabled = false assertPolicyUpdate(eve, expectedPolicy, chanPointEveCarol) + // Wait until Carol and Eve are reconnected before we disconnect them + // again. + net.EnsureConnected(t.t, eve, carol) + // Now we'll test a long disconnection. Disconnect Carol and Eve and // ensure they both detect each other as disabled. Their min backoffs // are high enough to not interfere with disabling logic. @@ -818,7 +814,7 @@ func testUpdateChannelPolicyForPrivateChannel(net *lntest.NetworkHarness, assertAmountPaid(t, "Bob(local) [private=>] Carol(remote)", net.Bob, bobFundPoint, paymentAmt, 0) - // Calcuate the amount in satoshis. + // Calculate the amount in satoshis. amtExpected := int64(paymentAmt + baseFeeMSat/1000) // Bob should have received 20k satoshis + fee from Alice. @@ -829,3 +825,88 @@ func testUpdateChannelPolicyForPrivateChannel(net *lntest.NetworkHarness, assertAmountPaid(t, "Alice(local) => Bob(remote)", net.Alice, aliceFundPoint, amtExpected, 0) } + +// testUpdateChannelPolicyFeeRateAccuracy tests that updating the channel policy +// rounds fee rate values correctly as well as setting fee rate with ppm works +// as expected. +func testUpdateChannelPolicyFeeRateAccuracy(net *lntest.NetworkHarness, + t *harnessTest) { + + chanAmt := funding.MaxBtcFundingAmount + pushAmt := chanAmt / 2 + + // Create a channel Alice -> Bob. + chanPoint := openChannelAndAssert( + t, net, net.Alice, net.Bob, + lntest.OpenChannelParams{ + Amt: chanAmt, + PushAmt: pushAmt, + }, + ) + defer closeChannelAndAssert(t, net, net.Alice, chanPoint, false) + + // Nodes that we need to make sure receive the channel updates. + nodes := []*lntest.HarnessNode{net.Alice, net.Bob} + + baseFee := int64(1500) + timeLockDelta := uint32(66) + maxHtlc := uint64(500000) + defaultMinHtlc := int64(1000) + + // Originally LND did not properly round up fee rates which caused + // inaccuracy where fee rates were simply rounded down due to the + // integer conversion. + // + // We'll use a fee rate of 0.031337 which without rounding up would + // have resulted in a fee rate ppm of 31336. + feeRate := 0.031337 + + // Expected fee rate will be rounded up. + expectedFeeRateMilliMsat := int64(math.Round(testFeeBase * feeRate)) + + expectedPolicy := &lnrpc.RoutingPolicy{ + FeeBaseMsat: baseFee, + FeeRateMilliMsat: expectedFeeRateMilliMsat, + TimeLockDelta: timeLockDelta, + MinHtlc: defaultMinHtlc, + MaxHtlcMsat: maxHtlc, + } + + req := &lnrpc.PolicyUpdateRequest{ + BaseFeeMsat: baseFee, + FeeRate: feeRate, + TimeLockDelta: timeLockDelta, + MaxHtlcMsat: maxHtlc, + Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{ + ChanPoint: chanPoint, + }, + } + + ctxb := context.Background() + ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) + if _, err := net.Alice.UpdateChannelPolicy(ctxt, req); err != nil { + t.Fatalf("unable to get alice's balance: %v", err) + } + + // Make sure that both Alice and Bob sees the same policy after update. + assertPolicyUpdate( + t, nodes, net.Alice.PubKeyStr, expectedPolicy, chanPoint, + ) + + // Now use the new PPM feerate field and make sure that the feerate is + // correctly set. + feeRatePPM := uint32(32337) + req.FeeRate = 0 // Can't set both at the same time. + req.FeeRatePpm = feeRatePPM + expectedPolicy.FeeRateMilliMsat = int64(feeRatePPM) + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if _, err := net.Alice.UpdateChannelPolicy(ctxt, req); err != nil { + t.Fatalf("unable to get alice's balance: %v", err) + } + + // Make sure that both Alice and Bob sees the same policy after update. + assertPolicyUpdate( + t, nodes, net.Alice.PubKeyStr, expectedPolicy, chanPoint, + ) +} diff --git a/lntest/itest/lnd_etcd_failover_test.go b/lntest/itest/lnd_etcd_failover_test.go index 9d6caceb7f..3be233303b 100644 --- a/lntest/itest/lnd_etcd_failover_test.go +++ b/lntest/itest/lnd_etcd_failover_test.go @@ -9,7 +9,7 @@ import ( "testing" "time" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/cluster" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lncfg" @@ -69,6 +69,9 @@ func testEtcdFailoverCase(net *lntest.NetworkHarness, ht *harnessTest, ctxb := context.Background() tmpDir, err := ioutil.TempDir("", "etcd") + if err != nil { + ht.Fatalf("Failed to create temp dir: %v", err) + } etcdCfg, cleanup, err := kvdb.StartEtcdTestBackend( tmpDir, uint16(lntest.NextAvailablePort()), uint16(lntest.NextAvailablePort()), "", @@ -78,12 +81,15 @@ func testEtcdFailoverCase(net *lntest.NetworkHarness, ht *harnessTest, } defer cleanup() + // Make leader election session TTL 5 sec to make the test run fast. + const leaderSessionTTL = 5 + observer, err := cluster.MakeLeaderElector( ctxb, cluster.EtcdLeaderElector, "observer", - lncfg.DefaultEtcdElectionPrefix, etcdCfg, + lncfg.DefaultEtcdElectionPrefix, leaderSessionTTL, etcdCfg, ) if err != nil { - ht.Fatalf("Cannot start election observer") + ht.Fatalf("Cannot start election observer: %v", err) } password := []byte("the quick brown fox jumps the lazy dog") @@ -93,6 +99,7 @@ func testEtcdFailoverCase(net *lntest.NetworkHarness, ht *harnessTest, carol1, _, _, err := net.NewNodeWithSeedEtcd( "Carol-1", etcdCfg, password, entropy[:], stateless, cluster, + leaderSessionTTL, ) if err != nil { ht.Fatalf("unable to start Carol-1: %v", err) @@ -100,6 +107,9 @@ func testEtcdFailoverCase(net *lntest.NetworkHarness, ht *harnessTest, ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) info1, err := carol1.GetInfo(ctxt, &lnrpc.GetInfoRequest{}) + if err != nil { + ht.Fatalf("unable to get info: %v", err) + } net.ConnectNodes(ht.t, carol1, net.Alice) @@ -116,7 +126,7 @@ func testEtcdFailoverCase(net *lntest.NetworkHarness, ht *harnessTest, // At this point Carol-1 is the elected leader, while Carol-2 will wait // to become the leader when Carol-1 stops. carol2, err := net.NewNodeEtcd( - "Carol-2", etcdCfg, password, cluster, false, + "Carol-2", etcdCfg, password, cluster, false, leaderSessionTTL, ) if err != nil { ht.Fatalf("Unable to start Carol-2: %v", err) @@ -139,18 +149,14 @@ func testEtcdFailoverCase(net *lntest.NetworkHarness, ht *harnessTest, ) // Shut down or kill Carol-1 and wait for Carol-2 to become the leader. - var failoverTimeout time.Duration + failoverTimeout := time.Duration(2*leaderSessionTTL) * time.Second if kill { err = net.KillNode(carol1) if err != nil { ht.Fatalf("Can't kill Carol-1: %v", err) } - - failoverTimeout = 2 * time.Minute - } else { shutdownAndAssert(net, ht, carol1) - failoverTimeout = 30 * time.Second } err = carol2.WaitUntilLeader(failoverTimeout) @@ -161,8 +167,7 @@ func testEtcdFailoverCase(net *lntest.NetworkHarness, ht *harnessTest, assertLeader(ht, observer, "Carol-2") - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = carol2.Unlock(ctxt, &lnrpc.UnlockWalletRequest{ + err = carol2.Unlock(&lnrpc.UnlockWalletRequest{ WalletPassword: password, }) if err != nil { @@ -173,6 +178,9 @@ func testEtcdFailoverCase(net *lntest.NetworkHarness, ht *harnessTest, // Make sure Carol-1 and Carol-2 have the same identity. info2, err := carol2.GetInfo(ctxt, &lnrpc.GetInfoRequest{}) + if err != nil { + ht.Fatalf("unable to get info: %v", err) + } if info1.IdentityPubkey != info2.IdentityPubkey { ht.Fatalf("Carol-1 and Carol-2 must have the same identity: "+ "%v vs %v", info1.IdentityPubkey, info2.IdentityPubkey) diff --git a/lntest/itest/lnd_forward_interceptor_test.go b/lntest/itest/lnd_forward_interceptor_test.go index 0355765948..0da675c39e 100644 --- a/lntest/itest/lnd_forward_interceptor_test.go +++ b/lntest/itest/lnd_forward_interceptor_test.go @@ -7,8 +7,8 @@ import ( "sync" "time" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/chainreg" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" @@ -41,10 +41,10 @@ func testForwardInterceptorDedupHtlc(net *lntest.NetworkHarness, t *harnessTest) defer shutdownAndAssert(net, t, alice) bob := net.NewNode(t.t, "bob", nil) - defer shutdownAndAssert(net, t, alice) + defer shutdownAndAssert(net, t, bob) carol := net.NewNode(t.t, "carol", nil) - defer shutdownAndAssert(net, t, alice) + defer shutdownAndAssert(net, t, carol) tc := newInterceptorTestContext(t, net, alice, bob, carol) @@ -210,10 +210,10 @@ func testForwardInterceptorBasic(net *lntest.NetworkHarness, t *harnessTest) { defer shutdownAndAssert(net, t, alice) bob := net.NewNode(t.t, "bob", nil) - defer shutdownAndAssert(net, t, alice) + defer shutdownAndAssert(net, t, bob) carol := net.NewNode(t.t, "carol", nil) - defer shutdownAndAssert(net, t, alice) + defer shutdownAndAssert(net, t, carol) testContext := newInterceptorTestContext(t, net, alice, bob, carol) @@ -424,8 +424,7 @@ func testForwardInterceptorBasic(net *lntest.NetworkHarness, t *harnessTest) { }) return err == nil && len(channels.Channels) > 0 }, defaultTimeout) - require.NoError(t.t, err, "alice <> bob channel didnt re-activate") - + require.NoError(t.t, err, "alice <> bob channel didn't re-activate") } // interceptorTestContext is a helper struct to hold the test context and @@ -534,8 +533,6 @@ func (c *interceptorTestContext) closeChannels() { } func (c *interceptorTestContext) waitForChannels() { - ctxb := context.Background() - // Wait for all nodes to have seen all channels. for _, chanPoint := range c.networkChans { for _, node := range c.nodes { @@ -547,8 +544,7 @@ func (c *interceptorTestContext) waitForChannels() { Index: chanPoint.OutputIndex, } - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - err = node.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = node.WaitForNetworkChannelOpen(chanPoint) require.NoError(c.t.t, err, fmt.Sprintf("(%d): timeout "+ "waiting for channel(%s) open", node.NodeID, point)) diff --git a/lntest/itest/lnd_funding_test.go b/lntest/itest/lnd_funding_test.go index 8c8cda653f..d6717b0263 100644 --- a/lntest/itest/lnd_funding_test.go +++ b/lntest/itest/lnd_funding_test.go @@ -7,9 +7,9 @@ import ( "testing" "time" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/funding" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/labels" @@ -77,7 +77,6 @@ func testBasicChannelFunding(net *lntest.NetworkHarness, t *harnessTest) { expType := carolCommitType switch daveCommitType { - // Dave supports anchors, type will be what // Carol supports. case lnrpc.CommitmentType_ANCHORS: @@ -139,7 +138,7 @@ test: "---- basic channel funding subtest %s ----\n", testName, ) - net.Alice.AddToLog(logLine) + net.Alice.AddToLogf(logLine) success := t.t.Run(testName, func(t *testing.T) { testFunding(cc, dc) @@ -196,7 +195,6 @@ func basicChannelFundingTest(t *harnessTest, net *lntest.NetworkHarness, // open or an error occurs in the funding process. A series of // assertions will be executed to ensure the funding process completed // successfully. - ctxb := context.Background() chanPoint := openChannelAndAssert( t, net, alice, bob, lntest.OpenChannelParams{ @@ -207,16 +205,14 @@ func basicChannelFundingTest(t *harnessTest, net *lntest.NetworkHarness, }, ) - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - - err := alice.WaitForNetworkChannelOpen(ctxt, chanPoint) + err := alice.WaitForNetworkChannelOpen(chanPoint) require.NoError(t.t, err, "alice didn't report channel") - err = bob.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = bob.WaitForNetworkChannelOpen(chanPoint) require.NoError(t.t, err, "bob didn't report channel") cType, err := channelCommitType(alice, chanPoint) - require.NoError(t.t, err, "unable to get channnel type") + require.NoError(t.t, err, "unable to get channel type") // With the channel open, ensure that the amount specified above has // properly been pushed to Bob. @@ -248,8 +244,6 @@ func basicChannelFundingTest(t *harnessTest, net *lntest.NetworkHarness, // testUnconfirmedChannelFunding tests that our unconfirmed change outputs can // be used to fund channels. func testUnconfirmedChannelFunding(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() - const ( chanAmt = funding.MaxBtcFundingAmount pushAmt = btcutil.Amount(100000) @@ -260,27 +254,10 @@ func testUnconfirmedChannelFunding(net *lntest.NetworkHarness, t *harnessTest) { defer shutdownAndAssert(net, t, carol) // We'll send her some confirmed funds. - net.SendCoins(t.t, 2*chanAmt, carol) - - // Now let Carol send some funds to herself, making a unconfirmed - // change output. - addrReq := &lnrpc.NewAddressRequest{ - Type: lnrpc.AddressType_WITNESS_PUBKEY_HASH, - } - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - resp, err := carol.NewAddress(ctxt, addrReq) - require.NoError(t.t, err, "unable to get new address") - - sendReq := &lnrpc.SendCoinsRequest{ - Addr: resp.Address, - Amount: int64(chanAmt) / 5, - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - _, err = carol.SendCoins(ctxt, sendReq) - require.NoError(t.t, err, "unable to send coins") + net.SendCoinsUnconfirmed(t.t, chanAmt*2, carol) // Make sure the unconfirmed tx is seen in the mempool. - _, err = waitForTxInMempool(net.Miner.Client, minerMempoolTimeout) + _, err := waitForTxInMempool(net.Miner.Client, minerMempoolTimeout) require.NoError(t.t, err, "failed to find tx in miner mempool") // Now, we'll connect her to Alice so that they can open a channel @@ -301,6 +278,7 @@ func testUnconfirmedChannelFunding(net *lntest.NetworkHarness, t *harnessTest) { // response to a channel balance RPC. checkChannelBalance := func(node *lntest.HarnessNode, local, remote, pendingLocal, pendingRemote btcutil.Amount) { + expectedResponse := &lnrpc.ChannelBalanceResponse{ LocalBalance: &lnrpc.Amount{ Sat: uint64(local), @@ -365,6 +343,141 @@ func testUnconfirmedChannelFunding(net *lntest.NetworkHarness, t *harnessTest) { closeChannelAndAssert(t, net, carol, chanPoint, false) } +// testChannelFundingInputTypes tests that any type of supported input type can +// be used to fund channels. +func testChannelFundingInputTypes(net *lntest.NetworkHarness, t *harnessTest) { + const ( + chanAmt = funding.MaxBtcFundingAmount + burnAddr = "bcrt1qxsnqpdc842lu8c0xlllgvejt6rhy49u6fmpgyz" + ) + addrTypes := []lnrpc.AddressType{ + lnrpc.AddressType_WITNESS_PUBKEY_HASH, + lnrpc.AddressType_NESTED_PUBKEY_HASH, + lnrpc.AddressType_TAPROOT_PUBKEY, + } + + // We'll start off by creating a node for Carol. + carol := net.NewNode(t.t, "Carol", nil) + defer shutdownAndAssert(net, t, carol) + + // Now, we'll connect her to Alice so that they can open a + // channel together. + net.ConnectNodes(t.t, carol, net.Alice) + + for _, addrType := range addrTypes { + // We'll send her some confirmed funds. + err := net.SendCoinsOfType(chanAmt*2, carol, addrType, true) + require.NoErrorf( + t.t, err, "unable to send coins for carol and addr "+ + "type %v", addrType, + ) + + chanOpenUpdate := openChannelStream( + t, net, carol, net.Alice, lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + + // Creates a helper closure to be used below which asserts the + // proper response to a channel balance RPC. + checkChannelBalance := func(node *lntest.HarnessNode, + local, remote, pendingLocal, + pendingRemote btcutil.Amount) { + + expectedResponse := &lnrpc.ChannelBalanceResponse{ + LocalBalance: &lnrpc.Amount{ + Sat: uint64(local), + Msat: uint64(lnwire.NewMSatFromSatoshis( + local, + )), + }, + RemoteBalance: &lnrpc.Amount{ + Sat: uint64(remote), + Msat: uint64(lnwire.NewMSatFromSatoshis( + remote, + )), + }, + PendingOpenLocalBalance: &lnrpc.Amount{ + Sat: uint64(pendingLocal), + Msat: uint64(lnwire.NewMSatFromSatoshis( + pendingLocal, + )), + }, + PendingOpenRemoteBalance: &lnrpc.Amount{ + Sat: uint64(pendingRemote), + Msat: uint64(lnwire.NewMSatFromSatoshis( + pendingRemote, + )), + }, + UnsettledLocalBalance: &lnrpc.Amount{}, + UnsettledRemoteBalance: &lnrpc.Amount{}, + // Deprecated fields. + Balance: int64(local), + PendingOpenBalance: int64(pendingLocal), + } + assertChannelBalanceResp(t, node, expectedResponse) + } + + // As the channel is pending open, it's expected Carol has both + // zero local and remote balances, and pending local/remote + // should not be zero. + // + // Note that atm we haven't obtained the chanPoint yet, so we + // use the type directly. + cType := lnrpc.CommitmentType_STATIC_REMOTE_KEY + carolLocalBalance := chanAmt - calcStaticFee(cType, 0) + checkChannelBalance(carol, 0, 0, carolLocalBalance, 0) + + // For Alice, her local/remote balances should be zero, and the + // local/remote balances are the mirror of Carol's. + checkChannelBalance(net.Alice, 0, 0, 0, carolLocalBalance) + + // Confirm the channel and wait for it to be recognized by both + // parties. Two transactions should be mined, the unconfirmed + // spend and the funding tx. + mineBlocks(t, net, 6, 1) + chanPoint, err := net.WaitForChannelOpen(chanOpenUpdate) + require.NoError( + t.t, err, "error while waiting for channel open", + ) + + // With the channel open, we'll check the balances on each side + // of the channel as a sanity check to ensure things worked out + // as intended. + checkChannelBalance(carol, carolLocalBalance, 0, 0, 0) + checkChannelBalance(net.Alice, 0, carolLocalBalance, 0, 0) + + // Now that we're done with the test, the channel can be closed. + closeChannelAndAssert(t, net, carol, chanPoint, false) + + // Empty out the wallet so there aren't any lingering coins. + sendAllCoinsConfirm(net, carol, t, burnAddr) + } +} + +// sendAllCoinsConfirm sends all coins of the node's wallet to the given address +// and awaits one confirmation. +func sendAllCoinsConfirm(net *lntest.NetworkHarness, node *lntest.HarnessNode, + t *harnessTest, addr string) { + + ctxb := context.Background() + ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) + defer cancel() + + sweepReq := &lnrpc.SendCoinsRequest{ + Addr: addr, + SendAll: true, + } + _, err := node.SendCoins(ctxt, sweepReq) + require.NoError(t.t, err) + + // Make sure the unconfirmed tx is seen in the mempool. + _, err = waitForTxInMempool(net.Miner.Client, minerMempoolTimeout) + require.NoError(t.t, err, "failed to find tx in miner mempool") + + mineBlocks(t, net, 1, 1) +} + // testExternalFundingChanPoint tests that we're able to carry out a normal // channel funding workflow given a channel point that was constructed outside // the main daemon. @@ -816,13 +929,11 @@ func testBatchChanFunding(net *lntest.NetworkHarness, t *harnessTest) { block := mineBlocks(t, net, 6, 1)[0] assertTxInBlock(t, block, txHash) - ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) - defer cancel() - err = net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint1) + err = net.Alice.WaitForNetworkChannelOpen(chanPoint1) require.NoError(t.t, err) - err = net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint2) + err = net.Alice.WaitForNetworkChannelOpen(chanPoint2) require.NoError(t.t, err) - err = net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint3) + err = net.Alice.WaitForNetworkChannelOpen(chanPoint3) require.NoError(t.t, err) // With the channel open, ensure that it is counted towards Carol's diff --git a/lntest/itest/lnd_hold_invoice_force_test.go b/lntest/itest/lnd_hold_invoice_force_test.go index 78b5bde5c3..7fd911d16f 100644 --- a/lntest/itest/lnd_hold_invoice_force_test.go +++ b/lntest/itest/lnd_hold_invoice_force_test.go @@ -14,7 +14,7 @@ import ( "github.com/stretchr/testify/require" ) -// testHoldInvoiceForceClose tests cancelation of accepted hold invoices which +// testHoldInvoiceForceClose tests cancellation of accepted hold invoices which // would otherwise trigger force closes when they expire. func testHoldInvoiceForceClose(net *lntest.NetworkHarness, t *harnessTest) { ctxb, cancel := context.WithCancel(context.Background()) @@ -74,8 +74,8 @@ func testHoldInvoiceForceClose(net *lntest.NetworkHarness, t *harnessTest) { require.Len(t.t, chans.Channels[0].PendingHtlcs, 1) activeHtlc := chans.Channels[0].PendingHtlcs[0] - require.NoError(t.t, net.Alice.WaitForBlockchainSync(ctxb)) - require.NoError(t.t, net.Bob.WaitForBlockchainSync(ctxb)) + require.NoError(t.t, net.Alice.WaitForBlockchainSync()) + require.NoError(t.t, net.Bob.WaitForBlockchainSync()) info, err := net.Alice.GetInfo(ctxb, &lnrpc.GetInfoRequest{}) require.NoError(t.t, err) @@ -99,8 +99,8 @@ func testHoldInvoiceForceClose(net *lntest.NetworkHarness, t *harnessTest) { mineBlocksSlow(t, net, blocksTillForce, 0) - require.NoError(t.t, net.Alice.WaitForBlockchainSync(ctxb)) - require.NoError(t.t, net.Bob.WaitForBlockchainSync(ctxb)) + require.NoError(t.t, net.Alice.WaitForBlockchainSync()) + require.NoError(t.t, net.Bob.WaitForBlockchainSync()) // Our channel should not have been force closed, instead we expect our // channel to still be open and our invoice to have been canceled before diff --git a/lntest/itest/lnd_hold_persistence_test.go b/lntest/itest/lnd_hold_persistence_test.go index a9b570a03c..11a90a0563 100644 --- a/lntest/itest/lnd_hold_persistence_test.go +++ b/lntest/itest/lnd_hold_persistence_test.go @@ -8,7 +8,7 @@ import ( "sync" "time" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" @@ -48,20 +48,38 @@ func testHoldInvoicePersistence(net *lntest.NetworkHarness, t *harnessTest) { // Wait for Alice and Carol to receive the channel edge from the // funding manager. - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - err := net.Alice.WaitForNetworkChannelOpen(ctxt, chanPointAlice) + err := net.Alice.WaitForNetworkChannelOpen(chanPointAlice) if err != nil { t.Fatalf("alice didn't see the alice->carol channel before "+ "timeout: %v", err) } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = carol.WaitForNetworkChannelOpen(ctxt, chanPointAlice) + err = carol.WaitForNetworkChannelOpen(chanPointAlice) if err != nil { t.Fatalf("carol didn't see the carol->alice channel before "+ "timeout: %v", err) } + // For Carol to include her private channel with Alice as a hop hint, + // we need Alice to be perceived as a "public" node, meaning that she + // has at least one public channel in the graph. We open a public + // channel from Alice -> Bob and wait for Carol to see it. + chanPointBob := openChannelAndAssert( + t, net, net.Alice, net.Bob, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + + // Wait for Alice and Carol to see the open channel + err = net.Alice.WaitForNetworkChannelOpen(chanPointBob) + require.NoError(t.t, err, "alice didn't see the alice->bob "+ + "channel before timeout") + + err = carol.WaitForNetworkChannelOpen(chanPointBob) + require.NoError(t.t, err, "carol didn't see the alice->bob "+ + "channel before timeout") + // Create preimages for all payments we are going to initiate. var preimages []lntypes.Preimage for i := 0; i < numPayments; i++ { @@ -92,7 +110,7 @@ func testHoldInvoicePersistence(net *lntest.NetworkHarness, t *harnessTest) { Hash: payHash[:], Private: true, } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) resp, err := carol.AddHoldInvoice(ctxt, invoiceReq) if err != nil { t.Fatalf("unable to add invoice: %v", err) @@ -111,6 +129,18 @@ func testHoldInvoicePersistence(net *lntest.NetworkHarness, t *harnessTest) { t.Fatalf("unable to subscribe to invoice: %v", err) } + // We expect all of our invoices to have hop hints attached, + // since Carol and Alice are connected with a private channel. + // We assert that we have one hop hint present to ensure that + // we've got coverage for hop hints. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + decodeReq := &lnrpc.PayReqString{ + PayReq: resp.PaymentRequest, + } + invoice, err := net.Alice.DecodePayReq(ctxt, decodeReq) + require.NoError(t.t, err, "could not decode invoice") + require.Len(t.t, invoice.RouteHints, 1) + invoiceStreams = append(invoiceStreams, stream) payReqs = append(payReqs, resp.PaymentRequest) } @@ -165,7 +195,7 @@ func testHoldInvoicePersistence(net *lntest.NetworkHarness, t *harnessTest) { req := &lnrpc.ListPaymentsRequest{ IncludeIncomplete: true, } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) paymentsResp, err := net.Alice.ListPayments(ctxt, req) if err != nil { return fmt.Errorf("error when obtaining payments: %v", @@ -231,7 +261,7 @@ func testHoldInvoicePersistence(net *lntest.NetworkHarness, t *harnessTest) { } // Now after a restart, we must re-track the payments. We set up a - // goroutine for each to track thir status updates. + // goroutine for each to track their status updates. var ( statusUpdates []chan *lnrpc.Payment wg sync.WaitGroup @@ -291,7 +321,7 @@ func testHoldInvoicePersistence(net *lntest.NetworkHarness, t *harnessTest) { payment.Status) } case <-time.After(5 * time.Second): - t.Fatalf("in flight status not recevied") + t.Fatalf("in flight status not received") } } @@ -299,7 +329,7 @@ func testHoldInvoicePersistence(net *lntest.NetworkHarness, t *harnessTest) { for i, preimage := range preimages { var expectedState lnrpc.Invoice_InvoiceState - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) if i%2 == 0 { settle := &invoicesrpc.SettleInvoiceMsg{ Preimage: preimage[:], @@ -343,7 +373,7 @@ func testHoldInvoicePersistence(net *lntest.NetworkHarness, t *harnessTest) { payment = p case <-time.After(5 * time.Second): - t.Fatalf("in flight status not recevied") + t.Fatalf("in flight status not received") } } @@ -368,7 +398,7 @@ func testHoldInvoicePersistence(net *lntest.NetworkHarness, t *harnessTest) { req := &lnrpc.ListPaymentsRequest{ IncludeIncomplete: true, } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) paymentsResp, err := net.Alice.ListPayments(ctxt, req) if err != nil { t.Fatalf("error when obtaining Alice payments: %v", err) diff --git a/lntest/itest/lnd_macaroons_test.go b/lntest/itest/lnd_macaroons_test.go index 9ea4552ae1..22ae852bac 100644 --- a/lntest/itest/lnd_macaroons_test.go +++ b/lntest/itest/lnd_macaroons_test.go @@ -665,8 +665,7 @@ func testStatelessInit(net *lntest.NetworkHarness, t *harnessTest) { NewPassword: newPw, StatelessInit: true, } - ctxb := context.Background() - response, err := carol.InitChangePassword(ctxb, changePwReq) + response, err := carol.InitChangePassword(changePwReq) require.NoError(t.t, err) // Again, make sure no macaroon files have been created by the node diff --git a/lntest/itest/lnd_max_channel_size_test.go b/lntest/itest/lnd_max_channel_size_test.go index 940e5f21b6..100f5bb346 100644 --- a/lntest/itest/lnd_max_channel_size_test.go +++ b/lntest/itest/lnd_max_channel_size_test.go @@ -7,7 +7,7 @@ import ( "fmt" "strings" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/funding" "github.com/lightningnetwork/lnd/lntest" ) diff --git a/lntest/itest/lnd_max_htlcs_test.go b/lntest/itest/lnd_max_htlcs_test.go index a6dff646ec..9f5218cac3 100644 --- a/lntest/itest/lnd_max_htlcs_test.go +++ b/lntest/itest/lnd_max_htlcs_test.go @@ -34,12 +34,10 @@ func testMaxHtlcPathfind(net *lntest.NetworkHarness, t *harnessTest) { // Wait for Alice and Bob to receive the channel edge from the // funding manager. - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - err := net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint) + err := net.Alice.WaitForNetworkChannelOpen(chanPoint) require.NoError(t.t, err, "alice does not have open channel") - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.Bob.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = net.Bob.WaitForNetworkChannelOpen(chanPoint) require.NoError(t.t, err, "bob does not have open channel") // Alice and bob should have one channel open with each other now. @@ -87,7 +85,7 @@ func testMaxHtlcPathfind(net *lntest.NetworkHarness, t *harnessTest) { // Now, we're going to try to send another payment from Bob -> Alice. // We've hit our max remote htlcs, so we expect this payment to spin // out dramatically with pathfinding. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) payment, err := net.Bob.RouterClient.SendPaymentV2( ctxt, &routerrpc.SendPaymentRequest{ Amt: 1000, diff --git a/lntest/itest/lnd_misc_test.go b/lntest/itest/lnd_misc_test.go index d90e64b404..6fb934bb42 100644 --- a/lntest/itest/lnd_misc_test.go +++ b/lntest/itest/lnd_misc_test.go @@ -9,9 +9,9 @@ import ( "strings" "time" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/btcsuite/btcwallet/wallet" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/chainreg" @@ -236,23 +236,22 @@ func testSphinxReplayPersistence(net *lntest.NetworkHarness, t *harnessTest) { } // Wait for all channels to be recognized and advertized. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = carol.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = carol.WaitForNetworkChannelOpen(chanPoint) if err != nil { t.Fatalf("alice didn't advertise channel before "+ "timeout: %v", err) } - err = dave.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = dave.WaitForNetworkChannelOpen(chanPoint) if err != nil { t.Fatalf("bob didn't advertise channel before "+ "timeout: %v", err) } - err = carol.WaitForNetworkChannelOpen(ctxt, chanPointFC) + err = carol.WaitForNetworkChannelOpen(chanPointFC) if err != nil { t.Fatalf("alice didn't advertise channel before "+ "timeout: %v", err) } - err = fred.WaitForNetworkChannelOpen(ctxt, chanPointFC) + err = fred.WaitForNetworkChannelOpen(chanPointFC) if err != nil { t.Fatalf("bob didn't advertise channel before "+ "timeout: %v", err) @@ -341,8 +340,8 @@ func testSphinxReplayPersistence(net *lntest.NetworkHarness, t *harnessTest) { // testListChannels checks that the response from ListChannels is correct. It // tests the values in all ChannelConstraints are returned as expected. Once -// ListChannels becomes mature, a test against all fields in ListChannels should -// be performed. +// ListChannels becomes mature, a test against all fields in ListChannels +// should be performed. func testListChannels(net *lntest.NetworkHarness, t *harnessTest) { ctxb := context.Background() @@ -370,8 +369,8 @@ func testListChannels(net *lntest.NetworkHarness, t *harnessTest) { net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, alice) // Open a channel with 100k satoshis between Alice and Bob with Alice - // being the sole funder of the channel. The minial HTLC amount is set to - // 4200 msats. + // being the sole funder of the channel. The minial HTLC amount is set + // to 4200 msats. const customizedMinHtlc = 4200 chanAmt := btcutil.Amount(100000) @@ -386,15 +385,13 @@ func testListChannels(net *lntest.NetworkHarness, t *harnessTest) { // Wait for Alice and Bob to receive the channel edge from the // funding manager. - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - err := alice.WaitForNetworkChannelOpen(ctxt, chanPoint) + err := alice.WaitForNetworkChannelOpen(chanPoint) if err != nil { t.Fatalf("alice didn't see the alice->bob channel before "+ "timeout: %v", err) } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = bob.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = bob.WaitForNetworkChannelOpen(chanPoint) if err != nil { t.Fatalf("bob didn't see the bob->alice channel before "+ "timeout: %v", err) @@ -407,8 +404,7 @@ func testListChannels(net *lntest.NetworkHarness, t *harnessTest) { // Get the ListChannel response from Alice. listReq := &lnrpc.ListChannelsRequest{} - ctxb = context.Background() - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) resp, err := alice.ListChannels(ctxt, listReq) if err != nil { t.Fatalf("unable to query for %s's channel list: %v", @@ -418,11 +414,19 @@ func testListChannels(net *lntest.NetworkHarness, t *harnessTest) { // Check the returned response is correct. aliceChannel := resp.Channels[0] + // Since Alice is the initiator, she pays the commit fee. + aliceBalance := int64(chanAmt) - aliceChannel.CommitFee + + // Check the balance related fields are correct. + require.Equal(t.t, aliceBalance, aliceChannel.LocalBalance) + require.Zero(t.t, aliceChannel.RemoteBalance) + require.Zero(t.t, aliceChannel.PushAmountSat) + // Calculate the dust limit we'll use for the test. dustLimit := lnwallet.DustLimitForSize(input.UnknownWitnessSize) - // defaultConstraints is a ChannelConstraints with default values. It is - // used to test against Alice's local channel constraints. + // defaultConstraints is a ChannelConstraints with default values. It + // is used to test against Alice's local channel constraints. defaultConstraints := &lnrpc.ChannelConstraints{ CsvDelay: 4, ChanReserveSat: 1000, @@ -468,24 +472,26 @@ func testListChannels(net *lntest.NetworkHarness, t *harnessTest) { ) } - // Check channel constraints match. Alice's local channel constraint should - // be equal to Bob's remote channel constraint, and her remote one should - // be equal to Bob's local one. + // Check the balance related fields are correct. + require.Equal(t.t, aliceBalance, bobChannel.RemoteBalance) + require.Zero(t.t, bobChannel.LocalBalance) + require.Zero(t.t, bobChannel.PushAmountSat) + + // Check channel constraints match. Alice's local channel constraint + // should be equal to Bob's remote channel constraint, and her remote + // one should be equal to Bob's local one. assertChannelConstraintsEqual( t, aliceChannel.LocalConstraints, bobChannel.RemoteConstraints, ) assertChannelConstraintsEqual( t, aliceChannel.RemoteConstraints, bobChannel.LocalConstraints, ) - } // testMaxPendingChannels checks that error is returned from remote peer if // max pending channel number was exceeded and that '--maxpendingchannels' flag // exists and works properly. func testMaxPendingChannels(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() - maxPendingChannels := lncfg.DefaultMaxPendingChannels + 1 amount := funding.MaxBtcFundingAmount @@ -529,6 +535,7 @@ func testMaxPendingChannels(net *lntest.NetworkHarness, t *harnessTest) { } else if !strings.Contains( err.Error(), lnwire.ErrMaxPendingChannels.Error(), ) { + t.Fatalf("not expected error was received: %v", err) } @@ -557,8 +564,7 @@ func testMaxPendingChannels(net *lntest.NetworkHarness, t *harnessTest) { // Ensure that the funding transaction enters a block, and is // properly advertised by Alice. assertTxInBlock(t, block, fundingTxID) - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - err = net.Alice.WaitForNetworkChannelOpen(ctxt, fundingChanPoint) + err = net.Alice.WaitForNetworkChannelOpen(fundingChanPoint) if err != nil { t.Fatalf("channel not seen on network before "+ "timeout: %v", err) @@ -780,7 +786,6 @@ func testGarbageCollectLinkNodes(net *lntest.NetworkHarness, t *harnessTest) { predErr = checkNumForceClosedChannels(pendingChanResp, 0) return predErr == nil - }, defaultTimeout) if err != nil { t.Fatalf("channels not marked as fully resolved: %v", predErr) @@ -878,8 +883,7 @@ func testDataLossProtection(net *lntest.NetworkHarness, t *harnessTest) { // Wait for Carol to receive the channel edge from the funding // manager. - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - err = carol.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = carol.WaitForNetworkChannelOpen(chanPoint) if err != nil { t.Fatalf("carol didn't see the carol->%s channel "+ "before timeout: %v", node.Name(), err) @@ -975,7 +979,7 @@ func testDataLossProtection(net *lntest.NetworkHarness, t *harnessTest) { } balReq := &lnrpc.WalletBalanceRequest{} - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) balResp, err := node.WalletBalance(ctxt, balReq) if err != nil { t.Fatalf("unable to get dave's balance: %v", err) @@ -1102,7 +1106,7 @@ func testDataLossProtection(net *lntest.NetworkHarness, t *harnessTest) { daveBalance := daveBalResp.ConfirmedBalance if daveBalance <= daveStartingBalance { return fmt.Errorf("expected dave to have balance "+ - "above %d, intead had %v", daveStartingBalance, + "above %d, instead had %v", daveStartingBalance, daveBalance) } @@ -1352,16 +1356,15 @@ func testAbandonChannel(net *lntest.NetworkHarness, t *harnessTest) { chanPointStr := fmt.Sprintf("%v:%v", txid, chanPoint.OutputIndex) // Wait for channel to be confirmed open. - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - err = net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = net.Alice.WaitForNetworkChannelOpen(chanPoint) require.NoError(t.t, err, "alice wait for network channel open") - err = net.Bob.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = net.Bob.WaitForNetworkChannelOpen(chanPoint) require.NoError(t.t, err, "bob wait for network channel open") // Now that the channel is open, we'll obtain its channel ID real quick // so we can use it to query the graph below. listReq := &lnrpc.ListChannelsRequest{} - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) aliceChannelList, err := net.Alice.ListChannels(ctxt, listReq) require.NoError(t.t, err) var chanID uint64 diff --git a/lntest/itest/lnd_mpp_test.go b/lntest/itest/lnd_mpp_test.go index 7c51e66f28..7288f42a9e 100644 --- a/lntest/itest/lnd_mpp_test.go +++ b/lntest/itest/lnd_mpp_test.go @@ -6,8 +6,8 @@ import ( "fmt" "time" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/chainreg" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" @@ -319,8 +319,6 @@ func (c *mppTestContext) shutdownNodes() { } func (c *mppTestContext) waitForChannels() { - ctxb := context.Background() - // Wait for all nodes to have seen all channels. for _, chanPoint := range c.networkChans { for _, node := range c.nodes { @@ -333,8 +331,7 @@ func (c *mppTestContext) waitForChannels() { Index: chanPoint.OutputIndex, } - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - err = node.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = node.WaitForNetworkChannelOpen(chanPoint) if err != nil { c.t.Fatalf("(%v:%d): timeout waiting for "+ "channel(%s) open: %v", diff --git a/lntest/itest/lnd_multi-hop-error-propagation_test.go b/lntest/itest/lnd_multi-hop-error-propagation_test.go index 3e01628fd0..64951753ff 100644 --- a/lntest/itest/lnd_multi-hop-error-propagation_test.go +++ b/lntest/itest/lnd_multi-hop-error-propagation_test.go @@ -29,8 +29,7 @@ func testHtlcErrorPropagation(net *lntest.NetworkHarness, t *harnessTest) { Amt: chanAmt, }, ) - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - if err := net.Alice.WaitForNetworkChannelOpen(ctxt, chanPointAlice); err != nil { + if err := net.Alice.WaitForNetworkChannelOpen(chanPointAlice); err != nil { t.Fatalf("channel not seen by alice before timeout: %v", err) } @@ -115,7 +114,7 @@ out: for { select { case <-checkTableTicker.C: - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) _, err := net.Alice.GetNodeInfo(ctxt, nodeInfoReq) if err != nil && strings.Contains(err.Error(), "unable to find") { @@ -138,7 +137,7 @@ out: Memo: "kek99", Value: payAmt, } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) carolInvoice, err := carol.AddInvoice(ctxt, invoiceReq) if err != nil { t.Fatalf("unable to generate carol invoice: %v", err) @@ -154,8 +153,7 @@ out: // Before we send the payment, ensure that the announcement of the new // channel has been processed by Alice. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err := net.Alice.WaitForNetworkChannelOpen(ctxt, chanPointBob); err != nil { + if err := net.Alice.WaitForNetworkChannelOpen(chanPointBob); err != nil { t.Fatalf("channel not seen by alice before timeout: %v", err) } diff --git a/lntest/itest/lnd_multi-hop-payments_test.go b/lntest/itest/lnd_multi-hop-payments_test.go index d1b578dc48..b19fead31d 100644 --- a/lntest/itest/lnd_multi-hop-payments_test.go +++ b/lntest/itest/lnd_multi-hop-payments_test.go @@ -4,8 +4,8 @@ import ( "context" "time" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/chainreg" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" @@ -107,8 +107,7 @@ func testMultiHopPayments(net *lntest.NetworkHarness, t *harnessTest) { Index: chanPoint.OutputIndex, } - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - err = node.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = node.WaitForNetworkChannelOpen(chanPoint) if err != nil { t.Fatalf("%s(%d): timeout waiting for "+ "channel(%s) open: %v", nodeNames[i], @@ -130,13 +129,11 @@ func testMultiHopPayments(net *lntest.NetworkHarness, t *harnessTest) { // We'll wait for all parties to recognize the new channels within the // network. - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - err = dave.WaitForNetworkChannelOpen(ctxt, chanPointDave) + err = dave.WaitForNetworkChannelOpen(chanPointDave) if err != nil { t.Fatalf("dave didn't advertise his channel: %v", err) } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = carol.WaitForNetworkChannelOpen(ctxt, chanPointCarol) + err = carol.WaitForNetworkChannelOpen(chanPointCarol) if err != nil { t.Fatalf("carol didn't advertise her channel in time: %v", err) @@ -372,6 +369,7 @@ func assertHtlcEvents(t *harnessTest, fwdCount, fwdFailCount, settleCount int, // type is different from the htlc event type (forward, link failure etc). func assertEventAndType(t *harnessTest, eventType routerrpc.HtlcEvent_EventType, client routerrpc.Router_SubscribeHtlcEventsClient) *routerrpc.HtlcEvent { + event, err := client.Recv() if err != nil { t.Fatalf("could not get event") diff --git a/lntest/itest/lnd_multi-hop_htlc_aggregation_test.go b/lntest/itest/lnd_multi-hop_htlc_aggregation_test.go index f17db42fe3..bdce297117 100644 --- a/lntest/itest/lnd_multi-hop_htlc_aggregation_test.go +++ b/lntest/itest/lnd_multi-hop_htlc_aggregation_test.go @@ -299,7 +299,6 @@ func testMultiHopHtlcAggregation(net *lntest.NetworkHarness, t *harnessTest, break } } - } // In case of anchor we expect all the timeout and success second @@ -311,7 +310,6 @@ func testMultiHopHtlcAggregation(net *lntest.NetworkHarness, t *harnessTest, } else { require.Len(t.t, timeoutTxs, numInvoices) require.Len(t.t, successTxs, numInvoices) - } // All mempool transactions should be spending from the commitment diff --git a/lntest/itest/lnd_multi-hop_htlc_local_chain_claim_test.go b/lntest/itest/lnd_multi-hop_htlc_local_chain_claim_test.go index 60e9aa7f8e..a7beae81d2 100644 --- a/lntest/itest/lnd_multi-hop_htlc_local_chain_claim_test.go +++ b/lntest/itest/lnd_multi-hop_htlc_local_chain_claim_test.go @@ -168,7 +168,7 @@ func testMultiHopHtlcLocalChainClaim(net *lntest.NetworkHarness, t *harnessTest, err = restartBob() require.NoError(t.t, err) - // After the force close transacion is mined, transactions will be + // After the force close transaction is mined, transactions will be // broadcast by both Bob and Carol. switch c { // Carol will broadcast her second level HTLC transaction and Bob will diff --git a/lntest/itest/lnd_multi-hop_htlc_local_timeout_test.go b/lntest/itest/lnd_multi-hop_htlc_local_timeout_test.go index 34a595a7b9..e68bca05e6 100644 --- a/lntest/itest/lnd_multi-hop_htlc_local_timeout_test.go +++ b/lntest/itest/lnd_multi-hop_htlc_local_timeout_test.go @@ -4,8 +4,8 @@ import ( "context" "time" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" diff --git a/lntest/itest/lnd_multi-hop_htlc_remote_chain_claim_test.go b/lntest/itest/lnd_multi-hop_htlc_remote_chain_claim_test.go index f1dee3435e..e1a8355142 100644 --- a/lntest/itest/lnd_multi-hop_htlc_remote_chain_claim_test.go +++ b/lntest/itest/lnd_multi-hop_htlc_remote_chain_claim_test.go @@ -184,7 +184,7 @@ func testMultiHopHtlcRemoteChainClaim(net *lntest.NetworkHarness, t *harnessTest err = restartBob() require.NoError(t.t, err) - // After the force close transacion is mined, we should expect Bob and + // After the force close transaction is mined, we should expect Bob and // Carol to broadcast some transactions depending on the channel // commitment type. switch c { diff --git a/lntest/itest/lnd_multi-hop_local_force_close_on_chain_htlc_timeout_test.go b/lntest/itest/lnd_multi-hop_local_force_close_on_chain_htlc_timeout_test.go index 591aefeb71..5f7fb6a1b2 100644 --- a/lntest/itest/lnd_multi-hop_local_force_close_on_chain_htlc_timeout_test.go +++ b/lntest/itest/lnd_multi-hop_local_force_close_on_chain_htlc_timeout_test.go @@ -4,8 +4,8 @@ import ( "context" "fmt" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lntest" diff --git a/lntest/itest/lnd_multi-hop_remote_force_close_on_chain_htlc_timeout_test.go b/lntest/itest/lnd_multi-hop_remote_force_close_on_chain_htlc_timeout_test.go index a88368a036..74b3ca367b 100644 --- a/lntest/itest/lnd_multi-hop_remote_force_close_on_chain_htlc_timeout_test.go +++ b/lntest/itest/lnd_multi-hop_remote_force_close_on_chain_htlc_timeout_test.go @@ -4,8 +4,8 @@ import ( "context" "fmt" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" diff --git a/lntest/itest/lnd_multi-hop_test.go b/lntest/itest/lnd_multi-hop_test.go index a2b11ab8ac..79d3439d8c 100644 --- a/lntest/itest/lnd_multi-hop_test.go +++ b/lntest/itest/lnd_multi-hop_test.go @@ -5,9 +5,9 @@ import ( "fmt" "testing" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc" "github.com/lightningnetwork/lnd/lntest" @@ -16,7 +16,6 @@ import ( ) func testMultiHopHtlcClaims(net *lntest.NetworkHarness, t *harnessTest) { - type testCase struct { name string test func(net *lntest.NetworkHarness, t *harnessTest, alice, @@ -99,7 +98,7 @@ func testMultiHopHtlcClaims(net *lntest.NetworkHarness, t *harnessTest) { "%s/%s ----\n", testName, subTest.name, ) - net.Alice.AddToLog(logLine) + net.Alice.AddToLogf(logLine) success := ht.t.Run(subTest.name, func(t *testing.T) { ht := newHarnessTest(t, net) @@ -180,7 +179,6 @@ func checkPaymentStatus(node *lntest.HarnessNode, preimage lntypes.Preimage, } switch status { - // If this expected status is SUCCEEDED, we expect the final preimage. case lnrpc.Payment_SUCCEEDED: if p.PaymentPreimage != preimage.String() { @@ -195,7 +193,6 @@ func checkPaymentStatus(node *lntest.HarnessNode, preimage lntypes.Preimage, p.PaymentPreimage) } } - } if !found { @@ -210,8 +207,6 @@ func createThreeHopNetwork(t *harnessTest, net *lntest.NetworkHarness, alice, bob *lntest.HarnessNode, carolHodl bool, c lnrpc.CommitmentType) ( *lnrpc.ChannelPoint, *lnrpc.ChannelPoint, *lntest.HarnessNode) { - ctxb := context.Background() - net.EnsureConnected(t.t, alice, bob) // Make sure there are enough utxos for anchoring. @@ -242,14 +237,12 @@ func createThreeHopNetwork(t *harnessTest, net *lntest.NetworkHarness, }, ) - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - err := alice.WaitForNetworkChannelOpen(ctxt, aliceChanPoint) + err := alice.WaitForNetworkChannelOpen(aliceChanPoint) if err != nil { t.Fatalf("alice didn't report channel: %v", err) } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = bob.WaitForNetworkChannelOpen(ctxt, aliceChanPoint) + err = bob.WaitForNetworkChannelOpen(aliceChanPoint) if err != nil { t.Fatalf("bob didn't report channel: %v", err) } @@ -289,18 +282,15 @@ func createThreeHopNetwork(t *harnessTest, net *lntest.NetworkHarness, FundingShim: bobFundingShim, }, ) - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = bob.WaitForNetworkChannelOpen(ctxt, bobChanPoint) + err = bob.WaitForNetworkChannelOpen(bobChanPoint) if err != nil { t.Fatalf("alice didn't report channel: %v", err) } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = carol.WaitForNetworkChannelOpen(ctxt, bobChanPoint) + err = carol.WaitForNetworkChannelOpen(bobChanPoint) if err != nil { t.Fatalf("bob didn't report channel: %v", err) } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = alice.WaitForNetworkChannelOpen(ctxt, bobChanPoint) + err = alice.WaitForNetworkChannelOpen(bobChanPoint) if err != nil { t.Fatalf("bob didn't report channel: %v", err) } diff --git a/lntest/itest/lnd_network_test.go b/lntest/itest/lnd_network_test.go index 559d34c76b..f2ab0bd587 100644 --- a/lntest/itest/lnd_network_test.go +++ b/lntest/itest/lnd_network_test.go @@ -3,10 +3,12 @@ package itest import ( "context" "fmt" + network "net" "strings" "time" "github.com/lightningnetwork/lnd" + "github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lntest" "github.com/stretchr/testify/require" @@ -109,7 +111,7 @@ func testReconnectAfterIPChange(net *lntest.NetworkHarness, t *harnessTest) { // withP2PPort is a helper closure used to set the P2P port that a node // should use. var withP2PPort = func(port int) lntest.NodeOption { - return func(cfg *lntest.NodeConfig) { + return func(cfg *lntest.BaseNodeConfig) { cfg.P2PPort = port } } @@ -278,3 +280,46 @@ func connect(ctxt context.Context, node *lntest.HarnessNode, } return nil } + +// testAddPeerConfig tests that the "--addpeer" config flag successfully adds +// a new peer. +func testAddPeerConfig(net *lntest.NetworkHarness, t *harnessTest) { + ctxb := context.Background() + + ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) + defer cancel() + alice := net.Alice + info, err := alice.GetInfo(ctxt, &lnrpc.GetInfoRequest{}) + require.NoError(t.t, err) + + alicePeerAddress := info.Uris[0] + + // Create a new node (Carol) with Alice as a peer. + args := []string{ + fmt.Sprintf("--addpeer=%v", alicePeerAddress), + } + carol := net.NewNode(t.t, "Carol", args) + defer shutdownAndAssert(net, t, carol) + + assertConnected(t, alice, carol) + + // If we list Carol's peers, Alice should already be + // listed as one, since we specified her using the + // addpeer flag. + ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) + defer cancel() + listPeersRequest := &lnrpc.ListPeersRequest{} + listPeersResp, err := carol.ListPeers(ctxt, listPeersRequest) + require.NoError(t.t, err) + + parsedPeerAddr, err := lncfg.ParseLNAddressString( + alicePeerAddress, "9735", network.ResolveTCPAddr, + ) + require.NoError(t.t, err) + + parsedKeyStr := fmt.Sprintf( + "%x", parsedPeerAddr.IdentityKey.SerializeCompressed(), + ) + + require.Equal(t.t, parsedKeyStr, listPeersResp.Peers[0].PubKey) +} diff --git a/lntest/itest/lnd_onchain_test.go b/lntest/itest/lnd_onchain_test.go index da4af2fa05..aa05272fdb 100644 --- a/lntest/itest/lnd_onchain_test.go +++ b/lntest/itest/lnd_onchain_test.go @@ -5,9 +5,12 @@ import ( "context" "fmt" "strings" + "time" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" "github.com/lightningnetwork/lnd/lntest" @@ -209,8 +212,7 @@ func testAnchorReservedValue(net *lntest.NetworkHarness, t *harnessTest) { // Alice opens a smaller channel. This works since it will have a // change output. aliceChanPoint1 := openChannelAndAssert( - t, net, alice, bob, - lntest.OpenChannelParams{ + t, net, alice, bob, lntest.OpenChannelParams{ Amt: chanAmt / 4, }, ) @@ -238,12 +240,10 @@ func testAnchorReservedValue(net *lntest.NetworkHarness, t *harnessTest) { aliceChanPoint1, aliceChanPoint2, aliceChanPoint3, } for _, chanPoint := range chanPoints { - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - err = alice.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = alice.WaitForNetworkChannelOpen(chanPoint) require.NoError(t.t, err) - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = bob.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = bob.WaitForNetworkChannelOpen(chanPoint) require.NoError(t.t, err) } @@ -326,7 +326,7 @@ func testAnchorReservedValue(net *lntest.NetworkHarness, t *harnessTest) { block = mineBlocks(t, net, 1, 1)[0] // The sweep transaction should have exactly one inputs as we only had - // the the single output from above in the wallet. + // the single output from above in the wallet. sweepTx = block.Transactions[1] if len(sweepTx.TxIn) != 1 { t.Fatalf("expected 1 inputs instead have %v", len(sweepTx.TxIn)) @@ -352,9 +352,12 @@ func testAnchorReservedValue(net *lntest.NetworkHarness, t *harnessTest) { t.Fatalf("Alice's balance did not increase after channel close") } + // Assert there are no open or pending channels anymore. + assertNumPendingChannels(t, alice, 0, 0) + assertNodeNumChannels(t, alice, 0) + // We'll wait for the balance to reflect that the channel has been // closed and the funds are in the wallet. - sweepReq = &lnrpc.SendCoinsRequest{ Addr: minerAddr.String(), SendAll: true, @@ -379,3 +382,239 @@ func testAnchorReservedValue(net *lntest.NetworkHarness, t *harnessTest) { t.Fatalf("expected 1 output instead have %v", len(sweepTx.TxOut)) } } + +// genAnchorSweep generates a "3rd party" anchor sweeping from an existing one. +// In practice, we just re-use the existing witness, and track on our own +// output producing a 1-in-1-out transaction. +func genAnchorSweep(t *harnessTest, net *lntest.NetworkHarness, + aliceAnchor *sweptOutput, anchorCsv uint32) *btcutil.Tx { + + // At this point, we have the transaction that Alice used to try to + // sweep her anchor. As this is actually just something anyone can + // spend, just need to find the input spending the anchor output, then + // we can swap the output address. + aliceAnchorTxIn := func() wire.TxIn { + sweepCopy := aliceAnchor.SweepTx.Copy() + for _, txIn := range sweepCopy.TxIn { + if txIn.PreviousOutPoint == aliceAnchor.OutPoint { + return *txIn + } + } + + t.Fatalf("anchor op not found") + return wire.TxIn{} + }() + + // We'll set the signature on the input to nil, and then set the + // sequence to 16 (the anchor CSV period). + aliceAnchorTxIn.Witness[0] = nil + aliceAnchorTxIn.Sequence = anchorCsv + + minerAddr, err := net.Miner.NewAddress() + if err != nil { + t.Fatalf("unable to get miner addr: %v", err) + } + addrScript, err := txscript.PayToAddrScript(minerAddr) + if err != nil { + t.Fatalf("unable to gen addr script: %v", err) + } + + // Now that we have the txIn, we can just make a new transaction that + // uses a different script for the output. + tx := wire.NewMsgTx(2) + tx.AddTxIn(&aliceAnchorTxIn) + tx.AddTxOut(&wire.TxOut{ + PkScript: addrScript, + Value: anchorSize - 1, + }) + + return btcutil.NewTx(tx) +} + +// testAnchorThirdPartySpend tests that if we force close a channel, but then +// don't sweep the anchor in time and a 3rd party spends it, that we remove any +// transactions that are a descendent of that sweep. +func testAnchorThirdPartySpend(net *lntest.NetworkHarness, t *harnessTest) { + // First, we'll create two new nodes that both default to anchor + // channels. + // + // NOTE: The itests differ here as anchors is default off vs the normal + // lnd binary. + args := nodeArgsForCommitType(lnrpc.CommitmentType_ANCHORS) + alice := net.NewNode(t.t, "Alice", args) + defer shutdownAndAssert(net, t, alice) + + bob := net.NewNode(t.t, "Bob", args) + defer shutdownAndAssert(net, t, bob) + + ctxb := context.Background() + net.ConnectNodes(t.t, alice, bob) + + // We'll fund our Alice with coins, as she'll be opening the channel. + // We'll fund her with *just* enough coins to open the channel. + const ( + firstChanSize = 1_000_000 + anchorFeeBuffer = 500_000 + ) + net.SendCoins(t.t, firstChanSize, alice) + + // We'll give Alice another spare UTXO as well so she can use it to + // help sweep all coins. + net.SendCoins(t.t, anchorFeeBuffer, alice) + + // Open the channel between the two nodes and wait for it to confirm + // fully. + aliceChanPoint1 := openChannelAndAssert( + t, net, alice, bob, lntest.OpenChannelParams{ + Amt: firstChanSize, + }, + ) + + // With the channel open, we'll actually immediately force close it. We + // don't care about network announcements here since there's no routing + // in this test. + _, _, err := net.CloseChannel(alice, aliceChanPoint1, true) + if err != nil { + t.Fatalf("unable to execute force channel closure: %v", err) + } + + // Now that the channel has been force closed, it should show up in the + // PendingChannels RPC under the waiting close section. + pendingChansRequest := &lnrpc.PendingChannelsRequest{} + ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) + pendingChanResp, err := alice.PendingChannels(ctxt, pendingChansRequest) + if err != nil { + t.Fatalf("unable to query for pending channels: %v", err) + } + err = checkNumWaitingCloseChannels(pendingChanResp, 1) + if err != nil { + t.Fatalf(err.Error()) + } + + // Get the normal channel outpoint so we can track it in the set of + // channels that are waiting to be closed. + fundingTxID, err := lnrpc.GetChanPointFundingTxid(aliceChanPoint1) + if err != nil { + t.Fatalf("unable to get txid: %v", err) + } + chanPoint := wire.OutPoint{ + Hash: *fundingTxID, + Index: aliceChanPoint1.OutputIndex, + } + waitingClose, err := findWaitingCloseChannel(pendingChanResp, &chanPoint) + if err != nil { + t.Fatalf(err.Error()) + } + + // At this point, the channel is waiting close, and we have both the + // commitment transaction and anchor sweep in the mempool. + const expectedTxns = 2 + sweepTxns, err := getNTxsFromMempool( + net.Miner.Client, expectedTxns, minerMempoolTimeout, + ) + require.NoError(t.t, err, "no sweep txns in miner mempool") + aliceCloseTx := waitingClose.Commitments.LocalTxid + _, aliceAnchor := findCommitAndAnchor(t, net, sweepTxns, aliceCloseTx) + + // We'll now mine _only_ the commitment force close transaction, as we + // want the anchor sweep to stay unconfirmed. + var emptyTime time.Time + forceCloseTxID, _ := chainhash.NewHashFromStr(aliceCloseTx) + commitTxn, err := net.Miner.Client.GetRawTransaction( + forceCloseTxID, + ) + if err != nil { + t.Fatalf("unable to get transaction: %v", err) + } + _, err = net.Miner.GenerateAndSubmitBlock( + []*btcutil.Tx{commitTxn}, -1, emptyTime, + ) + if err != nil { + t.Fatalf("unable to generate block: %v", err) + } + + // With the anchor output located, and the main commitment mined we'll + // instruct the wallet to send all coins in the wallet to a new address + // (to the miner), including unconfirmed change. + minerAddr, err := net.Miner.NewAddress() + if err != nil { + t.Fatalf("unable to create new miner addr: %v", err) + } + sweepReq := &lnrpc.SendCoinsRequest{ + Addr: minerAddr.String(), + SendAll: true, + MinConfs: 0, + SpendUnconfirmed: true, + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + sweepAllResp, err := alice.SendCoins(ctxt, sweepReq) + if err != nil { + t.Fatalf("unable to sweep coins: %v", err) + } + + // Both the original anchor sweep transaction, as well as the + // transaction we created to sweep all the coins from Alice's wallet + // should be found in her transaction store. + sweepAllTxID, _ := chainhash.NewHashFromStr(sweepAllResp.Txid) + assertTransactionInWallet(t.t, alice, aliceAnchor.SweepTx.TxHash()) + assertTransactionInWallet(t.t, alice, *sweepAllTxID) + + // Next, we'll shutdown Alice, and allow 16 blocks to pass so that the + // anchor output can be swept by anyone. Rather than use the normal API + // call, we'll generate a series of _empty_ blocks here. + aliceRestart, err := net.SuspendNode(alice) + if err != nil { + t.Fatalf("unable to shutdown alice: %v", err) + } + const anchorCsv = 16 + for i := 0; i < anchorCsv; i++ { + _, err := net.Miner.GenerateAndSubmitBlock(nil, -1, emptyTime) + if err != nil { + t.Fatalf("unable to generate block: %v", err) + } + } + + // Before we sweep the anchor, we'll restart Alice. + if err := aliceRestart(); err != nil { + t.Fatalf("unable to restart alice: %v", err) + } + + // Now that the channel has been closed, and Alice has an unconfirmed + // transaction spending the output produced by her anchor sweep, we'll + // mine a transaction that double spends the output. + thirdPartyAnchorSweep := genAnchorSweep(t, net, aliceAnchor, anchorCsv) + _, err = net.Miner.GenerateAndSubmitBlock( + []*btcutil.Tx{thirdPartyAnchorSweep}, -1, emptyTime, + ) + if err != nil { + t.Fatalf("unable to generate block: %v", err) + } + + // At this point, we should no longer find Alice's transaction that + // tried to sweep the anchor in her wallet. + assertTransactionNotInWallet(t.t, alice, aliceAnchor.SweepTx.TxHash()) + + // In addition, the transaction she sent to sweep all her coins to the + // miner also should no longer be found. + assertTransactionNotInWallet(t.t, alice, *sweepAllTxID) + + // The anchor should now show as being "lost", while the force close + // response is still present. + assertAnchorOutputLost(t, alice, chanPoint) + + // At this point Alice's CSV output should already be fully spent and + // the channel marked as being resolved. We mine a block first, as so + // far we've been generating custom blocks this whole time.. + commitSweepOp := wire.OutPoint{ + Hash: *forceCloseTxID, + Index: 1, + } + assertSpendingTxInMempool( + t, net.Miner.Client, minerMempoolTimeout, commitSweepOp, + ) + _, err = net.Miner.Client.Generate(1) + if err != nil { + t.Fatalf("unable to generate block: %v", err) + } + assertNumPendingChannels(t, alice, 0, 0) +} diff --git a/lntest/itest/lnd_open_channel_test.go b/lntest/itest/lnd_open_channel_test.go index 2864558c22..3b579214e8 100644 --- a/lntest/itest/lnd_open_channel_test.go +++ b/lntest/itest/lnd_open_channel_test.go @@ -6,10 +6,9 @@ import ( "time" "github.com/btcsuite/btcd/btcjson" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/integration/rpctest" - "github.com/btcsuite/btcd/rpcclient" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/funding" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lntest" @@ -34,16 +33,13 @@ func testOpenChannelAfterReorg(net *lntest.NetworkHarness, t *harnessTest) { ) // Set up a new miner that we can use to cause a reorg. - tempLogDir := fmt.Sprintf("%s/.tempminerlogs", lntest.GetLogDir()) + tempLogDir := ".tempminerlogs" logFilename := "output-open_channel_reorg-temp_miner.log" - tempMiner, tempMinerCleanUp, err := lntest.NewMiner( - tempLogDir, logFilename, harnessNetParams, - &rpcclient.NotificationHandlers{}, lntest.GetBtcdBinary(), - ) + tempMiner, err := lntest.NewTempMiner(tempLogDir, logFilename) require.NoError(t.t, err, "failed to create temp miner") defer func() { require.NoError( - t.t, tempMinerCleanUp(), + t.t, tempMiner.Stop(), "failed to clean up temp miner", ) }() @@ -61,7 +57,7 @@ func testOpenChannelAfterReorg(net *lntest.NetworkHarness, t *harnessTest) { if err != nil { t.Fatalf("unable to remove node: %v", err) } - nodeSlice := []*rpctest.Harness{net.Miner, tempMiner} + nodeSlice := []*rpctest.Harness{net.Miner.Harness, tempMiner.Harness} if err := rpctest.JoinNodes(nodeSlice, rpctest.Blocks); err != nil { t.Fatalf("unable to join node on blocks: %v", err) } @@ -142,14 +138,12 @@ func testOpenChannelAfterReorg(net *lntest.NetworkHarness, t *harnessTest) { // Wait for Alice and Bob to recognize and advertise the new channel // generated above. - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - err = net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = net.Alice.WaitForNetworkChannelOpen(chanPoint) if err != nil { t.Fatalf("alice didn't advertise channel before "+ "timeout: %v", err) } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.Bob.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = net.Bob.WaitForNetworkChannelOpen(chanPoint) if err != nil { t.Fatalf("bob didn't advertise channel before "+ "timeout: %v", err) @@ -159,7 +153,7 @@ func testOpenChannelAfterReorg(net *lntest.NetworkHarness, t *harnessTest) { req := &lnrpc.ChannelGraphRequest{ IncludeUnannounced: true, } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) chanGraph, err := net.Alice.DescribeGraph(ctxt, req) if err != nil { t.Fatalf("unable to query for alice's routing table: %v", err) @@ -188,7 +182,7 @@ func testOpenChannelAfterReorg(net *lntest.NetworkHarness, t *harnessTest) { t.Fatalf("unable to remove node: %v", err) } - nodes := []*rpctest.Harness{tempMiner, net.Miner} + nodes := []*rpctest.Harness{tempMiner.Harness, net.Miner.Harness} if err := rpctest.JoinNodes(nodes, rpctest.Blocks); err != nil { t.Fatalf("unable to join node on blocks: %v", err) } diff --git a/lntest/itest/lnd_payment_test.go b/lntest/itest/lnd_payment_test.go index aef6158c08..f67910c5ed 100644 --- a/lntest/itest/lnd_payment_test.go +++ b/lntest/itest/lnd_payment_test.go @@ -9,7 +9,7 @@ import ( "reflect" "time" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" @@ -69,12 +69,11 @@ func testListPayments(net *lntest.NetworkHarness, t *harnessTest) { // Wait for Alice to recognize and advertise the new channel generated // above. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err = net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint); err != nil { + if err = net.Alice.WaitForNetworkChannelOpen(chanPoint); err != nil { t.Fatalf("alice didn't advertise channel before "+ "timeout: %v", err) } - if err = net.Bob.WaitForNetworkChannelOpen(ctxt, chanPoint); err != nil { + if err = net.Bob.WaitForNetworkChannelOpen(chanPoint); err != nil { t.Fatalf("bob didn't advertise channel before "+ "timeout: %v", err) } @@ -292,8 +291,7 @@ func runAsyncPayments(net *lntest.NetworkHarness, t *harnessTest, alice, } // Wait for Alice to receive the channel edge from the funding manager. - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - err = alice.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = alice.WaitForNetworkChannelOpen(chanPoint) if err != nil { t.Fatalf("alice didn't see the alice->bob channel before "+ "timeout: %v", err) @@ -307,7 +305,7 @@ func runAsyncPayments(net *lntest.NetworkHarness, t *harnessTest, alice, for i := 0; i < numInvoices; i++ { payReq := bobPayReqs[i] go func() { - ctxt, _ = context.WithTimeout(ctxb, lntest.AsyncBenchmarkTimeout) + ctxt, _ := context.WithTimeout(ctxb, lntest.AsyncBenchmarkTimeout) stream, err := alice.RouterClient.SendPaymentV2( ctxt, &routerrpc.SendPaymentRequest{ @@ -465,12 +463,11 @@ func testBidirectionalAsyncPayments(net *lntest.NetworkHarness, t *harnessTest) } // Wait for Alice to receive the channel edge from the funding manager. - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - if err = net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint); err != nil { + if err = net.Alice.WaitForNetworkChannelOpen(chanPoint); err != nil { t.Fatalf("alice didn't see the alice->bob channel before "+ "timeout: %v", err) } - if err = net.Bob.WaitForNetworkChannelOpen(ctxt, chanPoint); err != nil { + if err = net.Bob.WaitForNetworkChannelOpen(chanPoint); err != nil { t.Fatalf("bob didn't see the bob->alice channel before "+ "timeout: %v", err) } @@ -478,7 +475,7 @@ func testBidirectionalAsyncPayments(net *lntest.NetworkHarness, t *harnessTest) // Reset mission control to prevent previous payment results from // interfering with this test. A new channel has been opened, but // mission control operates on node pairs. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) _, err = net.Alice.RouterClient.ResetMissionControl( ctxt, &routerrpc.ResetMissionControlRequest{}, ) @@ -680,8 +677,7 @@ func testInvoiceSubscriptions(net *lntest.NetworkHarness, t *harnessTest) { // Wait for the channel to be recognized by both Alice and Bob before // continuing the rest of the test. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = net.Alice.WaitForNetworkChannelOpen(chanPoint) if err != nil { // TODO(roasbeef): will need to make num blocks to advertise a // node param diff --git a/lntest/itest/lnd_psbt_test.go b/lntest/itest/lnd_psbt_test.go index afdef552a1..d547fea300 100644 --- a/lntest/itest/lnd_psbt_test.go +++ b/lntest/itest/lnd_psbt_test.go @@ -6,10 +6,19 @@ import ( "crypto/rand" "fmt" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/hdkeychain" + "github.com/btcsuite/btcd/btcutil/psbt" + "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/funding" + "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lnrpc/signrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" "github.com/lightningnetwork/lnd/lntest" "github.com/stretchr/testify/require" @@ -38,6 +47,12 @@ func runPsbtChanFunding(net *lntest.NetworkHarness, t *harnessTest, carol, dave *lntest.HarnessNode) { ctxb := context.Background() + + // Everything we do here should be done within a second or two, so we + // can just keep a single timeout context around for all calls. + ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) + defer cancel() + const chanSize = funding.MaxBtcFundingAmount net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, dave) @@ -61,8 +76,6 @@ func runPsbtChanFunding(net *lntest.NetworkHarness, t *harnessTest, carol, // Now that we have the pending channel ID, Carol will open the channel // by specifying a PSBT shim. We use the NoPublish flag here to avoid // publishing the whole batch TX too early. - ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) - defer cancel() chanUpdates, tempPsbt, err := openChannelPsbt( ctxt, carol, dave, lntest.OpenChannelParams{ Amt: chanSize, @@ -81,8 +94,6 @@ func runPsbtChanFunding(net *lntest.NetworkHarness, t *harnessTest, carol, // Let's add a second channel to the batch. This time between Carol and // Alice. We will publish the batch TX once this channel funding is // complete. - ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) - defer cancel() chanUpdates2, psbtBytes2, err := openChannelPsbt( ctxt, carol, net.Alice, lntest.OpenChannelParams{ Amt: chanSize, @@ -102,8 +113,6 @@ func runPsbtChanFunding(net *lntest.NetworkHarness, t *harnessTest, carol, // We'll now ask Dave's wallet to fund the PSBT for us. This will return // a packet with inputs and outputs set but without any witness data. // This is exactly what we need for the next step. - ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) - defer cancel() fundReq := &walletrpc.FundPsbtRequest{ Template: &walletrpc.FundPsbtRequest_Psbt{ Psbt: psbtBytes2, @@ -138,8 +147,6 @@ func runPsbtChanFunding(net *lntest.NetworkHarness, t *harnessTest, carol, // Now we'll ask Dave's wallet to sign the PSBT so we can finish the // funding flow. - ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) - defer cancel() finalizeReq := &walletrpc.FinalizePsbtRequest{ FundedPsbt: fundResp.FundedPsbt, } @@ -159,8 +166,6 @@ func runPsbtChanFunding(net *lntest.NetworkHarness, t *harnessTest, carol, // Consume the "channel pending" update. This waits until the funding // transaction was fully compiled. - ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) - defer cancel() updateResp, err := receiveChanUpdate(ctxt, chanUpdates) require.NoError(t.t, err) upd, ok := updateResp.Update.(*lnrpc.OpenStatusUpdate_ChanPending) @@ -193,8 +198,6 @@ func runPsbtChanFunding(net *lntest.NetworkHarness, t *harnessTest, carol, // Consume the "channel pending" update for the second channel. This // waits until the funding transaction was fully compiled and in this // case published. - ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) - defer cancel() updateResp2, err := receiveChanUpdate(ctxt, chanUpdates2) require.NoError(t.t, err) upd2, ok := updateResp2.Update.(*lnrpc.OpenStatusUpdate_ChanPending) @@ -215,18 +218,14 @@ func runPsbtChanFunding(net *lntest.NetworkHarness, t *harnessTest, carol, txHash := finalTx.TxHash() block := mineBlocks(t, net, 6, 1)[0] assertTxInBlock(t, block, &txHash) - ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) - defer cancel() - err = carol.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = carol.WaitForNetworkChannelOpen(chanPoint) require.NoError(t.t, err) - err = carol.WaitForNetworkChannelOpen(ctxt, chanPoint2) + err = carol.WaitForNetworkChannelOpen(chanPoint2) require.NoError(t.t, err) // With the channel open, ensure that it is counted towards Carol's // total channel balance. balReq := &lnrpc.ChannelBalanceRequest{} - ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) - defer cancel() balRes, err := carol.ChannelBalance(ctxt, balReq) require.NoError(t.t, err) require.NotEqual(t.t, int64(0), balRes.LocalBalance.Sat) @@ -238,7 +237,6 @@ func runPsbtChanFunding(net *lntest.NetworkHarness, t *harnessTest, carol, Memo: "new chans", Value: int64(payAmt), } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) resp, err := dave.AddInvoice(ctxt, invoice) require.NoError(t.t, err) err = completePaymentRequests( @@ -261,6 +259,11 @@ func testPsbtChanFundingExternal(net *lntest.NetworkHarness, t *harnessTest) { ctxb := context.Background() const chanSize = funding.MaxBtcFundingAmount + // Everything we do here should be done within a second or two, so we + // can just keep a single timeout context around for all calls. + ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) + defer cancel() + // First, we'll create two new nodes that we'll use to open channels // between for this test. Both these nodes have an empty wallet as Alice // will be funding the channel. @@ -290,8 +293,6 @@ func testPsbtChanFundingExternal(net *lntest.NetworkHarness, t *harnessTest) { // Now that we have the pending channel ID, Carol will open the channel // by specifying a PSBT shim. We use the NoPublish flag here to avoid // publishing the whole batch TX too early. - ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) - defer cancel() chanUpdates, tempPsbt, err := openChannelPsbt( ctxt, carol, dave, lntest.OpenChannelParams{ Amt: chanSize, @@ -310,8 +311,6 @@ func testPsbtChanFundingExternal(net *lntest.NetworkHarness, t *harnessTest) { // Let's add a second channel to the batch. This time between Carol and // Alice. We will publish the batch TX once this channel funding is // complete. - ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) - defer cancel() chanUpdates2, psbtBytes2, err := openChannelPsbt( ctxt, carol, net.Alice, lntest.OpenChannelParams{ Amt: chanSize, @@ -331,8 +330,6 @@ func testPsbtChanFundingExternal(net *lntest.NetworkHarness, t *harnessTest) { // We'll now ask Alice's wallet to fund the PSBT for us. This will // return a packet with inputs and outputs set but without any witness // data. This is exactly what we need for the next step. - ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) - defer cancel() fundReq := &walletrpc.FundPsbtRequest{ Template: &walletrpc.FundPsbtRequest_Psbt{ Psbt: psbtBytes2, @@ -375,8 +372,6 @@ func testPsbtChanFundingExternal(net *lntest.NetworkHarness, t *harnessTest) { // Consume the "channel pending" update. This waits until the funding // transaction was fully compiled for both channels. - ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) - defer cancel() updateResp, err := receiveChanUpdate(ctxt, chanUpdates) require.NoError(t.t, err) upd, ok := updateResp.Update.(*lnrpc.OpenStatusUpdate_ChanPending) @@ -403,8 +398,6 @@ func testPsbtChanFundingExternal(net *lntest.NetworkHarness, t *harnessTest) { // Now we'll ask Alice's wallet to sign the PSBT so we can finish the // funding flow. - ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) - defer cancel() finalizeReq := &walletrpc.FinalizePsbtRequest{ FundedPsbt: fundResp.FundedPsbt, } @@ -431,18 +424,14 @@ func testPsbtChanFundingExternal(net *lntest.NetworkHarness, t *harnessTest) { // for the new channel to be propagated through the network. block := mineBlocks(t, net, 6, 1)[0] assertTxInBlock(t, block, &txHash) - ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) - defer cancel() - err = carol.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = carol.WaitForNetworkChannelOpen(chanPoint) require.NoError(t.t, err) - err = carol.WaitForNetworkChannelOpen(ctxt, chanPoint2) + err = carol.WaitForNetworkChannelOpen(chanPoint2) require.NoError(t.t, err) // With the channel open, ensure that it is counted towards Carol's // total channel balance. balReq := &lnrpc.ChannelBalanceRequest{} - ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) - defer cancel() balRes, err := carol.ChannelBalance(ctxt, balReq) require.NoError(t.t, err) require.NotEqual(t.t, int64(0), balRes.LocalBalance.Sat) @@ -454,7 +443,6 @@ func testPsbtChanFundingExternal(net *lntest.NetworkHarness, t *harnessTest) { Memo: "new chans", Value: int64(payAmt), } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) resp, err := dave.AddInvoice(ctxt, invoice) require.NoError(t.t, err) err = completePaymentRequests( @@ -470,6 +458,595 @@ func testPsbtChanFundingExternal(net *lntest.NetworkHarness, t *harnessTest) { closeChannelAndAssert(t, net, carol, chanPoint2, false) } +// testPsbtChanFundingSingleStep checks whether PSBT funding works also when the +// wallet of both nodes are empty and one of them uses PSBT and an external +// wallet to fund the channel while creating reserve output in the same +// transaction. +func testPsbtChanFundingSingleStep(net *lntest.NetworkHarness, t *harnessTest) { + ctxb := context.Background() + const chanSize = funding.MaxBtcFundingAmount + + // Everything we do here should be done within a second or two, so we + // can just keep a single timeout context around for all calls. + ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) + defer cancel() + + args := nodeArgsForCommitType(lnrpc.CommitmentType_ANCHORS) + + // First, we'll create two new nodes that we'll use to open channels + // between for this test. But in this case both nodes have an empty + // wallet. + carol := net.NewNode(t.t, "carol", args) + defer shutdownAndAssert(net, t, carol) + + dave := net.NewNode(t.t, "dave", args) + defer shutdownAndAssert(net, t, dave) + + net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, net.Alice) + + // Get new address for anchor reserve. + reserveAddrReq := &lnrpc.NewAddressRequest{ + Type: lnrpc.AddressType_WITNESS_PUBKEY_HASH, + } + addrResp, err := carol.NewAddress(ctxb, reserveAddrReq) + require.NoError(t.t, err) + reserveAddr, err := btcutil.DecodeAddress(addrResp.Address, harnessNetParams) + require.NoError(t.t, err) + reserveAddrScript, err := txscript.PayToAddrScript(reserveAddr) + require.NoError(t.t, err) + + // Before we start the test, we'll ensure both sides are connected so + // the funding flow can be properly executed. + net.EnsureConnected(t.t, carol, dave) + + // At this point, we can begin our PSBT channel funding workflow. We'll + // start by generating a pending channel ID externally that will be used + // to track this new funding type. + var pendingChanID [32]byte + _, err = rand.Read(pendingChanID[:]) + require.NoError(t.t, err) + + // Now that we have the pending channel ID, Carol will open the channel + // by specifying a PSBT shim. + chanUpdates, tempPsbt, err := openChannelPsbt( + ctxt, carol, dave, lntest.OpenChannelParams{ + Amt: chanSize, + FundingShim: &lnrpc.FundingShim{ + Shim: &lnrpc.FundingShim_PsbtShim{ + PsbtShim: &lnrpc.PsbtShim{ + PendingChanId: pendingChanID[:], + NoPublish: false, + }, + }, + }, + }, + ) + require.NoError(t.t, err) + + decodedPsbt, err := psbt.NewFromRawBytes(bytes.NewReader(tempPsbt), false) + require.NoError(t.t, err) + + reserveTxOut := wire.TxOut{ + Value: 10000, + PkScript: reserveAddrScript, + } + + decodedPsbt.UnsignedTx.TxOut = append( + decodedPsbt.UnsignedTx.TxOut, &reserveTxOut, + ) + decodedPsbt.Outputs = append(decodedPsbt.Outputs, psbt.POutput{}) + + var psbtBytes bytes.Buffer + err = decodedPsbt.Serialize(&psbtBytes) + require.NoError(t.t, err) + + fundReq := &walletrpc.FundPsbtRequest{ + Template: &walletrpc.FundPsbtRequest_Psbt{ + Psbt: psbtBytes.Bytes(), + }, + Fees: &walletrpc.FundPsbtRequest_SatPerVbyte{ + SatPerVbyte: 2, + }, + } + fundResp, err := net.Alice.WalletKitClient.FundPsbt(ctxt, fundReq) + require.NoError(t.t, err) + + // Make sure the wallets are actually empty + unspentCarol, err := carol.ListUnspent(ctxb, &lnrpc.ListUnspentRequest{}) + require.NoError(t.t, err) + require.Len(t.t, unspentCarol.Utxos, 0) + + unspentDave, err := dave.ListUnspent(ctxb, &lnrpc.ListUnspentRequest{}) + require.NoError(t.t, err) + require.Len(t.t, unspentDave.Utxos, 0) + + // We have a PSBT that has no witness data yet, which is exactly what we + // need for the next step: Verify the PSBT with the funding intents. + _, err = carol.FundingStateStep(ctxb, &lnrpc.FundingTransitionMsg{ + Trigger: &lnrpc.FundingTransitionMsg_PsbtVerify{ + PsbtVerify: &lnrpc.FundingPsbtVerify{ + PendingChanId: pendingChanID[:], + FundedPsbt: fundResp.FundedPsbt, + }, + }, + }) + require.NoError(t.t, err) + + // Now we'll ask Alice's wallet to sign the PSBT so we can finish the + // funding flow. + finalizeReq := &walletrpc.FinalizePsbtRequest{ + FundedPsbt: fundResp.FundedPsbt, + } + finalizeRes, err := net.Alice.WalletKitClient.FinalizePsbt(ctxt, finalizeReq) + require.NoError(t.t, err) + + // We've signed our PSBT now, let's pass it to the intent again. + _, err = carol.FundingStateStep(ctxb, &lnrpc.FundingTransitionMsg{ + Trigger: &lnrpc.FundingTransitionMsg_PsbtFinalize{ + PsbtFinalize: &lnrpc.FundingPsbtFinalize{ + PendingChanId: pendingChanID[:], + SignedPsbt: finalizeRes.SignedPsbt, + }, + }, + }) + require.NoError(t.t, err) + + // Consume the "channel pending" update. This waits until the funding + // transaction was fully compiled. + updateResp, err := receiveChanUpdate(ctxt, chanUpdates) + require.NoError(t.t, err) + upd, ok := updateResp.Update.(*lnrpc.OpenStatusUpdate_ChanPending) + require.True(t.t, ok) + chanPoint := &lnrpc.ChannelPoint{ + FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{ + FundingTxidBytes: upd.ChanPending.Txid, + }, + OutputIndex: upd.ChanPending.OutputIndex, + } + + var finalTx wire.MsgTx + err = finalTx.Deserialize(bytes.NewReader(finalizeRes.RawFinalTx)) + require.NoError(t.t, err) + + txHash := finalTx.TxHash() + block := mineBlocks(t, net, 6, 1)[0] + assertTxInBlock(t, block, &txHash) + err = carol.WaitForNetworkChannelOpen(chanPoint) + require.NoError(t.t, err) + + // Next, to make sure the channel functions as normal, we'll make some + // payments within the channel. + payAmt := btcutil.Amount(100000) + invoice := &lnrpc.Invoice{ + Memo: "new chans", + Value: int64(payAmt), + } + resp, err := dave.AddInvoice(ctxt, invoice) + require.NoError(t.t, err) + err = completePaymentRequests( + carol, carol.RouterClient, []string{resp.PaymentRequest}, + true, + ) + require.NoError(t.t, err) + + // To conclude, we'll close the newly created channel between Carol and + // Dave. This function will also block until the channel is closed and + // will additionally assert the relevant channel closing post + // conditions. + closeChannelAndAssert(t, net, carol, chanPoint, false) +} + +// testSignPsbt tests that the SignPsbt RPC works correctly. +func testSignPsbt(net *lntest.NetworkHarness, t *harnessTest) { + runSignPsbtSegWitV0P2WKH(t, net, net.Alice) + runSignPsbtSegWitV1KeySpendBip86(t, net, net.Alice) + runSignPsbtSegWitV1KeySpendRootHash(t, net, net.Alice) + runSignPsbtSegWitV1ScriptSpend(t, net, net.Alice) +} + +// runSignPsbtSegWitV0P2WKH tests that the SignPsbt RPC works correctly for a +// SegWit v0 p2wkh input. +func runSignPsbtSegWitV0P2WKH(t *harnessTest, net *lntest.NetworkHarness, + alice *lntest.HarnessNode) { + + // Everything we do here should be done within a second or two, so we + // can just keep a single timeout context around for all calls. + ctxb := context.Background() + ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) + defer cancel() + + // We test that we can sign a PSBT that spends funds from an input that + // the wallet doesn't know about. To set up that test case, we first + // derive an address manually that the wallet won't be watching on + // chain. We can do that by exporting the account xpub of lnd's main + // account. + accounts, err := alice.WalletKitClient.ListAccounts( + ctxt, &walletrpc.ListAccountsRequest{}, + ) + require.NoError(t.t, err) + require.NotEmpty(t.t, accounts.Accounts) + + // We also need to parse the accounts, so we have easy access to the + // parsed derivation paths. + parsedAccounts, err := walletrpc.AccountsToWatchOnly(accounts.Accounts) + require.NoError(t.t, err) + + account := parsedAccounts[0] + xpub, err := hdkeychain.NewKeyFromString(account.Xpub) + require.NoError(t.t, err) + + const ( + changeIndex = 1 + addrIndex = 1337 + ) + fullDerivationPath := []uint32{ + hdkeychain.HardenedKeyStart + account.Purpose, + hdkeychain.HardenedKeyStart + account.CoinType, + hdkeychain.HardenedKeyStart + account.Account, + changeIndex, + addrIndex, + } + + // Let's simulate a change address. + change, err := xpub.DeriveNonStandard(changeIndex) // nolint:staticcheck + require.NoError(t.t, err) + + // At an index that we are certainly not watching in the wallet. + addrKey, err := change.DeriveNonStandard(addrIndex) // nolint:staticcheck + require.NoError(t.t, err) + + addrPubKey, err := addrKey.ECPubKey() + require.NoError(t.t, err) + pubKeyHash := btcutil.Hash160(addrPubKey.SerializeCompressed()) + witnessAddr, err := btcutil.NewAddressWitnessPubKeyHash( + pubKeyHash, harnessNetParams, + ) + require.NoError(t.t, err) + + pkScript, err := txscript.PayToAddrScript(witnessAddr) + require.NoError(t.t, err) + + // Send some funds to the output and then try to get a signature through + // the SignPsbt RPC to spend that output again. + assertPsbtSpend( + ctxt, t, net, alice, pkScript, + func(packet *psbt.Packet) { + in := &packet.Inputs[0] + in.Bip32Derivation = []*psbt.Bip32Derivation{{ + PubKey: addrPubKey.SerializeCompressed(), + Bip32Path: fullDerivationPath, + }} + in.SighashType = txscript.SigHashAll + }, + func(packet *psbt.Packet) { + require.Len(t.t, packet.Inputs, 1) + require.Len(t.t, packet.Inputs[0].PartialSigs, 1) + + partialSig := packet.Inputs[0].PartialSigs[0] + require.Equal( + t.t, partialSig.PubKey, + addrPubKey.SerializeCompressed(), + ) + require.Greater( + t.t, len(partialSig.Signature), ecdsa.MinSigLen, + ) + }, + ) +} + +// runSignPsbtSegWitV1KeySpendBip86 tests that the SignPsbt RPC works correctly +// for a SegWit v1 p2tr key spend BIP-0086 input. +func runSignPsbtSegWitV1KeySpendBip86(t *harnessTest, net *lntest.NetworkHarness, + alice *lntest.HarnessNode) { + + // Everything we do here should be done within a second or two, so we + // can just keep a single timeout context around for all calls. + ctxb := context.Background() + ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) + defer cancel() + + // Derive a key we can use for signing. + keyDesc, internalKey, fullDerivationPath := deriveInternalKey( + ctxt, t, alice, + ) + + // Our taproot key is a BIP0086 key spend only construction that just + // commits to the internal key and no root hash. + taprootKey := txscript.ComputeTaprootKeyNoScript(internalKey) + tapScriptAddr, err := btcutil.NewAddressTaproot( + schnorr.SerializePubKey(taprootKey), harnessNetParams, + ) + require.NoError(t.t, err) + p2trPkScript, err := txscript.PayToAddrScript(tapScriptAddr) + require.NoError(t.t, err) + + // Send some funds to the output and then try to get a signature through + // the SignPsbt RPC to spend that output again. + assertPsbtSpend( + ctxt, t, net, alice, p2trPkScript, + func(packet *psbt.Packet) { + in := &packet.Inputs[0] + in.Bip32Derivation = []*psbt.Bip32Derivation{{ + PubKey: keyDesc.RawKeyBytes, + Bip32Path: fullDerivationPath, + }} + in.TaprootBip32Derivation = []*psbt.TaprootBip32Derivation{{ + XOnlyPubKey: keyDesc.RawKeyBytes[1:], + Bip32Path: fullDerivationPath, + }} + in.SighashType = txscript.SigHashDefault + }, + func(packet *psbt.Packet) { + require.Len(t.t, packet.Inputs, 1) + require.Len( + t.t, packet.Inputs[0].TaprootKeySpendSig, 64, + ) + }, + ) +} + +// runSignPsbtSegWitV1KeySpendRootHash tests that the SignPsbt RPC works +// correctly for a SegWit v1 p2tr key spend that also commits to a script tree +// root hash. +func runSignPsbtSegWitV1KeySpendRootHash(t *harnessTest, + net *lntest.NetworkHarness, alice *lntest.HarnessNode) { + + // Everything we do here should be done within a second or two, so we + // can just keep a single timeout context around for all calls. + ctxb := context.Background() + ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) + defer cancel() + + // Derive a key we can use for signing. + keyDesc, internalKey, fullDerivationPath := deriveInternalKey( + ctxt, t, alice, + ) + + // Let's create a taproot script output now. This is a hash lock with a + // simple preimage of "foobar". + leaf1 := testScriptHashLock(t.t, []byte("foobar")) + + rootHash := leaf1.TapHash() + taprootKey := txscript.ComputeTaprootOutputKey(internalKey, rootHash[:]) + tapScriptAddr, err := btcutil.NewAddressTaproot( + schnorr.SerializePubKey(taprootKey), harnessNetParams, + ) + require.NoError(t.t, err) + p2trPkScript, err := txscript.PayToAddrScript(tapScriptAddr) + require.NoError(t.t, err) + + // Send some funds to the output and then try to get a signature through + // the SignPsbt RPC to spend that output again. + assertPsbtSpend( + ctxt, t, net, alice, p2trPkScript, + func(packet *psbt.Packet) { + in := &packet.Inputs[0] + in.Bip32Derivation = []*psbt.Bip32Derivation{{ + PubKey: keyDesc.RawKeyBytes, + Bip32Path: fullDerivationPath, + }} + in.TaprootBip32Derivation = []*psbt.TaprootBip32Derivation{{ + XOnlyPubKey: keyDesc.RawKeyBytes[1:], + Bip32Path: fullDerivationPath, + }} + in.TaprootMerkleRoot = rootHash[:] + in.SighashType = txscript.SigHashDefault + }, + func(packet *psbt.Packet) { + require.Len(t.t, packet.Inputs, 1) + require.Len( + t.t, packet.Inputs[0].TaprootKeySpendSig, 64, + ) + }, + ) +} + +// runSignPsbtSegWitV1ScriptSpend tests that the SignPsbt RPC works correctly +// for a SegWit v1 p2tr script spend. +func runSignPsbtSegWitV1ScriptSpend(t *harnessTest, + net *lntest.NetworkHarness, alice *lntest.HarnessNode) { + + // Everything we do here should be done within a second or two, so we + // can just keep a single timeout context around for all calls. + ctxb := context.Background() + ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) + defer cancel() + + // Derive a key we can use for signing. + keyDesc, internalKey, fullDerivationPath := deriveInternalKey( + ctxt, t, alice, + ) + + // Let's create a taproot script output now. This is a hash lock with a + // simple preimage of "foobar". + leaf1 := testScriptSchnorrSig(t.t, internalKey) + + rootHash := leaf1.TapHash() + taprootKey := txscript.ComputeTaprootOutputKey(internalKey, rootHash[:]) + tapScriptAddr, err := btcutil.NewAddressTaproot( + schnorr.SerializePubKey(taprootKey), harnessNetParams, + ) + require.NoError(t.t, err) + p2trPkScript, err := txscript.PayToAddrScript(tapScriptAddr) + require.NoError(t.t, err) + + // We need to assemble the control block to be able to spend through the + // script path. + tapscript := input.TapscriptPartialReveal(internalKey, leaf1, nil) + controlBlockBytes, err := tapscript.ControlBlock.ToBytes() + require.NoError(t.t, err) + + // Send some funds to the output and then try to get a signature through + // the SignPsbt RPC to spend that output again. + assertPsbtSpend( + ctxt, t, net, alice, p2trPkScript, + func(packet *psbt.Packet) { + in := &packet.Inputs[0] + in.Bip32Derivation = []*psbt.Bip32Derivation{{ + PubKey: keyDesc.RawKeyBytes, + Bip32Path: fullDerivationPath, + }} + in.TaprootBip32Derivation = []*psbt.TaprootBip32Derivation{{ + XOnlyPubKey: keyDesc.RawKeyBytes[1:], + Bip32Path: fullDerivationPath, + LeafHashes: [][]byte{rootHash[:]}, + }} + in.SighashType = txscript.SigHashDefault + in.TaprootLeafScript = []*psbt.TaprootTapLeafScript{{ + ControlBlock: controlBlockBytes, + Script: leaf1.Script, + LeafVersion: leaf1.LeafVersion, + }} + }, + func(packet *psbt.Packet) { + require.Len(t.t, packet.Inputs, 1) + require.Len( + t.t, packet.Inputs[0].TaprootScriptSpendSig, 1, + ) + + scriptSpendSig := packet.Inputs[0].TaprootScriptSpendSig[0] + require.Len(t.t, scriptSpendSig.Signature, 64) + }, + ) +} + +// assertPsbtSpend creates an output with the given pkScript on chain and then +// attempts to create a sweep transaction that is signed using the SignPsbt RPC +// that spends that output again. +func assertPsbtSpend(ctx context.Context, t *harnessTest, + net *lntest.NetworkHarness, alice *lntest.HarnessNode, pkScript []byte, + decorateUnsigned func(*psbt.Packet), verifySigned func(*psbt.Packet)) { + + // Let's send some coins to that address now. + utxo := &wire.TxOut{ + Value: 600_000, + PkScript: pkScript, + } + resp, err := alice.WalletKitClient.SendOutputs( + ctx, &walletrpc.SendOutputsRequest{ + Outputs: []*signrpc.TxOut{{ + Value: utxo.Value, + PkScript: utxo.PkScript, + }}, + MinConfs: 0, + SpendUnconfirmed: true, + SatPerKw: 2500, + }, + ) + require.NoError(t.t, err) + + prevTx := wire.NewMsgTx(2) + err = prevTx.Deserialize(bytes.NewReader(resp.RawTx)) + require.NoError(t.t, err) + + prevOut := -1 + for idx, txOut := range prevTx.TxOut { + if bytes.Equal(txOut.PkScript, pkScript) { + prevOut = idx + } + } + require.Greater(t.t, prevOut, -1) + + // Okay, we have everything we need to create a PSBT now. + pendingTx := &wire.MsgTx{ + Version: 2, + TxIn: []*wire.TxIn{{ + PreviousOutPoint: wire.OutPoint{ + Hash: prevTx.TxHash(), + Index: uint32(prevOut), + }, + }}, + // We send to the same address again, but deduct some fees. + TxOut: []*wire.TxOut{{ + Value: utxo.Value - 600, + PkScript: utxo.PkScript, + }}, + } + packet, err := psbt.NewFromUnsignedTx(pendingTx) + require.NoError(t.t, err) + + // Now let's add the meta information that we need for signing. + packet.Inputs[0].WitnessUtxo = utxo + packet.Inputs[0].NonWitnessUtxo = prevTx + decorateUnsigned(packet) + + // That's it, we should be able to sign the PSBT now. + var buf bytes.Buffer + err = packet.Serialize(&buf) + require.NoError(t.t, err) + + signResp, err := alice.WalletKitClient.SignPsbt( + ctx, &walletrpc.SignPsbtRequest{ + FundedPsbt: buf.Bytes(), + }, + ) + require.NoError(t.t, err) + + // Let's make sure we have a partial signature. + signedPacket, err := psbt.NewFromRawBytes( + bytes.NewReader(signResp.SignedPsbt), false, + ) + require.NoError(t.t, err) + + // Allow the caller to also verify (and potentially move) some of the + // returned fields. + verifySigned(signedPacket) + + // We should be able to finalize the PSBT and extract the final TX now. + err = psbt.MaybeFinalizeAll(signedPacket) + require.NoError(t.t, err) + + finalTx, err := psbt.Extract(signedPacket) + require.NoError(t.t, err) + + buf.Reset() + err = finalTx.Serialize(&buf) + require.NoError(t.t, err) + + // Publish the second transaction and then mine both of them. + _, err = alice.WalletKitClient.PublishTransaction( + ctx, &walletrpc.Transaction{ + TxHex: buf.Bytes(), + }, + ) + require.NoError(t.t, err) + + // Mine one block which should contain two transactions. + block := mineBlocks(t, net, 1, 2)[0] + firstTxHash := prevTx.TxHash() + secondTxHash := finalTx.TxHash() + assertTxInBlock(t, block, &firstTxHash) + assertTxInBlock(t, block, &secondTxHash) +} + +// deriveInternalKey derives a signing key and returns its descriptor, full +// derivation path and parsed public key. +func deriveInternalKey(ctx context.Context, t *harnessTest, + alice *lntest.HarnessNode) (*signrpc.KeyDescriptor, *btcec.PublicKey, + []uint32) { + + // For the next step, we need a public key. Let's use a special family + // for this. + keyDesc, err := alice.WalletKitClient.DeriveNextKey( + ctx, &walletrpc.KeyReq{KeyFamily: testTaprootKeyFamily}, + ) + require.NoError(t.t, err) + + // The DeriveNextKey returns a key from the internal 1017 scope. + fullDerivationPath := []uint32{ + hdkeychain.HardenedKeyStart + keychain.BIP0043Purpose, + hdkeychain.HardenedKeyStart + harnessNetParams.HDCoinType, + hdkeychain.HardenedKeyStart + uint32(keyDesc.KeyLoc.KeyFamily), + 0, + uint32(keyDesc.KeyLoc.KeyIndex), + } + + parsedPubKey, err := btcec.ParsePubKey(keyDesc.RawKeyBytes) + require.NoError(t.t, err) + + return keyDesc, parsedPubKey, fullDerivationPath +} + // openChannelPsbt attempts to open a channel between srcNode and destNode with // the passed channel funding parameters. If the passed context has a timeout, // then if the timeout is reached before the channel pending notification is @@ -483,11 +1060,11 @@ func openChannelPsbt(ctx context.Context, srcNode, destNode *lntest.HarnessNode, // Otherwise, we may run into a check within the funding manager that // prevents any funding workflows from being kicked off if the chain // isn't yet synced. - if err := srcNode.WaitForBlockchainSync(ctx); err != nil { + if err := srcNode.WaitForBlockchainSync(); err != nil { return nil, nil, fmt.Errorf("unable to sync srcNode chain: %v", err) } - if err := destNode.WaitForBlockchainSync(ctx); err != nil { + if err := destNode.WaitForBlockchainSync(); err != nil { return nil, nil, fmt.Errorf("unable to sync destNode chain: %v", err) } diff --git a/lntest/itest/lnd_recovery_test.go b/lntest/itest/lnd_recovery_test.go index af56c7a6f3..ce9cc79911 100644 --- a/lntest/itest/lnd_recovery_test.go +++ b/lntest/itest/lnd_recovery_test.go @@ -4,8 +4,8 @@ import ( "context" "math" - "github.com/btcsuite/btcutil" - "github.com/btcsuite/btcutil/hdkeychain" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/hdkeychain" "github.com/lightningnetwork/lnd/aezeed" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lntest" @@ -77,6 +77,7 @@ func testGetRecoveryInfo(net *lntest.NetworkHarness, t *harnessTest) { if recoveryMode != expectedRecoveryMode || recoveryFinished != expectedRecoveryFinished || progress != expectedProgress { + return false } @@ -219,15 +220,24 @@ func testOnchainFundRecovery(net *lntest.NetworkHarness, t *harnessTest) { Type: AddrTypeNestedPubkeyHash, } + newP2TRAddrReq := &lnrpc.NewAddressRequest{ + Type: AddrTypeTaprootPubkey, + } + // Generate and skip the number of addresses requested. + ctxt, cancel := context.WithTimeout( + ctxb, defaultTimeout, + ) + defer cancel() for i := 0; i < nskip; i++ { - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) _, err = node.NewAddress(ctxt, newP2WKHAddrReq) require.NoError(t.t, err) - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) _, err = node.NewAddress(ctxt, newNP2WKHAddrReq) require.NoError(t.t, err) + + _, err = node.NewAddress(ctxt, newP2TRAddrReq) + require.NoError(t.t, err) } // Send one BTC to the next P2WKH address. @@ -237,6 +247,11 @@ func testOnchainFundRecovery(net *lntest.NetworkHarness, t *harnessTest) { net.SendCoinsNP2WKH( t.t, btcutil.SatoshiPerBitcoin, node, ) + + // Add another whole coin to the P2TR address. + net.SendCoinsP2TR( + t.t, btcutil.SatoshiPerBitcoin, node, + ) } } @@ -255,26 +270,26 @@ func testOnchainFundRecovery(net *lntest.NetworkHarness, t *harnessTest) { // the two transactions above. We should also now have 2 UTXOs in the // wallet at the end of the recovery attempt. // - // After, we will generate and skip 9 P2WKH and NP2WKH addresses, and - // send another BTC to the subsequent 10th address in each derivation - // path. - restoreCheckBalance(2*btcutil.SatoshiPerBitcoin, 2, 1, skipAndSend(9)) + // After, we will generate and skip 9 P2WKH, NP2WKH and P2TR addresses, + // and send another BTC to the subsequent 10th address in each + // derivation path. + restoreCheckBalance(3*btcutil.SatoshiPerBitcoin, 3, 1, skipAndSend(9)) // Check that using a recovery window of 9 does not find the two most // recent txns. - restoreCheckBalance(2*btcutil.SatoshiPerBitcoin, 2, 9, nil) + restoreCheckBalance(3*btcutil.SatoshiPerBitcoin, 3, 9, nil) // Extending our recovery window to 10 should find the most recent - // transactions, leaving the wallet with 4 BTC total. We should also + // transactions, leaving the wallet with 6 BTC total. We should also // learn of the two additional UTXOs created above. // // After, we will skip 19 more addrs, sending to the 20th address past // our last found address, and repeat the same checks. - restoreCheckBalance(4*btcutil.SatoshiPerBitcoin, 4, 10, skipAndSend(19)) + restoreCheckBalance(6*btcutil.SatoshiPerBitcoin, 6, 10, skipAndSend(19)) // Check that recovering with a recovery window of 19 fails to find the // most recent transactions. - restoreCheckBalance(4*btcutil.SatoshiPerBitcoin, 4, 19, nil) + restoreCheckBalance(6*btcutil.SatoshiPerBitcoin, 6, 19, nil) // Ensure that using a recovery window of 20 succeeds with all UTXOs // found and the final balance reflected. @@ -284,11 +299,11 @@ func testOnchainFundRecovery(net *lntest.NetworkHarness, t *harnessTest) { // fixed bug in the wallet in which change addresses could at times be // created outside of the default key scopes. Recovery only used to be // performed on the default key scopes, so ideally this test case - // would've caught the bug earlier. Carol has received 6 BTC so far from - // the miner, we'll send 5 back to ensure all of her UTXOs get spent to + // would've caught the bug earlier. Carol has received 9 BTC so far from + // the miner, we'll send 8 back to ensure all of her UTXOs get spent to // avoid fee discrepancies and a change output is formed. - const minerAmt = 5 * btcutil.SatoshiPerBitcoin - const finalBalance = 6 * btcutil.SatoshiPerBitcoin + const minerAmt = 8 * btcutil.SatoshiPerBitcoin + const finalBalance = 9 * btcutil.SatoshiPerBitcoin promptChangeAddr := func(node *lntest.HarnessNode) { t.t.Helper() @@ -309,12 +324,13 @@ func testOnchainFundRecovery(net *lntest.NetworkHarness, t *harnessTest) { block := mineBlocks(t, net, 1, 1)[0] assertTxInBlock(t, block, txid) } - restoreCheckBalance(finalBalance, 6, 20, promptChangeAddr) + restoreCheckBalance(finalBalance, 9, 20, promptChangeAddr) - // We should expect a static fee of 27750 satoshis for spending 6 inputs - // (3 P2WPKH, 3 NP2WPKH) to two P2WPKH outputs. Carol should therefore - // only have one UTXO present (the change output) of 6 - 5 - fee BTC. - const fee = 27750 + // We should expect a static fee of 50100 satoshis for spending 9 inputs + // (3 P2WPKH, 3 NP2WPKH, 3 P2TR) to two P2WPKH outputs. Carol should + // therefore only have one UTXO present (the change output) of + // 9 - 8 - fee BTC. + const fee = 50100 restoreCheckBalance(finalBalance-minerAmt-fee, 1, 21, nil) // Last of all, make sure we can also restore a node from the extended diff --git a/lntest/itest/lnd_remote_signer_test.go b/lntest/itest/lnd_remote_signer_test.go index b1bae04cb3..1c5426a77b 100644 --- a/lntest/itest/lnd_remote_signer_test.go +++ b/lntest/itest/lnd_remote_signer_test.go @@ -5,7 +5,8 @@ import ( "fmt" "testing" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/hdkeychain" "github.com/btcsuite/btcwallet/waddrmgr" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnrpc" @@ -23,7 +24,7 @@ var ( accounts = []*lnrpc.WatchOnlyAccount{{ Purpose: waddrmgr.KeyScopeBIP0049Plus.Purpose, - // We always use the mainnet coin type for our BIP49/84 + // We always use the mainnet coin type for our BIP49/84/86 // addresses! CoinType: 0, Account: 0, @@ -32,7 +33,7 @@ var ( "FPhTq", }, { Purpose: waddrmgr.KeyScopeBIP0084.Purpose, - // We always use the mainnet coin type for our BIP49/84 + // We always use the mainnet coin type for our BIP49/84/86 // addresses! CoinType: 0, Account: 0, @@ -40,75 +41,14 @@ var ( "W2GgsFvsPN4RGmuoYtUgZ1LHZE8oftz7T4mzc1BxGt5rt8zJcVQi" + "KTPPV", }, { - Purpose: keychain.BIP0043Purpose, - CoinType: harnessNetParams.HDCoinType, - Account: uint32(keychain.KeyFamilyMultiSig), - Xpub: "tpubDDXFHr67Ro2tHKVWG2gNjjijKUH1Lyv5NKFYdJnuaLGVNBVwyV" + - "5AbykhR43iy8wYozEMbw2QfmAqZhb8gnuL5mm9sZh8YsR6FjGAbe" + - "w1xoT", - }, { - Purpose: keychain.BIP0043Purpose, - CoinType: harnessNetParams.HDCoinType, - Account: uint32(keychain.KeyFamilyRevocationBase), - Xpub: "tpubDDXFHr67Ro2tKkccDqNfDqZpd5wCs2n6XRV2Uh185DzCTbkDaE" + - "d9v7P837zZTYBNVfaRriuxgGVgxbGjDui4CKxyzBzwz4aAZxjn2P" + - "hNcQy", - }, { - Purpose: keychain.BIP0043Purpose, - CoinType: harnessNetParams.HDCoinType, - Account: uint32(keychain.KeyFamilyHtlcBase), - Xpub: "tpubDDXFHr67Ro2tNH4KH41i4oTsWfRjFigoH1Ee7urvHow51opH9x" + - "J7mu1qSPMPVtkVqQZ5tE4NTuFJPrbDqno7TQietyUDmPTwyVviJb" + - "GCwXk", - }, { - Purpose: keychain.BIP0043Purpose, - CoinType: harnessNetParams.HDCoinType, - Account: uint32(keychain.KeyFamilyPaymentBase), - Xpub: "tpubDDXFHr67Ro2tQj5Zvav2ALhkU6dRQAhEtNPnYJVBC8hs2U1A9e" + - "cqxRY3XTiJKBDD7e8tudhmTRs8aGWJAiAXJN5kXy3Hi6cmiwGWjX" + - "K5Cv5", - }, { - Purpose: keychain.BIP0043Purpose, - CoinType: harnessNetParams.HDCoinType, - Account: uint32(keychain.KeyFamilyDelayBase), - Xpub: "tpubDDXFHr67Ro2tSSR2LLBJtotxx2U45cuESLWKA72YT9td3SzVKH" + - "AptzDEx5chsUNZ4WRMY5h6HJxRSebjRatxQKX1uUsux1LvKS1wsf" + - "NJ2PH", - }, { - Purpose: keychain.BIP0043Purpose, - CoinType: harnessNetParams.HDCoinType, - Account: uint32(keychain.KeyFamilyRevocationRoot), - Xpub: "tpubDDXFHr67Ro2tTwzfWvNoMoPpZbxdMEfe1WhbXJxvXikGixPa4g" + - "gSRZeGx6T5yxVHTVT3rjVh35Veqsowj7emX8SZfXKDKDKcLduXCe" + - "WPUU3", - }, { - Purpose: keychain.BIP0043Purpose, - CoinType: harnessNetParams.HDCoinType, - Account: uint32(keychain.KeyFamilyNodeKey), - Xpub: "tpubDDXFHr67Ro2tYEDS2EByRedfsUoEwBtrzVbS1qdPrX6sAkUYGL" + - "rZWvMmQv8KZDZ4zd9r8WzM9bJ2nGp7XuNVC4w2EBtWg7i76gbrmu" + - "EWjQh", - }, { - Purpose: keychain.BIP0043Purpose, - CoinType: harnessNetParams.HDCoinType, - Account: uint32(keychain.KeyFamilyStaticBackup), - Xpub: "tpubDDXFHr67Ro2tYpwnFJEQaM8eAPM2UV5uY6gFgXeSzS5aC5T9Tf" + - "zXuawYKBbQMZJn8qHXLafY4tAutoda1aKP5h6Nbgy3swPbnhWbFj" + - "S5wnX", - }, { - Purpose: keychain.BIP0043Purpose, - CoinType: harnessNetParams.HDCoinType, - Account: uint32(keychain.KeyFamilyTowerSession), - Xpub: "tpubDDXFHr67Ro2tddKpAjUegXqt7EGxRXnHkeLbUkfuFMGbLJYgRp" + - "G4ew5pMmGg2nmcGmHFQ29w3juNhd8N5ZZ8HwJdymC4f5ukQLJ4yg" + - "9rEr3", - }, { - Purpose: keychain.BIP0043Purpose, - CoinType: harnessNetParams.HDCoinType, - Account: uint32(keychain.KeyFamilyTowerID), - Xpub: "tpubDDXFHr67Ro2tgE89V8ZdgMytC2Jq1iT9ttGhdzR1X7haQJNBmX" + - "t8kau6taC6DGASYzbrjmo9z9w6JQFcaLNqbhS2h2PVSzKf79j265" + - "Zi8hF", + Purpose: waddrmgr.KeyScopeBIP0086.Purpose, + // We always use the mainnet coin type for our BIP49/84/86 + // addresses! + CoinType: 0, + Account: 0, + Xpub: "tpubDDtdXpdJFU2zFKWHJwe5M2WtYtcV7qSWtKohT9VP9zarNSwKnm" + + "kwDQawsu1vUf9xwXhUDYXbdUqpcrRTn9bLyW4BAVRimZ4K7r5o1J" + + "S924u", }} ) @@ -166,6 +106,38 @@ func testRemoteSigner(net *lntest.NetworkHarness, t *harnessTest) { name: "psbt", fn: func(tt *harnessTest, wo, carol *lntest.HarnessNode) { runPsbtChanFunding(net, tt, carol, wo) + runSignPsbtSegWitV0P2WKH(tt, net, wo) + runSignPsbtSegWitV1KeySpendBip86(tt, net, wo) + runSignPsbtSegWitV1KeySpendRootHash(tt, net, wo) + runSignPsbtSegWitV1ScriptSpend(tt, net, wo) + }, + }, { + name: "sign output raw", + sendCoins: true, + fn: func(tt *harnessTest, wo, carol *lntest.HarnessNode) { + runSignOutputRaw(tt, net, wo) + }, + }, { + name: "taproot", + sendCoins: true, + fn: func(tt *harnessTest, wo, carol *lntest.HarnessNode) { + ctxt, cancel := context.WithTimeout( + ctxb, 3*defaultTimeout, + ) + defer cancel() + + testTaprootComputeInputScriptKeySpendBip86( + ctxt, tt, wo, net, + ) + testTaprootSignOutputRawScriptSpend(ctxt, tt, wo, net) + testTaprootSignOutputRawKeySpendBip86(ctxt, tt, wo, net) + testTaprootSignOutputRawKeySpendRootHash( + ctxt, tt, wo, net, + ) + testTaprootMuSig2KeySpendRootHash(ctxt, tt, wo, net) + testTaprootMuSig2ScriptSpend(ctxt, tt, wo, net) + testTaprootMuSig2KeySpendBip86(ctxt, tt, wo, net) + testTaprootMuSig2CombinedLeafKeySpend(ctxt, tt, wo, net) }, }} @@ -180,7 +152,7 @@ func testRemoteSigner(net *lntest.NetworkHarness, t *harnessTest) { password := []byte("itestpassword") var ( signerNodePubKey = nodePubKey - watchOnlyAccounts = accounts + watchOnlyAccounts = deriveCustomScopeAccounts(t.t) signer *lntest.HarnessNode err error ) @@ -237,10 +209,6 @@ func testRemoteSigner(net *lntest.NetworkHarness, t *harnessTest) { if subTest.sendCoins { net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, watchOnly) - assertAccountBalance( - t.t, signer, "default", - btcutil.SatoshiPerBitcoin, 0, - ) assertAccountBalance( t.t, watchOnly, "default", btcutil.SatoshiPerBitcoin, 0, @@ -264,3 +232,55 @@ func testRemoteSigner(net *lntest.NetworkHarness, t *harnessTest) { } } } + +// deriveCustomScopeAccounts derives the first 255 default accounts of the custom lnd +// internal key scope. +func deriveCustomScopeAccounts(t *testing.T) []*lnrpc.WatchOnlyAccount { + allAccounts := make([]*lnrpc.WatchOnlyAccount, 0, 255+len(accounts)) + allAccounts = append(allAccounts, accounts...) + + extendedRootKey, err := hdkeychain.NewKeyFromString(rootKey) + require.NoError(t, err) + + path := []uint32{ + keychain.BIP0043Purpose + hdkeychain.HardenedKeyStart, + harnessNetParams.HDCoinType + hdkeychain.HardenedKeyStart, + } + coinTypeKey, err := derivePath(extendedRootKey, path) + require.NoError(t, err) + for idx := uint32(0); idx <= 255; idx++ { + accountPath := []uint32{idx + hdkeychain.HardenedKeyStart} + accountKey, err := derivePath(coinTypeKey, accountPath) + require.NoError(t, err) + + accountXPub, err := accountKey.Neuter() + require.NoError(t, err) + + allAccounts = append(allAccounts, &lnrpc.WatchOnlyAccount{ + Purpose: keychain.BIP0043Purpose, + CoinType: harnessNetParams.HDCoinType, + Account: idx, + Xpub: accountXPub.String(), + }) + } + + return allAccounts +} + +// derivePath derives the given path from an extended key. +func derivePath(key *hdkeychain.ExtendedKey, path []uint32) ( + *hdkeychain.ExtendedKey, error) { + + var ( + currentKey = key + err error + ) + for _, pathPart := range path { + currentKey, err = currentKey.Derive(pathPart) + if err != nil { + return nil, err + } + } + + return currentKey, nil +} diff --git a/lntest/itest/lnd_res_handoff_test.go b/lntest/itest/lnd_res_handoff_test.go new file mode 100644 index 0000000000..36d74527ca --- /dev/null +++ b/lntest/itest/lnd_res_handoff_test.go @@ -0,0 +1,200 @@ +package itest + +import ( + "time" + + "github.com/btcsuite/btcd/btcutil" + "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lntest" + "github.com/lightningnetwork/lnd/lntest/wait" + "github.com/stretchr/testify/require" + "golang.org/x/net/context" +) + +// testResHandoff tests that the contractcourt is able to properly hand-off +// resolution messages to the switch. +func testResHandoff(net *lntest.NetworkHarness, t *harnessTest) { + const ( + chanAmt = btcutil.Amount(1000000) + paymentAmt = 50000 + ) + + ctxb := context.Background() + + // First we'll create a channel between Alice and Bob. + net.EnsureConnected(t.t, net.Alice, net.Bob) + + chanPointAlice := openChannelAndAssert( + t, net, net.Alice, net.Bob, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + defer closeChannelAndAssert(t, net, net.Alice, chanPointAlice, false) + + // Wait for Alice and Bob to receive the channel edge from the funding + // manager. + err := net.Alice.WaitForNetworkChannelOpen(chanPointAlice) + require.NoError(t.t, err) + + err = net.Bob.WaitForNetworkChannelOpen(chanPointAlice) + require.NoError(t.t, err) + + // Create a new node Carol that will be in hodl mode. This is used to + // trigger the behavior of checkRemoteDanglingActions in the + // contractcourt. This will cause Bob to fail the HTLC back to Alice. + carol := net.NewNode(t.t, "Carol", []string{"--hodl.commit"}) + defer shutdownAndAssert(net, t, carol) + + // Connect Bob to Carol. + net.ConnectNodes(t.t, net.Bob, carol) + + // Open a channel between Bob and Carol. + chanPointCarol := openChannelAndAssert( + t, net, net.Bob, carol, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + + // Wait for Bob and Carol to receive the channel edge from the funding + // manager. + err = net.Bob.WaitForNetworkChannelOpen(chanPointCarol) + require.NoError(t.t, err) + + err = carol.WaitForNetworkChannelOpen(chanPointCarol) + require.NoError(t.t, err) + + // Wait for Alice to see the channel edge in the graph. + err = net.Alice.WaitForNetworkChannelOpen(chanPointCarol) + require.NoError(t.t, err) + + // We'll create an invoice for Carol that Alice will attempt to pay. + // Since Carol is in hodl.commit mode, she won't send back any commit + // sigs. + carolPayReqs, _, _, err := createPayReqs( + carol, paymentAmt, 1, + ) + require.NoError(t.t, err) + + // Alice will now attempt to fulfill the invoice. + err = completePaymentRequests( + net.Alice, net.Alice.RouterClient, carolPayReqs, false, + ) + require.NoError(t.t, err) + + // Wait until Carol has received the Add, CommitSig from Bob, and has + // responded with a RevokeAndAck. We expect NumUpdates to be 1 meaning + // Carol's CommitHeight is 1. + err = wait.Predicate(func() bool { + carolInfo, err := getChanInfo(carol) + if err != nil { + return false + } + + return carolInfo.NumUpdates == 1 + }, defaultTimeout) + require.NoError(t.t, err) + + // Before we shutdown Alice, we'll assert that she only has 1 update. + err = wait.Predicate(func() bool { + aliceInfo, err := getChanInfo(net.Alice) + if err != nil { + return false + } + + return aliceInfo.NumUpdates == 1 + }, defaultTimeout) + require.NoError(t.t, err) + + // We'll shutdown Alice so that Bob can't connect to her. + restartAlice, err := net.SuspendNode(net.Alice) + require.NoError(t.t, err) + + // Bob will now force close his channel with Carol such that resolution + // messages are created and forwarded backwards to Alice. + _, _, err = net.CloseChannel(net.Bob, chanPointCarol, true) + require.NoError(t.t, err) + + // The channel should be listed in the PendingChannels result. + ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) + defer cancel() + + pendingChansRequest := &lnrpc.PendingChannelsRequest{} + pendingChanResp, err := net.Bob.PendingChannels( + ctxt, pendingChansRequest, + ) + require.NoError(t.t, err) + require.NoError(t.t, checkNumWaitingCloseChannels(pendingChanResp, 1)) + + // We'll mine a block to confirm the force close transaction and to + // advance Bob's contract state with Carol to StateContractClosed. + mineBlocks(t, net, 1, 1) + + // We sleep here so we can be sure that the hand-off has occurred from + // Bob's contractcourt to Bob's htlcswitch. This sleep could be removed + // if there was some feedback (i.e. API in switch) that allowed for + // querying the state of resolution messages. + time.Sleep(10 * time.Second) + + // Mine blocks until Bob has no waiting close channels. This tests + // that the circuit-deletion logic is skipped if a resolution message + // exists. + for { + _, err = net.Miner.Client.Generate(1) + require.NoError(t.t, err) + + pendingChanResp, err = net.Bob.PendingChannels( + ctxt, pendingChansRequest, + ) + require.NoError(t.t, err) + + isErr := checkNumForceClosedChannels(pendingChanResp, 0) + if isErr == nil { + break + } + + time.Sleep(150 * time.Millisecond) + } + + // We will now restart Bob so that we can test whether the resolution + // messages are re-forwarded on start-up. + restartBob, err := net.SuspendNode(net.Bob) + require.NoError(t.t, err) + + err = restartBob() + require.NoError(t.t, err) + + // We'll now also restart Alice and connect her with Bob. + err = restartAlice() + require.NoError(t.t, err) + + net.EnsureConnected(t.t, net.Alice, net.Bob) + + // We'll assert that Alice has received the failure resolution + // message. + err = wait.Predicate(func() bool { + aliceInfo, err := getChanInfo(net.Alice) + if err != nil { + return false + } + + return aliceInfo.NumUpdates == 2 + }, defaultTimeout) + require.NoError(t.t, err) + + // Assert that Alice's payment failed. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + paymentsResp, err := net.Alice.ListPayments( + ctxt, &lnrpc.ListPaymentsRequest{ + IncludeIncomplete: true, + }, + ) + require.NoError(t.t, err) + require.Equal(t.t, 1, len(paymentsResp.Payments)) + + htlcs := paymentsResp.Payments[0].Htlcs + + require.Equal(t.t, 1, len(htlcs)) + require.Equal(t.t, lnrpc.HTLCAttempt_FAILED, htlcs[0].Status) +} diff --git a/lntest/itest/lnd_rest_api_test.go b/lntest/itest/lnd_rest_api_test.go index 290b981715..c02e7da4ce 100644 --- a/lntest/itest/lnd_rest_api_test.go +++ b/lntest/itest/lnd_rest_api_test.go @@ -638,6 +638,16 @@ func wsTestPingPongTimeout(ht *harnessTest, net *lntest.NetworkHarness) { } }() + // The SubscribeInvoices call returns immediately after the gRPC/REST + // connection is established. But it can happen that the goroutine in + // lnd that actually registers the subscriber in the invoice backend + // didn't get any CPU time just yet. So we can run into the situation + // where we add our first invoice _before_ the subscription client is + // registered. If that happens, we'll never get notified about the + // invoice in question. So all we really can do is wait a bit here to + // make sure the subscription is registered correctly. + time.Sleep(500 * time.Millisecond) + // Let's create five invoices and wait for them to arrive. We'll wait // for at least one ping/pong cycle between each invoice. ctxb := context.Background() diff --git a/lntest/itest/lnd_revocation_test.go b/lntest/itest/lnd_revocation_test.go index 4846850b7a..a562c88d9e 100644 --- a/lntest/itest/lnd_revocation_test.go +++ b/lntest/itest/lnd_revocation_test.go @@ -8,8 +8,8 @@ import ( "testing" "time" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcutil" "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/funding" "github.com/lightningnetwork/lnd/lnrpc" @@ -24,8 +24,6 @@ import ( // retribution in the event that she fails immediately after detecting Bob's // breach txn in the mempool. func testRevokedCloseRetribution(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() - const ( chanAmt = funding.MaxBtcFundingAmount paymentAmt = 10000 @@ -71,8 +69,7 @@ func testRevokedCloseRetribution(net *lntest.NetworkHarness, t *harnessTest) { } // Wait for Carol to receive the channel edge from the funding manager. - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - err = carol.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = carol.WaitForNetworkChannelOpen(chanPoint) if err != nil { t.Fatalf("carol didn't see the carol->bob channel before "+ "timeout: %v", err) @@ -260,7 +257,6 @@ func testRevokedCloseRetribution(net *lntest.NetworkHarness, t *harnessTest) { // commitment output has zero-value. func testRevokedCloseRetributionZeroValueRemoteOutput(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() const ( chanAmt = funding.MaxBtcFundingAmount @@ -312,8 +308,7 @@ func testRevokedCloseRetributionZeroValueRemoteOutput(net *lntest.NetworkHarness } // Wait for Dave to receive the channel edge from the funding manager. - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - err = dave.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = dave.WaitForNetworkChannelOpen(chanPoint) if err != nil { t.Fatalf("dave didn't see the dave->carol channel before "+ "timeout: %v", err) @@ -475,7 +470,6 @@ func testRevokedCloseRetributionZeroValueRemoteOutput(net *lntest.NetworkHarness // remote party breaches before settling extended HTLCs. func testRevokedCloseRetributionRemoteHodl(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() const ( chanAmt = funding.MaxBtcFundingAmount @@ -559,8 +553,7 @@ func testRevokedCloseRetributionRemoteHodl(net *lntest.NetworkHarness, } // Wait for Dave to receive the channel edge from the funding manager. - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - err = dave.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = dave.WaitForNetworkChannelOpen(chanPoint) if err != nil { t.Fatalf("dave didn't see the dave->carol channel before "+ "timeout: %v", err) @@ -1017,8 +1010,7 @@ func testRevokedCloseRetributionAltruistWatchtowerCase( } // Wait for Dave to receive the channel edge from the funding manager. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = dave.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = dave.WaitForNetworkChannelOpen(chanPoint) if err != nil { t.Fatalf("dave didn't see the dave->carol channel before "+ "timeout: %v", err) @@ -1080,7 +1072,6 @@ func testRevokedCloseRetributionAltruistWatchtowerCase( ) if err != nil { return err - } if bkpStats == nil { return errors.New("no active backup sessions") diff --git a/lntest/itest/lnd_routing_test.go b/lntest/itest/lnd_routing_test.go index 8a6d40e6c5..4b431f1992 100644 --- a/lntest/itest/lnd_routing_test.go +++ b/lntest/itest/lnd_routing_test.go @@ -9,8 +9,8 @@ import ( "testing" "time" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/chainreg" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" @@ -138,8 +138,7 @@ func testSingleHopSendToRouteCase(net *lntest.NetworkHarness, t *harnessTest, Index: chanPoint.OutputIndex, } - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - err = node.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = node.WaitForNetworkChannelOpen(chanPoint) if err != nil { t.Fatalf("%s(%d): timeout waiting for "+ "channel(%s) open: %v", node.Name(), @@ -527,8 +526,7 @@ func runMultiHopSendToRoute(net *lntest.NetworkHarness, t *harnessTest, Index: chanPoint.OutputIndex, } - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - err = node.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = node.WaitForNetworkChannelOpen(chanPoint) if err != nil { t.Fatalf("%s(%d): timeout waiting for "+ "channel(%s) open: %v", nodeNames[i], @@ -567,8 +565,7 @@ func runMultiHopSendToRoute(net *lntest.NetworkHarness, t *harnessTest, // We'll wait for all parties to recognize the new channels within the // network. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = carol.WaitForNetworkChannelOpen(ctxt, chanPointBob) + err = carol.WaitForNetworkChannelOpen(chanPointBob) if err != nil { t.Fatalf("bob didn't advertise his channel in time: %v", err) } @@ -647,8 +644,7 @@ func testSendToRouteErrorPropagation(net *lntest.NetworkHarness, t *harnessTest) }, ) - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - err := net.Alice.WaitForNetworkChannelOpen(ctxt, chanPointAlice) + err := net.Alice.WaitForNetworkChannelOpen(chanPointAlice) if err != nil { t.Fatalf("alice didn't advertise her channel: %v", err) } @@ -678,8 +674,7 @@ func testSendToRouteErrorPropagation(net *lntest.NetworkHarness, t *harnessTest) Amt: chanAmt, }, ) - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = carol.WaitForNetworkChannelOpen(ctxt, chanPointCarol) + err = carol.WaitForNetworkChannelOpen(chanPointCarol) if err != nil { t.Fatalf("carol didn't advertise her channel: %v", err) } @@ -690,7 +685,7 @@ func testSendToRouteErrorPropagation(net *lntest.NetworkHarness, t *harnessTest) PubKey: charlie.PubKeyStr, Amt: int64(1), } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) fakeRoute, err := carol.QueryRoutes(ctxt, fakeReq) if err != nil { t.Fatalf("unable get fake route: %v", err) @@ -730,9 +725,8 @@ func testSendToRouteErrorPropagation(net *lntest.NetworkHarness, t *harnessTest) // At this place we should get an rpc error with notification // that edge is not found on hop(0) - if _, err := alicePayStream.Recv(); err != nil && strings.Contains(err.Error(), - "edge not found") { - + _, err = alicePayStream.Recv() + if err != nil && strings.Contains(err.Error(), "edge not found") { } else if err != nil { t.Fatalf("payment stream has been closed but fake route has consumed: %v", err) } @@ -842,8 +836,7 @@ func testPrivateChannels(net *lntest.NetworkHarness, t *harnessTest) { Index: chanPoint.OutputIndex, } - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - err = node.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = node.WaitForNetworkChannelOpen(chanPoint) if err != nil { t.Fatalf("%s(%d): timeout waiting for "+ "channel(%s) open: %v", nodeNames[i], @@ -1142,8 +1135,7 @@ func testInvoiceRoutingHints(net *lntest.NetworkHarness, t *harnessTest) { chanPointEve, } for i, chanPoint := range aliceChans { - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - err := net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint) + err := net.Alice.WaitForNetworkChannelOpen(chanPoint) if err != nil { t.Fatalf("timed out waiting for channel open %s: %v", chanNames[i], err) @@ -1263,14 +1255,12 @@ func testMultiHopOverPrivateChannels(net *lntest.NetworkHarness, t *harnessTest) }, ) - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - err := net.Alice.WaitForNetworkChannelOpen(ctxt, chanPointAlice) + err := net.Alice.WaitForNetworkChannelOpen(chanPointAlice) if err != nil { t.Fatalf("alice didn't see the channel alice <-> bob before "+ "timeout: %v", err) } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.Bob.WaitForNetworkChannelOpen(ctxt, chanPointAlice) + err = net.Bob.WaitForNetworkChannelOpen(chanPointAlice) if err != nil { t.Fatalf("bob didn't see the channel alice <-> bob before "+ "timeout: %v", err) @@ -1299,20 +1289,17 @@ func testMultiHopOverPrivateChannels(net *lntest.NetworkHarness, t *harnessTest) }, ) - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.Bob.WaitForNetworkChannelOpen(ctxt, chanPointBob) + err = net.Bob.WaitForNetworkChannelOpen(chanPointBob) if err != nil { t.Fatalf("bob didn't see the channel bob <-> carol before "+ "timeout: %v", err) } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = carol.WaitForNetworkChannelOpen(ctxt, chanPointBob) + err = carol.WaitForNetworkChannelOpen(chanPointBob) if err != nil { t.Fatalf("carol didn't see the channel bob <-> carol before "+ "timeout: %v", err) } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.Alice.WaitForNetworkChannelOpen(ctxt, chanPointBob) + err = net.Alice.WaitForNetworkChannelOpen(chanPointBob) if err != nil { t.Fatalf("alice didn't see the channel bob <-> carol before "+ "timeout: %v", err) @@ -1344,20 +1331,17 @@ func testMultiHopOverPrivateChannels(net *lntest.NetworkHarness, t *harnessTest) }, ) - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = carol.WaitForNetworkChannelOpen(ctxt, chanPointCarol) + err = carol.WaitForNetworkChannelOpen(chanPointCarol) if err != nil { t.Fatalf("carol didn't see the channel carol <-> dave before "+ "timeout: %v", err) } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = dave.WaitForNetworkChannelOpen(ctxt, chanPointCarol) + err = dave.WaitForNetworkChannelOpen(chanPointCarol) if err != nil { t.Fatalf("dave didn't see the channel carol <-> dave before "+ "timeout: %v", err) } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = dave.WaitForNetworkChannelOpen(ctxt, chanPointBob) + err = dave.WaitForNetworkChannelOpen(chanPointBob) if err != nil { t.Fatalf("dave didn't see the channel bob <-> carol before "+ "timeout: %v", err) @@ -1387,7 +1371,7 @@ func testMultiHopOverPrivateChannels(net *lntest.NetworkHarness, t *harnessTest) Private: true, } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) resp, err := dave.AddInvoice(ctxt, invoice) if err != nil { t.Fatalf("unable to add invoice for dave: %v", err) @@ -1438,7 +1422,7 @@ func testMultiHopOverPrivateChannels(net *lntest.NetworkHarness, t *harnessTest) closeChannelAndAssert(t, net, carol, chanPointCarol, false) } -// computeFee calculates the payment fee as specified in BOLT07 +// computeFee calculates the payment fee as specified in BOLT07. func computeFee(baseFee, feeRate, amt lnwire.MilliSatoshi) lnwire.MilliSatoshi { return baseFee + amt*feeRate/1000000 } @@ -1506,8 +1490,7 @@ func testQueryRoutes(net *lntest.NetworkHarness, t *harnessTest) { Index: chanPoint.OutputIndex, } - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - err = node.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = node.WaitForNetworkChannelOpen(chanPoint) if err != nil { t.Fatalf("%s(%d): timeout waiting for "+ "channel(%s) open: %v", nodeNames[i], @@ -1802,8 +1785,7 @@ func testRouteFeeCutoff(net *lntest.NetworkHarness, t *harnessTest) { Index: chanPoint.OutputIndex, } - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - err = node.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = node.WaitForNetworkChannelOpen(chanPoint) if err != nil { t.Fatalf("%s(%d) timed out waiting for "+ "channel(%s) open: %v", nodeNames[i], diff --git a/lntest/itest/lnd_rpc_middleware_interceptor_test.go b/lntest/itest/lnd_rpc_middleware_interceptor_test.go index 54e0b8db62..de1a483811 100644 --- a/lntest/itest/lnd_rpc_middleware_interceptor_test.go +++ b/lntest/itest/lnd_rpc_middleware_interceptor_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lntest" "github.com/lightningnetwork/lnd/macaroons" @@ -195,7 +195,7 @@ func middlewareInterceptionTest(t *testing.T, node *lntest.HarnessNode, // block the execution of the main task otherwise. req := &lnrpc.ListChannelsRequest{ActiveOnly: true} go registration.interceptUnary( - "/lnrpc.Lightning/ListChannels", req, nil, + "/lnrpc.Lightning/ListChannels", req, nil, readOnly, ) // Do the actual call now and wait for the interceptor to do its thing. @@ -208,7 +208,7 @@ func middlewareInterceptionTest(t *testing.T, node *lntest.HarnessNode, // Let's test the same for a streaming endpoint. req2 := &lnrpc.PeerEventSubscription{} go registration.interceptStream( - "/lnrpc.Lightning/SubscribePeerEvents", req2, nil, + "/lnrpc.Lightning/SubscribePeerEvents", req2, nil, readOnly, ) // Do the actual call now and wait for the interceptor to do its thing. @@ -327,6 +327,7 @@ func middlewareManipulationTest(t *testing.T, node *lntest.HarnessNode, req := &lnrpc.ListChannelsRequest{ActiveOnly: true} go registration.interceptUnary( "/lnrpc.Lightning/ListChannels", req, replacementResponse, + readOnly, ) // Do the actual call now and wait for the interceptor to do its thing. @@ -349,7 +350,7 @@ func middlewareManipulationTest(t *testing.T, node *lntest.HarnessNode, req2 := &lnrpc.PeerEventSubscription{} go registration.interceptStream( "/lnrpc.Lightning/SubscribePeerEvents", req2, - replacementResponse2, + replacementResponse2, readOnly, ) // Do the actual call now and wait for the interceptor to do its thing. @@ -403,9 +404,8 @@ func middlewareMandatoryTest(t *testing.T, node *lntest.HarnessNode, // test case. So we need to do the wait and client setup manually here. conn, err := node.ConnectRPC(true) require.NoError(t, err) - err = node.WaitUntilStateReached( - conn, defaultTimeout, lnrpc.WalletState_RPC_ACTIVE, - ) + node.InitRPCClients(conn) + err = node.WaitUntilStateReached(lnrpc.WalletState_RPC_ACTIVE) require.NoError(t, err) node.LightningClient = lnrpc.NewLightningClient(conn) @@ -523,11 +523,21 @@ func registerMiddleware(t *testing.T, node *lntest.HarnessNode, // NOTE: Must be called in a goroutine as this will block until the response is // read from the response channel. func (h *middlewareHarness) interceptUnary(methodURI string, - expectedRequest proto.Message, responseReplacement proto.Message) { + expectedRequest proto.Message, responseReplacement proto.Message, + readOnly bool) { // Read intercept message and make sure it's for an RPC request. reqIntercept, err := h.stream.Recv() require.NoError(h.t, err) + + // Make sure the custom condition is populated correctly (if we're using + // a macaroon with a custom condition). + if !readOnly { + require.Equal( + h.t, "itest-value", reqIntercept.CustomCaveatCondition, + ) + } + req := reqIntercept.GetRequest() require.NotNil(h.t, req) @@ -537,7 +547,7 @@ func (h *middlewareHarness) interceptUnary(methodURI string, assertInterceptedType(h.t, expectedRequest, req) // We need to accept the request. - h.sendAccept(reqIntercept.RequestId, nil) + h.sendAccept(reqIntercept.MsgId, nil) // Now read the intercept message for the response. respIntercept, err := h.stream.Recv() @@ -545,8 +555,14 @@ func (h *middlewareHarness) interceptUnary(methodURI string, res := respIntercept.GetResponse() require.NotNil(h.t, res) + // We expect the request ID to be the same for the request intercept + // and the response intercept messages. But the message IDs must be + // different/unique. + require.Equal(h.t, reqIntercept.RequestId, respIntercept.RequestId) + require.NotEqual(h.t, reqIntercept.MsgId, respIntercept.MsgId) + // We need to accept the response as well. - h.sendAccept(respIntercept.RequestId, responseReplacement) + h.sendAccept(respIntercept.MsgId, responseReplacement) h.responsesChan <- res } @@ -559,11 +575,21 @@ func (h *middlewareHarness) interceptUnary(methodURI string, // NOTE: Must be called in a goroutine as this will block until the first // response is read from the response channel. func (h *middlewareHarness) interceptStream(methodURI string, - expectedRequest proto.Message, responseReplacement proto.Message) { + expectedRequest proto.Message, responseReplacement proto.Message, + readOnly bool) { // Read intercept message and make sure it's for an RPC stream auth. authIntercept, err := h.stream.Recv() require.NoError(h.t, err) + + // Make sure the custom condition is populated correctly (if we're using + // a macaroon with a custom condition). + if !readOnly { + require.Equal( + h.t, "itest-value", authIntercept.CustomCaveatCondition, + ) + } + auth := authIntercept.GetStreamAuth() require.NotNil(h.t, auth) @@ -571,7 +597,7 @@ func (h *middlewareHarness) interceptStream(methodURI string, require.Equal(h.t, methodURI, auth.MethodFullUri) // We need to accept the auth. - h.sendAccept(authIntercept.RequestId, nil) + h.sendAccept(authIntercept.MsgId, nil) // Read intercept message and make sure it's for an RPC request. reqIntercept, err := h.stream.Recv() @@ -585,7 +611,7 @@ func (h *middlewareHarness) interceptStream(methodURI string, assertInterceptedType(h.t, expectedRequest, req) // We need to accept the request. - h.sendAccept(reqIntercept.RequestId, nil) + h.sendAccept(reqIntercept.MsgId, nil) // Now read the intercept message for the response. respIntercept, err := h.stream.Recv() @@ -593,14 +619,23 @@ func (h *middlewareHarness) interceptStream(methodURI string, res := respIntercept.GetResponse() require.NotNil(h.t, res) + // We expect the request ID to be the same for the auth intercept, + // request intercept and the response intercept messages. But the + // message IDs must be different/unique. + require.Equal(h.t, authIntercept.RequestId, respIntercept.RequestId) + require.Equal(h.t, reqIntercept.RequestId, respIntercept.RequestId) + require.NotEqual(h.t, authIntercept.MsgId, reqIntercept.MsgId) + require.NotEqual(h.t, authIntercept.MsgId, respIntercept.MsgId) + require.NotEqual(h.t, reqIntercept.MsgId, respIntercept.MsgId) + // We need to accept the response as well. - h.sendAccept(respIntercept.RequestId, responseReplacement) + h.sendAccept(respIntercept.MsgId, responseReplacement) h.responsesChan <- res } // sendAccept sends an accept feedback to the RPC server. -func (h *middlewareHarness) sendAccept(requestID uint64, +func (h *middlewareHarness) sendAccept(msgID uint64, responseReplacement proto.Message) { var replacementBytes []byte @@ -617,7 +652,7 @@ func (h *middlewareHarness) sendAccept(requestID uint64, ReplacementSerialized: replacementBytes, }, }, - RequestId: requestID, + RefMsgId: msgID, }) require.NoError(h.t, err) } diff --git a/lntest/itest/lnd_send_multi_path_payment_test.go b/lntest/itest/lnd_send_multi_path_payment_test.go index 45b25b4466..fe9e003354 100644 --- a/lntest/itest/lnd_send_multi_path_payment_test.go +++ b/lntest/itest/lnd_send_multi_path_payment_test.go @@ -4,7 +4,7 @@ import ( "context" "encoding/hex" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lntest" @@ -129,7 +129,6 @@ func testSendMultiPathPayment(net *lntest.NetworkHarness, t *harnessTest) { if htlc.State == lnrpc.InvoiceHTLCState_SETTLED { settled++ } - } if settled != succeeded { t.Fatalf("expected invoice to be settled "+ diff --git a/lntest/itest/lnd_signer_test.go b/lntest/itest/lnd_signer_test.go index 9630b9fdd0..0d4b78185f 100644 --- a/lntest/itest/lnd_signer_test.go +++ b/lntest/itest/lnd_signer_test.go @@ -1,12 +1,18 @@ package itest import ( + "bytes" "context" "fmt" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/keychain" + "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/signrpc" + "github.com/lightningnetwork/lnd/lnrpc/walletrpc" "github.com/lightningnetwork/lnd/lntest" "github.com/stretchr/testify/require" ) @@ -28,7 +34,7 @@ func runDeriveSharedKey(t *harnessTest, alice *lntest.HarnessNode) { // Create an ephemeral key, extracts its public key, and make a // PrivKeyECDH using the ephemeral key. - ephemeralPriv, err := btcec.NewPrivateKey(btcec.S256()) + ephemeralPriv, err := btcec.NewPrivateKey() require.NoError(t.t, err, "failed to create ephemeral key") ephemeralPubBytes := ephemeralPriv.PubKey().SerializeCompressed() @@ -50,7 +56,7 @@ func runDeriveSharedKey(t *harnessTest, alice *lntest.HarnessNode) { ) } - nodePub, err := btcec.ParsePubKey(alice.PubKey[:], btcec.S256()) + nodePub, err := btcec.ParsePubKey(alice.PubKey[:]) require.NoError(t.t, err, "failed to parse node pubkey") customizedKeyFamily := int32(keychain.KeyFamilyMultiSig) @@ -190,6 +196,201 @@ func runDeriveSharedKey(t *harnessTest, alice *lntest.HarnessNode) { assertErrorMatch("use either raw_key_bytes or key_index", req) } +// testSignOutputRaw makes sure that the SignOutputRaw RPC can be used with all +// custom ways of specifying the signing key in the key descriptor/locator. +func testSignOutputRaw(net *lntest.NetworkHarness, t *harnessTest) { + runSignOutputRaw(t, net, net.Alice) +} + +// runSignOutputRaw makes sure that the SignOutputRaw RPC can be used with all +// custom ways of specifying the signing key in the key descriptor/locator. +func runSignOutputRaw(t *harnessTest, net *lntest.NetworkHarness, + alice *lntest.HarnessNode) { + + ctxb := context.Background() + ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) + defer cancel() + + // For the next step, we need a public key. Let's use a special family + // for this. We want this to be an index of zero. + const testCustomKeyFamily = 44 + keyDesc, err := alice.WalletKitClient.DeriveNextKey( + ctxt, &walletrpc.KeyReq{ + KeyFamily: testCustomKeyFamily, + }, + ) + require.NoError(t.t, err) + require.Equal(t.t, int32(0), keyDesc.KeyLoc.KeyIndex) + + targetPubKey, err := btcec.ParsePubKey(keyDesc.RawKeyBytes) + require.NoError(t.t, err) + + // First, try with a key descriptor that only sets the public key. + assertSignOutputRaw( + t, net, alice, targetPubKey, &signrpc.KeyDescriptor{ + RawKeyBytes: keyDesc.RawKeyBytes, + }, + ) + + // Now try again, this time only with the (0 index!) key locator. + assertSignOutputRaw( + t, net, alice, targetPubKey, &signrpc.KeyDescriptor{ + KeyLoc: &signrpc.KeyLocator{ + KeyFamily: keyDesc.KeyLoc.KeyFamily, + KeyIndex: keyDesc.KeyLoc.KeyIndex, + }, + }, + ) + + // And now test everything again with a new key where we know the index + // is not 0. + ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) + defer cancel() + keyDesc, err = alice.WalletKitClient.DeriveNextKey( + ctxt, &walletrpc.KeyReq{ + KeyFamily: testCustomKeyFamily, + }, + ) + require.NoError(t.t, err) + require.Equal(t.t, int32(1), keyDesc.KeyLoc.KeyIndex) + + targetPubKey, err = btcec.ParsePubKey(keyDesc.RawKeyBytes) + require.NoError(t.t, err) + + // First, try with a key descriptor that only sets the public key. + assertSignOutputRaw( + t, net, alice, targetPubKey, &signrpc.KeyDescriptor{ + RawKeyBytes: keyDesc.RawKeyBytes, + }, + ) + + // Now try again, this time only with the key locator. + assertSignOutputRaw( + t, net, alice, targetPubKey, &signrpc.KeyDescriptor{ + KeyLoc: &signrpc.KeyLocator{ + KeyFamily: keyDesc.KeyLoc.KeyFamily, + KeyIndex: keyDesc.KeyLoc.KeyIndex, + }, + }, + ) +} + +// assertSignOutputRaw sends coins to a p2wkh address derived from the given +// target public key and then tries to spend that output again by invoking the +// SignOutputRaw RPC with the key descriptor provided. +func assertSignOutputRaw(t *harnessTest, net *lntest.NetworkHarness, + alice *lntest.HarnessNode, targetPubKey *btcec.PublicKey, + keyDesc *signrpc.KeyDescriptor) { + + ctxb := context.Background() + ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) + defer cancel() + + pubKeyHash := btcutil.Hash160(targetPubKey.SerializeCompressed()) + targetAddr, err := btcutil.NewAddressWitnessPubKeyHash( + pubKeyHash, harnessNetParams, + ) + require.NoError(t.t, err) + targetScript, err := txscript.PayToAddrScript(targetAddr) + require.NoError(t.t, err) + + // Send some coins to the generated p2wpkh address. + _, err = alice.SendCoins(ctxt, &lnrpc.SendCoinsRequest{ + Addr: targetAddr.String(), + Amount: 800_000, + }) + require.NoError(t.t, err) + + // Wait until the TX is found in the mempool. + txid, err := waitForTxInMempool(net.Miner.Client, minerMempoolTimeout) + require.NoError(t.t, err) + + targetOutputIndex := getOutputIndex( + t, net.Miner, txid, targetAddr.String(), + ) + + // Clear the mempool. + mineBlocks(t, net, 1, 1) + + // Try to spend the output now to a new p2wkh address. + p2wkhResp, err := alice.NewAddress(ctxt, &lnrpc.NewAddressRequest{ + Type: lnrpc.AddressType_WITNESS_PUBKEY_HASH, + }) + require.NoError(t.t, err) + + p2wkhAdrr, err := btcutil.DecodeAddress( + p2wkhResp.Address, harnessNetParams, + ) + require.NoError(t.t, err) + + p2wkhPkScript, err := txscript.PayToAddrScript(p2wkhAdrr) + require.NoError(t.t, err) + + tx := wire.NewMsgTx(2) + tx.TxIn = []*wire.TxIn{{ + PreviousOutPoint: wire.OutPoint{ + Hash: *txid, + Index: uint32(targetOutputIndex), + }, + }} + value := int64(800_000 - 200) + tx.TxOut = []*wire.TxOut{{ + PkScript: p2wkhPkScript, + Value: value, + }} + + var buf bytes.Buffer + require.NoError(t.t, tx.Serialize(&buf)) + + signResp, err := alice.SignerClient.SignOutputRaw( + ctxt, &signrpc.SignReq{ + RawTxBytes: buf.Bytes(), + SignDescs: []*signrpc.SignDescriptor{{ + Output: &signrpc.TxOut{ + PkScript: targetScript, + Value: 800_000, + }, + InputIndex: 0, + KeyDesc: keyDesc, + Sighash: uint32(txscript.SigHashAll), + WitnessScript: targetScript, + }}, + }, + ) + require.NoError(t.t, err) + + tx.TxIn[0].Witness = wire.TxWitness{ + append(signResp.RawSigs[0], byte(txscript.SigHashAll)), + targetPubKey.SerializeCompressed(), + } + + buf.Reset() + require.NoError(t.t, tx.Serialize(&buf)) + + _, err = alice.WalletKitClient.PublishTransaction( + ctxt, &walletrpc.Transaction{ + TxHex: buf.Bytes(), + }, + ) + require.NoError(t.t, err) + + // Wait until the spending tx is found. + txid, err = waitForTxInMempool(net.Miner.Client, minerMempoolTimeout) + require.NoError(t.t, err) + p2wkhOutputIndex := getOutputIndex( + t, net.Miner, txid, p2wkhAdrr.String(), + ) + op := &lnrpc.OutPoint{ + TxidBytes: txid[:], + OutputIndex: uint32(p2wkhOutputIndex), + } + assertWalletUnspent(t, alice, op) + + // Mine another block to clean up the mempool and to make sure the spend + // tx is actually included in a block. + mineBlocks(t, net, 1, 1) +} + // deriveCustomizedKey uses the family and index to derive a public key from // the node's walletkit client. func deriveCustomizedKey(ctx context.Context, node *lntest.HarnessNode, @@ -204,7 +405,7 @@ func deriveCustomizedKey(ctx context.Context, node *lntest.HarnessNode, if err != nil { return nil, fmt.Errorf("failed to derive key: %v", err) } - pub, err := btcec.ParsePubKey(resp.RawKeyBytes, btcec.S256()) + pub, err := btcec.ParsePubKey(resp.RawKeyBytes) if err != nil { return nil, fmt.Errorf("failed to parse node pubkey: %v", err) } diff --git a/lntest/itest/lnd_single_hop_invoice_test.go b/lntest/itest/lnd_single_hop_invoice_test.go index b8a347d7c0..74f3774581 100644 --- a/lntest/itest/lnd_single_hop_invoice_test.go +++ b/lntest/itest/lnd_single_hop_invoice_test.go @@ -6,7 +6,7 @@ import ( "encoding/hex" "time" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" @@ -48,13 +48,12 @@ func testSingleHopInvoice(net *lntest.NetworkHarness, t *harnessTest) { // Wait for Alice to recognize and advertise the new channel generated // above. - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - err = net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = net.Alice.WaitForNetworkChannelOpen(chanPoint) if err != nil { t.Fatalf("alice didn't advertise channel before "+ "timeout: %v", err) } - err = net.Bob.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = net.Bob.WaitForNetworkChannelOpen(chanPoint) if err != nil { t.Fatalf("bob didn't advertise channel before "+ "timeout: %v", err) @@ -78,7 +77,7 @@ func testSingleHopInvoice(net *lntest.NetworkHarness, t *harnessTest) { payHash := &lnrpc.PaymentHash{ RHash: invoiceResp.RHash, } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) dbInvoice, err := net.Bob.LookupInvoice(ctxt, payHash) if err != nil { t.Fatalf("unable to lookup invoice: %v", err) diff --git a/lntest/itest/lnd_switch_test.go b/lntest/itest/lnd_switch_test.go index e194101b4a..4ca5aa8423 100644 --- a/lntest/itest/lnd_switch_test.go +++ b/lntest/itest/lnd_switch_test.go @@ -4,8 +4,8 @@ import ( "context" "time" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lntest" "github.com/lightningnetwork/lnd/lntest/wait" @@ -118,8 +118,7 @@ func testSwitchCircuitPersistence(net *lntest.NetworkHarness, t *harnessTest) { Index: chanPoint.OutputIndex, } - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - err = node.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = node.WaitForNetworkChannelOpen(chanPoint) if err != nil { t.Fatalf("%s(%d): timeout waiting for "+ "channel(%s) open: %v", nodeNames[i], @@ -141,13 +140,11 @@ func testSwitchCircuitPersistence(net *lntest.NetworkHarness, t *harnessTest) { // We'll wait for all parties to recognize the new channels within the // network. - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - err = dave.WaitForNetworkChannelOpen(ctxt, chanPointDave) + err = dave.WaitForNetworkChannelOpen(chanPointDave) if err != nil { t.Fatalf("dave didn't advertise his channel: %v", err) } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = carol.WaitForNetworkChannelOpen(ctxt, chanPointCarol) + err = carol.WaitForNetworkChannelOpen(chanPointCarol) if err != nil { t.Fatalf("carol didn't advertise her channel in time: %v", err) @@ -214,7 +211,6 @@ func testSwitchCircuitPersistence(net *lntest.NetworkHarness, t *harnessTest) { err = wait.Predicate(func() bool { predErr = assertNumActiveHtlcs(nodes, 0) return predErr == nil - }, defaultTimeout) if err != nil { t.Fatalf("htlc mismatch: %v", predErr) @@ -251,7 +247,7 @@ func testSwitchCircuitPersistence(net *lntest.NetworkHarness, t *harnessTest) { Memo: "testing", Value: paymentAmt, } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) resp, err := carol.AddInvoice(ctxt, finalInvoice) if err != nil { t.Fatalf("unable to add invoice: %v", err) @@ -396,8 +392,7 @@ func testSwitchOfflineDelivery(net *lntest.NetworkHarness, t *harnessTest) { Index: chanPoint.OutputIndex, } - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - err = node.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = node.WaitForNetworkChannelOpen(chanPoint) if err != nil { t.Fatalf("%s(%d): timeout waiting for "+ "channel(%s) open: %v", nodeNames[i], @@ -419,23 +414,19 @@ func testSwitchOfflineDelivery(net *lntest.NetworkHarness, t *harnessTest) { // We'll wait for all parties to recognize the new channels within the // network. - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - err = dave.WaitForNetworkChannelOpen(ctxt, chanPointDave) + err = dave.WaitForNetworkChannelOpen(chanPointDave) if err != nil { t.Fatalf("dave didn't advertise his channel: %v", err) } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = carol.WaitForNetworkChannelOpen(ctxt, chanPointCarol) + err = carol.WaitForNetworkChannelOpen(chanPointCarol) if err != nil { t.Fatalf("carol didn't advertise her channel in time: %v", err) } // Make sure all nodes are fully synced before we continue. - ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) - defer cancel() for _, node := range nodes { - err := node.WaitForBlockchainSync(ctxt) + err := node.WaitForBlockchainSync() if err != nil { t.Fatalf("unable to wait for sync: %v", err) } @@ -503,10 +494,8 @@ func testSwitchOfflineDelivery(net *lntest.NetworkHarness, t *harnessTest) { } // Make sure all nodes are fully synced again. - ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) - defer cancel() for _, node := range nodes { - err := node.WaitForBlockchainSync(ctxt) + err := node.WaitForBlockchainSync() if err != nil { t.Fatalf("unable to wait for sync: %v", err) } @@ -555,7 +544,7 @@ func testSwitchOfflineDelivery(net *lntest.NetworkHarness, t *harnessTest) { Memo: "testing", Value: paymentAmt, } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) resp, err := carol.AddInvoice(ctxt, finalInvoice) if err != nil { t.Fatalf("unable to add invoice: %v", err) @@ -702,8 +691,7 @@ func testSwitchOfflineDeliveryPersistence(net *lntest.NetworkHarness, t *harness Index: chanPoint.OutputIndex, } - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - err = node.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = node.WaitForNetworkChannelOpen(chanPoint) if err != nil { t.Fatalf("%s(%d): timeout waiting for "+ "channel(%s) open: %v", nodeNames[i], @@ -725,13 +713,11 @@ func testSwitchOfflineDeliveryPersistence(net *lntest.NetworkHarness, t *harness // We'll wait for all parties to recognize the new channels within the // network. - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - err = dave.WaitForNetworkChannelOpen(ctxt, chanPointDave) + err = dave.WaitForNetworkChannelOpen(chanPointDave) if err != nil { t.Fatalf("dave didn't advertise his channel: %v", err) } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = carol.WaitForNetworkChannelOpen(ctxt, chanPointCarol) + err = carol.WaitForNetworkChannelOpen(chanPointCarol) if err != nil { t.Fatalf("carol didn't advertise her channel in time: %v", err) @@ -750,7 +736,6 @@ func testSwitchOfflineDeliveryPersistence(net *lntest.NetworkHarness, t *harness err = wait.Predicate(func() bool { predErr = assertNumActiveHtlcs(nodes, numPayments) return predErr == nil - }, defaultTimeout) if err != nil { t.Fatalf("htlc mismatch: %v", predErr) @@ -842,7 +827,7 @@ func testSwitchOfflineDeliveryPersistence(net *lntest.NetworkHarness, t *harness Memo: "testing", Value: paymentAmt, } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) resp, err := carol.AddInvoice(ctxt, finalInvoice) if err != nil { t.Fatalf("unable to add invoice: %v", err) @@ -896,7 +881,6 @@ func testSwitchOfflineDeliveryPersistence(net *lntest.NetworkHarness, t *harness // 5. Carol <-- Dave <-- Alice X expect settle to propagate func testSwitchOfflineDeliveryOutgoingOffline( net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() const chanAmt = btcutil.Amount(1000000) const pushAmt = btcutil.Amount(900000) @@ -991,8 +975,7 @@ func testSwitchOfflineDeliveryOutgoingOffline( Index: chanPoint.OutputIndex, } - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - err = node.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = node.WaitForNetworkChannelOpen(chanPoint) if err != nil { t.Fatalf("%s(%d): timeout waiting for "+ "channel(%s) open: %v", nodeNames[i], @@ -1014,13 +997,11 @@ func testSwitchOfflineDeliveryOutgoingOffline( // We'll wait for all parties to recognize the new channels within the // network. - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - err = dave.WaitForNetworkChannelOpen(ctxt, chanPointDave) + err = dave.WaitForNetworkChannelOpen(chanPointDave) if err != nil { t.Fatalf("dave didn't advertise his channel: %v", err) } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = carol.WaitForNetworkChannelOpen(ctxt, chanPointCarol) + err = carol.WaitForNetworkChannelOpen(chanPointCarol) if err != nil { t.Fatalf("carol didn't advertise her channel in time: %v", err) diff --git a/lntest/itest/lnd_taproot_test.go b/lntest/itest/lnd_taproot_test.go new file mode 100644 index 0000000000..1d28248d09 --- /dev/null +++ b/lntest/itest/lnd_taproot_test.go @@ -0,0 +1,1387 @@ +package itest + +import ( + "bytes" + "context" + "crypto/sha256" + "encoding/hex" + "testing" + + "github.com/btcsuite/btcd/blockchain" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lnrpc/chainrpc" + "github.com/lightningnetwork/lnd/lnrpc/signrpc" + "github.com/lightningnetwork/lnd/lnrpc/walletrpc" + "github.com/lightningnetwork/lnd/lntest" + "github.com/lightningnetwork/lnd/lnwallet/chainfee" + "github.com/stretchr/testify/require" +) + +const ( + testTaprootKeyFamily = 77 + testAmount = 800_000 + signMethodBip86 = signrpc.SignMethod_SIGN_METHOD_TAPROOT_KEY_SPEND_BIP0086 + signMethodRootHash = signrpc.SignMethod_SIGN_METHOD_TAPROOT_KEY_SPEND + signMethodTapscript = signrpc.SignMethod_SIGN_METHOD_TAPROOT_SCRIPT_SPEND +) + +var ( + dummyInternalKeyBytes, _ = hex.DecodeString( + "03464805f5468e294d88cf15a3f06aef6c89d63ef1bd7b42db2e0c74c1ac" + + "eb90fe", + ) + dummyInternalKey, _ = btcec.ParsePubKey(dummyInternalKeyBytes) +) + +// testTaproot ensures that the daemon can send to and spend from taproot (p2tr) +// outputs. +func testTaproot(net *lntest.NetworkHarness, t *harnessTest) { + ctxb := context.Background() + ctxt, cancel := context.WithTimeout(ctxb, 2*defaultTimeout) + defer cancel() + + testTaprootComputeInputScriptKeySpendBip86(ctxt, t, net.Alice, net) + testTaprootSignOutputRawScriptSpend(ctxt, t, net.Alice, net) + testTaprootSignOutputRawKeySpendBip86(ctxt, t, net.Alice, net) + testTaprootSignOutputRawKeySpendRootHash(ctxt, t, net.Alice, net) + testTaprootMuSig2KeySpendBip86(ctxt, t, net.Alice, net) + testTaprootMuSig2KeySpendRootHash(ctxt, t, net.Alice, net) + testTaprootMuSig2ScriptSpend(ctxt, t, net.Alice, net) + testTaprootMuSig2CombinedLeafKeySpend(ctxt, t, net.Alice, net) +} + +// testTaprootComputeInputScriptKeySpendBip86 tests sending to and spending from +// p2tr key spend only (BIP-0086) addresses through the SendCoins RPC which +// internally uses the ComputeInputScript method for signing. +func testTaprootComputeInputScriptKeySpendBip86(ctxt context.Context, + t *harnessTest, alice *lntest.HarnessNode, net *lntest.NetworkHarness) { + + // We'll start the test by sending Alice some coins, which she'll use to + // send to herself on a p2tr output. + net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, alice) + + // Let's create a p2tr address now. + p2trResp, err := alice.NewAddress(ctxt, &lnrpc.NewAddressRequest{ + Type: lnrpc.AddressType_TAPROOT_PUBKEY, + }) + require.NoError(t.t, err) + + // Assert this is a segwit v1 address that starts with bcrt1p. + require.Contains( + t.t, p2trResp.Address, net.Miner.ActiveNet.Bech32HRPSegwit+"1p", + ) + + // Send the coins from Alice's wallet to her own, but to the new p2tr + // address. + _, err = alice.SendCoins(ctxt, &lnrpc.SendCoinsRequest{ + Addr: p2trResp.Address, + Amount: 0.5 * btcutil.SatoshiPerBitcoin, + }) + require.NoError(t.t, err) + + txid, err := waitForTxInMempool(net.Miner.Client, defaultTimeout) + require.NoError(t.t, err) + + // Wait until bob has seen the tx and considers it as owned. + p2trOutputIndex := getOutputIndex(t, net.Miner, txid, p2trResp.Address) + op := &lnrpc.OutPoint{ + TxidBytes: txid[:], + OutputIndex: uint32(p2trOutputIndex), + } + assertWalletUnspent(t, alice, op) + + // Mine a block to clean up the mempool. + mineBlocks(t, net, 1, 1) + + // Let's sweep the whole wallet to a new p2tr address, making sure we + // can sign transactions with v0 and v1 inputs. + p2trResp, err = alice.NewAddress(ctxt, &lnrpc.NewAddressRequest{ + Type: lnrpc.AddressType_TAPROOT_PUBKEY, + }) + require.NoError(t.t, err) + + _, err = alice.SendCoins(ctxt, &lnrpc.SendCoinsRequest{ + Addr: p2trResp.Address, + SendAll: true, + }) + require.NoError(t.t, err) + + // Make sure the coins sent to the address are confirmed correctly, + // including the confirmation notification. + confirmAddress(ctxt, t, net, alice, p2trResp.Address) +} + +// testTaprootSignOutputRawScriptSpend tests sending to and spending from p2tr +// script addresses using the script path with the SignOutputRaw RPC. +func testTaprootSignOutputRawScriptSpend(ctxt context.Context, t *harnessTest, + alice *lntest.HarnessNode, net *lntest.NetworkHarness) { + + // For the next step, we need a public key. Let's use a special family + // for this. + keyDesc, err := alice.WalletKitClient.DeriveNextKey( + ctxt, &walletrpc.KeyReq{KeyFamily: testTaprootKeyFamily}, + ) + require.NoError(t.t, err) + + leafSigningKey, err := btcec.ParsePubKey(keyDesc.RawKeyBytes) + require.NoError(t.t, err) + + // Let's create a taproot script output now. This is a hash lock with a + // simple preimage of "foobar". + leaf1 := testScriptHashLock(t.t, []byte("foobar")) + + // Let's add a second script output as well to test the partial reveal. + leaf2 := testScriptSchnorrSig(t.t, leafSigningKey) + + inclusionProof := leaf1.TapHash() + tapscript := input.TapscriptPartialReveal( + dummyInternalKey, leaf2, inclusionProof[:], + ) + taprootKey, err := tapscript.TaprootKey() + require.NoError(t.t, err) + + // Send some coins to the generated tapscript address. + p2trOutpoint, p2trPkScript := sendToTaprootOutput( + ctxt, t, net, alice, taprootKey, testAmount, + ) + + // Spend the output again, this time back to a p2wkh address. + p2wkhAddr, p2wkhPkScript := newAddrWithScript( + ctxt, t.t, alice, lnrpc.AddressType_WITNESS_PUBKEY_HASH, + ) + + // Create fee estimation for a p2tr input and p2wkh output. + feeRate := chainfee.SatPerKWeight(12500) + estimator := input.TxWeightEstimator{} + estimator.AddTapscriptInput( + input.TaprootSignatureWitnessSize, tapscript, + ) + estimator.AddP2WKHOutput() + estimatedWeight := int64(estimator.Weight()) + requiredFee := feeRate.FeeForWeight(estimatedWeight) + + tx := wire.NewMsgTx(2) + tx.TxIn = []*wire.TxIn{{ + PreviousOutPoint: p2trOutpoint, + }} + value := int64(testAmount - requiredFee) + tx.TxOut = []*wire.TxOut{{ + PkScript: p2wkhPkScript, + Value: value, + }} + + var buf bytes.Buffer + require.NoError(t.t, tx.Serialize(&buf)) + + utxoInfo := []*signrpc.TxOut{{ + PkScript: p2trPkScript, + Value: testAmount, + }} + + // Before we actually sign, we want to make sure that we get an error + // when we try to sign for a Taproot output without specifying all UTXO + // information. + _, err = alice.SignerClient.SignOutputRaw( + ctxt, &signrpc.SignReq{ + RawTxBytes: buf.Bytes(), + SignDescs: []*signrpc.SignDescriptor{{ + Output: utxoInfo[0], + InputIndex: 0, + KeyDesc: keyDesc, + Sighash: uint32(txscript.SigHashDefault), + WitnessScript: leaf2.Script, + SignMethod: signMethodTapscript, + }}, + }, + ) + require.Error(t.t, err) + require.Contains( + t.t, err.Error(), "error signing taproot output, transaction "+ + "input 0 is missing its previous outpoint information", + ) + + // We also want to make sure we get an error when we don't specify the + // correct signing method. + _, err = alice.SignerClient.SignOutputRaw( + ctxt, &signrpc.SignReq{ + RawTxBytes: buf.Bytes(), + SignDescs: []*signrpc.SignDescriptor{{ + Output: utxoInfo[0], + InputIndex: 0, + KeyDesc: keyDesc, + Sighash: uint32(txscript.SigHashDefault), + WitnessScript: leaf2.Script, + }}, + PrevOutputs: utxoInfo, + }, + ) + require.Error(t.t, err) + require.Contains( + t.t, err.Error(), "selected sign method witness_v0 is not "+ + "compatible with given pk script 5120", + ) + + // Do the actual signing now. + signResp, err := alice.SignerClient.SignOutputRaw( + ctxt, &signrpc.SignReq{ + RawTxBytes: buf.Bytes(), + SignDescs: []*signrpc.SignDescriptor{{ + Output: utxoInfo[0], + InputIndex: 0, + KeyDesc: keyDesc, + Sighash: uint32(txscript.SigHashDefault), + WitnessScript: leaf2.Script, + SignMethod: signMethodTapscript, + }}, + PrevOutputs: utxoInfo, + }, + ) + require.NoError(t.t, err) + + // We can now assemble the witness stack. + controlBlockBytes, err := tapscript.ControlBlock.ToBytes() + require.NoError(t.t, err) + + tx.TxIn[0].Witness = wire.TxWitness{ + signResp.RawSigs[0], + leaf2.Script, + controlBlockBytes, + } + + // Serialize, weigh and publish the TX now, then make sure the + // coins are sent and confirmed to the final sweep destination address. + publishTxAndConfirmSweep( + ctxt, t, net, alice, tx, estimatedWeight, + &chainrpc.SpendRequest{ + Outpoint: &chainrpc.Outpoint{ + Hash: p2trOutpoint.Hash[:], + Index: p2trOutpoint.Index, + }, + Script: p2trPkScript, + }, + p2wkhAddr.String(), + ) +} + +// testTaprootSignOutputRawKeySpendBip86 tests that a tapscript address can +// also be spent using the key spend path through the SignOutputRaw RPC using a +// BIP0086 key spend only commitment. +func testTaprootSignOutputRawKeySpendBip86(ctxt context.Context, + t *harnessTest, alice *lntest.HarnessNode, net *lntest.NetworkHarness) { + + // For the next step, we need a public key. Let's use a special family + // for this. + keyDesc, err := alice.WalletKitClient.DeriveNextKey( + ctxt, &walletrpc.KeyReq{KeyFamily: testTaprootKeyFamily}, + ) + require.NoError(t.t, err) + + internalKey, err := btcec.ParsePubKey(keyDesc.RawKeyBytes) + require.NoError(t.t, err) + + // We want to make sure we can still use a tweaked key, even if it ends + // up being essentially double tweaked because of the taproot root hash. + dummyKeyTweak := sha256.Sum256([]byte("this is a key tweak")) + internalKey = input.TweakPubKeyWithTweak(internalKey, dummyKeyTweak[:]) + + // Our taproot key is a BIP0086 key spend only construction that just + // commits to the internal key and no root hash. + taprootKey := txscript.ComputeTaprootKeyNoScript(internalKey) + + // Send some coins to the generated tapscript address. + p2trOutpoint, p2trPkScript := sendToTaprootOutput( + ctxt, t, net, alice, taprootKey, testAmount, + ) + + // Spend the output again, this time back to a p2wkh address. + p2wkhAddr, p2wkhPkScript := newAddrWithScript( + ctxt, t.t, alice, lnrpc.AddressType_WITNESS_PUBKEY_HASH, + ) + + // Create fee estimation for a p2tr input and p2wkh output. + feeRate := chainfee.SatPerKWeight(12500) + estimator := input.TxWeightEstimator{} + estimator.AddTaprootKeySpendInput(txscript.SigHashDefault) + estimator.AddP2WKHOutput() + estimatedWeight := int64(estimator.Weight()) + requiredFee := feeRate.FeeForWeight(estimatedWeight) + + tx := wire.NewMsgTx(2) + tx.TxIn = []*wire.TxIn{{ + PreviousOutPoint: p2trOutpoint, + }} + value := int64(testAmount - requiredFee) + tx.TxOut = []*wire.TxOut{{ + PkScript: p2wkhPkScript, + Value: value, + }} + + var buf bytes.Buffer + require.NoError(t.t, tx.Serialize(&buf)) + + utxoInfo := []*signrpc.TxOut{{ + PkScript: p2trPkScript, + Value: testAmount, + }} + signResp, err := alice.SignerClient.SignOutputRaw( + ctxt, &signrpc.SignReq{ + RawTxBytes: buf.Bytes(), + SignDescs: []*signrpc.SignDescriptor{{ + Output: utxoInfo[0], + InputIndex: 0, + KeyDesc: keyDesc, + SingleTweak: dummyKeyTweak[:], + Sighash: uint32(txscript.SigHashDefault), + SignMethod: signMethodBip86, + }}, + PrevOutputs: utxoInfo, + }, + ) + require.NoError(t.t, err) + + tx.TxIn[0].Witness = wire.TxWitness{ + signResp.RawSigs[0], + } + + // Serialize, weigh and publish the TX now, then make sure the + // coins are sent and confirmed to the final sweep destination address. + publishTxAndConfirmSweep( + ctxt, t, net, alice, tx, estimatedWeight, + &chainrpc.SpendRequest{ + Outpoint: &chainrpc.Outpoint{ + Hash: p2trOutpoint.Hash[:], + Index: p2trOutpoint.Index, + }, + Script: p2trPkScript, + }, + p2wkhAddr.String(), + ) +} + +// testTaprootSignOutputRawKeySpendRootHash tests that a tapscript address can +// also be spent using the key spend path through the SignOutputRaw RPC using a +// tapscript root hash. +func testTaprootSignOutputRawKeySpendRootHash(ctxt context.Context, + t *harnessTest, alice *lntest.HarnessNode, net *lntest.NetworkHarness) { + + // For the next step, we need a public key. Let's use a special family + // for this. + keyDesc, err := alice.WalletKitClient.DeriveNextKey( + ctxt, &walletrpc.KeyReq{KeyFamily: testTaprootKeyFamily}, + ) + require.NoError(t.t, err) + + internalKey, err := btcec.ParsePubKey(keyDesc.RawKeyBytes) + require.NoError(t.t, err) + + // We want to make sure we can still use a tweaked key, even if it ends + // up being essentially double tweaked because of the taproot root hash. + dummyKeyTweak := sha256.Sum256([]byte("this is a key tweak")) + internalKey = input.TweakPubKeyWithTweak(internalKey, dummyKeyTweak[:]) + + // Let's create a taproot script output now. This is a hash lock with a + // simple preimage of "foobar". + leaf1 := testScriptHashLock(t.t, []byte("foobar")) + + rootHash := leaf1.TapHash() + taprootKey := txscript.ComputeTaprootOutputKey(internalKey, rootHash[:]) + + // Send some coins to the generated tapscript address. + p2trOutpoint, p2trPkScript := sendToTaprootOutput( + ctxt, t, net, alice, taprootKey, testAmount, + ) + + // Spend the output again, this time back to a p2wkh address. + p2wkhAddr, p2wkhPkScript := newAddrWithScript( + ctxt, t.t, alice, lnrpc.AddressType_WITNESS_PUBKEY_HASH, + ) + + // Create fee estimation for a p2tr input and p2wkh output. + feeRate := chainfee.SatPerKWeight(12500) + estimator := input.TxWeightEstimator{} + estimator.AddTaprootKeySpendInput(txscript.SigHashDefault) + estimator.AddP2WKHOutput() + estimatedWeight := int64(estimator.Weight()) + requiredFee := feeRate.FeeForWeight(estimatedWeight) + + tx := wire.NewMsgTx(2) + tx.TxIn = []*wire.TxIn{{ + PreviousOutPoint: p2trOutpoint, + }} + value := int64(testAmount - requiredFee) + tx.TxOut = []*wire.TxOut{{ + PkScript: p2wkhPkScript, + Value: value, + }} + + var buf bytes.Buffer + require.NoError(t.t, tx.Serialize(&buf)) + + utxoInfo := []*signrpc.TxOut{{ + PkScript: p2trPkScript, + Value: testAmount, + }} + signResp, err := alice.SignerClient.SignOutputRaw( + ctxt, &signrpc.SignReq{ + RawTxBytes: buf.Bytes(), + SignDescs: []*signrpc.SignDescriptor{{ + Output: utxoInfo[0], + InputIndex: 0, + KeyDesc: keyDesc, + SingleTweak: dummyKeyTweak[:], + Sighash: uint32(txscript.SigHashDefault), + TapTweak: rootHash[:], + SignMethod: signMethodRootHash, + }}, + PrevOutputs: utxoInfo, + }, + ) + require.NoError(t.t, err) + + tx.TxIn[0].Witness = wire.TxWitness{ + signResp.RawSigs[0], + } + + // Serialize, weigh and publish the TX now, then make sure the + // coins are sent and confirmed to the final sweep destination address. + publishTxAndConfirmSweep( + ctxt, t, net, alice, tx, estimatedWeight, + &chainrpc.SpendRequest{ + Outpoint: &chainrpc.Outpoint{ + Hash: p2trOutpoint.Hash[:], + Index: p2trOutpoint.Index, + }, + Script: p2trPkScript, + }, + p2wkhAddr.String(), + ) +} + +// testTaprootMuSig2KeySpendBip86 tests that a combined MuSig2 key can also be +// used as a BIP-0086 key spend only key. +func testTaprootMuSig2KeySpendBip86(ctxt context.Context, t *harnessTest, + alice *lntest.HarnessNode, net *lntest.NetworkHarness) { + + // We're not going to commit to a script. So our taproot tweak will be + // empty and just specify the necessary flag. + taprootTweak := &signrpc.TaprootTweakDesc{ + KeySpendOnly: true, + } + + keyDesc1, keyDesc2, keyDesc3, allPubKeys := deriveSigningKeys( + ctxt, t, alice, + ) + _, taprootKey, sessResp1, sessResp2, sessResp3 := createMuSigSessions( + ctxt, t, alice, taprootTweak, keyDesc1, keyDesc2, keyDesc3, + allPubKeys, + ) + + // Send some coins to the generated tapscript address. + p2trOutpoint, p2trPkScript := sendToTaprootOutput( + ctxt, t, net, alice, taprootKey, testAmount, + ) + + // Spend the output again, this time back to a p2wkh address. + p2wkhAddr, p2wkhPkScript := newAddrWithScript( + ctxt, t.t, alice, lnrpc.AddressType_WITNESS_PUBKEY_HASH, + ) + + // Create fee estimation for a p2tr input and p2wkh output. + feeRate := chainfee.SatPerKWeight(12500) + estimator := input.TxWeightEstimator{} + estimator.AddTaprootKeySpendInput(txscript.SigHashDefault) + estimator.AddP2WKHOutput() + estimatedWeight := int64(estimator.Weight()) + requiredFee := feeRate.FeeForWeight(estimatedWeight) + + tx := wire.NewMsgTx(2) + tx.TxIn = []*wire.TxIn{{ + PreviousOutPoint: p2trOutpoint, + }} + value := int64(testAmount - requiredFee) + tx.TxOut = []*wire.TxOut{{ + PkScript: p2wkhPkScript, + Value: value, + }} + + var buf bytes.Buffer + require.NoError(t.t, tx.Serialize(&buf)) + + utxoInfo := []*signrpc.TxOut{{ + PkScript: p2trPkScript, + Value: testAmount, + }} + + // We now need to create the raw sighash of the transaction, as that + // will be the message we're signing collaboratively. + prevOutputFetcher := txscript.NewCannedPrevOutputFetcher( + utxoInfo[0].PkScript, utxoInfo[0].Value, + ) + sighashes := txscript.NewTxSigHashes(tx, prevOutputFetcher) + + sigHash, err := txscript.CalcTaprootSignatureHash( + sighashes, txscript.SigHashDefault, tx, 0, prevOutputFetcher, + ) + require.NoError(t.t, err) + + // Now that we have the transaction prepared, we need to start with the + // signing. We simulate all three parties here, so we need to do + // everything three times. But because we're going to use session 1 to + // combine everything, we don't need its response, as it will store its + // own signature. + _, err = alice.SignerClient.MuSig2Sign( + ctxt, &signrpc.MuSig2SignRequest{ + SessionId: sessResp1.SessionId, + MessageDigest: sigHash, + }, + ) + require.NoError(t.t, err) + + signResp2, err := alice.SignerClient.MuSig2Sign( + ctxt, &signrpc.MuSig2SignRequest{ + SessionId: sessResp2.SessionId, + MessageDigest: sigHash, + Cleanup: true, + }, + ) + require.NoError(t.t, err) + + signResp3, err := alice.SignerClient.MuSig2Sign( + ctxt, &signrpc.MuSig2SignRequest{ + SessionId: sessResp3.SessionId, + MessageDigest: sigHash, + Cleanup: true, + }, + ) + require.NoError(t.t, err) + + // Luckily only one of the signers needs to combine the signature, so + // let's do that now. + combineReq1, err := alice.SignerClient.MuSig2CombineSig( + ctxt, &signrpc.MuSig2CombineSigRequest{ + SessionId: sessResp1.SessionId, + OtherPartialSignatures: [][]byte{ + signResp2.LocalPartialSignature, + signResp3.LocalPartialSignature, + }, + }, + ) + require.NoError(t.t, err) + require.Equal(t.t, true, combineReq1.HaveAllSignatures) + require.NotEmpty(t.t, combineReq1.FinalSignature) + + sig, err := schnorr.ParseSignature(combineReq1.FinalSignature) + require.NoError(t.t, err) + require.True(t.t, sig.Verify(sigHash, taprootKey)) + + tx.TxIn[0].Witness = wire.TxWitness{ + combineReq1.FinalSignature, + } + + // Serialize, weigh and publish the TX now, then make sure the + // coins are sent and confirmed to the final sweep destination address. + publishTxAndConfirmSweep( + ctxt, t, net, alice, tx, estimatedWeight, + &chainrpc.SpendRequest{ + Outpoint: &chainrpc.Outpoint{ + Hash: p2trOutpoint.Hash[:], + Index: p2trOutpoint.Index, + }, + Script: p2trPkScript, + }, + p2wkhAddr.String(), + ) +} + +// testTaprootMuSig2KeySpendRootHash tests that a tapscript address can also be +// spent using a MuSig2 combined key. +func testTaprootMuSig2KeySpendRootHash(ctxt context.Context, t *harnessTest, + alice *lntest.HarnessNode, net *lntest.NetworkHarness) { + + // We're going to commit to a script as well. This is a hash lock with a + // simple preimage of "foobar". We need to know this upfront so, we can + // specify the taproot tweak with the root hash when creating the Musig2 + // signing session. + leaf1 := testScriptHashLock(t.t, []byte("foobar")) + rootHash := leaf1.TapHash() + taprootTweak := &signrpc.TaprootTweakDesc{ + ScriptRoot: rootHash[:], + } + + keyDesc1, keyDesc2, keyDesc3, allPubKeys := deriveSigningKeys( + ctxt, t, alice, + ) + _, taprootKey, sessResp1, sessResp2, sessResp3 := createMuSigSessions( + ctxt, t, alice, taprootTweak, keyDesc1, keyDesc2, keyDesc3, + allPubKeys, + ) + + // Send some coins to the generated tapscript address. + p2trOutpoint, p2trPkScript := sendToTaprootOutput( + ctxt, t, net, alice, taprootKey, testAmount, + ) + + // Spend the output again, this time back to a p2wkh address. + p2wkhAddr, p2wkhPkScript := newAddrWithScript( + ctxt, t.t, alice, lnrpc.AddressType_WITNESS_PUBKEY_HASH, + ) + + // Create fee estimation for a p2tr input and p2wkh output. + feeRate := chainfee.SatPerKWeight(12500) + estimator := input.TxWeightEstimator{} + estimator.AddTaprootKeySpendInput(txscript.SigHashDefault) + estimator.AddP2WKHOutput() + estimatedWeight := int64(estimator.Weight()) + requiredFee := feeRate.FeeForWeight(estimatedWeight) + + tx := wire.NewMsgTx(2) + tx.TxIn = []*wire.TxIn{{ + PreviousOutPoint: p2trOutpoint, + }} + value := int64(testAmount - requiredFee) + tx.TxOut = []*wire.TxOut{{ + PkScript: p2wkhPkScript, + Value: value, + }} + + var buf bytes.Buffer + require.NoError(t.t, tx.Serialize(&buf)) + + utxoInfo := []*signrpc.TxOut{{ + PkScript: p2trPkScript, + Value: testAmount, + }} + + // We now need to create the raw sighash of the transaction, as that + // will be the message we're signing collaboratively. + prevOutputFetcher := txscript.NewCannedPrevOutputFetcher( + utxoInfo[0].PkScript, utxoInfo[0].Value, + ) + sighashes := txscript.NewTxSigHashes(tx, prevOutputFetcher) + + sigHash, err := txscript.CalcTaprootSignatureHash( + sighashes, txscript.SigHashDefault, tx, 0, prevOutputFetcher, + ) + require.NoError(t.t, err) + + // Now that we have the transaction prepared, we need to start with the + // signing. We simulate all three parties here, so we need to do + // everything three times. But because we're going to use session 1 to + // combine everything, we don't need its response, as it will store its + // own signature. + _, err = alice.SignerClient.MuSig2Sign( + ctxt, &signrpc.MuSig2SignRequest{ + SessionId: sessResp1.SessionId, + MessageDigest: sigHash, + }, + ) + require.NoError(t.t, err) + + signResp2, err := alice.SignerClient.MuSig2Sign( + ctxt, &signrpc.MuSig2SignRequest{ + SessionId: sessResp2.SessionId, + MessageDigest: sigHash, + Cleanup: true, + }, + ) + require.NoError(t.t, err) + + signResp3, err := alice.SignerClient.MuSig2Sign( + ctxt, &signrpc.MuSig2SignRequest{ + SessionId: sessResp3.SessionId, + MessageDigest: sigHash, + Cleanup: true, + }, + ) + require.NoError(t.t, err) + + // Luckily only one of the signers needs to combine the signature, so + // let's do that now. + combineReq1, err := alice.SignerClient.MuSig2CombineSig( + ctxt, &signrpc.MuSig2CombineSigRequest{ + SessionId: sessResp1.SessionId, + OtherPartialSignatures: [][]byte{ + signResp2.LocalPartialSignature, + signResp3.LocalPartialSignature, + }, + }, + ) + require.NoError(t.t, err) + require.Equal(t.t, true, combineReq1.HaveAllSignatures) + require.NotEmpty(t.t, combineReq1.FinalSignature) + + sig, err := schnorr.ParseSignature(combineReq1.FinalSignature) + require.NoError(t.t, err) + require.True(t.t, sig.Verify(sigHash, taprootKey)) + + tx.TxIn[0].Witness = wire.TxWitness{ + combineReq1.FinalSignature, + } + + // Serialize, weigh and publish the TX now, then make sure the + // coins are sent and confirmed to the final sweep destination address. + publishTxAndConfirmSweep( + ctxt, t, net, alice, tx, estimatedWeight, + &chainrpc.SpendRequest{ + Outpoint: &chainrpc.Outpoint{ + Hash: p2trOutpoint.Hash[:], + Index: p2trOutpoint.Index, + }, + Script: p2trPkScript, + }, + p2wkhAddr.String(), + ) +} + +// testTaprootMuSig2ScriptSpend tests that a tapscript address with an internal +// key that is a MuSig2 combined key can also be spent using the script path. +func testTaprootMuSig2ScriptSpend(ctxt context.Context, t *harnessTest, + alice *lntest.HarnessNode, net *lntest.NetworkHarness) { + + // We're going to commit to a script and spend the output using the + // script. This is a hash lock with a simple preimage of "foobar". We + // need to know this upfront so, we can specify the taproot tweak with + // the root hash when creating the Musig2 signing session. + leaf1 := testScriptHashLock(t.t, []byte("foobar")) + rootHash := leaf1.TapHash() + taprootTweak := &signrpc.TaprootTweakDesc{ + ScriptRoot: rootHash[:], + } + + keyDesc1, keyDesc2, keyDesc3, allPubKeys := deriveSigningKeys( + ctxt, t, alice, + ) + internalKey, taprootKey, _, _, _ := createMuSigSessions( + ctxt, t, alice, taprootTweak, keyDesc1, keyDesc2, keyDesc3, + allPubKeys, + ) + + // Because we know the internal key and the script we want to spend, we + // can now create the tapscript struct that's used for assembling the + // control block and fee estimation. + tapscript := input.TapscriptFullTree(internalKey, leaf1) + + // Send some coins to the generated tapscript address. + p2trOutpoint, p2trPkScript := sendToTaprootOutput( + ctxt, t, net, alice, taprootKey, testAmount, + ) + + // Spend the output again, this time back to a p2wkh address. + p2wkhAddr, p2wkhPkScript := newAddrWithScript( + ctxt, t.t, alice, lnrpc.AddressType_WITNESS_PUBKEY_HASH, + ) + + // Create fee estimation for a p2tr input and p2wkh output. + feeRate := chainfee.SatPerKWeight(12500) + estimator := input.TxWeightEstimator{} + estimator.AddTapscriptInput( + len([]byte("foobar"))+len(leaf1.Script)+1, tapscript, + ) + estimator.AddP2WKHOutput() + estimatedWeight := int64(estimator.Weight()) + requiredFee := feeRate.FeeForWeight(estimatedWeight) + + tx := wire.NewMsgTx(2) + tx.TxIn = []*wire.TxIn{{ + PreviousOutPoint: p2trOutpoint, + }} + value := int64(testAmount - requiredFee) + tx.TxOut = []*wire.TxOut{{ + PkScript: p2wkhPkScript, + Value: value, + }} + + // We can now assemble the witness stack. + controlBlockBytes, err := tapscript.ControlBlock.ToBytes() + require.NoError(t.t, err) + + tx.TxIn[0].Witness = wire.TxWitness{ + []byte("foobar"), + leaf1.Script, + controlBlockBytes, + } + + // Serialize, weigh and publish the TX now, then make sure the + // coins are sent and confirmed to the final sweep destination address. + publishTxAndConfirmSweep( + ctxt, t, net, alice, tx, estimatedWeight, + &chainrpc.SpendRequest{ + Outpoint: &chainrpc.Outpoint{ + Hash: p2trOutpoint.Hash[:], + Index: p2trOutpoint.Index, + }, + Script: p2trPkScript, + }, + p2wkhAddr.String(), + ) +} + +// testTaprootMuSig2CombinedLeafKeySpend tests that a MuSig2 combined key can be +// used for an OP_CHECKSIG inside a tap script leaf spend. +func testTaprootMuSig2CombinedLeafKeySpend(ctxt context.Context, t *harnessTest, + alice *lntest.HarnessNode, net *lntest.NetworkHarness) { + + // We're using the combined MuSig2 key in a script leaf. So we need to + // derive the combined key first, before we can build the script. + keyDesc1, keyDesc2, keyDesc3, allPubKeys := deriveSigningKeys( + ctxt, t, alice, + ) + combineResp, err := alice.SignerClient.MuSig2CombineKeys( + ctxt, &signrpc.MuSig2CombineKeysRequest{ + AllSignerPubkeys: allPubKeys, + }, + ) + require.NoError(t.t, err) + combinedPubKey, err := schnorr.ParsePubKey(combineResp.CombinedKey) + require.NoError(t.t, err) + + // We're going to commit to a script and spend the output using the + // script. This is just an OP_CHECKSIG with the combined MuSig2 public + // key. + leaf := testScriptSchnorrSig(t.t, combinedPubKey) + tapscript := input.TapscriptPartialReveal(dummyInternalKey, leaf, nil) + taprootKey, err := tapscript.TaprootKey() + require.NoError(t.t, err) + + // Send some coins to the generated tapscript address. + p2trOutpoint, p2trPkScript := sendToTaprootOutput( + ctxt, t, net, alice, taprootKey, testAmount, + ) + + // Spend the output again, this time back to a p2wkh address. + p2wkhAddr, p2wkhPkScript := newAddrWithScript( + ctxt, t.t, alice, lnrpc.AddressType_WITNESS_PUBKEY_HASH, + ) + + // Create fee estimation for a p2tr input and p2wkh output. + feeRate := chainfee.SatPerKWeight(12500) + estimator := input.TxWeightEstimator{} + estimator.AddTapscriptInput( + input.TaprootSignatureWitnessSize, tapscript, + ) + estimator.AddP2WKHOutput() + estimatedWeight := int64(estimator.Weight()) + requiredFee := feeRate.FeeForWeight(estimatedWeight) + + tx := wire.NewMsgTx(2) + tx.TxIn = []*wire.TxIn{{ + PreviousOutPoint: p2trOutpoint, + }} + value := int64(testAmount - requiredFee) + tx.TxOut = []*wire.TxOut{{ + PkScript: p2wkhPkScript, + Value: value, + }} + + var buf bytes.Buffer + require.NoError(t.t, tx.Serialize(&buf)) + + utxoInfo := []*signrpc.TxOut{{ + PkScript: p2trPkScript, + Value: testAmount, + }} + + // Do the actual signing now. + _, _, sessResp1, sessResp2, sessResp3 := createMuSigSessions( + ctxt, t, alice, nil, keyDesc1, keyDesc2, keyDesc3, allPubKeys, + ) + require.NoError(t.t, err) + + // We now need to create the raw sighash of the transaction, as that + // will be the message we're signing collaboratively. + prevOutputFetcher := txscript.NewCannedPrevOutputFetcher( + utxoInfo[0].PkScript, utxoInfo[0].Value, + ) + sighashes := txscript.NewTxSigHashes(tx, prevOutputFetcher) + + sigHash, err := txscript.CalcTapscriptSignaturehash( + sighashes, txscript.SigHashDefault, tx, 0, prevOutputFetcher, + leaf, + ) + require.NoError(t.t, err) + + // Now that we have the transaction prepared, we need to start with the + // signing. We simulate all three parties here, so we need to do + // everything three times. But because we're going to use session 1 to + // combine everything, we don't need its response, as it will store its + // own signature. + _, err = alice.SignerClient.MuSig2Sign( + ctxt, &signrpc.MuSig2SignRequest{ + SessionId: sessResp1.SessionId, + MessageDigest: sigHash, + }, + ) + require.NoError(t.t, err) + + signResp2, err := alice.SignerClient.MuSig2Sign( + ctxt, &signrpc.MuSig2SignRequest{ + SessionId: sessResp2.SessionId, + MessageDigest: sigHash, + Cleanup: true, + }, + ) + require.NoError(t.t, err) + + // Before we have all partial signatures, we shouldn't get a final + // signature back. + combineSigResp, err := alice.SignerClient.MuSig2CombineSig( + ctxt, &signrpc.MuSig2CombineSigRequest{ + SessionId: sessResp1.SessionId, + OtherPartialSignatures: [][]byte{ + signResp2.LocalPartialSignature, + }, + }, + ) + require.NoError(t.t, err) + require.False(t.t, combineSigResp.HaveAllSignatures) + require.Empty(t.t, combineSigResp.FinalSignature) + + signResp3, err := alice.SignerClient.MuSig2Sign( + ctxt, &signrpc.MuSig2SignRequest{ + SessionId: sessResp3.SessionId, + MessageDigest: sigHash, + }, + ) + require.NoError(t.t, err) + + // We manually clean up session 3, just to make sure that works as well. + _, err = alice.SignerClient.MuSig2Cleanup( + ctxt, &signrpc.MuSig2CleanupRequest{ + SessionId: sessResp3.SessionId, + }, + ) + require.NoError(t.t, err) + + // A second call to that cleaned up session should now fail with a + // specific error. + _, err = alice.SignerClient.MuSig2Sign( + ctxt, &signrpc.MuSig2SignRequest{ + SessionId: sessResp3.SessionId, + MessageDigest: sigHash, + }, + ) + require.Error(t.t, err) + require.Contains(t.t, err.Error(), "not found") + + // Luckily only one of the signers needs to combine the signature, so + // let's do that now. + combineReq1, err := alice.SignerClient.MuSig2CombineSig( + ctxt, &signrpc.MuSig2CombineSigRequest{ + SessionId: sessResp1.SessionId, + OtherPartialSignatures: [][]byte{ + signResp3.LocalPartialSignature, + }, + }, + ) + require.NoError(t.t, err) + require.Equal(t.t, true, combineReq1.HaveAllSignatures) + require.NotEmpty(t.t, combineReq1.FinalSignature) + + sig, err := schnorr.ParseSignature(combineReq1.FinalSignature) + require.NoError(t.t, err) + require.True(t.t, sig.Verify(sigHash, combinedPubKey)) + + // We can now assemble the witness stack. + controlBlockBytes, err := tapscript.ControlBlock.ToBytes() + require.NoError(t.t, err) + + tx.TxIn[0].Witness = wire.TxWitness{ + combineReq1.FinalSignature, + leaf.Script, + controlBlockBytes, + } + + // Serialize, weigh and publish the TX now, then make sure the + // coins are sent and confirmed to the final sweep destination address. + publishTxAndConfirmSweep( + ctxt, t, net, alice, tx, estimatedWeight, + &chainrpc.SpendRequest{ + Outpoint: &chainrpc.Outpoint{ + Hash: p2trOutpoint.Hash[:], + Index: p2trOutpoint.Index, + }, + Script: p2trPkScript, + }, + p2wkhAddr.String(), + ) +} + +// testScriptHashLock returns a simple bitcoin script that locks the funds to +// a hash lock of the given preimage. +func testScriptHashLock(t *testing.T, preimage []byte) txscript.TapLeaf { + builder := txscript.NewScriptBuilder() + builder.AddOp(txscript.OP_DUP) + builder.AddOp(txscript.OP_HASH160) + builder.AddData(btcutil.Hash160(preimage)) + builder.AddOp(txscript.OP_EQUALVERIFY) + script1, err := builder.Script() + require.NoError(t, err) + return txscript.NewBaseTapLeaf(script1) +} + +// testScriptSchnorrSig returns a simple bitcoin script that locks the funds to +// a Schnorr signature of the given public key. +func testScriptSchnorrSig(t *testing.T, + pubKey *btcec.PublicKey) txscript.TapLeaf { + + builder := txscript.NewScriptBuilder() + builder.AddData(schnorr.SerializePubKey(pubKey)) + builder.AddOp(txscript.OP_CHECKSIG) + script2, err := builder.Script() + require.NoError(t, err) + return txscript.NewBaseTapLeaf(script2) +} + +// newAddrWithScript returns a new address and its pkScript. +func newAddrWithScript(ctx context.Context, t *testing.T, + node *lntest.HarnessNode, addrType lnrpc.AddressType) (btcutil.Address, + []byte) { + + p2wkhResp, err := node.NewAddress(ctx, &lnrpc.NewAddressRequest{ + Type: addrType, + }) + require.NoError(t, err) + + p2wkhAddr, err := btcutil.DecodeAddress( + p2wkhResp.Address, harnessNetParams, + ) + require.NoError(t, err) + + p2wkhPkScript, err := txscript.PayToAddrScript(p2wkhAddr) + require.NoError(t, err) + + return p2wkhAddr, p2wkhPkScript +} + +// sendToTaprootOutput sends coins to a p2tr output of the given taproot key and +// mines a block to confirm the coins. +func sendToTaprootOutput(ctx context.Context, t *harnessTest, + net *lntest.NetworkHarness, node *lntest.HarnessNode, + taprootKey *btcec.PublicKey, amt int64) (wire.OutPoint, []byte) { + + tapScriptAddr, err := btcutil.NewAddressTaproot( + schnorr.SerializePubKey(taprootKey), harnessNetParams, + ) + require.NoError(t.t, err) + p2trPkScript, err := txscript.PayToAddrScript(tapScriptAddr) + require.NoError(t.t, err) + + // Send some coins to the generated tapscript address. + _, err = node.SendCoins(ctx, &lnrpc.SendCoinsRequest{ + Addr: tapScriptAddr.String(), + Amount: amt, + }) + require.NoError(t.t, err) + + // Wait until the TX is found in the mempool. + txid, err := waitForTxInMempool(net.Miner.Client, minerMempoolTimeout) + require.NoError(t.t, err) + + p2trOutputIndex := getOutputIndex( + t, net.Miner, txid, tapScriptAddr.String(), + ) + p2trOutpoint := wire.OutPoint{ + Hash: *txid, + Index: uint32(p2trOutputIndex), + } + + // Clear the mempool. + mineBlocks(t, net, 1, 1) + + return p2trOutpoint, p2trPkScript +} + +// publishTxAndConfirmSweep is a helper function that publishes a transaction +// after checking its weight against an estimate. After asserting the given +// spend request, the given sweep address' balance is verified to be seen as +// funds belonging to the wallet. +func publishTxAndConfirmSweep(ctx context.Context, t *harnessTest, + net *lntest.NetworkHarness, node *lntest.HarnessNode, tx *wire.MsgTx, + estimatedWeight int64, spendRequest *chainrpc.SpendRequest, + sweepAddr string) { + + // Before we publish the tx that spends the p2tr transaction, we want to + // register a spend listener that we expect to fire after mining the + // block. + _, currentHeight, err := net.Miner.Client.GetBestBlock() + require.NoError(t.t, err) + + // For a Taproot output we cannot leave the outpoint empty. Let's make + // sure the API returns the correct error here. + spendClient, err := node.ChainClient.RegisterSpendNtfn( + ctx, &chainrpc.SpendRequest{ + Script: spendRequest.Script, + HeightHint: uint32(currentHeight), + }, + ) + require.NoError(t.t, err) + + // The error is only thrown when trying to read a message. + _, err = spendClient.Recv() + require.Contains( + t.t, err.Error(), + "cannot register witness v1 spend request without outpoint", + ) + + // Now try again, this time with the outpoint set. + spendClient, err = node.ChainClient.RegisterSpendNtfn( + ctx, &chainrpc.SpendRequest{ + Outpoint: spendRequest.Outpoint, + Script: spendRequest.Script, + HeightHint: uint32(currentHeight), + }, + ) + require.NoError(t.t, err) + + var buf bytes.Buffer + require.NoError(t.t, tx.Serialize(&buf)) + + // Since Schnorr signatures are fixed size, we must be able to estimate + // the size of this transaction exactly. + txWeight := blockchain.GetTransactionWeight(btcutil.NewTx(tx)) + require.Equal(t.t, estimatedWeight, txWeight) + + _, err = node.WalletKitClient.PublishTransaction( + ctx, &walletrpc.Transaction{ + TxHex: buf.Bytes(), + }, + ) + require.NoError(t.t, err) + + // Make sure the coins sent to the address are confirmed correctly, + // including the confirmation notification. + confirmAddress(ctx, t, net, node, sweepAddr) + + // We now expect our spend event to go through. + spendMsg, err := spendClient.Recv() + require.NoError(t.t, err) + spend := spendMsg.GetSpend() + require.NotNil(t.t, spend) + require.Equal(t.t, spend.SpendingHeight, uint32(currentHeight+1)) +} + +// confirmAddress makes sure that a transaction in the mempool spends funds to +// the given address. It also checks that a confirmation notification for the +// address is triggered when the transaction is mined. +func confirmAddress(ctx context.Context, t *harnessTest, + net *lntest.NetworkHarness, node *lntest.HarnessNode, + addrString string) { + + // Wait until the tx that sends to the address is found. + txid, err := waitForTxInMempool(net.Miner.Client, minerMempoolTimeout) + require.NoError(t.t, err) + + // Wait until bob has seen the tx and considers it as owned. + addrOutputIndex := getOutputIndex(t, net.Miner, txid, addrString) + op := &lnrpc.OutPoint{ + TxidBytes: txid[:], + OutputIndex: uint32(addrOutputIndex), + } + assertWalletUnspent(t, node, op) + + // Before we confirm the transaction, let's register a confirmation + // listener for it, which we expect to fire after mining a block. + parsedAddr, err := btcutil.DecodeAddress(addrString, harnessNetParams) + require.NoError(t.t, err) + addrPkScript, err := txscript.PayToAddrScript(parsedAddr) + require.NoError(t.t, err) + + _, currentHeight, err := net.Miner.Client.GetBestBlock() + require.NoError(t.t, err) + confClient, err := node.ChainClient.RegisterConfirmationsNtfn( + ctx, &chainrpc.ConfRequest{ + Script: addrPkScript, + Txid: txid[:], + HeightHint: uint32(currentHeight), + NumConfs: 1, + }, + ) + require.NoError(t.t, err) + + // Mine another block to clean up the mempool. + mineBlocks(t, net, 1, 1) + + // We now expect our confirmation to go through. + confMsg, err := confClient.Recv() + require.NoError(t.t, err) + conf := confMsg.GetConf() + require.NotNil(t.t, conf) + require.Equal(t.t, conf.BlockHeight, uint32(currentHeight+1)) +} + +// deriveSigningKeys derives three signing keys and returns their descriptors, +// as well as the public keys in the Schnorr serialized format. +func deriveSigningKeys(ctx context.Context, t *harnessTest, + node *lntest.HarnessNode) (*signrpc.KeyDescriptor, + *signrpc.KeyDescriptor, *signrpc.KeyDescriptor, [][]byte) { + + // For muSig2 we need multiple keys. We derive three of them from the + // same wallet, just so we know we can also sign for them again. + keyDesc1, err := node.WalletKitClient.DeriveNextKey( + ctx, &walletrpc.KeyReq{KeyFamily: testTaprootKeyFamily}, + ) + require.NoError(t.t, err) + pubKey1, err := btcec.ParsePubKey(keyDesc1.RawKeyBytes) + require.NoError(t.t, err) + + keyDesc2, err := node.WalletKitClient.DeriveNextKey( + ctx, &walletrpc.KeyReq{KeyFamily: testTaprootKeyFamily}, + ) + require.NoError(t.t, err) + pubKey2, err := btcec.ParsePubKey(keyDesc2.RawKeyBytes) + require.NoError(t.t, err) + + keyDesc3, err := node.WalletKitClient.DeriveNextKey( + ctx, &walletrpc.KeyReq{KeyFamily: testTaprootKeyFamily}, + ) + require.NoError(t.t, err) + pubKey3, err := btcec.ParsePubKey(keyDesc3.RawKeyBytes) + require.NoError(t.t, err) + + // Now that we have all three keys we can create three sessions, one + // for each of the signers. This would of course normally not happen on + // the same node. + allPubKeys := [][]byte{ + schnorr.SerializePubKey(pubKey1), + schnorr.SerializePubKey(pubKey2), + schnorr.SerializePubKey(pubKey3), + } + + return keyDesc1, keyDesc2, keyDesc3, allPubKeys +} + +// createMuSigSessions creates a MuSig2 session with three keys that are +// combined into a single key. The same node is used for the three signing +// participants but a separate key is generated for each session. So the result +// should be the same as if it were three different nodes. +func createMuSigSessions(ctx context.Context, t *harnessTest, + node *lntest.HarnessNode, taprootTweak *signrpc.TaprootTweakDesc, + keyDesc1, keyDesc2, keyDesc3 *signrpc.KeyDescriptor, + allPubKeys [][]byte) (*btcec.PublicKey, *btcec.PublicKey, + *signrpc.MuSig2SessionResponse, *signrpc.MuSig2SessionResponse, + *signrpc.MuSig2SessionResponse) { + + sessResp1, err := node.SignerClient.MuSig2CreateSession( + ctx, &signrpc.MuSig2SessionRequest{ + KeyLoc: keyDesc1.KeyLoc, + AllSignerPubkeys: allPubKeys, + TaprootTweak: taprootTweak, + }, + ) + require.NoError(t.t, err) + + // Now that we have the three keys in a combined form, we want to make + // sure the tweaking for the taproot key worked correctly. We first need + // to parse the combined key without any tweaks applied to it. That will + // be our internal key. Once we know that, we can tweak it with the + // tapHash of the script root hash. We should arrive at the same result + // as the API. + combinedKey, err := schnorr.ParsePubKey(sessResp1.CombinedKey) + require.NoError(t.t, err) + + // When combining the key without creating a session, we expect the same + // combined key to be created. + expectedCombinedKey := combinedKey + + // Without a tweak, the internal key is equal to the combined key. + internalKey := combinedKey + + // If there is a tweak, then there is the internal, pre-tweaked combined + // key and the taproot key which is fully tweaked. + if taprootTweak != nil { + internalKey, err = schnorr.ParsePubKey( + sessResp1.TaprootInternalKey, + ) + require.NoError(t.t, err) + + // We now know the taproot key. The session with the tweak + // applied should produce the same key! + expectedCombinedKey = txscript.ComputeTaprootOutputKey( + internalKey, taprootTweak.ScriptRoot, + ) + require.Equal( + t.t, schnorr.SerializePubKey(expectedCombinedKey), + schnorr.SerializePubKey(combinedKey), + ) + } + + // We should also get the same keys when just calling the + // MuSig2CombineKeys RPC. + combineResp, err := node.SignerClient.MuSig2CombineKeys( + ctx, &signrpc.MuSig2CombineKeysRequest{ + AllSignerPubkeys: allPubKeys, + TaprootTweak: taprootTweak, + }, + ) + require.NoError(t.t, err) + require.Equal( + t.t, schnorr.SerializePubKey(expectedCombinedKey), + combineResp.CombinedKey, + ) + require.Equal( + t.t, schnorr.SerializePubKey(internalKey), + combineResp.TaprootInternalKey, + ) + + // Everything is good so far, let's continue with creating the signing + // session for the other two participants. + sessResp2, err := node.SignerClient.MuSig2CreateSession( + ctx, &signrpc.MuSig2SessionRequest{ + KeyLoc: keyDesc2.KeyLoc, + AllSignerPubkeys: allPubKeys, + OtherSignerPublicNonces: [][]byte{ + sessResp1.LocalPublicNonces, + }, + TaprootTweak: taprootTweak, + }, + ) + require.NoError(t.t, err) + require.Equal(t.t, sessResp1.CombinedKey, sessResp2.CombinedKey) + + sessResp3, err := node.SignerClient.MuSig2CreateSession( + ctx, &signrpc.MuSig2SessionRequest{ + KeyLoc: keyDesc3.KeyLoc, + AllSignerPubkeys: allPubKeys, + OtherSignerPublicNonces: [][]byte{ + sessResp1.LocalPublicNonces, + sessResp2.LocalPublicNonces, + }, + TaprootTweak: taprootTweak, + }, + ) + require.NoError(t.t, err) + require.Equal(t.t, sessResp2.CombinedKey, sessResp3.CombinedKey) + require.Equal(t.t, true, sessResp3.HaveAllNonces) + + // We need to distribute the rest of the nonces. + nonceResp1, err := node.SignerClient.MuSig2RegisterNonces( + ctx, &signrpc.MuSig2RegisterNoncesRequest{ + SessionId: sessResp1.SessionId, + OtherSignerPublicNonces: [][]byte{ + sessResp2.LocalPublicNonces, + sessResp3.LocalPublicNonces, + }, + }, + ) + require.NoError(t.t, err) + require.Equal(t.t, true, nonceResp1.HaveAllNonces) + + nonceResp2, err := node.SignerClient.MuSig2RegisterNonces( + ctx, &signrpc.MuSig2RegisterNoncesRequest{ + SessionId: sessResp2.SessionId, + OtherSignerPublicNonces: [][]byte{ + sessResp3.LocalPublicNonces, + }, + }, + ) + require.NoError(t.t, err) + require.Equal(t.t, true, nonceResp2.HaveAllNonces) + + return internalKey, combinedKey, sessResp1, sessResp2, sessResp3 +} diff --git a/lntest/itest/lnd_test.go b/lntest/itest/lnd_test.go index 0b342e3820..ebffaa91c4 100644 --- a/lntest/itest/lnd_test.go +++ b/lntest/itest/lnd_test.go @@ -9,7 +9,6 @@ import ( "time" "github.com/btcsuite/btcd/integration/rpctest" - "github.com/btcsuite/btcd/rpcclient" "github.com/lightningnetwork/lnd/lntest" "github.com/stretchr/testify/require" ) @@ -43,7 +42,7 @@ var ( "split test cases with the given (0-based) index", ) - // dbBackendFlag specifies the backend to use + // dbBackendFlag specifies the backend to use. dbBackendFlag = flag.String("dbbackend", "bbolt", "Database backend "+ "(bbolt, etcd, postgres)") ) @@ -113,14 +112,10 @@ func TestLightningNetworkDaemon(t *testing.T) { // guarantees of getting included in to blocks. // // We will also connect it to our chain backend. - minerLogDir := fmt.Sprintf("%s/.minerlogs", logDir) - miner, minerCleanUp, err := lntest.NewMiner( - minerLogDir, "output_btcd_miner.log", harnessNetParams, - &rpcclient.NotificationHandlers{}, lntest.GetBtcdBinary(), - ) + miner, err := lntest.NewMiner() require.NoError(t, err, "failed to create new miner") defer func() { - require.NoError(t, minerCleanUp(), "failed to clean up miner") + require.NoError(t, miner.Stop(), "failed to stop miner") }() // Start a chain backend. @@ -237,8 +232,8 @@ func TestLightningNetworkDaemon(t *testing.T) { testCase.name, ) - lndHarness.Alice.AddToLog(logLine) - lndHarness.Bob.AddToLog(logLine) + lndHarness.Alice.AddToLogf(logLine) + lndHarness.Bob.AddToLogf(logLine) // Start every test with the default static fee estimate. lndHarness.SetFeeEstimate(12500) diff --git a/lntest/itest/lnd_test_list_on_test.go b/lntest/itest/lnd_test_list_on_test.go index 5fa6b95921..82b71e9f19 100644 --- a/lntest/itest/lnd_test_list_on_test.go +++ b/lntest/itest/lnd_test_list_on_test.go @@ -24,6 +24,10 @@ var allTestCases = []*testCase{ name: "basic funding flow", test: testBasicChannelFunding, }, + { + name: "basic funding flow with all input types", + test: testChannelFundingInputTypes, + }, { name: "unconfirmed channel funding", test: testUnconfirmedChannelFunding, @@ -32,6 +36,10 @@ var allTestCases = []*testCase{ name: "update channel policy", test: testUpdateChannelPolicy, }, + { + name: "update channel policy fee rate accuracy", + test: testUpdateChannelPolicyFeeRateAccuracy, + }, { name: "open channel reorg test", test: testOpenChannelAfterReorg, @@ -80,6 +88,10 @@ var allTestCases = []*testCase{ name: "update channel status", test: testUpdateChanStatus, }, + { + name: "test update node announcement rpc", + test: testUpdateNodeAnnouncement, + }, { name: "list outgoing payments", test: testListPayments, @@ -153,6 +165,10 @@ var allTestCases = []*testCase{ name: "derive shared key", test: testDeriveSharedKey, }, + { + name: "sign output raw", + test: testSignOutputRaw, + }, { name: "async payments benchmark", test: testAsyncPayments, @@ -283,10 +299,18 @@ var allTestCases = []*testCase{ name: "psbt channel funding external", test: testPsbtChanFundingExternal, }, + { + name: "sign psbt", + test: testSignPsbt, + }, { name: "batch channel funding", test: testBatchChanFunding, }, + { + name: "psbt channel funding single step", + test: testPsbtChanFundingSingleStep, + }, { name: "sendtoroute multi path payment", test: testSendToRouteMultiPath, @@ -367,4 +391,20 @@ var allTestCases = []*testCase{ name: "remote signer", test: testRemoteSigner, }, + { + name: "3rd party anchor spend", + test: testAnchorThirdPartySpend, + }, + { + name: "taproot", + test: testTaproot, + }, + { + name: "addpeer config", + test: testAddPeerConfig, + }, + { + name: "resolution handoff", + test: testResHandoff, + }, } diff --git a/lntest/itest/lnd_wallet_import_test.go b/lntest/itest/lnd_wallet_import_test.go index db898282df..616372e0fb 100644 --- a/lntest/itest/lnd_wallet_import_test.go +++ b/lntest/itest/lnd_wallet_import_test.go @@ -9,11 +9,11 @@ import ( "testing" "time" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/hdkeychain" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" - "github.com/btcsuite/btcutil/hdkeychain" "github.com/btcsuite/btcwallet/waddrmgr" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/funding" @@ -496,11 +496,9 @@ func fundChanAndCloseFromImportedAccount(t *harnessTest, srcNode, destNode, }, OutputIndex: upd.ChanPending.OutputIndex, } - ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) - defer cancel() - err = srcNode.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = srcNode.WaitForNetworkChannelOpen(chanPoint) require.NoError(t.t, err) - err = destNode.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = destNode.WaitForNetworkChannelOpen(chanPoint) require.NoError(t.t, err) // Send a test payment to ensure the channel is operating as normal. diff --git a/lntest/itest/lnd_wipe_fwdpkgs_test.go b/lntest/itest/lnd_wipe_fwdpkgs_test.go index 8ff6da6855..7b4b969074 100644 --- a/lntest/itest/lnd_wipe_fwdpkgs_test.go +++ b/lntest/itest/lnd_wipe_fwdpkgs_test.go @@ -52,7 +52,7 @@ func testWipeForwardingPackages(net *lntest.NetworkHarness, // close channel should now become pending force closed channel. pendingChan = assertPendingForceClosedChannel(t.t, net.Bob) - // Check the forwarding pacakges are deleted. + // Check the forwarding packages are deleted. require.Zero(t.t, pendingChan.NumForwardingPackages) // For Alice, the forwarding packages should have been wiped too. @@ -87,7 +87,7 @@ func testWipeForwardingPackages(net *lntest.NetworkHarness, // really contains is channels whose closing tx has been broadcast. pendingChan = assertPendingForceClosedChannel(t.t, net.Bob) - // Check the forwarding pacakges are deleted. + // Check the forwarding packages are deleted. require.Zero(t.t, pendingChan.NumForwardingPackages) // Mine a block to confirm sweep transactions such that they diff --git a/lntest/itest/lnd_wumbo_channels_test.go b/lntest/itest/lnd_wumbo_channels_test.go index a8fc626b10..ddfd6d6774 100644 --- a/lntest/itest/lnd_wumbo_channels_test.go +++ b/lntest/itest/lnd_wumbo_channels_test.go @@ -3,7 +3,7 @@ package itest import ( "strings" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/funding" "github.com/lightningnetwork/lnd/lntest" ) diff --git a/lntest/itest/test_harness.go b/lntest/itest/test_harness.go index 494db9d427..1aff55b0a3 100644 --- a/lntest/itest/test_harness.go +++ b/lntest/itest/test_harness.go @@ -46,6 +46,7 @@ const ( AddrTypeWitnessPubkeyHash = lnrpc.AddressType_WITNESS_PUBKEY_HASH AddrTypeNestedPubkeyHash = lnrpc.AddressType_NESTED_PUBKEY_HASH + AddrTypeTaprootPubkey = lnrpc.AddressType_TAPROOT_PUBKEY ) // harnessTest wraps a regular testing.T providing enhanced error detection @@ -97,7 +98,6 @@ func (h *harnessTest) Fatalf(format string, a ...interface{}) { // RunTestCase executes a harness test case. Any errors or panics will be // represented as fatal. func (h *harnessTest) RunTestCase(testCase *testCase) { - h.testCase = testCase defer func() { h.testCase = nil diff --git a/lntest/itest/utils.go b/lntest/itest/utils.go index 84c3d776ec..fdc611eb2d 100644 --- a/lntest/itest/utils.go +++ b/lntest/itest/utils.go @@ -7,9 +7,11 @@ import ( "io" "time" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/rpcclient" + "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnrpc" @@ -136,7 +138,7 @@ func completePaymentRequests(client lnrpc.LightningClient, return nil } -// makeFakePayHash creates random pre image hash +// makeFakePayHash creates random pre image hash. func makeFakePayHash(t *harnessTest) []byte { randBuf := make([]byte, 32) @@ -190,7 +192,6 @@ func createPayReqs(node *lntest.HarnessNode, paymentAmt btcutil.Amount, // getChanInfo is a helper method for getting channel info for a node's sole // channel. func getChanInfo(node *lntest.HarnessNode) (*lnrpc.Channel, error) { - ctxb := context.Background() ctx, cancel := context.WithTimeout(ctxb, defaultTimeout) defer cancel() @@ -301,7 +302,6 @@ func calculateMaxHtlc(chanCap btcutil.Amount) uint64 { // waitForNodeBlockHeight queries the node for its current block height until // it reaches the passed height. func waitForNodeBlockHeight(node *lntest.HarnessNode, height int32) error { - ctxb := context.Background() ctx, cancel := context.WithTimeout(ctxb, defaultTimeout) defer cancel() @@ -465,3 +465,31 @@ func findTxAtHeight(t *harnessTest, height int32, return nil } + +// getOutputIndex returns the output index of the given address in the given +// transaction. +func getOutputIndex(t *harnessTest, miner *lntest.HarnessMiner, + txid *chainhash.Hash, addr string) int { + + t.t.Helper() + + // We'll then extract the raw transaction from the mempool in order to + // determine the index of the p2tr output. + tx, err := miner.Client.GetRawTransaction(txid) + require.NoError(t.t, err) + + p2trOutputIndex := -1 + for i, txOut := range tx.MsgTx().TxOut { + _, addrs, _, err := txscript.ExtractPkScriptAddrs( + txOut.PkScript, miner.ActiveNet, + ) + require.NoError(t.t, err) + + if addrs[0].String() == addr { + p2trOutputIndex = i + } + } + require.Greater(t.t, p2trOutputIndex, -1) + + return p2trOutputIndex +} diff --git a/lntest/mock/chainnotifier.go b/lntest/mock/chainnotifier.go index 185d15cd23..b7c9377c24 100644 --- a/lntest/mock/chainnotifier.go +++ b/lntest/mock/chainnotifier.go @@ -3,7 +3,6 @@ package mock import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/lightningnetwork/lnd/chainntnfs" ) diff --git a/lntest/mock/secretkeyring.go b/lntest/mock/secretkeyring.go index 1e2a4390a7..57f53256a0 100644 --- a/lntest/mock/secretkeyring.go +++ b/lntest/mock/secretkeyring.go @@ -1,7 +1,10 @@ package mock import ( - "github.com/btcsuite/btcd/btcec" + "fmt" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/keychain" ) @@ -9,6 +12,7 @@ import ( // SecretKeyRing is a mock implementation of the SecretKeyRing interface. type SecretKeyRing struct { RootKey *btcec.PrivateKey + Fail bool } // DeriveNextKey currently returns dummy values. @@ -24,6 +28,10 @@ func (s *SecretKeyRing) DeriveNextKey( func (s *SecretKeyRing) DeriveKey( _ keychain.KeyLocator) (keychain.KeyDescriptor, error) { + if s.Fail { + return keychain.KeyDescriptor{}, fmt.Errorf("fail") + } + return keychain.KeyDescriptor{ PubKey: s.RootKey.PubKey(), }, nil @@ -45,7 +53,7 @@ func (s *SecretKeyRing) ECDH(_ keychain.KeyDescriptor, // SignMessage signs the passed message and ignores the KeyDescriptor. func (s *SecretKeyRing) SignMessage(_ keychain.KeyLocator, - msg []byte, doubleHash bool) (*btcec.Signature, error) { + msg []byte, doubleHash bool) (*ecdsa.Signature, error) { var digest []byte if doubleHash { @@ -53,7 +61,7 @@ func (s *SecretKeyRing) SignMessage(_ keychain.KeyLocator, } else { digest = chainhash.HashB(msg) } - return s.RootKey.Sign(digest) + return ecdsa.Sign(s.RootKey, digest), nil } // SignMessageCompact signs the passed message. @@ -66,5 +74,5 @@ func (s *SecretKeyRing) SignMessageCompact(_ keychain.KeyLocator, } else { digest = chainhash.HashB(msg) } - return btcec.SignCompact(btcec.S256(), s.RootKey, digest, true) + return ecdsa.SignCompact(s.RootKey, digest, true) } diff --git a/lntest/mock/signer.go b/lntest/mock/signer.go index a0ce79b929..4dd465cd87 100644 --- a/lntest/mock/signer.go +++ b/lntest/mock/signer.go @@ -1,9 +1,13 @@ package mock import ( + "crypto/sha256" "fmt" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" @@ -46,6 +50,55 @@ func (d *DummySigner) ComputeInputScript(tx *wire.MsgTx, return &input.Script{}, nil } +// MuSig2CreateSession creates a new MuSig2 signing session using the local +// key identified by the key locator. The complete list of all public keys of +// all signing parties must be provided, including the public key of the local +// signing key. If nonces of other parties are already known, they can be +// submitted as well to reduce the number of method calls necessary later on. +func (d *DummySigner) MuSig2CreateSession(keychain.KeyLocator, + []*btcec.PublicKey, *input.MuSig2Tweaks, + [][musig2.PubNonceSize]byte) (*input.MuSig2SessionInfo, error) { + + return nil, nil +} + +// MuSig2RegisterNonces registers one or more public nonces of other signing +// participants for a session identified by its ID. This method returns true +// once we have all nonces for all other signing participants. +func (d *DummySigner) MuSig2RegisterNonces(input.MuSig2SessionID, + [][musig2.PubNonceSize]byte) (bool, error) { + + return false, nil +} + +// MuSig2Sign creates a partial signature using the local signing key +// that was specified when the session was created. This can only be +// called when all public nonces of all participants are known and have +// been registered with the session. If this node isn't responsible for +// combining all the partial signatures, then the cleanup parameter +// should be set, indicating that the session can be removed from memory +// once the signature was produced. +func (d *DummySigner) MuSig2Sign(input.MuSig2SessionID, + [sha256.Size]byte, bool) (*musig2.PartialSignature, error) { + + return nil, nil +} + +// MuSig2CombineSig combines the given partial signature(s) with the +// local one, if it already exists. Once a partial signature of all +// participants is registered, the final signature will be combined and +// returned. +func (d *DummySigner) MuSig2CombineSig(input.MuSig2SessionID, + []*musig2.PartialSignature) (*schnorr.Signature, bool, error) { + + return nil, false, nil +} + +// MuSig2Cleanup removes a session from memory to free up resources. +func (d *DummySigner) MuSig2Cleanup(input.MuSig2SessionID) error { + return nil +} + // SingleSigner is an implementation of the Signer interface that signs // everything with a single private key. type SingleSigner struct { @@ -82,7 +135,7 @@ func (s *SingleSigner) SignOutputRaw(tx *wire.MsgTx, return nil, err } - return btcec.ParseDERSignature(sig[:len(sig)-1], btcec.S256()) + return ecdsa.ParseDERSignature(sig[:len(sig)-1]) } // ComputeInputScript computes an input script with the stored private key @@ -116,7 +169,7 @@ func (s *SingleSigner) ComputeInputScript(tx *wire.MsgTx, // SignMessage takes a public key and a message and only signs the message // with the stored private key if the public key matches the private key. func (s *SingleSigner) SignMessage(keyLoc keychain.KeyLocator, - msg []byte, doubleHash bool) (*btcec.Signature, error) { + msg []byte, doubleHash bool) (*ecdsa.Signature, error) { mockKeyLoc := s.KeyLoc if s.KeyLoc.IsEmpty() { @@ -133,10 +186,54 @@ func (s *SingleSigner) SignMessage(keyLoc keychain.KeyLocator, } else { digest = chainhash.HashB(msg) } - sign, err := s.Privkey.Sign(digest) - if err != nil { - return nil, fmt.Errorf("can't sign the message: %v", err) - } + return ecdsa.Sign(s.Privkey, digest), nil +} + +// MuSig2CreateSession creates a new MuSig2 signing session using the local +// key identified by the key locator. The complete list of all public keys of +// all signing parties must be provided, including the public key of the local +// signing key. If nonces of other parties are already known, they can be +// submitted as well to reduce the number of method calls necessary later on. +func (s *SingleSigner) MuSig2CreateSession(keychain.KeyLocator, + []*btcec.PublicKey, *input.MuSig2Tweaks, + [][musig2.PubNonceSize]byte) (*input.MuSig2SessionInfo, error) { + + return nil, nil +} + +// MuSig2RegisterNonces registers one or more public nonces of other signing +// participants for a session identified by its ID. This method returns true +// once we have all nonces for all other signing participants. +func (s *SingleSigner) MuSig2RegisterNonces(input.MuSig2SessionID, + [][musig2.PubNonceSize]byte) (bool, error) { + + return false, nil +} + +// MuSig2Sign creates a partial signature using the local signing key +// that was specified when the session was created. This can only be +// called when all public nonces of all participants are known and have +// been registered with the session. If this node isn't responsible for +// combining all the partial signatures, then the cleanup parameter +// should be set, indicating that the session can be removed from memory +// once the signature was produced. +func (s *SingleSigner) MuSig2Sign(input.MuSig2SessionID, + [sha256.Size]byte, bool) (*musig2.PartialSignature, error) { + + return nil, nil +} + +// MuSig2CombineSig combines the given partial signature(s) with the +// local one, if it already exists. Once a partial signature of all +// participants is registered, the final signature will be combined and +// returned. +func (s *SingleSigner) MuSig2CombineSig(input.MuSig2SessionID, + []*musig2.PartialSignature) (*schnorr.Signature, bool, error) { + + return nil, false, nil +} - return sign, nil +// MuSig2Cleanup removes a session from memory to free up resources. +func (s *SingleSigner) MuSig2Cleanup(input.MuSig2SessionID) error { + return nil } diff --git a/lntest/mock/spendnotifier.go b/lntest/mock/spendnotifier.go index ced93f251e..04c861d913 100644 --- a/lntest/mock/spendnotifier.go +++ b/lntest/mock/spendnotifier.go @@ -4,7 +4,6 @@ import ( "sync" "github.com/btcsuite/btcd/wire" - "github.com/lightningnetwork/lnd/chainntnfs" ) diff --git a/lntest/mock/walletcontroller.go b/lntest/mock/walletcontroller.go index 959fb5382b..7dc79e2cc8 100644 --- a/lntest/mock/walletcontroller.go +++ b/lntest/mock/walletcontroller.go @@ -5,17 +5,16 @@ import ( "sync/atomic" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/hdkeychain" + "github.com/btcsuite/btcd/btcutil/psbt" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" - "github.com/btcsuite/btcutil/hdkeychain" - "github.com/btcsuite/btcutil/psbt" "github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/wallet/txauthor" "github.com/btcsuite/btcwallet/wtxmgr" - "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet/chainfee" ) @@ -53,15 +52,25 @@ func (w *WalletController) FetchInputInfo( return utxo, nil } +// ScriptForOutput returns the address, witness program and redeem script for a +// given UTXO. An error is returned if the UTXO does not belong to our wallet or +// it is not a managed pubKey address. +func (w *WalletController) ScriptForOutput(*wire.TxOut) ( + waddrmgr.ManagedPubKeyAddress, []byte, []byte, error) { + + return nil, nil, nil, nil +} + // ConfirmedBalance currently returns dummy values. -func (w *WalletController) ConfirmedBalance(confs int32, - _ string) (btcutil.Amount, error) { +func (w *WalletController) ConfirmedBalance(int32, string) (btcutil.Amount, + error) { + return 0, nil } // NewAddress is called to get new addresses for delivery, change etc. -func (w *WalletController) NewAddress(addrType lnwallet.AddressType, - change bool, _ string) (btcutil.Address, error) { +func (w *WalletController) NewAddress(lnwallet.AddressType, bool, + string) (btcutil.Address, error) { addr, _ := btcutil.NewAddressPubKey( w.RootKey.PubKey().SerializeCompressed(), &chaincfg.MainNetParams, @@ -70,19 +79,28 @@ func (w *WalletController) NewAddress(addrType lnwallet.AddressType, } // LastUnusedAddress currently returns dummy values. -func (w *WalletController) LastUnusedAddress(addrType lnwallet.AddressType, - _ string) (btcutil.Address, error) { +func (w *WalletController) LastUnusedAddress(lnwallet.AddressType, + string) (btcutil.Address, error) { + return nil, nil } // IsOurAddress currently returns a dummy value. -func (w *WalletController) IsOurAddress(a btcutil.Address) bool { +func (w *WalletController) IsOurAddress(btcutil.Address) bool { return false } +// AddressInfo currently returns a dummy value. +func (w *WalletController) AddressInfo( + btcutil.Address) (waddrmgr.ManagedAddress, error) { + + return nil, nil +} + // ListAccounts currently returns a dummy value. -func (w *WalletController) ListAccounts(_ string, - _ *waddrmgr.KeyScope) ([]*waddrmgr.AccountProperties, error) { +func (w *WalletController) ListAccounts(string, + *waddrmgr.KeyScope) ([]*waddrmgr.AccountProperties, error) { + return nil, nil } @@ -90,33 +108,35 @@ func (w *WalletController) ListAccounts(_ string, func (w *WalletController) ImportAccount(string, *hdkeychain.ExtendedKey, uint32, *waddrmgr.AddressType, bool) (*waddrmgr.AccountProperties, []btcutil.Address, []btcutil.Address, error) { + return nil, nil, nil, nil } // ImportPublicKey currently returns a dummy value. func (w *WalletController) ImportPublicKey(*btcec.PublicKey, waddrmgr.AddressType) error { + return nil } // SendOutputs currently returns dummy values. -func (w *WalletController) SendOutputs(outputs []*wire.TxOut, - _ chainfee.SatPerKWeight, _ int32, _ string) (*wire.MsgTx, error) { +func (w *WalletController) SendOutputs([]*wire.TxOut, + chainfee.SatPerKWeight, int32, string) (*wire.MsgTx, error) { return nil, nil } // CreateSimpleTx currently returns dummy values. -func (w *WalletController) CreateSimpleTx(outputs []*wire.TxOut, - _ chainfee.SatPerKWeight, _ int32, _ bool) (*txauthor.AuthoredTx, error) { +func (w *WalletController) CreateSimpleTx([]*wire.TxOut, + chainfee.SatPerKWeight, int32, bool) (*txauthor.AuthoredTx, error) { return nil, nil } // ListUnspentWitness is called by the wallet when doing coin selection. We just // need one unspent for the funding transaction. -func (w *WalletController) ListUnspentWitness(minConfs, - maxConfs int32, _ string) ([]*lnwallet.Utxo, error) { +func (w *WalletController) ListUnspentWitness(int32, int32, + string) ([]*lnwallet.Utxo, error) { // If the mock already has a list of utxos, return it. if w.Utxos != nil { @@ -140,8 +160,8 @@ func (w *WalletController) ListUnspentWitness(minConfs, } // ListTransactionDetails currently returns dummy values. -func (w *WalletController) ListTransactionDetails(_, - _ int32, _ string) ([]*lnwallet.TransactionDetail, error) { +func (w *WalletController) ListTransactionDetails(int32, int32, + string) ([]*lnwallet.TransactionDetail, error) { return nil, nil } @@ -169,12 +189,17 @@ func (w *WalletController) ListLeasedOutputs() ([]*wtxmgr.LockedOutput, error) { } // FundPsbt currently does nothing. -func (w *WalletController) FundPsbt(_ *psbt.Packet, _ int32, - _ chainfee.SatPerKWeight, _ string) (int32, error) { +func (w *WalletController) FundPsbt(*psbt.Packet, int32, chainfee.SatPerKWeight, + string) (int32, error) { return 0, nil } +// SignPsbt currently does nothing. +func (w *WalletController) SignPsbt(*psbt.Packet) error { + return nil +} + // FinalizePsbt currently does nothing. func (w *WalletController) FinalizePsbt(_ *psbt.Packet, _ string) error { return nil @@ -187,8 +212,8 @@ func (w *WalletController) PublishTransaction(tx *wire.MsgTx, _ string) error { } // LabelTransaction currently does nothing. -func (w *WalletController) LabelTransaction(_ chainhash.Hash, _ string, - _ bool) error { +func (w *WalletController) LabelTransaction(chainhash.Hash, string, + bool) error { return nil } @@ -219,3 +244,11 @@ func (w *WalletController) Start() error { func (w *WalletController) Stop() error { return nil } + +func (w *WalletController) FetchTx(chainhash.Hash) (*wire.MsgTx, error) { + return nil, nil +} + +func (w *WalletController) RemoveDescendants(*wire.MsgTx) error { + return nil +} diff --git a/lntest/neutrino.go b/lntest/neutrino.go index 6c8f7098a8..64e8803720 100644 --- a/lntest/neutrino.go +++ b/lntest/neutrino.go @@ -42,6 +42,12 @@ func (b NeutrinoBackendConfig) DisconnectMiner() error { return fmt.Errorf("unimplemented") } +// Credentials returns the rpc username, password and host for the backend. +// For neutrino, we return an error because there is no rpc client available. +func (b NeutrinoBackendConfig) Credentials() (string, string, string, error) { + return "", "", "", fmt.Errorf("unimplemented") +} + // Name returns the name of the backend type. func (b NeutrinoBackendConfig) Name() string { return NeutrinoBackendName diff --git a/lntest/test_common.go b/lntest/test_common.go new file mode 100644 index 0000000000..3259bf1cb4 --- /dev/null +++ b/lntest/test_common.go @@ -0,0 +1,187 @@ +package lntest + +import ( + "errors" + "flag" + "fmt" + "io" + "net" + "os" + "sync/atomic" + + "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/lnrpc" +) + +const ( + // defaultNodePort is the start of the range for listening ports of + // harness nodes. Ports are monotonically increasing starting from this + // number and are determined by the results of nextAvailablePort(). + defaultNodePort = 5555 + + // ListenerFormat is the format string that is used to generate local + // listener addresses. + ListenerFormat = "127.0.0.1:%d" + + // NeutrinoBackendName is the name of the neutrino backend. + NeutrinoBackendName = "neutrino" +) + +type DatabaseBackend int + +const ( + BackendBbolt DatabaseBackend = iota + BackendEtcd + BackendPostgres +) + +var ( + // lastPort is the last port determined to be free for use by a new + // node. It should be used atomically. + lastPort uint32 = defaultNodePort + + // logOutput is a flag that can be set to append the output from the + // seed nodes to log files. + logOutput = flag.Bool("logoutput", false, + "log output from node n to file output-n.log") + + // logSubDir is the default directory where the logs are written to if + // logOutput is true. + logSubDir = flag.String("logdir", ".", "default dir to write logs to") + + // goroutineDump is a flag that can be set to dump the active + // goroutines of test nodes on failure. + goroutineDump = flag.Bool("goroutinedump", false, + "write goroutine dump from node n to file pprof-n.log") + + // btcdExecutable is the full path to the btcd binary. + btcdExecutable = flag.String( + "btcdexec", "", "full path to btcd binary", + ) +) + +// NextAvailablePort returns the first port that is available for listening by +// a new node. It panics if no port is found and the maximum available TCP port +// is reached. +func NextAvailablePort() int { + port := atomic.AddUint32(&lastPort, 1) + for port < 65535 { + // If there are no errors while attempting to listen on this + // port, close the socket and return it as available. While it + // could be the case that some other process picks up this port + // between the time the socket is closed and it's reopened in + // the harness node, in practice in CI servers this seems much + // less likely than simply some other process already being + // bound at the start of the tests. + addr := fmt.Sprintf(ListenerFormat, port) + l, err := net.Listen("tcp4", addr) + if err == nil { + err := l.Close() + if err == nil { + return int(port) + } + } + port = atomic.AddUint32(&lastPort, 1) + } + + // No ports available? Must be a mistake. + panic("no ports available for listening") +} + +// ApplyPortOffset adds the given offset to the lastPort variable, making it +// possible to run the tests in parallel without colliding on the same ports. +func ApplyPortOffset(offset uint32) { + _ = atomic.AddUint32(&lastPort, offset) +} + +// GetLogDir returns the passed --logdir flag or the default value if it wasn't +// set. +func GetLogDir() string { + if logSubDir != nil && *logSubDir != "" { + return *logSubDir + } + return "." +} + +// GetBtcdBinary returns the full path to the binary of the custom built btcd +// executable or an empty string if none is set. +func GetBtcdBinary() string { + if btcdExecutable != nil { + return *btcdExecutable + } + + return "" +} + +// GenerateBtcdListenerAddresses is a function that returns two listener +// addresses with unique ports and should be used to overwrite rpctest's +// default generator which is prone to use colliding ports. +func GenerateBtcdListenerAddresses() (string, string) { + return fmt.Sprintf(ListenerFormat, NextAvailablePort()), + fmt.Sprintf(ListenerFormat, NextAvailablePort()) +} + +// MakeOutpoint returns the outpoint of the channel's funding transaction. +func MakeOutpoint(chanPoint *lnrpc.ChannelPoint) (wire.OutPoint, error) { + fundingTxID, err := lnrpc.GetChanPointFundingTxid(chanPoint) + if err != nil { + return wire.OutPoint{}, err + } + + return wire.OutPoint{ + Hash: *fundingTxID, + Index: chanPoint.OutputIndex, + }, nil +} + +// CheckChannelPolicy checks that the policy matches the expected one. +func CheckChannelPolicy(policy, expectedPolicy *lnrpc.RoutingPolicy) error { + if policy.FeeBaseMsat != expectedPolicy.FeeBaseMsat { + return fmt.Errorf("expected base fee %v, got %v", + expectedPolicy.FeeBaseMsat, policy.FeeBaseMsat) + } + if policy.FeeRateMilliMsat != expectedPolicy.FeeRateMilliMsat { + return fmt.Errorf("expected fee rate %v, got %v", + expectedPolicy.FeeRateMilliMsat, + policy.FeeRateMilliMsat) + } + if policy.TimeLockDelta != expectedPolicy.TimeLockDelta { + return fmt.Errorf("expected time lock delta %v, got %v", + expectedPolicy.TimeLockDelta, + policy.TimeLockDelta) + } + if policy.MinHtlc != expectedPolicy.MinHtlc { + return fmt.Errorf("expected min htlc %v, got %v", + expectedPolicy.MinHtlc, policy.MinHtlc) + } + if policy.MaxHtlcMsat != expectedPolicy.MaxHtlcMsat { + return fmt.Errorf("expected max htlc %v, got %v", + expectedPolicy.MaxHtlcMsat, policy.MaxHtlcMsat) + } + if policy.Disabled != expectedPolicy.Disabled { + return errors.New("edge should be disabled but isn't") + } + + return nil +} + +// CopyFile copies the file src to dest. +func CopyFile(dest, src string) error { + s, err := os.Open(src) + if err != nil { + return err + } + defer s.Close() + + d, err := os.Create(dest) + if err != nil { + return err + } + + if _, err := io.Copy(d, s); err != nil { + d.Close() + return err + } + + return d.Close() +} diff --git a/lnwallet/btcwallet/blockchain.go b/lnwallet/btcwallet/blockchain.go index 1373c06024..5cb0886f09 100644 --- a/lnwallet/btcwallet/blockchain.go +++ b/lnwallet/btcwallet/blockchain.go @@ -5,9 +5,9 @@ import ( "errors" "fmt" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/btcsuite/btcwallet/chain" "github.com/lightninglabs/neutrino" "github.com/lightninglabs/neutrino/headerfs" diff --git a/lnwallet/btcwallet/btcwallet.go b/lnwallet/btcwallet/btcwallet.go index 86793d1824..f972ab616e 100644 --- a/lnwallet/btcwallet/btcwallet.go +++ b/lnwallet/btcwallet/btcwallet.go @@ -9,23 +9,22 @@ import ( "sync" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/hdkeychain" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" - "github.com/btcsuite/btcutil/hdkeychain" - "github.com/btcsuite/btcutil/psbt" "github.com/btcsuite/btcwallet/chain" "github.com/btcsuite/btcwallet/waddrmgr" - "github.com/btcsuite/btcwallet/wallet" base "github.com/btcsuite/btcwallet/wallet" "github.com/btcsuite/btcwallet/wallet/txauthor" "github.com/btcsuite/btcwallet/wallet/txrules" "github.com/btcsuite/btcwallet/walletdb" "github.com/btcsuite/btcwallet/wtxmgr" "github.com/lightningnetwork/lnd/blockcache" + "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lnwallet" @@ -58,6 +57,10 @@ var ( // stored within the top-level waleltdb buckets of btcwallet. waddrmgrNamespaceKey = []byte("waddrmgr") + // wtxmgrNamespaceKey is the namespace key that the wtxmgr state is + // stored within the top-level waleltdb buckets of btcwallet. + wtxmgrNamespaceKey = []byte("wtxmgr") + // lightningAddrSchema is the scope addr schema for all keys that we // derive. We'll treat them all as p2wkh addresses, as atm we must // specify a particular type. @@ -96,6 +99,9 @@ type BtcWallet struct { chainKeyScope waddrmgr.KeyScope blockCache *blockcache.BlockCache + + musig2Sessions map[input.MuSig2SessionID]*muSig2State + musig2SessionsMtx sync.Mutex } // A compile time check to ensure that BtcWallet implements the @@ -158,13 +164,14 @@ func New(cfg Config, blockCache *blockcache.BlockCache) (*BtcWallet, error) { } return &BtcWallet{ - cfg: &cfg, - wallet: wallet, - db: wallet.Database(), - chain: cfg.ChainSource, - netParams: cfg.NetParams, - chainKeyScope: chainKeyScope, - blockCache: blockCache, + cfg: &cfg, + wallet: wallet, + db: wallet.Database(), + chain: cfg.ChainSource, + netParams: cfg.NetParams, + chainKeyScope: chainKeyScope, + blockCache: blockCache, + musig2Sessions: make(map[input.MuSig2SessionID]*muSig2State), }, nil } @@ -201,7 +208,7 @@ func LoaderWithExternalWalletDB(db kvdb.Backend) LoaderOption { // NewWalletLoader constructs a wallet loader. func NewWalletLoader(chainParams *chaincfg.Params, recoveryWindow uint32, - opts ...LoaderOption) (*wallet.Loader, error) { + opts ...LoaderOption) (*base.Loader, error) { cfg := &loaderCfg{} @@ -288,15 +295,66 @@ func (b *BtcWallet) InternalWallet() *base.Wallet { // // This is a part of the WalletController interface. func (b *BtcWallet) Start() error { + // Is the wallet (according to its database) currently watch-only + // already? If it is, we won't need to convert it later. + walletIsWatchOnly := b.wallet.Manager.WatchOnly() + + // If the wallet is watch-only, but we don't expect it to be, then we + // are in an unexpected state and cannot continue. + if walletIsWatchOnly && !b.cfg.WatchOnly { + return fmt.Errorf("wallet is watch-only but we expect it " + + "not to be; check if remote signing was disabled by " + + "accident") + } + // We'll start by unlocking the wallet and ensuring that the KeyScope: // (1017, 1) exists within the internal waddrmgr. We'll need this in // order to properly generate the keys required for signing various // contracts. If this is a watch-only wallet, we don't have any private // keys and therefore unlocking is not necessary. - if !b.cfg.WatchOnly { + if !walletIsWatchOnly { if err := b.wallet.Unlock(b.cfg.PrivatePass, nil); err != nil { return err } + + // If the wallet isn't about to be converted, we need to inform + // the user that this wallet still contains all private key + // material and that they need to migrate the existing wallet. + if b.cfg.WatchOnly && !b.cfg.MigrateWatchOnly { + log.Warnf("Wallet is expected to be in watch-only " + + "mode but hasn't been migrated to watch-only " + + "yet, it still contains private keys; " + + "consider turning on the watch-only wallet " + + "migration in remote signing mode") + } + } + + // Because we might add new "default" key scopes over time, they are + // created correctly for new wallets. Existing wallets don't + // automatically add them, we need to do that manually now. + for _, scope := range waddrmgr.DefaultKeyScopes { + _, err := b.wallet.Manager.FetchScopedKeyManager(scope) + if waddrmgr.IsError(err, waddrmgr.ErrScopeNotFound) { + // The default scope wasn't found, that probably means + // it was added recently and older wallets don't know it + // yet. Let's add it now. + addrSchema := waddrmgr.ScopeAddrMap[scope] + err := walletdb.Update( + b.db, func(tx walletdb.ReadWriteTx) error { + addrmgrNs := tx.ReadWriteBucket( + waddrmgrNamespaceKey, + ) + + _, err := b.wallet.Manager.NewScopedKeyManager( + addrmgrNs, scope, addrSchema, + ) + return err + }, + ) + if err != nil { + return err + } + } } scope, err := b.wallet.Manager.FetchScopedKeyManager(b.chainKeyScope) @@ -324,17 +382,13 @@ func (b *BtcWallet) Start() error { err = walletdb.Update(b.db, func(tx walletdb.ReadWriteTx) error { addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey) - for _, keyFam := range keychain.VersionZeroKeyFamilies { - // If this is the multi-sig key family, then we can - // return early as this is the default account that's - // created. - if keyFam == keychain.KeyFamilyMultiSig { - continue - } - + // Generate all accounts that we could ever need. This includes + // all lnd key families as well as some key families used in + // external liquidity tools. + for keyFam := uint32(1); keyFam <= 255; keyFam++ { // Otherwise, we'll check if the account already exists, // if so, we can once again bail early. - _, err := scope.AccountName(addrmgrNs, uint32(keyFam)) + _, err := scope.AccountName(addrmgrNs, keyFam) if err == nil { continue } @@ -342,7 +396,24 @@ func (b *BtcWallet) Start() error { // If we reach this point, then the account hasn't yet // been created, so we'll need to create it before we // can proceed. - err = scope.NewRawAccount(addrmgrNs, uint32(keyFam)) + err = scope.NewRawAccount(addrmgrNs, keyFam) + if err != nil { + return err + } + } + + // If this is the first startup with remote signing and wallet + // migration turned on and the wallet wasn't previously + // migrated, we can do that now that we made sure all accounts + // that we need were derived correctly. + if !walletIsWatchOnly && b.cfg.WatchOnly && + b.cfg.MigrateWatchOnly { + + log.Infof("Migrating wallet to watch-only mode, " + + "purging all private key material") + + ns := tx.ReadWriteBucket(waddrmgrNamespaceKey) + err = b.wallet.Manager.ConvertToWatchingOnly(ns) if err != nil { return err } @@ -423,6 +494,8 @@ func (b *BtcWallet) keyScopeForAccountAddr(accountName string, addrKeyScope = waddrmgr.KeyScopeBIP0084 case lnwallet.NestedWitnessPubKey: addrKeyScope = waddrmgr.KeyScopeBIP0049Plus + case lnwallet.TaprootPubkey: + addrKeyScope = waddrmgr.KeyScopeBIP0086 default: return waddrmgr.KeyScope{}, 0, fmt.Errorf("unknown address type") @@ -505,6 +578,16 @@ func (b *BtcWallet) IsOurAddress(a btcutil.Address) bool { return result && (err == nil) } +// AddressInfo returns the information about an address, if it's known to this +// wallet. +// +// NOTE: This is a part of the WalletController interface. +func (b *BtcWallet) AddressInfo(a btcutil.Address) (waddrmgr.ManagedAddress, + error) { + + return b.wallet.AddressInfo(a) +} + // ListAccounts retrieves all accounts belonging to the wallet by default. A // name and key scope filter can be provided to filter through all of the wallet // accounts and return only those matching. @@ -527,7 +610,7 @@ func (b *BtcWallet) ListAccounts(name string, // Only the name filter was provided. case name != "" && keyScope == nil: // If the name corresponds to the default or imported accounts, - // we'll return them for both of our supported key scopes. + // we'll return them for all our supported key scopes. if name == lnwallet.DefaultAccountName || name == waddrmgr.ImportedAddrAccountName { @@ -546,6 +629,14 @@ func (b *BtcWallet) ListAccounts(name string, return nil, err } res = append(res, a2) + + a3, err := b.wallet.AccountPropertiesByName( + waddrmgr.KeyScopeBIP0086, name, + ) + if err != nil { + return nil, err + } + res = append(res, a3) break } @@ -594,6 +685,15 @@ func (b *BtcWallet) ListAccounts(name string, res = append(res, &account.AccountProperties) } + accounts, err = b.wallet.Accounts(waddrmgr.KeyScopeBIP0086) + if err != nil { + return nil, err + } + for _, account := range accounts.Accounts { + account := account + res = append(res, &account.AccountProperties) + } + accounts, err = b.wallet.Accounts(waddrmgr.KeyScope{ Purpose: keychain.BIP0043Purpose, Coin: b.cfg.CoinType, @@ -683,6 +783,26 @@ func (b *BtcWallet) ImportPublicKey(pubKey *btcec.PublicKey, return b.wallet.ImportPublicKey(pubKey, addrType) } +// ImportTaprootScript imports a user-provided taproot script into the address +// manager. The imported script will act as a pay-to-taproot address. +func (b *BtcWallet) ImportTaprootScript(scope waddrmgr.KeyScope, + tapscript *waddrmgr.Tapscript) (waddrmgr.ManagedAddress, error) { + + // We want to be able to import script addresses into a watch-only + // wallet, which is only possible if we don't encrypt the script with + // the private key encryption key. By specifying the script as being + // "not secret", we can also decrypt the script in a watch-only wallet. + const isSecretScript = false + + // Currently, only v1 (Taproot) scripts are supported. We don't even + // know what a v2 witness version would look like at this point. + const witnessVersionTaproot byte = 1 + + return b.wallet.ImportTaprootScript( + scope, tapscript, nil, witnessVersionTaproot, isSecretScript, + ) +} + // SendOutputs funds, signs, and broadcasts a Bitcoin transaction paying out to // the specified outputs. In the case the wallet has insufficient funds, or the // outputs are non-standard, a non-nil error will be returned. @@ -812,7 +932,17 @@ func (b *BtcWallet) LeaseOutput(id wtxmgr.LockID, op wire.OutPoint, // ListLeasedOutputs returns a list of all currently locked outputs. func (b *BtcWallet) ListLeasedOutputs() ([]*wtxmgr.LockedOutput, error) { - return b.wallet.ListLeasedOutputs() + leasedOutputs, err := b.wallet.ListLeasedOutputs() + if err != nil { + return nil, err + } + + lockedOutputs := make([]*wtxmgr.LockedOutput, len(leasedOutputs)) + for i, output := range leasedOutputs { + lockedOutputs[i] = output.LockedOutput + } + + return lockedOutputs, nil } // ReleaseOutput unlocks an output, allowing it to be available for coin @@ -864,10 +994,13 @@ func (b *BtcWallet) ListUnspentWitness(minConfs, maxConfs int32, // wallet are nested p2pkh. We can't check the redeem script because // the btcwallet service does not include it. addressType = lnwallet.NestedWitnessPubKey + } else if txscript.IsPayToTaproot(pkScript) { + addressType = lnwallet.TaprootPubkey } if addressType == lnwallet.WitnessPubKey || - addressType == lnwallet.NestedWitnessPubKey { + addressType == lnwallet.NestedWitnessPubKey || + addressType == lnwallet.TaprootPubkey { txid, err := chainhash.NewHashFromStr(output.TxID) if err != nil { @@ -906,11 +1039,9 @@ func (b *BtcWallet) ListUnspentWitness(minConfs, maxConfs int32, // network (either in the mempool or chain) no error will be returned. func (b *BtcWallet) PublishTransaction(tx *wire.MsgTx, label string) error { if err := b.wallet.PublishTransaction(tx, label); err != nil { - // If we failed to publish the transaction, check whether we // got an error of known type. switch err.(type) { - // If the wallet reports a double spend, convert it to our // internal ErrDoubleSpend and return. case *base.ErrDoubleSpend: @@ -977,18 +1108,35 @@ func minedTransactionsToDetails( return nil, err } - var destAddresses []btcutil.Address - for _, txOut := range wireTx.TxOut { - _, outAddresses, _, err := txscript.ExtractPkScriptAddrs( + // isOurAddress is a map containing the output indices + // controlled by the wallet. + // Note: We make use of the information in `MyOutputs` provided + // by the `wallet.TransactionSummary` structure that holds + // information only if the output is controlled by the wallet. + isOurAddress := make(map[int]bool, len(tx.MyOutputs)) + for _, o := range tx.MyOutputs { + isOurAddress[int(o.Index)] = true + } + + var outputDetails []lnwallet.OutputDetail + for i, txOut := range wireTx.TxOut { + var addresses []btcutil.Address + sc, outAddresses, _, err := txscript.ExtractPkScriptAddrs( txOut.PkScript, chainParams, ) - if err != nil { - // Skip any unsupported addresses to prevent - // other transactions from not being returned. - continue + if err == nil { + // Add supported addresses. + addresses = outAddresses } - destAddresses = append(destAddresses, outAddresses...) + outputDetails = append(outputDetails, lnwallet.OutputDetail{ + OutputType: sc, + Addresses: addresses, + PkScript: txOut.PkScript, + OutputIndex: i, + Value: btcutil.Amount(txOut.Value), + IsOurAddress: isOurAddress[i], + }) } txDetail := &lnwallet.TransactionDetail{ @@ -998,7 +1146,7 @@ func minedTransactionsToDetails( BlockHeight: block.Height, Timestamp: block.Timestamp, TotalFees: int64(tx.Fee), - DestAddresses: destAddresses, + OutputDetails: outputDetails, RawTx: tx.Transaction, Label: tx.Label, } @@ -1029,24 +1177,42 @@ func unminedTransactionsToDetail( return nil, err } - var destAddresses []btcutil.Address - for _, txOut := range wireTx.TxOut { - _, outAddresses, _, err := - txscript.ExtractPkScriptAddrs(txOut.PkScript, chainParams) - if err != nil { - // Skip any unsupported addresses to prevent other - // transactions from not being returned. - continue + // isOurAddress is a map containing the output indices controlled by + // the wallet. + // Note: We make use of the information in `MyOutputs` provided + // by the `wallet.TransactionSummary` structure that holds information + // only if the output is controlled by the wallet. + isOurAddress := make(map[int]bool, len(summary.MyOutputs)) + for _, o := range summary.MyOutputs { + isOurAddress[int(o.Index)] = true + } + + var outputDetails []lnwallet.OutputDetail + for i, txOut := range wireTx.TxOut { + var addresses []btcutil.Address + sc, outAddresses, _, err := txscript.ExtractPkScriptAddrs( + txOut.PkScript, chainParams, + ) + if err == nil { + // Add supported addresses. + addresses = outAddresses } - destAddresses = append(destAddresses, outAddresses...) + outputDetails = append(outputDetails, lnwallet.OutputDetail{ + OutputType: sc, + Addresses: addresses, + PkScript: txOut.PkScript, + OutputIndex: i, + Value: btcutil.Amount(txOut.Value), + IsOurAddress: isOurAddress[i], + }) } txDetail := &lnwallet.TransactionDetail{ Hash: *summary.Hash, TotalFees: int64(summary.Fee), Timestamp: summary.Timestamp, - DestAddresses: destAddresses, + OutputDetails: outputDetails, RawTx: summary.Transaction, Label: summary.Label, } @@ -1114,104 +1280,6 @@ func (b *BtcWallet) ListTransactionDetails(startHeight, endHeight int32, return txDetails, nil } -// FundPsbt creates a fully populated PSBT packet that contains enough inputs to -// fund the outputs specified in the passed in packet with the specified fee -// rate. If there is change left, a change output from the internal wallet is -// added and the index of the change output is returned. Otherwise no additional -// output is created and the index -1 is returned. -// -// NOTE: If the packet doesn't contain any inputs, coin selection is performed -// automatically. The account parameter must be non-empty as it determines which -// set of coins are eligible for coin selection. If the packet does contain any -// inputs, it is assumed that full coin selection happened externally and no -// additional inputs are added. If the specified inputs aren't enough to fund -// the outputs with the given fee rate, an error is returned. No lock lease is -// acquired for any of the selected/validated inputs. It is in the caller's -// responsibility to lock the inputs before handing them out. -// -// This is a part of the WalletController interface. -func (b *BtcWallet) FundPsbt(packet *psbt.Packet, minConfs int32, - feeRate chainfee.SatPerKWeight, accountName string) (int32, error) { - - // The fee rate is passed in using units of sat/kw, so we'll convert - // this to sat/KB as the CreateSimpleTx method requires this unit. - feeSatPerKB := btcutil.Amount(feeRate.FeePerKVByte()) - - var ( - keyScope *waddrmgr.KeyScope - accountNum uint32 - ) - switch accountName { - // If the default/imported account name was specified, we'll provide a - // nil key scope to FundPsbt, allowing it to select inputs from both key - // scopes (NP2WKH, P2WKH). - case lnwallet.DefaultAccountName: - accountNum = defaultAccount - - case waddrmgr.ImportedAddrAccountName: - accountNum = importedAccount - - // Otherwise, map the account name to its key scope and internal account - // number to only select inputs from said account. - default: - scope, account, err := b.wallet.LookupAccount(accountName) - if err != nil { - return 0, err - } - keyScope = &scope - accountNum = account - } - - // Let the wallet handle coin selection and/or fee estimation based on - // the partial TX information in the packet. - return b.wallet.FundPsbt( - packet, keyScope, minConfs, accountNum, feeSatPerKB, - b.cfg.CoinSelectionStrategy, - ) -} - -// FinalizePsbt expects a partial transaction with all inputs and outputs fully -// declared and tries to sign all inputs that belong to the specified account. -// Lnd must be the last signer of the transaction. That means, if there are any -// unsigned non-witness inputs or inputs without UTXO information attached or -// inputs without witness data that do not belong to lnd's wallet, this method -// will fail. If no error is returned, the PSBT is ready to be extracted and the -// final TX within to be broadcast. -// -// NOTE: This method does NOT publish the transaction after it's been -// finalized successfully. -// -// This is a part of the WalletController interface. -func (b *BtcWallet) FinalizePsbt(packet *psbt.Packet, accountName string) error { - var ( - keyScope *waddrmgr.KeyScope - accountNum uint32 - ) - switch accountName { - // If the default/imported account name was specified, we'll provide a - // nil key scope to FundPsbt, allowing it to sign inputs from both key - // scopes (NP2WKH, P2WKH). - case lnwallet.DefaultAccountName: - accountNum = defaultAccount - - case waddrmgr.ImportedAddrAccountName: - accountNum = importedAccount - - // Otherwise, map the account name to its key scope and internal account - // number to determine if the inputs belonging to this account should be - // signed. - default: - scope, account, err := b.wallet.LookupAccount(accountName) - if err != nil { - return err - } - keyScope = &scope - accountNum = account - } - - return b.wallet.FinalizePsbt(keyScope, accountNum, packet) -} - // txSubscriptionClient encapsulates the transaction notification client from // the base wallet. Notifications received from the client will be proxied over // two distinct channels. @@ -1448,6 +1516,7 @@ func (b *BtcWallet) GetRecoveryInfo() (bool, float64, error) { // rollback happens. In that case, we will return zero progress here. if syncState.Height < birthdayBlock.Height || bestHeight < birthdayBlock.Height { + return isRecoveryMode, progress, nil } @@ -1465,3 +1534,46 @@ func (b *BtcWallet) GetRecoveryInfo() (bool, float64, error) { return isRecoveryMode, progress, nil } + +// FetchTx attempts to fetch a transaction in the wallet's database identified +// by the passed transaction hash. If the transaction can't be found, then a +// nil pointer is returned. +func (b *BtcWallet) FetchTx(txHash chainhash.Hash) (*wire.MsgTx, error) { + var targetTx *wtxmgr.TxDetails + err := walletdb.View(b.db, func(tx walletdb.ReadTx) error { + wtxmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) + txDetails, err := b.wallet.TxStore.TxDetails(wtxmgrNs, &txHash) + if err != nil { + return err + } + + targetTx = txDetails + + return nil + }) + if err != nil { + return nil, err + } + + if targetTx == nil { + return nil, nil + } + + return &targetTx.TxRecord.MsgTx, nil +} + +// RemoveDescendants attempts to remove any transaction from the wallet's tx +// store (that may be unconfirmed) that spends outputs created by the passed +// transaction. This remove propagates recursively down the chain of descendent +// transactions. +func (b *BtcWallet) RemoveDescendants(tx *wire.MsgTx) error { + txRecord, err := wtxmgr.NewTxRecordFromMsgTx(tx, time.Now()) + if err != nil { + return err + } + + return walletdb.Update(b.db, func(tx walletdb.ReadWriteTx) error { + wtxmgrNs := tx.ReadWriteBucket(wtxmgrNamespaceKey) + return b.wallet.TxStore.RemoveUnminedTx(wtxmgrNs, txRecord) + }) +} diff --git a/lnwallet/btcwallet/config.go b/lnwallet/btcwallet/config.go index f97f2943fd..45abdafceb 100644 --- a/lnwallet/btcwallet/config.go +++ b/lnwallet/btcwallet/config.go @@ -6,7 +6,6 @@ import ( "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcwallet/chain" "github.com/btcsuite/btcwallet/wallet" ) @@ -76,6 +75,13 @@ type Config struct { // WatchOnly indicates that the wallet was initialized with public key // material only and does not contain any private keys. WatchOnly bool + + // MigrateWatchOnly indicates that if a wallet with private key material + // already exists, it should be attempted to be converted into a + // watch-only wallet on first startup. This flag has no effect if no + // wallet exists and a watch-only one is created directly, or, if the + // wallet was previously converted to a watch-only already. + MigrateWatchOnly bool } // NetworkDir returns the directory name of a network directory to hold wallet diff --git a/lnwallet/btcwallet/log.go b/lnwallet/btcwallet/log.go new file mode 100644 index 0000000000..878db08e64 --- /dev/null +++ b/lnwallet/btcwallet/log.go @@ -0,0 +1,32 @@ +package btcwallet + +import ( + "github.com/btcsuite/btclog" + "github.com/lightningnetwork/lnd/build" +) + +// Subsystem defines the logging code for this subsystem. +const Subsystem = "BTWL" + +// log is a logger that is initialized with no output filters. This means the +// package will not perform any logging by default until the caller requests +// it. +var log btclog.Logger + +// The default amount of logging is none. +func init() { + UseLogger(build.NewSubLogger(Subsystem, nil)) +} + +// DisableLog disables all library log output. Logging output is disabled by +// default until UseLogger is called. +func DisableLog() { + UseLogger(btclog.Disabled) +} + +// UseLogger uses a specified Logger to output package logging info. This +// should be used in preference to SetLogWriter if the caller is also using +// btclog. +func UseLogger(logger btclog.Logger) { + log = logger +} diff --git a/lnwallet/btcwallet/psbt.go b/lnwallet/btcwallet/psbt.go new file mode 100644 index 0000000000..b621d63540 --- /dev/null +++ b/lnwallet/btcwallet/psbt.go @@ -0,0 +1,504 @@ +package btcwallet + +import ( + "bytes" + "crypto/sha256" + "fmt" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/psbt" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcwallet/waddrmgr" + "github.com/btcsuite/btcwallet/wallet" + "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/lnwallet" + "github.com/lightningnetwork/lnd/lnwallet/chainfee" +) + +var ( + // PsbtKeyTypeInputSignatureTweakSingle is a custom/proprietary PSBT key + // for an input that specifies what single tweak should be applied to + // the key before signing the input. The value 51 is leet speak for + // "si", short for "single". + PsbtKeyTypeInputSignatureTweakSingle = []byte{0x51} + + // PsbtKeyTypeInputSignatureTweakDouble is a custom/proprietary PSBT key + // for an input that specifies what double tweak should be applied to + // the key before signing the input. The value d0 is leet speak for + // "do", short for "double". + PsbtKeyTypeInputSignatureTweakDouble = []byte{0xd0} +) + +// FundPsbt creates a fully populated PSBT packet that contains enough inputs to +// fund the outputs specified in the passed in packet with the specified fee +// rate. If there is change left, a change output from the internal wallet is +// added and the index of the change output is returned. Otherwise no additional +// output is created and the index -1 is returned. +// +// NOTE: If the packet doesn't contain any inputs, coin selection is performed +// automatically. The account parameter must be non-empty as it determines which +// set of coins are eligible for coin selection. If the packet does contain any +// inputs, it is assumed that full coin selection happened externally and no +// additional inputs are added. If the specified inputs aren't enough to fund +// the outputs with the given fee rate, an error is returned. No lock lease is +// acquired for any of the selected/validated inputs. It is in the caller's +// responsibility to lock the inputs before handing them out. +// +// This is a part of the WalletController interface. +func (b *BtcWallet) FundPsbt(packet *psbt.Packet, minConfs int32, + feeRate chainfee.SatPerKWeight, accountName string) (int32, error) { + + // The fee rate is passed in using units of sat/kw, so we'll convert + // this to sat/KB as the CreateSimpleTx method requires this unit. + feeSatPerKB := btcutil.Amount(feeRate.FeePerKVByte()) + + var ( + keyScope *waddrmgr.KeyScope + accountNum uint32 + ) + switch accountName { + // If the default/imported account name was specified, we'll provide a + // nil key scope to FundPsbt, allowing it to select inputs from both key + // scopes (NP2WKH, P2WKH). + case lnwallet.DefaultAccountName: + accountNum = defaultAccount + + case waddrmgr.ImportedAddrAccountName: + accountNum = importedAccount + + // Otherwise, map the account name to its key scope and internal account + // number to only select inputs from said account. + default: + scope, account, err := b.wallet.LookupAccount(accountName) + if err != nil { + return 0, err + } + keyScope = &scope + accountNum = account + } + + // Let the wallet handle coin selection and/or fee estimation based on + // the partial TX information in the packet. + return b.wallet.FundPsbt( + packet, keyScope, minConfs, accountNum, feeSatPerKB, + b.cfg.CoinSelectionStrategy, + ) +} + +// SignPsbt expects a partial transaction with all inputs and outputs fully +// declared and tries to sign all unsigned inputs that have all required fields +// (UTXO information, BIP32 derivation information, witness or sig scripts) set. +// If no error is returned, the PSBT is ready to be given to the next signer or +// to be finalized if lnd was the last signer. +// +// NOTE: This method only signs inputs (and only those it can sign), it does not +// perform any other tasks (such as coin selection, UTXO locking or +// input/output/fee value validation, PSBT finalization). Any input that is +// incomplete will be skipped. +func (b *BtcWallet) SignPsbt(packet *psbt.Packet) error { + // Let's check that this is actually something we can and want to sign. + // We need at least one input and one output. + err := psbt.VerifyInputOutputLen(packet, true, true) + if err != nil { + return err + } + + // Go through each input that doesn't have final witness data attached + // to it already and try to sign it. If there is nothing more to sign or + // there are inputs that we don't know how to sign, we won't return any + // error. So it's possible we're not the final signer. + tx := packet.UnsignedTx + prevOutputFetcher := wallet.PsbtPrevOutputFetcher(packet) + sigHashes := txscript.NewTxSigHashes(tx, prevOutputFetcher) + for idx := range tx.TxIn { + in := &packet.Inputs[idx] + + // We can only sign if we have UTXO information available. Since + // we don't finalize, we just skip over any input that we know + // we can't do anything with. Since we only support signing + // witness inputs, we only look at the witness UTXO being set. + if in.WitnessUtxo == nil { + continue + } + + // Skip this input if it's got final witness data attached. + if len(in.FinalScriptWitness) > 0 { + continue + } + + // Skip this input if there is no BIP32 derivation info + // available. + if len(in.Bip32Derivation) == 0 { + continue + } + + // TODO(guggero): For multisig, we'll need to find out what key + // to use and there should be multiple derivation paths in the + // BIP32 derivation field. + + // Let's try and derive the key now. This method will decide if + // it's a BIP49/84 key for normal on-chain funds or a key of the + // custom purpose 1017 key scope. + derivationInfo := in.Bip32Derivation[0] + privKey, err := b.deriveKeyByBIP32Path(derivationInfo.Bip32Path) + if err != nil { + log.Warnf("SignPsbt: Skipping input %d, error "+ + "deriving signing key: %v", idx, err) + continue + } + + // We need to make sure we actually derived the key that was + // expected to be derived. + pubKeysEqual := bytes.Equal( + derivationInfo.PubKey, + privKey.PubKey().SerializeCompressed(), + ) + if !pubKeysEqual { + log.Warnf("SignPsbt: Skipping input %d, derived "+ + "public key %x does not match bip32 "+ + "derivation info public key %x", idx, + privKey.PubKey().SerializeCompressed(), + derivationInfo.PubKey) + continue + } + + // Do we need to tweak anything? Single or double tweaks are + // sent as custom/proprietary fields in the PSBT input section. + privKey = maybeTweakPrivKeyPsbt(in.Unknowns, privKey) + + // What kind of signature is expected from us and do we have all + // information we need? + signMethod, err := validateSigningMethod(in) + if err != nil { + return err + } + + switch signMethod { + // For p2wkh, np2wkh and p2wsh. + case input.WitnessV0SignMethod: + err = signSegWitV0(in, tx, sigHashes, idx, privKey) + + // For p2tr BIP0086 key spend only. + case input.TaprootKeySpendBIP0086SignMethod: + rootHash := make([]byte, 0) + err = signSegWitV1KeySpend( + in, tx, sigHashes, idx, privKey, rootHash, + ) + + // For p2tr with script commitment key spend path. + case input.TaprootKeySpendSignMethod: + rootHash := in.TaprootMerkleRoot + err = signSegWitV1KeySpend( + in, tx, sigHashes, idx, privKey, rootHash, + ) + + // For p2tr script spend path. + case input.TaprootScriptSpendSignMethod: + leafScript := in.TaprootLeafScript[0] + leaf := txscript.TapLeaf{ + LeafVersion: leafScript.LeafVersion, + Script: leafScript.Script, + } + err = signSegWitV1ScriptSpend( + in, tx, sigHashes, idx, privKey, leaf, + ) + + default: + err = fmt.Errorf("unsupported signing method for "+ + "PSBT signing: %v", signMethod) + } + if err != nil { + return err + } + } + + return nil +} + +// validateSigningMethod attempts to detect the signing method that is required +// to sign for the given PSBT input and makes sure all information is available +// to do so. +func validateSigningMethod(in *psbt.PInput) (input.SignMethod, error) { + script, err := txscript.ParsePkScript(in.WitnessUtxo.PkScript) + if err != nil { + return 0, fmt.Errorf("error detecting signing method, "+ + "couldn't parse pkScript: %v", err) + } + + switch script.Class() { + case txscript.WitnessV0PubKeyHashTy, txscript.ScriptHashTy, + txscript.WitnessV0ScriptHashTy: + + return input.WitnessV0SignMethod, nil + + case txscript.WitnessV1TaprootTy: + if len(in.TaprootBip32Derivation) == 0 { + return 0, fmt.Errorf("cannot sign for taproot input " + + "without taproot BIP0032 derivation info") + } + + // Currently, we only support creating one signature per input. + // + // TODO(guggero): Should we support signing multiple paths at + // the same time? What are the performance and security + // implications? + if len(in.TaprootBip32Derivation) > 1 { + return 0, fmt.Errorf("unsupported multiple taproot " + + "BIP0032 derivation info found, can only " + + "sign for one at a time") + } + + derivation := in.TaprootBip32Derivation[0] + switch { + // No leaf hashes means this is the internal key we're signing + // with, so it's a key spend. And no merkle root means this is + // a BIP0086 output we're signing for. + case len(derivation.LeafHashes) == 0 && + len(in.TaprootMerkleRoot) == 0: + + return input.TaprootKeySpendBIP0086SignMethod, nil + + // A non-empty merkle root means we committed to a taproot hash + // that we need to use in the tap tweak. + case len(derivation.LeafHashes) == 0: + // Getting here means the merkle root isn't empty, but + // is it exactly the length we need? + if len(in.TaprootMerkleRoot) != sha256.Size { + return 0, fmt.Errorf("invalid taproot merkle "+ + "root length, got %d expected %d", + len(in.TaprootMerkleRoot), sha256.Size) + } + + return input.TaprootKeySpendSignMethod, nil + + // Currently, we only support signing for one leaf at a time. + // + // TODO(guggero): Should we support signing multiple paths at + // the same time? What are the performance and security + // implications? + case len(derivation.LeafHashes) == 1: + // If we're supposed to be signing for a leaf hash, we + // also expect the leaf script that hashes to that hash + // in the appropriate field. + if len(in.TaprootLeafScript) != 1 { + return 0, fmt.Errorf("specified leaf hash in " + + "taproot BIP0032 derivation but " + + "missing taproot leaf script") + } + + leafScript := in.TaprootLeafScript[0] + leaf := txscript.TapLeaf{ + LeafVersion: leafScript.LeafVersion, + Script: leafScript.Script, + } + leafHash := leaf.TapHash() + if !bytes.Equal(leafHash[:], derivation.LeafHashes[0]) { + return 0, fmt.Errorf("specified leaf hash in" + + "taproot BIP0032 derivation but " + + "corresponding taproot leaf script " + + "was not found") + } + + return input.TaprootScriptSpendSignMethod, nil + + default: + return 0, fmt.Errorf("unsupported number of leaf " + + "hashes in taproot BIP0032 derivation info, " + + "can only sign for one at a time") + } + + default: + return 0, fmt.Errorf("unsupported script class for signing "+ + "PSBT: %v", script.Class()) + } +} + +// SignSegWitV0 attempts to generate a signature for a SegWit version 0 input +// and stores it in the PartialSigs (and FinalScriptSig for np2wkh addresses) +// field. +func signSegWitV0(in *psbt.PInput, tx *wire.MsgTx, + sigHashes *txscript.TxSigHashes, idx int, + privKey *btcec.PrivateKey) error { + + pubKeyBytes := privKey.PubKey().SerializeCompressed() + + // Extract the correct witness and/or legacy scripts now, depending on + // the type of input we sign. The txscript package has the peculiar + // requirement that the PkScript of a P2PKH must be given as the witness + // script in order for it to arrive at the correct sighash. That's why + // we call it subScript here instead of witness script. + subScript, scriptSig, err := prepareScriptsV0(in) + if err != nil { + // We derived the correct key so we _are_ expected to sign this. + // Not being able to sign at this point means there's something + // wrong. + return fmt.Errorf("error deriving script for input %d: %v", idx, + err) + } + + // We have everything we need for signing the input now. + sig, err := txscript.RawTxInWitnessSignature( + tx, sigHashes, idx, in.WitnessUtxo.Value, subScript, + in.SighashType, privKey, + ) + if err != nil { + return fmt.Errorf("error signing input %d: %v", idx, err) + } + in.FinalScriptSig = scriptSig + in.PartialSigs = append(in.PartialSigs, &psbt.PartialSig{ + PubKey: pubKeyBytes, + Signature: sig, + }) + + return nil +} + +// signSegWitV1KeySpend attempts to generate a signature for a SegWit version 1 +// (p2tr) input and stores it in the TaprootKeySpendSig field. +func signSegWitV1KeySpend(in *psbt.PInput, tx *wire.MsgTx, + sigHashes *txscript.TxSigHashes, idx int, privKey *btcec.PrivateKey, + tapscriptRootHash []byte) error { + + rawSig, err := txscript.RawTxInTaprootSignature( + tx, sigHashes, idx, in.WitnessUtxo.Value, + in.WitnessUtxo.PkScript, tapscriptRootHash, in.SighashType, + privKey, + ) + if err != nil { + return fmt.Errorf("error signing taproot input %d: %v", idx, + err) + } + + in.TaprootKeySpendSig = rawSig + + return nil +} + +// signSegWitV1ScriptSpend attempts to generate a signature for a SegWit version +// 1 (p2tr) input and stores it in the TaprootScriptSpendSig field. +func signSegWitV1ScriptSpend(in *psbt.PInput, tx *wire.MsgTx, + sigHashes *txscript.TxSigHashes, idx int, privKey *btcec.PrivateKey, + leaf txscript.TapLeaf) error { + + rawSig, err := txscript.RawTxInTapscriptSignature( + tx, sigHashes, idx, in.WitnessUtxo.Value, + in.WitnessUtxo.PkScript, leaf, in.SighashType, privKey, + ) + if err != nil { + return fmt.Errorf("error signing taproot script input %d: %v", + idx, err) + } + + leafHash := leaf.TapHash() + in.TaprootScriptSpendSig = append( + in.TaprootScriptSpendSig, &psbt.TaprootScriptSpendSig{ + XOnlyPubKey: in.TaprootBip32Derivation[0].XOnlyPubKey, + LeafHash: leafHash[:], + // We snip off the sighash flag from the end (if it was + // specified in the first place.) + Signature: rawSig[:schnorr.SignatureSize], + SigHash: in.SighashType, + }, + ) + + return nil +} + +// prepareScriptsV0 returns the appropriate witness v0 and/or legacy scripts, +// depending on the type of input that should be signed. +func prepareScriptsV0(in *psbt.PInput) ([]byte, []byte, error) { + switch { + // It's a NP2WKH input: + case len(in.RedeemScript) > 0: + builder := txscript.NewScriptBuilder() + builder.AddData(in.RedeemScript) + sigScript, err := builder.Script() + if err != nil { + return nil, nil, fmt.Errorf("error building np2wkh "+ + "script: %v", err) + } + + return in.RedeemScript, sigScript, nil + + // It's a P2WSH input: + case len(in.WitnessScript) > 0: + return in.WitnessScript, nil, nil + + // It's a P2WKH input: + default: + return in.WitnessUtxo.PkScript, nil, nil + } +} + +// maybeTweakPrivKeyPsbt examines if there are any tweak parameters given in the +// custom/proprietary PSBT fields and may perform a mapping on the passed +// private key in order to utilize the tweaks, if populated. +func maybeTweakPrivKeyPsbt(unknowns []*psbt.Unknown, + privKey *btcec.PrivateKey) *btcec.PrivateKey { + + // There can be other custom/unknown keys in a PSBT that we just ignore. + // Key tweaking is optional and only one tweak (single _or_ double) can + // ever be applied (at least for any use cases described in the BOLT + // spec). + for _, u := range unknowns { + if bytes.Equal(u.Key, PsbtKeyTypeInputSignatureTweakSingle) { + return input.TweakPrivKey(privKey, u.Value) + } + + if bytes.Equal(u.Key, PsbtKeyTypeInputSignatureTweakDouble) { + doubleTweakKey, _ := btcec.PrivKeyFromBytes( + u.Value, + ) + return input.DeriveRevocationPrivKey( + privKey, doubleTweakKey, + ) + } + } + + return privKey +} + +// FinalizePsbt expects a partial transaction with all inputs and outputs fully +// declared and tries to sign all inputs that belong to the specified account. +// Lnd must be the last signer of the transaction. That means, if there are any +// unsigned non-witness inputs or inputs without UTXO information attached or +// inputs without witness data that do not belong to lnd's wallet, this method +// will fail. If no error is returned, the PSBT is ready to be extracted and the +// final TX within to be broadcast. +// +// NOTE: This method does NOT publish the transaction after it's been +// finalized successfully. +// +// This is a part of the WalletController interface. +func (b *BtcWallet) FinalizePsbt(packet *psbt.Packet, accountName string) error { + var ( + keyScope *waddrmgr.KeyScope + accountNum uint32 + ) + switch accountName { + // If the default/imported account name was specified, we'll provide a + // nil key scope to FundPsbt, allowing it to sign inputs from both key + // scopes (NP2WKH, P2WKH). + case lnwallet.DefaultAccountName: + accountNum = defaultAccount + + case waddrmgr.ImportedAddrAccountName: + accountNum = importedAccount + + // Otherwise, map the account name to its key scope and internal account + // number to determine if the inputs belonging to this account should be + // signed. + default: + scope, account, err := b.wallet.LookupAccount(accountName) + if err != nil { + return err + } + keyScope = &scope + accountNum = account + } + + return b.wallet.FinalizePsbt(keyScope, accountNum, packet) +} diff --git a/lnwallet/btcwallet/psbt_test.go b/lnwallet/btcwallet/psbt_test.go new file mode 100644 index 0000000000..467d59b5d1 --- /dev/null +++ b/lnwallet/btcwallet/psbt_test.go @@ -0,0 +1,340 @@ +package btcwallet + +import ( + "bytes" + "crypto/sha256" + "encoding/hex" + "fmt" + "testing" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/psbt" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcwallet/waddrmgr" + "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/keychain" + "github.com/stretchr/testify/require" +) + +var ( + netParams = &chaincfg.RegressionNetParams + testValue int64 = 345678 + testCSVTimeout uint32 = 2016 + + testCommitSecretBytes, _ = hex.DecodeString( + "9f1f0db609718cf70c580aec6a0e570c3f086ec85a2a6119295b1d64240d" + + "aca5", + ) + testCommitSecret, testCommitPoint = btcec.PrivKeyFromBytes( + testCommitSecretBytes, + ) + + remoteRevocationBasePubKeyBytes, _ = hex.DecodeString( + "02baf067bfd1a6cf7229c7c459b106d384ad33e948ea1d561f2034475ff1" + + "7359fb", + ) + remoteRevocationBasePubKey, _ = btcec.ParsePubKey( + remoteRevocationBasePubKeyBytes, + ) + + testTweakSingle, _ = hex.DecodeString( + "020143a30cf6b71ca2af01efbd1758a04b4c7f5c2299f2ea63a8a6b58107" + + "63b1ed", + ) +) + +// testInputType is a type that represents different types of inputs that are +// signed within a PSBT. +type testInputType uint8 + +const ( + plainP2WKH testInputType = 0 + tweakedP2WKH testInputType = 1 + nestedP2WKH testInputType = 2 + singleKeyP2WSH testInputType = 3 + singleKeyDoubleTweakedP2WSH testInputType = 4 +) + +func (i testInputType) keyPath() []uint32 { + switch i { + case nestedP2WKH: + return []uint32{ + hardenedKey(waddrmgr.KeyScopeBIP0049Plus.Purpose), + hardenedKey(0), + hardenedKey(0), + 0, 0, + } + + case singleKeyP2WSH: + return []uint32{ + hardenedKey(keychain.BIP0043Purpose), + hardenedKey(netParams.HDCoinType), + hardenedKey(uint32(keychain.KeyFamilyPaymentBase)), + 0, 7, + } + + case singleKeyDoubleTweakedP2WSH: + return []uint32{ + hardenedKey(keychain.BIP0043Purpose), + hardenedKey(netParams.HDCoinType), + hardenedKey(uint32(keychain.KeyFamilyDelayBase)), + 0, 9, + } + + default: + return []uint32{ + hardenedKey(waddrmgr.KeyScopeBIP0084.Purpose), + hardenedKey(0), + hardenedKey(0), + 0, 0, + } + } +} + +func (i testInputType) output(t *testing.T, + privKey *btcec.PrivateKey) (*wire.TxOut, []byte) { + + var ( + addr btcutil.Address + witnessScript []byte + err error + ) + switch i { + case plainP2WKH: + h := btcutil.Hash160(privKey.PubKey().SerializeCompressed()) + addr, err = btcutil.NewAddressWitnessPubKeyHash(h, netParams) + require.NoError(t, err) + + case tweakedP2WKH: + privKey = input.TweakPrivKey(privKey, testTweakSingle) + + h := btcutil.Hash160(privKey.PubKey().SerializeCompressed()) + addr, err = btcutil.NewAddressWitnessPubKeyHash(h, netParams) + require.NoError(t, err) + + case nestedP2WKH: + h := btcutil.Hash160(privKey.PubKey().SerializeCompressed()) + witnessAddr, err := btcutil.NewAddressWitnessPubKeyHash( + h, netParams, + ) + require.NoError(t, err) + + witnessProgram, err := txscript.PayToAddrScript(witnessAddr) + require.NoError(t, err) + + addr, err = btcutil.NewAddressScriptHash( + witnessProgram, netParams, + ) + require.NoError(t, err) + + case singleKeyP2WSH: + // We're simulating a delay-to-self script which we're going to + // spend through the time lock path. We don't actually need to + // know the private key of the remote revocation base key. + revokeKey := input.DeriveRevocationPubkey( + remoteRevocationBasePubKey, testCommitPoint, + ) + witnessScript, err = input.CommitScriptToSelf( + testCSVTimeout, privKey.PubKey(), revokeKey, + ) + require.NoError(t, err) + + h := sha256.Sum256(witnessScript) + addr, err = btcutil.NewAddressWitnessScriptHash(h[:], netParams) + require.NoError(t, err) + + case singleKeyDoubleTweakedP2WSH: + // We're simulating breaching a remote party's delay-to-self + // output which we're going to spend through the revocation + // path. In that case the self key is the other party's self key + // and, we only know the revocation base private key and commit + // secret. + revokeKey := input.DeriveRevocationPubkey( + privKey.PubKey(), testCommitPoint, + ) + witnessScript, err = input.CommitScriptToSelf( + testCSVTimeout, remoteRevocationBasePubKey, revokeKey, + ) + require.NoError(t, err) + + h := sha256.Sum256(witnessScript) + addr, err = btcutil.NewAddressWitnessScriptHash(h[:], netParams) + require.NoError(t, err) + + default: + t.Fatalf("invalid input type") + } + + pkScript, err := txscript.PayToAddrScript(addr) + require.NoError(t, err) + return &wire.TxOut{ + Value: testValue, + PkScript: pkScript, + }, witnessScript +} + +func (i testInputType) decorateInput(t *testing.T, privKey *btcec.PrivateKey, + in *psbt.PInput) { + + switch i { + case tweakedP2WKH: + in.Unknowns = []*psbt.Unknown{{ + Key: PsbtKeyTypeInputSignatureTweakSingle, + Value: testTweakSingle, + }} + + case nestedP2WKH: + h := btcutil.Hash160(privKey.PubKey().SerializeCompressed()) + witnessAddr, err := btcutil.NewAddressWitnessPubKeyHash( + h, netParams, + ) + require.NoError(t, err) + + witnessProgram, err := txscript.PayToAddrScript(witnessAddr) + require.NoError(t, err) + in.RedeemScript = witnessProgram + + case singleKeyDoubleTweakedP2WSH: + in.Unknowns = []*psbt.Unknown{{ + Key: PsbtKeyTypeInputSignatureTweakDouble, + Value: testCommitSecret.Serialize(), + }} + } +} + +func (i testInputType) beforeFinalize(t *testing.T, packet *psbt.Packet) { + in := &packet.Inputs[0] + sigBytes := in.PartialSigs[0].Signature + pubKeyBytes := in.PartialSigs[0].PubKey + + var witnessStack wire.TxWitness + switch i { + case singleKeyP2WSH: + witnessStack = make([][]byte, 3) + witnessStack[0] = sigBytes + witnessStack[1] = nil + witnessStack[2] = in.WitnessScript + + case singleKeyDoubleTweakedP2WSH: + // Place a 1 as the first item in the evaluated witness stack to + // force script execution to the revocation clause. + witnessStack = make([][]byte, 3) + witnessStack[0] = sigBytes + witnessStack[1] = []byte{1} + witnessStack[2] = in.WitnessScript + + default: + witnessStack = make([][]byte, 2) + witnessStack[0] = sigBytes + witnessStack[1] = pubKeyBytes + } + + var err error + in.FinalScriptWitness, err = serializeTxWitness(witnessStack) + require.NoError(t, err) +} + +// serializeTxWitness return the wire witness stack into raw bytes. +func serializeTxWitness(txWitness wire.TxWitness) ([]byte, error) { + var witnessBytes bytes.Buffer + err := psbt.WriteTxWitness(&witnessBytes, txWitness) + if err != nil { + return nil, fmt.Errorf("error serializing witness: %v", err) + } + + return witnessBytes.Bytes(), nil +} + +// TestSignPsbt tests the PSBT signing functionality. +func TestSignPsbt(t *testing.T) { + w, _, cleanup := newTestWallet(t, netParams, seedBytes) + defer cleanup() + + testCases := []struct { + name string + inputType testInputType + }{{ + name: "plain P2WKH", + inputType: plainP2WKH, + }, { + name: "tweaked P2WKH", + inputType: tweakedP2WKH, + }, { + name: "nested P2WKH", + inputType: nestedP2WKH, + }, { + name: "single key P2WSH", + inputType: singleKeyP2WSH, + }, { + name: "single key double tweaked P2WSH", + inputType: singleKeyDoubleTweakedP2WSH, + }} + + for _, tc := range testCases { + tc := tc + + // This is the private key we're going to sign with. + privKey, err := w.deriveKeyByBIP32Path(tc.inputType.keyPath()) + require.NoError(t, err) + + txOut, witnessScript := tc.inputType.output(t, privKey) + + // Create the reference transaction that has the input that is + // going to be spent by our PSBT. + refTx := wire.NewMsgTx(2) + refTx.AddTxIn(&wire.TxIn{}) + refTx.AddTxOut(txOut) + + // Create the unsigned spend transaction that is going to be the + // main content of our PSBT. + spendTx := wire.NewMsgTx(2) + spendTx.LockTime = testCSVTimeout + spendTx.AddTxIn(&wire.TxIn{ + PreviousOutPoint: wire.OutPoint{ + Hash: refTx.TxHash(), + Index: 0, + }, + Sequence: testCSVTimeout, + }) + spendTx.AddTxOut(txOut) + + // Convert it to a PSBT now and add all required signing + // metadata to it. + packet, err := psbt.NewFromUnsignedTx(spendTx) + require.NoError(t, err) + packet.Inputs[0].WitnessScript = witnessScript + packet.Inputs[0].SighashType = txscript.SigHashAll + packet.Inputs[0].WitnessUtxo = refTx.TxOut[0] + packet.Inputs[0].Bip32Derivation = []*psbt.Bip32Derivation{{ + PubKey: privKey.PubKey().SerializeCompressed(), + Bip32Path: tc.inputType.keyPath(), + }} + tc.inputType.decorateInput(t, privKey, &packet.Inputs[0]) + + // Let the wallet do its job. We expect to be the only signer + // for this PSBT, so we'll be able to finalize it later. + err = w.SignPsbt(packet) + require.NoError(t, err) + + // If the witness stack needs to be assembled, give the caller + // the option to do that now. + tc.inputType.beforeFinalize(t, packet) + + finalTx, err := psbt.Extract(packet) + require.NoError(t, err) + + vm, err := txscript.NewEngine( + refTx.TxOut[0].PkScript, finalTx, 0, + txscript.StandardVerifyFlags, nil, nil, + refTx.TxOut[0].Value, + txscript.NewCannedPrevOutputFetcher( + refTx.TxOut[0].PkScript, refTx.TxOut[0].Value, + ), + ) + require.NoError(t, err) + require.NoError(t, vm.Execute()) + } +} diff --git a/lnwallet/btcwallet/signer.go b/lnwallet/btcwallet/signer.go index 277f391320..91ee53e80e 100644 --- a/lnwallet/btcwallet/signer.go +++ b/lnwallet/btcwallet/signer.go @@ -1,14 +1,20 @@ package btcwallet import ( - "github.com/btcsuite/btcd/btcec" + "crypto/sha256" + "fmt" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "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/hdkeychain" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/walletdb" - "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwallet" @@ -21,7 +27,9 @@ import ( // // This is a part of the WalletController interface. func (b *BtcWallet) FetchInputInfo(prevOut *wire.OutPoint) (*lnwallet.Utxo, error) { - _, txOut, _, confirmations, err := b.wallet.FetchInputInfo(prevOut) + prevTx, txOut, bip32, confirmations, err := b.wallet.FetchInputInfo( + prevOut, + ) if err != nil { return nil, err } @@ -33,6 +41,8 @@ func (b *BtcWallet) FetchInputInfo(prevOut *wire.OutPoint) (*lnwallet.Utxo, erro addressType = lnwallet.WitnessPubKey case txscript.IsPayToScriptHash(txOut.PkScript): addressType = lnwallet.NestedWitnessPubKey + case txscript.IsPayToTaproot(txOut.PkScript): + addressType = lnwallet.TaprootPubkey } return &lnwallet.Utxo{ @@ -41,9 +51,20 @@ func (b *BtcWallet) FetchInputInfo(prevOut *wire.OutPoint) (*lnwallet.Utxo, erro PkScript: txOut.PkScript, Confirmations: confirmations, OutPoint: *prevOut, + Derivation: bip32, + PrevTx: prevTx, }, nil } +// ScriptForOutput returns the address, witness program and redeem script for a +// given UTXO. An error is returned if the UTXO does not belong to our wallet or +// it is not a managed pubKey address. +func (b *BtcWallet) ScriptForOutput(output *wire.TxOut) ( + waddrmgr.ManagedPubKeyAddress, []byte, []byte, error) { + + return b.wallet.ScriptForOutput(output) +} + // deriveFromKeyLoc attempts to derive a private key using a fully specified // KeyLocator. func deriveFromKeyLoc(scopedMgr *waddrmgr.ScopedKeyManager, @@ -64,9 +85,139 @@ func deriveFromKeyLoc(scopedMgr *waddrmgr.ScopedKeyManager, return addr.(waddrmgr.ManagedPubKeyAddress).PrivKey() } +// deriveKeyByBIP32Path derives a key described by a BIP32 path. We expect the +// first three elements of the path to be hardened according to BIP44, so they +// must be a number >= 2^31. +func (b *BtcWallet) deriveKeyByBIP32Path(path []uint32) (*btcec.PrivateKey, + error) { + + // Make sure we get a full path with exactly 5 elements. A path is + // either custom purpose one with 4 dynamic and one static elements: + // m/1017'/coinType'/keyFamily'/0/index + // Or a default BIP49/89 one with 5 elements: + // m/purpose'/coinType'/account'/change/index + const expectedDerivationPathDepth = 5 + if len(path) != expectedDerivationPathDepth { + return nil, fmt.Errorf("invalid BIP32 derivation path, "+ + "expected path length %d, instead was %d", + expectedDerivationPathDepth, len(path)) + } + + // Assert that the first three parts of the path are actually hardened + // to avoid under-flowing the uint32 type. + if err := assertHardened(path[0], path[1], path[2]); err != nil { + return nil, fmt.Errorf("invalid BIP32 derivation path, "+ + "expected first three elements to be hardened: %w", err) + } + + purpose := path[0] - hdkeychain.HardenedKeyStart + coinType := path[1] - hdkeychain.HardenedKeyStart + account := path[2] - hdkeychain.HardenedKeyStart + change, index := path[3], path[4] + + // Is this a custom lnd internal purpose key? + switch purpose { + case keychain.BIP0043Purpose: + // Make sure it's for the same coin type as our wallet's + // keychain scope. + if coinType != b.chainKeyScope.Coin { + return nil, fmt.Errorf("invalid BIP32 derivation "+ + "path, expected coin type %d, instead was %d", + b.chainKeyScope.Coin, coinType) + } + + return b.deriveKeyByLocator(keychain.KeyLocator{ + Family: keychain.KeyFamily(account), + Index: index, + }) + + // Is it a standard, BIP defined purpose that the wallet understands? + case waddrmgr.KeyScopeBIP0044.Purpose, + waddrmgr.KeyScopeBIP0049Plus.Purpose, + waddrmgr.KeyScopeBIP0084.Purpose, + waddrmgr.KeyScopeBIP0086.Purpose: + + // We're going to continue below the switch statement to avoid + // unnecessary indentation for this default case. + + // Currently, there is no way to import any other key scopes than the + // one custom purpose or three standard ones into lnd's wallet. So we + // shouldn't accept any other scopes to sign for. + default: + return nil, fmt.Errorf("invalid BIP32 derivation path, "+ + "unknown purpose %d", purpose) + } + + // Okay, we made sure it's a BIP49/84 key, so we need to derive it now. + // Interestingly, the btcwallet never actually uses a coin type other + // than 0 for those keys, so we need to make sure this behavior is + // replicated here. + if coinType != 0 { + return nil, fmt.Errorf("invalid BIP32 derivation path, coin " + + "type must be 0 for BIP49/84 btcwallet keys") + } + + // We only expect to be asked to sign with key scopes that we know + // about. So if the scope doesn't exist, we don't create it. + scope := waddrmgr.KeyScope{ + Purpose: purpose, + Coin: coinType, + } + scopedMgr, err := b.wallet.Manager.FetchScopedKeyManager(scope) + if err != nil { + return nil, fmt.Errorf("error fetching manager for scope %v: "+ + "%w", scope, err) + } + + // Let's see if we can hit the private key cache. + keyPath := waddrmgr.DerivationPath{ + InternalAccount: account, + Account: account, + Branch: change, + Index: index, + } + privKey, err := scopedMgr.DeriveFromKeyPathCache(keyPath) + if err == nil { + return privKey, nil + } + + // The key wasn't in the cache, let's fully derive it now. + err = walletdb.View(b.db, func(tx walletdb.ReadTx) error { + addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) + + addr, err := scopedMgr.DeriveFromKeyPath(addrmgrNs, keyPath) + if err != nil { + return fmt.Errorf("error deriving private key: %w", err) + } + + privKey, err = addr.(waddrmgr.ManagedPubKeyAddress).PrivKey() + return err + }) + if err != nil { + return nil, fmt.Errorf("error deriving key from path %#v: %w", + keyPath, err) + } + + return privKey, nil +} + +// assertHardened makes sure each given element is >= 2^31. +func assertHardened(elements ...uint32) error { + for idx, element := range elements { + if element < hdkeychain.HardenedKeyStart { + return fmt.Errorf("element at index %d is not hardened", + idx) + } + } + + return nil +} + // deriveKeyByLocator attempts to derive a key stored in the wallet given a // valid key locator. -func (b *BtcWallet) deriveKeyByLocator(keyLoc keychain.KeyLocator) (*btcec.PrivateKey, error) { +func (b *BtcWallet) deriveKeyByLocator( + keyLoc keychain.KeyLocator) (*btcec.PrivateKey, error) { + // We'll assume the special lightning key scope in this case. scopedMgr, err := b.wallet.Manager.FetchScopedKeyManager( b.chainKeyScope, @@ -210,6 +361,56 @@ func (b *BtcWallet) SignOutputRaw(tx *wire.MsgTx, return nil, err } + // In case of a taproot output any signature is always a Schnorr + // signature, based on the new tapscript sighash algorithm. + if txscript.IsPayToTaproot(signDesc.Output.PkScript) { + sigHashes := txscript.NewTxSigHashes( + tx, signDesc.PrevOutputFetcher, + ) + + // Are we spending a script path or the key path? The API is + // slightly different, so we need to account for that to get the + // raw signature. + var rawSig []byte + switch signDesc.SignMethod { + case input.TaprootKeySpendBIP0086SignMethod, + input.TaprootKeySpendSignMethod: + + // This function tweaks the private key using the tap + // root key supplied as the tweak. + rawSig, err = txscript.RawTxInTaprootSignature( + tx, sigHashes, signDesc.InputIndex, + signDesc.Output.Value, signDesc.Output.PkScript, + signDesc.TapTweak, signDesc.HashType, + privKey, + ) + if err != nil { + return nil, err + } + + case input.TaprootScriptSpendSignMethod: + leaf := txscript.TapLeaf{ + LeafVersion: txscript.BaseLeafVersion, + Script: witnessScript, + } + rawSig, err = txscript.RawTxInTapscriptSignature( + tx, sigHashes, signDesc.InputIndex, + signDesc.Output.Value, signDesc.Output.PkScript, + leaf, signDesc.HashType, privKey, + ) + if err != nil { + return nil, err + } + } + + sig, err := schnorr.ParseSignature(rawSig) + if err != nil { + return nil, err + } + + return sig, nil + } + // TODO(roasbeef): generate sighash midstate if not present? amt := signDesc.Output.Value @@ -222,7 +423,7 @@ func (b *BtcWallet) SignOutputRaw(tx *wire.MsgTx, } // Chop off the sighash flag at the end of the signature. - return btcec.ParseDERSignature(sig[:len(sig)-1], btcec.S256()) + return ecdsa.ParseDERSignature(sig[:len(sig)-1]) } // ComputeInputScript generates a complete InputScript for the passed @@ -256,6 +457,275 @@ func (b *BtcWallet) ComputeInputScript(tx *wire.MsgTx, }, nil } +// muSig2State is a struct that holds on to the internal signing session state +// of a MuSig2 session. +type muSig2State struct { + // MuSig2SessionInfo is the associated meta information of the signing + // session. + input.MuSig2SessionInfo + + // context is the signing context responsible for keeping track of the + // public keys involved in the signing process. + context *musig2.Context + + // session is the signing session responsible for keeping track of the + // nonces and partial signatures involved in the signing process. + session *musig2.Session +} + +// MuSig2CreateSession creates a new MuSig2 signing session using the local +// key identified by the key locator. The complete list of all public keys of +// all signing parties must be provided, including the public key of the local +// signing key. If nonces of other parties are already known, they can be +// submitted as well to reduce the number of method calls necessary later on. +func (b *BtcWallet) MuSig2CreateSession(keyLoc keychain.KeyLocator, + allSignerPubKeys []*btcec.PublicKey, tweaks *input.MuSig2Tweaks, + otherSignerNonces [][musig2.PubNonceSize]byte) (*input.MuSig2SessionInfo, + error) { + + // We need to derive the private key for signing. In the remote signing + // setup, this whole RPC call will be forwarded to the signing + // instance, which requires it to be stateful. + privKey, err := b.fetchPrivKey(&keychain.KeyDescriptor{ + KeyLocator: keyLoc, + }) + if err != nil { + return nil, fmt.Errorf("error deriving private key: %v", err) + } + + // The context keeps track of all signing keys and our local key. + allOpts := append( + []musig2.ContextOption{ + musig2.WithKnownSigners(allSignerPubKeys), + }, + tweaks.ToContextOptions()..., + ) + musigContext, err := musig2.NewContext(privKey, true, allOpts...) + if err != nil { + return nil, fmt.Errorf("error creating MuSig2 signing "+ + "context: %v", err) + } + + // The session keeps track of the own and other nonces. + musigSession, err := musigContext.NewSession() + if err != nil { + return nil, fmt.Errorf("error creating MuSig2 signing "+ + "session: %v", err) + } + + // Add all nonces we might've learned so far. + haveAllNonces := false + for _, otherSignerNonce := range otherSignerNonces { + haveAllNonces, err = musigSession.RegisterPubNonce( + otherSignerNonce, + ) + if err != nil { + return nil, fmt.Errorf("error registering other "+ + "signer public nonce: %v", err) + } + } + + // Register the new session. + combinedKey, err := musigContext.CombinedKey() + if err != nil { + return nil, fmt.Errorf("error getting combined key: %v", err) + } + session := &muSig2State{ + MuSig2SessionInfo: input.MuSig2SessionInfo{ + SessionID: input.NewMuSig2SessionID( + combinedKey, musigSession.PublicNonce(), + ), + PublicNonce: musigSession.PublicNonce(), + CombinedKey: combinedKey, + TaprootTweak: tweaks.HasTaprootTweak(), + HaveAllNonces: haveAllNonces, + }, + context: musigContext, + session: musigSession, + } + + // The internal key is only calculated if we are using a taproot tweak + // and need to know it for a potential script spend. + if tweaks.HasTaprootTweak() { + internalKey, err := musigContext.TaprootInternalKey() + if err != nil { + return nil, fmt.Errorf("error getting internal key: %v", + err) + } + session.TaprootInternalKey = internalKey + } + + // Since we generate new nonces for every session, there is no way that + // a session with the same ID already exists. So even if we call the API + // twice with the same signers, we still get a new ID. + b.musig2SessionsMtx.Lock() + b.musig2Sessions[session.SessionID] = session + b.musig2SessionsMtx.Unlock() + + return &session.MuSig2SessionInfo, nil +} + +// MuSig2RegisterNonces registers one or more public nonces of other signing +// participants for a session identified by its ID. This method returns true +// once we have all nonces for all other signing participants. +func (b *BtcWallet) MuSig2RegisterNonces(sessionID input.MuSig2SessionID, + otherSignerNonces [][musig2.PubNonceSize]byte) (bool, error) { + + // We hold the lock during the whole operation, we don't want any + // interference with calls that might come through in parallel for the + // same session. + b.musig2SessionsMtx.Lock() + defer b.musig2SessionsMtx.Unlock() + + session, ok := b.musig2Sessions[sessionID] + if !ok { + return false, fmt.Errorf("session with ID %x not found", + sessionID[:]) + } + + // Make sure we don't exceed the number of expected nonces as that would + // indicate something is wrong with the signing setup. + if session.HaveAllNonces { + return true, fmt.Errorf("already have all nonces") + } + + numSigners := len(session.context.SigningKeys()) + remainingNonces := numSigners - session.session.NumRegisteredNonces() + if len(otherSignerNonces) > remainingNonces { + return false, fmt.Errorf("only %d other nonces remaining but "+ + "trying to register %d more", remainingNonces, + len(otherSignerNonces)) + } + + // Add all nonces we've learned so far. + var err error + for _, otherSignerNonce := range otherSignerNonces { + session.HaveAllNonces, err = session.session.RegisterPubNonce( + otherSignerNonce, + ) + if err != nil { + return false, fmt.Errorf("error registering other "+ + "signer public nonce: %v", err) + } + } + + return session.HaveAllNonces, nil +} + +// MuSig2Sign creates a partial signature using the local signing key +// that was specified when the session was created. This can only be +// called when all public nonces of all participants are known and have +// been registered with the session. If this node isn't responsible for +// combining all the partial signatures, then the cleanup parameter +// should be set, indicating that the session can be removed from memory +// once the signature was produced. +func (b *BtcWallet) MuSig2Sign(sessionID input.MuSig2SessionID, + msg [sha256.Size]byte, cleanUp bool) (*musig2.PartialSignature, error) { + + // We hold the lock during the whole operation, we don't want any + // interference with calls that might come through in parallel for the + // same session. + b.musig2SessionsMtx.Lock() + defer b.musig2SessionsMtx.Unlock() + + session, ok := b.musig2Sessions[sessionID] + if !ok { + return nil, fmt.Errorf("session with ID %x not found", + sessionID[:]) + } + + // We can only sign once we have all other signer's nonces. + if !session.HaveAllNonces { + return nil, fmt.Errorf("only have %d of %d required nonces", + session.session.NumRegisteredNonces(), + len(session.context.SigningKeys())) + } + + // Create our own partial signature with the local signing key. + partialSig, err := session.session.Sign(msg, musig2.WithSortedKeys()) + if err != nil { + return nil, fmt.Errorf("error signing with local key: %v", err) + } + + // Clean up our local state if requested. + if cleanUp { + delete(b.musig2Sessions, sessionID) + } + + return partialSig, nil +} + +// MuSig2CombineSig combines the given partial signature(s) with the +// local one, if it already exists. Once a partial signature of all +// participants is registered, the final signature will be combined and +// returned. +func (b *BtcWallet) MuSig2CombineSig(sessionID input.MuSig2SessionID, + partialSigs []*musig2.PartialSignature) (*schnorr.Signature, bool, + error) { + + // We hold the lock during the whole operation, we don't want any + // interference with calls that might come through in parallel for the + // same session. + b.musig2SessionsMtx.Lock() + defer b.musig2SessionsMtx.Unlock() + + session, ok := b.musig2Sessions[sessionID] + if !ok { + return nil, false, fmt.Errorf("session with ID %x not found", + sessionID[:]) + } + + // Make sure we don't exceed the number of expected partial signatures + // as that would indicate something is wrong with the signing setup. + if session.HaveAllSigs { + return nil, true, fmt.Errorf("already have all partial" + + "signatures") + } + + // Add all sigs we got so far. + var ( + finalSig *schnorr.Signature + err error + ) + for _, otherPartialSig := range partialSigs { + session.HaveAllSigs, err = session.session.CombineSig( + otherPartialSig, + ) + if err != nil { + return nil, false, fmt.Errorf("error combining "+ + "partial signature: %v", err) + } + } + + // If we have all partial signatures, we should be able to get the + // complete signature now. We also remove this session from memory since + // there is nothing more left to do. + if session.HaveAllSigs { + finalSig = session.session.FinalSig() + delete(b.musig2Sessions, sessionID) + } + + return finalSig, session.HaveAllSigs, nil +} + +// MuSig2Cleanup removes a session from memory to free up resources. +func (b *BtcWallet) MuSig2Cleanup(sessionID input.MuSig2SessionID) error { + // We hold the lock during the whole operation, we don't want any + // interference with calls that might come through in parallel for the + // same session. + b.musig2SessionsMtx.Lock() + defer b.musig2SessionsMtx.Unlock() + + _, ok := b.musig2Sessions[sessionID] + if !ok { + return fmt.Errorf("session with ID %x not found", sessionID[:]) + } + + delete(b.musig2Sessions, sessionID) + + return nil +} + // A compile time check to ensure that BtcWallet implements the Signer // interface. var _ input.Signer = (*BtcWallet)(nil) @@ -267,7 +737,7 @@ var _ input.Signer = (*BtcWallet)(nil) // // NOTE: This is a part of the MessageSigner interface. func (b *BtcWallet) SignMessage(keyLoc keychain.KeyLocator, - msg []byte, doubleHash bool) (*btcec.Signature, error) { + msg []byte, doubleHash bool) (*ecdsa.Signature, error) { // First attempt to fetch the private key which corresponds to the // specified public key. @@ -285,12 +755,7 @@ func (b *BtcWallet) SignMessage(keyLoc keychain.KeyLocator, } else { msgDigest = chainhash.HashB(msg) } - sign, err := privKey.Sign(msgDigest) - if err != nil { - return nil, errors.Errorf("unable sign the message: %v", err) - } - - return sign, nil + return ecdsa.Sign(privKey, msgDigest), nil } // A compile time check to ensure that BtcWallet implements the MessageSigner diff --git a/lnwallet/btcwallet/signer_test.go b/lnwallet/btcwallet/signer_test.go new file mode 100644 index 0000000000..4b36accc74 --- /dev/null +++ b/lnwallet/btcwallet/signer_test.go @@ -0,0 +1,359 @@ +package btcwallet + +import ( + "encoding/hex" + "fmt" + "io/ioutil" + "math" + "os" + "testing" + "time" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/hdkeychain" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/integration/rpctest" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcwallet/chain" + "github.com/btcsuite/btcwallet/waddrmgr" + "github.com/lightningnetwork/lnd/blockcache" + "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/lnwallet" + "github.com/stretchr/testify/require" +) + +var ( + // seedBytes is the raw entropy of the aezeed: + // able promote dizzy mixture sword myth share public find tattoo + // catalog cousin bulb unfair machine alarm cool large promote kick + // shop rug mean year + // Which corresponds to the master root key: + // xprv9s21ZrQH143K2KADjED57FvNbptdKLp4sqKzssegwEGKQMGoDkbyhUeCKe5m3A + // MU44z4vqkmGswwQVKrv599nFG16PPZDEkNrogwoDGeCmZ + seedBytes, _ = hex.DecodeString("4a7611b6979ba7c4bc5c5cd2239b2973") + + // firstAddress is the first address that we should get from the wallet, + // corresponding to the derivation path m/84'/0'/0'/0/0 (even on regtest + // which is a special case for the BIP49/84 addresses in btcwallet). + firstAddress = "bcrt1qgdlgjc5ede7fjv350wcjqat80m0zsmfaswsj9p" + + // firstAddressTaproot is the first address that we should get from the + // wallet when deriving a taproot address. + firstAddressTaproot = "bcrt1ps8c222fgysvnsj2m8hxk8khy6wthcrhv9va9z3t4" + + "h3qeyz65sh4qqwvdgc" + + testPubKeyBytes, _ = hex.DecodeString( + "037a67771635344641d4b56aac33cd5f7a265b59678dce3aec31b89125e3" + + "b8b9b2", + ) + testPubKey, _ = btcec.ParsePubKey(testPubKeyBytes) + testTaprootKeyBytes, _ = hex.DecodeString( + "03f068684c9141027318eed958dccbf4f7f748700e1da53315630d82a362" + + "d6a887", + ) + testTaprootKey, _ = btcec.ParsePubKey(testTaprootKeyBytes) + + testTapscriptAddr = "bcrt1p7p5xsny3gyp8xx8wm9vdejl57lm5suqwrkjnx9trpk" + + "p2xckk4zrs4xehl8" + testTapscriptPkScript = append( + []byte{txscript.OP_1, txscript.OP_DATA_32}, + schnorr.SerializePubKey(testTaprootKey)..., + ) + + testCases = []struct { + name string + path []uint32 + err string + wif string + }{{ + name: "m/84'/0'/0'/0/0", + path: []uint32{ + hardenedKey(84), hardenedKey(0), hardenedKey(0), 0, 0, + }, + wif: "cPp3XUewCBQVg3pgwVWbtpzDwhWTQpHhu8saN3SdGRTkiLpu1R6h", + }, { + name: "m/84'/0'/0'/1/0", + path: []uint32{ + hardenedKey(84), hardenedKey(0), hardenedKey(0), 1, 0, + }, + wif: "cPUR1nFAeYAtSWSkKoWB6WbzRTbDSGdrGRmv1kVLRPyo7QXph2gt", + }, { + name: "m/84'/0'/0'/0/12345", + path: []uint32{ + hardenedKey(84), hardenedKey(0), hardenedKey(0), 0, + 12345, + }, + wif: "cQCdGxqKeGZKiC2uRYMAGenJHkDvajiPieT4Yg7k1BKawjKkywvz", + }, { + name: "m/49'/0'/0'/0/0", + path: []uint32{ + hardenedKey(49), hardenedKey(0), hardenedKey(0), 0, 0, + }, + wif: "cMwVK2bcTzivPZfcCH585rBGghqsJAP9MdVy8inRti1wZvLn5DvY", + }, { + name: "m/49'/0'/0'/1/0", + path: []uint32{ + hardenedKey(49), hardenedKey(0), hardenedKey(0), 1, 0, + }, + wif: "cNPW9bMtdc2YGBzWzSCXFN4excjrT34nZzGYtfkzkazUrt3dXuv7", + }, { + name: "m/49'/0'/0'/1/12345", + path: []uint32{ + hardenedKey(49), hardenedKey(0), hardenedKey(0), 1, + 12345, + }, + wif: "cNdJt2fSNUJYVSb8JFjhosPcQgNvJ92SjNeNpsf1gUwDVDv2KVRa", + }, { + name: "m/1017'/1'/0'/0/0", + path: []uint32{ + hardenedKey(1017), hardenedKey(1), hardenedKey(0), 0, 0, + }, + wif: "cPsCmbWQENgptj3eTiyd85QSAD1xqYKPM9jUkfvm7vgN3SoVPWSP", + }, { + name: "m/1017'/1'/6'/0/0", + path: []uint32{ + hardenedKey(1017), hardenedKey(1), hardenedKey(6), 0, 0, + }, + wif: "cPeQdpcGJmLqpdmvokh3DK9ZtjYAXxiw4p4ELNUWkWt6bMRqArEV", + }, { + name: "m/1017'/1'/7'/0/123", + path: []uint32{ + hardenedKey(1017), hardenedKey(1), hardenedKey(7), 0, + 123, + }, + wif: "cPcWZMqY4YErkcwjtFJaYoXkzd7bKxrfxAVzhDgy3n5BGH8CU8sn", + }, { + name: "m/84'/1'/0'/0/0", + path: []uint32{ + hardenedKey(84), hardenedKey(1), hardenedKey(0), 0, 0, + }, + err: "coin type must be 0 for BIP49/84 btcwallet keys", + }, { + name: "m/1017'/0'/0'/0/0", + path: []uint32{ + hardenedKey(1017), hardenedKey(0), hardenedKey(0), 0, 0, + }, + err: "expected coin type 1, instead was 0", + }, { + name: "m/84'/0'/1'/0/0", + path: []uint32{ + hardenedKey(84), hardenedKey(0), hardenedKey(1), 0, 0, + }, + err: "account 1 not found", + }, { + name: "m/49'/0'/1'/0/0", + path: []uint32{ + hardenedKey(49), hardenedKey(0), hardenedKey(1), 0, 0, + }, + err: "account 1 not found", + }, { + name: "non-hardened purpose m/84/0/0/0/0", + path: []uint32{84, 0, 0, 0, 0}, + err: "element at index 0 is not hardened", + }, { + name: "non-hardened account m/84'/0'/0/0/0", + path: []uint32{hardenedKey(84), hardenedKey(0), 0, 0, 0}, + err: "element at index 2 is not hardened", + }} +) + +// TestBip32KeyDerivation makes sure that private keys can be derived from a +// BIP32 key path correctly. +func TestBip32KeyDerivation(t *testing.T) { + netParams := &chaincfg.RegressionNetParams + w, _, cleanup := newTestWallet(t, netParams, seedBytes) + defer cleanup() + + // This is just a sanity check that the wallet was initialized + // correctly. We make sure the first derived address is the expected + // one. + firstDerivedAddr, err := w.NewAddress( + lnwallet.WitnessPubKey, false, lnwallet.DefaultAccountName, + ) + require.NoError(t, err) + require.Equal(t, firstAddress, firstDerivedAddr.String()) + + // Let's go through the test cases now that we know our wallet is ready. + for _, tc := range testCases { + tc := tc + + t.Run(tc.name, func(t *testing.T) { + privKey, err := w.deriveKeyByBIP32Path(tc.path) + + if tc.err == "" { + require.NoError(t, err) + wif, err := btcutil.NewWIF( + privKey, netParams, true, + ) + require.NoError(t, err) + require.Equal(t, tc.wif, wif.String()) + } else { + require.Error(t, err) + require.Contains(t, err.Error(), tc.err) + } + }) + } +} + +// TestScriptImport tests the btcwallet's tapscript import capabilities by +// importing both a full taproot script tree and a partially revealed branch +// with a proof to make sure the resulting addresses match up. +func TestScriptImport(t *testing.T) { + netParams := &chaincfg.RegressionNetParams + w, miner, cleanup := newTestWallet(t, netParams, seedBytes) + defer cleanup() + + firstDerivedAddr, err := w.NewAddress( + lnwallet.TaprootPubkey, false, lnwallet.DefaultAccountName, + ) + require.NoError(t, err) + require.Equal(t, firstAddressTaproot, firstDerivedAddr.String()) + + scope := waddrmgr.KeyScopeBIP0086 + _, err = w.InternalWallet().Manager.FetchScopedKeyManager(scope) + require.NoError(t, err) + + // Let's create a taproot script output now. This is a hash lock with a + // simple preimage of "foobar". + builder := txscript.NewScriptBuilder() + builder.AddOp(txscript.OP_DUP) + builder.AddOp(txscript.OP_HASH160) + builder.AddData(btcutil.Hash160([]byte("foobar"))) + builder.AddOp(txscript.OP_EQUALVERIFY) + script1, err := builder.Script() + require.NoError(t, err) + leaf1 := txscript.NewBaseTapLeaf(script1) + + // Let's add a second script output as well to test the partial reveal. + builder = txscript.NewScriptBuilder() + builder.AddData(schnorr.SerializePubKey(testPubKey)) + builder.AddOp(txscript.OP_CHECKSIG) + script2, err := builder.Script() + require.NoError(t, err) + leaf2 := txscript.NewBaseTapLeaf(script2) + + // Our first test case is storing the script with all its leaves. + tapscript1 := input.TapscriptFullTree(testPubKey, leaf1, leaf2) + + taprootKey1, err := tapscript1.TaprootKey() + require.NoError(t, err) + require.Equal( + t, testTaprootKey.SerializeCompressed(), + taprootKey1.SerializeCompressed(), + ) + + addr1, err := w.ImportTaprootScript(scope, tapscript1) + require.NoError(t, err) + + require.Equal(t, testTapscriptAddr, addr1.Address().String()) + pkScript, err := txscript.PayToAddrScript(addr1.Address()) + require.NoError(t, err) + require.Equal(t, testTapscriptPkScript, pkScript) + + // Send some coins to the taproot address now and wait until they are + // seen as unconfirmed. + _, err = miner.SendOutputs([]*wire.TxOut{{ + Value: btcutil.SatoshiPerBitcoin, + PkScript: pkScript, + }}, 1) + require.NoError(t, err) + + var utxos []*lnwallet.Utxo + require.Eventually(t, func() bool { + utxos, err = w.ListUnspentWitness(0, math.MaxInt32, "") + require.NoError(t, err) + + return len(utxos) == 1 + }, time.Minute, 50*time.Millisecond) + require.Equal(t, testTapscriptPkScript, utxos[0].PkScript) + + // Now, as a last test, make sure that when we try adding an address + // with partial script reveal, we get an error that the address already + // exists. + inclusionProof := leaf2.TapHash() + tapscript2 := input.TapscriptPartialReveal( + testPubKey, leaf1, inclusionProof[:], + ) + _, err = w.ImportTaprootScript(scope, tapscript2) + require.Error(t, err) + require.Contains(t, err.Error(), fmt.Sprintf( + "address for script hash/key %x already exists", + schnorr.SerializePubKey(testTaprootKey), + )) +} + +func newTestWallet(t *testing.T, netParams *chaincfg.Params, + seedBytes []byte) (*BtcWallet, *rpctest.Harness, func()) { + + tempDir, err := ioutil.TempDir("", "lnwallet") + if err != nil { + _ = os.RemoveAll(tempDir) + t.Fatalf("creating temp dir failed: %v", err) + } + + chainBackend, miner, backendCleanup := getChainBackend(t, netParams) + cleanup := func() { + _ = os.RemoveAll(tempDir) + backendCleanup() + } + + loaderOpt := LoaderWithLocalWalletDB(tempDir, false, time.Minute) + config := Config{ + PrivatePass: []byte("some-pass"), + HdSeed: seedBytes, + NetParams: netParams, + CoinType: netParams.HDCoinType, + ChainSource: chainBackend, + // wallet starts in recovery mode + RecoveryWindow: 2, + LoaderOptions: []LoaderOption{loaderOpt}, + } + blockCache := blockcache.NewBlockCache(10000) + w, err := New(config, blockCache) + if err != nil { + cleanup() + t.Fatalf("creating wallet failed: %v", err) + } + + err = w.Start() + if err != nil { + cleanup() + t.Fatalf("starting wallet failed: %v", err) + } + + return w, miner, cleanup +} + +// getChainBackend returns a simple btcd based chain backend to back the wallet. +func getChainBackend(t *testing.T, netParams *chaincfg.Params) (chain.Interface, + *rpctest.Harness, func()) { + + miningNode, err := rpctest.New(netParams, nil, nil, "") + require.NoError(t, err) + require.NoError(t, miningNode.SetUp(true, 25)) + + // Next, mine enough blocks in order for SegWit and the CSV package + // soft-fork to activate on RegNet. + numBlocks := netParams.MinerConfirmationWindow * 2 + _, err = miningNode.Client.Generate(numBlocks) + require.NoError(t, err) + + rpcConfig := miningNode.RPCConfig() + chainClient, err := chain.NewRPCClient( + netParams, rpcConfig.Host, rpcConfig.User, rpcConfig.Pass, + rpcConfig.Certificates, false, 20, + ) + require.NoError(t, err) + + return chainClient, miningNode, func() { + _ = miningNode.TearDown() + } +} + +// hardenedKey returns a key of a hardened derivation key path. +func hardenedKey(part uint32) uint32 { + return part + hdkeychain.HardenedKeyStart +} diff --git a/lnwallet/chainfee/estimator.go b/lnwallet/chainfee/estimator.go index 7439c528a7..93912d368e 100644 --- a/lnwallet/chainfee/estimator.go +++ b/lnwallet/chainfee/estimator.go @@ -2,16 +2,18 @@ package chainfee import ( "encoding/json" + "errors" "fmt" "io" + "math" prand "math/rand" "net" "net/http" "sync" "time" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/rpcclient" - "github.com/btcsuite/btcutil" ) const ( @@ -35,6 +37,15 @@ const ( maxFeeUpdateTimeout = 20 * time.Minute ) +var ( + // errNoFeeRateFound is used when a given conf target cannot be found + // from the fee estimator. + errNoFeeRateFound = errors.New("no fee estimation for block target") + + // errEmptyCache is used when the fee rate cache is empty. + errEmptyCache = errors.New("fee rate cache is empty") +) + // Estimator provides the ability to estimate on-chain transaction fees for // various combinations of transaction sizes and desired confirmation time // (measured by number of blocks). @@ -74,7 +85,6 @@ type StaticEstimator struct { // NewStaticEstimator returns a new static fee estimator instance. func NewStaticEstimator(feePerKW, relayFee SatPerKWeight) *StaticEstimator { - return &StaticEstimator{ feePerKW: feePerKW, relayFee: relayFee, @@ -579,7 +589,9 @@ func NewWebAPIEstimator(api WebAPIFeeSource, noCache bool) *WebAPIEstimator { // confirmation and returns the estimated fee expressed in sat/kw. // // NOTE: This method is part of the Estimator interface. -func (w *WebAPIEstimator) EstimateFeePerKW(numBlocks uint32) (SatPerKWeight, error) { +func (w *WebAPIEstimator) EstimateFeePerKW(numBlocks uint32) ( + SatPerKWeight, error) { + if numBlocks > maxBlockTarget { numBlocks = maxBlockTarget } else if numBlocks < minBlockTarget { @@ -593,8 +605,12 @@ func (w *WebAPIEstimator) EstimateFeePerKW(numBlocks uint32) (SatPerKWeight, err } feePerKb, err := w.getCachedFee(numBlocks) + + // If the estimator returns an error, a zero value fee rate will be + // returned. We will log the error and return the fall back fee rate + // instead. if err != nil { - return 0, err + log.Errorf("unable to query estimator: %v", err) } // If the result is too low, then we'll clamp it to our current fee @@ -672,31 +688,76 @@ func (w *WebAPIEstimator) randomFeeUpdateTimeout() time.Duration { return time.Duration(prand.Int63n(upper-lower) + lower) } -// getCachedFee takes in a target for the number of blocks until an initial -// confirmation and returns an estimated fee (if one was returned by the API). If -// the fee was not previously cached, we cache it here. +// getCachedFee takes a conf target and returns the cached fee rate. When the +// fee rate cannot be found, it will search the cache by decrementing the conf +// target until a fee rate is found. If still not found, it will return the fee +// rate of the minimum conf target cached, in other words, the most expensive +// fee rate it knows of. func (w *WebAPIEstimator) getCachedFee(numBlocks uint32) (uint32, error) { w.feesMtx.Lock() defer w.feesMtx.Unlock() - // Search our cached fees for the desired block target. If the target is - // not cached, then attempt to extrapolate it from the next lowest target - // that *is* cached. If we successfully extrapolate, then cache the - // target's fee. + // If the cache is empty, return an error. + if len(w.feeByBlockTarget) == 0 { + return 0, fmt.Errorf("web API error: %w", errEmptyCache) + } + + // Search the conf target from the cache. We expect a query to the web + // API has been made and the result has been cached at this point. + fee, ok := w.feeByBlockTarget[numBlocks] + + // If the conf target can be found, exit early. + if ok { + return fee, nil + } + + // The conf target cannot be found. We will first search the cache + // using a lower conf target. This is a conservative approach as the + // fee rate returned will be larger than what's requested. for target := numBlocks; target >= minBlockTarget; target-- { fee, ok := w.feeByBlockTarget[target] if !ok { continue } - _, ok = w.feeByBlockTarget[numBlocks] - if !ok { - w.feeByBlockTarget[numBlocks] = fee - } + log.Warnf("Web API does not have a fee rate for target=%d, "+ + "using the fee rate for target=%d instead", + numBlocks, target) + + // Return the fee rate found, which will be more expensive than + // requested. We will not cache the fee rate here in the hope + // that the web API will later populate this value. return fee, nil } - return 0, fmt.Errorf("web API does not include a fee estimation for "+ - "block target of %v", numBlocks) + + // There are no lower conf targets cached, which is likely when the + // requested conf target is 1. We will search the cache using a higher + // conf target, which gives a fee rate that's cheaper than requested. + // + // NOTE: we can only get here iff the requested conf target is smaller + // than the minimum conf target cached, so we return the minimum conf + // target from the cache. + minTargetCached := uint32(math.MaxUint32) + for target := range w.feeByBlockTarget { + if target < minTargetCached { + minTargetCached = target + } + } + + fee, ok = w.feeByBlockTarget[minTargetCached] + if !ok { + // We should never get here, just a vanity check. + return 0, fmt.Errorf("web API error: %w, conf target: %d", + errNoFeeRateFound, numBlocks) + } + + // Log an error instead of a warning as a cheaper fee rate may delay + // the confirmation for some important transactions. + log.Errorf("Web API does not have a fee rate for target=%d, "+ + "using the fee rate for target=%d instead", + numBlocks, minTargetCached) + + return fee, nil } // updateFeeEstimates re-queries the API for fresh fees and caches them. diff --git a/lnwallet/chainfee/estimator_test.go b/lnwallet/chainfee/estimator_test.go index 2900ff174c..d898030c27 100644 --- a/lnwallet/chainfee/estimator_test.go +++ b/lnwallet/chainfee/estimator_test.go @@ -8,7 +8,8 @@ import ( "strings" "testing" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" + "github.com/stretchr/testify/require" ) type mockSparseConfFeeSource struct { @@ -163,8 +164,9 @@ func TestSparseConfFeeSource(t *testing.T) { // as expected. func TestWebAPIFeeEstimator(t *testing.T) { t.Parallel() - feeFloor := uint32(FeePerKwFloor.FeePerKVByte()) + testFeeRate := feeFloor * 100 + testCases := []struct { name string target uint32 @@ -172,11 +174,41 @@ func TestWebAPIFeeEstimator(t *testing.T) { est uint32 err string }{ - {"target_below_min", 0, 12345, 12345, "too low, minimum"}, - {"target_w_too-low_fee", 10, 42, feeFloor, ""}, - {"API-omitted_target", 2, 0, 0, "web API does not include"}, - {"valid_target", 20, 54321, 54321, ""}, - {"valid_target_extrapolated_fee", 25, 0, 54321, ""}, + { + name: "target_below_min", + target: 0, + apiEst: 0, + est: 0, + err: "too low, minimum", + }, + { + name: "target_w_too-low_fee", + target: 100, + apiEst: 42, + est: feeFloor, + err: "", + }, + { + name: "API-omitted_target", + target: 2, + apiEst: 0, + est: testFeeRate, + err: "", + }, + { + name: "valid_target", + target: 20, + apiEst: testFeeRate, + est: testFeeRate, + err: "", + }, + { + name: "valid_target_extrapolated_fee", + target: 25, + apiEst: 0, + est: testFeeRate, + err: "", + }, } // Construct mock fee source for the Estimator to pull fees from. @@ -195,12 +227,10 @@ func TestWebAPIFeeEstimator(t *testing.T) { estimator := NewWebAPIEstimator(feeSource, false) // Test that requesting a fee when no fees have been cached fails. - _, err := estimator.EstimateFeePerKW(5) - if err == nil || - !strings.Contains(err.Error(), "web API does not include") { - - t.Fatalf("expected fee estimation to fail, instead got: %v", err) - } + feeRate, err := estimator.EstimateFeePerKW(5) + require.NoErrorf(t, err, "expected no error") + require.Equalf(t, FeePerKwFloor, feeRate, "expected fee rate floor "+ + "returned when no cached fee rate found") if err := estimator.Start(); err != nil { t.Fatalf("unable to start fee estimator, got: %v", err) @@ -233,3 +263,62 @@ func TestWebAPIFeeEstimator(t *testing.T) { }) } } + +// TestGetCachedFee checks that the fee caching logic works as expected. +func TestGetCachedFee(t *testing.T) { + target := uint32(2) + fee := uint32(100) + + // Create a dummy estimator without WebAPIFeeSource. + estimator := NewWebAPIEstimator(nil, false) + + // When the cache is empty, an error should be returned. + cachedFee, err := estimator.getCachedFee(target) + require.Zero(t, cachedFee) + require.ErrorIs(t, err, errEmptyCache) + + // Store a fee rate inside the cache. + estimator.feeByBlockTarget[target] = fee + + testCases := []struct { + name string + confTarget uint32 + expectedFee uint32 + expectErr error + }{ + { + // When the target is cached, return it. + name: "return cached fee", + confTarget: target, + expectedFee: fee, + expectErr: nil, + }, + { + // When the target is not cached, return the next + // lowest target that's cached. + name: "return next cached fee", + confTarget: target + 1, + expectedFee: fee, + expectErr: nil, + }, + { + // When the target is not cached, and the next lowest + // target is not cached, return the nearest fee rate. + name: "return highest cached fee", + confTarget: target - 1, + expectedFee: fee, + expectErr: nil, + }, + } + + for _, tc := range testCases { + tc := tc + + t.Run(tc.name, func(t *testing.T) { + cachedFee, err := estimator.getCachedFee(tc.confTarget) + + require.Equal(t, tc.expectedFee, cachedFee) + require.ErrorIs(t, err, tc.expectErr) + }) + } +} diff --git a/lnwallet/chainfee/minfeemanager.go b/lnwallet/chainfee/minfeemanager.go index 1f377d2cfc..7eaef3e8b5 100644 --- a/lnwallet/chainfee/minfeemanager.go +++ b/lnwallet/chainfee/minfeemanager.go @@ -32,6 +32,12 @@ func newMinFeeManager(minUpdateInterval time.Duration, return nil, err } + // Ensure that the minimum fee we use is always clamped by our fee + // floor. + if minFee < FeePerKwFloor { + minFee = FeePerKwFloor + } + return &minFeeManager{ minFeePerKW: minFee, lastUpdatedTime: time.Now(), diff --git a/lnwallet/chainfee/minfeemanager_test.go b/lnwallet/chainfee/minfeemanager_test.go index 0209f1a299..b498e7962e 100644 --- a/lnwallet/chainfee/minfeemanager_test.go +++ b/lnwallet/chainfee/minfeemanager_test.go @@ -23,8 +23,10 @@ func (m *mockChainBackend) fetchFee() (SatPerKWeight, error) { func TestMinFeeManager(t *testing.T) { t.Parallel() + // Initialize the mock backend and let it have a minimum fee rate + // below our fee floor. chainBackend := &mockChainBackend{ - minFee: SatPerKWeight(1000), + minFee: FeePerKwFloor - 1, } // Initialise the min fee manager. This should call the chain backend @@ -36,11 +38,14 @@ func TestMinFeeManager(t *testing.T) { require.NoError(t, err) require.Equal(t, 1, chainBackend.callCount) + // Check that the minimum fee rate is clamped by our fee floor. + require.Equal(t, feeManager.minFeePerKW, FeePerKwFloor) + // If the fee is requested again, the stored fee should be returned // and the chain backend should not be queried. chainBackend.minFee = SatPerKWeight(2000) minFee := feeManager.fetchMinFee() - require.Equal(t, minFee, SatPerKWeight(1000)) + require.Equal(t, minFee, FeePerKwFloor) require.Equal(t, 1, chainBackend.callCount) // Fake the passing of time. diff --git a/lnwallet/chainfee/rates.go b/lnwallet/chainfee/rates.go index 69c458a4a0..f9b606f749 100644 --- a/lnwallet/chainfee/rates.go +++ b/lnwallet/chainfee/rates.go @@ -4,7 +4,7 @@ import ( "fmt" "github.com/btcsuite/btcd/blockchain" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) const ( diff --git a/lnwallet/chancloser/chancloser.go b/lnwallet/chancloser/chancloser.go index 2eaa832885..7ece30a4a4 100644 --- a/lnwallet/chancloser/chancloser.go +++ b/lnwallet/chancloser/chancloser.go @@ -4,10 +4,10 @@ import ( "bytes" "fmt" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/labels" @@ -334,7 +334,6 @@ func (c *ChanCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, bool, error) { switch c.state { - // If we're in the close idle state, and we're receiving a channel closure // related message, then this indicates that we're on the receiving side of // an initiated channel closure. @@ -484,6 +483,10 @@ func (c *ChanCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, feeProposal := calcCompromiseFee(c.chanPoint, c.idealFeeSat, c.lastFeeProposal, remoteProposedFee, ) + if feeProposal > c.idealFeeSat*3 { + return nil, false, fmt.Errorf("couldn't find" + + " compromise fee") + } // With our new fee proposal calculated, we'll craft a new close // signed signature to send to the other party so we can continue @@ -662,7 +665,6 @@ func calcCompromiseFee(chanPoint wire.OutPoint, ourIdealFee, lastSentFee, // Otherwise, we'll need to attempt to make a fee compromise if this is the // second round, and neither side has agreed on fees. switch { - // If their proposed fee is identical to our ideal fee, then we'll go with // that as we can short circuit the fee negotiation. Similarly, if we // haven't sent an offer yet, we'll default to our ideal fee. diff --git a/lnwallet/chanfunding/assembler.go b/lnwallet/chanfunding/assembler.go index 4fcb867698..4e6e62d259 100644 --- a/lnwallet/chanfunding/assembler.go +++ b/lnwallet/chanfunding/assembler.go @@ -1,8 +1,8 @@ package chanfunding import ( + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/lnwallet/chainfee" ) diff --git a/lnwallet/chanfunding/canned_assembler.go b/lnwallet/chanfunding/canned_assembler.go index 81b426bae0..603d90fe27 100644 --- a/lnwallet/chanfunding/canned_assembler.go +++ b/lnwallet/chanfunding/canned_assembler.go @@ -3,9 +3,9 @@ package chanfunding import ( "fmt" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" ) diff --git a/lnwallet/chanfunding/coin_select.go b/lnwallet/chanfunding/coin_select.go index e73a11b8d0..761f7c0325 100644 --- a/lnwallet/chanfunding/coin_select.go +++ b/lnwallet/chanfunding/coin_select.go @@ -3,9 +3,9 @@ package chanfunding import ( "fmt" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnwallet/chainfee" ) @@ -73,13 +73,17 @@ func calculateFees(utxos []Coin, feeRate chainfee.SatPerKWeight) (btcutil.Amount var weightEstimate input.TxWeightEstimator for _, utxo := range utxos { switch { - case txscript.IsPayToWitnessPubKeyHash(utxo.PkScript): weightEstimate.AddP2WKHInput() case txscript.IsPayToScriptHash(utxo.PkScript): weightEstimate.AddNestedP2WKHInput() + case txscript.IsPayToTaproot(utxo.PkScript): + weightEstimate.AddTaprootKeySpendInput( + txscript.SigHashDefault, + ) + default: return 0, 0, &errUnsupportedInput{utxo.PkScript} } @@ -151,7 +155,6 @@ func CoinSelect(feeRate chainfee.SatPerKWeight, amt, dustLimit btcutil.Amount, var changeAmt btcutil.Amount switch { - // If the excess amount isn't enough to pay for fees based on // fee rate and estimated size without using a change output, // then increase the requested coin amount by the estimate @@ -171,7 +174,6 @@ func CoinSelect(feeRate chainfee.SatPerKWeight, amt, dustLimit btcutil.Amount, // change output. default: changeAmt = 0 - } if changeAmt < dustLimit { diff --git a/lnwallet/chanfunding/coin_select_test.go b/lnwallet/chanfunding/coin_select_test.go index d6cbd5ec7b..0f3d4ef4bc 100644 --- a/lnwallet/chanfunding/coin_select_test.go +++ b/lnwallet/chanfunding/coin_select_test.go @@ -5,8 +5,8 @@ import ( "regexp" "testing" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/stretchr/testify/require" diff --git a/lnwallet/chanfunding/psbt_assembler.go b/lnwallet/chanfunding/psbt_assembler.go index 945359c2d8..8632b175d9 100644 --- a/lnwallet/chanfunding/psbt_assembler.go +++ b/lnwallet/chanfunding/psbt_assembler.go @@ -6,12 +6,12 @@ import ( "fmt" "sync" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/psbt" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" - "github.com/btcsuite/btcutil/psbt" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" ) @@ -428,7 +428,6 @@ func (i *PsbtIntent) Inputs() []wire.OutPoint { var inputs []wire.OutPoint switch i.State { - // We return the inputs to the pending psbt. case PsbtVerified: for _, in := range i.PendingPsbt.UnsignedTx.TxIn { @@ -453,7 +452,6 @@ func (i *PsbtIntent) Inputs() []wire.OutPoint { // know about. These are only known after the PSBT has been verified. func (i *PsbtIntent) Outputs() []*wire.TxOut { switch i.State { - // We return the outputs of the pending psbt. case PsbtVerified: return i.PendingPsbt.UnsignedTx.TxOut diff --git a/lnwallet/chanfunding/psbt_assembler_test.go b/lnwallet/chanfunding/psbt_assembler_test.go index 1b6b2d9036..d646bd4a2b 100644 --- a/lnwallet/chanfunding/psbt_assembler_test.go +++ b/lnwallet/chanfunding/psbt_assembler_test.go @@ -10,12 +10,12 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/psbt" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" - "github.com/btcsuite/btcutil/psbt" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" @@ -54,8 +54,8 @@ func TestPsbtIntent(t *testing.T) { // negotiating with the remote peer and they accept. By accepting, they // send over their multisig key that's going to be used for the funding // output. With that known, we can start crafting a PSBT. - _, localPubkey := btcec.PrivKeyFromBytes(btcec.S256(), localPrivkey) - _, remotePubkey := btcec.PrivKeyFromBytes(btcec.S256(), remotePrivkey) + _, localPubkey := btcec.PrivKeyFromBytes(localPrivkey) + _, remotePubkey := btcec.PrivKeyFromBytes(remotePrivkey) psbtIntent.BindKeys( &keychain.KeyDescriptor{PubKey: localPubkey}, remotePubkey, ) @@ -197,8 +197,8 @@ func TestPsbtIntentBasePsbt(t *testing.T) { // Generate the funding multisig keys and the address so we can compare // it to the output of the intent. - _, localPubkey := btcec.PrivKeyFromBytes(btcec.S256(), localPrivkey) - _, remotePubkey := btcec.PrivKeyFromBytes(btcec.S256(), remotePrivkey) + _, localPubkey := btcec.PrivKeyFromBytes(localPrivkey) + _, remotePubkey := btcec.PrivKeyFromBytes(remotePrivkey) // Make sure the output script address is correct. script, _, err := input.GenFundingPkScript( localPubkey.SerializeCompressed(), @@ -474,8 +474,8 @@ func TestPsbtVerify(t *testing.T) { psbtIntent := intent.(*PsbtIntent) // Bind our test keys to get the funding parameters. - _, localPubkey := btcec.PrivKeyFromBytes(btcec.S256(), localPrivkey) - _, remotePubkey := btcec.PrivKeyFromBytes(btcec.S256(), remotePrivkey) + _, localPubkey := btcec.PrivKeyFromBytes(localPrivkey) + _, remotePubkey := btcec.PrivKeyFromBytes(remotePrivkey) psbtIntent.BindKeys( &keychain.KeyDescriptor{PubKey: localPubkey}, remotePubkey, ) @@ -642,8 +642,8 @@ func TestPsbtFinalize(t *testing.T) { psbtIntent := intent.(*PsbtIntent) // Bind our test keys to get the funding parameters. - _, localPubkey := btcec.PrivKeyFromBytes(btcec.S256(), localPrivkey) - _, remotePubkey := btcec.PrivKeyFromBytes(btcec.S256(), remotePrivkey) + _, localPubkey := btcec.PrivKeyFromBytes(localPrivkey) + _, remotePubkey := btcec.PrivKeyFromBytes(remotePrivkey) psbtIntent.BindKeys( &keychain.KeyDescriptor{PubKey: localPubkey}, remotePubkey, ) diff --git a/lnwallet/chanfunding/wallet_assembler.go b/lnwallet/chanfunding/wallet_assembler.go index c8c424a227..376360901b 100644 --- a/lnwallet/chanfunding/wallet_assembler.go +++ b/lnwallet/chanfunding/wallet_assembler.go @@ -4,11 +4,11 @@ import ( "fmt" "math" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/txsort" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" - "github.com/btcsuite/btcutil/txsort" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" ) @@ -107,9 +107,14 @@ func (f *FullIntent) CompileFundingTx(extraInputs []*wire.TxIn, // Next, sign all inputs that are ours, collecting the signatures in // order of the inputs. + prevOutFetcher := NewSegWitV0DualFundingPrevOutputFetcher( + f.coinSource, extraInputs, + ) signDesc := input.SignDescriptor{ - HashType: txscript.SigHashAll, - SigHashes: txscript.NewTxSigHashes(fundingTx), + SigHashes: txscript.NewTxSigHashes( + fundingTx, prevOutFetcher, + ), + PrevOutputFetcher: prevOutFetcher, } for i, txIn := range fundingTx.TxIn { // We can only sign this input if it's ours, so we'll ask the @@ -130,6 +135,13 @@ func (f *FullIntent) CompileFundingTx(extraInputs []*wire.TxIn, } signDesc.InputIndex = i + // We support spending a p2tr input ourselves. But not as part + // of their inputs. + signDesc.HashType = txscript.SigHashAll + if txscript.IsPayToTaproot(info.PkScript) { + signDesc.HashType = txscript.SigHashDefault + } + // Finally, we'll sign the input as is, and populate the input // with the witness and sigScript (if needed). inputScript, err := f.signer.ComputeInputScript( @@ -374,3 +386,57 @@ func (w *WalletAssembler) FundingTxAvailable() {} // A compile-time assertion to ensure the WalletAssembler meets the // FundingTxAssembler interface. var _ FundingTxAssembler = (*WalletAssembler)(nil) + +// SegWitV0DualFundingPrevOutputFetcher is a txscript.PrevOutputFetcher that +// knows about local and remote funding inputs. +// +// TODO(guggero): Support dual funding with p2tr inputs, currently only segwit +// v0 inputs are supported. +type SegWitV0DualFundingPrevOutputFetcher struct { + local CoinSource + remote *txscript.MultiPrevOutFetcher +} + +var _ txscript.PrevOutputFetcher = (*SegWitV0DualFundingPrevOutputFetcher)(nil) + +// NewSegWitV0DualFundingPrevOutputFetcher creates a new +// txscript.PrevOutputFetcher from the given local and remote inputs. +// +// NOTE: Since the actual pkScript and amounts aren't passed in, this will just +// make sure that nothing will panic when creating a SegWit v0 sighash. But this +// code will NOT WORK for transactions that spend any _remote_ Taproot inputs! +// So basically dual-funding won't work with Taproot inputs unless the UTXO info +// is exchanged between the peers. +func NewSegWitV0DualFundingPrevOutputFetcher(localSource CoinSource, + remoteInputs []*wire.TxIn) txscript.PrevOutputFetcher { + + remote := txscript.NewMultiPrevOutFetcher(nil) + for _, inp := range remoteInputs { + // We add an empty output to prevent the sighash calculation + // from panicking. But this will always detect the inputs as + // SegWig v0! + remote.AddPrevOut(inp.PreviousOutPoint, &wire.TxOut{}) + } + return &SegWitV0DualFundingPrevOutputFetcher{ + local: localSource, + remote: remote, + } +} + +// FetchPrevOutput attempts to fetch the previous output referenced by the +// passed outpoint. +// +// NOTE: This is a part of the txscript.PrevOutputFetcher interface. +func (d *SegWitV0DualFundingPrevOutputFetcher) FetchPrevOutput( + op wire.OutPoint) *wire.TxOut { + + // Try the local source first. This will return nil if our internal + // wallet doesn't know the outpoint. + coin, err := d.local.CoinFromOutPoint(op) + if err == nil && coin != nil { + return &coin.TxOut + } + + // Fall back to the remote + return d.remote.FetchPrevOutput(op) +} diff --git a/lnwallet/channel.go b/lnwallet/channel.go index 917f06b1d7..98789c053f 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -11,13 +11,14 @@ import ( "sync" "github.com/btcsuite/btcd/blockchain" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/txsort" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btclog" - "github.com/btcsuite/btcutil" - "github.com/btcsuite/btcutil/txsort" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/build" "github.com/lightningnetwork/lnd/chainntnfs" @@ -95,6 +96,17 @@ var ( // both parties can retrieve their funds. ErrCommitSyncRemoteDataLoss = fmt.Errorf("possible remote commitment " + "state data loss") + + // ErrNoRevocationLogFound is returned when both the returned logs are + // nil from querying the revocation log bucket. In theory this should + // never happen as the query will return `ErrLogEntryNotFound`, yet + // we'd still perform a sanity check to make sure at least one of the + // logs is non-nil. + ErrNoRevocationLogFound = errors.New("no revocation log found") + + // ErrOutputIndexOutOfRange is returned when an output index is greater + // than or equal to the length of a given transaction's outputs. + ErrOutputIndexOutOfRange = errors.New("output index is out of range") ) // ErrCommitSyncLocalDataLoss is returned in the case that we receive a valid @@ -303,7 +315,7 @@ type PaymentDescriptor struct { // local node. This signature is generated by the remote node and // stored by the local node in the case that local node needs to // broadcast their commitment transaction. - sig *btcec.Signature + sig *ecdsa.Signature // addCommitHeight[Remote|Local] encodes the height of the commitment // which included this HTLC on either the remote or local commitment @@ -1035,26 +1047,29 @@ type updateLog struct { logIndex uint64 // htlcCounter is a monotonically increasing integer that tracks the - // total number of offered HTLC's by the owner of this update log. We - // use a distinct index for this purpose, as update's that remove - // entries from the log will be indexed using this counter. + // total number of offered HTLC's by the owner of this update log, + // hence the `Add` update type. We use a distinct index for this + // purpose, as update's that remove entries from the log will be + // indexed using this counter. htlcCounter uint64 // List is the updatelog itself, we embed this value so updateLog has // access to all the method of a list.List. *list.List - // updateIndex is an index that maps a particular entries index to the - // list element within the list.List above. + // updateIndex maps a `logIndex` to a particular update entry. It + // deals with the four update types: + // `Fail|MalformedFail|Settle|FeeUpdate` updateIndex map[uint64]*list.Element - // offerIndex is an index that maps the counter for offered HTLC's to - // their list element within the main list.List. + // htlcIndex maps a `htlcCounter` to an offered HTLC entry, hence the + // `Add` update. htlcIndex map[uint64]*list.Element // modifiedHtlcs is a set that keeps track of all the current modified - // htlcs. A modified HTLC is one that's present in the log, and has as - // a pending fail or settle that's attempting to consume it. + // htlcs, hence update types `Fail|MalformedFail|Settle`. A modified + // HTLC is one that's present in the log, and has as a pending fail or + // settle that's attempting to consume it. modifiedHtlcs map[uint64]struct{} } @@ -1541,7 +1556,6 @@ func (lc *LightningChannel) localLogUpdateToPayDesc(logUpdate *channeldb.LogUpda // Since Add updates aren't saved to disk under this key, the update will // never be an Add. switch wireMsg := logUpdate.UpdateMsg.(type) { - // For HTLCs that we settled, we'll fetch the original offered HTLC from // the remote update log so we can retrieve the same PaymentDescriptor that // ReceiveHTLCSettle would produce. @@ -1621,7 +1635,6 @@ func (lc *LightningChannel) remoteLogUpdateToPayDesc(logUpdate *channeldb.LogUpd error) { switch wireMsg := logUpdate.UpdateMsg.(type) { - case *lnwire.UpdateAddHTLC: pd := &PaymentDescriptor{ RHash: wireMsg.PaymentHash, @@ -1744,7 +1757,7 @@ func (lc *LightningChannel) restoreCommitState( } lc.localCommitChain.addCommitment(localCommit) - lc.log.Debugf("starting local commitment: %v", + lc.log.Tracef("starting local commitment: %v", newLogClosure(func() string { return spew.Sdump(lc.localCommitChain.tail()) }), @@ -1760,7 +1773,7 @@ func (lc *LightningChannel) restoreCommitState( } lc.remoteCommitChain.addCommitment(remoteCommit) - lc.log.Debugf("starting remote commitment: %v", + lc.log.Tracef("starting remote commitment: %v", newLogClosure(func() string { return spew.Sdump(lc.remoteCommitChain.tail()) }), @@ -1884,7 +1897,6 @@ func (lc *LightningChannel) restoreStateLogs( // To ensure proper channel operation, we restore the add's addCommitHeightLocal // field to the height of our local commitment. for _, logUpdate := range unsignedAckedUpdates { - var htlcIdx uint64 switch wireMsg := logUpdate.UpdateMsg.(type) { case *lnwire.UpdateFulfillHTLC: @@ -1912,7 +1924,6 @@ func (lc *LightningChannel) restoreStateLogs( // To ensure proper channel operation, we restore the add's addCommitHeightRemote // field to the height of the remote commitment. for _, logUpdate := range remoteUnsignedLocalUpdates { - var htlcIdx uint64 switch wireMsg := logUpdate.UpdateMsg.(type) { case *lnwire.UpdateFulfillHTLC: @@ -2024,7 +2035,7 @@ func (lc *LightningChannel) restorePendingRemoteUpdates( logIdx) } - // We previously restored Adds along with all the other upates, + // We previously restored Adds along with all the other updates, // but this Add restoration was a no-op as every single one of // these Adds was already restored since they're all incoming // htlcs on the local commitment. @@ -2143,7 +2154,7 @@ func (lc *LightningChannel) restorePendingLocalUpdates( } // At this point the restored update's logIndex must be equal - // to the update log, otherwise somthing is horribly wrong. + // to the update log, otherwise something is horribly wrong. if payDesc.LogIndex != lc.localUpdateLog.logIndex { panic(fmt.Sprintf("log index mismatch: "+ "%v vs %v", payDesc.LogIndex, @@ -2213,10 +2224,10 @@ type HtlcRetribution struct { // transaction. The BreachRetribution is then sent over the ContractBreach // channel in order to allow the subscriber of the channel to dispatch justice. type BreachRetribution struct { - // BreachTransaction is the transaction which breached the channel + // BreachTxHash is the transaction hash which breached the channel // contract by spending from the funding multi-sig with a revoked // commitment transaction. - BreachTransaction *wire.MsgTx + BreachTxHash chainhash.Hash // BreachHeight records the block height confirming the breach // transaction, used as a height hint when registering for @@ -2231,13 +2242,9 @@ type BreachRetribution struct { // RevokedStateNum is the revoked state number which was broadcast. RevokedStateNum uint64 - // PendingHTLCs is a slice of the HTLCs which were pending at this - // point within the channel's history transcript. - PendingHTLCs []channeldb.HTLC - // LocalOutputSignDesc is a SignDescriptor which is capable of // generating the signature necessary to sweep the output within the - // BreachTransaction that pays directly us. + // breach transaction that pays directly us. // // NOTE: A nil value indicates that the local output is considered dust // according to the remote party's dust limit. @@ -2282,16 +2289,22 @@ type BreachRetribution struct { // passed channel, at a particular revoked state number, and one which targets // the passed commitment transaction. func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, - breachHeight uint32) (*BreachRetribution, error) { + breachHeight uint32, spendTx *wire.MsgTx) (*BreachRetribution, error) { // Query the on-disk revocation log for the snapshot which was recorded - // at this particular state num. - revokedSnapshot, err := chanState.FindPreviousState(stateNum) + // at this particular state num. Based on whether a legacy revocation + // log is returned or not, we will process them differently. + revokedLog, revokedLogLegacy, err := chanState.FindPreviousState( + stateNum, + ) if err != nil { return nil, err } - commitHash := revokedSnapshot.CommitTx.TxHash() + // Sanity check that at least one of the logs is returned. + if revokedLog == nil && revokedLogLegacy == nil { + return nil, ErrNoRevocationLogFound + } // With the state number broadcast known, we can now derive/restore the // proper revocation preimage necessary to sweep the remote party's @@ -2301,7 +2314,7 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, return nil, err } commitmentSecret, commitmentPoint := btcec.PrivKeyFromBytes( - btcec.S256(), revocationPreimage[:], + revocationPreimage[:], ) // With the commitment point generated, we can now generate the four @@ -2314,22 +2327,14 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, // Next, reconstruct the scripts as they were present at this state // number so we can have the proper witness script to sign and include // within the final witness. - theirDelay := uint32(chanState.RemoteChanCfg.CsvDelay) - isRemoteInitiator := !chanState.IsInitiator var leaseExpiry uint32 if chanState.ChanType.HasLeaseExpiration() { leaseExpiry = chanState.ThawHeight } - theirScript, err := CommitScriptToSelf( - chanState.ChanType, isRemoteInitiator, keyRing.ToLocalKey, - keyRing.RevocationKey, theirDelay, leaseExpiry, - ) - if err != nil { - return nil, err - } - // Since it is the remote breach we are reconstructing, the output going - // to us will be a to-remote script with our local params. + // Since it is the remote breach we are reconstructing, the output + // going to us will be a to-remote script with our local params. + isRemoteInitiator := !chanState.IsInitiator ourScript, ourDelay, err := CommitScriptToRemote( chanState.ChanType, isRemoteInitiator, keyRing.ToRemoteKey, leaseExpiry, @@ -2338,45 +2343,63 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, return nil, err } - // In order to fully populate the breach retribution struct, we'll need - // to find the exact index of the commitment outputs. - ourOutpoint := wire.OutPoint{ - Hash: commitHash, - } - theirOutpoint := wire.OutPoint{ - Hash: commitHash, + theirDelay := uint32(chanState.RemoteChanCfg.CsvDelay) + theirScript, err := CommitScriptToSelf( + chanState.ChanType, isRemoteInitiator, keyRing.ToLocalKey, + keyRing.RevocationKey, theirDelay, leaseExpiry, + ) + if err != nil { + return nil, err } - for i, txOut := range revokedSnapshot.CommitTx.TxOut { - switch { - case bytes.Equal(txOut.PkScript, ourScript.PkScript): - ourOutpoint.Index = uint32(i) - case bytes.Equal(txOut.PkScript, theirScript.PkScript): - theirOutpoint.Index = uint32(i) + + // Define an empty breach retribution that will be overwritten based on + // different version of the revocation log found. + var br *BreachRetribution + + // Define our and their amounts, that will be overwritten below. + var ourAmt, theirAmt int64 + + // If the returned *RevocationLog is non-nil, use it to derive the info + // we need. + if revokedLog != nil { + br, ourAmt, theirAmt, err = createBreachRetribution( + revokedLog, spendTx, chanState, keyRing, + commitmentSecret, leaseExpiry, + ) + if err != nil { + return nil, err + } + } else { + // The returned revocation log is in legacy format, which is a + // *ChannelCommitment. + // + // NOTE: this branch is kept for compatibility such that for + // old nodes which refuse to migrate the legacy revocation log + // data can still function. This branch can be deleted once we + // are confident that no legacy format is in use. + br, ourAmt, theirAmt, err = createBreachRetributionLegacy( + revokedLogLegacy, chanState, keyRing, commitmentSecret, + ourScript, theirScript, leaseExpiry, + ) + if err != nil { + return nil, err } } // Conditionally instantiate a sign descriptor for each of the // commitment outputs. If either is considered dust using the remote // party's dust limit, the respective sign descriptor will be nil. - var ( - ourSignDesc *input.SignDescriptor - theirSignDesc *input.SignDescriptor - ) - - // Compute the balances in satoshis. - ourAmt := revokedSnapshot.LocalBalance.ToSatoshis() - theirAmt := revokedSnapshot.RemoteBalance.ToSatoshis() - + // // If our balance exceeds the remote party's dust limit, instantiate // the sign descriptor for our output. - if ourAmt >= chanState.RemoteChanCfg.DustLimit { - ourSignDesc = &input.SignDescriptor{ + if ourAmt >= int64(chanState.RemoteChanCfg.DustLimit) { + br.LocalOutputSignDesc = &input.SignDescriptor{ SingleTweak: keyRing.LocalCommitKeyTweak, KeyDesc: chanState.LocalChanCfg.PaymentBasePoint, WitnessScript: ourScript.WitnessScript, Output: &wire.TxOut{ PkScript: ourScript.PkScript, - Value: int64(ourAmt), + Value: ourAmt, }, HashType: txscript.SigHashAll, } @@ -2384,98 +2407,238 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, // Similarly, if their balance exceeds the remote party's dust limit, // assemble the sign descriptor for their output, which we can sweep. - if theirAmt >= chanState.RemoteChanCfg.DustLimit { - theirSignDesc = &input.SignDescriptor{ - KeyDesc: chanState.LocalChanCfg.RevocationBasePoint, + if theirAmt >= int64(chanState.RemoteChanCfg.DustLimit) { + br.RemoteOutputSignDesc = &input.SignDescriptor{ + KeyDesc: chanState.LocalChanCfg. + RevocationBasePoint, DoubleTweak: commitmentSecret, WitnessScript: theirScript.WitnessScript, Output: &wire.TxOut{ PkScript: theirScript.PkScript, - Value: int64(theirAmt), + Value: theirAmt, + }, + HashType: txscript.SigHashAll, + } + } + + // Finally, with all the necessary data constructed, we can pad the + // BreachRetribution struct which houses all the data necessary to + // swiftly bring justice to the cheating remote party. + br.BreachHeight = breachHeight + br.RevokedStateNum = stateNum + br.LocalDelay = ourDelay + br.RemoteDelay = theirDelay + + return br, nil +} + +// createHtlcRetribution is a helper function to construct an HtlcRetribution +// based on the passed params. +func createHtlcRetribution(chanState *channeldb.OpenChannel, + keyRing *CommitmentKeyRing, commitHash chainhash.Hash, + commitmentSecret *btcec.PrivateKey, leaseExpiry uint32, + htlc *channeldb.HTLCEntry) (HtlcRetribution, error) { + + var emptyRetribution HtlcRetribution + + theirDelay := uint32(chanState.RemoteChanCfg.CsvDelay) + isRemoteInitiator := !chanState.IsInitiator + + // We'll generate the original second level witness script now, as + // we'll need it if we're revoking an HTLC output on the remote + // commitment transaction, and *they* go to the second level. + secondLevelScript, err := SecondLevelHtlcScript( + chanState.ChanType, isRemoteInitiator, + keyRing.RevocationKey, keyRing.ToLocalKey, theirDelay, + leaseExpiry, + ) + if err != nil { + return emptyRetribution, err + } + + // If this is an incoming HTLC, then this means that they were the + // sender of the HTLC (relative to us). So we'll re-generate the sender + // HTLC script. Otherwise, is this was an outgoing HTLC that we sent, + // then from the PoV of the remote commitment state, they're the + // receiver of this HTLC. + htlcPkScript, htlcWitnessScript, err := genHtlcScript( + chanState.ChanType, htlc.Incoming, false, + htlc.RefundTimeout, htlc.RHash, keyRing, + ) + if err != nil { + return emptyRetribution, err + } + + return HtlcRetribution{ + SignDesc: input.SignDescriptor{ + KeyDesc: chanState.LocalChanCfg. + RevocationBasePoint, + DoubleTweak: commitmentSecret, + WitnessScript: htlcWitnessScript, + Output: &wire.TxOut{ + PkScript: htlcPkScript, + Value: int64(htlc.Amt), }, HashType: txscript.SigHashAll, + }, + OutPoint: wire.OutPoint{ + Hash: commitHash, + Index: uint32(htlc.OutputIndex), + }, + SecondLevelWitnessScript: secondLevelScript.WitnessScript, + IsIncoming: htlc.Incoming, + }, nil +} + +// createBreachRetribution creates a partially initiated BreachRetribution +// using a RevocationLog. Returns the constructed retribution, our amount, +// their amount, and a possible non-nil error. +func createBreachRetribution(revokedLog *channeldb.RevocationLog, + spendTx *wire.MsgTx, chanState *channeldb.OpenChannel, + keyRing *CommitmentKeyRing, commitmentSecret *btcec.PrivateKey, + leaseExpiry uint32) (*BreachRetribution, int64, int64, error) { + + commitHash := revokedLog.CommitTxHash + + // Create the htlc retributions. + htlcRetributions := make([]HtlcRetribution, len(revokedLog.HTLCEntries)) + for i, htlc := range revokedLog.HTLCEntries { + hr, err := createHtlcRetribution( + chanState, keyRing, commitHash, + commitmentSecret, leaseExpiry, htlc, + ) + if err != nil { + return nil, 0, 0, err + } + htlcRetributions[i] = hr + } + + var ourAmt, theirAmt int64 + + // Construct the our outpoint. + ourOutpoint := wire.OutPoint{ + Hash: commitHash, + } + if revokedLog.OurOutputIndex != channeldb.OutputIndexEmpty { + ourOutpoint.Index = uint32(revokedLog.OurOutputIndex) + + // Sanity check that OurOutputIndex is within range. + if int(ourOutpoint.Index) >= len(spendTx.TxOut) { + return nil, 0, 0, fmt.Errorf("%w: ours=%v, "+ + "len(TxOut)=%v", ErrOutputIndexOutOfRange, + ourOutpoint.Index, len(spendTx.TxOut), + ) + } + // Read the amounts from the breach transaction. + // + // NOTE: ourAmt here includes commit fee and anchor amount(if + // enabled). + ourAmt = spendTx.TxOut[ourOutpoint.Index].Value + } + + // Construct the their outpoint. + theirOutpoint := wire.OutPoint{ + Hash: commitHash, + } + if revokedLog.TheirOutputIndex != channeldb.OutputIndexEmpty { + theirOutpoint.Index = uint32(revokedLog.TheirOutputIndex) + + // Sanity check that TheirOutputIndex is within range. + if int(revokedLog.TheirOutputIndex) >= len(spendTx.TxOut) { + return nil, 0, 0, fmt.Errorf("%w: theirs=%v, "+ + "len(TxOut)=%v", ErrOutputIndexOutOfRange, + revokedLog.TheirOutputIndex, len(spendTx.TxOut), + ) + } + + // Read the amounts from the breach transaction. + theirAmt = spendTx.TxOut[theirOutpoint.Index].Value + } + + return &BreachRetribution{ + BreachTxHash: commitHash, + ChainHash: chanState.ChainHash, + LocalOutpoint: ourOutpoint, + RemoteOutpoint: theirOutpoint, + HtlcRetributions: htlcRetributions, + KeyRing: keyRing, + }, ourAmt, theirAmt, nil +} + +// createBreachRetributionLegacy creates a partially initiated +// BreachRetribution using a ChannelCommitment. Returns the constructed +// retribution, our amount, their amount, and a possible non-nil error. +func createBreachRetributionLegacy(revokedLog *channeldb.ChannelCommitment, + chanState *channeldb.OpenChannel, keyRing *CommitmentKeyRing, + commitmentSecret *btcec.PrivateKey, + ourScript, theirScript *ScriptInfo, + leaseExpiry uint32) (*BreachRetribution, int64, int64, error) { + + commitHash := revokedLog.CommitTx.TxHash() + ourOutpoint := wire.OutPoint{ + Hash: commitHash, + } + theirOutpoint := wire.OutPoint{ + Hash: commitHash, + } + + // In order to fully populate the breach retribution struct, we'll need + // to find the exact index of the commitment outputs. + for i, txOut := range revokedLog.CommitTx.TxOut { + switch { + case bytes.Equal(txOut.PkScript, ourScript.PkScript): + ourOutpoint.Index = uint32(i) + case bytes.Equal(txOut.PkScript, theirScript.PkScript): + theirOutpoint.Index = uint32(i) } } // With the commitment outputs located, we'll now generate all the // retribution structs for each of the HTLC transactions active on the // remote commitment transaction. - htlcRetributions := make([]HtlcRetribution, 0, len(revokedSnapshot.Htlcs)) - for _, htlc := range revokedSnapshot.Htlcs { + htlcRetributions := make([]HtlcRetribution, len(revokedLog.Htlcs)) + for i, htlc := range revokedLog.Htlcs { // If the HTLC is dust, then we'll skip it as it doesn't have // an output on the commitment transaction. if HtlcIsDust( chanState.ChanType, htlc.Incoming, false, - chainfee.SatPerKWeight(revokedSnapshot.FeePerKw), - htlc.Amt.ToSatoshis(), chanState.RemoteChanCfg.DustLimit, + chainfee.SatPerKWeight(revokedLog.FeePerKw), + htlc.Amt.ToSatoshis(), + chanState.RemoteChanCfg.DustLimit, ) { + continue } - // We'll generate the original second level witness script now, - // as we'll need it if we're revoking an HTLC output on the - // remote commitment transaction, and *they* go to the second - // level. - secondLevelScript, err := SecondLevelHtlcScript( - chanState.ChanType, isRemoteInitiator, - keyRing.RevocationKey, keyRing.ToLocalKey, theirDelay, - leaseExpiry, - ) - if err != nil { - return nil, err + entry := &channeldb.HTLCEntry{ + RHash: htlc.RHash, + RefundTimeout: htlc.RefundTimeout, + OutputIndex: uint16(htlc.OutputIndex), + Incoming: htlc.Incoming, + Amt: htlc.Amt.ToSatoshis(), } - - // If this is an incoming HTLC, then this means that they were - // the sender of the HTLC (relative to us). So we'll - // re-generate the sender HTLC script. Otherwise, is this was - // an outgoing HTLC that we sent, then from the PoV of the - // remote commitment state, they're the receiver of this HTLC. - htlcPkScript, htlcWitnessScript, err := genHtlcScript( - chanState.ChanType, htlc.Incoming, false, - htlc.RefundTimeout, htlc.RHash, keyRing, + hr, err := createHtlcRetribution( + chanState, keyRing, commitHash, + commitmentSecret, leaseExpiry, entry, ) if err != nil { - return nil, err + return nil, 0, 0, err } - - htlcRetributions = append(htlcRetributions, HtlcRetribution{ - SignDesc: input.SignDescriptor{ - KeyDesc: chanState.LocalChanCfg.RevocationBasePoint, - DoubleTweak: commitmentSecret, - WitnessScript: htlcWitnessScript, - Output: &wire.TxOut{ - PkScript: htlcPkScript, - Value: int64(htlc.Amt.ToSatoshis()), - }, - HashType: txscript.SigHashAll, - }, - OutPoint: wire.OutPoint{ - Hash: commitHash, - Index: uint32(htlc.OutputIndex), - }, - SecondLevelWitnessScript: secondLevelScript.WitnessScript, - IsIncoming: htlc.Incoming, - }) + htlcRetributions[i] = hr } - // Finally, with all the necessary data constructed, we can create the - // BreachRetribution struct which houses all the data necessary to - // swiftly bring justice to the cheating remote party. + // Compute the balances in satoshis. + ourAmt := int64(revokedLog.LocalBalance.ToSatoshis()) + theirAmt := int64(revokedLog.RemoteBalance.ToSatoshis()) + return &BreachRetribution{ - ChainHash: chanState.ChainHash, - BreachTransaction: revokedSnapshot.CommitTx, - BreachHeight: breachHeight, - RevokedStateNum: stateNum, - PendingHTLCs: revokedSnapshot.Htlcs, - LocalOutpoint: ourOutpoint, - LocalOutputSignDesc: ourSignDesc, - LocalDelay: ourDelay, - RemoteOutpoint: theirOutpoint, - RemoteOutputSignDesc: theirSignDesc, - RemoteDelay: theirDelay, - HtlcRetributions: htlcRetributions, - KeyRing: keyRing, - }, nil + BreachTxHash: commitHash, + ChainHash: chanState.ChainHash, + LocalOutpoint: ourOutpoint, + RemoteOutpoint: theirOutpoint, + HtlcRetributions: htlcRetributions, + KeyRing: keyRing, + }, ourAmt, theirAmt, nil } // HtlcIsDust determines if an HTLC output is dust or not depending on two @@ -3004,6 +3167,7 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing, chanType, true, false, feePerKw, htlc.Amount.ToSatoshis(), dustLimit, ) { + continue } @@ -3046,7 +3210,7 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing, WitnessScript: htlc.theirWitnessScript, Output: txOut, HashType: sigHashType, - SigHashes: txscript.NewTxSigHashes(sigJob.Tx), + SigHashes: input.NewTxSigHashesV0Only(sigJob.Tx), InputIndex: 0, } sigJob.OutputIndex = htlc.remoteOutputIndex @@ -3058,6 +3222,7 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing, chanType, false, false, feePerKw, htlc.Amount.ToSatoshis(), dustLimit, ) { + continue } @@ -3099,7 +3264,7 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing, WitnessScript: htlc.theirWitnessScript, Output: txOut, HashType: sigHashType, - SigHashes: txscript.NewTxSigHashes(sigJob.Tx), + SigHashes: input.NewTxSigHashesV0Only(sigJob.Tx), InputIndex: 0, } sigJob.OutputIndex = htlc.remoteOutputIndex @@ -3460,7 +3625,7 @@ func (lc *LightningChannel) validateCommitmentSanity(theirLogCounter, } // Now that we know the total value of added HTLCs, we check - // that this satisfy the MaxPendingAmont contraint. + // that this satisfy the MaxPendingAmont constraint. if amtInFlight > constraints.MaxPendingAmount { return ErrMaxPendingAmount } @@ -3507,7 +3672,8 @@ func (lc *LightningChannel) validateCommitmentSanity(theirLogCounter, // any). The HTLC signatures are sorted according to the BIP 69 order of the // HTLC's on the commitment transaction. Finally, the new set of pending HTLCs // for the remote party's commitment are also returned. -func (lc *LightningChannel) SignNextCommitment() (lnwire.Sig, []lnwire.Sig, []channeldb.HTLC, error) { +func (lc *LightningChannel) SignNextCommitment() (lnwire.Sig, []lnwire.Sig, + []channeldb.HTLC, error) { lc.Lock() defer lc.Unlock() @@ -3530,8 +3696,10 @@ func (lc *LightningChannel) SignNextCommitment() (lnwire.Sig, []lnwire.Sig, []ch // party, then we're unable to create new states. Each time we create a // new state, we consume a prior revocation point. commitPoint := lc.channelState.RemoteNextRevocation - if lc.remoteCommitChain.hasUnackedCommitment() || commitPoint == nil { - + unacked := lc.remoteCommitChain.hasUnackedCommitment() + if unacked || commitPoint == nil { + lc.log.Tracef("waiting for remote ack=%v, nil "+ + "RemoteNextRevocation: %v", unacked, commitPoint == nil) return sig, htlcSigs, nil, ErrNoWindow } @@ -3608,7 +3776,7 @@ func (lc *LightningChannel) SignNextCommitment() (lnwire.Sig, []lnwire.Sig, []ch // While the jobs are being carried out, we'll Sign their version of // the new commitment transaction while we're waiting for the rest of // the HTLC signatures to be processed. - lc.signDesc.SigHashes = txscript.NewTxSigHashes(newCommitView.txn) + lc.signDesc.SigHashes = input.NewTxSigHashesV0Only(newCommitView.txn) rawSig, err := lc.Signer.SignOutputRaw(newCommitView.txn, lc.signDesc) if err != nil { close(cancelChan) @@ -3820,7 +3988,7 @@ func (lc *LightningChannel) ProcessChanSyncMsg( // but died before the signature was sent. We re-transmit our // revocation, but also initiate a state transition to re-sync // them. - if lc.OweCommitment(true) { + if lc.OweCommitment() { commitSig, htlcSigs, _, err := lc.SignNextCommitment() switch { @@ -4054,6 +4222,7 @@ func (lc *LightningChannel) computeView(view *htlcView, remoteChain bool, lc.channelState.ChanType, false, !remoteChain, feePerKw, htlc.Amount.ToSatoshis(), dustLimit, ) { + continue } @@ -4064,6 +4233,7 @@ func (lc *LightningChannel) computeView(view *htlcView, remoteChain bool, lc.channelState.ChanType, true, !remoteChain, feePerKw, htlc.Amount.ToSatoshis(), dustLimit, ) { + continue } @@ -4105,7 +4275,7 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment, var ( htlcIndex uint64 sigHash func() ([]byte, error) - sig *btcec.Signature + sig *ecdsa.Signature err error ) @@ -4139,7 +4309,7 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment, return nil, err } - hashCache := txscript.NewTxSigHashes(successTx) + hashCache := input.NewTxSigHashesV0Only(successTx) sigHash, err := txscript.CalcWitnessSigHash( htlc.ourWitnessScript, hashCache, sigHashType, successTx, 0, @@ -4194,7 +4364,7 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment, return nil, err } - hashCache := txscript.NewTxSigHashes(timeoutTx) + hashCache := input.NewTxSigHashesV0Only(timeoutTx) sigHash, err := txscript.CalcWitnessSigHash( htlc.ourWitnessScript, hashCache, sigHashType, timeoutTx, 0, @@ -4387,7 +4557,7 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSig lnwire.Sig, // this newly proposed state update. localCommitTx := localCommitmentView.txn multiSigScript := lc.signDesc.WitnessScript - hashCache := txscript.NewTxSigHashes(localCommitTx) + hashCache := input.NewTxSigHashesV0Only(localCommitTx) sigHash, err := txscript.CalcWitnessSigHash( multiSigScript, hashCache, txscript.SigHashAll, localCommitTx, 0, int64(lc.channelState.Capacity), @@ -4421,16 +4591,13 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSig lnwire.Sig, // While the HTLC verification jobs are proceeding asynchronously, // we'll ensure that the newly constructed commitment state has a valid // signature. - verifyKey := btcec.PublicKey{ - X: lc.channelState.RemoteChanCfg.MultiSigKey.PubKey.X, - Y: lc.channelState.RemoteChanCfg.MultiSigKey.PubKey.Y, - Curve: btcec.S256(), - } + verifyKey := lc.channelState.RemoteChanCfg.MultiSigKey.PubKey + cSig, err := commitSig.ToSignature() if err != nil { return err } - if !cSig.Verify(sigHash, &verifyKey) { + if !cSig.Verify(sigHash, verifyKey) { close(cancelChan) // If we fail to validate their commitment signature, we'll @@ -4535,11 +4702,11 @@ func (lc *LightningChannel) IsChannelClean() bool { // out a commitment signature because there are outstanding local updates and/or // updates in the local commit tx that aren't reflected in the remote commit tx // yet. -func (lc *LightningChannel) OweCommitment(local bool) bool { +func (lc *LightningChannel) OweCommitment() bool { lc.RLock() defer lc.RUnlock() - return lc.oweCommitment(local) + return lc.oweCommitment(true) } // oweCommitment is the internal version of OweCommitment. This function expects @@ -4567,7 +4734,6 @@ func (lc *LightningChannel) oweCommitment(local bool) bool { // have added to our remote commitment tx yet. remoteUpdatesPending = lastLocalCommit.theirMessageIndex != lastRemoteCommit.theirMessageIndex - } else { perspective = "remote" @@ -4874,12 +5040,28 @@ func (lc *LightningChannel) ReceiveRevocation(revMsg *lnwire.RevokeAndAck) ( source, remoteChainTail, addUpdates, settleFailUpdates, ) + // We will soon be saving the current remote commitment to revocation + // log bucket, which is `lc.channelState.RemoteCommitment`. After that, + // the `RemoteCommitment` will be replaced with a newer version found + // in `CommitDiff`. Thus we need to compute the output indexes here + // before the change since the indexes are meant for the current, + // revoked remote commitment. + ourOutputIndex, theirOutputIndex, err := findOutputIndexesFromRemote( + revocation, lc.channelState, + ) + if err != nil { + return nil, nil, nil, nil, err + } + // At this point, the revocation has been accepted, and we've rotated // the current revocation key+hash for the remote party. Therefore we // sync now to ensure the revocation producer state is consistent with // the current commitment height and also to advance the on-disk // commitment chain. - err = lc.channelState.AdvanceCommitChainTail(fwdPkg, localPeerUpdates) + err = lc.channelState.AdvanceCommitChainTail( + fwdPkg, localPeerUpdates, + ourOutputIndex, theirOutputIndex, + ) if err != nil { return nil, nil, nil, nil, err } @@ -5029,6 +5211,7 @@ func (lc *LightningChannel) GetDustSum(remote bool) lnwire.MilliSatoshi { if HtlcIsDust( chanType, false, !remote, feeRate, amt, dustLimit, ) { + dustSum += pd.Amount } } @@ -5047,6 +5230,7 @@ func (lc *LightningChannel) GetDustSum(remote bool) lnwire.MilliSatoshi { if HtlcIsDust( chanType, true, !remote, feeRate, amt, dustLimit, ) { + dustSum += pd.Amount } } @@ -5412,8 +5596,7 @@ func (lc *LightningChannel) MalformedFailHTLC(htlcIndex uint64, // ReceiveFailHTLC attempts to cancel a targeted HTLC by its log index, // inserting an entry which will remove the target log entry within the next // commitment update. This method should be called in response to the upstream -// party cancelling an outgoing HTLC. The value of the failed HTLC is returned -// along with an error indicating success. +// party cancelling an outgoing HTLC. func (lc *LightningChannel) ReceiveFailHTLC(htlcIndex uint64, reason []byte, ) error { @@ -5484,16 +5667,14 @@ func (lc *LightningChannel) getSignedCommitTx() (*wire.MsgTx, error) { localCommit := lc.channelState.LocalCommitment commitTx := localCommit.CommitTx.Copy() - theirSig, err := btcec.ParseDERSignature( - localCommit.CommitSig, btcec.S256(), - ) + theirSig, err := ecdsa.ParseDERSignature(localCommit.CommitSig) if err != nil { return nil, err } // With this, we then generate the full witness so the caller can // broadcast a fully signed transaction. - lc.signDesc.SigHashes = txscript.NewTxSigHashes(commitTx) + lc.signDesc.SigHashes = input.NewTxSigHashesV0Only(commitTx) ourSig, err := lc.Signer.SignOutputRaw(commitTx, lc.signDesc) if err != nil { return nil, err @@ -5901,11 +6082,11 @@ func newOutgoingHtlcResolution(signer input.Signer, WitnessScript: htlcScript, Output: txOut, HashType: txscript.SigHashAll, - SigHashes: txscript.NewTxSigHashes(timeoutTx), + SigHashes: input.NewTxSigHashesV0Only(timeoutTx), InputIndex: 0, } - htlcSig, err := btcec.ParseDERSignature(htlc.Signature, btcec.S256()) + htlcSig, err := ecdsa.ParseDERSignature(htlc.Signature) if err != nil { return nil, err } @@ -6036,11 +6217,11 @@ func newIncomingHtlcResolution(signer input.Signer, WitnessScript: htlcScript, Output: txOut, HashType: txscript.SigHashAll, - SigHashes: txscript.NewTxSigHashes(successTx), + SigHashes: input.NewTxSigHashesV0Only(successTx), InputIndex: 0, } - htlcSig, err := btcec.ParseDERSignature(htlc.Signature, btcec.S256()) + htlcSig, err := ecdsa.ParseDERSignature(htlc.Signature) if err != nil { return nil, err } @@ -6152,6 +6333,7 @@ func extractHtlcResolutions(feePerKw chainfee.SatPerKWeight, ourCommit bool, chanType, htlc.Incoming, ourCommit, feePerKw, htlc.Amt.ToSatoshis(), dustLimit, ) { + continue } @@ -6458,7 +6640,7 @@ func (lc *LightningChannel) CreateCloseProposal(proposedFee btcutil.Amount, // initiator we'll simply send our signature over to the remote party, // using the generated txid to be notified once the closure transaction // has been confirmed. - lc.signDesc.SigHashes = txscript.NewTxSigHashes(closeTx) + lc.signDesc.SigHashes = input.NewTxSigHashesV0Only(closeTx) sig, err := lc.Signer.SignOutputRaw(closeTx, lc.signDesc) if err != nil { return nil, nil, 0, err @@ -6518,7 +6700,7 @@ func (lc *LightningChannel) CompleteCooperativeClose( if err := blockchain.CheckTransactionSanity(tx); err != nil { return nil, 0, err } - hashCache := txscript.NewTxSigHashes(closeTx) + hashCache := input.NewTxSigHashesV0Only(closeTx) // Finally, construct the witness stack minding the order of the // pubkeys+sigs on the stack. @@ -6535,8 +6717,12 @@ func (lc *LightningChannel) CompleteCooperativeClose( // Validate the finalized transaction to ensure the output script is // properly met, and that the remote peer supplied a valid signature. prevOut := lc.signDesc.Output - vm, err := txscript.NewEngine(prevOut.PkScript, closeTx, 0, - txscript.StandardVerifyFlags, nil, hashCache, prevOut.Value) + vm, err := txscript.NewEngine( + prevOut.PkScript, closeTx, 0, txscript.StandardVerifyFlags, nil, + hashCache, prevOut.Value, txscript.NewCannedPrevOutputFetcher( + prevOut.PkScript, prevOut.Value, + ), + ) if err != nil { return nil, 0, err } @@ -6780,7 +6966,7 @@ func (lc *LightningChannel) availableCommitmentBalance(view *htlcView, // than the htlcCommitFee, where we could still be sending dust // HTLCs, but we return 0 in this case. This is to avoid // lowering our balance even further, as this takes us into a - // bad state wehere neither we nor our channel counterparty can + // bad state where neither we nor our channel counterparty can // add HTLCs. if ourBalance < htlcCommitFee { return 0, commitWeight @@ -7071,7 +7257,6 @@ func (lc *LightningChannel) IdealCommitFeeRate(netFeeRate, minRelayFeeRate, // Otherwise, cap the fee rate at the max fee rate. switch lc.channelState.ChanType.HasAnchors() && maxFeeRate > maxAnchorCommitFeeRate { - case true: commitFeeRate = chainfee.SatPerKWeight( math.Min( diff --git a/lnwallet/channel_test.go b/lnwallet/channel_test.go index f3b06b6557..812eb4b0e2 100644 --- a/lnwallet/channel_test.go +++ b/lnwallet/channel_test.go @@ -12,10 +12,11 @@ import ( "testing/quick" "github.com/btcsuite/btcd/blockchain" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" @@ -654,7 +655,7 @@ func testCommitHTLCSigTieBreak(t *testing.T, restart bool) { lastIndex = htlc.OutputIndex } - // If requsted, restart Alice so that we can test that the necessary + // If requested, restart Alice so that we can test that the necessary // indexes can be reconstructed before needing to validate the // signatures from Bob. if restart { @@ -990,9 +991,14 @@ func testForceClose(t *testing.T, testCase *forceCloseTestCase) { // the multi-sig clause within the output on the commitment transaction // that produces this HTLC. timeoutTx := htlcResolution.SignedTimeoutTx - vm, err := txscript.NewEngine(senderHtlcPkScript, + vm, err := txscript.NewEngine( + senderHtlcPkScript, timeoutTx, 0, txscript.StandardVerifyFlags, nil, - nil, int64(htlcAmount.ToSatoshis())) + nil, int64(htlcAmount.ToSatoshis()), + txscript.NewCannedPrevOutputFetcher( + senderHtlcPkScript, int64(htlcAmount.ToSatoshis()), + ), + ) if err != nil { t.Fatalf("unable to create engine: %v", err) } @@ -1028,6 +1034,10 @@ func testForceClose(t *testing.T, testCase *forceCloseTestCase) { htlcResolution.SweepSignDesc.Output.PkScript, sweepTx, 0, txscript.StandardVerifyFlags, nil, nil, htlcResolution.SweepSignDesc.Output.Value, + txscript.NewCannedPrevOutputFetcher( + htlcResolution.SweepSignDesc.Output.PkScript, + htlcResolution.SweepSignDesc.Output.Value, + ), ) if err != nil { t.Fatalf("unable to create engine: %v", err) @@ -1057,9 +1067,14 @@ func testForceClose(t *testing.T, testCase *forceCloseTestCase) { // before publication. successTx := inHtlcResolution.SignedSuccessTx successTx.TxIn[0].Witness[3] = preimageBob[:] - vm, err = txscript.NewEngine(receiverHtlcScript, + vm, err = txscript.NewEngine( + receiverHtlcScript, successTx, 0, txscript.StandardVerifyFlags, nil, - nil, int64(htlcAmount.ToSatoshis())) + nil, int64(htlcAmount.ToSatoshis()), + txscript.NewCannedPrevOutputFetcher( + receiverHtlcScript, int64(htlcAmount.ToSatoshis()), + ), + ) if err != nil { t.Fatalf("unable to create engine: %v", err) } @@ -1091,6 +1106,10 @@ func testForceClose(t *testing.T, testCase *forceCloseTestCase) { inHtlcResolution.SweepSignDesc.Output.PkScript, sweepTx, 0, txscript.StandardVerifyFlags, nil, nil, inHtlcResolution.SweepSignDesc.Output.Value, + txscript.NewCannedPrevOutputFetcher( + inHtlcResolution.SweepSignDesc.Output.PkScript, + inHtlcResolution.SweepSignDesc.Output.Value, + ), ) if err != nil { t.Fatalf("unable to create engine: %v", err) @@ -1172,7 +1191,7 @@ func TestForceCloseDustOutput(t *testing.T) { defer cleanUp() // We set both node's channel reserves to 0, to make sure - // they can create small dust ouputs without going under + // they can create small dust outputs without going under // their channel reserves. aliceChannel.channelState.LocalChanCfg.ChanReserve = 0 bobChannel.channelState.LocalChanCfg.ChanReserve = 0 @@ -2576,6 +2595,7 @@ func TestUpdateFeeSenderCommits(t *testing.T) { if chainfee.SatPerKWeight( bobChannel.channelState.LocalCommitment.FeePerKw, ) == fee { + t.Fatalf("bob's feePerKw was unexpectedly locked in") } @@ -2589,6 +2609,7 @@ func TestUpdateFeeSenderCommits(t *testing.T) { if chainfee.SatPerKWeight( bobChannel.channelState.LocalCommitment.FeePerKw, ) != fee { + t.Fatalf("bob's feePerKw was not locked in") } @@ -2617,6 +2638,7 @@ func TestUpdateFeeSenderCommits(t *testing.T) { if chainfee.SatPerKWeight( aliceChannel.channelState.LocalCommitment.FeePerKw, ) == fee { + t.Fatalf("alice's feePerKw was unexpectedly locked in") } @@ -2630,6 +2652,7 @@ func TestUpdateFeeSenderCommits(t *testing.T) { if chainfee.SatPerKWeight( aliceChannel.channelState.LocalCommitment.FeePerKw, ) != fee { + t.Fatalf("alice's feePerKw was not locked in") } @@ -2728,6 +2751,7 @@ func TestUpdateFeeReceiverCommits(t *testing.T) { if chainfee.SatPerKWeight( bobChannel.channelState.LocalCommitment.FeePerKw, ) == fee { + t.Fatalf("bob's feePerKw was unexpectedly locked in") } @@ -2742,6 +2766,7 @@ func TestUpdateFeeReceiverCommits(t *testing.T) { if chainfee.SatPerKWeight( bobChannel.channelState.LocalCommitment.FeePerKw, ) != fee { + t.Fatalf("bob's feePerKw was not locked in") } @@ -2769,6 +2794,7 @@ func TestUpdateFeeReceiverCommits(t *testing.T) { if chainfee.SatPerKWeight( aliceChannel.channelState.LocalCommitment.FeePerKw, ) == fee { + t.Fatalf("alice's feePerKw was unexpectedly locked in") } @@ -2782,6 +2808,7 @@ func TestUpdateFeeReceiverCommits(t *testing.T) { if chainfee.SatPerKWeight( aliceChannel.channelState.LocalCommitment.FeePerKw, ) != fee { + t.Fatalf("Alice's feePerKw was not locked in") } @@ -2871,6 +2898,7 @@ func TestUpdateFeeMultipleUpdates(t *testing.T) { if chainfee.SatPerKWeight( bobChannel.channelState.LocalCommitment.FeePerKw, ) == fee { + t.Fatalf("bob's feePerKw was unexpectedly locked in") } @@ -2896,6 +2924,7 @@ func TestUpdateFeeMultipleUpdates(t *testing.T) { if chainfee.SatPerKWeight( bobChannel.channelState.LocalCommitment.FeePerKw, ) != fee { + t.Fatalf("bob's feePerKw was not locked in") } @@ -2923,6 +2952,7 @@ func TestUpdateFeeMultipleUpdates(t *testing.T) { if chainfee.SatPerKWeight( aliceChannel.channelState.LocalCommitment.FeePerKw, ) == fee { + t.Fatalf("alice's feePerKw was unexpectedly locked in") } @@ -2936,6 +2966,7 @@ func TestUpdateFeeMultipleUpdates(t *testing.T) { if chainfee.SatPerKWeight( aliceChannel.channelState.LocalCommitment.FeePerKw, ) != fee { + t.Fatalf("alice's feePerKw was not locked in") } @@ -4403,7 +4434,7 @@ func TestChanSyncFailure(t *testing.T) { } p := bobSyncMsg.LocalUnrevokedCommitPoint.SerializeCompressed() p[4] ^= 0x01 - modCommitPoint, err := btcec.ParsePubKey(p, btcec.S256()) + modCommitPoint, err := btcec.ParsePubKey(p) if err != nil { t.Fatalf("unable to parse pubkey: %v", err) } @@ -4632,11 +4663,13 @@ func TestChannelRetransmissionFeeUpdate(t *testing.T) { if chainfee.SatPerKWeight( aliceChannel.channelState.LocalCommitment.FeePerKw, ) != newFeeRate { + t.Fatalf("alice's feePerKw was not locked in") } if chainfee.SatPerKWeight( bobChannel.channelState.LocalCommitment.FeePerKw, ) != newFeeRate { + t.Fatalf("bob's feePerKw was not locked in") } @@ -4851,11 +4884,13 @@ func TestFeeUpdateOldDiskFormat(t *testing.T) { if chainfee.SatPerKWeight( aliceChannel.channelState.LocalCommitment.FeePerKw, ) != newFeeRate { + t.Fatalf("alice's feePerKw was not locked in") } if chainfee.SatPerKWeight( bobChannel.channelState.LocalCommitment.FeePerKw, ) != newFeeRate { + t.Fatalf("bob's feePerKw was not locked in") } @@ -4880,11 +4915,13 @@ func TestFeeUpdateOldDiskFormat(t *testing.T) { if chainfee.SatPerKWeight( aliceChannel.channelState.LocalCommitment.FeePerKw, ) != newFeeRate { + t.Fatalf("alice's feePerKw was not locked in") } if chainfee.SatPerKWeight( bobChannel.channelState.LocalCommitment.FeePerKw, ) != newFeeRate { + t.Fatalf("bob's feePerKw was not locked in") } } @@ -5427,7 +5464,7 @@ func TestChanCommitWeightDustHtlcs(t *testing.T) { return w } - // Start by getting the initial remote commitment wight seen from + // Start by getting the initial remote commitment weight seen from // Alice's perspective. At this point there are no HTLCs on the // commitment. weight1 := remoteCommitWeight(aliceChannel) @@ -5438,7 +5475,7 @@ func TestChanCommitWeightDustHtlcs(t *testing.T) { bobDustHtlc := bobDustlimit + htlcSuccessFee - 1 preimg := addHtlc(bobDustHtlc) - // Now get the current wight of the remote commitment. We expect it to + // Now get the current weight of the remote commitment. We expect it to // not have changed, since the HTLC we added is considered dust. weight2 := remoteCommitWeight(aliceChannel) require.Equal(t, weight1, weight2) @@ -5711,7 +5748,7 @@ func TestLockedInHtlcForwardingSkipAfterRestart(t *testing.T) { t.Fatalf("unable to restart bob: %v", err) } - // Readd the Fail to both Alice and Bob's channels, as the non-committed + // Re-add the Fail to both Alice and Bob's channels, as the non-committed // update will not have survived the restart. err = bobChannel.FailHTLC(htlc2.ID, []byte("failreason"), nil, nil, nil) if err != nil { @@ -5723,7 +5760,7 @@ func TestLockedInHtlcForwardingSkipAfterRestart(t *testing.T) { } // Have Alice initiate a state transition, which does not include the - // HTLCs just readded to the channel state. + // HTLCs just re-added to the channel state. aliceSig, aliceHtlcSigs, _, err = aliceChannel.SignNextCommitment() if err != nil { t.Fatal(err) @@ -5960,7 +5997,7 @@ func TestChannelUnilateralCloseHtlcResolution(t *testing.T) { Value: outHtlcResolution.SweepSignDesc.Output.Value, }) outHtlcResolution.SweepSignDesc.InputIndex = 0 - outHtlcResolution.SweepSignDesc.SigHashes = txscript.NewTxSigHashes( + outHtlcResolution.SweepSignDesc.SigHashes = input.NewTxSigHashesV0Only( sweepTx, ) sweepTx.LockTime = outHtlcResolution.Expiry @@ -5978,6 +6015,10 @@ func TestChannelUnilateralCloseHtlcResolution(t *testing.T) { outHtlcResolution.SweepSignDesc.Output.PkScript, sweepTx, 0, txscript.StandardVerifyFlags, nil, nil, outHtlcResolution.SweepSignDesc.Output.Value, + txscript.NewCannedPrevOutputFetcher( + outHtlcResolution.SweepSignDesc.Output.PkScript, + outHtlcResolution.SweepSignDesc.Output.Value, + ), ) if err != nil { t.Fatalf("unable to create engine: %v", err) @@ -5998,7 +6039,7 @@ func TestChannelUnilateralCloseHtlcResolution(t *testing.T) { Value: inHtlcResolution.SweepSignDesc.Output.Value, }) inHtlcResolution.SweepSignDesc.InputIndex = 0 - inHtlcResolution.SweepSignDesc.SigHashes = txscript.NewTxSigHashes( + inHtlcResolution.SweepSignDesc.SigHashes = input.NewTxSigHashesV0Only( sweepTx, ) sweepTx.TxIn[0].Witness, err = input.SenderHtlcSpendRedeem( @@ -6016,6 +6057,10 @@ func TestChannelUnilateralCloseHtlcResolution(t *testing.T) { inHtlcResolution.SweepSignDesc.Output.PkScript, sweepTx, 0, txscript.StandardVerifyFlags, nil, nil, inHtlcResolution.SweepSignDesc.Output.Value, + txscript.NewCannedPrevOutputFetcher( + inHtlcResolution.SweepSignDesc.Output.PkScript, + inHtlcResolution.SweepSignDesc.Output.Value, + ), ) if err != nil { t.Fatalf("unable to create engine: %v", err) @@ -6133,7 +6178,7 @@ func TestChannelUnilateralClosePendingCommit(t *testing.T) { PkScript: testHdSeed[:], Value: aliceSignDesc.Output.Value, }) - aliceSignDesc.SigHashes = txscript.NewTxSigHashes(sweepTx) + aliceSignDesc.SigHashes = input.NewTxSigHashesV0Only(sweepTx) sweepTx.TxIn[0].Witness, err = input.CommitSpendNoDelay( aliceChannel.Signer, &aliceSignDesc, sweepTx, false, ) @@ -6147,6 +6192,10 @@ func TestChannelUnilateralClosePendingCommit(t *testing.T) { aliceSignDesc.Output.PkScript, sweepTx, 0, txscript.StandardVerifyFlags, nil, nil, aliceSignDesc.Output.Value, + txscript.NewCannedPrevOutputFetcher( + aliceSignDesc.Output.PkScript, + aliceSignDesc.Output.Value, + ), ) if err != nil { t.Fatalf("unable to create engine: %v", err) @@ -6808,7 +6857,7 @@ func TestChanReserveRemoteInitiator(t *testing.T) { // Set Alice's channel reserve to be 5 BTC-commitfee. This means she // has just enough balance to cover the comitment fee, but not enough // to add any more HTLCs to the commitment. Although a reserve this - // high is unrealistic, a channel can easiliy get into a situation + // high is unrealistic, a channel can easily get into a situation // where the initiator cannot pay for the fee of any more HTLCs. commitFee := aliceChannel.channelState.LocalCommitment.CommitFee aliceMinReserve := 5*btcutil.SatoshiPerBitcoin - commitFee @@ -7058,8 +7107,9 @@ func TestNewBreachRetributionSkipsDustHtlcs(t *testing.T) { // At this point, we'll now simulate a contract breach by Bob using the // NewBreachRetribution method. + breachTx := aliceChannel.channelState.RemoteCommitment.CommitTx breachRet, err := NewBreachRetribution( - aliceChannel.channelState, revokedStateNum, 100, + aliceChannel.channelState, revokedStateNum, 100, breachTx, ) if err != nil { t.Fatalf("unable to create breach retribution: %v", err) @@ -7203,7 +7253,7 @@ func TestChannelRestoreUpdateLogs(t *testing.T) { // signature from Bob yet. _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) if err != nil { - t.Fatalf("unable to recive revocation: %v", err) + t.Fatalf("unable to receive revocation: %v", err) } // Now make Alice send and sign an additional HTLC. We don't let Bob @@ -7709,7 +7759,7 @@ func TestChannelRestoreCommitHeight(t *testing.T) { // Alice receives the revocation, ACKing her pending commitment. _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) if err != nil { - t.Fatalf("unable to recive revocation: %v", err) + t.Fatalf("unable to receive revocation: %v", err) } // However, the HTLC is still not locked into her local commitment, so @@ -7726,7 +7776,7 @@ func TestChannelRestoreCommitHeight(t *testing.T) { } // At this stage Bob has a pending remote commitment. Make sure - // restoring at this stage correcly restores the HTLC add commit + // restoring at this stage correctly restores the HTLC add commit // heights. bobChannel = restoreAndAssertCommitHeights(t, bobChannel, true, 0, 1, 1) @@ -7746,7 +7796,7 @@ func TestChannelRestoreCommitHeight(t *testing.T) { _, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) if err != nil { - t.Fatalf("unable to recive revocation: %v", err) + t.Fatalf("unable to receive revocation: %v", err) } // Alice ACKing Bob's pending commitment shouldn't change the heights @@ -7790,7 +7840,7 @@ func TestChannelRestoreCommitHeight(t *testing.T) { } // Since Bob just revoked another commitment, a restoration should - // increase the add height of the firt HTLC to 2, as we only keep the + // increase the add height of the first HTLC to 2, as we only keep the // last unrevoked commitment. The new HTLC will also have a local add // height of 2. bobChannel = restoreAndAssertCommitHeights(t, bobChannel, true, 0, 2, 1) @@ -8506,7 +8556,6 @@ func TestFetchParent(t *testing.T) { test.parentIndex, parent.HtlcIndex) } }) - } } @@ -10202,3 +10251,388 @@ func testGetDustSum(t *testing.T, chantype channeldb.ChannelType) { checkDust(bobChannel, htlc2Amt+htlc3Amt, htlc2Amt+htlc3Amt) } } + +// deriveDummyRetributionParams is a helper function that derives a list of +// dummy params to assist retribution creation related tests. +func deriveDummyRetributionParams(chanState *channeldb.OpenChannel) (uint32, + *CommitmentKeyRing, chainhash.Hash) { + + config := chanState.RemoteChanCfg + commitHash := chanState.RemoteCommitment.CommitTx.TxHash() + keyRing := DeriveCommitmentKeys( + config.RevocationBasePoint.PubKey, false, chanState.ChanType, + &chanState.LocalChanCfg, &chanState.RemoteChanCfg, + ) + leaseExpiry := chanState.ThawHeight + return leaseExpiry, keyRing, commitHash +} + +// TestCreateHtlcRetribution checks that `createHtlcRetribution` behaves as +// epxected. +func TestCreateHtlcRetribution(t *testing.T) { + t.Parallel() + + // Create a dummy private key and an HTLC amount for testing. + dummyPrivate, _ := btcec.PrivKeyFromBytes([]byte{1}) + testAmt := btcutil.Amount(100) + + // Create a test channel. + aliceChannel, _, cleanUp, err := CreateTestChannels( + channeldb.ZeroHtlcTxFeeBit, + ) + require.NoError(t, err) + defer cleanUp() + + // Prepare the params needed to call the function. Note that the values + // here are not necessary "cryptography-correct", we just use them to + // construct the htlc retribution. + leaseExpiry, keyRing, commitHash := deriveDummyRetributionParams( + aliceChannel.channelState, + ) + htlc := &channeldb.HTLCEntry{ + Amt: testAmt, + Incoming: true, + OutputIndex: 1, + } + + // Create the htlc retribution. + hr, err := createHtlcRetribution( + aliceChannel.channelState, keyRing, commitHash, + dummyPrivate, leaseExpiry, htlc, + ) + // Expect no error. + require.NoError(t, err) + + // Check the fields have expected values. + require.EqualValues(t, testAmt, hr.SignDesc.Output.Value) + require.Equal(t, commitHash, hr.OutPoint.Hash) + require.EqualValues(t, htlc.OutputIndex, hr.OutPoint.Index) + require.Equal(t, htlc.Incoming, hr.IsIncoming) +} + +// TestCreateBreachRetribution checks that `createBreachRetribution` behaves as +// epxected. +func TestCreateBreachRetribution(t *testing.T) { + t.Parallel() + + // Create dummy values for the test. + dummyPrivate, _ := btcec.PrivKeyFromBytes([]byte{1}) + testAmt := int64(100) + ourAmt := int64(1000) + theirAmt := int64(2000) + localIndex := uint32(0) + remoteIndex := uint32(1) + htlcIndex := uint32(2) + + // Create a dummy breach tx, which has our output located at index 0 + // and theirs at 1. + spendTx := &wire.MsgTx{ + TxOut: []*wire.TxOut{ + {Value: ourAmt}, + {Value: theirAmt}, + {Value: testAmt}, + }, + } + + // Create a test channel. + aliceChannel, _, cleanUp, err := CreateTestChannels( + channeldb.ZeroHtlcTxFeeBit, + ) + require.NoError(t, err) + defer cleanUp() + + // Prepare the params needed to call the function. Note that the values + // here are not necessary "cryptography-correct", we just use them to + // construct the retribution. + leaseExpiry, keyRing, commitHash := deriveDummyRetributionParams( + aliceChannel.channelState, + ) + htlc := &channeldb.HTLCEntry{ + Amt: btcutil.Amount(testAmt), + Incoming: true, + OutputIndex: uint16(htlcIndex), + } + + // Create a dummy revocation log. + revokedLog := channeldb.RevocationLog{ + CommitTxHash: commitHash, + OurOutputIndex: uint16(localIndex), + TheirOutputIndex: uint16(remoteIndex), + HTLCEntries: []*channeldb.HTLCEntry{htlc}, + } + + // Create a log with an empty local output index. + revokedLogNoLocal := revokedLog + revokedLogNoLocal.OurOutputIndex = channeldb.OutputIndexEmpty + + // Create a log with an empty remote output index. + revokedLogNoRemote := revokedLog + revokedLogNoRemote.TheirOutputIndex = channeldb.OutputIndexEmpty + + testCases := []struct { + name string + revocationLog *channeldb.RevocationLog + expectedErr error + expectedOurAmt int64 + expectedTheirAmt int64 + }{ + { + name: "create retribution successfully", + revocationLog: &revokedLog, + expectedErr: nil, + expectedOurAmt: ourAmt, + expectedTheirAmt: theirAmt, + }, + { + name: "fail due to our index too big", + revocationLog: &channeldb.RevocationLog{ + OurOutputIndex: uint16(htlcIndex + 1), + }, + expectedErr: ErrOutputIndexOutOfRange, + }, + { + name: "fail due to their index too big", + revocationLog: &channeldb.RevocationLog{ + TheirOutputIndex: uint16(htlcIndex + 1), + }, + expectedErr: ErrOutputIndexOutOfRange, + }, + { + name: "empty local output index", + revocationLog: &revokedLogNoLocal, + expectedErr: nil, + expectedOurAmt: 0, + expectedTheirAmt: theirAmt, + }, + { + name: "empty remote output index", + revocationLog: &revokedLogNoRemote, + expectedErr: nil, + expectedOurAmt: ourAmt, + expectedTheirAmt: 0, + }, + } + + // assertRetribution is a helper closure that checks a given breach + // retribution has the expected values on certain fields. + assertRetribution := func(br *BreachRetribution, our, their int64) { + chainHash := aliceChannel.channelState.ChainHash + require.Equal(t, commitHash, br.BreachTxHash) + require.Equal(t, chainHash, br.ChainHash) + + // Construct local outpoint, we only have the index when the + // amount is not zero. + local := wire.OutPoint{ + Hash: commitHash, + } + if our != 0 { + local.Index = localIndex + } + + // Construct remote outpoint, we only have the index when the + // amount is not zero. + remote := wire.OutPoint{ + Hash: commitHash, + } + if their != 0 { + remote.Index = remoteIndex + } + + require.Equal(t, local, br.LocalOutpoint) + require.Equal(t, remote, br.RemoteOutpoint) + + for _, hr := range br.HtlcRetributions { + require.EqualValues(t, testAmt, + hr.SignDesc.Output.Value) + require.Equal(t, commitHash, hr.OutPoint.Hash) + require.EqualValues(t, htlcIndex, hr.OutPoint.Index) + require.Equal(t, htlc.Incoming, hr.IsIncoming) + } + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + br, our, their, err := createBreachRetribution( + tc.revocationLog, spendTx, + aliceChannel.channelState, keyRing, + dummyPrivate, leaseExpiry, + ) + + // Check the error if expected. + if tc.expectedErr != nil { + require.ErrorIs(t, err, tc.expectedErr) + } else { + // Otherwise we expect no error. + require.NoError(t, err) + + // Check the amounts and the contructed partial + // retribution are returned as expected. + require.Equal(t, tc.expectedOurAmt, our) + require.Equal(t, tc.expectedTheirAmt, their) + assertRetribution(br, our, their) + } + }) + } +} + +// TestCreateBreachRetributionLegacy checks that +// `createBreachRetributionLegacy` behaves as expected. +func TestCreateBreachRetributionLegacy(t *testing.T) { + t.Parallel() + + // Create dummy values for the test. + dummyPrivate, _ := btcec.PrivKeyFromBytes([]byte{1}) + + // Create a test channel. + aliceChannel, _, cleanUp, err := CreateTestChannels( + channeldb.ZeroHtlcTxFeeBit, + ) + require.NoError(t, err) + defer cleanUp() + + // Prepare the params needed to call the function. Note that the values + // here are not necessary "cryptography-correct", we just use them to + // construct the retribution. + leaseExpiry, keyRing, _ := deriveDummyRetributionParams( + aliceChannel.channelState, + ) + + // Use the remote commitment as our revocation log. + revokedLog := aliceChannel.channelState.RemoteCommitment + + ourOp := revokedLog.CommitTx.TxOut[0] + theirOp := revokedLog.CommitTx.TxOut[1] + + // Create the dummy scripts. + ourScript := &ScriptInfo{ + PkScript: ourOp.PkScript, + } + theirScript := &ScriptInfo{ + PkScript: theirOp.PkScript, + } + + // Create the breach retribution using the legacy format. + br, ourAmt, theirAmt, err := createBreachRetributionLegacy( + &revokedLog, aliceChannel.channelState, keyRing, + dummyPrivate, ourScript, theirScript, leaseExpiry, + ) + require.NoError(t, err) + + // Check the commitHash and chainHash. + commitHash := revokedLog.CommitTx.TxHash() + chainHash := aliceChannel.channelState.ChainHash + require.Equal(t, commitHash, br.BreachTxHash) + require.Equal(t, chainHash, br.ChainHash) + + // Check the outpoints. + local := wire.OutPoint{ + Hash: commitHash, + Index: 0, + } + remote := wire.OutPoint{ + Hash: commitHash, + Index: 1, + } + require.Equal(t, local, br.LocalOutpoint) + require.Equal(t, remote, br.RemoteOutpoint) + + // Validate the amounts, note that in the legacy format, our amount is + // not directly the amount found in the to local output. Rather, it's + // the local output value minus the commit fee and anchor value(if + // present). + require.EqualValues(t, revokedLog.LocalBalance.ToSatoshis(), ourAmt) + require.Equal(t, theirOp.Value, theirAmt) +} + +// TestNewBreachRetribution tests that the function `NewBreachRetribution` +// behaves as expected. +func TestNewBreachRetribution(t *testing.T) { + t.Run("non-anchor", func(t *testing.T) { + testNewBreachRetribution(t, channeldb.ZeroHtlcTxFeeBit) + }) + t.Run("anchor", func(t *testing.T) { + chanType := channeldb.SingleFunderTweaklessBit | + channeldb.AnchorOutputsBit + testNewBreachRetribution(t, chanType) + }) +} + +// testNewBreachRetribution takes a channel type and tests the function +// `NewBreachRetribution`. +func testNewBreachRetribution(t *testing.T, chanType channeldb.ChannelType) { + t.Parallel() + + aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(chanType) + require.NoError(t, err) + defer cleanUp() + + breachHeight := uint32(101) + stateNum := uint64(0) + chainHash := aliceChannel.channelState.ChainHash + theirDelay := uint32(aliceChannel.channelState.RemoteChanCfg.CsvDelay) + breachTx := aliceChannel.channelState.RemoteCommitment.CommitTx + + // Create a breach retribution at height 0, which should give us an + // error as there are no past delta state saved as revocation logs yet. + _, err = NewBreachRetribution( + aliceChannel.channelState, stateNum, breachHeight, breachTx, + ) + require.ErrorIs(t, err, channeldb.ErrNoPastDeltas) + + // We now force a state transition which will give us a revocation log + // at height 0. + txid := aliceChannel.channelState.RemoteCommitment.CommitTx.TxHash() + err = ForceStateTransition(aliceChannel, bobChannel) + require.NoError(t, err) + + // assertRetribution is a helper closure that checks a given breach + // retribution has the expected values on certain fields. + assertRetribution := func(br *BreachRetribution, + localIndex, remoteIndex uint32) { + + require.Equal(t, txid, br.BreachTxHash) + require.Equal(t, chainHash, br.ChainHash) + require.Equal(t, breachHeight, br.BreachHeight) + require.Equal(t, stateNum, br.RevokedStateNum) + require.Equal(t, theirDelay, br.RemoteDelay) + + local := wire.OutPoint{ + Hash: txid, + Index: localIndex, + } + remote := wire.OutPoint{ + Hash: txid, + Index: remoteIndex, + } + + if chanType.HasAnchors() { + // For anchor channels, we expect the local delay to be + // 1 otherwise 0. + require.EqualValues(t, 1, br.LocalDelay) + } else { + require.Zero(t, br.LocalDelay) + } + + require.Equal(t, local, br.LocalOutpoint) + require.Equal(t, remote, br.RemoteOutpoint) + } + + // Create the retribution again and we should expect it to be created + // successfully. + br, err := NewBreachRetribution( + aliceChannel.channelState, stateNum, breachHeight, breachTx, + ) + require.NoError(t, err) + + // Check the retribution is as expected. + t.Log(spew.Sdump(breachTx)) + assertRetribution(br, 1, 0) + + // Create the retribution using a stateNum+1 and we should expect an + // error. + _, err = NewBreachRetribution( + aliceChannel.channelState, stateNum+1, breachHeight, breachTx, + ) + require.ErrorIs(t, err, channeldb.ErrLogEntryNotFound) +} diff --git a/lnwallet/chanvalidate/validate.go b/lnwallet/chanvalidate/validate.go index 213767fd75..c349ea974b 100644 --- a/lnwallet/chanvalidate/validate.go +++ b/lnwallet/chanvalidate/validate.go @@ -4,9 +4,9 @@ import ( "bytes" "fmt" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/lnwire" ) @@ -189,6 +189,7 @@ func Validate(ctx *Context) (*wire.OutPoint, error) { vm, err := txscript.NewEngine( ctx.MultiSigPkScript, ctx.CommitCtx.FullySignedCommitTx, 0, txscript.StandardVerifyFlags, nil, nil, fundingValue, + txscript.NewCannedPrevOutputFetcher(ctx.MultiSigPkScript, 0), ) if err != nil { return nil, err diff --git a/lnwallet/chanvalidate/validate_test.go b/lnwallet/chanvalidate/validate_test.go index e979b014b0..85d0e92a10 100644 --- a/lnwallet/chanvalidate/validate_test.go +++ b/lnwallet/chanvalidate/validate_test.go @@ -4,11 +4,12 @@ import ( "bytes" "testing" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnwire" ) @@ -27,8 +28,8 @@ var ( 0x69, 0x49, 0x18, 0x83, 0x31, 0x98, 0x47, 0x53, } - alicePriv, alicePub = btcec.PrivKeyFromBytes(btcec.S256(), aliceKey[:]) - bobPriv, bobPub = btcec.PrivKeyFromBytes(btcec.S256(), bobKey[:]) + alicePriv, alicePub = btcec.PrivKeyFromBytes(aliceKey[:]) + bobPriv, bobPub = btcec.PrivKeyFromBytes(bobKey[:]) ) // channelTestCtx holds shared context that will be used in all tests cases @@ -97,7 +98,7 @@ func newChannelTestCtx(chanSize int64) (*channelTestCtx, error) { }, } - sigHashes := txscript.NewTxSigHashes(commitTx) + sigHashes := input.NewTxSigHashesV0Only(commitTx) aliceSigRaw, err := txscript.RawTxInWitnessSignature( commitTx, sigHashes, 0, chanSize, multiSigScript, txscript.SigHashAll, alicePriv, @@ -106,9 +107,7 @@ func newChannelTestCtx(chanSize int64) (*channelTestCtx, error) { return nil, err } - aliceSig, err := btcec.ParseDERSignature( - aliceSigRaw, btcec.S256(), - ) + aliceSig, err := ecdsa.ParseDERSignature(aliceSigRaw) if err != nil { return nil, err } @@ -121,9 +120,7 @@ func newChannelTestCtx(chanSize int64) (*channelTestCtx, error) { return nil, err } - bobSig, err := btcec.ParseDERSignature( - bobSigRaw, btcec.S256(), - ) + bobSig, err := ecdsa.ParseDERSignature(bobSigRaw) if err != nil { return nil, err } diff --git a/lnwallet/commitment.go b/lnwallet/commitment.go index 08cf96245c..c4f4d931a4 100644 --- a/lnwallet/commitment.go +++ b/lnwallet/commitment.go @@ -1,13 +1,15 @@ package lnwallet import ( + "bytes" "fmt" "github.com/btcsuite/btcd/blockchain" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnwallet/chainfee" @@ -193,7 +195,7 @@ type ScriptInfo struct { // CommitScriptToSelf constructs the public key script for the output on the // commitment transaction paying to the "owner" of said commitment transaction. // The `initiator` argument should correspond to the owner of the commitment -// tranasction which we are generating the to_local script for. If the other +// transaction which we are generating the to_local script for. If the other // party learns of the preimage to the revocation hash, then they can claim all // the settled funds in the channel, plus the unsettled funds. func CommitScriptToSelf(chanType channeldb.ChannelType, initiator bool, @@ -234,7 +236,7 @@ func CommitScriptToSelf(chanType channeldb.ChannelType, initiator bool, // CommitScriptToRemote derives the appropriate to_remote script based on the // channel's commitment type. The `initiator` argument should correspond to the -// owner of the commitment tranasction which we are generating the to_remote +// owner of the commitment transaction which we are generating the to_remote // script for. The second return value is the CSV delay of the output script, // what must be satisfied in order to spend the output. func CommitScriptToRemote(chanType channeldb.ChannelType, initiator bool, @@ -293,7 +295,6 @@ func CommitScriptToRemote(chanType channeldb.ChannelType, initiator bool, WitnessScript: p2wkh, PkScript: p2wkh, }, 0, nil - } } @@ -340,7 +341,7 @@ func HtlcSecondLevelInputSequence(chanType channeldb.ChannelType) uint32 { // output for the second-level HTLC transactions. The second level transaction // act as a sort of covenant, ensuring that a 2-of-2 multi-sig output can only // be spent in a particular way, and to a particular output. The `initiator` -// argument should correspond to the owner of the commitment tranasction which +// argument should correspond to the owner of the commitment transaction which // we are generating the to_local script for. func SecondLevelHtlcScript(chanType channeldb.ChannelType, initiator bool, revocationKey, delayKey *btcec.PublicKey, @@ -554,6 +555,7 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance, cb.chanState.ChanType, false, isOurs, feePerKw, htlc.Amount.ToSatoshis(), dustLimit, ) { + continue } @@ -564,6 +566,7 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance, cb.chanState.ChanType, true, isOurs, feePerKw, htlc.Amount.ToSatoshis(), dustLimit, ) { + continue } @@ -647,6 +650,7 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance, cb.chanState.ChanType, false, isOurs, feePerKw, htlc.Amount.ToSatoshis(), dustLimit, ) { + continue } @@ -657,13 +661,14 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance, if err != nil { return nil, err } - cltvs = append(cltvs, htlc.Timeout) + cltvs = append(cltvs, htlc.Timeout) // nolint:makezero } for _, htlc := range filteredHTLCView.theirUpdates { if HtlcIsDust( cb.chanState.ChanType, true, isOurs, feePerKw, htlc.Amount.ToSatoshis(), dustLimit, ) { + continue } @@ -674,7 +679,7 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance, if err != nil { return nil, err } - cltvs = append(cltvs, htlc.Timeout) + cltvs = append(cltvs, htlc.Timeout) // nolint:makezero } // Set the state hint of the commitment transaction to facilitate @@ -725,7 +730,7 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance, // spent after a relative block delay or revocation event, and a remote output // paying the counterparty within the channel, which can be spent immediately // or after a delay depending on the commitment type. The `initiator` argument -// should correspond to the owner of the commitment tranasction we are creating. +// should correspond to the owner of the commitment transaction we are creating. func CreateCommitTx(chanType channeldb.ChannelType, fundingOutput wire.TxIn, keyRing *CommitmentKeyRing, localChanCfg, remoteChanCfg *channeldb.ChannelConfig, @@ -957,3 +962,76 @@ func addHTLC(commitTx *wire.MsgTx, ourCommit bool, return nil } + +// findOutputIndexesFromRemote finds the index of our and their outputs from +// the remote commitment transaction. It derives the key ring to compute the +// output scripts and compares them against the outputs inside the commitment +// to find the match. +func findOutputIndexesFromRemote(revocationPreimage *chainhash.Hash, + chanState *channeldb.OpenChannel) (uint32, uint32, error) { + + // Init the output indexes as empty. + ourIndex := uint32(channeldb.OutputIndexEmpty) + theirIndex := uint32(channeldb.OutputIndexEmpty) + + chanCommit := chanState.RemoteCommitment + _, commitmentPoint := btcec.PrivKeyFromBytes(revocationPreimage[:]) + + // With the commitment point generated, we can now derive the king ring + // which will be used to generate the output scripts. + keyRing := DeriveCommitmentKeys( + commitmentPoint, false, chanState.ChanType, + &chanState.LocalChanCfg, &chanState.RemoteChanCfg, + ) + + // Since it's remote commitment chain, we'd used the mirrored values. + // + // We use the remote's channel config for the csv delay. + theirDelay := uint32(chanState.RemoteChanCfg.CsvDelay) + + // If we are the initiator of this channel, then it's be false from the + // remote's PoV. + isRemoteInitiator := !chanState.IsInitiator + + var leaseExpiry uint32 + if chanState.ChanType.HasLeaseExpiration() { + leaseExpiry = chanState.ThawHeight + } + + // Map the scripts from our PoV. When facing a local commitment, the to + // local output belongs to us and the to remote output belongs to them. + // When facing a remote commitment, the to local output belongs to them + // and the to remote output belongs to us. + + // Compute the to local script. From our PoV, when facing a remote + // commitment, the to local output belongs to them. + theirScript, err := CommitScriptToSelf( + chanState.ChanType, isRemoteInitiator, keyRing.ToLocalKey, + keyRing.RevocationKey, theirDelay, leaseExpiry, + ) + if err != nil { + return ourIndex, theirIndex, err + } + + // Compute the to remote script. From our PoV, when facing a remote + // commitment, the to remote output belongs to us. + ourScript, _, err := CommitScriptToRemote( + chanState.ChanType, isRemoteInitiator, keyRing.ToRemoteKey, + leaseExpiry, + ) + if err != nil { + return ourIndex, theirIndex, err + } + + // Now compare the scripts to find our/their output index. + for i, txOut := range chanCommit.CommitTx.TxOut { + switch { + case bytes.Equal(txOut.PkScript, ourScript.PkScript): + ourIndex = uint32(i) + case bytes.Equal(txOut.PkScript, theirScript.PkScript): + theirIndex = uint32(i) + } + } + + return ourIndex, theirIndex, nil +} diff --git a/lnwallet/errors.go b/lnwallet/errors.go index 0526acffda..b2d1a5c42e 100644 --- a/lnwallet/errors.go +++ b/lnwallet/errors.go @@ -4,8 +4,8 @@ import ( "errors" "fmt" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/lnwire" ) diff --git a/lnwallet/interface.go b/lnwallet/interface.go index 42c2f633a4..e9c06b5e2c 100644 --- a/lnwallet/interface.go +++ b/lnwallet/interface.go @@ -6,12 +6,14 @@ import ( "sync" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/hdkeychain" + "github.com/btcsuite/btcd/btcutil/psbt" "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" - "github.com/btcsuite/btcutil/hdkeychain" - "github.com/btcsuite/btcutil/psbt" "github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/wallet/txauthor" "github.com/btcsuite/btcwallet/wtxmgr" @@ -40,6 +42,9 @@ const ( // NestedWitnessPubKey represents a p2sh output which is itself a // nested p2wkh output. NestedWitnessPubKey + + // TaprootPubkey represents a p2tr key path spending address. + TaprootPubkey ) var ( @@ -78,6 +83,18 @@ type Utxo struct { Confirmations int64 PkScript []byte wire.OutPoint + Derivation *psbt.Bip32Derivation + PrevTx *wire.MsgTx +} + +// OutputDetail contains additional information on a destination address. +type OutputDetail struct { + OutputType txscript.ScriptClass + Addresses []btcutil.Address + PkScript []byte + OutputIndex int + Value btcutil.Amount + IsOurAddress bool } // TransactionDetail describes a transaction with either inputs which belong to @@ -114,8 +131,9 @@ type TransactionDetail struct { // TotalFees is the total fee in satoshis paid by this transaction. TotalFees int64 - // DestAddresses are the destinations for a transaction - DestAddresses []btcutil.Address + // OutputDetails contains output data for each destination address, such + // as the output script and amount. + OutputDetails []OutputDetail // RawTx returns the raw serialized transaction. RawTx []byte @@ -158,6 +176,12 @@ type WalletController interface { // a non-nil error value of ErrNotMine should be returned instead. FetchInputInfo(prevOut *wire.OutPoint) (*Utxo, error) + // ScriptForOutput returns the address, witness program and redeem + // script for a given UTXO. An error is returned if the UTXO does not + // belong to our wallet or it is not a managed pubKey address. + ScriptForOutput(output *wire.TxOut) (waddrmgr.ManagedPubKeyAddress, + []byte, []byte, error) + // ConfirmedBalance returns the sum of all the wallet's unspent outputs // that have at least confs confirmations. If confs is set to zero, // then all unspent outputs, including those currently in the mempool @@ -168,7 +192,8 @@ type WalletController interface { // NOTE: Only witness outputs should be included in the computation of // the total spendable balance of the wallet. We require this as only // witness inputs can be used for funding channels. - ConfirmedBalance(confs int32, accountFilter string) (btcutil.Amount, error) + ConfirmedBalance(confs int32, accountFilter string) (btcutil.Amount, + error) // NewAddress returns the next external or internal address for the // wallet dictated by the value of the `change` parameter. If change is @@ -194,10 +219,15 @@ type WalletController interface { // IsOurAddress checks if the passed address belongs to this wallet IsOurAddress(a btcutil.Address) bool + // AddressInfo returns the information about an address, if it's known + // to this wallet. + AddressInfo(a btcutil.Address) (waddrmgr.ManagedAddress, error) + // ListAccounts retrieves all accounts belonging to the wallet by // default. A name and key scope filter can be provided to filter // through all of the wallet accounts and return only those matching. - ListAccounts(string, *waddrmgr.KeyScope) ([]*waddrmgr.AccountProperties, error) + ListAccounts(string, *waddrmgr.KeyScope) ([]*waddrmgr.AccountProperties, + error) // ImportAccount imports an account backed by an account extended public // key. The master key fingerprint denotes the fingerprint of the root @@ -228,7 +258,8 @@ type WalletController interface { // in the case of legacy versions (xpub, tpub), an address type must be // specified as we intend to not support importing BIP-44 keys into the // wallet using the legacy pay-to-pubkey-hash (P2PKH) scheme. - ImportPublicKey(pubKey *btcec.PublicKey, addrType waddrmgr.AddressType) error + ImportPublicKey(pubKey *btcec.PublicKey, + addrType waddrmgr.AddressType) error // SendOutputs funds, signs, and broadcasts a Bitcoin transaction paying // out to the specified outputs. In the case the wallet has insufficient @@ -237,8 +268,8 @@ type WalletController interface { // be used when crafting the transaction. // // NOTE: This method requires the global coin selection lock to be held. - SendOutputs(outputs []*wire.TxOut, - feeRate chainfee.SatPerKWeight, minConfs int32, label string) (*wire.MsgTx, error) + SendOutputs(outputs []*wire.TxOut, feeRate chainfee.SatPerKWeight, + minConfs int32, label string) (*wire.MsgTx, error) // CreateSimpleTx creates a Bitcoin transaction paying to the specified // outputs. The transaction is not broadcasted to the network. In the @@ -334,6 +365,17 @@ type WalletController interface { // is set. Labels must not be empty, and they are limited to 500 chars. LabelTransaction(hash chainhash.Hash, label string, overwrite bool) error + // FetchTx attempts to fetch a transaction in the wallet's database + // identified by the passed transaction hash. If the transaction can't + // be found, then a nil pointer is returned. + FetchTx(chainhash.Hash) (*wire.MsgTx, error) + + // RemoveDescendants attempts to remove any transaction from the + // wallet's tx store (that may be unconfirmed) that spends outputs + // created by the passed transaction. This remove propagates + // recursively down the chain of descendent transactions. + RemoveDescendants(*wire.MsgTx) error + // FundPsbt creates a fully populated PSBT packet that contains enough // inputs to fund the outputs specified in the passed in packet with the // specified fee rate. If there is change left, a change output from the @@ -353,6 +395,19 @@ type WalletController interface { FundPsbt(packet *psbt.Packet, minConfs int32, feeRate chainfee.SatPerKWeight, account string) (int32, error) + // SignPsbt expects a partial transaction with all inputs and outputs + // fully declared and tries to sign all unsigned inputs that have all + // required fields (UTXO information, BIP32 derivation information, + // witness or sig scripts) set. + // If no error is returned, the PSBT is ready to be given to the next + // signer or to be finalized if lnd was the last signer. + // + // NOTE: This method only signs inputs (and only those it can sign), it + // does not perform any other tasks (such as coin selection, UTXO + // locking or input/output/fee value validation, PSBT finalization). Any + // input that is incomplete will be skipped. + SignPsbt(packet *psbt.Packet) error + // FinalizePsbt expects a partial transaction with all inputs and // outputs fully declared and tries to sign all inputs that belong to // the specified account. Lnd must be the last signer of the @@ -444,7 +499,7 @@ type MessageSigner interface { // be found, then an error will be returned. The actual digest signed is // the single or double SHA-256 of the passed message. SignMessage(keyLoc keychain.KeyLocator, msg []byte, - doubleHash bool) (*btcec.Signature, error) + doubleHash bool) (*ecdsa.Signature, error) } // WalletDriver represents a "driver" for a particular concrete diff --git a/lnwallet/log.go b/lnwallet/log.go index 31881e2d19..341edeca7d 100644 --- a/lnwallet/log.go +++ b/lnwallet/log.go @@ -5,7 +5,6 @@ import ( "github.com/btcsuite/btcwallet/chain" btcwallet "github.com/btcsuite/btcwallet/wallet" "github.com/btcsuite/btcwallet/wtxmgr" - "github.com/lightningnetwork/lnd/build" "github.com/lightningnetwork/lnd/lnwallet/chainfee" ) diff --git a/lnwallet/parameters.go b/lnwallet/parameters.go index ccd5583d0d..36ee4baf8f 100644 --- a/lnwallet/parameters.go +++ b/lnwallet/parameters.go @@ -1,12 +1,44 @@ package lnwallet import ( + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/mempool" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/lnwire" ) +var ( + // RoutingFee100PercentUpTo is the cut-off amount we allow 100% fees to + // be charged up to. + RoutingFee100PercentUpTo = lnwire.NewMSatFromSatoshis(1_000) +) + +const ( + + // DefaultRoutingFeePercentage is the default off-chain routing fee we + // allow to be charged for a payment over the RoutingFee100PercentUpTo + // size. + DefaultRoutingFeePercentage lnwire.MilliSatoshi = 5 +) + +// DefaultRoutingFeeLimitForAmount returns the default off-chain routing fee +// limit lnd uses if the user does not specify a limit manually. The fee is +// amount dependent because of the base routing fee that is set on many +// channels. For example the default base fee is 1 satoshi. So sending a payment +// of one satoshi will cost 1 satoshi in fees over most channels, which comes to +// a fee of 100%. That's why for very small amounts we allow 100% fee. +func DefaultRoutingFeeLimitForAmount(a lnwire.MilliSatoshi) lnwire.MilliSatoshi { + // Allow 100% fees up to a certain amount to accommodate for base fees. + if a <= RoutingFee100PercentUpTo { + return a + } + + // Everything larger than the cut-off amount will get a default fee + // percentage. + return a * DefaultRoutingFeePercentage / 100 +} + // DustLimitForSize retrieves the dust limit for a given pkscript size. Given // the size, it automatically determines whether the script is a witness script // or not. It calls btcd's GetDustThreshold method under the hood. It must be diff --git a/lnwallet/parameters_test.go b/lnwallet/parameters_test.go index 021cb7bc67..3cee8f3e65 100644 --- a/lnwallet/parameters_test.go +++ b/lnwallet/parameters_test.go @@ -1,13 +1,52 @@ package lnwallet import ( + "fmt" "testing" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/lnwire" "github.com/stretchr/testify/require" ) +// TestDefaultRoutingFeeLimitForAmount tests that we use the correct default +// routing fee depending on the amount. +func TestDefaultRoutingFeeLimitForAmount(t *testing.T) { + t.Parallel() + + tests := []struct { + amount lnwire.MilliSatoshi + expectedLimit lnwire.MilliSatoshi + }{ + { + amount: 1, + expectedLimit: 1, + }, + { + amount: lnwire.NewMSatFromSatoshis(1_000), + expectedLimit: lnwire.NewMSatFromSatoshis(1_000), + }, + { + amount: lnwire.NewMSatFromSatoshis(1_001), + expectedLimit: 50_050, + }, + { + amount: 5_000_000_000, + expectedLimit: 250_000_000, + }, + } + + for _, test := range tests { + test := test + + t.Run(fmt.Sprintf("%d sats", test.amount), func(t *testing.T) { + feeLimit := DefaultRoutingFeeLimitForAmount(test.amount) + require.Equal(t, int64(test.expectedLimit), int64(feeLimit)) + }) + } +} + // TestDustLimitForSize tests that we receive the expected dust limits for // various script types from btcd's GetDustThreshold function. func TestDustLimitForSize(t *testing.T) { diff --git a/lnwallet/reservation.go b/lnwallet/reservation.go index c5e7dfc34f..c36ba170e3 100644 --- a/lnwallet/reservation.go +++ b/lnwallet/reservation.go @@ -5,10 +5,10 @@ import ( "net" "sync" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" @@ -349,7 +349,6 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount, default: chanType |= channeldb.NoFundingTxBit } - } else { // Otherwise, this is a dual funder channel, and no side is // technically the "initiator" @@ -404,8 +403,10 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount, FeePerKw: btcutil.Amount(commitFeePerKw), CommitFee: commitFee, }, - ThawHeight: thawHeight, - Db: wallet.Cfg.Database, + ThawHeight: thawHeight, + Db: wallet.Cfg.Database, + InitialLocalBalance: ourBalance, + InitialRemoteBalance: theirBalance, }, pushMSat: pushMSat, pendingChanID: pendingChanID, @@ -435,6 +436,7 @@ func (r *ChannelReservation) SetNumConfsRequired(numConfs uint16) { // if the parameters are seemed unsound. func (r *ChannelReservation) CommitConstraints(c *channeldb.ChannelConstraints, maxLocalCSVDelay uint16, responder bool) error { + r.Lock() defer r.Unlock() diff --git a/lnwallet/rpcwallet/healthcheck.go b/lnwallet/rpcwallet/healthcheck.go new file mode 100644 index 0000000000..7a412959e0 --- /dev/null +++ b/lnwallet/rpcwallet/healthcheck.go @@ -0,0 +1,33 @@ +package rpcwallet + +import ( + "fmt" + "time" + + "github.com/lightningnetwork/lnd/lncfg" +) + +// HealthCheck returns a health check function for the given remote signing +// configuration. +func HealthCheck(cfg *lncfg.RemoteSigner, timeout time.Duration) func() error { + return func() error { + conn, err := connectRPC( + cfg.RPCHost, cfg.TLSCertPath, cfg.MacaroonPath, timeout, + ) + if err != nil { + return fmt.Errorf("error connecting to the remote "+ + "signing node through RPC: %v", err) + } + + defer func() { + err = conn.Close() + if err != nil { + log.Warnf("Failed to close health check "+ + "connection to remote signing node: %v", + err) + } + }() + + return nil + } +} diff --git a/lnwallet/rpcwallet/log.go b/lnwallet/rpcwallet/log.go new file mode 100644 index 0000000000..190aa623ad --- /dev/null +++ b/lnwallet/rpcwallet/log.go @@ -0,0 +1,32 @@ +package rpcwallet + +import ( + "github.com/btcsuite/btclog" + "github.com/lightningnetwork/lnd/build" +) + +// Subsystem defines the logging code for this subsystem. +const Subsystem = "RPWL" + +// log is a logger that is initialized with no output filters. This means the +// package will not perform any logging by default until the caller requests +// it. +var log btclog.Logger + +// The default amount of logging is none. +func init() { + UseLogger(build.NewSubLogger(Subsystem, nil)) +} + +// DisableLog disables all library log output. Logging output is disabled by +// default until UseLogger is called. +func DisableLog() { + UseLogger(btclog.Disabled) +} + +// UseLogger uses a specified Logger to output package logging info. This +// should be used in preference to SetLogWriter if the caller is also using +// btclog. +func UseLogger(logger btclog.Logger) { + log = logger +} diff --git a/lnwallet/rpcwallet/rpcwallet.go b/lnwallet/rpcwallet/rpcwallet.go index b31881d15e..de47a0759c 100644 --- a/lnwallet/rpcwallet/rpcwallet.go +++ b/lnwallet/rpcwallet/rpcwallet.go @@ -3,42 +3,42 @@ package rpcwallet import ( "bytes" "context" + "crypto/sha256" "crypto/x509" - "encoding/binary" "errors" "fmt" "io/ioutil" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "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/hdkeychain" + "github.com/btcsuite/btcd/btcutil/psbt" + "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" - "github.com/btcsuite/btcutil/hdkeychain" - "github.com/btcsuite/btcutil/psbt" "github.com/btcsuite/btcwallet/waddrmgr" - btcwallet "github.com/btcsuite/btcwallet/wallet" + basewallet "github.com/btcsuite/btcwallet/wallet" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lnrpc/signrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" "github.com/lightningnetwork/lnd/lnwallet" + "github.com/lightningnetwork/lnd/lnwallet/btcwallet" "github.com/lightningnetwork/lnd/lnwallet/chainfee" - "github.com/lightningnetwork/lnd/lnwallet/chanfunding" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/macaroons" "google.golang.org/grpc" + "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials" + "google.golang.org/grpc/status" "gopkg.in/macaroon.v2" ) -const ( - // DefaultRPCTimeout is the default timeout that is used when forwarding - // a request to the remote signer through RPC. - DefaultRPCTimeout = 5 * time.Second -) - var ( // ErrRemoteSigningPrivateKeyNotAvailable is the error that is returned // if an operation is requested from the RPC wallet that is not @@ -58,6 +58,8 @@ type RPCKeyRing struct { watchOnlyKeyRing keychain.SecretKeyRing + netParams *chaincfg.Params + rpcTimeout time.Duration signerClient signrpc.SignerClient @@ -75,11 +77,11 @@ var _ lnwallet.WalletController = (*RPCKeyRing)(nil) func NewRPCKeyRing(watchOnlyKeyRing keychain.SecretKeyRing, watchOnlyWalletController lnwallet.WalletController, remoteSigner *lncfg.RemoteSigner, - rpcTimeout time.Duration) (*RPCKeyRing, error) { + netParams *chaincfg.Params) (*RPCKeyRing, error) { rpcConn, err := connectRPC( remoteSigner.RPCHost, remoteSigner.TLSCertPath, - remoteSigner.MacaroonPath, + remoteSigner.MacaroonPath, remoteSigner.Timeout, ) if err != nil { return nil, fmt.Errorf("error connecting to the remote "+ @@ -89,7 +91,8 @@ func NewRPCKeyRing(watchOnlyKeyRing keychain.SecretKeyRing, return &RPCKeyRing{ WalletController: watchOnlyWalletController, watchOnlyKeyRing: watchOnlyKeyRing, - rpcTimeout: rpcTimeout, + netParams: netParams, + rpcTimeout: remoteSigner.Timeout, signerClient: signrpc.NewSignerClient(rpcConn), walletClient: walletrpc.NewWalletKitClient(rpcConn), }, nil @@ -105,189 +108,7 @@ func NewRPCKeyRing(watchOnlyKeyRing keychain.SecretKeyRing, func (r *RPCKeyRing) NewAddress(addrType lnwallet.AddressType, change bool, account string) (btcutil.Address, error) { - ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout) - defer cancel() - - rpcAddrType := walletrpc.AddressType_WITNESS_PUBKEY_HASH - if addrType == lnwallet.NestedWitnessPubKey { - rpcAddrType = walletrpc.AddressType_NESTED_WITNESS_PUBKEY_HASH - } - - remoteAddr, err := r.walletClient.NextAddr(ctxt, &walletrpc.AddrRequest{ - Account: account, - Type: rpcAddrType, - Change: change, - }) - if err != nil { - return nil, fmt.Errorf("error deriving address on remote "+ - "signer instance: %v", err) - } - - localAddr, err := r.WalletController.NewAddress( - addrType, change, account, - ) - if err != nil { - return nil, fmt.Errorf("error deriving address on local "+ - "wallet instance: %v", err) - } - - // We need to make sure we've derived the same address on the remote - // signing machine, otherwise we don't know whether we're at the same - // address index (and therefore the same wallet state in general). - if localAddr.String() != remoteAddr.Addr { - return nil, fmt.Errorf("error deriving address on remote "+ - "signing instance, got different address (%s) than "+ - "on local wallet instance (%s)", remoteAddr.Addr, - localAddr.String()) - } - - return localAddr, nil -} - -// LastUnusedAddress returns the last *unused* address known by the wallet. An -// address is unused if it hasn't received any payments. This can be useful in -// UIs in order to continually show the "freshest" address without having to -// worry about "address inflation" caused by continual refreshing. Similar to -// NewAddress it can derive a specified address type, and also optionally a -// change address. The account parameter must be non-empty as it determines -// which account the address should be generated from. -func (r *RPCKeyRing) LastUnusedAddress(lnwallet.AddressType, - string) (btcutil.Address, error) { - - // Because the underlying wallet will create a new address if the last - // derived address has been used in the meantime, we would need to proxy - // that call as well. But since that's deep within the btcwallet code, - // we cannot easily proxy it without more refactoring. Since this is an - // address type that is probably not widely used we can probably get - // away with not supporting it. - return nil, fmt.Errorf("unused address types are not supported when " + - "remote signing is enabled") -} - -// ImportAccount imports an account backed by an account extended public key. -// The master key fingerprint denotes the fingerprint of the root key -// corresponding to the account public key (also known as the key with -// derivation path m/). This may be required by some hardware wallets for proper -// identification and signing. -// -// The address type can usually be inferred from the key's version, but may be -// required for certain keys to map them into the proper scope. -// -// For BIP-0044 keys, an address type must be specified as we intend to not -// support importing BIP-0044 keys into the wallet using the legacy -// pay-to-pubkey-hash (P2PKH) scheme. A nested witness address type will force -// the standard BIP-0049 derivation scheme, while a witness address type will -// force the standard BIP-0084 derivation scheme. -// -// For BIP-0049 keys, an address type must also be specified to make a -// distinction between the standard BIP-0049 address schema (nested witness -// pubkeys everywhere) and our own BIP-0049Plus address schema (nested pubkeys -// externally, witness pubkeys internally). -func (r *RPCKeyRing) ImportAccount(name string, - accountPubKey *hdkeychain.ExtendedKey, masterKeyFingerprint uint32, - addrType *waddrmgr.AddressType, - dryRun bool) (*waddrmgr.AccountProperties, []btcutil.Address, - []btcutil.Address, error) { - - ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout) - defer cancel() - - var masterKeyFingerprintBytes [4]byte - binary.BigEndian.PutUint32( - masterKeyFingerprintBytes[:], masterKeyFingerprint, - ) - - rpcAddrType, err := toRPCAddrType(addrType) - if err != nil { - return nil, nil, nil, fmt.Errorf("error converting address "+ - "type: %v", err) - } - - remoteAcct, err := r.walletClient.ImportAccount( - ctxt, &walletrpc.ImportAccountRequest{ - Name: name, - ExtendedPublicKey: accountPubKey.String(), - MasterKeyFingerprint: masterKeyFingerprintBytes[:], - AddressType: rpcAddrType, - DryRun: dryRun, - }, - ) - if err != nil { - return nil, nil, nil, fmt.Errorf("error importing account on "+ - "remote signer instance: %v", err) - } - - props, extAddrs, intAddrs, err := r.WalletController.ImportAccount( - name, accountPubKey, masterKeyFingerprint, addrType, dryRun, - ) - if err != nil { - return nil, nil, nil, fmt.Errorf("error importing account on "+ - "local wallet instance: %v", err) - } - - mismatchErr := fmt.Errorf("error importing account on remote signing "+ - "instance, got different external addresses (%v) than on "+ - "local wallet instance (%s)", remoteAcct.DryRunExternalAddrs, - extAddrs) - if len(remoteAcct.DryRunExternalAddrs) != len(extAddrs) { - return nil, nil, nil, mismatchErr - } - for idx, remoteExtAddr := range remoteAcct.DryRunExternalAddrs { - if extAddrs[idx].String() != remoteExtAddr { - return nil, nil, nil, mismatchErr - } - } - - mismatchErr = fmt.Errorf("error importing account on remote signing "+ - "instance, got different internal addresses (%v) than on "+ - "local wallet instance (%s)", remoteAcct.DryRunInternalAddrs, - intAddrs) - if len(remoteAcct.DryRunInternalAddrs) != len(intAddrs) { - return nil, nil, nil, mismatchErr - } - for idx, remoteIntAddr := range remoteAcct.DryRunInternalAddrs { - if intAddrs[idx].String() != remoteIntAddr { - return nil, nil, nil, mismatchErr - } - } - - return props, extAddrs, intAddrs, nil -} - -// ImportPublicKey imports a single derived public key into the wallet. The -// address type can usually be inferred from the key's version, but in the case -// of legacy versions (xpub, tpub), an address type must be specified as we -// intend to not support importing BIP-44 keys into the wallet using the legacy -// pay-to-pubkey-hash (P2PKH) scheme. -func (r *RPCKeyRing) ImportPublicKey(pubKey *btcec.PublicKey, - addrType waddrmgr.AddressType) error { - - ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout) - defer cancel() - - rpcAddrType, err := toRPCAddrType(&addrType) - if err != nil { - return fmt.Errorf("error converting address type: %v", err) - } - - _, err = r.walletClient.ImportPublicKey( - ctxt, &walletrpc.ImportPublicKeyRequest{ - PublicKey: pubKey.SerializeCompressed(), - AddressType: rpcAddrType, - }, - ) - if err != nil { - return fmt.Errorf("error importing pubkey on remote signer "+ - "instance: %v", err) - } - - err = r.WalletController.ImportPublicKey(pubKey, addrType) - if err != nil { - return fmt.Errorf("error importing pubkey on local signer "+ - "instance: %v", err) - } - - return nil + return r.WalletController.NewAddress(addrType, change, account) } // SendOutputs funds, signs, and broadcasts a Bitcoin transaction paying out to @@ -296,7 +117,9 @@ func (r *RPCKeyRing) ImportPublicKey(pubKey *btcec.PublicKey, // // NOTE: This method requires the global coin selection lock to be held. // -// This is a part of the WalletController interface. +// NOTE: This is a part of the WalletController interface. +// +// NOTE: This method only signs with BIP49/84 keys. func (r *RPCKeyRing) SendOutputs(outputs []*wire.TxOut, feeRate chainfee.SatPerKWeight, minConfs int32, label string) (*wire.MsgTx, error) { @@ -304,7 +127,7 @@ func (r *RPCKeyRing) SendOutputs(outputs []*wire.TxOut, tx, err := r.WalletController.SendOutputs( outputs, feeRate, minConfs, label, ) - if err != nil && err != btcwallet.ErrTxUnsigned { + if err != nil && err != basewallet.ErrTxUnsigned { return nil, err } if err == nil { @@ -315,16 +138,20 @@ func (r *RPCKeyRing) SendOutputs(outputs []*wire.TxOut, // We know at this point that we only have inputs from our own wallet. // So we can just compute the input script using the remote signer. + outputFetcher := lnwallet.NewWalletPrevOutputFetcher(r.WalletController) signDesc := input.SignDescriptor{ - HashType: txscript.SigHashAll, - SigHashes: txscript.NewTxSigHashes(tx), + HashType: txscript.SigHashAll, + SigHashes: txscript.NewTxSigHashes(tx, outputFetcher), + PrevOutputFetcher: outputFetcher, } for i, txIn := range tx.TxIn { // We can only sign this input if it's ours, so we'll ask the // watch-only wallet if it can map this outpoint into a coin we // own. If not, then we can't continue because our wallet state // is out of sync. - info, err := r.coinFromOutPoint(txIn.PreviousOutPoint) + info, err := r.WalletController.FetchInputInfo( + &txIn.PreviousOutPoint, + ) if err != nil { return nil, fmt.Errorf("error looking up utxo: %v", err) } @@ -332,7 +159,7 @@ func (r *RPCKeyRing) SendOutputs(outputs []*wire.TxOut, // Now that we know the input is ours, we'll populate the // signDesc with the per input unique information. signDesc.Output = &wire.TxOut{ - Value: info.Value, + Value: int64(info.Value), PkScript: info.PkScript, } signDesc.InputIndex = i @@ -351,19 +178,17 @@ func (r *RPCKeyRing) SendOutputs(outputs []*wire.TxOut, return tx, r.WalletController.PublishTransaction(tx, label) } -// FinalizePsbt expects a partial transaction with all inputs and outputs fully -// declared and tries to sign all inputs that belong to the specified account. -// Lnd must be the last signer of the transaction. That means, if there are any -// unsigned non-witness inputs or inputs without UTXO information attached or -// inputs without witness data that do not belong to lnd's wallet, this method -// will fail. If no error is returned, the PSBT is ready to be extracted and the -// final TX within to be broadcast. -// -// NOTE: This method does NOT publish the transaction after it's been -// finalized successfully. +// SignPsbt expects a partial transaction with all inputs and outputs fully +// declared and tries to sign all unsigned inputs that have all required fields +// (UTXO information, BIP32 derivation information, witness or sig scripts) set. +// If no error is returned, the PSBT is ready to be given to the next signer or +// to be finalized if lnd was the last signer. // -// This is a part of the WalletController interface. -func (r *RPCKeyRing) FinalizePsbt(packet *psbt.Packet, accountName string) error { +// NOTE: This RPC only signs inputs (and only those it can sign), it does not +// perform any other tasks (such as coin selection, UTXO locking or +// input/output/fee value validation, PSBT finalization). Any input that is +// incomplete will be skipped. +func (r *RPCKeyRing) SignPsbt(packet *psbt.Packet) error { ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout) defer cancel() @@ -372,14 +197,12 @@ func (r *RPCKeyRing) FinalizePsbt(packet *psbt.Packet, accountName string) error return fmt.Errorf("error serializing PSBT: %v", err) } - resp, err := r.walletClient.FinalizePsbt( - ctxt, &walletrpc.FinalizePsbtRequest{ - FundedPsbt: buf.Bytes(), - Account: accountName, - }, - ) + resp, err := r.walletClient.SignPsbt(ctxt, &walletrpc.SignPsbtRequest{ + FundedPsbt: buf.Bytes(), + }) if err != nil { - return fmt.Errorf("error finalizing PSBT in remote signer "+ + considerShutdown(err) + return fmt.Errorf("error signing PSBT in remote signer "+ "instance: %v", err) } @@ -401,48 +224,146 @@ func (r *RPCKeyRing) FinalizePsbt(packet *psbt.Packet, accountName string) error return nil } -// DeriveNextKey attempts to derive the *next* key within the key family -// (account in BIP43) specified. This method should return the next external -// child within this branch. +// FinalizePsbt expects a partial transaction with all inputs and outputs fully +// declared and tries to sign all inputs that belong to the specified account. +// Lnd must be the last signer of the transaction. That means, if there are any +// unsigned non-witness inputs or inputs without UTXO information attached or +// inputs without witness data that do not belong to lnd's wallet, this method +// will fail. If no error is returned, the PSBT is ready to be extracted and the +// final TX within to be broadcast. // -// NOTE: This method is part of the keychain.KeyRing interface. -func (r *RPCKeyRing) DeriveNextKey( - keyFam keychain.KeyFamily) (keychain.KeyDescriptor, error) { +// NOTE: This method does NOT publish the transaction after it's been +// finalized successfully. +// +// NOTE: This is a part of the WalletController interface. +// +// NOTE: We need to overwrite this method because we need to redirect the call +// to ComputeInputScript to the RPC key ring's implementation. If we forward +// the call to the default WalletController implementation, we get an error +// since that wallet is watch-only. If we forward the call to the remote signer, +// we get an error because the signer doesn't know the UTXO information required +// in ComputeInputScript. +// +// TODO(guggero): Refactor btcwallet to accept ComputeInputScript as a function +// parameter in FinalizePsbt so we can get rid of this code duplication. +func (r *RPCKeyRing) FinalizePsbt(packet *psbt.Packet, _ string) error { + // Let's check that this is actually something we can and want to sign. + // We need at least one input and one output. + err := psbt.VerifyInputOutputLen(packet, true, true) + if err != nil { + return err + } - ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout) - defer cancel() + // Go through each input that doesn't have final witness data attached + // to it already and try to sign it. We do expect that we're the last + // ones to sign. If there is any input without witness data that we + // cannot sign because it's not our UTXO, this will be a hard failure. + tx := packet.UnsignedTx + sigHashes := input.NewTxSigHashesV0Only(tx) + for idx, txIn := range tx.TxIn { + in := packet.Inputs[idx] + + // We can only sign if we have UTXO information available. We + // can just continue here as a later step will fail with a more + // precise error message. + if in.WitnessUtxo == nil && in.NonWitnessUtxo == nil { + continue + } - // We need to keep the local and remote wallet in sync. That's why we - // first attempt to also derive the next key on the remote wallet. - remoteDesc, err := r.walletClient.DeriveNextKey(ctxt, &walletrpc.KeyReq{ - KeyFamily: int32(keyFam), - }) - if err != nil { - return keychain.KeyDescriptor{}, fmt.Errorf("error deriving "+ - "key on remote signer instance: %v", err) + // Skip this input if it's got final witness data attached. + if len(in.FinalScriptWitness) > 0 { + continue + } + + // We can only sign this input if it's ours, so we try to map it + // to a coin we own. If we can't, then we'll continue as it + // isn't our input. + utxo, err := r.FetchInputInfo(&txIn.PreviousOutPoint) + if err != nil { + continue + } + fullTx := utxo.PrevTx + signDesc := &input.SignDescriptor{ + KeyDesc: keychain.KeyDescriptor{}, + Output: &wire.TxOut{ + Value: int64(utxo.Value), + PkScript: utxo.PkScript, + }, + HashType: in.SighashType, + SigHashes: sigHashes, + InputIndex: idx, + } + + // Find out what UTXO we are signing. Wallets _should_ always + // provide the full non-witness UTXO for segwit v0. + var signOutput *wire.TxOut + if in.NonWitnessUtxo != nil { + prevIndex := txIn.PreviousOutPoint.Index + signOutput = in.NonWitnessUtxo.TxOut[prevIndex] + + if !psbt.TxOutsEqual(signDesc.Output, signOutput) { + return fmt.Errorf("found UTXO %#v but it "+ + "doesn't match PSBT's input %v", + signDesc.Output, signOutput) + } + + if fullTx.TxHash() != txIn.PreviousOutPoint.Hash { + return fmt.Errorf("found UTXO tx %v but it "+ + "doesn't match PSBT's input %v", + fullTx.TxHash(), + txIn.PreviousOutPoint.Hash) + } + } + + // Fall back to witness UTXO only for older wallets. + if in.WitnessUtxo != nil { + signOutput = in.WitnessUtxo + + if !psbt.TxOutsEqual(signDesc.Output, signOutput) { + return fmt.Errorf("found UTXO %#v but it "+ + "doesn't match PSBT's input %v", + signDesc.Output, signOutput) + } + } + + // Do the actual signing in ComputeInputScript which in turn + // will invoke the remote signer. + script, err := r.ComputeInputScript(tx, signDesc) + if err != nil { + return fmt.Errorf("error computing input script for "+ + "input %d: %v", idx, err) + } + + // Serialize the witness format from the stack representation to + // the wire representation. + var witnessBytes bytes.Buffer + err = psbt.WriteTxWitness(&witnessBytes, script.Witness) + if err != nil { + return fmt.Errorf("error serializing witness: %v", err) + } + packet.Inputs[idx].FinalScriptWitness = witnessBytes.Bytes() + packet.Inputs[idx].FinalScriptSig = script.SigScript } - localDesc, err := r.watchOnlyKeyRing.DeriveNextKey(keyFam) + // Make sure the PSBT itself thinks it's finalized and ready to be + // broadcast. + err = psbt.MaybeFinalizeAll(packet) if err != nil { - return keychain.KeyDescriptor{}, fmt.Errorf("error deriving "+ - "key on local wallet instance: %v", err) + return fmt.Errorf("error finalizing PSBT: %v", err) } - // We never know if the administrator of the remote signing wallet does - // manual calls to next address or whatever. So we cannot be certain - // that we're always fully in sync. But as long as our local index is - // lower or equal to the remote index we know the remote wallet should - // have all keys we have locally. Only if the remote wallet falls behind - // the local we might have problems that the remote wallet won't know - // outputs we're giving it to sign. - if uint32(remoteDesc.KeyLoc.KeyIndex) < localDesc.Index { - return keychain.KeyDescriptor{}, fmt.Errorf("error deriving "+ - "key on remote signer instance, derived index %d was "+ - "lower than local index %d", remoteDesc.KeyLoc.KeyIndex, - localDesc.Index) - } + return nil +} + +// DeriveNextKey attempts to derive the *next* key within the key family +// (account in BIP43) specified. This method should return the next external +// child within this branch. +// +// NOTE: This method is part of the keychain.KeyRing interface. +func (r *RPCKeyRing) DeriveNextKey( + keyFam keychain.KeyFamily) (keychain.KeyDescriptor, error) { - return localDesc, nil + return r.watchOnlyKeyRing.DeriveNextKey(keyFam) } // DeriveKey attempts to derive an arbitrary key specified by the passed @@ -453,41 +374,7 @@ func (r *RPCKeyRing) DeriveNextKey( func (r *RPCKeyRing) DeriveKey( keyLoc keychain.KeyLocator) (keychain.KeyDescriptor, error) { - ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout) - defer cancel() - - // We need to keep the local and remote wallet in sync. That's why we - // first attempt to also derive the same key on the remote wallet. - remoteDesc, err := r.walletClient.DeriveKey(ctxt, &signrpc.KeyLocator{ - KeyFamily: int32(keyLoc.Family), - KeyIndex: int32(keyLoc.Index), - }) - if err != nil { - return keychain.KeyDescriptor{}, fmt.Errorf("error deriving "+ - "key on remote signer instance: %v", err) - } - - localDesc, err := r.watchOnlyKeyRing.DeriveKey(keyLoc) - if err != nil { - return keychain.KeyDescriptor{}, fmt.Errorf("error deriving "+ - "key on local wallet instance: %v", err) - } - - // We never know if the administrator of the remote signing wallet does - // manual calls to next address or whatever. So we cannot be certain - // that we're always fully in sync. But as long as our local index is - // lower or equal to the remote index we know the remote wallet should - // have all keys we have locally. Only if the remote wallet falls behind - // the local we might have problems that the remote wallet won't know - // outputs we're giving it to sign. - if uint32(remoteDesc.KeyLoc.KeyIndex) < localDesc.Index { - return keychain.KeyDescriptor{}, fmt.Errorf("error deriving "+ - "key on remote signer instance, derived index %d was "+ - "lower than local index %d", remoteDesc.KeyLoc.KeyIndex, - localDesc.Index) - } - - return localDesc, nil + return r.watchOnlyKeyRing.DeriveKey(keyLoc) } // ECDH performs a scalar multiplication (ECDH-like operation) between the @@ -522,7 +409,9 @@ func (r *RPCKeyRing) ECDH(keyDesc keychain.KeyDescriptor, resp, err := r.signerClient.DeriveSharedKey(ctxt, req) if err != nil { - return key, err + considerShutdown(err) + return key, fmt.Errorf("error deriving shared key in remote "+ + "signer instance: %v", err) } copy(key[:], resp.SharedKey) @@ -536,7 +425,7 @@ func (r *RPCKeyRing) ECDH(keyDesc keychain.KeyDescriptor, // // NOTE: This method is part of the keychain.MessageSignerRing interface. func (r *RPCKeyRing) SignMessage(keyLoc keychain.KeyLocator, - msg []byte, doubleHash bool) (*btcec.Signature, error) { + msg []byte, doubleHash bool) (*ecdsa.Signature, error) { ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout) defer cancel() @@ -550,7 +439,9 @@ func (r *RPCKeyRing) SignMessage(keyLoc keychain.KeyLocator, DoubleHash: doubleHash, }) if err != nil { - return nil, err + considerShutdown(err) + return nil, fmt.Errorf("error signing message in remote "+ + "signer instance: %v", err) } wireSig, err := lnwire.NewSigFromRawSignature(resp.Signature) @@ -586,7 +477,9 @@ func (r *RPCKeyRing) SignMessageCompact(keyLoc keychain.KeyLocator, CompactSig: true, }) if err != nil { - return nil, err + considerShutdown(err) + return nil, fmt.Errorf("error signing message in remote "+ + "signer instance: %v", err) } // The signature in the response is zbase32 encoded, so we need to @@ -618,23 +511,15 @@ func (r *RPCKeyRing) DerivePrivKey(_ keychain.KeyDescriptor) (*btcec.PrivateKey, // NOTE: The resulting signature should be void of a sighash byte. // // NOTE: This method is part of the input.Signer interface. +// +// NOTE: This method only signs with BIP1017 (internal) keys! func (r *RPCKeyRing) SignOutputRaw(tx *wire.MsgTx, signDesc *input.SignDescriptor) (input.Signature, error) { - ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout) - defer cancel() - - rpcSignReq, err := toRPCSignReq(tx, signDesc) - if err != nil { - return nil, err - } - - resp, err := r.signerClient.SignOutputRaw(ctxt, rpcSignReq) - if err != nil { - return nil, err - } - - return btcec.ParseDERSignature(resp.RawSigs[0], btcec.S256()) + // Forward the call to the remote signing instance. This call is only + // ever called for signing witness (p2pkh or p2wsh) inputs and never + // nested witness inputs, so the sigScript is always nil. + return r.remoteSign(tx, signDesc, nil) } // ComputeInputScript generates a complete InputIndex for the passed @@ -645,121 +530,658 @@ func (r *RPCKeyRing) SignOutputRaw(tx *wire.MsgTx, // // NOTE: This method will ignore any tweak parameters set within the // passed SignDescriptor as it assumes a set of typical script -// templates (p2wkh, np2wkh, etc). +// templates (p2wkh, np2wkh, BIP0086 p2tr, etc). // // NOTE: This method is part of the input.Signer interface. func (r *RPCKeyRing) ComputeInputScript(tx *wire.MsgTx, signDesc *input.SignDescriptor) (*input.Script, error) { - ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout) - defer cancel() - - rpcSignReq, err := toRPCSignReq(tx, signDesc) + addr, witnessProgram, sigScript, err := r.WalletController.ScriptForOutput( + signDesc.Output, + ) if err != nil { return nil, err } + signDesc.WitnessScript = witnessProgram + + // If this is a p2tr address, then it must be a BIP0086 key spend if we + // are coming through this path (instead of SignOutputRaw). + switch addr.AddrType() { + case waddrmgr.TaprootPubKey: + signDesc.SignMethod = input.TaprootKeySpendBIP0086SignMethod + signDesc.WitnessScript = nil + + sig, err := r.remoteSign(tx, signDesc, sigScript) + if err != nil { + return nil, fmt.Errorf("error signing with remote"+ + "instance: %v", err) + } + + rawSig := sig.Serialize() + if signDesc.HashType != txscript.SigHashDefault { + rawSig = append(rawSig, byte(signDesc.HashType)) + } + + return &input.Script{ + Witness: wire.TxWitness{ + rawSig, + }, + }, nil + + case waddrmgr.TaprootScript: + return nil, fmt.Errorf("computing input script for taproot " + + "script address not supported") + } - resp, err := r.signerClient.ComputeInputScript(ctxt, rpcSignReq) + // Let's give the TX to the remote instance now, so it can sign the + // input. + sig, err := r.remoteSign(tx, signDesc, sigScript) if err != nil { - return nil, err + return nil, fmt.Errorf("error signing with remote instance: %v", + err) } + // ComputeInputScript currently is only used for P2WKH and NP2WKH + // addresses. So the last item on the stack is always the compressed + // public key. return &input.Script{ - Witness: resp.InputScripts[0].Witness, - SigScript: resp.InputScripts[0].SigScript, + Witness: wire.TxWitness{ + append(sig.Serialize(), byte(signDesc.HashType)), + addr.PubKey().SerializeCompressed(), + }, + SigScript: sigScript, }, nil } -// coinFromOutPoint attempts to locate details pertaining to a coin based on -// its outpoint. If the coin isn't under the control of the backing watch-only -// wallet, then an error is returned. -func (r *RPCKeyRing) coinFromOutPoint(op wire.OutPoint) (*chanfunding.Coin, +// MuSig2CreateSession creates a new MuSig2 signing session using the local +// key identified by the key locator. The complete list of all public keys of +// all signing parties must be provided, including the public key of the local +// signing key. If nonces of other parties are already known, they can be +// submitted as well to reduce the number of method calls necessary later on. +func (r *RPCKeyRing) MuSig2CreateSession(keyLoc keychain.KeyLocator, + pubKeys []*btcec.PublicKey, tweaks *input.MuSig2Tweaks, + otherNonces [][musig2.PubNonceSize]byte) (*input.MuSig2SessionInfo, error) { - inputInfo, err := r.WalletController.FetchInputInfo(&op) + // We need to serialize all data for the RPC call. We can do that by + // putting everything directly into the request struct. + req := &signrpc.MuSig2SessionRequest{ + KeyLoc: &signrpc.KeyLocator{ + KeyFamily: int32(keyLoc.Family), + KeyIndex: int32(keyLoc.Index), + }, + AllSignerPubkeys: make([][]byte, len(pubKeys)), + Tweaks: make( + []*signrpc.TweakDesc, len(tweaks.GenericTweaks), + ), + OtherSignerPublicNonces: make([][]byte, len(otherNonces)), + } + for idx, pubKey := range pubKeys { + req.AllSignerPubkeys[idx] = schnorr.SerializePubKey(pubKey) + } + for idx, genericTweak := range tweaks.GenericTweaks { + req.Tweaks[idx] = &signrpc.TweakDesc{ + Tweak: genericTweak.Tweak[:], + IsXOnly: genericTweak.IsXOnly, + } + } + for idx, nonce := range otherNonces { + req.OtherSignerPublicNonces[idx] = make([]byte, len(nonce)) + copy(req.OtherSignerPublicNonces[idx], nonce[:]) + } + if tweaks.HasTaprootTweak() { + req.TaprootTweak = &signrpc.TaprootTweakDesc{ + KeySpendOnly: tweaks.TaprootBIP0086Tweak, + ScriptRoot: tweaks.TaprootTweak, + } + } + + ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout) + defer cancel() + + resp, err := r.signerClient.MuSig2CreateSession(ctxt, req) if err != nil { - return nil, err + considerShutdown(err) + return nil, fmt.Errorf("error creating MuSig2 session in "+ + "remote signer instance: %v", err) } - return &chanfunding.Coin{ - TxOut: wire.TxOut{ - Value: int64(inputInfo.Value), - PkScript: inputInfo.PkScript, - }, - OutPoint: inputInfo.OutPoint, - }, nil + // De-Serialize all the info back into our native struct. + info := &input.MuSig2SessionInfo{ + TaprootTweak: tweaks.HasTaprootTweak(), + HaveAllNonces: resp.HaveAllNonces, + } + copy(info.SessionID[:], resp.SessionId) + copy(info.PublicNonce[:], resp.LocalPublicNonces) + + info.CombinedKey, err = schnorr.ParsePubKey(resp.CombinedKey) + if err != nil { + return nil, fmt.Errorf("error parsing combined key: %v", err) + } + + if tweaks.HasTaprootTweak() { + info.TaprootInternalKey, err = schnorr.ParsePubKey( + resp.TaprootInternalKey, + ) + if err != nil { + return nil, fmt.Errorf("error parsing internal key: %v", + err) + } + } + + return info, nil } -// toRPCSignReq converts the given raw transaction and sign descriptors into -// their corresponding RPC counterparts. -func toRPCSignReq(tx *wire.MsgTx, - signDesc *input.SignDescriptor) (*signrpc.SignReq, error) { +// MuSig2RegisterNonces registers one or more public nonces of other signing +// participants for a session identified by its ID. This method returns true +// once we have all nonces for all other signing participants. +func (r *RPCKeyRing) MuSig2RegisterNonces(sessionID input.MuSig2SessionID, + pubNonces [][musig2.PubNonceSize]byte) (bool, error) { + + // We need to serialize all data for the RPC call. We can do that by + // putting everything directly into the request struct. + req := &signrpc.MuSig2RegisterNoncesRequest{ + SessionId: sessionID[:], + OtherSignerPublicNonces: make([][]byte, len(pubNonces)), + } + for idx, nonce := range pubNonces { + req.OtherSignerPublicNonces[idx] = make([]byte, len(nonce)) + copy(req.OtherSignerPublicNonces[idx], nonce[:]) + } - if signDesc.Output == nil { - return nil, fmt.Errorf("need output to sign") + ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout) + defer cancel() + + resp, err := r.signerClient.MuSig2RegisterNonces(ctxt, req) + if err != nil { + considerShutdown(err) + return false, fmt.Errorf("error registering MuSig2 nonces in "+ + "remote signer instance: %v", err) } - var buf bytes.Buffer - if err := tx.Serialize(&buf); err != nil { - return nil, err + return resp.HaveAllNonces, nil +} + +// MuSig2Sign creates a partial signature using the local signing key +// that was specified when the session was created. This can only be +// called when all public nonces of all participants are known and have +// been registered with the session. If this node isn't responsible for +// combining all the partial signatures, then the cleanup parameter +// should be set, indicating that the session can be removed from memory +// once the signature was produced. +func (r *RPCKeyRing) MuSig2Sign(sessionID input.MuSig2SessionID, + msg [sha256.Size]byte, cleanUp bool) (*musig2.PartialSignature, error) { + + // We need to serialize all data for the RPC call. We can do that by + // putting everything directly into the request struct. + req := &signrpc.MuSig2SignRequest{ + SessionId: sessionID[:], + MessageDigest: msg[:], + Cleanup: cleanUp, } - rpcSignDesc := &signrpc.SignDescriptor{ - KeyDesc: &signrpc.KeyDescriptor{ - KeyLoc: &signrpc.KeyLocator{ - KeyFamily: int32(signDesc.KeyDesc.Family), - KeyIndex: int32(signDesc.KeyDesc.Index), + ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout) + defer cancel() + + resp, err := r.signerClient.MuSig2Sign(ctxt, req) + if err != nil { + considerShutdown(err) + return nil, fmt.Errorf("error signing MuSig2 session in "+ + "remote signer instance: %v", err) + } + + partialSig, err := input.DeserializePartialSignature( + resp.LocalPartialSignature, + ) + if err != nil { + return nil, fmt.Errorf("error parsing partial signature from "+ + "remote signer: %v", err) + } + + return partialSig, nil +} + +// MuSig2CombineSig combines the given partial signature(s) with the +// local one, if it already exists. Once a partial signature of all +// participants is registered, the final signature will be combined and +// returned. +func (r *RPCKeyRing) MuSig2CombineSig(sessionID input.MuSig2SessionID, + partialSigs []*musig2.PartialSignature) (*schnorr.Signature, bool, + error) { + + // We need to serialize all data for the RPC call. We can do that by + // putting everything directly into the request struct. + req := &signrpc.MuSig2CombineSigRequest{ + SessionId: sessionID[:], + OtherPartialSignatures: make([][]byte, len(partialSigs)), + } + for idx, partialSig := range partialSigs { + rawSig, err := input.SerializePartialSignature(partialSig) + if err != nil { + return nil, false, fmt.Errorf("error serializing "+ + "partial signature: %v", err) + } + req.OtherPartialSignatures[idx] = rawSig[:] + } + + ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout) + defer cancel() + + resp, err := r.signerClient.MuSig2CombineSig(ctxt, req) + if err != nil { + considerShutdown(err) + return nil, false, fmt.Errorf("error combining MuSig2 "+ + "signatures in remote signer instance: %v", err) + } + + // The final signature is only available when we have all the other + // partial signatures from all participants. + if !resp.HaveAllSignatures { + return nil, resp.HaveAllSignatures, nil + } + + finalSig, err := schnorr.ParseSignature(resp.FinalSignature) + if err != nil { + return nil, false, fmt.Errorf("error parsing final signature: "+ + "%v", err) + } + + return finalSig, resp.HaveAllSignatures, nil +} + +// MuSig2Cleanup removes a session from memory to free up resources. +func (r *RPCKeyRing) MuSig2Cleanup(sessionID input.MuSig2SessionID) error { + req := &signrpc.MuSig2CleanupRequest{ + SessionId: sessionID[:], + } + + ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout) + defer cancel() + + _, err := r.signerClient.MuSig2Cleanup(ctxt, req) + if err != nil { + considerShutdown(err) + return fmt.Errorf("error cleaning up MuSig2 session in remote "+ + "signer instance: %v", err) + } + + return nil +} + +// remoteSign signs the input specified in signDesc of the given transaction tx +// using the remote signing instance. +func (r *RPCKeyRing) remoteSign(tx *wire.MsgTx, signDesc *input.SignDescriptor, + sigScript []byte) (input.Signature, error) { + + packet, err := packetFromTx(tx) + if err != nil { + return nil, fmt.Errorf("error converting TX into PSBT: %v", err) + } + + // We need to add witness information for all inputs! Otherwise, we'll + // have a problem when attempting to sign a taproot input! + for idx := range packet.Inputs { + // Skip the input we're signing for, that will get a special + // treatment later on. + if idx == signDesc.InputIndex { + continue + } + + txIn := tx.TxIn[idx] + info, err := r.WalletController.FetchInputInfo( + &txIn.PreviousOutPoint, + ) + if err != nil { + // Maybe we have an UTXO in the previous output fetcher? + if signDesc.PrevOutputFetcher != nil { + utxo := signDesc.PrevOutputFetcher.FetchPrevOutput( + txIn.PreviousOutPoint, + ) + if utxo != nil && utxo.Value != 0 && + len(utxo.PkScript) > 0 { + + packet.Inputs[idx].WitnessUtxo = utxo + continue + } + } + + log.Warnf("No UTXO info found for index %d "+ + "(prev_outpoint=%v), won't be able to sign "+ + "for taproot output!", idx, + txIn.PreviousOutPoint) + continue + } + packet.Inputs[idx].WitnessUtxo = &wire.TxOut{ + Value: int64(info.Value), + PkScript: info.PkScript, + } + } + + // Catch incorrect signing input index, just in case. + if signDesc.InputIndex < 0 || signDesc.InputIndex >= len(packet.Inputs) { + return nil, fmt.Errorf("invalid input index in sign descriptor") + } + in := &packet.Inputs[signDesc.InputIndex] + txIn := tx.TxIn[signDesc.InputIndex] + + // Things are a bit tricky with the sign descriptor. There basically are + // four ways to describe a key: + // 1. By public key only. To match this case both family and index + // must be set to 0. + // 2. By family and index only. To match this case the public key + // must be nil and either the family or index must be non-zero. + // 3. All values are set and locator is non-empty. To match this case + // the public key must be set and either the family or index must + // be non-zero. + // 4. All values are set and locator is empty. This is a special case + // for the very first channel ever created (with the multi-sig key + // family which is 0 and the index which is 0 as well). This looks + // identical to case 1 and will also be handled like that case. + // We only really handle case 1 and 2 here, since 3 is no problem and 4 + // is identical to 1. + switch { + // Case 1: Public key only. We need to find out the derivation path for + // this public key by asking the wallet. This is only possible for our + // internal, custom 1017 scope since we know all keys derived there are + // internally stored as p2wkh addresses. + case signDesc.KeyDesc.PubKey != nil && signDesc.KeyDesc.IsEmpty(): + pubKeyBytes := signDesc.KeyDesc.PubKey.SerializeCompressed() + addr, err := btcutil.NewAddressWitnessPubKeyHash( + btcutil.Hash160(pubKeyBytes), r.netParams, + ) + if err != nil { + return nil, fmt.Errorf("error deriving address from "+ + "public key %x: %v", pubKeyBytes, err) + } + + managedAddr, err := r.AddressInfo(addr) + if err != nil { + return nil, fmt.Errorf("error fetching address info "+ + "for public key %x: %v", pubKeyBytes, err) + } + + pubKeyAddr, ok := managedAddr.(waddrmgr.ManagedPubKeyAddress) + if !ok { + return nil, fmt.Errorf("address derived for public "+ + "key %x is not a p2wkh address", pubKeyBytes) + } + + scope, path, _ := pubKeyAddr.DerivationInfo() + if scope.Purpose != keychain.BIP0043Purpose { + return nil, fmt.Errorf("address derived for public "+ + "key %x is not in custom key scope %d'", + pubKeyBytes, keychain.BIP0043Purpose) + } + + // We now have all the information we need to complete our key + // locator information. + signDesc.KeyDesc.KeyLocator = keychain.KeyLocator{ + Family: keychain.KeyFamily(path.InternalAccount), + Index: path.Index, + } + + // Case 2: Family and index only. This case is easy, we can just go + // ahead and derive the public key from the family and index and then + // supply that information in the BIP32 derivation field. + case signDesc.KeyDesc.PubKey == nil && !signDesc.KeyDesc.IsEmpty(): + fullDesc, err := r.watchOnlyKeyRing.DeriveKey( + signDesc.KeyDesc.KeyLocator, + ) + if err != nil { + return nil, fmt.Errorf("error deriving key with "+ + "family %d and index %d from the watch-only "+ + "wallet: %v", + signDesc.KeyDesc.KeyLocator.Family, + signDesc.KeyDesc.KeyLocator.Index, err) + } + signDesc.KeyDesc.PubKey = fullDesc.PubKey + } + + // Make sure we actually know about the input. We either have been + // watching the UTXO on-chain or we have been given all the required + // info in the sign descriptor. + info, err := r.WalletController.FetchInputInfo(&txIn.PreviousOutPoint) + switch { + // No error, we do have the full UTXO and derivation info available. + case err == nil: + in.WitnessUtxo = &wire.TxOut{ + Value: int64(info.Value), + PkScript: info.PkScript, + } + in.NonWitnessUtxo = info.PrevTx + in.Bip32Derivation = []*psbt.Bip32Derivation{info.Derivation} + + // The wallet doesn't know about this UTXO, so it's probably a TX that + // we haven't published yet (e.g. a channel funding TX). So we need to + // assemble everything from the sign descriptor. We won't be able to + // supply a non-witness UTXO (=full TX of the input being spent) in this + // case. That is no problem if the signing instance is another lnd + // instance since we don't require it for pure witness inputs. But a + // hardware wallet might require it for security reasons. + case signDesc.KeyDesc.PubKey != nil && signDesc.Output != nil: + in.WitnessUtxo = signDesc.Output + in.Bip32Derivation = []*psbt.Bip32Derivation{{ + Bip32Path: []uint32{ + keychain.BIP0043Purpose + + hdkeychain.HardenedKeyStart, + r.netParams.HDCoinType + + hdkeychain.HardenedKeyStart, + uint32(signDesc.KeyDesc.Family) + + hdkeychain.HardenedKeyStart, + 0, + signDesc.KeyDesc.Index, }, - }, - SingleTweak: signDesc.SingleTweak, - WitnessScript: signDesc.WitnessScript, - Output: &signrpc.TxOut{ - Value: signDesc.Output.Value, - PkScript: signDesc.Output.PkScript, - }, - Sighash: uint32(signDesc.HashType), - InputIndex: int32(signDesc.InputIndex), + PubKey: signDesc.KeyDesc.PubKey.SerializeCompressed(), + }} + + // We need to specify a pk script in the witness UTXO, otherwise + // the field becomes invalid when serialized as a PSBT. To avoid + // running into a generic "Invalid PSBT serialization format" + // error later, we return a more descriptive error now. + if len(in.WitnessUtxo.PkScript) == 0 { + return nil, fmt.Errorf("error assembling UTXO " + + "information, output not known to wallet and " + + "no UTXO pk script provided in sign descriptor") + } + + default: + return nil, fmt.Errorf("error assembling UTXO information, "+ + "wallet returned err='%v' and sign descriptor is "+ + "incomplete", err) } - if signDesc.KeyDesc.PubKey != nil { - rpcSignDesc.KeyDesc.RawKeyBytes = - signDesc.KeyDesc.PubKey.SerializeCompressed() + // Assemble all other information about the input we have. + in.RedeemScript = sigScript + in.SighashType = signDesc.HashType + in.WitnessScript = signDesc.WitnessScript + + if len(signDesc.SingleTweak) > 0 { + in.Unknowns = append(in.Unknowns, &psbt.Unknown{ + Key: btcwallet.PsbtKeyTypeInputSignatureTweakSingle, + Value: signDesc.SingleTweak, + }) } if signDesc.DoubleTweak != nil { - rpcSignDesc.DoubleTweak = signDesc.DoubleTweak.Serialize() + in.Unknowns = append(in.Unknowns, &psbt.Unknown{ + Key: btcwallet.PsbtKeyTypeInputSignatureTweakDouble, + Value: signDesc.DoubleTweak.Serialize(), + }) } - return &signrpc.SignReq{ - RawTxBytes: buf.Bytes(), - SignDescs: []*signrpc.SignDescriptor{rpcSignDesc}, - }, nil -} + // Add taproot specific fields. + switch signDesc.SignMethod { + case input.TaprootKeySpendBIP0086SignMethod, + input.TaprootKeySpendSignMethod: + + // The key identifying factor for a key spend is that we don't + // provide any leaf hashes to signal we want a signature for the + // key spend path (with the internal key). + d := in.Bip32Derivation[0] + in.TaprootBip32Derivation = []*psbt.TaprootBip32Derivation{{ + // The x-only public key is just our compressed public + // key without the first byte (type/parity). + XOnlyPubKey: d.PubKey[1:], + LeafHashes: nil, + MasterKeyFingerprint: d.MasterKeyFingerprint, + Bip32Path: d.Bip32Path, + }} + + // If this is a BIP0086 key spend then the tap tweak is empty, + // otherwise it's set to the Taproot root hash. + in.TaprootMerkleRoot = signDesc.TapTweak + + case input.TaprootScriptSpendSignMethod: + // The script spend path is a bit more involved when doing it + // through the PSBT method. We need to specify the leaf hash + // that the signer should sign for. + leaf := txscript.TapLeaf{ + LeafVersion: txscript.BaseLeafVersion, + Script: signDesc.WitnessScript, + } + leafHash := leaf.TapHash() + + d := in.Bip32Derivation[0] + in.TaprootBip32Derivation = []*psbt.TaprootBip32Derivation{{ + XOnlyPubKey: d.PubKey[1:], + LeafHashes: [][]byte{leafHash[:]}, + MasterKeyFingerprint: d.MasterKeyFingerprint, + Bip32Path: d.Bip32Path, + }} + + // We also need to supply a control block. But because we don't + // know the internal key nor the merkle proofs (both is not + // supplied through the SignOutputRaw RPC) and is technically + // not really needed by the signer (since we only want a + // signature, the full witness stack is assembled by the caller + // of this RPC), we can get by with faking certain information + // that we don't have. + fakeInternalKey, _ := btcec.ParsePubKey(d.PubKey) + fakeKeyIsOdd := d.PubKey[0] == input.PubKeyFormatCompressedOdd + controlBlock := txscript.ControlBlock{ + InternalKey: fakeInternalKey, + OutputKeyYIsOdd: fakeKeyIsOdd, + LeafVersion: leaf.LeafVersion, + } + blockBytes, err := controlBlock.ToBytes() + if err != nil { + return nil, fmt.Errorf("error serializing control "+ + "block: %v", err) + } -// toRPCAddrType converts the given address type to its RPC counterpart. -func toRPCAddrType(addrType *waddrmgr.AddressType) (walletrpc.AddressType, - error) { + in.TaprootLeafScript = []*psbt.TaprootTapLeafScript{{ + ControlBlock: blockBytes, + Script: leaf.Script, + LeafVersion: leaf.LeafVersion, + }} + } - if addrType == nil { - return walletrpc.AddressType_UNKNOWN, nil + // Okay, let's sign the input by the remote signer now. + ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout) + defer cancel() + + var buf bytes.Buffer + if err := packet.Serialize(&buf); err != nil { + return nil, fmt.Errorf("error serializing PSBT: %v", err) + } + + resp, err := r.walletClient.SignPsbt( + ctxt, &walletrpc.SignPsbtRequest{FundedPsbt: buf.Bytes()}, + ) + if err != nil { + considerShutdown(err) + return nil, fmt.Errorf("error signing PSBT in remote signer "+ + "instance: %v", err) } - switch *addrType { - case waddrmgr.WitnessPubKey: - return walletrpc.AddressType_WITNESS_PUBKEY_HASH, nil + signedPacket, err := psbt.NewFromRawBytes( + bytes.NewReader(resp.SignedPsbt), false, + ) + if err != nil { + return nil, fmt.Errorf("error parsing signed PSBT: %v", err) + } - case waddrmgr.NestedWitnessPubKey: - return walletrpc.AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH, - nil + // We expect a signature in the input now. + if signDesc.InputIndex >= len(signedPacket.Inputs) { + return nil, fmt.Errorf("remote signer returned invalid PSBT") + } + in = &signedPacket.Inputs[signDesc.InputIndex] + + return extractSignature(in, signDesc.SignMethod) +} + +// extractSignature attempts to extract the signature from the PSBT input, +// looking at different fields depending on the signing method that was used. +func extractSignature(in *psbt.PInput, + signMethod input.SignMethod) (input.Signature, error) { + + switch signMethod { + case input.WitnessV0SignMethod: + if len(in.PartialSigs) != 1 { + return nil, fmt.Errorf("remote signer returned "+ + "invalid partial signature, wanted 1, got %d", + len(in.PartialSigs)) + } + sigWithSigHash := in.PartialSigs[0] + if sigWithSigHash == nil { + return nil, fmt.Errorf("remote signer returned nil " + + "signature") + } + + // The remote signer always adds the sighash type, so we need to + // account for that. + sigLen := len(sigWithSigHash.Signature) + if sigLen < ecdsa.MinSigLen+1 { + return nil, fmt.Errorf("remote signer returned "+ + "invalid partial signature: signature too "+ + "short with %d bytes", sigLen) + } + + // Parse the signature, but chop off the last byte which is the + // sighash type. + sig := sigWithSigHash.Signature[0 : sigLen-1] + return ecdsa.ParseDERSignature(sig) + + // The type of key spend doesn't matter, the signature should be in the + // same field for both of those signing methods. + case input.TaprootKeySpendBIP0086SignMethod, + input.TaprootKeySpendSignMethod: + + sigLen := len(in.TaprootKeySpendSig) + if sigLen < schnorr.SignatureSize { + return nil, fmt.Errorf("remote signer returned "+ + "invalid key spend signature: signature too "+ + "short with %d bytes", sigLen) + } + + return schnorr.ParseSignature( + in.TaprootKeySpendSig[:schnorr.SignatureSize], + ) + + case input.TaprootScriptSpendSignMethod: + if len(in.TaprootScriptSpendSig) != 1 { + return nil, fmt.Errorf("remote signer returned "+ + "invalid taproot script spend signature, "+ + "wanted 1, got %d", + len(in.TaprootScriptSpendSig)) + } + scriptSpendSig := in.TaprootScriptSpendSig[0] + if scriptSpendSig == nil { + return nil, fmt.Errorf("remote signer returned nil " + + "taproot script spend signature") + } + + return schnorr.ParseSignature(scriptSpendSig.Signature) default: - return 0, fmt.Errorf("unhandled address type %v", *addrType) + return nil, fmt.Errorf("can't extract signature, unsupported "+ + "signing method: %v", signMethod) } } // connectRPC tries to establish an RPC connection to the given host:port with // the supplied certificate and macaroon. -func connectRPC(hostPort, tlsCertPath, macaroonPath string) (*grpc.ClientConn, - error) { +func connectRPC(hostPort, tlsCertPath, macaroonPath string, + timeout time.Duration) (*grpc.ClientConn, error) { certBytes, err := ioutil.ReadFile(tlsCertPath) if err != nil { @@ -793,8 +1215,11 @@ func connectRPC(hostPort, tlsCertPath, macaroonPath string) (*grpc.ClientConn, cp, "", )), grpc.WithPerRPCCredentials(macCred), + grpc.WithBlock(), } - conn, err := grpc.Dial(hostPort, opts...) + ctxt, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + conn, err := grpc.DialContext(ctxt, hostPort, opts...) if err != nil { return nil, fmt.Errorf("unable to connect to RPC server: %v", err) @@ -802,3 +1227,65 @@ func connectRPC(hostPort, tlsCertPath, macaroonPath string) (*grpc.ClientConn, return conn, nil } + +// packetFromTx creates a PSBT from a tx that potentially already contains +// signed inputs. +func packetFromTx(original *wire.MsgTx) (*psbt.Packet, error) { + // The psbt.NewFromUnsignedTx function complains if there are any + // scripts or witness content on a TX. So we create a copy of the TX and + // nil out all the offending data, but also keep a backup around that we + // add to the PSBT afterwards. + noSigs := original.Copy() + for idx := range noSigs.TxIn { + noSigs.TxIn[idx].SignatureScript = nil + noSigs.TxIn[idx].Witness = nil + } + + // With all the data that is seen as "signed", we can now create the + // empty packet. + packet, err := psbt.NewFromUnsignedTx(noSigs) + if err != nil { + return nil, err + } + + var buf bytes.Buffer + for idx, txIn := range original.TxIn { + if len(txIn.SignatureScript) > 0 { + packet.Inputs[idx].FinalScriptSig = txIn.SignatureScript + } + + if len(txIn.Witness) > 0 { + buf.Reset() + err = psbt.WriteTxWitness(&buf, txIn.Witness) + if err != nil { + return nil, err + } + packet.Inputs[idx].FinalScriptWitness = buf.Bytes() + } + } + + return packet, nil +} + +// considerShutdown inspects the error and issues a shutdown (through logging +// a critical error, which will cause the logger to issue a clean shutdown +// request) if the error looks like a connection or general availability error +// and not some application specific problem. +func considerShutdown(err error) { + statusErr, isStatusErr := status.FromError(err) + switch { + // The context attached to the client request has timed out. This can be + // due to not being able to reach the signing server, or it's taking too + // long to respond. In either case, request a shutdown. + case err == context.DeadlineExceeded: + fallthrough + + // The signing server's context timed out before the client's due to + // clock skew, request a shutdown anyway. + case isStatusErr && statusErr.Code() == codes.DeadlineExceeded: + log.Critical("RPC signing timed out: %v", err) + + case isStatusErr && statusErr.Code() == codes.Unavailable: + log.Critical("RPC signing server not available: %v", err) + } +} diff --git a/lnwallet/sigpool.go b/lnwallet/sigpool.go index ec1d4f3462..30dc37ae90 100644 --- a/lnwallet/sigpool.go +++ b/lnwallet/sigpool.go @@ -4,7 +4,8 @@ import ( "fmt" "sync" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnwire" @@ -35,7 +36,7 @@ type VerifyJob struct { // Sig is the raw signature generated using the above public key. This // is the signature to be verified. - Sig *btcec.Signature + Sig *ecdsa.Signature // SigHash is a function closure generates the sighashes that the // passed signature is known to have signed. @@ -155,6 +156,7 @@ func NewSigPool(numWorkers int, signer input.Signer) *SigPool { // carry out its duties. func (s *SigPool) Start() error { s.started.Do(func() { + walletLog.Info("SigPool starting") for i := 0; i < s.numWorkers; i++ { s.wg.Add(1) go s.poolWorker() diff --git a/lnwallet/test/bitcoind/bitcoind_test.go b/lnwallet/test/bitcoind/bitcoind_test.go index a5df25281e..b63ff92582 100644 --- a/lnwallet/test/bitcoind/bitcoind_test.go +++ b/lnwallet/test/bitcoind/bitcoind_test.go @@ -6,8 +6,14 @@ import ( lnwallettest "github.com/lightningnetwork/lnd/lnwallet/test" ) -// TestLightningWallet tests LightningWallet powered by bitcoind against our -// suite of interface tests. -func TestLightningWallet(t *testing.T) { +// TestLightningWalletBitcoindZMQ tests LightningWallet powered by bitcoind, +// using its ZMQ interface, against our suite of interface tests. +func TestLightningWalletBitcoindZMQ(t *testing.T) { lnwallettest.TestLightningWallet(t, "bitcoind") } + +// TestLightningWalletBitcoindRPCPolling tests LightningWallet powered by +// bitcoind, using its RPC interface, against our suite of interface tests. +func TestLightningWalletBitcoindRPCPolling(t *testing.T) { + lnwallettest.TestLightningWallet(t, "bitcoind-rpc-polling") +} diff --git a/lnwallet/test/test_interface.go b/lnwallet/test/test_interface.go index 66da4f5873..2a99c72873 100644 --- a/lnwallet/test/test_interface.go +++ b/lnwallet/test/test_interface.go @@ -6,7 +6,6 @@ import ( "encoding/hex" "fmt" "io/ioutil" - "math/rand" "net" "os" "os/exec" @@ -14,11 +13,13 @@ import ( "reflect" "runtime" "strings" + "sync/atomic" "testing" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcjson" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/integration/rpctest" @@ -26,7 +27,6 @@ import ( "github.com/btcsuite/btcd/rpcclient" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/btcsuite/btcwallet/chain" "github.com/btcsuite/btcwallet/walletdb" _ "github.com/btcsuite/btcwallet/walletdb/bdb" @@ -81,8 +81,8 @@ var ( netParams = &chaincfg.RegressionNetParams chainHash = netParams.GenesisHash - _, alicePub = btcec.PrivKeyFromBytes(btcec.S256(), testHdSeed[:]) - _, bobPub = btcec.PrivKeyFromBytes(btcec.S256(), bobsPrivKey) + _, alicePub = btcec.PrivKeyFromBytes(testHdSeed[:]) + _, bobPub = btcec.PrivKeyFromBytes(bobsPrivKey) // The number of confirmations required to consider any created channel // open. @@ -96,6 +96,35 @@ var ( defaultMaxLocalCsvDelay uint16 = 10000 ) +var ( + // lastPort is the last port determined to be free for use by a new + // bitcoind server. It should be used atomically. + lastPort uint32 = 1024 +) + +// getFreePort returns the first port that is available for listening by a new +// embedded etcd server. It panics if no port is found and the maximum available +// TCP port is reached. +func getFreePort() int { + port := atomic.AddUint32(&lastPort, 1) + for port < 65535 { + // If there are no errors while attempting to listen on this + // port, close the socket and return it as available. + addr := fmt.Sprintf("127.0.0.1:%d", port) + l, err := net.Listen("tcp4", addr) + if err == nil { + err := l.Close() + if err == nil { + return int(port) + } + } + port = atomic.AddUint32(&lastPort, 1) + } + + // No ports available? Must be a mistake. + panic("no ports available for listening") +} + // assertProperBalance asserts than the total value of the unspent outputs // within the wallet are *exactly* amount. If unable to retrieve the current // balance, or the assertion fails, the test will halt with a fatal error. @@ -1140,6 +1169,7 @@ func testListTransactionDetails(miner *rpctest.Harness, // Create 5 new outputs spendable by the wallet. const numTxns = 5 const outputAmt = btcutil.SatoshiPerBitcoin + isOurAddress := make(map[string]bool) txids := make(map[chainhash.Hash]struct{}) for i := 0; i < numTxns; i++ { addr, err := alice.NewAddress( @@ -1149,6 +1179,7 @@ func testListTransactionDetails(miner *rpctest.Harness, if err != nil { t.Fatalf("unable to create new address: %v", err) } + isOurAddress[addr.EncodeAddress()] = true script, err := txscript.PayToAddrScript(addr) if err != nil { t.Fatalf("unable to create output script: %v", err) @@ -1245,20 +1276,27 @@ func testListTransactionDetails(miner *rpctest.Harness, t.Fatalf("tx (%v) not found in block (%v)", txDetail.Hash, txDetail.BlockHash) } else { - var destinationAddresses []btcutil.Address + var destinationOutputs []lnwallet.OutputDetail - for _, txOut := range txOuts { - _, addrs, _, err := + for i, txOut := range txOuts { + sc, addrs, _, err := txscript.ExtractPkScriptAddrs(txOut.PkScript, &alice.Cfg.NetParams) if err != nil { t.Fatalf("err extract script addresses: %s", err) } - destinationAddresses = append(destinationAddresses, addrs...) + destinationOutputs = append(destinationOutputs, lnwallet.OutputDetail{ + OutputType: sc, + Addresses: addrs, + PkScript: txOut.PkScript, + OutputIndex: i, + Value: btcutil.Amount(txOut.Value), + IsOurAddress: isOurAddress[addrs[0].EncodeAddress()], + }) } - if !reflect.DeepEqual(txDetail.DestAddresses, destinationAddresses) { - t.Fatalf("destination addresses mismatch, got %v expected %v", - txDetail.DestAddresses, destinationAddresses) + if !reflect.DeepEqual(txDetail.OutputDetails, destinationOutputs) { + t.Fatalf("destination outputs mismatch, got %v expected %v", + txDetail.OutputDetails, destinationOutputs) } } @@ -1330,10 +1368,12 @@ func testListTransactionDetails(miner *rpctest.Harness, // that even when we have 0 confirmation transactions, the destination // addresses are returned. var match bool - for _, addr := range txDetail.DestAddresses { - if addr.String() == minerAddr.String() { - match = true - break + for _, o := range txDetail.OutputDetails { + for _, addr := range o.Addresses { + if addr.String() == minerAddr.String() { + match = true + break + } } } if !match { @@ -1699,7 +1739,7 @@ func txFromOutput(tx *wire.MsgTx, signer input.Signer, fromPubKey, WitnessScript: keyScript, Output: tx.TxOut[outputIndex], HashType: txscript.SigHashAll, - SigHashes: txscript.NewTxSigHashes(tx1), + SigHashes: input.NewTxSigHashesV0Only(tx1), InputIndex: 0, // Has only one input. } @@ -1719,7 +1759,9 @@ func txFromOutput(tx *wire.MsgTx, signer input.Signer, fromPubKey, // private key. vm, err := txscript.NewEngine( keyScript, tx1, 0, txscript.StandardVerifyFlags, nil, - nil, outputValue, + nil, outputValue, txscript.NewCannedPrevOutputFetcher( + keyScript, outputValue, + ), ) if err != nil { return nil, fmt.Errorf("unable to create engine: %v", err) @@ -1735,6 +1777,7 @@ func txFromOutput(tx *wire.MsgTx, signer input.Signer, fromPubKey, // new, unconfirmed tx that spends this output to pubKey. func newTx(t *testing.T, r *rpctest.Harness, pubKey *btcec.PublicKey, alice *lnwallet.LightningWallet, rbf bool) *wire.MsgTx { + t.Helper() keyScript, err := scriptFromKey(pubKey) @@ -2000,8 +2043,7 @@ func testSignOutputUsingTweaks(r *rpctest.Harness, // we'll generate a commitment pre-image, then derive a revocation key // and single tweak from that. commitPreimage := bytes.Repeat([]byte{2}, 32) - commitSecret, commitPoint := btcec.PrivKeyFromBytes(btcec.S256(), - commitPreimage) + commitSecret, commitPoint := btcec.PrivKeyFromBytes(commitPreimage) revocationKey := input.DeriveRevocationPubkey(pubKey.PubKey, commitPoint) commitTweak := input.SingleTweakBytes(commitPoint, pubKey.PubKey) @@ -2085,7 +2127,7 @@ func testSignOutputUsingTweaks(r *rpctest.Harness, WitnessScript: keyScript, Output: newOutput, HashType: txscript.SigHashAll, - SigHashes: txscript.NewTxSigHashes(sweepTx), + SigHashes: input.NewTxSigHashesV0Only(sweepTx), InputIndex: 0, } @@ -2112,9 +2154,13 @@ func testSignOutputUsingTweaks(r *rpctest.Harness, // Finally, attempt to validate the completed transaction. This // should succeed if the wallet was able to properly generate // the proper private key. - vm, err := txscript.NewEngine(keyScript, - sweepTx, 0, txscript.StandardVerifyFlags, nil, - nil, int64(btcutil.SatoshiPerBitcoin)) + vm, err := txscript.NewEngine( + keyScript, sweepTx, 0, txscript.StandardVerifyFlags, + nil, nil, int64(btcutil.SatoshiPerBitcoin), + txscript.NewCannedPrevOutputFetcher( + keyScript, int64(btcutil.SatoshiPerBitcoin), + ), + ) if err != nil { t.Fatalf("unable to create engine: %v", err) } @@ -2822,7 +2868,7 @@ func testSignOutputCreateAccount(r *rpctest.Harness, w *lnwallet.LightningWallet Value: 1000, }, HashType: txscript.SigHashAll, - SigHashes: txscript.NewTxSigHashes(fakeTx), + SigHashes: input.NewTxSigHashesV0Only(fakeTx), InputIndex: 0, } @@ -3224,6 +3270,7 @@ func TestLightningWallet(t *testing.T, targetBackEnd string) { if !runTests(t, walletDriver, backEnd, miningNode, rpcConfig, chainNotifier) { + return } } @@ -3359,7 +3406,7 @@ func runTests(t *testing.T, walletDriver *lnwallet.WalletDriver, zmqBlockHost := "ipc:///" + tempBitcoindDir + "/blocks.socket" zmqTxHost := "ipc:///" + tempBitcoindDir + "/tx.socket" defer os.RemoveAll(tempBitcoindDir) - rpcPort := rand.Int()%(65536-1024) + 1024 + rpcPort := getFreePort() bitcoind := exec.Command( "bitcoind", "-datadir="+tempBitcoindDir, @@ -3378,8 +3425,18 @@ func runTests(t *testing.T, walletDriver *lnwallet.WalletDriver, if err != nil { t.Fatalf("couldn't start bitcoind: %v", err) } - defer bitcoind.Wait() - defer bitcoind.Process.Kill() + + // Sanity check to ensure that the process did in fact + // start. + if bitcoind.Process == nil { + t.Fatalf("bitcoind cmd Process is not set " + + "after Start") + } + + defer func() { + _ = bitcoind.Process.Kill() + _ = bitcoind.Wait() + }() // Wait for the bitcoind instance to start up. @@ -3387,13 +3444,15 @@ func runTests(t *testing.T, walletDriver *lnwallet.WalletDriver, var chainConn *chain.BitcoindConn err = wait.NoError(func() error { chainConn, err = chain.NewBitcoindConn(&chain.BitcoindConfig{ - ChainParams: netParams, - Host: host, - User: "weks", - Pass: "weks", - ZMQBlockHost: zmqBlockHost, - ZMQTxHost: zmqTxHost, - ZMQReadDeadline: 5 * time.Second, + ChainParams: netParams, + Host: host, + User: "weks", + Pass: "weks", + ZMQConfig: &chain.ZMQConfig{ + ZMQBlockHost: zmqBlockHost, + ZMQTxHost: zmqTxHost, + ZMQReadDeadline: 5 * time.Second, + }, // Fields only required for pruned nodes, not // needed for these tests. Dialer: nil, @@ -3415,6 +3474,78 @@ func runTests(t *testing.T, walletDriver *lnwallet.WalletDriver, // Bob. aliceClient = chainConn.NewBitcoindClient() bobClient = chainConn.NewBitcoindClient() + + case "bitcoind-rpc-polling": + // Start a bitcoind instance. + tempBitcoindDir, err := ioutil.TempDir("", "bitcoind") + if err != nil { + t.Fatalf("unable to create temp directory: %v", err) + } + defer os.RemoveAll(tempBitcoindDir) + rpcPort := getFreePort() + bitcoind := exec.Command( + "bitcoind", + "-datadir="+tempBitcoindDir, + "-regtest", + "-connect="+miningNode.P2PAddress(), + "-txindex", + "-rpcauth=weks:469e9bb14ab2360f8e226efed5ca6f"+ + "d$507c670e800a95284294edb5773b05544b"+ + "220110063096c221be9933c82d38e1", + fmt.Sprintf("-rpcport=%d", rpcPort), + "-disablewallet", + ) + err = bitcoind.Start() + if err != nil { + t.Fatalf("couldn't start bitcoind: %v", err) + } + defer func() { + _ = bitcoind.Process.Kill() + _ = bitcoind.Wait() + }() + + // Sanity check to ensure that the process did in fact + // start. + if bitcoind.Process == nil { + t.Fatalf("bitcoind cmd Process is not set " + + "after Start") + } + + // Wait for the bitcoind instance to start up. + host := fmt.Sprintf("127.0.0.1:%d", rpcPort) + var chainConn *chain.BitcoindConn + err = wait.NoError(func() error { + chainConn, err = chain.NewBitcoindConn(&chain.BitcoindConfig{ + ChainParams: netParams, + Host: host, + User: "weks", + Pass: "weks", + PollingConfig: &chain.PollingConfig{ + BlockPollingInterval: time.Millisecond * 20, + TxPollingInterval: time.Millisecond * 20, + }, + // Fields only required for pruned nodes, not + // needed for these tests. + Dialer: nil, + PrunedModeMaxPeers: 0, + }) + if err != nil { + return err + } + + return chainConn.Start() + }, 10*time.Second) + if err != nil { + t.Fatalf("unable to establish connection to "+ + "bitcoind: %v", err) + } + defer chainConn.Stop() + + // Create a btcwallet bitcoind client for both Alice and + // Bob. + aliceClient = chainConn.NewBitcoindClient() + bobClient = chainConn.NewBitcoindClient() + default: t.Fatalf("unknown chain driver: %v", backEnd) } @@ -3521,10 +3652,12 @@ func runTests(t *testing.T, walletDriver *lnwallet.WalletDriver, success := t.Run(testName, func(t *testing.T) { if backEnd == "neutrino" && strings.Contains(walletTest.name, "dual funder") { + t.Skip("skipping dual funder tests for neutrino") } if backEnd == "neutrino" && strings.Contains(walletTest.name, "spend unconfirmed") { + t.Skip("skipping spend unconfirmed tests for neutrino") } @@ -3539,6 +3672,7 @@ func runTests(t *testing.T, walletDriver *lnwallet.WalletDriver, // wipe buckets if err := clearWalletStates(alice, bob); err != nil && err != kvdb.ErrBucketNotFound { + t.Fatalf("unable to wipe wallet state: %v", err) } } diff --git a/lnwallet/test_utils.go b/lnwallet/test_utils.go index c33430c210..f1209d7014 100644 --- a/lnwallet/test_utils.go +++ b/lnwallet/test_utils.go @@ -10,10 +10,10 @@ import ( "net" "os" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" @@ -138,14 +138,14 @@ func CreateTestChannels(chanType channeldb.ChannelType) ( copy(key[:], testWalletPrivKey[:]) key[0] ^= byte(i + 1) - aliceKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), key) + aliceKey, _ := btcec.PrivKeyFromBytes(key) aliceKeys = append(aliceKeys, aliceKey) key = make([]byte, len(bobsPrivKey)) copy(key[:], bobsPrivKey) key[0] ^= byte(i + 1) - bobKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), key) + bobKey, _ := btcec.PrivKeyFromBytes(key) bobKeys = append(bobKeys, bobKey) } @@ -450,7 +450,7 @@ func pubkeyFromHex(keyHex string) (*btcec.PublicKey, error) { if err != nil { return nil, err } - return btcec.ParsePubKey(bytes, btcec.S256()) + return btcec.ParsePubKey(bytes) } // privkeyFromHex parses a Bitcoin private key from a hex encoded string. @@ -459,7 +459,7 @@ func privkeyFromHex(keyHex string) (*btcec.PrivateKey, error) { if err != nil { return nil, err } - key, _ := btcec.PrivKeyFromBytes(btcec.S256(), bytes) + key, _ := btcec.PrivKeyFromBytes(bytes) return key, nil } diff --git a/lnwallet/transactions.go b/lnwallet/transactions.go index ce2eb9e80d..7fccf97c0d 100644 --- a/lnwallet/transactions.go +++ b/lnwallet/transactions.go @@ -4,9 +4,9 @@ import ( "encoding/binary" "fmt" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/channeldb" ) diff --git a/lnwallet/transactions_test.go b/lnwallet/transactions_test.go index de73d6f330..e8446e0e4c 100644 --- a/lnwallet/transactions_test.go +++ b/lnwallet/transactions_test.go @@ -15,11 +15,11 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" @@ -511,15 +511,15 @@ func testSpendValidation(t *testing.T, tweakless bool) { // Each side currently has 1 BTC within the channel, with a total // channel capacity of 2BTC. aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes( - btcec.S256(), testWalletPrivKey, + testWalletPrivKey, ) bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes( - btcec.S256(), bobsPrivKey, + bobsPrivKey, ) revocationPreimage := testHdSeed.CloneBytes() commitSecret, commitPoint := btcec.PrivKeyFromBytes( - btcec.S256(), revocationPreimage, + revocationPreimage, ) revokePubKey := input.DeriveRevocationPubkey(bobKeyPub, commitPoint) @@ -612,7 +612,7 @@ func testSpendValidation(t *testing.T, tweakless bool) { PubKey: aliceKeyPub, }, SingleTweak: remoteCommitTweak, - SigHashes: txscript.NewTxSigHashes(sweepTx), + SigHashes: input.NewTxSigHashesV0Only(sweepTx), Output: &wire.TxOut{ Value: int64(channelBalance), }, @@ -626,9 +626,11 @@ func testSpendValidation(t *testing.T, tweakless bool) { t.Fatalf("unable to generate delay commit spend witness: %v", err) } sweepTx.TxIn[0].Witness = aliceWitnessSpend - vm, err := txscript.NewEngine(delayOutput.PkScript, - sweepTx, 0, txscript.StandardVerifyFlags, nil, - nil, int64(channelBalance)) + vm, err := txscript.NewEngine( + delayOutput.PkScript, sweepTx, 0, txscript.StandardVerifyFlags, + nil, nil, int64(channelBalance), + txscript.NewCannedPrevOutputFetcher(nil, 0), + ) if err != nil { t.Fatalf("unable to create engine: %v", err) } @@ -647,7 +649,7 @@ func testSpendValidation(t *testing.T, tweakless bool) { }, DoubleTweak: commitSecret, WitnessScript: delayScript, - SigHashes: txscript.NewTxSigHashes(sweepTx), + SigHashes: input.NewTxSigHashesV0Only(sweepTx), Output: &wire.TxOut{ Value: int64(channelBalance), }, @@ -660,9 +662,11 @@ func testSpendValidation(t *testing.T, tweakless bool) { t.Fatalf("unable to generate revocation witness: %v", err) } sweepTx.TxIn[0].Witness = bobWitnessSpend - vm, err = txscript.NewEngine(delayOutput.PkScript, - sweepTx, 0, txscript.StandardVerifyFlags, nil, - nil, int64(channelBalance)) + vm, err = txscript.NewEngine( + delayOutput.PkScript, sweepTx, 0, txscript.StandardVerifyFlags, + nil, nil, int64(channelBalance), + txscript.NewCannedPrevOutputFetcher(nil, 0), + ) if err != nil { t.Fatalf("unable to create engine: %v", err) } @@ -691,7 +695,7 @@ func testSpendValidation(t *testing.T, tweakless bool) { PubKey: bobKeyPub, }, WitnessScript: bobScriptP2WKH, - SigHashes: txscript.NewTxSigHashes(sweepTx), + SigHashes: input.NewTxSigHashesV0Only(sweepTx), Output: &wire.TxOut{ Value: int64(channelBalance), PkScript: bobScriptP2WKH, @@ -713,6 +717,7 @@ func testSpendValidation(t *testing.T, tweakless bool) { regularOutput.PkScript, sweepTx, 0, txscript.StandardVerifyFlags, nil, nil, int64(channelBalance), + txscript.NewCannedPrevOutputFetcher(bobScriptP2WKH, 0), ) if err != nil { t.Fatalf("unable to create engine: %v", err) @@ -783,7 +788,7 @@ func createTestChannelsForVectors(tc *testContext, chanType channeldb.ChannelTyp &remoteDummy1, &remoteDummy2, &localDummy1, &localDummy2, } for _, keyRef := range generateKeys { - privkey, err := btcec.NewPrivateKey(btcec.S256()) + privkey, err := btcec.NewPrivateKey() require.NoError(t, err) *keyRef = privkey } diff --git a/lnwallet/wallet.go b/lnwallet/wallet.go index feb8b0cd91..86eb1a9205 100644 --- a/lnwallet/wallet.go +++ b/lnwallet/wallet.go @@ -11,14 +11,14 @@ import ( "sync/atomic" "github.com/btcsuite/btcd/blockchain" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/psbt" + "github.com/btcsuite/btcd/btcutil/txsort" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" - "github.com/btcsuite/btcutil/psbt" - "github.com/btcsuite/btcutil/txsort" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/input" @@ -706,6 +706,12 @@ func (l *LightningWallet) handleFundingReserveRequest(req *InitFundingReserveMsg return } + // We need to avoid enforcing reserved value in the middle of PSBT + // funding because some of the following steps may add UTXOs funding + // the on-chain wallet. + // The enforcement still happens at the last step - in PsbtFundingVerify + enforceNewReservedValue := true + // If no chanFunder was provided, then we'll assume the default // assembler, which is backed by the wallet's internal coin selection. if req.ChanFunder == nil { @@ -720,6 +726,9 @@ func (l *LightningWallet) handleFundingReserveRequest(req *InitFundingReserveMsg DustLimit: DustLimitForSize(input.P2WSHSize), } req.ChanFunder = chanfunding.NewWalletAssembler(cfg) + } else { + _, isPsbtFunder := req.ChanFunder.(*chanfunding.PsbtAssembler) + enforceNewReservedValue = !isPsbtFunder } localFundingAmt := req.LocalFundingAmt @@ -819,13 +828,15 @@ func (l *LightningWallet) handleFundingReserveRequest(req *InitFundingReserveMsg // when the PSBT has been verified. isPublic := req.Flags&lnwire.FFAnnounceChannel != 0 hasAnchors := req.CommitType.HasAnchors() - err = l.enforceNewReservedValue(fundingIntent, isPublic, hasAnchors) - if err != nil { - fundingIntent.Cancel() + if enforceNewReservedValue { + err = l.enforceNewReservedValue(fundingIntent, isPublic, hasAnchors) + if err != nil { + fundingIntent.Cancel() - req.err <- err - req.resp <- nil - return + req.err <- err + req.resp <- nil + return + } } // The total channel capacity will be the size of the funding output we @@ -930,7 +941,6 @@ func (l *LightningWallet) currentNumAnchorChans() (int, error) { if c.ChanType.HasAnchors() { numAnchors++ } - } for _, c := range chans { @@ -1073,7 +1083,6 @@ func (l *LightningWallet) CheckReservedValueTx(req CheckReservedValueTxReq) ( inputs, req.Tx.TxOut, numAnchors, ) switch { - // If the error returned from CheckReservedValue is // ErrReservedValueInvalidated, then it did nonetheless return // the required reserved value and we check for the optional @@ -1210,7 +1219,7 @@ func (l *LightningWallet) handleFundingCancelRequest(req *fundingReserveCancelMs pendingReservation.Lock() defer pendingReservation.Unlock() - // Mark all previously locked outpoints as useable for future funding + // Mark all previously locked outpoints as usable for future funding // requests. for _, unusedInput := range pendingReservation.ourContribution.Inputs { delete(l.lockedOutPoints, unusedInput.PreviousOutPoint) @@ -1431,7 +1440,7 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) { ) } - walletLog.Debugf("Funding tx for ChannelPoint(%v) "+ + walletLog.Tracef("Funding tx for ChannelPoint(%v) "+ "generated: %v", chanPoint, spew.Sdump(fundingTx)) } @@ -1578,9 +1587,9 @@ func (l *LightningWallet) handleChanPointReady(req *continueContributionMsg) { txsort.InPlaceSort(ourCommitTx) txsort.InPlaceSort(theirCommitTx) - walletLog.Debugf("Local commit tx for ChannelPoint(%v): %v", + walletLog.Tracef("Local commit tx for ChannelPoint(%v): %v", chanPoint, spew.Sdump(ourCommitTx)) - walletLog.Debugf("Remote commit tx for ChannelPoint(%v): %v", + walletLog.Tracef("Remote commit tx for ChannelPoint(%v): %v", chanPoint, spew.Sdump(theirCommitTx)) // Record newly available information within the open channel state. @@ -1606,7 +1615,7 @@ func (l *LightningWallet) handleChanPointReady(req *continueContributionMsg) { KeyDesc: ourKey, Output: fundingOutput, HashType: txscript.SigHashAll, - SigHashes: txscript.NewTxSigHashes(theirCommitTx), + SigHashes: input.NewTxSigHashesV0Only(theirCommitTx), InputIndex: 0, } sigTheirCommit, err := l.Cfg.Signer.SignOutputRaw(theirCommitTx, &signDesc) @@ -1681,7 +1690,7 @@ func (l *LightningWallet) verifyFundingInputs(fundingTx *wire.MsgTx, remoteInputScripts []*input.Script) error { sigIndex := 0 - fundingHashCache := txscript.NewTxSigHashes(fundingTx) + fundingHashCache := input.NewTxSigHashesV0Only(fundingTx) inputScripts := remoteInputScripts for i, txin := range fundingTx.TxIn { if len(inputScripts) != 0 && len(txin.Witness) == 0 { @@ -1718,6 +1727,9 @@ func (l *LightningWallet) verifyFundingInputs(fundingTx *wire.MsgTx, output.PkScript, fundingTx, i, txscript.StandardVerifyFlags, nil, fundingHashCache, output.Value, + txscript.NewCannedPrevOutputFetcher( + output.PkScript, output.Value, + ), ) if err != nil { return fmt.Errorf("cannot create script "+ @@ -1797,7 +1809,7 @@ func (l *LightningWallet) handleFundingCounterPartySigs(msg *addCounterPartySigs // Next, create the spending scriptSig, and then verify that the script // is complete, allowing us to spend from the funding transaction. channelValue := int64(res.partialState.Capacity) - hashCache := txscript.NewTxSigHashes(commitTx) + hashCache := input.NewTxSigHashesV0Only(commitTx) sigHash, err := txscript.CalcWitnessSigHash( witnessScript, hashCache, txscript.SigHashAll, commitTx, 0, channelValue, @@ -1948,7 +1960,7 @@ func (l *LightningWallet) handleSingleFunderSigs(req *addSingleFunderSigsMsg) { req.fundingOutpoint, spew.Sdump(theirCommitTx)) channelValue := int64(pendingReservation.partialState.Capacity) - hashCache := txscript.NewTxSigHashes(ourCommitTx) + hashCache := input.NewTxSigHashesV0Only(ourCommitTx) theirKey := pendingReservation.theirContribution.MultiSigKey ourKey := pendingReservation.ourContribution.MultiSigKey witnessScript, _, err := input.GenFundingPkScript( @@ -1999,7 +2011,7 @@ func (l *LightningWallet) handleSingleFunderSigs(req *addSingleFunderSigsMsg) { Value: channelValue, }, HashType: txscript.SigHashAll, - SigHashes: txscript.NewTxSigHashes(theirCommitTx), + SigHashes: input.NewTxSigHashesV0Only(theirCommitTx), InputIndex: 0, } sigTheirCommit, err := l.Cfg.Signer.SignOutputRaw(theirCommitTx, &signDesc) @@ -2254,3 +2266,36 @@ func validateUpfrontShutdown(shutdown lnwire.DeliveryAddress, return false } } + +// WalletPrevOutputFetcher is a txscript.PrevOutputFetcher that can fetch +// outputs from a given wallet controller. +type WalletPrevOutputFetcher struct { + wc WalletController +} + +// A compile time assertion that WalletPrevOutputFetcher implements the +// txscript.PrevOutputFetcher interface. +var _ txscript.PrevOutputFetcher = (*WalletPrevOutputFetcher)(nil) + +// NewWalletPrevOutputFetcher creates a new WalletPrevOutputFetcher that fetches +// previous outputs from the given wallet controller. +func NewWalletPrevOutputFetcher(wc WalletController) *WalletPrevOutputFetcher { + return &WalletPrevOutputFetcher{ + wc: wc, + } +} + +// FetchPrevOutput attempts to fetch the previous output referenced by the +// passed outpoint. A nil value will be returned if the passed outpoint doesn't +// exist. +func (w *WalletPrevOutputFetcher) FetchPrevOutput(op wire.OutPoint) *wire.TxOut { + utxo, err := w.wc.FetchInputInfo(&op) + if err != nil { + return nil + } + + return &wire.TxOut{ + Value: int64(utxo.Value), + PkScript: utxo.PkScript, + } +} diff --git a/lnwire/accept_channel.go b/lnwire/accept_channel.go index 4c7aedb9d2..cce1ba42ba 100644 --- a/lnwire/accept_channel.go +++ b/lnwire/accept_channel.go @@ -4,8 +4,8 @@ import ( "bytes" "io" - "github.com/btcsuite/btcd/btcec" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/tlv" ) diff --git a/lnwire/accept_channel_test.go b/lnwire/accept_channel_test.go index a1ab2be48c..87d9dc029c 100644 --- a/lnwire/accept_channel_test.go +++ b/lnwire/accept_channel_test.go @@ -4,7 +4,7 @@ import ( "bytes" "testing" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" ) // TestDecodeAcceptChannel tests decoding of an accept channel wire message with @@ -32,7 +32,7 @@ func TestDecodeAcceptChannel(t *testing.T) { test := test t.Run(test.name, func(t *testing.T) { - priv, err := btcec.NewPrivateKey(btcec.S256()) + priv, err := btcec.NewPrivateKey() if err != nil { t.Fatalf("cannot create privkey: %v", err) } @@ -63,6 +63,7 @@ func TestDecodeAcceptChannel(t *testing.T) { if !bytes.Equal( decoded.UpfrontShutdownScript, encoded.UpfrontShutdownScript, ) { + t.Fatalf("decoded script: %x does not equal encoded script: %x", decoded.UpfrontShutdownScript, encoded.UpfrontShutdownScript) } diff --git a/lnwire/channel_reestablish.go b/lnwire/channel_reestablish.go index 0de16a3659..387cc5b580 100644 --- a/lnwire/channel_reestablish.go +++ b/lnwire/channel_reestablish.go @@ -4,7 +4,7 @@ import ( "bytes" "io" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" ) // ChannelReestablish is a message sent between peers that have an existing diff --git a/lnwire/closing_signed.go b/lnwire/closing_signed.go index 9ba170ba5e..8e11c86993 100644 --- a/lnwire/closing_signed.go +++ b/lnwire/closing_signed.go @@ -4,7 +4,7 @@ import ( "bytes" "io" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) // ClosingSigned is sent by both parties to a channel once the channel is clear diff --git a/lnwire/extra_bytes_test.go b/lnwire/extra_bytes_test.go index 88ffcc307c..6e840a2a64 100644 --- a/lnwire/extra_bytes_test.go +++ b/lnwire/extra_bytes_test.go @@ -60,7 +60,6 @@ func TestExtraOpaqueDataEncodeDecode(t *testing.T) { // time, we'll actually feed in blank bytes. quickCfg := &quick.Config{ Values: func(v []reflect.Value, r *rand.Rand) { - var newTestCase testCase if r.Int31()%2 == 0 { newTestCase.emptyBytes = true diff --git a/lnwire/features.go b/lnwire/features.go index 28b1d9c4e5..b62202ba5c 100644 --- a/lnwire/features.go +++ b/lnwire/features.go @@ -155,7 +155,25 @@ const ( // TODO: Decide on actual feature bit value. ExplicitChannelTypeOptional = 45 - // ScriptEnforcedLeaseOptional is an optional feature bit that signals + // PaymentMetadataRequired is a required bit that denotes that if an + // invoice contains metadata, it must be passed along with the payment + // htlc(s). + PaymentMetadataRequired = 48 + + // PaymentMetadataOptional is an optional bit that denotes that if an + // invoice contains metadata, it may be passed along with the payment + // htlc(s). + PaymentMetadataOptional = 49 + + // KeysendRequired is a required bit that indicates that the node is + // able and willing to accept keysend payments. + KeysendRequired = 54 + + // KeysendOptional is an optional bit that indicates that the node is + // able and willing to accept keysend payments. + KeysendOptional = 55 + + // ScriptEnforcedLeaseRequired is a required feature bit that signals // that the node requires channels having zero-fee second-level HTLC // transactions, which also imply anchor commitments, along with an // additional CLTV constraint of a channel lease's expiration height @@ -164,7 +182,7 @@ const ( // TODO: Decide on actual feature bit value. ScriptEnforcedLeaseRequired FeatureBit = 2022 - // ScriptEnforcedLeaseOptional is a required feature bit that signals + // ScriptEnforcedLeaseOptional is an optional feature bit that signals // that the node requires channels having zero-fee second-level HTLC // transactions, which also imply anchor commitments, along with an // additional CLTV constraint of a channel lease's expiration height @@ -218,8 +236,12 @@ var Features = map[FeatureBit]string{ WumboChannelsOptional: "wumbo-channels", AMPRequired: "amp", AMPOptional: "amp", + PaymentMetadataOptional: "payment-metadata", + PaymentMetadataRequired: "payment-metadata", ExplicitChannelTypeOptional: "explicit-commitment-type", ExplicitChannelTypeRequired: "explicit-commitment-type", + KeysendOptional: "keysend", + KeysendRequired: "keysend", ScriptEnforcedLeaseRequired: "script-enforced-lease", ScriptEnforcedLeaseOptional: "script-enforced-lease", } diff --git a/lnwire/features_test.go b/lnwire/features_test.go index d4be13d4cf..a945eeb3e9 100644 --- a/lnwire/features_test.go +++ b/lnwire/features_test.go @@ -89,7 +89,6 @@ func TestFeatureVectorSetUnset(t *testing.T) { t.Errorf("Expectation failed in case %d, bit %d", i, j) break } - } for _, bit := range test.bits { diff --git a/lnwire/funding_locked.go b/lnwire/funding_locked.go index bdb0a9b063..02ee4d40be 100644 --- a/lnwire/funding_locked.go +++ b/lnwire/funding_locked.go @@ -4,7 +4,7 @@ import ( "bytes" "io" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" ) // FundingLocked is the message that both parties to a new channel creation diff --git a/lnwire/lnwire.go b/lnwire/lnwire.go index 20edcbab40..0361d76484 100644 --- a/lnwire/lnwire.go +++ b/lnwire/lnwire.go @@ -7,13 +7,12 @@ import ( "image/color" "io" "math" - "net" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/tor" ) @@ -542,7 +541,7 @@ func ReadElement(r io.Reader, element interface{}) error { return err } - pubKey, err := btcec.ParsePubKey(b[:], btcec.S256()) + pubKey, err := btcec.ParsePubKey(b[:]) if err != nil { return err } @@ -805,7 +804,29 @@ func ReadElement(r io.Reader, element interface{}) error { addrBytesRead += aType.AddrLen() default: - return &ErrUnknownAddrType{aType} + // If we don't understand this address type, + // we just store it along with the remaining + // address bytes as type OpaqueAddrs. We need + // to hold onto the bytes so that we can still + // write them back to the wire when we + // propagate this message. + payloadLen := 1 + addrsLen - addrBytesRead + payload := make([]byte, payloadLen) + + // First write a byte for the address type that + // we already read. + payload[0] = byte(aType) + + // Now append the rest of the address bytes. + _, err := io.ReadFull(addrBuf, payload[1:]) + if err != nil { + return err + } + + address = &OpaqueAddrs{ + Payload: payload, + } + addrBytesRead = addrsLen } addresses = append(addresses, address) diff --git a/lnwire/lnwire_test.go b/lnwire/lnwire_test.go index 0743c1704a..ec528a38a9 100644 --- a/lnwire/lnwire_test.go +++ b/lnwire/lnwire_test.go @@ -6,7 +6,6 @@ import ( "encoding/hex" "image/color" "math" - "math/big" "math/rand" "net" "reflect" @@ -14,24 +13,28 @@ import ( "testing/quick" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/tor" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) var ( shaHash1Bytes, _ = hex.DecodeString("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855") shaHash1, _ = chainhash.NewHash(shaHash1Bytes) outpoint1 = wire.NewOutPoint(shaHash1, 0) - testSig = &btcec.Signature{ - R: new(big.Int), - S: new(big.Int), - } - _, _ = testSig.R.SetString("63724406601629180062774974542967536251589935445068131219452686511677818569431", 10) - _, _ = testSig.S.SetString("18801056069249825825291287104931333862866033135609736119018462340006816851118", 10) + + testRBytes, _ = hex.DecodeString("8ce2bc69281ce27da07e6683571319d18e949ddfa2965fb6caa1bf0314f882d7") + testSBytes, _ = hex.DecodeString("299105481d63e0f4bc2a88121167221b6700d72a0ead154c03be696a292d24ae") + testRScalar = new(btcec.ModNScalar) + testSScalar = new(btcec.ModNScalar) + _ = testRScalar.SetByteSlice(testRBytes) + _ = testSScalar.SetByteSlice(testSBytes) + testSig = ecdsa.NewSignature(testRScalar, testSScalar) ) const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" @@ -46,7 +49,7 @@ func randAlias(r *rand.Rand) NodeAlias { } func randPubKey() (*btcec.PublicKey, error) { - priv, err := btcec.NewPrivateKey(btcec.S256()) + priv, err := btcec.NewPrivateKey() if err != nil { return nil, err } @@ -57,7 +60,7 @@ func randPubKey() (*btcec.PublicKey, error) { func randRawKey() ([33]byte, error) { var n [33]byte - priv, err := btcec.NewPrivateKey(btcec.S256()) + priv, err := btcec.NewPrivateKey() if err != nil { return n, err } @@ -156,6 +159,22 @@ func randV3OnionAddr(r *rand.Rand) (*tor.OnionAddr, error) { return &tor.OnionAddr{OnionService: onionService, Port: addrPort}, nil } +func randOpaqueAddr(r *rand.Rand) (*OpaqueAddrs, error) { + payloadLen := r.Int63n(64) + 1 + payload := make([]byte, payloadLen) + + // The first byte is the address type. So set it to one that we + // definitely don't know about. + payload[0] = math.MaxUint8 + + // Generate random bytes for the rest of the payload. + if _, err := r.Read(payload[1:]); err != nil { + return nil, err + } + + return &OpaqueAddrs{Payload: payload}, nil +} + func randAddrs(r *rand.Rand) ([]net.Addr, error) { tcp4Addr, err := randTCP4Addr(r) if err != nil { @@ -177,7 +196,14 @@ func randAddrs(r *rand.Rand) ([]net.Addr, error) { return nil, err } - return []net.Addr{tcp4Addr, tcp6Addr, v2OnionAddr, v3OnionAddr}, nil + opaqueAddrs, err := randOpaqueAddr(r) + if err != nil { + return nil, err + } + + return []net.Addr{ + tcp4Addr, tcp6Addr, v2OnionAddr, v3OnionAddr, opaqueAddrs, + }, nil } // TestChanUpdateChanFlags ensures that converting the ChanUpdateChanFlags and @@ -224,6 +250,45 @@ func TestChanUpdateChanFlags(t *testing.T) { } } +// TestDecodeUnknownAddressType shows that an unknown address type is currently +// incorrectly dealt with. +func TestDecodeUnknownAddressType(t *testing.T) { + // Add a normal, clearnet address. + tcpAddr := &net.TCPAddr{ + IP: net.IP{127, 0, 0, 1}, + Port: 8080, + } + + // Add an onion address. + onionAddr := &tor.OnionAddr{ + OnionService: "abcdefghijklmnop.onion", + Port: 9065, + } + + // Now add an address with an unknown type. + var newAddrType addressType = math.MaxUint8 + data := make([]byte, 0, 16) + data = append(data, uint8(newAddrType)) + opaqueAddrs := &OpaqueAddrs{ + Payload: data, + } + + buffer := bytes.NewBuffer(make([]byte, 0, MaxMsgBody)) + err := WriteNetAddrs( + buffer, []net.Addr{tcpAddr, onionAddr, opaqueAddrs}, + ) + require.NoError(t, err) + + // Now we attempt to parse the bytes and assert that we get an error. + var addrs []net.Addr + err = ReadElement(buffer, &addrs) + require.NoError(t, err) + require.Len(t, addrs, 3) + require.Equal(t, tcpAddr.String(), addrs[0].String()) + require.Equal(t, onionAddr.String(), addrs[1].String()) + require.Equal(t, hex.EncodeToString(data), addrs[2].String()) +} + func TestMaxOutPointIndex(t *testing.T) { t.Parallel() @@ -864,6 +929,22 @@ func TestLightningWireProtocol(t *testing.T) { NewShortChanIDFromInt(uint64(r.Int63()))) } + v[0] = reflect.ValueOf(req) + }, + MsgPing: func(v []reflect.Value, r *rand.Rand) { + // We use a special message generator here to ensure we + // don't generate ping messages that are too large, + // which'll cause the test to fail. + // + // We'll allow the test to generate padding bytes up to + // the max message limit, factoring in the 2 bytes for + // the num pong bytes. + paddingBytes := make([]byte, r.Intn(MaxMsgBody-1)) + req := Ping{ + NumPongBytes: uint16(r.Intn(MaxPongBytes + 1)), + PaddingBytes: paddingBytes, + } + v[0] = reflect.ValueOf(req) }, } diff --git a/lnwire/message.go b/lnwire/message.go index d0d5e891c0..e6de25d25b 100644 --- a/lnwire/message.go +++ b/lnwire/message.go @@ -1,7 +1,7 @@ // Copyright (c) 2013-2017 The btcsuite developers // Copyright (c) 2015-2016 The Decred developers // code derived from https://github .com/btcsuite/btcd/blob/master/wire/message.go -// Copyright (C) 2015-2017 The Lightning Network Developers +// Copyright (C) 2015-2022 The Lightning Network Developers package lnwire @@ -70,7 +70,6 @@ func ErrorPayloadTooLarge(size int) error { "but maximum message payload is %d bytes", size, MaxMsgBody, ) - } // String return the string representation of message type. diff --git a/lnwire/message_test.go b/lnwire/message_test.go index 55e0c01539..b934b928db 100644 --- a/lnwire/message_test.go +++ b/lnwire/message_test.go @@ -7,14 +7,14 @@ import ( "image/color" "io" "math" - "math/big" "math/rand" "net" "sync" "testing" - "github.com/btcsuite/btcd/btcec" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/tor" "github.com/stretchr/testify/mock" @@ -25,10 +25,7 @@ const deliveryAddressMaxSize = 34 const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" var ( - testSig = &btcec.Signature{ - R: new(big.Int), - S: new(big.Int), - } + testSig = &ecdsa.Signature{} testNodeSig, _ = lnwire.NewSigFromSignature(testSig) testNumExtraBytes = 1000 @@ -200,7 +197,6 @@ func BenchmarkWriteMessage(b *testing.B) { m := msg // Run each message as a sub benchmark test. b.Run(msg.MsgType().String(), func(b *testing.B) { - b.ResetTimer() for i := 0; i < b.N; i++ { // Fetch a buffer from the pool and reset it. @@ -866,7 +862,7 @@ func randRawKey(t testing.TB) [33]byte { var n [33]byte - priv, err := btcec.NewPrivateKey(btcec.S256()) + priv, err := btcec.NewPrivateKey() require.NoError(t, err, "failed to create privKey") copy(n[:], priv.PubKey().SerializeCompressed()) @@ -877,7 +873,7 @@ func randRawKey(t testing.TB) [33]byte { func randPubKey(t testing.TB) *btcec.PublicKey { t.Helper() - priv, err := btcec.NewPrivateKey(btcec.S256()) + priv, err := btcec.NewPrivateKey() require.NoError(t, err, "failed to create pubkey") return priv.PubKey() diff --git a/lnwire/msat.go b/lnwire/msat.go index d3789dfa31..7473d72c82 100644 --- a/lnwire/msat.go +++ b/lnwire/msat.go @@ -3,7 +3,7 @@ package lnwire import ( "fmt" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) const ( diff --git a/lnwire/msat_test.go b/lnwire/msat_test.go index 5b543558f4..50e51f554c 100644 --- a/lnwire/msat_test.go +++ b/lnwire/msat_test.go @@ -3,7 +3,7 @@ package lnwire import ( "testing" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) func TestMilliSatoshiConversion(t *testing.T) { diff --git a/lnwire/netaddress.go b/lnwire/netaddress.go index f31ac1f92a..dd5a7c57b8 100644 --- a/lnwire/netaddress.go +++ b/lnwire/netaddress.go @@ -4,7 +4,7 @@ import ( "fmt" "net" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/wire" ) diff --git a/lnwire/netaddress_test.go b/lnwire/netaddress_test.go index 7988a73641..dbefef32af 100644 --- a/lnwire/netaddress_test.go +++ b/lnwire/netaddress_test.go @@ -5,7 +5,7 @@ import ( "net" "testing" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" ) func TestNetAddressDisplay(t *testing.T) { @@ -17,7 +17,7 @@ func TestNetAddressDisplay(t *testing.T) { t.Fatalf("unable to decode str: %v", err) } - pubKey, err := btcec.ParsePubKey(pubHex, btcec.S256()) + pubKey, err := btcec.ParsePubKey(pubHex) if err != nil { t.Fatalf("unable to parse pubkey: %v", err) } diff --git a/lnwire/onion_error.go b/lnwire/onion_error.go index 60d61184d1..593f58e3d6 100644 --- a/lnwire/onion_error.go +++ b/lnwire/onion_error.go @@ -2,13 +2,12 @@ package lnwire import ( "bufio" + "bytes" "crypto/sha256" "encoding/binary" "fmt" "io" - "bytes" - "github.com/davecgh/go-spew/spew" "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/tlv" diff --git a/lnwire/opaque_addrs.go b/lnwire/opaque_addrs.go new file mode 100644 index 0000000000..92f59ea21a --- /dev/null +++ b/lnwire/opaque_addrs.go @@ -0,0 +1,34 @@ +package lnwire + +import ( + "encoding/hex" + "net" +) + +// OpaqueAddrs is used to store the address bytes for address types that are +// unknown to us. +type OpaqueAddrs struct { + Payload []byte +} + +// A compile-time assertion to ensure that OpaqueAddrs meets the net.Addr +// interface. +var _ net.Addr = (*OpaqueAddrs)(nil) + +// String returns a human-readable string describing the target OpaqueAddrs. +// Since this is an unknown address (and could even be multiple addresses), we +// just return the hex string of the payload. +// +// This part of the net.Addr interface. +func (o *OpaqueAddrs) String() string { + return hex.EncodeToString(o.Payload) +} + +// Network returns the name of the network this address is bound to. Since this +// is an unknown address, we don't know the network and so just return a string +// indicating this. +// +// This part of the net.Addr interface. +func (o *OpaqueAddrs) Network() string { + return "unknown network for unrecognized address type" +} diff --git a/lnwire/open_channel.go b/lnwire/open_channel.go index 273129ad2a..bb86cb2c1a 100644 --- a/lnwire/open_channel.go +++ b/lnwire/open_channel.go @@ -4,9 +4,9 @@ import ( "bytes" "io" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/tlv" ) @@ -236,7 +236,6 @@ func (o *OpenChannel) Encode(w *bytes.Buffer, pver uint32) error { if err := WritePublicKey(w, o.HtlcPoint); err != nil { return err - } if err := WritePublicKey(w, o.FirstCommitmentPoint); err != nil { diff --git a/lnwire/ping.go b/lnwire/ping.go index 1e2877d0c8..a21f2fa8b7 100644 --- a/lnwire/ping.go +++ b/lnwire/ping.go @@ -38,9 +38,16 @@ var _ Message = (*Ping)(nil) // // This is part of the lnwire.Message interface. func (p *Ping) Decode(r io.Reader, pver uint32) error { - return ReadElements(r, - &p.NumPongBytes, - &p.PaddingBytes) + err := ReadElements(r, &p.NumPongBytes, &p.PaddingBytes) + if err != nil { + return err + } + + if p.NumPongBytes > MaxPongBytes { + return ErrMaxPongBytesExceeded + } + + return nil } // Encode serializes the target Ping into the passed io.Writer observing the diff --git a/lnwire/pong.go b/lnwire/pong.go index 953eef5f5b..3ab80d70f8 100644 --- a/lnwire/pong.go +++ b/lnwire/pong.go @@ -2,9 +2,19 @@ package lnwire import ( "bytes" + "fmt" "io" ) +// MaxPongBytes is the maximum number of extra bytes a pong can be requested to +// send. The type of the message (19) takes 2 bytes, the length field takes up +// 2 bytes, leaving 65531 bytes. +const MaxPongBytes = 65531 + +// ErrMaxPongBytesExceeded indicates that the NumPongBytes field from the ping +// message has exceeded MaxPongBytes. +var ErrMaxPongBytesExceeded = fmt.Errorf("pong bytes exceeded") + // PongPayload is a set of opaque bytes sent in response to a ping message. type PongPayload []byte diff --git a/lnwire/query_short_chan_ids_test.go b/lnwire/query_short_chan_ids_test.go index 6a2ecb62d8..e421840446 100644 --- a/lnwire/query_short_chan_ids_test.go +++ b/lnwire/query_short_chan_ids_test.go @@ -49,7 +49,6 @@ var ( // TestQueryShortChanIDsUnsorted tests that decoding a QueryShortChanID request // that contains duplicate or unsorted ids returns an ErrUnsortedSIDs failure. func TestQueryShortChanIDsUnsorted(t *testing.T) { - for _, test := range unsortedSidTests { test := test t.Run(test.name, func(t *testing.T) { diff --git a/lnwire/revoke_and_ack.go b/lnwire/revoke_and_ack.go index 0acf3ef7b6..bdc06d2fe4 100644 --- a/lnwire/revoke_and_ack.go +++ b/lnwire/revoke_and_ack.go @@ -4,7 +4,7 @@ import ( "bytes" "io" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" ) // RevokeAndAck is sent by either side once a CommitSig message has been diff --git a/lnwire/signature.go b/lnwire/signature.go index 2adf26133c..f0bed72cb3 100644 --- a/lnwire/signature.go +++ b/lnwire/signature.go @@ -1,35 +1,74 @@ package lnwire import ( + "errors" "fmt" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/lightningnetwork/lnd/input" ) // Sig is a fixed-sized ECDSA signature. Unlike Bitcoin, we use fixed sized // signatures on the wire, instead of DER encoded signatures. This type // provides several methods to convert to/from a regular Bitcoin DER encoded -// signature (raw bytes and *btcec.Signature). +// signature (raw bytes and *ecdsa.Signature). type Sig [64]byte +var ( + errSigTooShort = errors.New("malformed signature: too short") + errBadLength = errors.New("malformed signature: bad length") + errBadRLength = errors.New("malformed signature: bogus R length") + errBadSLength = errors.New("malformed signature: bogus S length") + errRTooLong = errors.New("R is over 32 bytes long without padding") + errSTooLong = errors.New("S is over 32 bytes long without padding") +) + // NewSigFromRawSignature returns a Sig from a Bitcoin raw signature encoded in // the canonical DER encoding. func NewSigFromRawSignature(sig []byte) (Sig, error) { var b Sig - if len(sig) == 0 { - return b, fmt.Errorf("cannot decode empty signature") + // Check the total length is above the minimal. + if len(sig) < ecdsa.MinSigLen { + return b, errSigTooShort } - // Extract lengths of R and S. The DER representation is laid out as - // 0x30 0x02 r 0x02 s - // which means the length of R is the 4th byte and the length of S - // is the second byte after R ends. 0x02 signifies a length-prefixed, + // The DER representation is laid out as: + // 0x30 0x02 r 0x02 s + // which means the length of R is the 4th byte and the length of S is + // the second byte after R ends. 0x02 signifies a length-prefixed, // zero-padded, big-endian bigint. 0x30 signifies a DER signature. - // See the Serialize() method for btcec.Signature for details. - rLen := sig[3] - sLen := sig[5+rLen] + // See the Serialize() method for ecdsa.Signature for details. + + // Reading , remaining: [0x02 r 0x02 s] + sigLen := int(sig[1]) + + // siglen should be less than the entire message and greater than + // the minimal message size. + if sigLen+2 > len(sig) || sigLen+2 < ecdsa.MinSigLen { + return b, errBadLength + } + + // Reading , remaining: [r 0x02 s] + rLen := int(sig[3]) + + // rLen must be positive and must be able to fit in other elements. + // Assuming s is one byte, then we have 0x30, , 0x20, + // , 0x20, , s, a total of 7 bytes. + if rLen <= 0 || rLen+7 > len(sig) { + return b, errBadRLength + } + + // Reading , remaining: [s] + sLen := int(sig[5+rLen]) + + // S should be the rest of the string. + // sLen must be positive and must be able to fit in other elements. + // We know r is rLen bytes, and we have 0x30, , 0x20, + // , 0x20, , a total of rLen+6 bytes. + if sLen <= 0 || sLen+rLen+6 > len(sig) { + return b, errBadSLength + } // Check to make sure R and S can both fit into their intended buffers. // We check S first because these code blocks decrement sLen and rLen @@ -39,8 +78,7 @@ func NewSigFromRawSignature(sig []byte) (Sig, error) { // check S first. if sLen > 32 { if (sLen > 33) || (sig[6+rLen] != 0x00) { - return b, fmt.Errorf("S is over 32 bytes long " + - "without padding") + return b, errSTooLong } sLen-- copy(b[64-sLen:], sig[7+rLen:]) @@ -51,8 +89,7 @@ func NewSigFromRawSignature(sig []byte) (Sig, error) { // Do the same for R as we did for S if rLen > 32 { if (rLen > 33) || (sig[4] != 0x00) { - return b, fmt.Errorf("R is over 32 bytes long " + - "without padding") + return b, errRTooLong } rLen-- copy(b[32-rLen:], sig[5:5+rLen]) @@ -64,7 +101,7 @@ func NewSigFromRawSignature(sig []byte) (Sig, error) { } // NewSigFromSignature creates a new signature as used on the wire, from an -// existing btcec.Signature. +// existing ecdsa.Signature. func NewSigFromSignature(e input.Signature) (Sig, error) { if e == nil { return Sig{}, fmt.Errorf("cannot decode empty signature") @@ -72,7 +109,7 @@ func NewSigFromSignature(e input.Signature) (Sig, error) { // Nil is still a valid interface, apparently. So we need a more // explicit check here. - if ecsig, ok := e.(*btcec.Signature); ok && ecsig == nil { + if ecsig, ok := e.(*ecdsa.Signature); ok && ecsig == nil { return Sig{}, fmt.Errorf("cannot decode empty signature") } @@ -80,12 +117,12 @@ func NewSigFromSignature(e input.Signature) (Sig, error) { return NewSigFromRawSignature(e.Serialize()) } -// ToSignature converts the fixed-sized signature to a btcec.Signature objects +// ToSignature converts the fixed-sized signature to a ecdsa.Signature objects // which can be used for signature validation checks. -func (b *Sig) ToSignature() (*btcec.Signature, error) { +func (b *Sig) ToSignature() (*ecdsa.Signature, error) { // Parse the signature with strict checks. sigBytes := b.ToSignatureBytes() - sig, err := btcec.ParseDERSignature(sigBytes, btcec.S256()) + sig, err := ecdsa.ParseDERSignature(sigBytes) if err != nil { return nil, err } diff --git a/lnwire/signature_test.go b/lnwire/signature_test.go index ae9e43b698..48ce212a05 100644 --- a/lnwire/signature_test.go +++ b/lnwire/signature_test.go @@ -5,7 +5,9 @@ import ( "math/big" "testing" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "github.com/stretchr/testify/require" ) func TestSignatureSerializeDeserialize(t *testing.T) { @@ -13,8 +15,8 @@ func TestSignatureSerializeDeserialize(t *testing.T) { // Local-scoped closure to serialize and deserialize a Signature and // check for errors as well as check if the results are correct. - signatureSerializeDeserialize := func(e btcec.Signature) error { - sig, err := NewSigFromSignature(&e) + signatureSerializeDeserialize := func(e *ecdsa.Signature) error { + sig, err := NewSigFromSignature(e) if err != nil { return err } @@ -24,67 +26,249 @@ func TestSignatureSerializeDeserialize(t *testing.T) { return err } - if e.R.Cmp(e2.R) != 0 { - return fmt.Errorf("Pre/post-serialize Rs don't match"+ - ": %s, %s", e.R, e2.R) - } - if e.S.Cmp(e2.S) != 0 { - return fmt.Errorf("Pre/post-serialize Ss don't match"+ - ": %s, %s", e.S, e2.S) + if !e.IsEqual(e2) { + return fmt.Errorf("pre/post-serialize sigs don't " + + "match") } return nil } - sig := btcec.Signature{} - // Check R = N-1, S = 128. - sig.R = big.NewInt(1) // Allocate a big.Int before we call .Sub. - sig.R.Sub(btcec.S256().N, sig.R) - sig.S = big.NewInt(128) + r := big.NewInt(1) // Allocate a big.Int before we call .Sub. + r.Sub(btcec.S256().N, r) + rScalar := new(btcec.ModNScalar) + rScalar.SetByteSlice(r.Bytes()) + + sig := ecdsa.NewSignature(rScalar, new(btcec.ModNScalar).SetInt(128)) err := signatureSerializeDeserialize(sig) if err != nil { t.Fatalf("R = N-1, S = 128: %s", err.Error()) } // Check R = N-1, S = 127. - sig.S = big.NewInt(127) + sig = ecdsa.NewSignature(rScalar, new(btcec.ModNScalar).SetInt(127)) err = signatureSerializeDeserialize(sig) if err != nil { t.Fatalf("R = N-1, S = 127: %s", err.Error()) } // Check R = N-1, S = N>>1. - sig.S.Set(btcec.S256().N) - sig.S.Rsh(sig.S, 1) + s := new(big.Int).Set(btcec.S256().N) + s.Rsh(s, 1) + sScalar := new(btcec.ModNScalar) + sScalar.SetByteSlice(s.Bytes()) + sig = ecdsa.NewSignature(rScalar, sScalar) err = signatureSerializeDeserialize(sig) if err != nil { t.Fatalf("R = N-1, S = N>>1: %s", err.Error()) } // Check R = N-1, S = N. - sig.S.Set(btcec.S256().N) + s = new(big.Int).Set(btcec.S256().N) + overflow := sScalar.SetByteSlice(s.Bytes()) + if !overflow { + t.Fatalf("Expect ModNScalar to overflow when setting N but " + + "didn't") + } + sig = ecdsa.NewSignature(rScalar, sScalar) err = signatureSerializeDeserialize(sig) - if err.Error() != "signature S isn't 1 or more" { + if err.Error() != "invalid signature: S is 0" { t.Fatalf("R = N-1, S = N should become R = N-1, S = 0: %s", err.Error()) } // Check R = N-1, S = N-1. - sig.S.Sub(sig.S, big.NewInt(1)) + s = new(big.Int).Set(btcec.S256().N) + s.Sub(s, big.NewInt(1)) + sScalar.SetByteSlice(s.Bytes()) + sig = ecdsa.NewSignature(rScalar, sScalar) err = signatureSerializeDeserialize(sig) - if err.Error() != "Pre/post-serialize Ss don't match: 115792089237316"+ - "195423570985008687907852837564279074904382605163141518161494"+ - "336, 1" { + if err.Error() != "pre/post-serialize sigs don't match" { t.Fatalf("R = N-1, S = N-1 should become R = N-1, S = 1: %s", err.Error()) } // Check R = 2N, S = 128 - sig.R.Mul(btcec.S256().N, big.NewInt(2)) - sig.S.Set(big.NewInt(127)) - err = signatureSerializeDeserialize(sig) - if err.Error() != "R is over 32 bytes long without padding" { - t.Fatalf("R = 2N, S = 128, R should be over 32 bytes: %s", - err.Error()) + // This cannot be tested anymore since the new ecdsa package creates + // the signature from ModNScalar values which don't allow setting a + // value larger than N (hence the name mod n). +} + +var ( + // signatures from bitcoin blockchain tx + // 0437cd7f8525ceed2324359c2d0ba26006d92d85. + normalSig = []byte{ + 0x30, 0x44, 0x02, 0x20, + // r value + 0x4e, 0x45, 0xe1, 0x69, 0x32, 0xb8, 0xaf, 0x51, + 0x49, 0x61, 0xa1, 0xd3, 0xa1, 0xa2, 0x5f, 0xdf, + 0x3f, 0x4f, 0x77, 0x32, 0xe9, 0xd6, 0x24, 0xc6, + 0xc6, 0x15, 0x48, 0xab, 0x5f, 0xb8, 0xcd, 0x41, + + 0x02, 0x20, + // s value + 0x18, 0x15, 0x22, 0xec, 0x8e, 0xca, 0x07, 0xde, + 0x48, 0x60, 0xa4, 0xac, 0xdd, 0x12, 0x90, 0x9d, + 0x83, 0x1c, 0xc5, 0x6c, 0xbb, 0xac, 0x46, 0x22, + 0x08, 0x22, 0x21, 0xa8, 0x76, 0x8d, 0x1d, 0x09, + } + + // minimal length with 1 byte r and 1 byte s. + minSig = []byte{ + 0x30, 0x06, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00, + } + + // sig length is below 6. + smallLenSig = []byte{ + 0x30, 0x05, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00, + } + + // sig length is above 6. + largeLenSig = []byte{ + 0x30, 0x07, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00, + } + + // r length is 2. + largeRSig = []byte{ + 0x30, 0x06, 0x02, 0x02, 0x00, 0x02, 0x01, 0x00, + } + + // r length is 0. + smallRSig = []byte{ + 0x30, 0x06, 0x02, 0x00, 0x00, 0x02, 0x01, 0x00, + } + + // s length is 2. + largeSSig = []byte{ + 0x30, 0x06, 0x02, 0x01, 0x00, 0x02, 0x02, 0x00, + } + + // s length is 0. + smallSSig = []byte{ + 0x30, 0x06, 0x02, 0x01, 0x00, 0x02, 0x00, 0x00, + } + + // r length is 33. + missPaddingRSig = []byte{ + 0x30, 0x25, 0x02, 0x21, + // r value with a wrong padding. + 0xff, + 0x4e, 0x45, 0xe1, 0x69, 0x32, 0xb8, 0xaf, 0x51, + 0x49, 0x61, 0xa1, 0xd3, 0xa1, 0xa2, 0x5f, 0xdf, + 0x3f, 0x4f, 0x77, 0x32, 0xe9, 0xd6, 0x24, 0xc6, + 0xc6, 0x15, 0x48, 0xab, 0x5f, 0xb8, 0xcd, 0x41, + // s value is 0. + 0x02, 0x01, 0x00, + } + + // s length is 33. + missPaddingSSig = []byte{ + // r value is 0. + 0x30, 0x25, 0x02, 0x01, 0x00, + 0x02, 0x21, + // s value with a wrong padding. + 0xff, + 0x18, 0x15, 0x22, 0xec, 0x8e, 0xca, 0x07, 0xde, + 0x48, 0x60, 0xa4, 0xac, 0xdd, 0x12, 0x90, 0x9d, + 0x83, 0x1c, 0xc5, 0x6c, 0xbb, 0xac, 0x46, 0x22, + 0x08, 0x22, 0x21, 0xa8, 0x76, 0x8d, 0x1d, 0x09, + } +) + +func TestNewSigFromRawSignature(t *testing.T) { + t.Parallel() + testCases := []struct { + name string + rawSig []byte + expectedErr error + expectedSig Sig + }{ + { + name: "valid signature", + rawSig: normalSig, + expectedErr: nil, + expectedSig: Sig{ + // r value + 0x4e, 0x45, 0xe1, 0x69, 0x32, 0xb8, 0xaf, 0x51, + 0x49, 0x61, 0xa1, 0xd3, 0xa1, 0xa2, 0x5f, 0xdf, + 0x3f, 0x4f, 0x77, 0x32, 0xe9, 0xd6, 0x24, 0xc6, + 0xc6, 0x15, 0x48, 0xab, 0x5f, 0xb8, 0xcd, 0x41, + // s value + 0x18, 0x15, 0x22, 0xec, 0x8e, 0xca, 0x07, 0xde, + 0x48, 0x60, 0xa4, 0xac, 0xdd, 0x12, 0x90, 0x9d, + 0x83, 0x1c, 0xc5, 0x6c, 0xbb, 0xac, 0x46, 0x22, + 0x08, 0x22, 0x21, 0xa8, 0x76, 0x8d, 0x1d, 0x09, + }, + }, + { + name: "minimal length signature", + rawSig: minSig, + expectedErr: nil, + // NOTE: r and s are both 0x00 here. + expectedSig: Sig{}, + }, + { + name: "signature length too short", + rawSig: []byte{0x30}, + expectedErr: errSigTooShort, + expectedSig: Sig{}, + }, + { + name: "sig length too large", + rawSig: largeLenSig, + expectedErr: errBadLength, + expectedSig: Sig{}, + }, + { + name: "sig length too small", + rawSig: smallLenSig, + expectedErr: errBadLength, + expectedSig: Sig{}, + }, + { + name: "r length too large", + rawSig: largeRSig, + expectedErr: errBadRLength, + expectedSig: Sig{}, + }, + { + name: "r length too small", + rawSig: smallRSig, + expectedErr: errBadRLength, + expectedSig: Sig{}, + }, + { + name: "s length too large", + rawSig: largeSSig, + expectedErr: errBadSLength, + expectedSig: Sig{}, + }, + { + name: "s length too small", + rawSig: smallSSig, + expectedErr: errBadSLength, + expectedSig: Sig{}, + }, + { + name: "missing padding in r", + rawSig: missPaddingRSig, + expectedErr: errRTooLong, + expectedSig: Sig{}, + }, + { + name: "missing padding in s", + rawSig: missPaddingSSig, + expectedErr: errSTooLong, + expectedSig: Sig{}, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + result, err := NewSigFromRawSignature(tc.rawSig) + require.Equal(t, tc.expectedErr, err) + require.Equal(t, tc.expectedSig, result) + }) } } diff --git a/lnwire/update_fail_htlc.go b/lnwire/update_fail_htlc.go index 61f02bac97..8cd9c76878 100644 --- a/lnwire/update_fail_htlc.go +++ b/lnwire/update_fail_htlc.go @@ -15,7 +15,7 @@ type OpaqueReason []byte // next commitment transaction, with the UpdateFailHTLC propagated backwards in // the route to fully undo the HTLC. type UpdateFailHTLC struct { - // ChanIDPoint is the particular active channel that this + // ChanID is the particular active channel that this // UpdateFailHTLC is bound to. ChanID ChannelID diff --git a/lnwire/writer.go b/lnwire/writer.go index 6a20c57157..5b99ab368a 100644 --- a/lnwire/writer.go +++ b/lnwire/writer.go @@ -9,9 +9,9 @@ import ( "math" "net" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/tor" ) @@ -32,6 +32,9 @@ var ( // ErrNilNetAddress is returned when a nil value is used in []net.Addr. ErrNilNetAddress = errors.New("cannot write nil address") + // ErrNilOpaqueAddrs is returned when the supplied address is nil. + ErrNilOpaqueAddrs = errors.New("cannot write nil OpaqueAddrs") + // ErrNilPublicKey is returned when a nil pubkey is used. ErrNilPublicKey = errors.New("cannot write nil pubkey") @@ -50,32 +53,20 @@ func ErrOutpointIndexTooBig(index uint32) error { } // WriteBytes appends the given bytes to the provided buffer. -// -// Note: We intentionally skip the interfacer linter check here because we want -// to have concrete type (bytes.Buffer) rather than interface type (io.Write) -// due to performance concern. -func WriteBytes(buf *bytes.Buffer, b []byte) error { // nolint: interfacer +func WriteBytes(buf *bytes.Buffer, b []byte) error { _, err := buf.Write(b) return err } // WriteUint8 appends the uint8 to the provided buffer. -// -// Note: We intentionally skip the interfacer linter check here because we want -// to have concrete type (bytes.Buffer) rather than interface type (io.Write) -// due to performance concern. -func WriteUint8(buf *bytes.Buffer, n uint8) error { // nolint: interfacer +func WriteUint8(buf *bytes.Buffer, n uint8) error { _, err := buf.Write([]byte{n}) return err } // WriteUint16 appends the uint16 to the provided buffer. It encodes the // integer using big endian byte order. -// -// Note: We intentionally skip the interfacer linter check here because we want -// to have concrete type (bytes.Buffer) rather than interface type (io.Write) -// due to performance concern. -func WriteUint16(buf *bytes.Buffer, n uint16) error { // nolint: interfacer +func WriteUint16(buf *bytes.Buffer, n uint16) error { var b [2]byte binary.BigEndian.PutUint16(b[:], n) _, err := buf.Write(b[:]) @@ -93,11 +84,7 @@ func WriteUint32(buf *bytes.Buffer, n uint32) error { // WriteUint64 appends the uint64 to the provided buffer. It encodes the // integer using big endian byte order. -// -// Note: We intentionally skip the interfacer linter check here because we want -// to have concrete type (bytes.Buffer) rather than interface type (io.Write) -// due to performance concern. -func WriteUint64(buf *bytes.Buffer, n uint64) error { // nolint: interfacer +func WriteUint64(buf *bytes.Buffer, n uint64) error { var b [8]byte binary.BigEndian.PutUint64(b[:], n) _, err := buf.Write(b[:]) @@ -122,7 +109,6 @@ func WritePublicKey(buf *bytes.Buffer, pub *btcec.PublicKey) error { serializedPubkey := pub.SerializeCompressed() return WriteBytes(buf, serializedPubkey) - } // WriteChannelID appends the ChannelID to the provided buffer. @@ -195,13 +181,7 @@ func WriteFailCode(buf *bytes.Buffer, e FailCode) error { // WriteRawFeatureVector encodes the feature using the feature's Encode method // and appends the data to the provided buffer. An error will return if the // passed feature is nil. -// -// Note: We intentionally skip the interfacer linter check here because we want -// to have concrete type (bytes.Buffer) rather than interface type (io.Write) -// due to performance concern. -func WriteRawFeatureVector(buf *bytes.Buffer, // nolint: interfacer - feature *RawFeatureVector) error { - +func WriteRawFeatureVector(buf *bytes.Buffer, feature *RawFeatureVector) error { if feature == nil { return ErrNilFeatureVector } @@ -281,11 +261,7 @@ func WriteBool(buf *bytes.Buffer, b bool) error { // WritePkScript appends the script to the provided buffer. Returns an error if // the provided script exceeds 34 bytes. -// -// Note: We intentionally skip the interfacer linter check here because we want -// to have concrete type (bytes.Buffer) rather than interface type (io.Write) -// due to performance concern. -func WritePkScript(buf *bytes.Buffer, s PkScript) error { // nolint: interfacer +func WritePkScript(buf *bytes.Buffer, s PkScript) error { // The largest script we'll accept is a p2wsh which is exactly // 34 bytes long. scriptLength := len(s) @@ -384,6 +360,16 @@ func WriteOnionAddr(buf *bytes.Buffer, addr *tor.OnionAddr) error { return WriteUint16(buf, uint16(addr.Port)) } +// WriteOpaqueAddrs appends the payload of the given OpaqueAddrs to buffer. +func WriteOpaqueAddrs(buf *bytes.Buffer, addr *OpaqueAddrs) error { + if addr == nil { + return ErrNilOpaqueAddrs + } + + _, err := buf.Write(addr.Payload) + return err +} + // WriteNetAddrs appends a slice of addresses to the provided buffer with the // length info. func WriteNetAddrs(buf *bytes.Buffer, addresses []net.Addr) error { @@ -403,6 +389,10 @@ func WriteNetAddrs(buf *bytes.Buffer, addresses []net.Addr) error { if err := WriteOnionAddr(addrBuf, a); err != nil { return err } + case *OpaqueAddrs: + if err := WriteOpaqueAddrs(addrBuf, a); err != nil { + return err + } default: return ErrNilNetAddress } @@ -413,13 +403,7 @@ func WriteNetAddrs(buf *bytes.Buffer, addresses []net.Addr) error { } // writeDataWithLength writes the data and its length to the buffer. -// -// Note: We intentionally skip the interfacer linter check here because we want -// to have concrete type (bytes.Buffer) rather than interface type (io.Write) -// due to performance concern. -func writeDataWithLength(buf *bytes.Buffer, // nolint: interfacer - data []byte) error { - +func writeDataWithLength(buf *bytes.Buffer, data []byte) error { var l [2]byte binary.BigEndian.PutUint16(l[:], uint16(len(data))) if _, err := buf.Write(l[:]); err != nil { diff --git a/lnwire/writer_test.go b/lnwire/writer_test.go index 3d16b13368..68594e59a4 100644 --- a/lnwire/writer_test.go +++ b/lnwire/writer_test.go @@ -8,9 +8,9 @@ import ( "net" "testing" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/tor" "github.com/stretchr/testify/require" ) diff --git a/log.go b/log.go index 68b592bb4e..747c807fd2 100644 --- a/log.go +++ b/log.go @@ -23,20 +23,24 @@ import ( "github.com/lightningnetwork/lnd/invoices" "github.com/lightningnetwork/lnd/lnrpc/autopilotrpc" "github.com/lightningnetwork/lnd/lnrpc/chainrpc" + "github.com/lightningnetwork/lnd/lnrpc/devrpc" "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc" + "github.com/lightningnetwork/lnd/lnrpc/neutrinorpc" + "github.com/lightningnetwork/lnd/lnrpc/peersrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lnrpc/signrpc" "github.com/lightningnetwork/lnd/lnrpc/verrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" "github.com/lightningnetwork/lnd/lnwallet" + "github.com/lightningnetwork/lnd/lnwallet/btcwallet" "github.com/lightningnetwork/lnd/lnwallet/chancloser" "github.com/lightningnetwork/lnd/lnwallet/chanfunding" + "github.com/lightningnetwork/lnd/lnwallet/rpcwallet" "github.com/lightningnetwork/lnd/monitoring" "github.com/lightningnetwork/lnd/netann" "github.com/lightningnetwork/lnd/peer" "github.com/lightningnetwork/lnd/peernotifier" "github.com/lightningnetwork/lnd/routing" - "github.com/lightningnetwork/lnd/routing/localchans" "github.com/lightningnetwork/lnd/rpcperms" "github.com/lightningnetwork/lnd/signal" "github.com/lightningnetwork/lnd/sweep" @@ -138,6 +142,8 @@ func SetupLoggers(root *build.RotatingLogWriter, interceptor signal.Interceptor) AddSubLogger(root, "SGNR", interceptor, signrpc.UseLogger) AddSubLogger(root, "WLKT", interceptor, walletrpc.UseLogger) AddSubLogger(root, "ARPC", interceptor, autopilotrpc.UseLogger) + AddSubLogger(root, "NRPC", interceptor, neutrinorpc.UseLogger) + AddSubLogger(root, "DRPC", interceptor, devrpc.UseLogger) AddSubLogger(root, "INVC", interceptor, invoices.UseLogger) AddSubLogger(root, "NANN", interceptor, netann.UseLogger) AddSubLogger(root, "WTWR", interceptor, watchtower.UseLogger) @@ -152,7 +158,7 @@ func SetupLoggers(root *build.RotatingLogWriter, interceptor signal.Interceptor) AddSubLogger(root, "PEER", interceptor, peer.UseLogger) AddSubLogger(root, "CHCL", interceptor, chancloser.UseLogger) - AddSubLogger(root, routing.Subsystem, interceptor, routing.UseLogger, localchans.UseLogger) + AddSubLogger(root, routing.Subsystem, interceptor, routing.UseLogger) AddSubLogger(root, routerrpc.Subsystem, interceptor, routerrpc.UseLogger) AddSubLogger(root, chanfitness.Subsystem, interceptor, chanfitness.UseLogger) AddSubLogger(root, verrpc.Subsystem, interceptor, verrpc.UseLogger) @@ -163,6 +169,9 @@ func SetupLoggers(root *build.RotatingLogWriter, interceptor signal.Interceptor) AddSubLogger(root, cluster.Subsystem, interceptor, cluster.UseLogger) AddSubLogger(root, rpcperms.Subsystem, interceptor, rpcperms.UseLogger) AddSubLogger(root, tor.Subsystem, interceptor, tor.UseLogger) + AddSubLogger(root, btcwallet.Subsystem, interceptor, btcwallet.UseLogger) + AddSubLogger(root, rpcwallet.Subsystem, interceptor, rpcwallet.UseLogger) + AddSubLogger(root, peersrpc.Subsystem, interceptor, peersrpc.UseLogger) } // AddSubLogger is a helper method to conveniently create and register the diff --git a/macaroons/constraints.go b/macaroons/constraints.go index 2cb36cacc1..642f8edcf4 100644 --- a/macaroons/constraints.go +++ b/macaroons/constraints.go @@ -9,7 +9,6 @@ import ( "time" "google.golang.org/grpc/peer" - "gopkg.in/macaroon-bakery.v2/bakery/checkers" macaroon "gopkg.in/macaroon.v2" ) @@ -216,3 +215,36 @@ func HasCustomCaveat(mac *macaroon.Macaroon, customCaveatName string) bool { return false } + +// GetCustomCaveatCondition returns the custom caveat condition for the given +// custom caveat name from the given macaroon. +func GetCustomCaveatCondition(mac *macaroon.Macaroon, + customCaveatName string) string { + + if mac == nil { + return "" + } + + caveatPrefix := []byte(fmt.Sprintf( + "%s %s ", CondLndCustom, customCaveatName, + )) + for _, caveat := range mac.Caveats() { + // The caveat id has a format of + // "lnd-custom [custom-caveat-name] [custom-caveat-condition]" + // and we only want the condition part. If we match the prefix + // part we return the condition that comes after the prefix. + if bytes.HasPrefix(caveat.Id, caveatPrefix) { + caveatSplit := strings.SplitN( + string(caveat.Id), + string(caveatPrefix), + 2, + ) + if len(caveatSplit) == 2 { + return caveatSplit[1] + } + } + } + + // We didn't find a condition for the given custom caveat name. + return "" +} diff --git a/macaroons/constraints_test.go b/macaroons/constraints_test.go index 659b5d724a..8bceb0b32c 100644 --- a/macaroons/constraints_test.go +++ b/macaroons/constraints_test.go @@ -6,9 +6,8 @@ import ( "testing" "time" - "github.com/stretchr/testify/require" - "github.com/lightningnetwork/lnd/macaroons" + "github.com/stretchr/testify/require" macaroon "gopkg.in/macaroon.v2" ) @@ -77,6 +76,7 @@ func TestTimeoutConstraint(t *testing.T) { string(testMacaroon.Caveats()[0].Id), expectedTimeCaveatSubstring, ) { + t.Fatalf("Added caveat '%s' does not meet the expectations!", testMacaroon.Caveats()[0].Id) } @@ -132,6 +132,11 @@ func TestCustomConstraint(t *testing.T) { require.False(t, macaroons.HasCustomCaveat(testMacaroon, "something")) require.False(t, macaroons.HasCustomCaveat(nil, "foo")) + customCaveatCondition := macaroons.GetCustomCaveatCondition( + testMacaroon, "unit-test", + ) + require.Equal(t, customCaveatCondition, "test-value") + // Custom caveats don't necessarily need a value, just the name is fine // too to create a tagged macaroon. constraintFunc = macaroons.CustomConstraint("unit-test", "") @@ -144,4 +149,9 @@ func TestCustomConstraint(t *testing.T) { require.True(t, macaroons.HasCustomCaveat(testMacaroon, "unit-test")) require.False(t, macaroons.HasCustomCaveat(testMacaroon, "test-value")) require.False(t, macaroons.HasCustomCaveat(testMacaroon, "something")) + + customCaveatCondition = macaroons.GetCustomCaveatCondition( + testMacaroon, "unit-test", + ) + require.Equal(t, customCaveatCondition, "") } diff --git a/macaroons/service.go b/macaroons/service.go index 3daccf0e24..49cb1c6da7 100644 --- a/macaroons/service.go +++ b/macaroons/service.go @@ -4,6 +4,7 @@ import ( "context" "encoding/hex" "fmt" + "github.com/lightningnetwork/lnd/kvdb" "google.golang.org/grpc/metadata" "gopkg.in/macaroon-bakery.v2/bakery" @@ -245,6 +246,7 @@ func (svc *Service) ListMacaroonIDs(ctxt context.Context) ([][]byte, error) { // found and deleted, it will be returned. func (svc *Service) DeleteMacaroonID(ctxt context.Context, rootKeyID []byte) ([]byte, error) { + return svc.rks.DeleteMacaroonID(ctxt, rootKeyID) } diff --git a/macaroons/store.go b/macaroons/store.go index 7b0c336437..51d1c22539 100644 --- a/macaroons/store.go +++ b/macaroons/store.go @@ -8,10 +8,9 @@ import ( "io" "sync" - "github.com/lightningnetwork/lnd/kvdb" - "github.com/btcsuite/btcwallet/snacl" "github.com/btcsuite/btcwallet/walletdb" + "github.com/lightningnetwork/lnd/kvdb" ) const ( @@ -386,7 +385,6 @@ func (r *RootKeyStorage) ListMacaroonIDs(_ context.Context) ([][]byte, error) { // Read all the items in the bucket and append the keys, which are the // root key IDs we want. err := kvdb.View(r.Backend, func(tx kvdb.RTx) error { - // appendRootKey is a function closure that appends root key ID // to rootKeySlice. appendRootKey := func(k, _ []byte) error { diff --git a/macaroons/store_test.go b/macaroons/store_test.go index 8dfe9e3a83..9eac2912d3 100644 --- a/macaroons/store_test.go +++ b/macaroons/store_test.go @@ -7,10 +7,9 @@ import ( "path" "testing" + "github.com/btcsuite/btcwallet/snacl" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/macaroons" - - "github.com/btcsuite/btcwallet/snacl" "github.com/stretchr/testify/require" ) diff --git a/make/builder.Dockerfile b/make/builder.Dockerfile index 79f40325fd..0c5049ba22 100644 --- a/make/builder.Dockerfile +++ b/make/builder.Dockerfile @@ -4,7 +4,7 @@ # /dev.Dockerfile # /.github/workflows/main.yml # /.github/workflows/release.yml -FROM golang:1.17.1-buster +FROM golang:1.18-buster MAINTAINER Olaoluwa Osuntokun diff --git a/make/release_flags.mk b/make/release_flags.mk index 8eb4adf5f8..10beb70d7f 100644 --- a/make/release_flags.mk +++ b/make/release_flags.mk @@ -17,7 +17,6 @@ dragonfly-amd64 \ freebsd-386 \ freebsd-amd64 \ freebsd-arm \ -illumos-amd64 \ linux-386 \ linux-amd64 \ linux-armv6 \ @@ -38,7 +37,9 @@ windows-386 \ windows-amd64 \ windows-arm -RELEASE_TAGS = autopilotrpc signrpc walletrpc chainrpc invoicesrpc watchtowerrpc monitoring +RELEASE_TAGS = autopilotrpc signrpc walletrpc chainrpc invoicesrpc watchtowerrpc neutrinorpc monitoring peersrpc kvdb_postgres kvdb_etcd + +WASM_RELEASE_TAGS = autopilotrpc signrpc walletrpc chainrpc invoicesrpc watchtowerrpc neutrinorpc monitoring peersrpc # One can either specify a git tag as the version suffix or one is generated # from the current date. diff --git a/make/testing_flags.mk b/make/testing_flags.mk index cad28daec2..a81ce19ccd 100644 --- a/make/testing_flags.mk +++ b/make/testing_flags.mk @@ -1,5 +1,5 @@ DEV_TAGS = dev -RPC_TAGS = autopilotrpc chainrpc invoicesrpc routerrpc signrpc verrpc walletrpc watchtowerrpc wtclientrpc +RPC_TAGS = autopilotrpc chainrpc invoicesrpc neutrinorpc peersrpc routerrpc signrpc verrpc walletrpc watchtowerrpc wtclientrpc LOG_TAGS = TEST_FLAGS = ITEST_FLAGS = diff --git a/mobile/README.md b/mobile/README.md index 2dd0dc10e3..538cf2fbc9 100644 --- a/mobile/README.md +++ b/mobile/README.md @@ -1,67 +1,239 @@ -## Building mobile libraries +# Building mobile libraries -### Prerequisites -#### protoc -Install the dependencies for generating protobuf definitions as stated in -[lnrpc docs]( ../lnrpc/README.md#generate-protobuf-definitions) +## Prerequisites -#### gomobile -Follow [gomobile](https://github.com/golang/go/wiki/Mobile) in order to install -`gomobile` and dependencies. +To build for iOS, you need to run macOS with either +[Command Line Tools](https://developer.apple.com/download/all/?q=command%20line%20tools) +or [Xcode](https://apps.apple.com/app/xcode/id497799835) installed. -Remember to run `gomobile init` (otherwise the `lnd` build might just hang). +To build for Android, you need either +[Android Studio](https://developer.android.com/studio) or +[Command Line Tools](https://developer.android.com/studio#downloads) installed, which in turn must be used to install [Android NDK](https://developer.android.com/ndk/). -Note that `gomobile` only supports building projects from `GOPATH` at this -point. -#### falafel -Install [`falafel`](https://github.com/lightninglabs/falafel): +### Go and Go mobile + +First, follow the instructions to [install Go](https://github.com/lightningnetwork/lnd/blob/master/docs/INSTALL.md#building-a-development-version-from-source). + +Then, install [Go mobile](https://github.com/golang/go/wiki/Mobile) and +initialize it: + +```shell +⛰ go install golang.org/x/mobile/cmd/gomobile@latest +⛰ go mod download golang.org/x/mobile +⛰ gomobile init +``` + +### Docker + +Install and run [Docker](https://www.docker.com/products/docker-desktop). + +### Make + +Check that `make` is available by running the following command without errors: + ```shell -⛰ go get -u -v github.com/lightninglabs/falafel +⛰ make --version ``` -### Building `lnd` for iOS +## Building the libraries + +Note that `gomobile` only supports building projects from `$GOPATH` at this +point. However, with the introduction of Go modules, the source code files are +no longer installed there by default. + +To be able to do so, we must turn off module and using the now deprecated +`go get` command before turning modules back on again. + +```shell +⛰ go env -w GO111MODULE="off" +⛰ go get github.com/lightningnetwork/lnd +⛰ go get golang.org/x/mobile/bind +⛰ go env -w GO111MODULE="on" +``` + +Finally, let’s change directory to the newly created lnd folder inside `$GOPATH`: + +```shell +⛰ cd $GOPATH/src/github.com/lightningnetwork/lnd +``` + +It is not recommended building from the master branch for mainnet. To checkout +the latest tagged release of lnd, run + +```shell +⛰ git checkout $(git describe --match "v[0-9]*" --abbrev=0) +``` + +### iOS + ```shell ⛰ make ios ``` -### Building `lnd` for Android +The Xcode framework file will be found in `mobile/build/ios/Lndmobile.xcframework`. + +### Android + ```shell ⛰ make android ``` -`make mobile` will build both iOS and Android libs. +The AAR library file will be found in `mobile/build/android/Lndmobile.aar`. -### Libraries -After the build has succeeded, the libraries will be found in -`mobile/build/ios/Lndmobile.framework` and -`mobile/build/android/Lndmobile.aar`. Reference your platforms' SDK -documentation for how to add the library to your project. +--- +Tip: `make mobile` will build both iOS and Android libraries. + +--- + +## Generating proto definitions -#### Generating proto definitions for your language. In order to call the methods in the generated library, the serialized proto for the given RPC call must be provided. Similarly, the response will be a serialized proto. -In order to generate protobuf definitions for your language of choice, add the -proto plugin to the `protoc` invocations found in -[`gen_protos.sh`](../lnrpc/gen_protos.sh). For instance to generate protos for -Swift, add `--swift_out=.` and run `make rpc`. +### iOS + +In order to generate protobuf definitions for iOS, add `--swift_out=.` to the +first `protoc` invocation found in [ `gen_protos.sh` ](../lnrpc/gen_protos.sh). + +Then, some changes to [Dockerfile](../lnrpc/Dockerfile) need to be done in +order to use the [Swift protobuf](https://github.com/apple/swift-protobuf) +plugin with protoc: + +1. Replace the base image with `FROM swift:focal` so that Swift can be used. +2. `clang-format='1:7.0*'` is unavailable in Ubuntu Focal. Change that to +`clang-format='1:10.0*'`. +3. On the next line, install Go and set the environment variables by adding the +following commands: + +``` +RUN apt-get install -y wget \ + && wget -c https://golang.org/dl/go1.17.6.linux-amd64.tar.gz -O - \ + | tar -xz -C /usr/local +ENV GOPATH=/go +ENV PATH=$PATH:/usr/local/go/bin:/go/bin +``` -### Options -Similar to lnd, subservers can be conditionally compiled with the build by -setting the tags argument: +4. At the end of the file, just above `CMD`, add the following `RUN` command. +This will download and compile the latest tagged release of Swift protobuf. + +``` +RUN git clone https://github.com/apple/swift-protobuf.git \ +&& cd swift-protobuf \ +&& git checkout $(git describe --tags --abbrev=0) \ +&& swift build -c release \ +&& mv .build/release/protoc-gen-swift /bin +``` + +Finally, run `make rpc`. + +Tip: The generated Swift files will be found in various folders. If you’d like +to move them to the same folder as the framework file, run ```shell -⛰ make ios +⛰ find . -name "*.swift" -print0 | xargs -0 -I {} mv {} mobile/build/ios +``` + +`Lndmobile.xcframework` and all Swift files should now be added to your Xcode +project. You will also need to add [Swift Protobuf](https://github.com/apple/swift-protobuf) +to your project to support the generated code. + +### Android + +#### First option: + +In order to generate protobuf definitions for Android, add `--java_out=.` + +to the first `protoc` invocation found in +[ `gen_protos.sh` ](../lnrpc/gen_protos.sh). Then, run `make rpc`. + + +#### Second option (preferable): + +- You have to install the profobuf plugin to your Android application. +Please, follow this link https://github.com/google/protobuf-gradle-plugin. +- Add this line to your `app build.gradle` file. +```shell +classpath "com.google.protobuf:protobuf-gradle-plugin:0.8.17" +``` +- Create a `proto` folder under the `main` folder. + +![proto_folder](docs/proto_folder.png) + +- Add `aar` file to libs folder. + +- After that add these lines to your `module's` `build.gradle` file: + +```shell +plugins { + id "com.google.protobuf" +} + +android { + sourceSets { + main { + proto { + + } + } + } +} + +dependencies { + implementation fileTree(dir: "libs", include: ["*.jar"]) + implementation "com.google.protobuf:protobuf-javalite:${rootProject.ext.javalite_version}" +} + +protobuf { + protoc { + artifact = "com.google.protobuf:protoc:${rootProject.ext.protoc_version}" + } + generateProtoTasks { + all().each { task -> + task.builtins { + java { + option "lite" + } + } + } + } +} ``` +- Then, copy all the proto files from `lnd/lnrpc` to your `proto` folder, saving the structure. +- Build the project and wait until you see the generated Java proto files in the `build` folder. + + +--- +*Note:* + +If Android Studio tells you that the `aar` file cannot be included into the `app-bundle`, this is a workaround: + +1. Create a separate gradle module +2. Remove everything from there and leave only `aar` and `build.gradle`. -To support subservers that have APIs with name conflicts, pass the "prefix" -flag. This will add the subserver name as a prefix to each method name: +![separate_gradle_module](docs/separate_gradle_module.png) +3. Gradle file should contain only these lines: + +```shell +configurations.maybeCreate("default") +artifacts.add("default", file('Lndmobile.aar')) +``` + +4. In `dependencies` add this line instead of depending on `libs` folder: ```shell -⛰ make ios prefix=1 +implementation project(":lndmobile", { "default" }) ``` +--- + +## Calling the API + +In LND v0.15+ all API methods have prefixed the generated methods with the subserver name. This is required to support subservers with name conflicts. + +eg. `QueryScores` is now `AutopilotQueryScores`. `GetBlockHeader` is now `NeutrinoKitGetBlockHeader`. + +## API docs -### API docs -TODO(halseth) +- [LND gRPC API Reference](https://api.lightning.community) +- [LND Builder’s Guide](https://docs.lightning.engineering) diff --git a/mobile/bindings.go b/mobile/bindings.go index 141955763a..17252953d2 100644 --- a/mobile/bindings.go +++ b/mobile/bindings.go @@ -16,7 +16,7 @@ import ( "google.golang.org/grpc" ) -// lndStarted will be used atomically to ensure only a singel lnd instance is +// lndStarted will be used atomically to ensure only a single lnd instance is // attempted to be started at once. var lndStarted int32 diff --git a/mobile/docs/proto_folder.png b/mobile/docs/proto_folder.png new file mode 100644 index 0000000000..43f0e15701 Binary files /dev/null and b/mobile/docs/proto_folder.png differ diff --git a/mobile/docs/separate_gradle_module.png b/mobile/docs/separate_gradle_module.png new file mode 100644 index 0000000000..cc884ee3b0 Binary files /dev/null and b/mobile/docs/separate_gradle_module.png differ diff --git a/mobile/gen_bindings.sh b/mobile/gen_bindings.sh index 36b478b2b0..035cf79bdf 100755 --- a/mobile/gen_bindings.sh +++ b/mobile/gen_bindings.sh @@ -33,13 +33,13 @@ target_pkg="github.com/lightningnetwork/lnd/lnrpc" # A mapping from grpc service to name of the custom listeners. The grpc server # must be configured to listen on these. -listeners="lightning=lightningLis walletunlocker=lightningLis state=lightningLis" +listeners="lightning=lightningLis walletunlocker=lightningLis state=lightningLis autopilot=lightningLis chainnotifier=lightningLis invoices=lightningLis neutrinokit=lightningLis peers=lightningLis router=lightningLis signer=lightningLis versioner=lightningLis walletkit=lightningLis watchtower=lightningLis watchtowerclient=lightningLis" # Set to 1 to create boiler plate grpc client code and listeners. If more than # one proto file is being parsed, it should only be done once. mem_rpc=1 -PROTOS="lightning.proto walletunlocker.proto stateservice.proto" +PROTOS="lightning.proto walletunlocker.proto stateservice.proto autopilotrpc/autopilot.proto chainrpc/chainnotifier.proto invoicesrpc/invoices.proto neutrinorpc/neutrino.proto peersrpc/peers.proto routerrpc/router.proto signrpc/signer.proto verrpc/verrpc.proto walletrpc/walletkit.proto watchtowerrpc/watchtower.proto wtclientrpc/wtclient.proto" opts="package_name=$pkg,target_package=$target_pkg,listeners=$listeners,mem_rpc=$mem_rpc" diff --git a/monitoring/monitoring_off.go b/monitoring/monitoring_off.go index 8e2a1c2be8..cb8ca45433 100644 --- a/monitoring/monitoring_off.go +++ b/monitoring/monitoring_off.go @@ -6,9 +6,8 @@ package monitoring import ( "fmt" - "google.golang.org/grpc" - "github.com/lightningnetwork/lnd/lncfg" + "google.golang.org/grpc" ) // GetPromInterceptors returns the set of interceptors for Prometheus diff --git a/monitoring/monitoring_on.go b/monitoring/monitoring_on.go index 78a0f7e226..aaf2dfd973 100644 --- a/monitoring/monitoring_on.go +++ b/monitoring/monitoring_on.go @@ -7,11 +7,10 @@ import ( "net/http" "sync" - "google.golang.org/grpc" - grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus" "github.com/lightningnetwork/lnd/lncfg" "github.com/prometheus/client_golang/prometheus/promhttp" + "google.golang.org/grpc" ) var started sync.Once @@ -36,6 +35,14 @@ func ExportPrometheusMetrics(grpcServer *grpc.Server, cfg lncfg.Prometheus) erro grpc_prometheus.Register(grpcServer) + // Enable the histograms which can allow plotting latency + // distributions of inbound calls. However we guard this behind + // another flag as this can generate a lot of additional data, + // as its a high cardinality metric typically. + if cfg.PerfHistograms { + grpc_prometheus.EnableHandlingTimeHistogram() + } + http.Handle("/metrics", promhttp.Handler()) go func() { http.ListenAndServe(cfg.Listen, nil) diff --git a/nat/traversal.go b/nat/traversal.go index 852a28cb60..9ae407320d 100644 --- a/nat/traversal.go +++ b/nat/traversal.go @@ -7,7 +7,7 @@ import ( var ( // private24BitBlock contains the set of private IPv4 addresses within - // the 10.0.0.0/8 adddress space. + // the 10.0.0.0/8 address space. private24BitBlock *net.IPNet // private20BitBlock contains the set of private IPv4 addresses within diff --git a/netann/chan_status_manager.go b/netann/chan_status_manager.go index 66d6d9682d..c1eedc7753 100644 --- a/netann/chan_status_manager.go +++ b/netann/chan_status_manager.go @@ -5,7 +5,7 @@ import ( "sync" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/keychain" @@ -21,9 +21,9 @@ var ( // ErrInvalidTimeoutConstraints signals that the ChanStatusManager could // not be initialized because the timeouts and sample intervals were // malformed. - ErrInvalidTimeoutConstraints = errors.New("active_timeout + " + - "sample_interval must be less than or equal to " + - "inactive_timeout and be positive integers") + ErrInvalidTimeoutConstraints = errors.New("chan-enable-timeout + " + + "chan-status-sample-interval must <= chan-disable-timeout " + + "and all three chan configs must be positive integers") // ErrEnableInactiveChan signals that a request to enable a channel // could not be completed because the channel isn't actually active at @@ -69,12 +69,12 @@ type ChanStatusConfig struct { Graph ChannelGraph // ChanEnableTimeout is the duration a peer's connect must remain stable - // before attempting to reenable the channel. + // before attempting to re-enable the channel. // // NOTE: This value is only used to verify that the relation between // itself, ChanDisableTimeout, and ChanStatusSampleInterval is correct. // The user is still responsible for ensuring that the same duration - // elapses before attempting to reenable a channel. + // elapses before attempting to re-enable a channel. ChanEnableTimeout time.Duration // ChanDisableTimeout is the duration the manager will wait after @@ -138,7 +138,7 @@ func NewChanStatusManager(cfg *ChanStatusConfig) (*ChanStatusManager, error) { // enable_timeout + sample_interval to be less than or equal to the // disable_timeout and that all are positive values. A peer that // disconnects and reconnects quickly may cause a disable update to be - // sent, shortly followed by a reenable. Ensuring a healthy separation + // sent, shortly followed by a re-enable. Ensuring a healthy separation // helps dampen the possibility of spamming updates that toggle the // disable bit for such events. if cfg.ChanStatusSampleInterval <= 0 { @@ -172,6 +172,7 @@ func NewChanStatusManager(cfg *ChanStatusConfig) (*ChanStatusManager, error) { func (m *ChanStatusManager) Start() error { var err error m.started.Do(func() { + log.Info("Channel Status Manager starting") err = m.start() }) return err @@ -492,7 +493,7 @@ func (m *ChanStatusManager) processAutoRequest(outpoint wire.OutPoint) error { // scheduled. Once an active channel is determined to be pending-inactive, one // of two transitions can follow. Either the channel is disabled because no // request to enable is received before the scheduled disable is broadcast, or -// the channel is successfully reenabled and channel is returned to an active +// the channel is successfully re-enabled and channel is returned to an active // state from the POV of the ChanStatusManager. func (m *ChanStatusManager) markPendingInactiveChannels() { channels, err := m.fetchChannels() diff --git a/netann/chan_status_manager_test.go b/netann/chan_status_manager_test.go index 3e9cd66625..19ada7ba63 100644 --- a/netann/chan_status_manager_test.go +++ b/netann/chan_status_manager_test.go @@ -11,7 +11,7 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/keychain" @@ -21,6 +21,11 @@ import ( var ( testKeyLoc = keychain.KeyLocator{Family: keychain.KeyFamilyNodeKey} + + // testSigBytes specifies a testing signature with the minimal length. + testSigBytes = []byte{ + 0x30, 0x06, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00, + } ) // randOutpoint creates a random wire.Outpoint. @@ -80,7 +85,7 @@ func createEdgePolicies(t *testing.T, channel *channeldb.OpenChannel, } // Generate and set pubkey2 for THEIR pubkey. - privKey2, err := btcec.NewPrivateKey(btcec.S256()) + privKey2, err := btcec.NewPrivateKey() if err != nil { t.Fatalf("unable to generate key pair: %v", err) } @@ -105,13 +110,13 @@ func createEdgePolicies(t *testing.T, channel *channeldb.OpenChannel, ChannelID: channel.ShortChanID().ToUint64(), ChannelFlags: dir1, LastUpdate: time.Now(), - SigBytes: make([]byte, 64), + SigBytes: testSigBytes, }, &channeldb.ChannelEdgePolicy{ ChannelID: channel.ShortChanID().ToUint64(), ChannelFlags: dir2, LastUpdate: time.Now(), - SigBytes: make([]byte, 64), + SigBytes: testSigBytes, } } @@ -209,7 +214,7 @@ func (g *mockGraph) ApplyChannelUpdate(update *lnwire.ChannelUpdate) error { ChannelID: update.ShortChannelID.ToUint64(), ChannelFlags: update.ChannelFlags, LastUpdate: timestamp, - SigBytes: make([]byte, 64), + SigBytes: testSigBytes, } if update1 { @@ -310,7 +315,7 @@ func newManagerCfg(t *testing.T, numChannels int, t.Helper() - privKey, err := btcec.NewPrivateKey(btcec.S256()) + privKey, err := btcec.NewPrivateKey() if err != nil { t.Fatalf("unable to generate key pair: %v", err) } @@ -651,7 +656,7 @@ var stateMachineTests = []stateMachineTest{ time.Sleep(100 * time.Millisecond) // Simulate reconnect by making channels active. h.markActive(h.graph.chans()) - // Request that all channels be reenabled. + // Request that all channels be re-enabled. h.assertEnables(h.graph.chans(), nil, false) // Pending disable should have been canceled, and // no updates sent. Channels remain enabled on the diff --git a/netann/channel_announcement_test.go b/netann/channel_announcement_test.go index 49e0789b98..663de5c857 100644 --- a/netann/channel_announcement_test.go +++ b/netann/channel_announcement_test.go @@ -4,9 +4,9 @@ import ( "bytes" "testing" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/lnwire" "github.com/stretchr/testify/assert" diff --git a/netann/channel_state.go b/netann/channel_state.go index 123a586b85..904196331a 100644 --- a/netann/channel_state.go +++ b/netann/channel_state.go @@ -20,7 +20,7 @@ const ( // detected in an inactive state. Channels in this state will have a // disabling announcement sent after the ChanInactiveTimeout expires // from the time of the first detection--unless the channel is - // explicitly reenabled before the disabling occurs. + // explicitly re-enabled before the disabling occurs. ChanStatusPendingDisabled // ChanStatusDisabled indicates that the channel's last announcement has diff --git a/netann/channel_update.go b/netann/channel_update.go index 0fec750f9d..ca26acac67 100644 --- a/netann/channel_update.go +++ b/netann/channel_update.go @@ -5,7 +5,7 @@ import ( "fmt" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwallet" diff --git a/netann/channel_update_test.go b/netann/channel_update_test.go index e28f381841..4d4a6d72b1 100644 --- a/netann/channel_update_test.go +++ b/netann/channel_update_test.go @@ -5,7 +5,8 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" @@ -18,7 +19,7 @@ type mockSigner struct { } func (m *mockSigner) SignMessage(_ keychain.KeyLocator, - _ []byte, _ bool) (*btcec.Signature, error) { + _ []byte, _ bool) (*ecdsa.Signature, error) { if m.err != nil { return nil, m.err @@ -30,7 +31,7 @@ func (m *mockSigner) SignMessage(_ keychain.KeyLocator, var _ lnwallet.MessageSigner = (*mockSigner)(nil) var ( - privKey, _ = btcec.NewPrivateKey(btcec.S256()) + privKey, _ = btcec.NewPrivateKey() privKeySigner = keychain.NewPrivKeyMessageSigner(privKey, testKeyLoc) pubKey = privKey.PubKey() diff --git a/netann/host_ann.go b/netann/host_ann.go index 3e87c4b0fe..bc50289c12 100644 --- a/netann/host_ann.go +++ b/netann/host_ann.go @@ -58,6 +58,7 @@ func NewHostAnnouncer(cfg HostAnnouncerConfig) *HostAnnouncer { // Start starts the HostAnnouncer. func (h *HostAnnouncer) Start() error { h.startOnce.Do(func() { + log.Info("HostAnnouncer starting") h.wg.Add(1) go h.hostWatcher() }) @@ -83,7 +84,6 @@ func (h *HostAnnouncer) hostWatcher() { ipMapping := make(map[string]net.Addr) refreshHosts := func() { - // We'll now run through each of our hosts to check if they had // their backing IPs changed. If so, we'll want to re-announce // them. diff --git a/netann/host_ann_test.go b/netann/host_ann_test.go index 4f6573f5a8..c8bbcac259 100644 --- a/netann/host_ann_test.go +++ b/netann/host_ann_test.go @@ -236,7 +236,6 @@ func TestHostAnnouncerUpdates(t *testing.T) { // If we expect an update, then we'll assert that we received // the proper set of modified addresses. if testCase.updateTriggered { - select { // The receive update should match exactly what the // test case dictates. diff --git a/netann/node_announcement.go b/netann/node_announcement.go index d73b6692f2..42296bf222 100644 --- a/netann/node_announcement.go +++ b/netann/node_announcement.go @@ -1,6 +1,7 @@ package netann import ( + "image/color" "net" "time" @@ -13,6 +14,14 @@ import ( // lnwire.NodeAnnouncement. type NodeAnnModifier func(*lnwire.NodeAnnouncement) +// NodeAnnSetAlias is a functional option that sets the alias of the +// given node announcement. +func NodeAnnSetAlias(alias lnwire.NodeAlias) func(*lnwire.NodeAnnouncement) { + return func(nodeAnn *lnwire.NodeAnnouncement) { + nodeAnn.Alias = alias + } +} + // NodeAnnSetAddrs is a functional option that allows updating the addresses of // the given node announcement. func NodeAnnSetAddrs(addrs []net.Addr) func(*lnwire.NodeAnnouncement) { @@ -21,6 +30,22 @@ func NodeAnnSetAddrs(addrs []net.Addr) func(*lnwire.NodeAnnouncement) { } } +// NodeAnnSetColor is a functional option that sets the color of the +// given node announcement. +func NodeAnnSetColor(newColor color.RGBA) func(*lnwire.NodeAnnouncement) { + return func(nodeAnn *lnwire.NodeAnnouncement) { + nodeAnn.RGBColor = newColor + } +} + +// NodeAnnSetFeatures is a functional option that allows updating the features of +// the given node announcement. +func NodeAnnSetFeatures(features *lnwire.RawFeatureVector) func(*lnwire.NodeAnnouncement) { + return func(nodeAnn *lnwire.NodeAnnouncement) { + nodeAnn.Features = features + } +} + // NodeAnnSetTimestamp is a functional option that sets the timestamp of the // announcement to the current time, or increments it if the timestamp is // already in the future. diff --git a/netann/node_signer.go b/netann/node_signer.go index d357282693..e0b439b14d 100644 --- a/netann/node_signer.go +++ b/netann/node_signer.go @@ -3,7 +3,7 @@ package netann import ( "fmt" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwallet" ) @@ -26,7 +26,7 @@ func NewNodeSigner(keySigner keychain.SingleKeyMessageSigner) *NodeSigner { // resident node's private key described in the key locator. If the target key // locator is _not_ the node's private key, then an error will be returned. func (n *NodeSigner) SignMessage(keyLoc keychain.KeyLocator, - msg []byte, doubleHash bool) (*btcec.Signature, error) { + msg []byte, doubleHash bool) (*ecdsa.Signature, error) { // If this isn't our identity public key, then we'll exit early with an // error as we can't sign with this key. diff --git a/peer/brontide.go b/peer/brontide.go index b0450de19d..dd0576fba1 100644 --- a/peer/brontide.go +++ b/peer/brontide.go @@ -10,13 +10,12 @@ import ( "sync/atomic" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/connmgr" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/davecgh/go-spew/spew" - "github.com/lightningnetwork/lnd/buffer" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" @@ -61,11 +60,6 @@ const ( // message. handshakeTimeout = 15 * time.Second - // outgoingQueueLen is the buffer size of the channel which houses - // messages to be sent across the wire, requested by objects outside - // this struct. - outgoingQueueLen = 50 - // ErrorBufferSize is the number of historic peer errors that we store. ErrorBufferSize = 10 ) @@ -319,6 +313,13 @@ type Config struct { // operations at the cost of latency. ChannelCommitInterval time.Duration + // PendingCommitInterval is the maximum time that is allowed to pass + // while waiting for the remote party to revoke a locally initiated + // commitment state. Setting this to a longer duration if a slow + // response is expected from the remote party or large number of + // payments are attempted at the same time. + PendingCommitInterval time.Duration + // ChannelCommitBatchSize is the maximum number of channel state updates // that is accumulated before signing a new commitment. ChannelCommitBatchSize uint32 @@ -327,6 +328,12 @@ type Config struct { // from the peer. HandleCustomMessage func(peer [33]byte, msg *lnwire.Custom) error + // PongBuf is a slice we'll reuse instead of allocating memory on the + // heap. Since only reads will occur and no writes, there is no need + // for any synchronization primitives. As a result, it's safe to share + // this across multiple Peer struct instances. + PongBuf []byte + // Quit is the server's quit channel. If this is closed, we halt operation. Quit chan struct{} } @@ -336,7 +343,7 @@ type Config struct { // several helper goroutines to handle events such as HTLC timeouts, new // funding workflow, and detecting an uncooperative closure of any active // channels. -// TODO(roasbeef): proper reconnection logic +// TODO(roasbeef): proper reconnection logic. type Brontide struct { // MUST be used atomically. started int32 @@ -479,7 +486,8 @@ func (p *Brontide) Start() error { return nil } - peerLog.Tracef("Peer %v starting", p) + peerLog.Tracef("Peer %v starting with conn[%v->%v]", p, + p.cfg.Conn.LocalAddr(), p.cfg.Conn.RemoteAddr()) // Fetch and then load all the active channels we have with this remote // peer from the database. @@ -611,7 +619,6 @@ func (p *Brontide) Start() error { // initGossipSync initializes either a gossip syncer or an initial routing // dump, depending on the negotiated synchronization method. func (p *Brontide) initGossipSync() { - // If the remote peer knows of the new gossip queries feature, then // we'll create a new gossipSyncer in the AuthenticatedGossiper for it. if p.remoteFeatures.HasFeature(lnwire.GossipQueriesOptional) { @@ -690,6 +697,31 @@ func (p *Brontide) loadActiveChannels(chans []*channeldb.OpenChannel) ( } msgs = append(msgs, chanSync) + + // Check if this channel needs to have the cooperative + // close process restarted. If so, we'll need to send + // the Shutdown message that is returned. + if dbChan.HasChanStatus( + channeldb.ChanStatusCoopBroadcasted, + ) { + + shutdownMsg, err := p.restartCoopClose(lnChan) + if err != nil { + peerLog.Errorf("Unable to restart "+ + "coop close for channel: %v", + err) + continue + } + + if shutdownMsg == nil { + continue + } + + // Append the message to the set of messages to + // send. + msgs = append(msgs, shutdownMsg) + } + continue } @@ -810,6 +842,10 @@ func (p *Brontide) addLink(chanPoint *wire.OutPoint, return p.cfg.ChainArb.UpdateContractSignals(*chanPoint, signals) } + notifyContractUpdate := func(update *contractcourt.ContractUpdate) error { + return p.cfg.ChainArb.NotifyContractUpdate(*chanPoint, update) + } + chanType := lnChan.State().ChanType // Select the appropriate tower client based on the channel type. It's @@ -823,25 +859,28 @@ func (p *Brontide) addLink(chanPoint *wire.OutPoint, } linkCfg := htlcswitch.ChannelLinkConfig{ - Peer: p, - DecodeHopIterators: p.cfg.Sphinx.DecodeHopIterators, - ExtractErrorEncrypter: p.cfg.Sphinx.ExtractErrorEncrypter, - FetchLastChannelUpdate: p.cfg.FetchLastChanUpdate, - HodlMask: p.cfg.Hodl.Mask(), - Registry: p.cfg.Invoices, - BestHeight: p.cfg.Switch.BestHeight, - Circuits: p.cfg.Switch.CircuitModifier(), - ForwardPackets: p.cfg.InterceptSwitch.ForwardPackets, - FwrdingPolicy: *forwardingPolicy, - FeeEstimator: p.cfg.FeeEstimator, - PreimageCache: p.cfg.WitnessBeacon, - ChainEvents: chainEvents, - UpdateContractSignals: updateContractSignals, - OnChannelFailure: onChannelFailure, - SyncStates: syncStates, - BatchTicker: ticker.New(p.cfg.ChannelCommitInterval), - FwdPkgGCTicker: ticker.New(time.Hour), - PendingCommitTicker: ticker.New(time.Minute), + Peer: p, + DecodeHopIterators: p.cfg.Sphinx.DecodeHopIterators, + ExtractErrorEncrypter: p.cfg.Sphinx.ExtractErrorEncrypter, + FetchLastChannelUpdate: p.cfg.FetchLastChanUpdate, + HodlMask: p.cfg.Hodl.Mask(), + Registry: p.cfg.Invoices, + BestHeight: p.cfg.Switch.BestHeight, + Circuits: p.cfg.Switch.CircuitModifier(), + ForwardPackets: p.cfg.InterceptSwitch.ForwardPackets, + FwrdingPolicy: *forwardingPolicy, + FeeEstimator: p.cfg.FeeEstimator, + PreimageCache: p.cfg.WitnessBeacon, + ChainEvents: chainEvents, + UpdateContractSignals: updateContractSignals, + NotifyContractUpdate: notifyContractUpdate, + OnChannelFailure: onChannelFailure, + SyncStates: syncStates, + BatchTicker: ticker.New(p.cfg.ChannelCommitInterval), + FwdPkgGCTicker: ticker.New(time.Hour), + PendingCommitTicker: ticker.New( + p.cfg.PendingCommitInterval, + ), BatchSize: p.cfg.ChannelCommitBatchSize, UnsafeReplay: p.cfg.UnsafeReplay, MinFeeUpdateTimeout: htlcswitch.DefaultMinLinkFeeUpdateTimeout, @@ -1012,7 +1051,7 @@ func (p *Brontide) readNextMessage() (lnwire.Message, error) { // delivered via closure to a receiver. These messages MUST be in order due to // the nature of the lightning channel commitment and gossiper state machines. // TODO(conner): use stream handler interface to abstract out stream -// state/logging +// state/logging. type msgStream struct { streamShutdown int32 // To be used atomically. @@ -1144,7 +1183,7 @@ func (ms *msgStream) msgConsumer() { // concurrent access. func (ms *msgStream) AddMsg(msg lnwire.Message) { // First, we'll attempt to receive from the producerSema struct. This - // acts as a sempahore to prevent us from indefinitely buffering + // acts as a semaphore to prevent us from indefinitely buffering // incoming items from the wire. Either the msg queue isn't full, and // we'll not block, or the queue is full, and we'll block until either // we're signalled to quit, or a slot is freed up. @@ -1238,7 +1277,6 @@ func waitUntilLinkActive(p *Brontide, // channel this stream forwards to is held in scope to prevent unnecessary // lookups. func newChanMsgStream(p *Brontide, cid lnwire.ChannelID) *msgStream { - var chanLink htlcswitch.ChannelUpdateHandler apply := func(msg lnwire.Message) { @@ -1393,10 +1431,8 @@ out: // Next, we'll send over the amount of specified pong // bytes. - // - // TODO(roasbeef): read out from pong scratch instead? - pongBytes := make([]byte, msg.NumPongBytes) - p.queueMsg(lnwire.NewPong(pongBytes), nil) + pong := lnwire.NewPong(p.cfg.PongBuf[0:msg.NumPongBytes]) + p.queueMsg(pong, nil) case *lnwire.OpenChannel, *lnwire.AcceptChannel, @@ -1564,7 +1600,6 @@ func (p *Brontide) handleError(msg *lnwire.Error) bool { p.storeError(msg) switch { - // In the case of an all-zero channel ID we want to forward the error to // all channels with this peer. case msg.ChanID == lnwire.ConnectionWideID: @@ -1754,31 +1789,6 @@ func (p *Brontide) logWireMessage(msg lnwire.Message, read bool) { msgType, summary, preposition, p) })) - switch m := msg.(type) { - case *lnwire.ChannelReestablish: - if m.LocalUnrevokedCommitPoint != nil { - m.LocalUnrevokedCommitPoint.Curve = nil - } - case *lnwire.RevokeAndAck: - m.NextRevocationKey.Curve = nil - case *lnwire.AcceptChannel: - m.FundingKey.Curve = nil - m.RevocationPoint.Curve = nil - m.PaymentPoint.Curve = nil - m.DelayedPaymentPoint.Curve = nil - m.HtlcPoint.Curve = nil - m.FirstCommitmentPoint.Curve = nil - case *lnwire.OpenChannel: - m.FundingKey.Curve = nil - m.RevocationPoint.Curve = nil - m.PaymentPoint.Curve = nil - m.DelayedPaymentPoint.Curve = nil - m.HtlcPoint.Curve = nil - m.FirstCommitmentPoint.Curve = nil - case *lnwire.FundingLocked: - m.NextPerCommitmentPoint.Curve = nil - } - prefix := "readMessage from" if !read { prefix = "writeMessage to" @@ -2043,6 +2053,7 @@ func (p *Brontide) pingHandler() { "subscription: %v", err) return } + defer blockEpochs.Cancel() var ( pingPayload [wire.MaxBlockHeaderPayload]byte @@ -2136,7 +2147,7 @@ func (p *Brontide) ChannelSnapshots() []*channeldb.ChannelSnapshot { } // We'll only return a snapshot for channels that are - // *immedately* available for routing payments over. + // *immediately* available for routing payments over. if activeChan.RemoteNextRevocation() == nil { continue } @@ -2417,6 +2428,15 @@ func (p *Brontide) reenableActiveChannels() { func (p *Brontide) fetchActiveChanCloser(chanID lnwire.ChannelID) ( *chancloser.ChanCloser, error) { + chanCloser, found := p.activeChanCloses[chanID] + if found { + // An entry will only be found if the closer has already been + // created for a non-pending channel or for a channel that had + // previously started the shutdown process but the connection + // was restarted. + return chanCloser, nil + } + // First, we'll ensure that we actually know of the target channel. If // not, we'll ignore this message. p.activeChanMtx.RLock() @@ -2429,74 +2449,51 @@ func (p *Brontide) fetchActiveChanCloser(chanID lnwire.ChannelID) ( return nil, ErrChannelNotFound } - // We'll attempt to look up the matching state machine, if we can't - // find one then this means that the remote party is initiating a - // cooperative channel closure. - chanCloser, ok := p.activeChanCloses[chanID] - if !ok { - // Optimistically try a link shutdown, erroring out if it - // failed. - if err := p.tryLinkShutdown(chanID); err != nil { - peerLog.Errorf("failed link shutdown: %v", err) - return nil, err - } - - // We'll create a valid closing state machine in order to - // respond to the initiated cooperative channel closure. First, - // we set the delivery script that our funds will be paid out - // to. If an upfront shutdown script was set, we will use it. - // Otherwise, we get a fresh delivery script. - // - // TODO: Expose option to allow upfront shutdown script from - // watch-only accounts. - deliveryScript := channel.LocalUpfrontShutdownScript() - if len(deliveryScript) == 0 { - var err error - deliveryScript, err = p.genDeliveryScript() - if err != nil { - peerLog.Errorf("unable to gen delivery script: %v", err) - return nil, fmt.Errorf("close addr unavailable") - } - } + // Optimistically try a link shutdown, erroring out if it failed. + if err := p.tryLinkShutdown(chanID); err != nil { + peerLog.Errorf("failed link shutdown: %v", err) + return nil, err + } - // In order to begin fee negotiations, we'll first compute our - // target ideal fee-per-kw. - feePerKw, err := p.cfg.FeeEstimator.EstimateFeePerKW( - p.cfg.CoopCloseTargetConfs, - ) + // We'll create a valid closing state machine in order to respond to + // the initiated cooperative channel closure. First, we set the + // delivery script that our funds will be paid out to. If an upfront + // shutdown script was set, we will use it. Otherwise, we get a fresh + // delivery script. + // + // TODO: Expose option to allow upfront shutdown script from watch-only + // accounts. + deliveryScript := channel.LocalUpfrontShutdownScript() + if len(deliveryScript) == 0 { + var err error + deliveryScript, err = p.genDeliveryScript() if err != nil { - peerLog.Errorf("unable to query fee estimator: %v", err) - - return nil, fmt.Errorf("unable to estimate fee") + peerLog.Errorf("unable to gen delivery script: %v", + err) + return nil, fmt.Errorf("close addr unavailable") } + } - _, startingHeight, err := p.cfg.ChainIO.GetBestBlock() - if err != nil { - peerLog.Errorf("unable to obtain best block: %v", err) - return nil, fmt.Errorf("cannot obtain best block") - } - - chanCloser = chancloser.NewChanCloser( - chancloser.ChanCloseCfg{ - Channel: channel, - BroadcastTx: p.cfg.Wallet.PublishTransaction, - DisableChannel: func(chanPoint wire.OutPoint) error { - return p.cfg.ChanStatusMgr.RequestDisable(chanPoint, false) - }, - Disconnect: func() error { - return p.cfg.DisconnectPeer(p.IdentityKey()) - }, - Quit: p.quit, - }, - deliveryScript, - feePerKw, - uint32(startingHeight), - nil, - false, - ) - p.activeChanCloses[chanID] = chanCloser + // In order to begin fee negotiations, we'll first compute our target + // ideal fee-per-kw. + feePerKw, err := p.cfg.FeeEstimator.EstimateFeePerKW( + p.cfg.CoopCloseTargetConfs, + ) + if err != nil { + peerLog.Errorf("unable to query fee estimator: %v", err) + return nil, fmt.Errorf("unable to estimate fee") } + chanCloser, err = p.createChanCloser( + channel, deliveryScript, feePerKw, nil, false, + ) + if err != nil { + peerLog.Errorf("unable to create chan closer: %v", err) + return nil, fmt.Errorf("unable to create chan closer") + } + + p.activeChanCloses[chanID] = chanCloser + return chanCloser, nil } @@ -2531,6 +2528,118 @@ func chooseDeliveryScript(upfront, return upfront, nil } +// restartCoopClose checks whether we need to restart the cooperative close +// process for a given channel. +func (p *Brontide) restartCoopClose(lnChan *lnwallet.LightningChannel) ( + *lnwire.Shutdown, error) { + + // If this channel has status ChanStatusCoopBroadcasted and does not + // have a closing transaction, then the cooperative close process was + // started but never finished. We'll re-create the chanCloser state + // machine and resend Shutdown. BOLT#2 requires that we retransmit + // Shutdown exactly, but doing so would mean persisting the RPC + // provided close script. Instead use the LocalUpfrontShutdownScript + // or generate a script. + c := lnChan.State() + _, err := c.BroadcastedCooperative() + if err != nil && err != channeldb.ErrNoCloseTx { + // An error other than ErrNoCloseTx was encountered. + return nil, err + } else if err == nil { + // This channel has already completed the coop close + // negotiation. + return nil, nil + } + + // As mentioned above, we don't re-create the delivery script. + deliveryScript := c.LocalShutdownScript + if len(deliveryScript) == 0 { + var err error + deliveryScript, err = p.genDeliveryScript() + if err != nil { + peerLog.Errorf("unable to gen delivery script: %v", + err) + return nil, fmt.Errorf("close addr unavailable") + } + } + + // Compute an ideal fee. + feePerKw, err := p.cfg.FeeEstimator.EstimateFeePerKW( + p.cfg.CoopCloseTargetConfs, + ) + if err != nil { + peerLog.Errorf("unable to query fee estimator: %v", err) + return nil, fmt.Errorf("unable to estimate fee") + } + + // Determine whether we or the peer are the initiator of the coop + // close attempt by looking at the channel's status. + locallyInitiated := c.HasChanStatus( + channeldb.ChanStatusLocalCloseInitiator, + ) + + chanCloser, err := p.createChanCloser( + lnChan, deliveryScript, feePerKw, nil, locallyInitiated, + ) + if err != nil { + peerLog.Errorf("unable to create chan closer: %v", err) + return nil, fmt.Errorf("unable to create chan closer") + } + + // This does not need a mutex even though it is in a different + // goroutine since this is done before the channelManager goroutine is + // created. + chanID := lnwire.NewChanIDFromOutPoint(&c.FundingOutpoint) + p.activeChanCloses[chanID] = chanCloser + + // Create the Shutdown message. + shutdownMsg, err := chanCloser.ShutdownChan() + if err != nil { + peerLog.Errorf("unable to create shutdown message: %v", err) + delete(p.activeChanCloses, chanID) + return nil, err + } + + return shutdownMsg, nil +} + +// createChanCloser constructs a ChanCloser from the passed parameters and is +// used to de-duplicate code. +func (p *Brontide) createChanCloser(channel *lnwallet.LightningChannel, + deliveryScript lnwire.DeliveryAddress, fee chainfee.SatPerKWeight, + req *htlcswitch.ChanClose, + locallyInitiated bool) (*chancloser.ChanCloser, error) { + + _, startingHeight, err := p.cfg.ChainIO.GetBestBlock() + if err != nil { + peerLog.Errorf("unable to obtain best block: %v", err) + return nil, fmt.Errorf("cannot obtain best block") + } + + chanCloser := chancloser.NewChanCloser( + chancloser.ChanCloseCfg{ + Channel: channel, + BroadcastTx: p.cfg.Wallet.PublishTransaction, + DisableChannel: func(op wire.OutPoint) error { + return p.cfg.ChanStatusMgr.RequestDisable( + op, false, + ) + }, + Disconnect: func() error { + return p.cfg.DisconnectPeer(p.IdentityKey()) + }, + Quit: p.quit, + }, + deliveryScript, + fee, + uint32(startingHeight), + req, + locallyInitiated, + ) + + return chanCloser, nil +} + // handleLocalCloseReq kicks-off the workflow to execute a cooperative or // forced unilateral closure of the channel initiated by a local subsystem. func (p *Brontide) handleLocalCloseReq(req *htlcswitch.ChanClose) { @@ -2551,7 +2660,6 @@ func (p *Brontide) handleLocalCloseReq(req *htlcswitch.ChanClose) { } switch req.CloseType { - // A type of CloseRegular indicates that the user has opted to close // out this channel on-chain, so we execute the cooperative channel // closure workflow. @@ -2584,15 +2692,6 @@ func (p *Brontide) handleLocalCloseReq(req *htlcswitch.ChanClose) { } } - // Next, we'll create a new channel closer state machine to - // handle the close negotiation. - _, startingHeight, err := p.cfg.ChainIO.GetBestBlock() - if err != nil { - peerLog.Errorf(err.Error()) - req.Err <- err - return - } - // Optimistically try a link shutdown, erroring out if it // failed. if err := p.tryLinkShutdown(chanID); err != nil { @@ -2601,24 +2700,15 @@ func (p *Brontide) handleLocalCloseReq(req *htlcswitch.ChanClose) { return } - chanCloser := chancloser.NewChanCloser( - chancloser.ChanCloseCfg{ - Channel: channel, - BroadcastTx: p.cfg.Wallet.PublishTransaction, - DisableChannel: func(chanPoint wire.OutPoint) error { - return p.cfg.ChanStatusMgr.RequestDisable(chanPoint, false) - }, - Disconnect: func() error { - return p.cfg.DisconnectPeer(p.IdentityKey()) - }, - Quit: p.quit, - }, - deliveryScript, - req.TargetFeePerKw, - uint32(startingHeight), - req, - true, + chanCloser, err := p.createChanCloser( + channel, deliveryScript, req.TargetFeePerKw, req, true, ) + if err != nil { + peerLog.Errorf(err.Error()) + req.Err <- err + return + } + p.activeChanCloses[chanID] = chanCloser // Finally, we'll initiate the channel shutdown within the @@ -2792,6 +2882,10 @@ func (p *Brontide) finalizeChanClosure(chanCloser *chancloser.ChanCloser) { chanPoint := chanCloser.Channel().ChannelPoint() p.WipeChannel(chanPoint) + // Also clear the activeChanCloses map of this channel. + cid := lnwire.NewChanIDFromOutPoint(chanPoint) + delete(p.activeChanCloses, cid) + // Next, we'll launch a goroutine which will request to be notified by // the ChainNotifier once the closure transaction obtains a single // confirmation. @@ -2825,7 +2919,6 @@ func (p *Brontide) finalizeChanClosure(chanCloser *chancloser.ChanCloser) { go WaitForChanToClose(chanCloser.NegotiationHeight(), notifier, errChan, chanPoint, &closingTxid, closingTx.TxOut[0].PkScript, func() { - // Respond to the local subsystem which requested the // channel closure. if closeReq != nil { @@ -3008,7 +3101,7 @@ func (p *Brontide) resendChanSyncMsg(cid lnwire.ChannelID) error { if !c.RemotePub.IsEqual(p.IdentityKey()) { return fmt.Errorf("ignoring channel reestablish from "+ - "peer=%x", p.IdentityKey()) + "peer=%x", p.IdentityKey().SerializeCompressed()) } peerLog.Debugf("Re-sending channel sync message for channel %v to "+ diff --git a/peer/brontide_test.go b/peer/brontide_test.go index 825a767f08..43fb2b782e 100644 --- a/peer/brontide_test.go +++ b/peer/brontide_test.go @@ -6,11 +6,11 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/contractcourt" @@ -876,7 +876,7 @@ func TestCustomShutdownScript(t *testing.T) { // If the test has not specified an expected address, do not check // whether the shutdown address matches. This covers the case where - // we epect shutdown to a random address and cannot match it. + // we expect shutdown to a random address and cannot match it. if len(test.expectedScript) == 0 { return } @@ -999,6 +999,7 @@ func TestStaticRemoteDowngrade(t *testing.T) { Features: test.features, Conn: mockConn, WritePool: writePool, + PongBuf: make([]byte, lnwire.MaxPongBytes), }, } @@ -1046,7 +1047,7 @@ func TestPeerCustomMessage(t *testing.T) { dbAlice, err := channeldb.Open(alicePath) require.NoError(t, err) - aliceKey, err := btcec.NewPrivateKey(btcec.S256()) + aliceKey, err := btcec.NewPrivateKey() require.NoError(t, err) writeBufferPool := pool.NewWriteBuffer( @@ -1103,6 +1104,7 @@ func TestPeerCustomMessage(t *testing.T) { } return nil }, + PongBuf: make([]byte, lnwire.MaxPongBytes), }) // Set up the init sequence. diff --git a/peer/test_utils.go b/peer/test_utils.go index a29cb7e53e..1a9e6f61e3 100644 --- a/peer/test_utils.go +++ b/peer/test_utils.go @@ -12,10 +12,10 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/htlcswitch" @@ -38,6 +38,10 @@ const ( // timeout is a timeout value to use for tests which need to wait for // a return value on a channel. timeout = time.Second * 5 + + // testCltvRejectDelta is the minimum delta between expiry and current + // height below which htlcs are rejected. + testCltvRejectDelta = 13 ) var ( @@ -64,13 +68,13 @@ func createTestPeer(notifier chainntnfs.ChainNotifier, Family: keychain.KeyFamilyNodeKey, } aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes( - btcec.S256(), channels.AlicesPrivKey, + channels.AlicesPrivKey, ) aliceKeySigner := keychain.NewPrivKeyMessageSigner( aliceKeyPriv, nodeKeyLocator, ) bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes( - btcec.S256(), channels.BobsPrivKey, + channels.BobsPrivKey, ) channelCapacity := btcutil.Amount(10 * 1e8) @@ -367,7 +371,9 @@ func createTestPeer(notifier chainntnfs.ChainNotifier, Switch: mockSwitch, ChanActiveTimeout: chanActiveTimeout, - InterceptSwitch: htlcswitch.NewInterceptableSwitch(nil), + InterceptSwitch: htlcswitch.NewInterceptableSwitch( + nil, testCltvRejectDelta, false, + ), ChannelDB: dbAlice.ChannelStateDB(), FeeEstimator: estimator, @@ -521,3 +527,11 @@ func (m *mockMessageConn) ReadNextHeader() (uint32, error) { func (m *mockMessageConn) ReadNextBody(buf []byte) ([]byte, error) { return m.curReadMessage, nil } + +func (m *mockMessageConn) RemoteAddr() net.Addr { + return nil +} + +func (m *mockMessageConn) LocalAddr() net.Addr { + return nil +} diff --git a/pilot.go b/pilot.go index 55bd266708..b0bb6d9f80 100644 --- a/pilot.go +++ b/pilot.go @@ -5,9 +5,9 @@ import ( "fmt" "net" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/autopilot" "github.com/lightningnetwork/lnd/chainreg" "github.com/lightningnetwork/lnd/funding" diff --git a/pool/worker.go b/pool/worker.go index 9fc2f84e09..6aed815cce 100644 --- a/pool/worker.go +++ b/pool/worker.go @@ -28,7 +28,7 @@ type ( Cleanup() } - // WorkerConfig parameterizes the behavior of a Worker pool. + // WorkerConfig parametrizes the behavior of a Worker pool. WorkerConfig struct { // NewWorkerState allocates a new state for a worker goroutine. // This method is called each time a new worker goroutine is @@ -60,10 +60,10 @@ type ( requests chan *request // work is a channel where new tasks are submitted, but is only - // read by active worker gorotuines. + // read by active worker goroutines. work chan *request - // workerSem is a channel-based sempahore that is used to limit + // workerSem is a channel-based semaphore that is used to limit // the total number of worker goroutines to the number // prescribed by the WorkerConfig. workerSem chan struct{} diff --git a/queue/gc_queue.go b/queue/gc_queue.go index 965e841dd3..04972b13b0 100644 --- a/queue/gc_queue.go +++ b/queue/gc_queue.go @@ -158,10 +158,10 @@ func (q *GCQueue) queueManager() { // recycle ticker to cleanup any entries that go unused. q.recycleTicker.Resume() - // If the recycle ticker fires, we will aggresively release any + // If the recycle ticker fires, we will aggressively release any // write buffers in the freelist for which the expiryInterval // has elapsed since their insertion. If after doing so, no - // elements remain, we will pause the recylce ticker. + // elements remain, we will pause the recycle ticker. case <-q.recycleTicker.Ticks(): // Since the insert time of all entries will be // monotonically increasing, iterate over elements and diff --git a/record/amp.go b/record/amp.go index 6deb404266..eb431fec3f 100644 --- a/record/amp.go +++ b/record/amp.go @@ -66,8 +66,8 @@ const ( // bytes, leaving only the root_share and set_id. minAMPLength = 64 - // maxAMPLength is the maximum legnth of a serialized AMP TLV record, - // which occurs when the truncated endoing of a child_index takes 2 + // maxAMPLength is the maximum length of a serialized AMP TLV record, + // which occurs when the truncated encoding of a child_index takes 2 // bytes. maxAMPLength = 68 ) @@ -100,7 +100,7 @@ func (a *AMP) PayloadSize() uint64 { return 32 + 32 + tlv.SizeTUint32(a.childIndex) } -// String returns a human-readble description of the amp payload fields. +// String returns a human-readable description of the amp payload fields. func (a *AMP) String() string { if a == nil { return "" diff --git a/record/hop.go b/record/hop.go index 3f515e6b0d..0611df0437 100644 --- a/record/hop.go +++ b/record/hop.go @@ -5,17 +5,21 @@ import ( ) const ( - // AmtOnionType is the type used in the onion to refrence the amount to + // AmtOnionType is the type used in the onion to reference the amount to // send to the next hop. AmtOnionType tlv.Type = 2 - // LockTimeTLV is the type used in the onion to refenernce the CLTV + // LockTimeTLV is the type used in the onion to reference the CLTV // value that should be used for the next hop's HTLC. LockTimeOnionType tlv.Type = 4 // NextHopOnionType is the type used in the onion to reference the ID // of the next hop. NextHopOnionType tlv.Type = 6 + + // MetadataOnionType is the type used in the onion for the payment + // metadata. + MetadataOnionType tlv.Type = 16 ) // NewAmtToFwdRecord creates a tlv.Record that encodes the amount_to_forward @@ -45,3 +49,15 @@ func NewLockTimeRecord(lockTime *uint32) tlv.Record { func NewNextHopIDRecord(cid *uint64) tlv.Record { return tlv.MakePrimitiveRecord(NextHopOnionType, cid) } + +// NewMetadataRecord creates a tlv.Record that encodes the metadata (type 10) +// for an onion payload. +func NewMetadataRecord(metadata *[]byte) tlv.Record { + return tlv.MakeDynamicRecord( + MetadataOnionType, metadata, + func() uint64 { + return uint64(len(*metadata)) + }, + tlv.EVarBytes, tlv.DVarBytes, + ) +} diff --git a/record/mpp.go b/record/mpp.go index db6c952119..a5457e5029 100644 --- a/record/mpp.go +++ b/record/mpp.go @@ -80,7 +80,6 @@ func MPPDecoder(r io.Reader, val interface{}, buf *[8]byte, l uint64) error { v.totalMsat = lnwire.MilliSatoshi(total) return nil - } return tlv.NewTypeForDecodingErr(val, "MPP", l, maxMPPLength) } diff --git a/routing/ann_validation.go b/routing/ann_validation.go index cc8530bb16..6a84aa6102 100644 --- a/routing/ann_validation.go +++ b/routing/ann_validation.go @@ -4,9 +4,9 @@ import ( "bytes" "fmt" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcutil" "github.com/davecgh/go-spew/spew" "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/lnwire" @@ -32,7 +32,7 @@ func ValidateChannelAnn(a *lnwire.ChannelAnnouncement) error { if err != nil { return err } - bitcoinKey1, err := btcec.ParsePubKey(a.BitcoinKey1[:], btcec.S256()) + bitcoinKey1, err := btcec.ParsePubKey(a.BitcoinKey1[:]) if err != nil { return err } @@ -47,7 +47,7 @@ func ValidateChannelAnn(a *lnwire.ChannelAnnouncement) error { if err != nil { return err } - bitcoinKey2, err := btcec.ParsePubKey(a.BitcoinKey2[:], btcec.S256()) + bitcoinKey2, err := btcec.ParsePubKey(a.BitcoinKey2[:]) if err != nil { return err } @@ -61,7 +61,7 @@ func ValidateChannelAnn(a *lnwire.ChannelAnnouncement) error { if err != nil { return err } - nodeKey1, err := btcec.ParsePubKey(a.NodeID1[:], btcec.S256()) + nodeKey1, err := btcec.ParsePubKey(a.NodeID1[:]) if err != nil { return err } @@ -73,7 +73,7 @@ func ValidateChannelAnn(a *lnwire.ChannelAnnouncement) error { if err != nil { return err } - nodeKey2, err := btcec.ParsePubKey(a.NodeID2[:], btcec.S256()) + nodeKey2, err := btcec.ParsePubKey(a.NodeID2[:]) if err != nil { return err } @@ -100,7 +100,7 @@ func ValidateNodeAnn(a *lnwire.NodeAnnouncement) error { if err != nil { return err } - nodeKey, err := btcec.ParsePubKey(a.NodeID[:], btcec.S256()) + nodeKey, err := btcec.ParsePubKey(a.NodeID[:]) if err != nil { return err } diff --git a/routing/bandwidth.go b/routing/bandwidth.go index e6084649b5..8abf025aba 100644 --- a/routing/bandwidth.go +++ b/routing/bandwidth.go @@ -72,6 +72,7 @@ func newBandwidthManager(graph routingGraph, sourceNode route.Vertex, // is interpreted as the link being offline. func (b *bandwidthManager) getBandwidth(cid lnwire.ShortChannelID, amount lnwire.MilliSatoshi) lnwire.MilliSatoshi { + link, err := b.getLink(cid) if err != nil { // If the link isn't online, then we'll report that it has diff --git a/routing/bandwidth_test.go b/routing/bandwidth_test.go index 9d044b418d..ef12d69737 100644 --- a/routing/bandwidth_test.go +++ b/routing/bandwidth_test.go @@ -3,7 +3,7 @@ package routing import ( "testing" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/lnwire" diff --git a/routing/chainview/bitcoind.go b/routing/chainview/bitcoind.go index 9a298edd4b..c38c4df8f5 100644 --- a/routing/chainview/bitcoind.go +++ b/routing/chainview/bitcoind.go @@ -231,7 +231,7 @@ func (b *BitcoindFilteredChainView) FilterBlock(blockHash *chainhash.Hash) (*Fil // chainFilterer is the primary goroutine which: listens for new blocks coming // and dispatches the relevant FilteredBlock notifications, updates the filter -// due to requests by callers, and finally is able to preform targeted block +// due to requests by callers, and finally is able to perform targeted block // filtration. // // TODO(roasbeef): change to use loadfilter RPC's diff --git a/routing/chainview/btcd.go b/routing/chainview/btcd.go index 4e5a95b5b6..c0234386aa 100644 --- a/routing/chainview/btcd.go +++ b/routing/chainview/btcd.go @@ -8,10 +8,10 @@ import ( "sync/atomic" "github.com/btcsuite/btcd/btcjson" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/rpcclient" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/blockcache" "github.com/lightningnetwork/lnd/channeldb" ) diff --git a/routing/chainview/interface_test.go b/routing/chainview/interface_test.go index 9cf8ed888a..bb1700729e 100644 --- a/routing/chainview/interface_test.go +++ b/routing/chainview/interface_test.go @@ -4,31 +4,32 @@ import ( "bytes" "fmt" "io/ioutil" - "math/rand" + "net" "os" "os/exec" "path/filepath" "runtime" + "sync/atomic" "testing" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcjson" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/integration/rpctest" "github.com/btcsuite/btcd/rpcclient" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/btcsuite/btcwallet/chain" "github.com/btcsuite/btcwallet/walletdb" _ "github.com/btcsuite/btcwallet/walletdb/bdb" // Required to register the boltdb walletdb implementation. - "github.com/lightninglabs/neutrino" "github.com/lightningnetwork/lnd/blockcache" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/kvdb" + "github.com/lightningnetwork/lnd/lntest/wait" ) var ( @@ -41,7 +42,7 @@ var ( 0x1e, 0xb, 0x4c, 0xfd, 0x9e, 0xc5, 0x8c, 0xe9, } - privKey, pubKey = btcec.PrivKeyFromBytes(btcec.S256(), testPrivKey) + privKey, pubKey = btcec.PrivKeyFromBytes(testPrivKey) addrPk, _ = btcutil.NewAddressPubKey(pubKey.SerializeCompressed(), netParams) testAddr = addrPk.AddressPubKeyHash() @@ -49,6 +50,35 @@ var ( testScript, _ = txscript.PayToAddrScript(testAddr) ) +var ( + // lastPort is the last port determined to be free for use by a new + // bitcoind server. It should be used atomically. + lastPort uint32 = 1024 +) + +// getFreePort returns the first port that is available for listening by a new +// embedded etcd server. It panics if no port is found and the maximum available +// TCP port is reached. +func getFreePort() int { + port := atomic.AddUint32(&lastPort, 1) + for port < 65535 { + // If there are no errors while attempting to listen on this + // port, close the socket and return it as available. + addr := fmt.Sprintf("127.0.0.1:%d", port) + l, err := net.Listen("tcp4", addr) + if err == nil { + err := l.Close() + if err == nil { + return int(port) + } + } + port = atomic.AddUint32(&lastPort, 1) + } + + // No ports available? Must be a mistake. + panic("no ports available for listening") +} + func waitForMempoolTx(r *rpctest.Harness, txid *chainhash.Hash) error { var found bool var tx *btcutil.Tx @@ -548,14 +578,28 @@ func testFilterBlockDisconnected(node *rpctest.Harness, } defer reorgNode.TearDown() + // We want to overwrite some of the connection settings to make the + // tests more robust. We might need to restart the backend while there + // are already blocks present, which will take a bit longer than the + // 1 second the default settings amount to. Doubling both values will + // give us retries up to 4 seconds. + reorgNode.MaxConnRetries = rpctest.DefaultMaxConnectionRetries * 2 + reorgNode.ConnectionRetryTimeout = rpctest.DefaultConnectionRetryTimeout * 2 + // This node's chain will be 105 blocks. if err := reorgNode.SetUp(true, 5); err != nil { t.Fatalf("unable to set up mining node: %v", err) } + _, bestHeight, err := reorgNode.Client.GetBestBlock() + if err != nil { + t.Fatalf("error getting best block: %v", err) + } + // Init a chain view that has this node as its block source. - cleanUpFunc, reorgView, err := chainViewInit(reorgNode.RPCConfig(), - reorgNode.P2PAddress()) + cleanUpFunc, reorgView, err := chainViewInit( + reorgNode.RPCConfig(), reorgNode.P2PAddress(), bestHeight, + ) if err != nil { t.Fatalf("unable to create chain view: %v", err) } @@ -743,7 +787,7 @@ func testFilterBlockDisconnected(node *rpctest.Harness, } type chainViewInitFunc func(rpcInfo rpcclient.ConnConfig, - p2pAddr string) (func(), FilteredChainView, error) + p2pAddr string, bestHeight int32) (func(), FilteredChainView, error) type testCase struct { name string @@ -776,7 +820,10 @@ var interfaceImpls = []struct { }{ { name: "bitcoind_zmq", - chainViewInit: func(_ rpcclient.ConnConfig, p2pAddr string) (func(), FilteredChainView, error) { + chainViewInit: func(_ rpcclient.ConnConfig, + p2pAddr string, bestHeight int32) (func(), + FilteredChainView, error) { + // Start a bitcoind instance. tempBitcoindDir, err := ioutil.TempDir("", "bitcoind") if err != nil { @@ -787,7 +834,7 @@ var interfaceImpls = []struct { cleanUp1 := func() { os.RemoveAll(tempBitcoindDir) } - rpcPort := rand.Int()%(65536-1024) + 1024 + rpcPort := getFreePort() bitcoind := exec.Command( "bitcoind", "-datadir="+tempBitcoindDir, @@ -807,35 +854,170 @@ var interfaceImpls = []struct { cleanUp1() return nil, nil, err } - cleanUp2 := func() { - bitcoind.Process.Kill() - bitcoind.Wait() + + // Sanity check to ensure that the process did in fact + // start. + if bitcoind.Process == nil { cleanUp1() + return nil, nil, fmt.Errorf("bitcoind cmd " + + "Process is not set after Start") } - // Wait for the bitcoind instance to start up. - time.Sleep(time.Second) + cleanUp2 := func() { + _ = bitcoind.Process.Kill() + _ = bitcoind.Wait() + cleanUp1() + } host := fmt.Sprintf("127.0.0.1:%d", rpcPort) - chainConn, err := chain.NewBitcoindConn(&chain.BitcoindConfig{ - ChainParams: &chaincfg.RegressionNetParams, - Host: host, - User: "weks", - Pass: "weks", - ZMQBlockHost: zmqBlockHost, - ZMQTxHost: zmqTxHost, - ZMQReadDeadline: 5 * time.Second, + cfg := &chain.BitcoindConfig{ + ChainParams: &chaincfg.RegressionNetParams, + Host: host, + User: "weks", + Pass: "weks", + ZMQConfig: &chain.ZMQConfig{ + ZMQBlockHost: zmqBlockHost, + ZMQTxHost: zmqTxHost, + ZMQReadDeadline: 5 * time.Second, + }, // Fields only required for pruned nodes, not // needed for these tests. Dialer: nil, PrunedModeMaxPeers: 0, - }) + } + + var chainConn *chain.BitcoindConn + err = wait.NoError(func() error { + chainConn, err = chain.NewBitcoindConn(cfg) + if err != nil { + return err + } + + err = chainConn.Start() + if err != nil { + return err + } + + client := chainConn.NewBitcoindClient() + _, currentHeight, err := client.GetBestBlock() + if err != nil { + return err + } + + if currentHeight < bestHeight { + return fmt.Errorf("not synced yet") + } + + return nil + }, 10*time.Second) if err != nil { return cleanUp2, nil, fmt.Errorf("unable to "+ "establish connection to bitcoind: %v", err) } - if err := chainConn.Start(); err != nil { + cleanUp3 := func() { + chainConn.Stop() + cleanUp2() + } + + blockCache := blockcache.NewBlockCache(10000) + + chainView := NewBitcoindFilteredChainView( + chainConn, blockCache, + ) + + return cleanUp3, chainView, nil + }, + }, + { + name: "bitcoind_polling", + chainViewInit: func(_ rpcclient.ConnConfig, + p2pAddr string, bestHeight int32) (func(), + FilteredChainView, error) { + + // Start a bitcoind instance. + tempBitcoindDir, err := ioutil.TempDir("", "bitcoind") + if err != nil { + return nil, nil, err + } + cleanUp1 := func() { + os.RemoveAll(tempBitcoindDir) + } + rpcPort := getFreePort() + bitcoind := exec.Command( + "bitcoind", + "-datadir="+tempBitcoindDir, + "-regtest", + "-connect="+p2pAddr, + "-txindex", + "-rpcauth=weks:469e9bb14ab2360f8e226efed5ca6f"+ + "d$507c670e800a95284294edb5773b05544b"+ + "220110063096c221be9933c82d38e1", + fmt.Sprintf("-rpcport=%d", rpcPort), + "-disablewallet", + ) + err = bitcoind.Start() + if err != nil { + cleanUp1() + return nil, nil, err + } + + // Sanity check to ensure that the process did in fact + // start. + if bitcoind.Process == nil { + cleanUp1() + return nil, nil, fmt.Errorf("bitcoind cmd " + + "Process is not set after Start") + } + + cleanUp2 := func() { + _ = bitcoind.Process.Kill() + _ = bitcoind.Wait() + cleanUp1() + } + + host := fmt.Sprintf("127.0.0.1:%d", rpcPort) + cfg := &chain.BitcoindConfig{ + ChainParams: &chaincfg.RegressionNetParams, + Host: host, + User: "weks", + Pass: "weks", + PollingConfig: &chain.PollingConfig{ + BlockPollingInterval: time.Millisecond * 100, + TxPollingInterval: time.Millisecond * 100, + }, + // Fields only required for pruned nodes, not + // needed for these tests. + Dialer: nil, + PrunedModeMaxPeers: 0, + } + + // Wait for the bitcoind instance to start up. + var chainConn *chain.BitcoindConn + err = wait.NoError(func() error { + chainConn, err = chain.NewBitcoindConn(cfg) + if err != nil { + return err + } + + err = chainConn.Start() + if err != nil { + return err + } + + client := chainConn.NewBitcoindClient() + _, currentHeight, err := client.GetBestBlock() + if err != nil { + return err + } + + if currentHeight < bestHeight { + return fmt.Errorf("not synced yet") + } + + return nil + }, 10*time.Second) + if err != nil { return cleanUp2, nil, fmt.Errorf("unable to "+ "establish connection to bitcoind: %v", err) @@ -856,7 +1038,10 @@ var interfaceImpls = []struct { }, { name: "p2p_neutrino", - chainViewInit: func(_ rpcclient.ConnConfig, p2pAddr string) (func(), FilteredChainView, error) { + chainViewInit: func(_ rpcclient.ConnConfig, + p2pAddr string, bestHeight int32) (func(), + FilteredChainView, error) { + spvDir, err := ioutil.TempDir("", "neutrino") if err != nil { return nil, nil, err @@ -881,12 +1066,30 @@ var interfaceImpls = []struct { if err != nil { return nil, nil, err } - spvNode.Start() // Wait until the node has fully synced up to the local // btcd node. - for !spvNode.IsCurrent() { - time.Sleep(time.Millisecond * 100) + err = wait.NoError(func() error { + err := spvNode.Start() + if err != nil { + return err + } + + bestBlock, err := spvNode.BestBlock() + if err != nil { + return err + } + + if bestBlock.Height < bestHeight { + return fmt.Errorf("not synced yet") + } + + return nil + }, 10*time.Second) + if err != nil { + return nil, nil, fmt.Errorf("unable to "+ + "establish connection to bitcoind: %v", + err) } cleanUp := func() { @@ -909,7 +1112,10 @@ var interfaceImpls = []struct { }, { name: "btcd_websockets", - chainViewInit: func(config rpcclient.ConnConfig, _ string) (func(), FilteredChainView, error) { + chainViewInit: func(config rpcclient.ConnConfig, + p2pAddr string, bestHeight int32) (func(), + FilteredChainView, error) { + blockCache := blockcache.NewBlockCache(10000) chainView, err := NewBtcdFilteredChainView( config, blockCache, @@ -944,11 +1150,17 @@ func TestFilteredChainView(t *testing.T) { t.Logf("Testing '%v' implementation of FilteredChainView", chainViewImpl.name) - cleanUpFunc, chainView, err := chainViewImpl.chainViewInit(rpcConfig, p2pAddr) + _, bestHeight, err := miner.Client.GetBestBlock() if err != nil { - t.Fatalf("unable to make chain view: %v", err) + t.Fatalf("error getting best block: %v", err) } + cleanUpFunc, chainView, err := chainViewImpl.chainViewInit( + rpcConfig, p2pAddr, bestHeight, + ) + if err != nil { + t.Fatalf("unable to make chain view: %v", err) + } if err := chainView.Start(); err != nil { t.Fatalf("unable to start chain view: %v", err) } @@ -956,8 +1168,10 @@ func TestFilteredChainView(t *testing.T) { testName := fmt.Sprintf("%v: %v", chainViewImpl.name, chainViewTest.name) success := t.Run(testName, func(t *testing.T) { - chainViewTest.test(miner, chainView, - chainViewImpl.chainViewInit, t) + chainViewTest.test( + miner, chainView, + chainViewImpl.chainViewInit, t, + ) }) if !success { diff --git a/routing/chainview/neutrino.go b/routing/chainview/neutrino.go index 2050439112..8bba71a7e6 100644 --- a/routing/chainview/neutrino.go +++ b/routing/chainview/neutrino.go @@ -5,11 +5,11 @@ import ( "sync" "sync/atomic" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/gcs/builder" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/rpcclient" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" - "github.com/btcsuite/btcutil/gcs/builder" "github.com/lightninglabs/neutrino" "github.com/lightningnetwork/lnd/blockcache" "github.com/lightningnetwork/lnd/channeldb" @@ -64,6 +64,7 @@ var _ FilteredChainView = (*CfFilteredChainView)(nil) // this function. func NewCfFilteredChainView(node *neutrino.ChainService, blockCache *blockcache.BlockCache) (*CfFilteredChainView, error) { + return &CfFilteredChainView{ blockQueue: newBlockEventQueue(), quit: make(chan struct{}), diff --git a/routing/control_tower.go b/routing/control_tower.go index 950b16f316..19b5ab504c 100644 --- a/routing/control_tower.go +++ b/routing/control_tower.go @@ -15,7 +15,7 @@ import ( // restarts. Payments are transitioned through various payment states, and the // ControlTower interface provides access to driving the state transitions. type ControlTower interface { - // This method checks that no suceeded payment exist for this payment + // This method checks that no succeeded payment exist for this payment // hash. InitPayment(lntypes.Hash, *channeldb.PaymentCreationInfo) error @@ -117,7 +117,7 @@ func NewControlTower(db *channeldb.PaymentControl) ControlTower { // InitPayment checks or records the given PaymentCreationInfo with the DB, // making sure it does not already exist as an in-flight payment. Then this -// method returns successfully, the payment is guranteeed to be in the InFlight +// method returns successfully, the payment is guaranteed to be in the InFlight // state. func (p *controlTower) InitPayment(paymentHash lntypes.Hash, info *channeldb.PaymentCreationInfo) error { @@ -258,7 +258,7 @@ func (p *controlTower) SubscribePayment(paymentHash lntypes.Hash) ( // notifySubscribers sends a final payment event to all subscribers of this // payment. The channel will be closed after this. Note that this function must // be executed atomically (by means of a lock) with the database update to -// guarantuee consistency of the notifications. +// guarantee consistency of the notifications. func (p *controlTower) notifySubscribers(paymentHash lntypes.Hash, event *channeldb.MPPayment) { diff --git a/routing/control_tower_test.go b/routing/control_tower_test.go index 8b7082db47..591ea3ced1 100644 --- a/routing/control_tower_test.go +++ b/routing/control_tower_test.go @@ -10,7 +10,7 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/lntypes" @@ -18,7 +18,7 @@ import ( ) var ( - priv, _ = btcec.NewPrivateKey(btcec.S256()) + priv, _ = btcec.NewPrivateKey() pub = priv.PubKey() testHop = &route.Hop{ diff --git a/routing/errors.go b/routing/errors.go index a5c7fd3ca3..95ed613ca0 100644 --- a/routing/errors.go +++ b/routing/errors.go @@ -25,9 +25,18 @@ const ( // funding transaction described by the short channel ID on chain. ErrNoFundingTransaction - // ErrInvalidFundingOutput is returned if the channle funding output + // ErrInvalidFundingOutput is returned if the channel funding output // fails validation. ErrInvalidFundingOutput + + // ErrVBarrierShuttingDown signals that the barrier has been requested + // to shutdown, and that the caller should not treat the wait condition + // as fulfilled. + ErrVBarrierShuttingDown + + // ErrParentValidationFailed signals that the validation of a + // dependent's parent failed, so the dependent must not be processed. + ErrParentValidationFailed ) // routerError is a structure that represent the error inside the routing package, diff --git a/routing/integrated_routing_test.go b/routing/integrated_routing_test.go index 80fefaf93e..5eaa859ac6 100644 --- a/routing/integrated_routing_test.go +++ b/routing/integrated_routing_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/lnwire" "github.com/stretchr/testify/require" diff --git a/routing/localchans/log.go b/routing/localchans/log.go deleted file mode 100644 index 9ebcf4d0ff..0000000000 --- a/routing/localchans/log.go +++ /dev/null @@ -1,16 +0,0 @@ -package localchans - -import ( - "github.com/btcsuite/btclog" -) - -// log is a logger that is initialized with no output filters. This -// means the package will not perform any logging by default until the caller -// requests it. -var log btclog.Logger - -// UseLogger uses a specified Logger to output package logging info. This -// function is called from the parent package htlcswitch logger initialization. -func UseLogger(logger btclog.Logger) { - log = logger -} diff --git a/routing/localchans/manager.go b/routing/localchans/manager.go index c879888f0a..4484d5f625 100644 --- a/routing/localchans/manager.go +++ b/routing/localchans/manager.go @@ -185,12 +185,11 @@ func (r *Manager) updateEdge(tx kvdb.RTx, chanPoint wire.OutPoint, // Retrieve negotiated channel htlc amt limits. amtMin, amtMax, err := r.getHtlcAmtLimits(tx, chanPoint) if err != nil { - return nil + return err } // We now update the edge max htlc value. switch { - // If a non-zero max htlc was specified, use it to update the edge. // Otherwise keep the value unchanged. case newSchema.MaxHTLC != 0: diff --git a/routing/localchans/manager_test.go b/routing/localchans/manager_test.go index 40f05f2fa6..55bf97e713 100644 --- a/routing/localchans/manager_test.go +++ b/routing/localchans/manager_test.go @@ -3,19 +3,17 @@ package localchans import ( "testing" - "github.com/lightningnetwork/lnd/kvdb" - "github.com/lightningnetwork/lnd/lnrpc" - "github.com/lightningnetwork/lnd/lnwire" - "github.com/stretchr/testify/require" - + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcutil" - "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/discovery" "github.com/lightningnetwork/lnd/htlcswitch" + "github.com/lightningnetwork/lnd/kvdb" + "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing" + "github.com/stretchr/testify/require" ) // TestManager tests that the local channel manager properly propagates fee diff --git a/routing/missioncontrol.go b/routing/missioncontrol.go index a9fad3938d..2bce890d5d 100644 --- a/routing/missioncontrol.go +++ b/routing/missioncontrol.go @@ -45,7 +45,7 @@ const ( // DefaultMaxMcHistory is the default maximum history size. DefaultMaxMcHistory = 1000 - // DefaultMcFlushInterval is the defaul inteval we use to flush MC state + // DefaultMcFlushInterval is the default interval we use to flush MC state // to the database. DefaultMcFlushInterval = time.Second @@ -363,7 +363,9 @@ func (m *MissionControl) GetHistorySnapshot() *MissionControlSnapshot { // ImportHistory imports the set of mission control results provided to our // in-memory state. These results are not persisted, so will not survive // restarts. -func (m *MissionControl) ImportHistory(history *MissionControlSnapshot) error { +func (m *MissionControl) ImportHistory(history *MissionControlSnapshot, + force bool) error { + if history == nil { return errors.New("cannot import nil history") } @@ -374,7 +376,7 @@ func (m *MissionControl) ImportHistory(history *MissionControlSnapshot) error { log.Infof("Importing history snapshot with %v pairs to mission control", len(history.Pairs)) - imported := m.state.importSnapshot(history) + imported := m.state.importSnapshot(history, force) log.Infof("Imported %v results to mission control", imported) @@ -520,7 +522,7 @@ func (m *MissionControl) applyPaymentResult( } m.state.setLastPairResult( - pair.From, pair.To, result.timeReply, &pairResult, + pair.From, pair.To, result.timeReply, &pairResult, false, ) } diff --git a/routing/missioncontrol_state.go b/routing/missioncontrol_state.go index 05e083b80c..7d6633f2bd 100644 --- a/routing/missioncontrol_state.go +++ b/routing/missioncontrol_state.go @@ -54,7 +54,7 @@ func (m *missionControlState) resetHistory() { // setLastPairResult stores a result for a node pair. func (m *missionControlState) setLastPairResult(fromNode, toNode route.Vertex, - timestamp time.Time, result *pairResult) { + timestamp time.Time, result *pairResult, force bool) { nodePairs, ok := m.lastPairResult[fromNode] if !ok { @@ -74,7 +74,7 @@ func (m *missionControlState) setLastPairResult(fromNode, toNode route.Vertex, // prevents the success range from shrinking when there is no // reason to do so. For example: small amount probes shouldn't // affect a previous success for a much larger amount. - if successAmt > current.SuccessAmt { + if force || successAmt > current.SuccessAmt { current.SuccessAmt = successAmt } @@ -83,13 +83,15 @@ func (m *missionControlState) setLastPairResult(fromNode, toNode route.Vertex, // are likely to succeed. We don't want to clear the failure // completely, because we haven't learnt much for amounts above // the current success amount. - if !current.FailTime.IsZero() && successAmt >= current.FailAmt { + if force || (!current.FailTime.IsZero() && + successAmt >= current.FailAmt) { + current.FailAmt = successAmt + 1 } } else { // For failures we always want to update both the amount and the // time. Those need to relate to the same result, because the - // time is used to gradually diminish the penality for that + // time is used to gradually diminish the penalty for that // specific result. Updating the timestamp but not the amount // could cause a failure for a lower amount (a more severe // condition) to be revived as if it just happened. @@ -100,7 +102,7 @@ func (m *missionControlState) setLastPairResult(fromNode, toNode route.Vertex, // come in out of order. This check makes it easier for payment // processes to converge to a final state. failInterval := timestamp.Sub(current.FailTime) - if failAmt > current.FailAmt && + if !force && failAmt > current.FailAmt && failInterval < m.minFailureRelaxInterval { log.Debugf("Ignoring higher amount failure within min "+ @@ -213,7 +215,9 @@ func (m *missionControlState) getSnapshot() *MissionControlSnapshot { // importSnapshot takes an existing snapshot and merges it with our current // state if the result provided are fresher than our current results. It returns // the number of pairs that were used. -func (m *missionControlState) importSnapshot(snapshot *MissionControlSnapshot) int { +func (m *missionControlState) importSnapshot(snapshot *MissionControlSnapshot, + force bool) int { + var imported int for _, pair := range snapshot.Pairs { @@ -230,13 +234,13 @@ func (m *missionControlState) importSnapshot(snapshot *MissionControlSnapshot) i failResult := failPairResult(pair.FailAmt) imported += m.importResult( lastResult.FailTime, pair.FailTime, failResult, - fromNode, toNode, + fromNode, toNode, force, ) successResult := successPairResult(pair.SuccessAmt) imported += m.importResult( lastResult.SuccessTime, pair.SuccessTime, successResult, - fromNode, toNode, + fromNode, toNode, force, ) } @@ -244,9 +248,10 @@ func (m *missionControlState) importSnapshot(snapshot *MissionControlSnapshot) i } func (m *missionControlState) importResult(currentTs, importedTs time.Time, - importedResult pairResult, fromNode, toNode route.Vertex) int { + importedResult pairResult, fromNode, toNode route.Vertex, + force bool) int { - if currentTs.After(importedTs) { + if !force && currentTs.After(importedTs) { log.Debugf("Not setting pair result for %v->%v (%v) "+ "success=%v, timestamp %v older than last result %v", fromNode, toNode, importedResult.amt, @@ -255,7 +260,9 @@ func (m *missionControlState) importResult(currentTs, importedTs time.Time, return 0 } - m.setLastPairResult(fromNode, toNode, importedTs, &importedResult) + m.setLastPairResult( + fromNode, toNode, importedTs, &importedResult, force, + ) return 1 } diff --git a/routing/missioncontrol_state_test.go b/routing/missioncontrol_state_test.go index 28635d4375..178a9cded2 100644 --- a/routing/missioncontrol_state_test.go +++ b/routing/missioncontrol_state_test.go @@ -5,11 +5,12 @@ import ( "time" "github.com/lightningnetwork/lnd/routing/route" + "github.com/stretchr/testify/require" ) -// TestMissionControlStateFailureResult tests setting failure results on the -// mission control state. -func TestMissionControlStateFailureResult(t *testing.T) { +// TestMissionControlStateSetLastPairResult tests setting mission control state +// pair results. +func TestMissionControlStateSetLastPairResult(t *testing.T) { const minFailureRelaxInterval = time.Minute state := newMissionControlState(minFailureRelaxInterval) @@ -20,7 +21,9 @@ func TestMissionControlStateFailureResult(t *testing.T) { ) // Report a 1000 sat failure. - state.setLastPairResult(from, to, timestamp, &pairResult{amt: 1000}) + state.setLastPairResult( + from, to, timestamp, &pairResult{amt: 1000}, false, + ) result, _ := state.getLastPairResult(from) if result[to].FailAmt != 1000 { t.Fatalf("unexpected fail amount %v", result[to].FailAmt) @@ -29,7 +32,9 @@ func TestMissionControlStateFailureResult(t *testing.T) { // Report an 1100 sat failure one hour later. It is expected to // overwrite the previous failure. timestamp = timestamp.Add(time.Hour) - state.setLastPairResult(from, to, timestamp, &pairResult{amt: 1100}) + state.setLastPairResult( + from, to, timestamp, &pairResult{amt: 1100}, false, + ) result, _ = state.getLastPairResult(from) if result[to].FailAmt != 1100 { t.Fatalf("unexpected fail amount %v", result[to].FailAmt) @@ -39,9 +44,50 @@ func TestMissionControlStateFailureResult(t *testing.T) { // the failure amount is too soon after the previous failure, the result // is not applied. timestamp = timestamp.Add(time.Second) - state.setLastPairResult(from, to, timestamp, &pairResult{amt: 1200}) + state.setLastPairResult( + from, to, timestamp, &pairResult{amt: 1200}, false, + ) result, _ = state.getLastPairResult(from) if result[to].FailAmt != 1100 { t.Fatalf("unexpected fail amount %v", result[to].FailAmt) } + + // Roll back time 1 second to test forced import. + timestamp = testTime + state.setLastPairResult( + from, to, timestamp, &pairResult{amt: 999}, true, + ) + result, _ = state.getLastPairResult(from) + require.Equal(t, 999, int(result[to].FailAmt)) + + // Report an 1500 sat success. + timestamp = timestamp.Add(time.Second) + state.setLastPairResult( + from, to, timestamp, &pairResult{amt: 1500, success: true}, false, + ) + result, _ = state.getLastPairResult(from) + // We don't expect the failtime to change, only the fail amount, we + // expect however change of both the success time and the success amount. + expected := TimedPairResult{ + FailTime: timestamp.Add(-time.Second), + FailAmt: 1501, + SuccessTime: timestamp, + SuccessAmt: 1500, + } + require.Equal(t, expected, result[to]) + + // Again roll back time to test forced import. + state.setLastPairResult( + from, to, testTime, &pairResult{amt: 50, success: true}, true, + ) + result, _ = state.getLastPairResult(from) + // We don't expect the failtime to change, only the fail amount, we + // expect however change of both the success time and the success amount. + expected = TimedPairResult{ + FailTime: timestamp.Add(-time.Second), + FailAmt: 51, + SuccessTime: testTime, + SuccessAmt: 50, + } + require.Equal(t, expected, result[to]) } diff --git a/routing/missioncontrol_store.go b/routing/missioncontrol_store.go index 193f7d13a3..27be86821d 100644 --- a/routing/missioncontrol_store.go +++ b/routing/missioncontrol_store.go @@ -82,7 +82,7 @@ func newMissionControlStore(db kvdb.Backend, maxRecords int, // difference when updating the DB state. c := resultsBucket.ReadCursor() for k, _ := c.First(); k != nil; k, _ = c.Next() { - keys.PushBack(k) + keys.PushBack(string(k)) keysMap[string(k)] = struct{}{} } @@ -334,7 +334,7 @@ func (b *missionControlStore) storeResults() error { return err } - keys.PushBack(k) + keys.PushBack(string(k)) keysMap[string(k)] = struct{}{} } @@ -345,14 +345,14 @@ func (b *missionControlStore) storeResults() error { } front := keys.Front() - key := front.Value.([]byte) + key := front.Value.(string) - if err := bucket.Delete(key); err != nil { + if err := bucket.Delete([]byte(key)); err != nil { return err } keys.Remove(front) - delete(keysMap, string(key)) + delete(keysMap, key) } return nil diff --git a/routing/mock_graph_test.go b/routing/mock_graph_test.go index 6d01566665..33a9c11af6 100644 --- a/routing/mock_graph_test.go +++ b/routing/mock_graph_test.go @@ -5,7 +5,7 @@ import ( "fmt" "testing" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" diff --git a/routing/mock_test.go b/routing/mock_test.go index e32e002d58..1bc09a5a98 100644 --- a/routing/mock_test.go +++ b/routing/mock_test.go @@ -4,7 +4,7 @@ import ( "fmt" "sync" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/htlcswitch" @@ -632,6 +632,7 @@ var _ PaymentSession = (*mockPaymentSession)(nil) func (m *mockPaymentSession) RequestRoute(maxAmt, feeLimit lnwire.MilliSatoshi, activeShards, height uint32) (*route.Route, error) { + args := m.Called(maxAmt, feeLimit, activeShards, height) return args.Get(0).(*route.Route), args.Error(1) } diff --git a/routing/notifications.go b/routing/notifications.go index 02956c84c3..fe1b3c236e 100644 --- a/routing/notifications.go +++ b/routing/notifications.go @@ -7,9 +7,9 @@ import ( "sync" "sync/atomic" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/davecgh/go-spew/spew" "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/channeldb" @@ -125,9 +125,8 @@ func (r *ChannelRouter) notifyTopologyChange(topologyDiff *TopologyChange) { return } - log.Debugf("Sending topology notification to %v clients %v", - numClients, - newLogClosure(func() string { + log.Tracef("Sending topology notification to %v clients %v", + numClients, newLogClosure(func() string { return spew.Sdump(topologyDiff) }), ) @@ -325,7 +324,6 @@ func addToTopologyChange(graph *channeldb.ChannelGraph, update *TopologyChange, Color: EncodeHexColor(m.Color), Features: m.Features.Clone(), } - nodeUpdate.IdentityKey.Curve = nil update.NodeUpdates = append(update.NodeUpdates, nodeUpdate) return nil @@ -378,8 +376,6 @@ func addToTopologyChange(graph *channeldb.ChannelGraph, update *TopologyChange, ConnectingNode: cNode, Disabled: m.ChannelFlags&lnwire.ChanUpdateDisabled != 0, } - edgeUpdate.AdvertisingNode.Curve = nil - edgeUpdate.ConnectingNode.Curve = nil // TODO(roasbeef): add bit to toggle update.ChannelEdgeUpdates = append(update.ChannelEdgeUpdates, diff --git a/routing/notifications_test.go b/routing/notifications_test.go index c63aa66e49..9a9f52cf14 100644 --- a/routing/notifications_test.go +++ b/routing/notifications_test.go @@ -4,17 +4,16 @@ import ( "bytes" "fmt" "image/color" + prand "math/rand" "net" "sync" "testing" "time" - prand "math/rand" - - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/input" @@ -42,10 +41,10 @@ var ( testTime = time.Date(2018, time.January, 9, 14, 00, 00, 0, time.UTC) - priv1, _ = btcec.NewPrivateKey(btcec.S256()) + priv1, _ = btcec.NewPrivateKey() bitcoinKey1 = priv1.PubKey() - priv2, _ = btcec.NewPrivateKey(btcec.S256()) + priv2, _ = btcec.NewPrivateKey() bitcoinKey2 = priv2.PubKey() timeout = time.Second * 5 @@ -54,7 +53,7 @@ var ( func createTestNode() (*channeldb.LightningNode, error) { updateTime := prand.Int63() - priv, err := btcec.NewPrivateKey(btcec.S256()) + priv, err := btcec.NewPrivateKey() if err != nil { return nil, errors.Errorf("unable create private key: %v", err) } diff --git a/routing/pathfind.go b/routing/pathfind.go index c07271550f..a92d0a7e52 100644 --- a/routing/pathfind.go +++ b/routing/pathfind.go @@ -41,7 +41,7 @@ const ( // pathFinder defines the interface of a path finding algorithm. type pathFinder = func(g *graphParams, r *RestrictParams, cfg *PathFindingConfig, source, target route.Vertex, - amt lnwire.MilliSatoshi, finalHtlcExpiry int32) ( + amt lnwire.MilliSatoshi, timePref float64, finalHtlcExpiry int32) ( []*channeldb.CachedEdgePolicy, error) var ( @@ -89,6 +89,10 @@ type finalHopParams struct { cltvDelta uint16 records record.CustomSet paymentAddr *[32]byte + + // metadata is additional data that is sent along with the payment to + // the payee. + metadata []byte } // newRoute constructs a route using the provided path and final hop constraints. @@ -138,6 +142,7 @@ func newRoute(sourceVertex route.Vertex, tlvPayload bool customRecords record.CustomSet mpp *record.MPP + metadata []byte ) // Define a helper function that checks this edge's feature @@ -202,6 +207,8 @@ func newRoute(sourceVertex route.Vertex, *finalHop.paymentAddr, ) } + + metadata = finalHop.metadata } else { // The amount that the current hop needs to forward is // equal to the incoming amount of the next hop. @@ -232,6 +239,7 @@ func newRoute(sourceVertex route.Vertex, LegacyPayload: !tlvPayload, CustomRecords: customRecords, MPP: mpp, + Metadata: metadata, } hops = append([]*route.Hop{currentHop}, hops...) @@ -330,6 +338,10 @@ type RestrictParams struct { // mitigate probing vectors and payment sniping attacks on overpaid // invoices. PaymentAddr *[32]byte + + // Metadata is additional data that is sent along with the payment to + // the payee. + Metadata []byte } // PathFindingConfig defines global parameters that control the trade-off in @@ -413,7 +425,7 @@ func getOutgoingBalance(node route.Vertex, outgoingChans map[uint64]struct{}, // path and accurately check the amount to forward at every node against the // available bandwidth. func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig, - source, target route.Vertex, amt lnwire.MilliSatoshi, + source, target route.Vertex, amt lnwire.MilliSatoshi, timePref float64, finalHtlcExpiry int32) ([]*channeldb.CachedEdgePolicy, error) { // Pathfinding can be a significant portion of the total payment @@ -474,6 +486,14 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig, return nil, errNoPaymentAddr } + // If the caller needs to send custom records, check that our + // destination feature vector supports TLV. + if r.Metadata != nil && + !features.HasFeature(lnwire.TLVOnionPayloadOptional) { + + return nil, errNoTlvPayload + } + // Set up outgoing channel map for quicker access. var outgoingChanMap map[uint64]struct{} if len(r.OutgoingChannelIDs) > 0 { @@ -547,7 +567,8 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig, LegacyPayload: !features.HasFeature( lnwire.TLVOnionPayloadOptional, ), - MPP: mpp, + MPP: mpp, + Metadata: r.Metadata, } // We can't always assume that the end destination is publicly @@ -573,13 +594,27 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig, // if the cltv limit is MaxUint32. absoluteCltvLimit := uint64(r.CltvLimit) + uint64(finalHtlcExpiry) - // Calculate the absolute attempt cost that is used for probability - // estimation. - absoluteAttemptCost := int64(cfg.AttemptCost) + - int64(amt)*cfg.AttemptCostPPM/1000000 + // Calculate the default attempt cost as configured globally. + defaultAttemptCost := float64( + cfg.AttemptCost + + amt*lnwire.MilliSatoshi(cfg.AttemptCostPPM)/1000000, + ) + + // Validate time preference value. + if math.Abs(timePref) > 1 { + return nil, fmt.Errorf("time preference %v out of range [-1, 1]", + timePref) + } + + // Scale to avoid the extremes -1 and 1 which run into infinity issues. + timePref *= 0.9 + + // Apply time preference. At 0, the default attempt cost will + // be used. + absoluteAttemptCost := defaultAttemptCost * (1/(0.5-timePref/2) - 1) log.Debugf("Pathfinding absolute attempt cost: %v sats", - float64(absoluteAttemptCost)/1000) + absoluteAttemptCost/1000) // processEdge is a helper closure that will be used to make sure edges // satisfy our specific requirements. @@ -959,13 +994,24 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig, // Fa + c/Pa < Fb + c/Pb // // So the value of F + c/P can be used to compare routes. -func getProbabilityBasedDist(weight int64, probability float64, penalty int64) int64 { - // Clamp probability to prevent overflow. - const minProbability = 0.00001 +func getProbabilityBasedDist(weight int64, probability float64, + penalty float64) int64 { + + // Prevent divide by zero by returning early. + if probability == 0 { + return infinity + } + + // Calculate distance. + dist := float64(weight) + penalty/probability - if probability < minProbability { + // Avoid cast if an overflow would occur. The maxFloat constant is + // chosen to stay well below the maximum float64 value that is still + // convertable to int64. + const maxFloat = 9000000000000000000 + if dist > maxFloat { return infinity } - return weight + int64(float64(penalty)/probability) + return int64(dist) } diff --git a/routing/pathfind_test.go b/routing/pathfind_test.go index dbe38475d1..ca53a23dc2 100644 --- a/routing/pathfind_test.go +++ b/routing/pathfind_test.go @@ -10,7 +10,6 @@ import ( "fmt" "io/ioutil" "math" - "math/big" "net" "os" "reflect" @@ -18,16 +17,18 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/record" "github.com/lightningnetwork/lnd/routing/route" + "github.com/stretchr/testify/require" ) const ( @@ -87,12 +88,13 @@ var ( ) var ( - testSig = &btcec.Signature{ - R: new(big.Int), - S: new(big.Int), - } - _, _ = testSig.R.SetString("63724406601629180062774974542967536251589935445068131219452686511677818569431", 10) - _, _ = testSig.S.SetString("18801056069249825825291287104931333862866033135609736119018462340006816851118", 10) + testRBytes, _ = hex.DecodeString("8ce2bc69281ce27da07e6683571319d18e949ddfa2965fb6caa1bf0314f882d7") + testSBytes, _ = hex.DecodeString("299105481d63e0f4bc2a88121167221b6700d72a0ead154c03be696a292d24ae") + testRScalar = new(btcec.ModNScalar) + testSScalar = new(btcec.ModNScalar) + _ = testRScalar.SetByteSlice(testRBytes) + _ = testSScalar.SetByteSlice(testSBytes) + testSig = ecdsa.NewSignature(testRScalar, testSScalar) testAuthProof = channeldb.ChannelAuthProof{ NodeSig1Bytes: testSig.Serialize(), @@ -175,7 +177,7 @@ func makeTestGraph(useCache bool) (*channeldb.ChannelGraph, kvdb.Backend, graph, err := channeldb.NewChannelGraph( backend, opts.RejectCacheSize, opts.ChannelCacheSize, opts.BatchCommitInterval, opts.PreAllocCacheNumNodes, - useCache, + useCache, false, ) if err != nil { cleanUp() @@ -259,7 +261,7 @@ func parseTestGraph(useCache bool, path string) (*testGraphInstance, error) { } if len(privBytes) > 0 { key, derivedPub := btcec.PrivKeyFromBytes( - btcec.S256(), privBytes, + privBytes, ) if !bytes.Equal( @@ -564,8 +566,7 @@ func createTestGraphFromChannels(useCache bool, testChannels []*testChannel, 0, 0, 0, 0, 0, 0, 0, nodeIndex + 1, } - privKey, pubKey := btcec.PrivKeyFromBytes(btcec.S256(), - keyBytes) + privKey, pubKey := btcec.PrivKeyFromBytes(keyBytes) if features == nil { features = lnwire.EmptyFeatureVector() @@ -614,8 +615,8 @@ func createTestGraphFromChannels(useCache bool, testChannels []*testChannel, for _, testChannel := range testChannels { for _, node := range []*testChannelEnd{ - testChannel.Node1, testChannel.Node2} { - + testChannel.Node1, testChannel.Node2, + } { _, exists := aliasMap[node.Alias] if !exists { var features *lnwire.FeatureVector @@ -840,6 +841,9 @@ func TestPathFinding(t *testing.T) { }, { name: "route to self", fn: runRouteToSelf, + }, { + name: "with metadata", + fn: runFindPathWithMetadata, }} // Run with graph cache enabled. @@ -866,6 +870,46 @@ func TestPathFinding(t *testing.T) { } } +// runFindPathWithMetadata tests that metadata is taken into account during +// pathfinding. +func runFindPathWithMetadata(t *testing.T, useCache bool) { + testChannels := []*testChannel{ + symmetricTestChannel("alice", "bob", 100000, &testChannelPolicy{ + Expiry: 144, + FeeRate: 400, + MinHTLC: 1, + MaxHTLC: 100000000, + }), + } + + ctx := newPathFindingTestContext(t, useCache, testChannels, "alice") + defer ctx.cleanup() + + paymentAmt := lnwire.NewMSatFromSatoshis(100) + target := ctx.keyFromAlias("bob") + + // Assert that a path is found when metadata is specified. + ctx.restrictParams.Metadata = []byte{1, 2, 3} + ctx.restrictParams.DestFeatures = tlvFeatures + + path, err := ctx.findPath(target, paymentAmt) + require.NoError(t, err) + require.Len(t, path, 1) + + // Assert that no path is found when metadata is too large. + ctx.restrictParams.Metadata = make([]byte, 2000) + + _, err = ctx.findPath(target, paymentAmt) + require.ErrorIs(t, errNoPathFound, err) + + // Assert that tlv payload support takes precedence over metadata + // issues. + ctx.restrictParams.DestFeatures = lnwire.EmptyFeatureVector() + + _, err = ctx.findPath(target, paymentAmt) + require.ErrorIs(t, errNoTlvPayload, err) +} + // runFindLowestFeePath tests that out of two routes with identical total // time lock values, the route with the lowest total fee should be returned. // The fee rates are chosen such that the test failed on the previous edge @@ -1059,7 +1103,7 @@ func testBasicGraphPathFindingCase(t *testing.T, graphInstance *testGraphInstanc CltvLimit: math.MaxUint32, }, testPathFindingConfig, - sourceNode.PubKeyBytes, target, paymentAmt, + sourceNode.PubKeyBytes, target, paymentAmt, 0, startingHeight+finalHopCLTV, ) if test.expectFailureNoPath { @@ -1213,7 +1257,7 @@ func runPathFindingWithAdditionalEdges(t *testing.T, useCache bool) { if err != nil { t.Fatalf("unable to decode public key: %v", err) } - dogePubKey, err := btcec.ParsePubKey(dogePubKeyBytes, btcec.S256()) + dogePubKey, err := btcec.ParsePubKey(dogePubKeyBytes) if err != nil { t.Fatalf("unable to parse public key from bytes: %v", err) } @@ -1248,7 +1292,7 @@ func runPathFindingWithAdditionalEdges(t *testing.T, useCache bool) { graph.graph, additionalEdges, &mockBandwidthHints{}, r, testPathFindingConfig, sourceNode.PubKeyBytes, doge.PubKeyBytes, paymentAmt, - 0, + 0, 0, ) } @@ -1340,6 +1384,9 @@ func TestNewRoute(t *testing.T) { paymentAddr *[32]byte + // metadata is the payment metadata to attach to the route. + metadata []byte + // expectedFees is a list of fees that every hop is expected // to charge for forwarding. expectedFees []lnwire.MilliSatoshi @@ -1380,6 +1427,7 @@ func TestNewRoute(t *testing.T) { hops: []*channeldb.CachedEdgePolicy{ createHop(100, 1000, 1000000, 10), }, + metadata: []byte{1, 2, 3}, expectedFees: []lnwire.MilliSatoshi{0}, expectedTimeLocks: []uint32{1}, expectedTotalAmount: 100000, @@ -1556,10 +1604,17 @@ func TestNewRoute(t *testing.T) { if !reflect.DeepEqual( finalHop.MPP, testCase.expectedMPP, ) { + t.Errorf("Expected final hop mpp field: %v, "+ " but got: %v instead", testCase.expectedMPP, finalHop.MPP) } + + if !bytes.Equal(finalHop.Metadata, testCase.metadata) { + t.Errorf("Expected final metadata field: %v, "+ + " but got: %v instead", + testCase.metadata, finalHop.Metadata) + } } t.Run(testCase.name, func(t *testing.T) { @@ -1571,6 +1626,7 @@ func TestNewRoute(t *testing.T) { cltvDelta: finalHopCLTV, records: nil, paymentAddr: testCase.paymentAddr, + metadata: testCase.metadata, }, ) @@ -1668,7 +1724,7 @@ func runPathNotAvailable(t *testing.T, useCache bool) { _, err = dbFindPath( graph.graph, nil, &mockBandwidthHints{}, noRestrictions, testPathFindingConfig, - sourceNode.PubKeyBytes, unknownNode, 100, 0, + sourceNode.PubKeyBytes, unknownNode, 100, 0, 0, ) if err != errNoPathFound { t.Fatalf("path shouldn't have been found: %v", err) @@ -1713,7 +1769,6 @@ func runDestTLVGraphFallback(t *testing.T, useCache bool) { sourceNode, err := ctx.graph.SourceNode() if err != nil { t.Fatalf("unable to fetch source node: %v", err) - } find := func(r *RestrictParams, @@ -1722,7 +1777,7 @@ func runDestTLVGraphFallback(t *testing.T, useCache bool) { return dbFindPath( ctx.graph, nil, &mockBandwidthHints{}, r, testPathFindingConfig, - sourceNode.PubKeyBytes, target, 100, 0, + sourceNode.PubKeyBytes, target, 100, 0, 0, ) } @@ -1983,7 +2038,7 @@ func runPathInsufficientCapacity(t *testing.T, useCache bool) { _, err = dbFindPath( graph.graph, nil, &mockBandwidthHints{}, noRestrictions, testPathFindingConfig, - sourceNode.PubKeyBytes, target, payAmt, 0, + sourceNode.PubKeyBytes, target, payAmt, 0, 0, ) if err != errInsufficientBalance { t.Fatalf("graph shouldn't be able to support payment: %v", err) @@ -2012,7 +2067,7 @@ func runRouteFailMinHTLC(t *testing.T, useCache bool) { _, err = dbFindPath( graph.graph, nil, &mockBandwidthHints{}, noRestrictions, testPathFindingConfig, - sourceNode.PubKeyBytes, target, payAmt, 0, + sourceNode.PubKeyBytes, target, payAmt, 0, 0, ) if err != errNoPathFound { t.Fatalf("graph shouldn't be able to support payment: %v", err) @@ -2105,7 +2160,7 @@ func runRouteFailDisabledEdge(t *testing.T, useCache bool) { _, err = dbFindPath( graph.graph, nil, &mockBandwidthHints{}, noRestrictions, testPathFindingConfig, - sourceNode.PubKeyBytes, target, payAmt, 0, + sourceNode.PubKeyBytes, target, payAmt, 0, 0, ) if err != nil { t.Fatalf("unable to find path: %v", err) @@ -2131,7 +2186,7 @@ func runRouteFailDisabledEdge(t *testing.T, useCache bool) { _, err = dbFindPath( graph.graph, nil, &mockBandwidthHints{}, noRestrictions, testPathFindingConfig, - sourceNode.PubKeyBytes, target, payAmt, 0, + sourceNode.PubKeyBytes, target, payAmt, 0, 0, ) if err != nil { t.Fatalf("unable to find path: %v", err) @@ -2154,7 +2209,7 @@ func runRouteFailDisabledEdge(t *testing.T, useCache bool) { _, err = dbFindPath( graph.graph, nil, &mockBandwidthHints{}, noRestrictions, testPathFindingConfig, - sourceNode.PubKeyBytes, target, payAmt, 0, + sourceNode.PubKeyBytes, target, payAmt, 0, 0, ) if err != errNoPathFound { t.Fatalf("graph shouldn't be able to support payment: %v", err) @@ -2184,7 +2239,7 @@ func runPathSourceEdgesBandwidth(t *testing.T, useCache bool) { path, err := dbFindPath( graph.graph, nil, &mockBandwidthHints{}, noRestrictions, testPathFindingConfig, - sourceNode.PubKeyBytes, target, payAmt, 0, + sourceNode.PubKeyBytes, target, payAmt, 0, 0, ) if err != nil { t.Fatalf("unable to find path: %v", err) @@ -2207,7 +2262,7 @@ func runPathSourceEdgesBandwidth(t *testing.T, useCache bool) { _, err = dbFindPath( graph.graph, nil, bandwidths, noRestrictions, testPathFindingConfig, - sourceNode.PubKeyBytes, target, payAmt, 0, + sourceNode.PubKeyBytes, target, payAmt, 0, 0, ) if err != errNoPathFound { t.Fatalf("graph shouldn't be able to support payment: %v", err) @@ -2222,7 +2277,7 @@ func runPathSourceEdgesBandwidth(t *testing.T, useCache bool) { path, err = dbFindPath( graph.graph, nil, bandwidths, noRestrictions, testPathFindingConfig, - sourceNode.PubKeyBytes, target, payAmt, 0, + sourceNode.PubKeyBytes, target, payAmt, 0, 0, ) if err != nil { t.Fatalf("unable to find path: %v", err) @@ -2250,7 +2305,7 @@ func runPathSourceEdgesBandwidth(t *testing.T, useCache bool) { path, err = dbFindPath( graph.graph, nil, bandwidths, noRestrictions, testPathFindingConfig, - sourceNode.PubKeyBytes, target, payAmt, 0, + sourceNode.PubKeyBytes, target, payAmt, 0, 0, ) if err != nil { t.Fatalf("unable to find path: %v", err) @@ -2296,7 +2351,7 @@ func TestPathFindSpecExample(t *testing.T) { carol := ctx.aliases["C"] const amt lnwire.MilliSatoshi = 4999999 route, err := ctx.router.FindRoute( - bobNode.PubKeyBytes, carol, amt, noRestrictions, nil, nil, + bobNode.PubKeyBytes, carol, amt, 0, noRestrictions, nil, nil, MinCLTVDelta, ) if err != nil { @@ -2352,7 +2407,7 @@ func TestPathFindSpecExample(t *testing.T) { // We'll now request a route from A -> B -> C. route, err = ctx.router.FindRoute( - source.PubKeyBytes, carol, amt, noRestrictions, nil, nil, + source.PubKeyBytes, carol, amt, 0, noRestrictions, nil, nil, MinCLTVDelta, ) if err != nil { @@ -2686,6 +2741,7 @@ func runProbabilityRouting(t *testing.T, useCache bool) { minProbability float64 expectedChan uint64 amount btcutil.Amount + timePref float64 }{ // Test two variations with probabilities that should multiply // to the same total route probability. In both cases the three @@ -2709,6 +2765,18 @@ func runProbabilityRouting(t *testing.T, useCache bool) { amount: 100, }, + // If we increase the time preference, we expect the algorithm + // to choose - with everything else being equal - the more + // expensive, more reliable route. + { + name: "three hop timepref", + p10: 0.5, p11: 0.8, p20: 0.7, + minProbability: 0.1, + expectedChan: 20, + amount: 100, + timePref: 1, + }, + // If a larger amount is sent, the effect of the proportional // attempt cost becomes more noticeable. This amount in this // test brings the attempt cost to 9 + 1% * 300 = 12 sat. The @@ -2765,7 +2833,8 @@ func runProbabilityRouting(t *testing.T, useCache bool) { t.Run(tc.name, func(t *testing.T) { testProbabilityRouting( - t, useCache, tc.amount, tc.p10, tc.p11, tc.p20, + t, useCache, tc.amount, tc.timePref, + tc.p10, tc.p11, tc.p20, tc.minProbability, tc.expectedChan, ) }) @@ -2773,7 +2842,8 @@ func runProbabilityRouting(t *testing.T, useCache bool) { } func testProbabilityRouting(t *testing.T, useCache bool, - paymentAmt btcutil.Amount, p10, p11, p20, minProbability float64, + paymentAmt btcutil.Amount, timePref float64, + p10, p11, p20, minProbability float64, expectedChan uint64) { t.Parallel() @@ -2834,6 +2904,8 @@ func testProbabilityRouting(t *testing.T, useCache bool, MinProbability: minProbability, } + ctx.timePref = timePref + path, err := ctx.findPath( target, lnwire.NewMSatFromSatoshis(paymentAmt), ) @@ -2861,7 +2933,7 @@ func runEqualCostRouteSelection(t *testing.T, useCache bool) { // Set up a test graph with two possible paths to the target: via a and // via b. The routing fees and probabilities are chosen such that the // algorithm will first explore target->a->source (backwards search). - // This route has fee 6 and a penality of 4 for the 25% success + // This route has fee 6 and a penalty of 4 for the 25% success // probability. The algorithm will then proceed with evaluating // target->b->source, which has a fee of 8 and a penalty of 2 for the // 50% success probability. Both routes have the same path finding cost @@ -3044,6 +3116,7 @@ type pathFindingTestContext struct { pathFindingConfig PathFindingConfig testGraphInstance *testGraphInstance source route.Vertex + timePref float64 } func newPathFindingTestContext(t *testing.T, useCache bool, @@ -3097,7 +3170,7 @@ func (c *pathFindingTestContext) findPath(target route.Vertex, return dbFindPath( c.graph, nil, c.bandwidthHints, &c.restrictParams, - &c.pathFindingConfig, c.source, target, amt, 0, + &c.pathFindingConfig, c.source, target, amt, c.timePref, 0, ) } @@ -3123,7 +3196,7 @@ func dbFindPath(graph *channeldb.ChannelGraph, additionalEdges map[route.Vertex][]*channeldb.CachedEdgePolicy, bandwidthHints bandwidthHints, r *RestrictParams, cfg *PathFindingConfig, - source, target route.Vertex, amt lnwire.MilliSatoshi, + source, target route.Vertex, amt lnwire.MilliSatoshi, timePref float64, finalHtlcExpiry int32) ([]*channeldb.CachedEdgePolicy, error) { sourceNode, err := graph.SourceNode() @@ -3148,6 +3221,6 @@ func dbFindPath(graph *channeldb.ChannelGraph, bandwidthHints: bandwidthHints, graph: routingGraph, }, - r, cfg, source, target, amt, finalHtlcExpiry, + r, cfg, source, target, amt, timePref, finalHtlcExpiry, ) } diff --git a/routing/payment_lifecycle.go b/routing/payment_lifecycle.go index 945a534666..f8b3cd5586 100644 --- a/routing/payment_lifecycle.go +++ b/routing/payment_lifecycle.go @@ -5,7 +5,7 @@ import ( "sync" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/davecgh/go-spew/spew" sphinx "github.com/lightningnetwork/lightning-onion" "github.com/lightningnetwork/lnd/channeldb" @@ -180,7 +180,6 @@ lifecycle: // TODO(yy): sanity check all the states to make sure // everything is expected. switch { - // We have a terminal condition and no active shards, we are // ready to exit. case currentState.terminated(): @@ -470,7 +469,6 @@ type shardResult struct { // given HTLC attempt to be available then handle its result. It will fail the // payment with the control tower if a terminal error is encountered. func (p *shardHandler) collectResultAsync(attempt *channeldb.HTLCAttemptInfo) { - // errToSend is the error to be sent to sh.shardErrors. var errToSend error @@ -557,7 +555,6 @@ func (p *shardHandler) collectResult(attempt *channeldb.HTLCAttemptInfo) ( attempt.AttemptID, p.identifier, errorDecryptor, ) switch { - // If this attempt ID is unknown to the Switch, it means it was never // checkpointed and forwarded by the switch before a restart. In this // case we can safely send a new payment attempt, and wait for its @@ -600,9 +597,6 @@ func (p *shardHandler) collectResult(attempt *channeldb.HTLCAttemptInfo) ( case <-p.router.quit: return nil, ErrRouterShuttingDown - - case <-p.quit: - return nil, errShardHandlerExiting } // In case of a payment failure, fail the attempt with the control @@ -669,9 +663,9 @@ func (p *shardHandler) createNewPaymentAttempt(rt *route.Route, lastShard bool) return lnwire.ShortChannelID{}, nil, nil, err } - // Requesst a new shard from the ShardTracker. If this is an AMP + // Request a new shard from the ShardTracker. If this is an AMP // payment, and this is the last shard, the outstanding shards together - // with ths one will be enough for the receiver to derive all HTLC + // with this one will be enough for the receiver to derive all HTLC // preimages. If this a non-AMP payment, the ShardTracker will return a // simple shard with the payment's static payment hash. shard, err := p.shardTracker.NewShard(attemptID, lastShard) @@ -780,8 +774,8 @@ func (p *shardHandler) handleSendError(attempt *channeldb.HTLCAttemptInfo, // Fail the payment via control tower. if err := p.router.cfg.Control.Fail( - p.identifier, *reason); err != nil { - + p.identifier, *reason, + ); err != nil { log.Errorf("unable to report failure to control "+ "tower: %v", err) @@ -886,9 +880,7 @@ func (p *shardHandler) handleFailureMessage(rt *route.Route, // always succeed, otherwise there is something wrong in our // implementation. Therefore return an error. errVertex := rt.Hops[errorSourceIdx-1].PubKeyBytes - errSource, err := btcec.ParsePubKey( - errVertex[:], btcec.S256(), - ) + errSource, err := btcec.ParsePubKey(errVertex[:]) if err != nil { log.Errorf("Cannot parse pubkey: idx=%v, pubkey=%v", errorSourceIdx, errVertex) @@ -968,7 +960,6 @@ func marshallError(sendError error, time time.Time) *channeldb.HTLCFailInfo { } switch sendError { - case htlcswitch.ErrPaymentIDNotFound: response.Reason = channeldb.HTLCFailInternal return response diff --git a/routing/payment_lifecycle_test.go b/routing/payment_lifecycle_test.go index df8cbe4a08..0f4c08a8f3 100644 --- a/routing/payment_lifecycle_test.go +++ b/routing/payment_lifecycle_test.go @@ -7,7 +7,7 @@ import ( "testing" "time" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/clock" @@ -352,7 +352,7 @@ func TestRouterPaymentStateMachine(t *testing.T) { }, { // Tests that the router is able to handle the - // receieved payment result after a restart. + // received payment result after a restart. name: "single shot restart", steps: []string{ @@ -453,7 +453,7 @@ func testPaymentLifecycle(t *testing.T, test paymentLifecycleTestCase, chainView := newMockChainView(chain) // We set uo the use the following channels and a mock Payer to - // synchonize with the interaction to the Switch. + // synchronize with the interaction to the Switch. sendResult := make(chan error) paymentResult := make(chan *htlcswitch.PaymentResult) @@ -570,7 +570,6 @@ func testPaymentLifecycle(t *testing.T, test paymentLifecycleTestCase, } switch step { - case routerInitPayment: var args initArgs select { @@ -688,7 +687,7 @@ func testPaymentLifecycle(t *testing.T, test paymentLifecycleTestCase, // In this state we expect the router to call the // GetPaymentResult method, and we will respond with a - // terminal error, indiating the router should stop + // terminal error, indicating the router should stop // making payment attempts. case getPaymentResultTerminalFailure: failure := htlcswitch.NewForwardingError( @@ -934,7 +933,6 @@ func TestPaymentState(t *testing.T) { ) }) } - } // TestUpdatePaymentState checks that the method updatePaymentState updates the @@ -1071,7 +1069,6 @@ func TestUpdatePaymentState(t *testing.T) { ct.On("FetchPayment", paymentHash).Return( nil, dummyErr, ) - } else { // Otherwise we will return the payment. ct.On("FetchPayment", paymentHash).Return( @@ -1096,10 +1093,8 @@ func TestUpdatePaymentState(t *testing.T) { t, tc.expectedState, state, "state not updated as expected", ) - }) } - } func makeActiveAttempt(total, fee int) channeldb.HTLCAttempt { diff --git a/routing/payment_session.go b/routing/payment_session.go index 4d593113f7..f3092ee7b4 100644 --- a/routing/payment_session.go +++ b/routing/payment_session.go @@ -3,7 +3,7 @@ package routing import ( "fmt" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btclog" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/build" @@ -72,7 +72,7 @@ var ( DefaultShardMinAmt = lnwire.NewMSatFromSatoshis(10000) ) -// Error returns the string representation of the noRouteError +// Error returns the string representation of the noRouteError. func (e noRouteError) Error() string { switch e { case errNoTlvPayload: @@ -257,6 +257,7 @@ func (p *paymentSession) RequestRoute(maxAmt, feeLimit lnwire.MilliSatoshi, DestCustomRecords: p.payment.DestCustomRecords, DestFeatures: p.payment.DestFeatures, PaymentAddr: p.payment.PaymentAddr, + Metadata: p.payment.Metadata, } finalHtlcExpiry := int32(height) + int32(finalCltvDelta) @@ -304,7 +305,7 @@ func (p *paymentSession) RequestRoute(maxAmt, feeLimit lnwire.MilliSatoshi, }, restrictions, &p.pathFindingConfig, sourceVertex, p.payment.Target, - maxAmt, finalHtlcExpiry, + maxAmt, p.payment.TimePref, finalHtlcExpiry, ) // Close routing graph. @@ -388,6 +389,7 @@ func (p *paymentSession) RequestRoute(maxAmt, feeLimit lnwire.MilliSatoshi, cltvDelta: finalCltvDelta, records: p.payment.DestCustomRecords, paymentAddr: p.payment.PaymentAddr, + metadata: p.payment.Metadata, }, ) if err != nil { diff --git a/routing/payment_session_source.go b/routing/payment_session_source.go index 930d69e004..1a589fd4e3 100644 --- a/routing/payment_session_source.go +++ b/routing/payment_session_source.go @@ -1,7 +1,7 @@ package routing import ( - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" @@ -115,7 +115,7 @@ func RouteHintsToEdges(routeHints [][]zpay32.HopHint, target route.Vertex) ( endNode.AddPubKey(routeHint[i+1].NodeID) } else { targetPubKey, err := btcec.ParsePubKey( - target[:], btcec.S256(), + target[:], ) if err != nil { return nil, err diff --git a/routing/payment_session_test.go b/routing/payment_session_test.go index 11823d4c78..4eaa5af660 100644 --- a/routing/payment_session_test.go +++ b/routing/payment_session_test.go @@ -76,7 +76,6 @@ func TestValidateCLTVLimit(t *testing.T) { // TestUpdateAdditionalEdge checks that we can update the additional edges as // expected. func TestUpdateAdditionalEdge(t *testing.T) { - var ( testChannelID = uint64(12345) oldFeeBaseMSat = uint32(1000) @@ -213,6 +212,7 @@ func TestRequestRoute(t *testing.T) { session.pathFinder = func( g *graphParams, r *RestrictParams, cfg *PathFindingConfig, source, target route.Vertex, amt lnwire.MilliSatoshi, + timePref float64, finalHtlcExpiry int32) ([]*channeldb.CachedEdgePolicy, error) { // We expect find path to receive a cltv limit excluding the diff --git a/routing/probability_estimator.go b/routing/probability_estimator.go index 7cd2df1696..851cecf168 100644 --- a/routing/probability_estimator.go +++ b/routing/probability_estimator.go @@ -118,7 +118,6 @@ func (p *probabilityEstimator) getNodeProbability(now time.Time, for _, result := range results { switch { - // Weigh success with a constant high weight of 1. There is no // decay. Amt is never zero, so this clause is never executed // when result.SuccessAmt is zero. diff --git a/routing/probability_estimator_test.go b/routing/probability_estimator_test.go index 094c0550e8..3bfb0536a6 100644 --- a/routing/probability_estimator_test.go +++ b/routing/probability_estimator_test.go @@ -9,7 +9,7 @@ import ( ) const ( - // Define node identifiers + // Define node identifiers. node1 = 1 node2 = 2 node3 = 3 diff --git a/routing/result_interpretation.go b/routing/result_interpretation.go index 19c43e4a17..8c8829420e 100644 --- a/routing/result_interpretation.go +++ b/routing/result_interpretation.go @@ -190,7 +190,7 @@ func (i *interpretedResult) processPaymentOutcomeFinal( // from its predecessor. i.failPair(route, n-1) - // The other hops relayed corectly, so assign those pairs a + // The other hops relayed correctly, so assign those pairs a // success result. At this point, n >= 2. i.successPairRange(route, 0, n-2) diff --git a/routing/result_interpretation_test.go b/routing/result_interpretation_test.go index 954f079815..b9fcc68d14 100644 --- a/routing/result_interpretation_test.go +++ b/routing/result_interpretation_test.go @@ -6,7 +6,6 @@ import ( "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/routing/route" ) diff --git a/routing/route/route.go b/routing/route/route.go index 63944af18a..5992bd4046 100644 --- a/routing/route/route.go +++ b/routing/route/route.go @@ -10,7 +10,7 @@ import ( "strconv" "strings" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" sphinx "github.com/lightningnetwork/lightning-onion" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/record" @@ -127,6 +127,10 @@ type Hop struct { // understand the new TLV payload, so we must instead use the legacy // payload. LegacyPayload bool + + // Metadata is additional data that is sent along with the payment to + // the payee. + Metadata []byte } // Copy returns a deep copy of the Hop. @@ -205,6 +209,13 @@ func (h *Hop) PackHopPayload(w io.Writer, nextChanID uint64) error { } } + // If metadata is specified, generate a tlv record for it. + if h.Metadata != nil { + records = append(records, + record.NewMetadataRecord(&h.Metadata), + ) + } + // Append any custom types destined for this hop. tlvRecords := tlv.MapToRecords(h.CustomRecords) records = append(records, tlvRecords...) @@ -259,6 +270,11 @@ func (h *Hop) PayloadSize(nextChanID uint64) uint64 { addRecord(record.AMPOnionType, h.AMP.PayloadSize()) } + // Add metadata if present. + if h.Metadata != nil { + addRecord(record.MetadataOnionType, uint64(len(h.Metadata))) + } + // Add custom records. for k, v := range h.CustomRecords { addRecord(tlv.Type(k), uint64(len(v))) @@ -404,9 +420,7 @@ func (r *Route) ToSphinxPath() (*sphinx.PaymentPath, error) { // to an OnionHop with matching per-hop payload within the path as used // by the sphinx package. for i, hop := range r.Hops { - pub, err := btcec.ParsePubKey( - hop.PubKeyBytes[:], btcec.S256(), - ) + pub, err := btcec.ParsePubKey(hop.PubKeyBytes[:]) if err != nil { return nil, err } diff --git a/routing/route/route_test.go b/routing/route/route_test.go index 991175f493..bf5f5c0f41 100644 --- a/routing/route/route_test.go +++ b/routing/route/route_test.go @@ -5,14 +5,14 @@ import ( "encoding/hex" "testing" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/record" ) var ( testPrivKeyBytes, _ = hex.DecodeString("e126f68f7eafcc8b74f54d269fe206be715000f94dac067d1c04a8ca3b2db734") - _, testPubKey = btcec.PrivKeyFromBytes(btcec.S256(), testPrivKeyBytes) + _, testPubKey = btcec.PrivKeyFromBytes(testPrivKeyBytes) testPubKeyBytes, _ = NewVertexFromBytes(testPubKey.SerializeCompressed()) ) @@ -186,6 +186,7 @@ func TestPayloadSize(t *testing.T) { 100000: {1, 2, 3}, 1000000: {4, 5}, }, + Metadata: []byte{10, 11}, }, } diff --git a/routing/router.go b/routing/router.go index 5baf6eccc2..71e72c3d18 100644 --- a/routing/router.go +++ b/routing/router.go @@ -10,12 +10,11 @@ import ( "sync/atomic" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/davecgh/go-spew/spew" "github.com/go-errors/errors" - sphinx "github.com/lightningnetwork/lightning-onion" "github.com/lightningnetwork/lnd/amp" "github.com/lightningnetwork/lnd/batch" @@ -157,11 +156,6 @@ type ChannelGraphSource interface { // ForEachNode is used to iterate over every node in the known graph. ForEachNode(func(node *channeldb.LightningNode) error) error - - // ForEachChannel is used to iterate over every channel in the known - // graph. - ForEachChannel(func(chanInfo *channeldb.ChannelEdgeInfo, - e1, e2 *channeldb.ChannelEdgePolicy) error) error } // PaymentAttemptDispatcher is used by the router to send payment attempts onto @@ -200,7 +194,7 @@ type PaymentAttemptDispatcher interface { } // PaymentSessionSource is an interface that defines a source for the router to -// retrive new payment sessions. +// retrieve new payment sessions. type PaymentSessionSource interface { // NewPaymentSession creates a new payment session that will produce // routes to the given target. An optional set of routing hints can be @@ -496,7 +490,7 @@ func (r *ChannelRouter) Start() error { return nil } - log.Tracef("Channel Router starting") + log.Info("Channel Router starting") bestHash, bestHeight, err := r.cfg.Chain.GetBestBlock() if err != nil { @@ -1062,13 +1056,19 @@ func (r *ChannelRouter) networkHandler() { update.msg, ) if err != nil { - switch err { - case ErrVBarrierShuttingDown: + switch { + case IsError( + err, ErrVBarrierShuttingDown, + ): update.err <- err - case ErrParentValidationFailed: + + case IsError( + err, ErrParentValidationFailed, + ): update.err <- newErrf( ErrIgnored, err.Error(), ) + default: log.Warnf("unexpected error "+ "during validation "+ @@ -1732,7 +1732,8 @@ type routingMsg struct { // particular target destination to which it is able to send `amt` after // factoring in channel capacities and cumulative fees along the route. func (r *ChannelRouter) FindRoute(source, target route.Vertex, - amt lnwire.MilliSatoshi, restrictions *RestrictParams, + amt lnwire.MilliSatoshi, timePref float64, + restrictions *RestrictParams, destCustomRecords record.CustomSet, routeHints map[route.Vertex][]*channeldb.CachedEdgePolicy, finalExpiry uint16) (*route.Route, error) { @@ -1759,6 +1760,11 @@ func (r *ChannelRouter) FindRoute(source, target route.Vertex, // execute our path finding algorithm. finalHtlcExpiry := currentHeight + int32(finalExpiry) + // Validate time preference. + if timePref < -1 || timePref > 1 { + return nil, errors.New("time preference out of range") + } + path, err := findPath( &graphParams{ additionalEdges: routeHints, @@ -1767,7 +1773,7 @@ func (r *ChannelRouter) FindRoute(source, target route.Vertex, }, restrictions, &r.cfg.PathFindingConfig, - source, target, amt, finalHtlcExpiry, + source, target, amt, timePref, finalHtlcExpiry, ) if err != nil { return nil, err @@ -1803,7 +1809,7 @@ func generateNewSessionKey() (*btcec.PrivateKey, error) { // any replay. // // TODO(roasbeef): add more sources of randomness? - return btcec.NewPrivateKey(btcec.S256()) + return btcec.NewPrivateKey() } // generateSphinxPacket generates then encodes a sphinx packet which encodes @@ -1827,7 +1833,6 @@ func generateSphinxPacket(rt *route.Route, paymentHash []byte, path := make([]sphinx.OnionHop, sphinxPath.TrueRouteLength()) for i := range path { hopCopy := sphinxPath[i] - hopCopy.NodePub.Curve = nil path[i] = hopCopy } return spew.Sdump(path) @@ -1857,7 +1862,6 @@ func generateSphinxPacket(rt *route.Route, paymentHash []byte, // internal curve here in order to keep the logs from // getting noisy. key := *sphinxPacket.EphemeralKey - key.Curve = nil packetCopy := *sphinxPacket packetCopy.EphemeralKey = &key return spew.Sdump(packetCopy) @@ -1962,6 +1966,15 @@ type LightningPayment struct { // // NOTE: This field is _optional_. MaxShardAmt *lnwire.MilliSatoshi + + // TimePref is the time preference for this payment. Set to -1 to + // optimize for fees only, to 1 to optimize for reliability only or a + // value in between for a mix. + TimePref float64 + + // Metadata is additional data that is sent along with the payment to + // the payee. + Metadata []byte } // AMPOptions houses information that must be known in order to send an AMP @@ -2071,7 +2084,6 @@ func spewPayment(payment *LightningPayment) logClosure { var hopHints []zpay32.HopHint for _, hopHint := range routeHint { h := hopHint.Copy() - h.NodeID.Curve = nil hopHints = append(hopHints, h) } routeHints = append(routeHints, hopHints) @@ -2110,7 +2122,6 @@ func (r *ChannelRouter) preparePayment(payment *LightningPayment) ( // this payment. var shardTracker shards.ShardTracker switch { - // If this is an AMP payment, we'll use the AMP shard tracker. case payment.amp != nil: shardTracker = amp.NewShardTracker( @@ -2535,16 +2546,6 @@ func (r *ChannelRouter) ForAllOutgoingChannels(cb func(kvdb.RTx, }) } -// ForEachChannel is used to iterate over every known edge (channel) within our -// view of the channel graph. -// -// NOTE: This method is part of the ChannelGraphSource interface. -func (r *ChannelRouter) ForEachChannel(cb func(chanInfo *channeldb.ChannelEdgeInfo, - e1, e2 *channeldb.ChannelEdgePolicy) error) error { - - return r.cfg.Graph.ForEachChannel(cb) -} - // AddProof updates the channel edge info with proof which is needed to // properly announce the edge to the rest of the network. // @@ -2588,7 +2589,7 @@ func (r *ChannelRouter) IsKnownEdge(chanID lnwire.ShortChannelID) bool { return exists || isZombie } -// IsStaleEdgePolicy returns true if the graph soruce has a channel edge for +// IsStaleEdgePolicy returns true if the graph source has a channel edge for // the passed channel ID (and flags) that have a more recent timestamp. // // NOTE: This method is part of the ChannelGraphSource interface. diff --git a/routing/router_test.go b/routing/router_test.go index 34bfcac055..ff9d14a410 100644 --- a/routing/router_test.go +++ b/routing/router_test.go @@ -10,14 +10,12 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/davecgh/go-spew/spew" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/clock" @@ -29,6 +27,8 @@ import ( "github.com/lightningnetwork/lnd/record" "github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/zpay32" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" ) var uniquePaymentID uint64 = 1 // to be used atomically @@ -233,8 +233,7 @@ func signErrChanUpdate(t *testing.T, key *btcec.PrivateKey, require.NoError(t, err, "failed to retrieve data to sign") digest := chainhash.DoubleHashB(chanUpdateMsg) - sig, err := key.Sign(digest) - require.NoError(t, err, "failed to sign msg") + sig := ecdsa.Sign(key, digest) errChanUpdate.Signature, err = lnwire.NewSigFromSignature(sig) require.NoError(t, err, "failed to create new signature") @@ -269,7 +268,7 @@ func TestFindRoutesWithFeeLimit(t *testing.T) { route, err := ctx.router.FindRoute( ctx.router.selfNode.PubKeyBytes, - target, paymentAmt, restrictions, nil, nil, + target, paymentAmt, 0, restrictions, nil, nil, MinCLTVDelta, ) require.NoError(t, err, "unable to find any routes") @@ -630,7 +629,7 @@ func TestSendPaymentErrorFeeInsufficientPrivateEdge(t *testing.T) { sgNode = ctx.aliases["songoku"] ) - sgNodeID, err := btcec.ParsePubKey(sgNode[:], btcec.S256()) + sgNodeID, err := btcec.ParsePubKey(sgNode[:]) require.NoError(t, err) // Craft a LightningPayment struct that'll send a payment from roasbeef @@ -671,7 +670,6 @@ func TestSendPaymentErrorFeeInsufficientPrivateEdge(t *testing.T) { copy(preImage[:], bytes.Repeat([]byte{9}, 32)) ctx.router.cfg.Payer.(*mockPaymentAttemptDispatcherOld).setPaymentResult( func(firstHop lnwire.ShortChannelID) ([32]byte, error) { - if firstHop != roasbeefSongoku || errorReturned { return preImage, nil } @@ -686,7 +684,8 @@ func TestSendPaymentErrorFeeInsufficientPrivateEdge(t *testing.T) { Update: errChanUpdate, }, 1, ) - }) + }, + ) // Send off the payment request to the router, route through son // goku and then across the private channel to elst. @@ -764,7 +763,7 @@ func TestSendPaymentPrivateEdgeUpdateFeeExceedsLimit(t *testing.T) { feeLimit = lnwire.MilliSatoshi(500000) ) - sgNodeID, err := btcec.ParsePubKey(sgNode[:], btcec.S256()) + sgNodeID, err := btcec.ParsePubKey(sgNode[:]) require.NoError(t, err) // Craft a LightningPayment struct that'll send a payment from roasbeef @@ -803,7 +802,6 @@ func TestSendPaymentPrivateEdgeUpdateFeeExceedsLimit(t *testing.T) { copy(preImage[:], bytes.Repeat([]byte{9}, 32)) ctx.router.cfg.Payer.(*mockPaymentAttemptDispatcherOld).setPaymentResult( func(firstHop lnwire.ShortChannelID) ([32]byte, error) { - if firstHop != roasbeefSongoku || errorReturned { return preImage, nil } @@ -818,7 +816,8 @@ func TestSendPaymentPrivateEdgeUpdateFeeExceedsLimit(t *testing.T) { Update: errChanUpdate, }, 1, ) - }) + }, + ) // Send off the payment request to the router, route through son // goku and then across the private channel to elst. @@ -858,7 +857,7 @@ func TestSendPaymentPrivateEdgeUpdateFeeExceedsLimit(t *testing.T) { // TestSendPaymentErrorNonFinalTimeLockErrors tests that if we receive either // an ExpiryTooSoon or a IncorrectCltvExpiry error from a node, then we prune -// that node from the available graph witin a mission control session. This +// that node from the available graph within a mission control session. This // test ensures that we'll route around errors due to nodes not knowing the // current block height. func TestSendPaymentErrorNonFinalTimeLockErrors(t *testing.T) { @@ -1440,7 +1439,7 @@ func TestAddEdgeUnknownVertexes(t *testing.T) { // We will connect node 1 to "sophon" connectNode := ctx.aliases["sophon"] - connectNodeKey, err := btcec.ParsePubKey(connectNode[:], btcec.S256()) + connectNodeKey, err := btcec.ParsePubKey(connectNode[:]) if err != nil { t.Fatal(err) } @@ -1526,7 +1525,7 @@ func TestAddEdgeUnknownVertexes(t *testing.T) { copy(targetPubKeyBytes[:], targetNode.SerializeCompressed()) _, err = ctx.router.FindRoute( ctx.router.selfNode.PubKeyBytes, - targetPubKeyBytes, paymentAmt, noRestrictions, nil, nil, + targetPubKeyBytes, paymentAmt, 0, noRestrictions, nil, nil, MinCLTVDelta, ) if err != nil { @@ -1569,7 +1568,7 @@ func TestAddEdgeUnknownVertexes(t *testing.T) { // updated. _, err = ctx.router.FindRoute( ctx.router.selfNode.PubKeyBytes, - targetPubKeyBytes, paymentAmt, noRestrictions, nil, nil, + targetPubKeyBytes, paymentAmt, 0, noRestrictions, nil, nil, MinCLTVDelta, ) if err != nil { @@ -2466,7 +2465,7 @@ func TestFindPathFeeWeighting(t *testing.T) { ctx.graph, nil, &mockBandwidthHints{}, noRestrictions, testPathFindingConfig, - sourceNode.PubKeyBytes, target, amt, 0, + sourceNode.PubKeyBytes, target, amt, 0, 0, ) if err != nil { t.Fatalf("unable to find path: %v", err) @@ -2719,7 +2718,7 @@ func TestIsStaleEdgePolicy(t *testing.T) { func TestEmptyRoutesGenerateSphinxPacket(t *testing.T) { t.Parallel() - sessionKey, _ := btcec.NewPrivateKey(btcec.S256()) + sessionKey, _ := btcec.NewPrivateKey() emptyRoute := &route.Route{} _, _, err := generateSphinxPacket(emptyRoute, testHash[:], sessionKey) if err != route.ErrNoRouteHopsProvided { @@ -3072,7 +3071,6 @@ func TestSendToRouteMultiShardSend(t *testing.T) { ctx.router.cfg.Payer.(*mockPaymentAttemptDispatcherOld).setPaymentResult( func(firstHop lnwire.ShortChannelID) ([32]byte, error) { - // Signal that the shard has been initiated and is // waiting for a result. waitForResultSignal <- struct{}{} @@ -3083,7 +3081,8 @@ func TestSendToRouteMultiShardSend(t *testing.T) { return [32]byte{}, fmt.Errorf("failure") } return res, nil - }) + }, + ) // Launch three shards by calling SendToRoute in three goroutines, // returning their final error on the channel. @@ -3823,7 +3822,6 @@ func TestSendMPPaymentSucceedOnExtraShards(t *testing.T) { payment.HTLCs[i] = attempt return } - }) // Setup ReportPaymentFail to return nil reason and error so the @@ -4007,7 +4005,6 @@ func TestSendMPPaymentFailed(t *testing.T) { ), } return - } // We will make the rest attempts failed with temporary error. @@ -4036,7 +4033,6 @@ func TestSendMPPaymentFailed(t *testing.T) { payment.HTLCs[i] = attempt return } - }) // Setup ReportPaymentFail to return nil reason and error so the @@ -4187,10 +4183,14 @@ func TestSendMPPaymentFailedWithShardsInFlight(t *testing.T) { // Create a buffered chan and it will be returned by GetPaymentResult. payer.resultChan = make(chan *htlcswitch.PaymentResult, 10) - // We use the failAttemptCount to track how many attempts we want to - // fail. Each time the following mock method is called, the count gets - // updated. - failAttemptCount := 0 + // We use the getPaymentResultCnt to track how many times we called + // GetPaymentResult. As shard launch is sequential, and we fail the + // first shard that calls GetPaymentResult, we may end up with different + // counts since the lifecycle itself is asynchronous. To avoid flakes + // due to this undeterminsitic behavior, we'll compare the final + // getPaymentResultCnt with other counters to create a final test + // expectation. + getPaymentResultCnt := 0 payer.On("GetPaymentResult", mock.Anything, identifier, mock.Anything, ).Run(func(args mock.Arguments) { @@ -4198,10 +4198,10 @@ func TestSendMPPaymentFailedWithShardsInFlight(t *testing.T) { // the read-only chan. // Update the counter. - failAttemptCount++ + getPaymentResultCnt++ // We fail the first attempt with terminal error. - if failAttemptCount == 1 { + if getPaymentResultCnt == 1 { payer.resultChan <- &htlcswitch.PaymentResult{ Error: htlcswitch.NewForwardingError( &lnwire.FailIncorrectDetails{}, @@ -4209,15 +4209,20 @@ func TestSendMPPaymentFailedWithShardsInFlight(t *testing.T) { ), } return - } - // For the rest attempts we will NOT send anything to the - // resultChan, thus making all the shards in active state, - // neither settled or failed. + // For the rest of the attempts we'll simulate that a network + // result update_fail_htlc has been received. This way the + // payment will fail cleanly. + payer.resultChan <- &htlcswitch.PaymentResult{ + Error: htlcswitch.NewForwardingError( + &lnwire.FailTemporaryChannelFailure{}, + 1, + ), + } }) - // Mock the FailAttempt method to fail EXACTLY once. + // Mock the FailAttempt method to fail (at least once). var failedAttempt channeldb.HTLCAttempt controlTower.On("FailAttempt", identifier, mock.Anything, mock.Anything, @@ -4227,22 +4232,27 @@ func TestSendMPPaymentFailedWithShardsInFlight(t *testing.T) { failedAttempt = payment.HTLCs[0] failedAttempt.Failure = &channeldb.HTLCFailInfo{} payment.HTLCs[0] = failedAttempt - }).Once() + }) // Setup ReportPaymentFail to return nil reason and error so the // payment won't fail. failureReason := channeldb.FailureReasonPaymentDetails + cntReportPaymentFail := 0 missionControl.On("ReportPaymentFail", mock.Anything, mock.Anything, mock.Anything, mock.Anything, ).Return(&failureReason, nil).Run(func(args mock.Arguments) { payment.FailureReason = &failureReason - }).Once() + cntReportPaymentFail++ + }) // Simple mocking the rest. - controlTower.On("Fail", identifier, failureReason).Return(nil).Once() + cntFail := 0 + controlTower.On("Fail", identifier, failureReason).Return(nil) payer.On("SendHTLC", mock.Anything, mock.Anything, mock.Anything, - ).Return(nil) + ).Return(nil).Run(func(args mock.Arguments) { + cntFail++ + }) // Call the actual method SendPayment on router. This is place inside a // goroutine so we can set a timeout for the whole test, in case @@ -4264,6 +4274,9 @@ func TestSendMPPaymentFailedWithShardsInFlight(t *testing.T) { // methods are called as expected. require.Error(t, err, "expected send payment error") require.EqualValues(t, [32]byte{}, p, "preimage not match") + require.GreaterOrEqual(t, getPaymentResultCnt, 1) + require.Equal(t, getPaymentResultCnt, cntReportPaymentFail) + require.Equal(t, getPaymentResultCnt, cntFail) controlTower.AssertExpectations(t) payer.AssertExpectations(t) diff --git a/routing/setup_test.go b/routing/setup_test.go new file mode 100644 index 0000000000..544fc3885a --- /dev/null +++ b/routing/setup_test.go @@ -0,0 +1,11 @@ +package routing + +import ( + "testing" + + "github.com/lightningnetwork/lnd/kvdb" +) + +func TestMain(m *testing.M) { + kvdb.RunTests(m) +} diff --git a/routing/shards/shard_tracker.go b/routing/shards/shard_tracker.go index a41461eb4d..a7c0e9ea27 100644 --- a/routing/shards/shard_tracker.go +++ b/routing/shards/shard_tracker.go @@ -24,7 +24,7 @@ type PaymentShard interface { AMP() *record.AMP } -// ShardTracker is an interfae representing a tracker that keeps track of the +// ShardTracker is an interface representing a tracker that keeps track of the // inflight shards of a payment, and is able to assign new shards the correct // options such as hash and extra records. type ShardTracker interface { @@ -41,11 +41,11 @@ type ShardTracker interface { CancelShard(uint64) error // GetHash retrieves the hash used by the shard of the given attempt - // ID. This wil return an error if the attempt ID is unknown. + // ID. This will return an error if the attempt ID is unknown. GetHash(uint64) (lntypes.Hash, error) } -// Shard is a struct used for simple shards where we obly need to keep map it +// Shard is a struct used for simple shards where we only need to keep map it // to a single hash. type Shard struct { hash lntypes.Hash @@ -82,7 +82,7 @@ type SimpleShardTracker struct { // ShardTracker interface. var _ ShardTracker = (*SimpleShardTracker)(nil) -// NewSimpleShardTracker creates a new intance of the SimpleShardTracker with +// NewSimpleShardTracker creates a new instance of the SimpleShardTracker with // the given payment hash and existing attempts. func NewSimpleShardTracker(paymentHash lntypes.Hash, shards map[uint64]lntypes.Hash) ShardTracker { diff --git a/routing/shards/shard_tracker_test.go b/routing/shards/shard_tracker_test.go index b755e91fc5..5515750dc5 100644 --- a/routing/shards/shard_tracker_test.go +++ b/routing/shards/shard_tracker_test.go @@ -28,7 +28,7 @@ func TestSimpleShardTracker(t *testing.T) { _, err := tracker.GetHash(0) require.Error(t, err) - // Getting id 1 should workd. + // Getting id 1 should work. hash1, err := tracker.GetHash(1) require.NoError(t, err) diff --git a/routing/unified_policies.go b/routing/unified_policies.go index b54689e134..ec7e594d59 100644 --- a/routing/unified_policies.go +++ b/routing/unified_policies.go @@ -1,7 +1,7 @@ package routing import ( - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" diff --git a/routing/validation_barrier.go b/routing/validation_barrier.go index c8c5e7b340..80866acd50 100644 --- a/routing/validation_barrier.go +++ b/routing/validation_barrier.go @@ -1,7 +1,6 @@ package routing import ( - "errors" "sync" "github.com/lightningnetwork/lnd/channeldb" @@ -9,17 +8,6 @@ import ( "github.com/lightningnetwork/lnd/routing/route" ) -var ( - // ErrVBarrierShuttingDown signals that the barrier has been requested - // to shutdown, and that the caller should not treat the wait condition - // as fulfilled. - ErrVBarrierShuttingDown = errors.New("validation barrier shutting down") - - // ErrParentValidationFailed signals that the validation of a - // dependent's parent failed, so the dependent must not be processed. - ErrParentValidationFailed = errors.New("parent validation failed") -) - // validationSignals contains two signals which allows the ValidationBarrier to // communicate back to the caller whether a dependent should be processed or not // based on whether its parent was successfully validated. Only one of these @@ -40,7 +28,7 @@ type validationSignals struct { // validate the item on the left of the arrow before that on the right. type ValidationBarrier struct { // validationSemaphore is a channel of structs which is used as a - // sempahore. Initially we'll fill this with a buffered channel of the + // semaphore. Initially we'll fill this with a buffered channel of the // size of the number of active requests. Each new job will consume // from this channel, then restore the value upon completion. validationSemaphore chan struct{} @@ -80,7 +68,7 @@ func NewValidationBarrier(numActiveReqs int, quit: quitChan, } - // We'll first initialize a set of sempahores to limit our concurrency + // We'll first initialize a set of semaphores to limit our concurrency // when validating incoming requests in parallel. v.validationSemaphore = make(chan struct{}, numActiveReqs) for i := 0; i < numActiveReqs; i++ { @@ -228,9 +216,11 @@ func (v *ValidationBarrier) WaitForDependants(job interface{}) error { if ok { select { case <-v.quit: - return ErrVBarrierShuttingDown + return newErrf(ErrVBarrierShuttingDown, + "validation barrier shutting down") case <-signals.deny: - return ErrParentValidationFailed + return newErrf(ErrParentValidationFailed, + "parent validation failed") case <-signals.allow: return nil } diff --git a/routing/validation_barrier_test.go b/routing/validation_barrier_test.go index 248c1dc201..2eda0120fc 100644 --- a/routing/validation_barrier_test.go +++ b/routing/validation_barrier_test.go @@ -141,14 +141,18 @@ func TestValidationBarrierQuit(t *testing.T) { switch { // First half should return without failure. - case i < numTasks/4 && err != routing.ErrParentValidationFailed: + case i < numTasks/4 && !routing.IsError( + err, routing.ErrParentValidationFailed, + ): t.Fatalf("unexpected failure while waiting: %v", err) case i >= numTasks/4 && i < numTasks/2 && err != nil: t.Fatalf("unexpected failure while waiting: %v", err) // Last half should return the shutdown error. - case i >= numTasks/2 && err != routing.ErrVBarrierShuttingDown: + case i >= numTasks/2 && !routing.IsError( + err, routing.ErrVBarrierShuttingDown, + ): t.Fatalf("expected failure after quitting: want %v, "+ "got %v", routing.ErrVBarrierShuttingDown, err) } diff --git a/rpcperms/interceptor.go b/rpcperms/interceptor.go index 97e752b175..0ec9e7d6e4 100644 --- a/rpcperms/interceptor.go +++ b/rpcperms/interceptor.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "sync" + "sync/atomic" "github.com/btcsuite/btclog" grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" @@ -23,7 +24,7 @@ type rpcState uint8 const ( // waitingToStart indicates that we're at the beginning of the startup - // process. In a cluster evironment this may mean that we're waiting to + // process. In a cluster environment this may mean that we're waiting to // become the leader in which case RPC calls will be disabled until // this instance has been elected as leader. waitingToStart rpcState = iota @@ -39,7 +40,7 @@ const ( walletLocked // walletUnlocked means that the wallet has been unlocked, but the full - // RPC server is not yeat ready. + // RPC server is not yet ready. walletUnlocked // rpcActive means that the RPC server is ready to accept calls. @@ -50,7 +51,7 @@ const ( ) var ( - // ErrWaitingToStart is returned if LND is still wating to start, + // ErrWaitingToStart is returned if LND is still waiting to start, // possibly blocked until elected as the leader. ErrWaitingToStart = fmt.Errorf("waiting to start, RPC services not " + "available") @@ -134,6 +135,12 @@ var ( // | edited gRPC request to client // v type InterceptorChain struct { + // lastRequestID is the ID of the last gRPC request or stream that was + // intercepted by the middleware interceptor. + // + // NOTE: Must be used atomically! + lastRequestID uint64 + // Required by the grpc-gateway/v2 library for forward compatibility. lnrpc.UnimplementedStateServer @@ -688,7 +695,6 @@ func (r *InterceptorChain) checkRPCState(srv interface{}) error { r.RUnlock() switch state { - // Do not accept any RPC calls (unless to the state service) until LND // has not started. case waitingToStart: @@ -790,7 +796,8 @@ func (r *InterceptorChain) middlewareUnaryServerInterceptor() grpc.UnaryServerIn return nil, err } - err = r.acceptRequest(msg) + requestID := atomic.AddUint64(&r.lastRequestID, 1) + err = r.acceptRequest(requestID, msg) if err != nil { return nil, err } @@ -800,7 +807,9 @@ func (r *InterceptorChain) middlewareUnaryServerInterceptor() grpc.UnaryServerIn return resp, respErr } - return r.interceptResponse(ctx, false, info.FullMethod, resp) + return r.interceptResponse( + ctx, requestID, false, info.FullMethod, resp, + ) } } @@ -845,13 +854,15 @@ func (r *InterceptorChain) middlewareStreamServerInterceptor() grpc.StreamServer return err } - err = r.acceptRequest(msg) + requestID := atomic.AddUint64(&r.lastRequestID, 1) + err = r.acceptRequest(requestID, msg) if err != nil { return err } wrappedSS := &serverStreamWrapper{ ServerStream: ss, + requestID: requestID, fullMethod: info.FullMethod, interceptor: r, } @@ -898,9 +909,11 @@ func (r *InterceptorChain) middlewareRegistered() bool { // acceptRequest sends an intercept request to all middlewares that have // registered for it. This means either a middleware has requested read-only -// access or the request actually has a macaroon which a caveat the middleware +// access or the request actually has a macaroon with a caveat the middleware // registered for. -func (r *InterceptorChain) acceptRequest(msg *InterceptionRequest) error { +func (r *InterceptorChain) acceptRequest(requestID uint64, + msg *InterceptionRequest) error { + r.RLock() defer r.RUnlock() @@ -915,7 +928,11 @@ func (r *InterceptorChain) acceptRequest(msg *InterceptionRequest) error { continue } - resp, err := middleware.intercept(msg) + msg.CustomCaveatCondition = macaroons.GetCustomCaveatCondition( + msg.Macaroon, middleware.customCaveatName, + ) + + resp, err := middleware.intercept(requestID, msg) // Error during interception itself. if err != nil { @@ -936,7 +953,8 @@ func (r *InterceptorChain) acceptRequest(msg *InterceptionRequest) error { // overwrite/replace the response, this needs to be handled differently than the // request/auth path above. func (r *InterceptorChain) interceptResponse(ctx context.Context, - isStream bool, fullMethod string, m interface{}) (interface{}, error) { + requestID uint64, isStream bool, fullMethod string, + m interface{}) (interface{}, error) { r.RLock() defer r.RUnlock() @@ -960,7 +978,11 @@ func (r *InterceptorChain) interceptResponse(ctx context.Context, continue } - resp, err := middleware.intercept(msg) + msg.CustomCaveatCondition = macaroons.GetCustomCaveatCondition( + msg.Macaroon, middleware.customCaveatName, + ) + + resp, err := middleware.intercept(requestID, msg) // Error during interception itself. if err != nil { @@ -988,6 +1010,8 @@ type serverStreamWrapper struct { // ServerStream is the stream that's being wrapped. grpc.ServerStream + requestID uint64 + fullMethod string interceptor *InterceptorChain @@ -997,7 +1021,7 @@ type serverStreamWrapper struct { // intercept streaming RPC responses. func (w *serverStreamWrapper) SendMsg(m interface{}) error { newMsg, err := w.interceptor.interceptResponse( - w.ServerStream.Context(), true, w.fullMethod, m, + w.ServerStream.Context(), w.requestID, true, w.fullMethod, m, ) if err != nil { return err @@ -1022,5 +1046,5 @@ func (w *serverStreamWrapper) RecvMsg(m interface{}) error { return err } - return w.interceptor.acceptRequest(msg) + return w.interceptor.acceptRequest(w.requestID, msg) } diff --git a/rpcperms/middleware_handler.go b/rpcperms/middleware_handler.go index f79810282f..4663bec8a2 100644 --- a/rpcperms/middleware_handler.go +++ b/rpcperms/middleware_handler.go @@ -37,11 +37,11 @@ var ( // whenever the custom business logic implemented there should give feedback to // a request or response that's happening on the main gRPC server. type MiddlewareHandler struct { - // lastRequestID is the ID of the last request that was forwarded to the - // middleware. + // lastMsgID is the ID of the last intercept message that was forwarded + // to the middleware. // // NOTE: Must be used atomically! - lastRequestID uint64 + lastMsgID uint64 middlewareName string @@ -83,7 +83,7 @@ func NewMiddlewareHandler(name, customCaveatName string, readOnly bool, log.Warnf("A new gRPC middleware with the name '%s' was registered "+ " with custom_macaroon_caveat='%s', read_only=%v. Make sure "+ "you trust the middleware author since that code will be able "+ - "to intercept and possibly modify and gRPC messages sent/"+ + "to intercept and possibly modify any gRPC messages sent/"+ "received to/from a client that has a macaroon with that "+ "custom caveat.", name, customCaveatName, readOnly) @@ -107,14 +107,15 @@ func NewMiddlewareHandler(name, customCaveatName string, readOnly bool, // feedback on it and sending the feedback to the appropriate channel. All steps // are guarded by the configured timeout to make sure a middleware cannot slow // down requests too much. -func (h *MiddlewareHandler) intercept( +func (h *MiddlewareHandler) intercept(requestID uint64, req *InterceptionRequest) (*interceptResponse, error) { respChan := make(chan *interceptResponse, 1) newRequest := &interceptRequest{ - request: req, - response: respChan, + requestID: requestID, + request: req, + response: respChan, } // timeout is the time after which intercept requests expire. @@ -228,12 +229,14 @@ func (h *MiddlewareHandler) sendInterceptRequests(errChan chan error, // Consume requests passed to us from our Accept() function and // send them into our stream. case newRequest := <-h.interceptRequests: - id := atomic.AddUint64(&h.lastRequestID, 1) + msgID := atomic.AddUint64(&h.lastMsgID, 1) req := newRequest.request - interceptRequests[id] = newRequest + interceptRequests[msgID] = newRequest - interceptReq, err := req.ToRPC(id) + interceptReq, err := req.ToRPC( + newRequest.requestID, msgID, + ) if err != nil { return err } @@ -246,7 +249,7 @@ func (h *MiddlewareHandler) sendInterceptRequests(errChan chan error, // looking the original request up in our map of requests and // dispatching the response. case resp := <-responses: - requestInfo, ok := interceptRequests[resp.RequestId] + requestInfo, ok := interceptRequests[resp.RefMsgId] if !ok { continue } @@ -290,7 +293,7 @@ func (h *MiddlewareHandler) sendInterceptRequests(errChan chan error, case <-h.quit: } - delete(interceptRequests, resp.RequestId) + delete(interceptRequests, resp.RefMsgId) // If we failed to receive from our middleware, we exit. case err := <-errChan: @@ -447,11 +450,12 @@ func macaroonFromContext(ctx context.Context) (*macaroon.Macaroon, []byte, } // ToRPC converts the interception request to its RPC counterpart. -func (r *InterceptionRequest) ToRPC(id uint64) (*lnrpc.RPCMiddlewareRequest, - error) { +func (r *InterceptionRequest) ToRPC(requestID, + msgID uint64) (*lnrpc.RPCMiddlewareRequest, error) { rpcRequest := &lnrpc.RPCMiddlewareRequest{ - RequestId: id, + RequestId: requestID, + MsgId: msgID, RawMacaroon: r.RawMacaroon, CustomCaveatCondition: r.CustomCaveatCondition, } @@ -495,8 +499,9 @@ func (r *InterceptionRequest) ToRPC(id uint64) (*lnrpc.RPCMiddlewareRequest, // out to a middleware and the response that is eventually sent back by the // middleware. type interceptRequest struct { - request *InterceptionRequest - response chan *interceptResponse + requestID uint64 + request *InterceptionRequest + response chan *interceptResponse } // interceptResponse is the response a middleware sends back for each diff --git a/rpcserver.go b/rpcserver.go index 9b15348abc..6f1ab8f915 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -8,6 +8,7 @@ import ( "fmt" "io" "math" + "net" "net/http" "runtime" "sort" @@ -18,13 +19,14 @@ import ( "time" "github.com/btcsuite/btcd/blockchain" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/psbt" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" - "github.com/btcsuite/btcutil/psbt" "github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/wallet/txauthor" "github.com/davecgh/go-spew/spew" @@ -255,7 +257,6 @@ func calculateFeeRate(satPerByte, satPerVByte uint64, targetConf uint32, } return feeRate, nil - } // GetAllPermissions returns all the permissions required to interact with lnd. @@ -282,7 +283,6 @@ func GetAllPermissions() []bakery.Op { allPerms, perm, ) } - } else { allPermsMap[entity] = make(map[string]struct{}) allPermsMap[entity][action] = struct{}{} @@ -628,6 +628,10 @@ type rpcServer struct { // interceptor is used to be able to request a shutdown interceptor signal.Interceptor + + graphCache sync.RWMutex + describeGraphResp *lnrpc.ChannelGraph + graphCacheEvictor *time.Timer } // A compile time check to ensure that rpcServer fully implements the @@ -726,6 +730,14 @@ func (r *rpcServer) addDeps(s *server, macService *macaroons.Service, return s.featureMgr.Get(feature.SetInvoiceAmp) } + getNodeAnnouncement := func() (lnwire.NodeAnnouncement, error) { + return s.genNodeAnnouncement(false) + } + + parseAddr := func(addr string) (net.Addr, error) { + return parseAddr(addr, r.cfg.net) + } + var ( subServers []lnrpc.SubServer subServerPerms []lnrpc.MacaroonPerms @@ -742,7 +754,8 @@ func (r *rpcServer) addDeps(s *server, macService *macaroons.Service, routerBackend, s.nodeSigner, s.graphDB, s.chanStateDB, s.sweeper, tower, s.towerClient, s.anchorTowerClient, r.cfg.net.ResolveTCPAddr, genInvoiceFeatures, - genAmpInvoiceFeatures, rpcsLog, + genAmpInvoiceFeatures, getNodeAnnouncement, + s.updateAndBrodcastSelfNode, parseAddr, rpcsLog, ) if err != nil { return err @@ -813,6 +826,23 @@ func (r *rpcServer) addDeps(s *server, macService *macaroons.Service, r.chanPredicate = chanPredicate r.macService = macService r.selfNode = selfNode.PubKeyBytes + + graphCacheDuration := r.cfg.Caches.RPCGraphCacheDuration + if graphCacheDuration != 0 { + r.graphCacheEvictor = time.AfterFunc(graphCacheDuration, func() { + // Grab the mutex and purge the current populated + // describe graph response. + r.graphCache.Lock() + defer r.graphCache.Unlock() + + r.describeGraphResp = nil + + // Reset ourselves as well at the end so we run again + // after the duration. + r.graphCacheEvictor.Reset(graphCacheDuration) + }) + } + return nil } @@ -1058,13 +1088,13 @@ func (r *rpcServer) sendCoinsOnChain(paymentMap map[string]int64, return &txHash, nil } -// ListUnspent returns useful information about each unspent output owned by the -// wallet, as reported by the underlying `ListUnspentWitness`; the information -// returned is: outpoint, amount in satoshis, address, address type, -// scriptPubKey in hex and number of confirmations. The result is filtered to -// contain outputs whose number of confirmations is between a minimum and -// maximum number of confirmations specified by the user, with 0 meaning -// unconfirmed. +// ListUnspent returns useful information about each unspent output owned by +// the wallet, as reported by the underlying `ListUnspentWitness`; the +// information returned is: outpoint, amount in satoshis, address, address +// type, scriptPubKey in hex and number of confirmations. The result is +// filtered to contain outputs whose number of confirmations is between a +// minimum and maximum number of confirmations specified by the user, with +// 0 meaning unconfirmed. func (r *rpcServer) ListUnspent(ctx context.Context, in *lnrpc.ListUnspentRequest) (*lnrpc.ListUnspentResponse, error) { @@ -1220,7 +1250,7 @@ func (r *rpcServer) SendCoins(ctx context.Context, // accidentally tried to send funds to a bare pubkey address. This check is // here to prevent unintended transfers. decodedAddr, _ := hex.DecodeString(in.Addr) - _, err = btcec.ParsePubKey(decodedAddr, btcec.S256()) + _, err = btcec.ParsePubKey(decodedAddr) if err == nil { return nil, fmt.Errorf("cannot send coins to pubkeys") } @@ -1476,6 +1506,14 @@ func (r *rpcServer) NewAddress(ctx context.Context, return nil, err } + case lnrpc.AddressType_TAPROOT_PUBKEY: + addr, err = r.server.cc.Wallet.NewAddress( + lnwallet.TaprootPubkey, false, account, + ) + if err != nil { + return nil, err + } + case lnrpc.AddressType_UNUSED_WITNESS_PUBKEY_HASH: addr, err = r.server.cc.Wallet.LastUnusedAddress( lnwallet.WitnessPubKey, account, @@ -1491,6 +1529,14 @@ func (r *rpcServer) NewAddress(ctx context.Context, if err != nil { return nil, err } + + case lnrpc.AddressType_UNUSED_TAPROOT_PUBKEY: + addr, err = r.server.cc.Wallet.LastUnusedAddress( + lnwallet.TaprootPubkey, account, + ) + if err != nil { + return nil, err + } } rpcsLog.Debugf("[newaddress] account=%v type=%v addr=%v", account, @@ -1551,7 +1597,7 @@ func (r *rpcServer) VerifyMessage(ctx context.Context, digest := chainhash.DoubleHashB(in.Msg) // RecoverCompact both recovers the pubkey and validates the signature. - pubKey, _, err := btcec.RecoverCompact(btcec.S256(), sig, digest) + pubKey, _, err := ecdsa.RecoverCompact(sig, digest) if err != nil { return &lnrpc.VerifyMessageResponse{Valid: false}, nil } @@ -1594,7 +1640,7 @@ func (r *rpcServer) ConnectPeer(ctx context.Context, if err != nil { return nil, err } - pubKey, err := btcec.ParsePubKey(pubkeyHex, btcec.S256()) + pubKey, err := btcec.ParsePubKey(pubkeyHex) if err != nil { return nil, err } @@ -1631,9 +1677,9 @@ func (r *rpcServer) ConnectPeer(ctx context.Context, ) } - if err := r.server.ConnectToPeer(peerAddr, - in.Perm, timeout); err != nil { - + if err := r.server.ConnectToPeer( + peerAddr, in.Perm, timeout, + ); err != nil { rpcsLog.Errorf( "[connectpeer]: error connecting to peer: %v", err, ) @@ -1663,7 +1709,7 @@ func (r *rpcServer) DisconnectPeer(ctx context.Context, if err != nil { return nil, fmt.Errorf("unable to decode pubkey bytes: %v", err) } - peerPubKey, err := btcec.ParsePubKey(pubKeyBytes, btcec.S256()) + peerPubKey, err := btcec.ParsePubKey(pubKeyBytes) if err != nil { return nil, fmt.Errorf("unable to parse pubkey: %v", err) } @@ -1730,17 +1776,13 @@ func newFundingShimAssembler(chanPointShim *lnrpc.ChanPointShim, initiator bool, // Next we'll parse out the remote party's funding key, as well as our // full key descriptor. - remoteKey, err := btcec.ParsePubKey( - chanPointShim.RemoteKey, btcec.S256(), - ) + remoteKey, err := btcec.ParsePubKey(chanPointShim.RemoteKey) if err != nil { return nil, err } shimKeyDesc := chanPointShim.LocalKey - localKey, err := btcec.ParsePubKey( - shimKeyDesc.RawKeyBytes, btcec.S256(), - ) + localKey, err := btcec.ParsePubKey(shimKeyDesc.RawKeyBytes) if err != nil { return nil, err } @@ -1915,11 +1957,10 @@ func (r *rpcServer) parseOpenChannelReq(in *lnrpc.OpenChannelRequest, // not present, we'll fallback to the deprecated version that parses the // key from a hex string if this is for REST for backwards compatibility. switch { - // Parse the raw bytes of the node key into a pubkey object so we can // easily manipulate it. case len(in.NodePubkey) > 0: - nodePubKey, err = btcec.ParsePubKey(in.NodePubkey, btcec.S256()) + nodePubKey, err = btcec.ParsePubKey(in.NodePubkey) if err != nil { return nil, err } @@ -1933,7 +1974,7 @@ func (r *rpcServer) parseOpenChannelReq(in *lnrpc.OpenChannelRequest, return nil, err } - nodePubKey, err = btcec.ParsePubKey(keyBytes, btcec.S256()) + nodePubKey, err = btcec.ParsePubKey(keyBytes) if err != nil { return nil, err } @@ -2498,6 +2539,7 @@ func createRPCCloseUpdate(update interface{}) ( Update: &lnrpc.CloseStatusUpdate_ChanClose{ ChanClose: &lnrpc.ChannelCloseUpdate{ ClosingTxid: u.ClosingTxid, + Success: u.Success, }, }, }, nil @@ -2760,24 +2802,25 @@ func (r *rpcServer) GetInfo(_ context.Context, // TODO(roasbeef): add synced height n stuff return &lnrpc.GetInfoResponse{ - IdentityPubkey: encodedIDPub, - NumPendingChannels: nPendingChannels, - NumActiveChannels: activeChannels, - NumInactiveChannels: inactiveChannels, - NumPeers: uint32(len(serverPeers)), - BlockHeight: uint32(bestHeight), - BlockHash: bestHash.String(), - SyncedToChain: isSynced, - Testnet: chainreg.IsTestnet(&r.cfg.ActiveNetParams), - Chains: activeChains, - Uris: uris, - Alias: nodeAnn.Alias.String(), - Color: routing.EncodeHexColor(nodeAnn.RGBColor), - BestHeaderTimestamp: int64(bestHeaderTimestamp), - Version: build.Version() + " commit=" + build.Commit, - CommitHash: build.CommitHash, - SyncedToGraph: isGraphSynced, - Features: features, + IdentityPubkey: encodedIDPub, + NumPendingChannels: nPendingChannels, + NumActiveChannels: activeChannels, + NumInactiveChannels: inactiveChannels, + NumPeers: uint32(len(serverPeers)), + BlockHeight: uint32(bestHeight), + BlockHash: bestHash.String(), + SyncedToChain: isSynced, + Testnet: chainreg.IsTestnet(&r.cfg.ActiveNetParams), + Chains: activeChains, + Uris: uris, + Alias: nodeAnn.Alias.String(), + Color: routing.EncodeHexColor(nodeAnn.RGBColor), + BestHeaderTimestamp: bestHeaderTimestamp, + Version: build.Version() + " commit=" + build.Commit, + CommitHash: build.CommitHash, + SyncedToGraph: isGraphSynced, + Features: features, + RequireHtlcInterceptor: r.cfg.RequireInterceptor, }, nil } @@ -3072,6 +3115,27 @@ func (r *rpcServer) WalletBalance(ctx context.Context, } } + // Now that we have the base balance accounted for with each account, + // we'll look at the set of locked UTXOs to tally that as well. If we + // don't display this, then anytime we attempt a funding reservation, + // the outputs will chose as being "gone" until they're confirmed on + // chain. + var lockedBalance btcutil.Amount + leases, err := r.server.cc.Wallet.ListLeasedOutputs() + if err != nil { + return nil, err + } + for _, leasedOutput := range leases { + utxoInfo, err := r.server.cc.Wallet.FetchInputInfo( + &leasedOutput.Outpoint, + ) + if err != nil { + return nil, err + } + + lockedBalance += utxoInfo.Value + } + rpcsLog.Debugf("[walletbalance] Total balance=%v (confirmed=%v, "+ "unconfirmed=%v)", totalBalance, confirmedBalance, unconfirmedBalance) @@ -3080,6 +3144,7 @@ func (r *rpcServer) WalletBalance(ctx context.Context, TotalBalance: int64(totalBalance), ConfirmedBalance: int64(confirmedBalance), UnconfirmedBalance: int64(unconfirmedBalance), + LockedBalance: int64(lockedBalance), AccountBalance: rpcAccountBalances, }, nil } @@ -3169,38 +3234,27 @@ func (r *rpcServer) ChannelBalance(ctx context.Context, }, nil } -// PendingChannels returns a list of all the channels that are currently -// considered "pending". A channel is pending if it has finished the funding -// workflow and is waiting for confirmations for the funding txn, or is in the -// process of closure, either initiated cooperatively or non-cooperatively. -func (r *rpcServer) PendingChannels(ctx context.Context, - in *lnrpc.PendingChannelsRequest) (*lnrpc.PendingChannelsResponse, error) { - - rpcsLog.Debugf("[pendingchannels]") - - resp := &lnrpc.PendingChannelsResponse{} - - // rpcInitiator returns the correct lnrpc initiator for channels where - // we have a record of the opening channel. - rpcInitiator := func(isInitiator bool) lnrpc.Initiator { - if isInitiator { - return lnrpc.Initiator_INITIATOR_LOCAL - } - - return lnrpc.Initiator_INITIATOR_REMOTE - } +type ( + pendingOpenChannels []*lnrpc.PendingChannelsResponse_PendingOpenChannel + pendingForceClose []*lnrpc.PendingChannelsResponse_ForceClosedChannel + waitingCloseChannels []*lnrpc.PendingChannelsResponse_WaitingCloseChannel +) +// fetchPendingOpenChannels queries the database for a list of channels that +// have pending open state. The returned result is used in the response of the +// PendingChannels RPC. +func (r *rpcServer) fetchPendingOpenChannels() (pendingOpenChannels, error) { // First, we'll populate the response with all the channels that are // soon to be opened. We can easily fetch this data from the database // and map the db struct to the proto response. - pendingOpenChannels, err := r.server.chanStateDB.FetchPendingChannels() + channels, err := r.server.chanStateDB.FetchPendingChannels() if err != nil { rpcsLog.Errorf("unable to fetch pending channels: %v", err) return nil, err } - resp.PendingOpenChannels = make([]*lnrpc.PendingChannelsResponse_PendingOpenChannel, - len(pendingOpenChannels)) - for i, pendingChan := range pendingOpenChannels { + + result := make(pendingOpenChannels, len(channels)) + for i, pendingChan := range channels { pub := pendingChan.IdentityPub.SerializeCompressed() // As this is required for display purposes, we'll calculate @@ -3215,7 +3269,7 @@ func (r *rpcServer) PendingChannels(ctx context.Context, commitBaseWeight := blockchain.GetTransactionWeight(utx) commitWeight := commitBaseWeight + input.WitnessCommitmentTxWeight - resp.PendingOpenChannels[i] = &lnrpc.PendingChannelsResponse_PendingOpenChannel{ + result[i] = &lnrpc.PendingChannelsResponse_PendingOpenChannel{ Channel: &lnrpc.PendingChannelsResponse_PendingChannel{ RemoteNodePub: hex.EncodeToString(pub), ChannelPoint: pendingChan.FundingOutpoint.String(), @@ -3226,6 +3280,7 @@ func (r *rpcServer) PendingChannels(ctx context.Context, RemoteChanReserveSat: int64(pendingChan.RemoteChanCfg.ChanReserve), Initiator: rpcInitiator(pendingChan.IsInitiator), CommitmentType: rpcCommitmentType(pendingChan.ChanType), + Private: isPrivate(pendingChan), }, CommitWeight: commitWeight, CommitFee: int64(localCommitment.CommitFee), @@ -3234,19 +3289,32 @@ func (r *rpcServer) PendingChannels(ctx context.Context, } } + return result, nil +} + +// fetchPendingForceCloseChannels queries the database for a list of channels +// that have their closing transactions confirmed but not fully resolved yet. +// The returned result is used in the response of the PendingChannels RPC. +func (r *rpcServer) fetchPendingForceCloseChannels() (pendingForceClose, + int64, error) { + _, currentHeight, err := r.server.cc.ChainIO.GetBestBlock() if err != nil { - return nil, err + return nil, 0, err } // Next, we'll examine the channels that are soon to be closed so we // can populate these fields within the response. - pendingCloseChannels, err := r.server.chanStateDB.FetchClosedChannels(true) + channels, err := r.server.chanStateDB.FetchClosedChannels(true) if err != nil { rpcsLog.Errorf("unable to fetch closed channels: %v", err) - return nil, err + return nil, 0, err } - for _, pendingClose := range pendingCloseChannels { + + result := make(pendingForceClose, 0) + limboBalance := int64(0) + + for _, pendingClose := range channels { // First construct the channel struct itself, this will be // needed regardless of how this channel was closed. pub := pendingClose.RemotePub.SerializeCompressed() @@ -3292,14 +3360,20 @@ func (r *rpcServer) PendingChannels(ctx context.Context, rpcsLog.Errorf("unable to load forwarding "+ "packages for channel:%s, %v", historical.ShortChannelID, err) - return nil, err + return nil, 0, err } channel.NumForwardingPackages = int64(len(fwdPkgs)) + channel.RemoteBalance = int64( + historical.LocalCommitment.RemoteBalance.ToSatoshis(), + ) + + channel.Private = isPrivate(historical) + // If the error is non-nil, and not due to older versions of lnd // not persisting historical channels, return it. default: - return nil, err + return nil, 0, err } closeTXID := pendingClose.ClosingTXID.String() @@ -3334,36 +3408,83 @@ func (r *rpcServer) PendingChannels(ctx context.Context, &chanPoint, currentHeight, forceClose, ) if err != nil { - return nil, err + return nil, 0, err } err = r.arbitratorPopulateForceCloseResp( &chanPoint, currentHeight, forceClose, ) if err != nil { - return nil, err + return nil, 0, err } - resp.TotalLimboBalance += forceClose.LimboBalance - - resp.PendingForceClosingChannels = append( - resp.PendingForceClosingChannels, - forceClose, - ) + limboBalance += forceClose.LimboBalance + result = append(result, forceClose) } } + return result, limboBalance, nil +} + +// fetchWaitingCloseChannels queries the database for a list of channels +// that have their closing transactions broadcast but not confirmed yet. +// The returned result is used in the response of the PendingChannels RPC. +func (r *rpcServer) fetchWaitingCloseChannels() (waitingCloseChannels, + int64, error) { + // We'll also fetch all channels that are open, but have had their // commitment broadcasted, meaning they are waiting for the closing // transaction to confirm. - waitingCloseChans, err := r.server.chanStateDB.FetchWaitingCloseChannels() + channels, err := r.server.chanStateDB.FetchWaitingCloseChannels() if err != nil { rpcsLog.Errorf("unable to fetch channels waiting close: %v", err) - return nil, err + return nil, 0, err } - for _, waitingClose := range waitingCloseChans { + result := make(waitingCloseChannels, 0) + limboBalance := int64(0) + + // getClosingTx is a helper closure that tries to find the closing txid + // of a given waiting close channel. Notice that if the remote closes + // the channel, we may not have the closing txid. + getClosingTx := func(c *channeldb.OpenChannel) (string, error) { + var ( + tx *wire.MsgTx + err error + ) + + // First, we try to locate the force closing txid. If not + // found, we will then try to find its coop closing txid. + tx, err = c.BroadcastedCommitment() + if err == nil { + return tx.TxHash().String(), nil + } + + // If the error returned is not ErrNoCloseTx, something + // unexpected happened and we will return the error. + if err != channeldb.ErrNoCloseTx { + return "", err + } + + // Otherwise, we continue to locate its coop closing txid. + tx, err = c.BroadcastedCooperative() + if err == nil { + return tx.TxHash().String(), nil + } + + // Return the error if it's not ErrNoCloseTx. + if err != channeldb.ErrNoCloseTx { + return "", err + } + + // Otherwise return an empty txid. This can happen if the + // remote broadcast the closing txid and we haven't recorded it + // yet. + return "", nil + } + + for _, waitingClose := range channels { pub := waitingClose.IdentityPub.SerializeCompressed() chanPoint := waitingClose.FundingOutpoint @@ -3395,13 +3516,12 @@ func (r *rpcServer) PendingChannels(ctx context.Context, remoteCommitDiff, err := waitingClose.RemoteCommitChainTip() switch { - // Don't set hash if there is no pending remote commit. case err == channeldb.ErrNoPendingCommit: // An unexpected error occurred. case err != nil: - return nil, err + return nil, 0, err // There is a pending remote commit. Set its hash in the // response. @@ -3418,7 +3538,18 @@ func (r *rpcServer) PendingChannels(ctx context.Context, rpcsLog.Errorf("unable to load forwarding packages "+ "for channel:%s, %v", waitingClose.ShortChannelID, err) - return nil, err + return nil, 0, err + } + + // Get the closing txid. + // NOTE: the closing txid could be empty here if it's the + // remote broadcasted the closing tx. + closingTxid, err := getClosingTx(waitingClose) + if err != nil { + rpcsLog.Errorf("unable to find closing txid for "+ + "channel:%s, %v", + waitingClose.ShortChannelID, err) + return nil, 0, err } channel := &lnrpc.PendingChannelsResponse_PendingChannel{ @@ -3432,22 +3563,65 @@ func (r *rpcServer) PendingChannels(ctx context.Context, Initiator: rpcInitiator(waitingClose.IsInitiator), CommitmentType: rpcCommitmentType(waitingClose.ChanType), NumForwardingPackages: int64(len(fwdPkgs)), + ChanStatusFlags: waitingClose.ChanStatus().String(), + Private: isPrivate(waitingClose), } waitingCloseResp := &lnrpc.PendingChannelsResponse_WaitingCloseChannel{ Channel: channel, LimboBalance: channel.LocalBalance, Commitments: &commitments, + ClosingTxid: closingTxid, } // A close tx has been broadcasted, all our balance will be in // limbo until it confirms. - resp.WaitingCloseChannels = append( - resp.WaitingCloseChannels, waitingCloseResp, - ) + result = append(result, waitingCloseResp) + limboBalance += channel.LocalBalance + } + + return result, limboBalance, nil +} + +// PendingChannels returns a list of all the channels that are currently +// considered "pending". A channel is pending if it has finished the funding +// workflow and is waiting for confirmations for the funding txn, or is in the +// process of closure, either initiated cooperatively or non-cooperatively. +func (r *rpcServer) PendingChannels(ctx context.Context, + in *lnrpc.PendingChannelsRequest) ( + *lnrpc.PendingChannelsResponse, error) { - resp.TotalLimboBalance += channel.LocalBalance + rpcsLog.Debugf("[pendingchannels]") + + resp := &lnrpc.PendingChannelsResponse{} + + // First, we find all the channels that will soon be opened. + pendingOpenChannels, err := r.fetchPendingOpenChannels() + if err != nil { + return nil, err } + resp.PendingOpenChannels = pendingOpenChannels + + // Second, we fetch all channels that considered pending force closing. + // This means the channels here have their closing transactions + // confirmed but not considered fully resolved yet. For instance, they + // may have a second level HTLCs to be resolved onchain. + pendingCloseChannels, limbo, err := r.fetchPendingForceCloseChannels() + if err != nil { + return nil, err + } + resp.PendingForceClosingChannels = pendingCloseChannels + resp.TotalLimboBalance = limbo + + // Third, we fetch all channels that are open, but have had their + // commitment broadcasted, meaning they are waiting for the closing + // transaction to confirm. + waitingCloseChannels, limbo, err := r.fetchWaitingCloseChannels() + if err != nil { + return nil, err + } + resp.WaitingCloseChannels = waitingCloseChannels + resp.TotalLimboBalance += limbo return resp, nil } @@ -3467,7 +3641,6 @@ func (r *rpcServer) arbitratorPopulateForceCloseResp(chanPoint *wire.OutPoint, for _, report := range reports { switch report.Type { - // For a direct output, populate/update the top level // response properties. case contractcourt.ReportOutputUnencumbered: @@ -3514,7 +3687,6 @@ func (r *rpcServer) arbitratorPopulateForceCloseResp(chanPoint *wire.OutPoint, // limbo, lost and recovered. Derive the current state // from the limbo and recovered balances. switch { - case report.RecoveredBalance != 0: forceClose.Anchor = lnrpc.PendingChannelsResponse_ForceClosedChannel_RECOVERED @@ -3677,8 +3849,6 @@ func (r *rpcServer) ListChannels(ctx context.Context, resp := &lnrpc.ListChannelsResponse{} - graph := r.server.graphDB - dbChannels, err := r.server.chanStateDB.FetchAllOpenChannels() if err != nil { return nil, err @@ -3715,7 +3885,7 @@ func (r *rpcServer) ListChannels(ctx context.Context, // Next, we'll determine whether we should add this channel to // our list depending on the type of channels requested to us. isActive := peerOnline && linkActive - channel, err := createRPCOpenChannel(r, graph, dbChannel, isActive) + channel, err := createRPCOpenChannel(r, dbChannel, isActive) if err != nil { return nil, err } @@ -3775,17 +3945,23 @@ func createChannelConstraint( } } +// isPrivate evaluates the ChannelFlags of the db channel to determine if the +// channel is private or not. +func isPrivate(dbChannel *channeldb.OpenChannel) bool { + if dbChannel == nil { + return false + } + return dbChannel.ChannelFlags&lnwire.FFAnnounceChannel != 1 +} + // createRPCOpenChannel creates an *lnrpc.Channel from the *channeldb.Channel. -func createRPCOpenChannel(r *rpcServer, graph *channeldb.ChannelGraph, - dbChannel *channeldb.OpenChannel, isActive bool) (*lnrpc.Channel, error) { +func createRPCOpenChannel(r *rpcServer, dbChannel *channeldb.OpenChannel, + isActive bool) (*lnrpc.Channel, error) { nodePub := dbChannel.IdentityPub nodeID := hex.EncodeToString(nodePub.SerializeCompressed()) chanPoint := dbChannel.FundingOutpoint - // Next, we'll determine whether the channel is public or not. - isPublic := dbChannel.ChannelFlags&lnwire.FFAnnounceChannel != 0 - // As this is required for display purposes, we'll calculate // the weight of the commitment transaction. We also add on the // estimated weight of the witness to calculate the weight of @@ -3817,7 +3993,7 @@ func createRPCOpenChannel(r *rpcServer, graph *channeldb.ChannelGraph, channel := &lnrpc.Channel{ Active: isActive, - Private: !isPublic, + Private: isPrivate(dbChannel), RemotePubkey: nodeID, ChannelPoint: chanPoint.String(), ChanId: dbChannel.ShortChannelID.ToUint64(), @@ -3905,21 +4081,13 @@ func createRPCOpenChannel(r *rpcServer, graph *channeldb.ChannelGraph, channel.UnsettledBalance += channel.PendingHtlcs[i].Amount } - // Lookup our balances at height 0, because they will reflect any - // push amounts that may have been present when this channel was - // created. - localBalance, remoteBalance, err := dbChannel.BalancesAtHeight(0) - if err != nil { - return nil, err - } - // If we initiated opening the channel, the zero height remote balance // is the push amount. Otherwise, our starting balance is the push // amount. If there is no push amount, these values will simply be zero. if dbChannel.IsInitiator { - channel.PushAmountSat = uint64(remoteBalance.ToSatoshis()) + channel.PushAmountSat = uint64(dbChannel.InitialRemoteBalance) } else { - channel.PushAmountSat = uint64(localBalance.ToSatoshis()) + channel.PushAmountSat = uint64(dbChannel.InitialLocalBalance) } if len(dbChannel.LocalShutdownScript) > 0 { @@ -4203,8 +4371,6 @@ func (r *rpcServer) SubscribeChannelEvents(req *lnrpc.ChannelEventSubscription, // the server, or client exits. defer channelEventSub.Cancel() - graph := r.server.graphDB - for { select { // A new update has been sent by the channel router, we'll @@ -4224,8 +4390,9 @@ func (r *rpcServer) SubscribeChannelEvents(req *lnrpc.ChannelEventSubscription, }, } case channelnotifier.OpenChannelEvent: - channel, err := createRPCOpenChannel(r, graph, - event.Channel, true) + channel, err := createRPCOpenChannel( + r, event.Channel, true, + ) if err != nil { return err } @@ -4428,6 +4595,7 @@ type rpcPaymentIntent struct { destFeatures *lnwire.FeatureVector paymentAddr *[32]byte payReq []byte + metadata []byte destCustomRecords record.CustomSet @@ -4561,6 +4729,7 @@ func (r *rpcServer) extractPaymentIntent(rpcPayReq *rpcPaymentRequest) (rpcPayme payIntent.payReq = []byte(rpcPayReq.PaymentRequest) payIntent.destFeatures = payReq.Features payIntent.paymentAddr = payReq.PaymentAddr + payIntent.metadata = payReq.Metadata if err := validateDest(payIntent.dest); err != nil { return payIntent, err @@ -4715,6 +4884,7 @@ func (r *rpcServer) dispatchPaymentIntent( DestCustomRecords: payIntent.destCustomRecords, DestFeatures: payIntent.destFeatures, PaymentAddr: payIntent.paymentAddr, + Metadata: payIntent.metadata, // Don't enable multi-part payments on the main rpc. // Users need to use routerrpc for that. @@ -5292,38 +5462,13 @@ func (r *rpcServer) SubscribeTransactions(req *lnrpc.GetTransactionsRequest, for { select { case tx := <-txClient.ConfirmedTransactions(): - destAddresses := make([]string, 0, len(tx.DestAddresses)) - for _, destAddress := range tx.DestAddresses { - destAddresses = append(destAddresses, destAddress.EncodeAddress()) - } - detail := &lnrpc.Transaction{ - TxHash: tx.Hash.String(), - Amount: int64(tx.Value), - NumConfirmations: tx.NumConfirmations, - BlockHash: tx.BlockHash.String(), - BlockHeight: tx.BlockHeight, - TimeStamp: tx.Timestamp, - TotalFees: tx.TotalFees, - DestAddresses: destAddresses, - RawTxHex: hex.EncodeToString(tx.RawTx), - } + detail := lnrpc.RPCTransaction(tx) if err := updateStream.Send(detail); err != nil { return err } case tx := <-txClient.UnconfirmedTransactions(): - var destAddresses []string - for _, destAddress := range tx.DestAddresses { - destAddresses = append(destAddresses, destAddress.EncodeAddress()) - } - detail := &lnrpc.Transaction{ - TxHash: tx.Hash.String(), - Amount: int64(tx.Value), - TimeStamp: tx.Timestamp, - TotalFees: tx.TotalFees, - DestAddresses: destAddresses, - RawTxHex: hex.EncodeToString(tx.RawTx), - } + detail := lnrpc.RPCTransaction(tx) if err := updateStream.Send(detail); err != nil { return err } @@ -5381,6 +5526,20 @@ func (r *rpcServer) DescribeGraph(ctx context.Context, resp := &lnrpc.ChannelGraph{} includeUnannounced := req.IncludeUnannounced + // Check to see if the cache is already populated, if so then we can + // just return it directly. + // + // TODO(roasbeef): move this to an interceptor level feature? + graphCacheActive := r.cfg.Caches.RPCGraphCacheDuration != 0 + if graphCacheActive { + r.graphCache.Lock() + defer r.graphCache.Unlock() + + if r.describeGraphResp != nil { + return r.describeGraphResp, nil + } + } + // Obtain the pointer to the global singleton channel graph, this will // provide a consistent view of the graph due to bolt db's // transactional model. @@ -5439,6 +5598,13 @@ func (r *rpcServer) DescribeGraph(ctx context.Context, return nil, err } + // We still have the mutex held, so we can safely populate the cache + // now to save on GC churn for this query, but only if the cache isn't + // disabled. + if graphCacheActive { + r.describeGraphResp = resp + } + return resp, nil } @@ -5665,7 +5831,7 @@ func (r *rpcServer) GetNodeInfo(ctx context.Context, // QueryRoutes attempts to query the daemons' Channel Router for a possible // route to a target destination capable of carrying a specific amount of -// satoshis within the route's flow. The retuned route contains the full +// satoshis within the route's flow. The returned route contains the full // details required to craft and send an HTLC, also including the necessary // information that should be present within the Sphinx packet encapsulated // within the HTLC. @@ -5708,7 +5874,9 @@ func (r *rpcServer) GetNetworkInfo(ctx context.Context, // network, tallying up the total number of nodes, and also gathering // each node so we can measure the graph diameter and degree stats // below. - if err := graph.ForEachNode(func(tx kvdb.RTx, node *channeldb.LightningNode) error { + err := graph.ForEachNodeCached(func(node route.Vertex, + edges map[uint64]*channeldb.DirectedChannel) error { + // Increment the total number of nodes with each iteration. numNodes++ @@ -5718,9 +5886,7 @@ func (r *rpcServer) GetNetworkInfo(ctx context.Context, // through the db transaction from the outer view so we can // re-use it within this inner view. var outDegree uint32 - if err := node.ForEachChannel(tx, func(_ kvdb.RTx, - edge *channeldb.ChannelEdgeInfo, _, _ *channeldb.ChannelEdgePolicy) error { - + for _, edge := range edges { // Bump up the out degree for this node for each // channel encountered. outDegree++ @@ -5751,9 +5917,6 @@ func (r *rpcServer) GetNetworkInfo(ctx context.Context, seenChans[edge.ChannelID] = struct{}{} allChans = append(allChans, edge.Capacity) - return nil - }); err != nil { - return err } // Finally, if the out degree of this node is greater than what @@ -5763,7 +5926,8 @@ func (r *rpcServer) GetNetworkInfo(ctx context.Context, } return nil - }); err != nil { + }) + if err != nil { return nil, err } @@ -5782,10 +5946,20 @@ func (r *rpcServer) GetNetworkInfo(ctx context.Context, minChannelSize = 0 } - // TODO(roasbeef): graph diameter + // Graph diameter. + channelGraph := autopilot.ChannelGraphFromCachedDatabase(graph) + simpleGraph, err := autopilot.NewSimpleGraph(channelGraph) + if err != nil { + return nil, err + } + start := time.Now() + diameter := simpleGraph.DiameterRadialCutoff() + rpcsLog.Infof("elapsed time for diameter (%d) calculation: %v", diameter, + time.Since(start)) // TODO(roasbeef): also add oldest channel? netInfo := &lnrpc.NetworkInfo{ + GraphDiameter: diameter, MaxOutDegree: maxChanOut, AvgOutDegree: float64(2*numChannels) / float64(numNodes), NumNodes: numNodes, @@ -5991,6 +6165,7 @@ func (r *rpcServer) ListPayments(ctx context.Context, MaxPayments: req.MaxPayments, Reversed: req.Reversed, IncludeIncomplete: req.IncludeIncomplete, + CountTotal: req.CountTotalPayments, } // If the maximum number of payments wasn't specified, then we'll @@ -6007,6 +6182,7 @@ func (r *rpcServer) ListPayments(ctx context.Context, paymentsResp := &lnrpc.ListPaymentsResponse{ LastIndexOffset: paymentsQuerySlice.LastIndexOffset, FirstIndexOffset: paymentsQuerySlice.FirstIndexOffset, + TotalNumPayments: paymentsQuerySlice.TotalCount, } for _, payment := range paymentsQuerySlice.Payments { @@ -6169,7 +6345,7 @@ func (r *rpcServer) DecodePayReq(ctx context.Context, // Nodes on the network advertise their fee rate using this point as a base. // This means that the minimal possible fee rate if 1e-6, or 0.000001, or // 0.0001%. -const feeBase = 1000000 +const feeBase float64 = 1000000 // FeeReport allows the caller to obtain a report detailing the current fee // schedule enforced by the node globally for each channel. @@ -6202,7 +6378,7 @@ func (r *rpcServer) FeeReport(ctx context.Context, // 1mil mSAT sent, so will divide by this to get the proper fee // rate. feeRateFixedPoint := edgePolicy.FeeProportionalMillionths - feeRate := float64(feeRateFixedPoint) / float64(feeBase) + feeRate := float64(feeRateFixedPoint) / feeBase // TODO(roasbeef): also add stats for revenue for each channel feeReports = append(feeReports, &lnrpc.ChannelFeeReport{ @@ -6342,28 +6518,52 @@ func (r *rpcServer) UpdateChannelPolicy(ctx context.Context, return nil, fmt.Errorf("unknown scope: %v", scope) } + var feeRateFixed uint32 + switch { - // As a sanity check, if the fee isn't zero, we'll ensure that the - // passed fee rate is below 1e-6, or the lowest allowed non-zero fee - // rate expressible within the protocol. - case req.FeeRate != 0 && req.FeeRate < minFeeRate: - return nil, fmt.Errorf("fee rate of %v is too small, min fee "+ - "rate is %v", req.FeeRate, minFeeRate) + // The request should use either the fee rate in percent, or the new + // ppm rate, but not both. + case req.FeeRate != 0 && req.FeeRatePpm != 0: + errMsg := "cannot set both FeeRate and FeeRatePpm at the " + + "same time" + + return nil, status.Errorf(codes.InvalidArgument, errMsg) + + // If the request is using fee_rate. + case req.FeeRate != 0: + // As a sanity check, if the fee isn't zero, we'll ensure that + // the passed fee rate is below 1e-6, or the lowest allowed + // non-zero fee rate expressible within the protocol. + if req.FeeRate != 0 && req.FeeRate < minFeeRate { + return nil, fmt.Errorf("fee rate of %v is too "+ + "small, min fee rate is %v", req.FeeRate, + minFeeRate) + } + + // We'll also need to convert the floating point fee rate we + // accept over RPC to the fixed point rate that we use within + // the protocol. We do this by multiplying the passed fee rate + // by the fee base. This gives us the fixed point, scaled by 1 + // million that's used within the protocol. + // + // Because of the inaccurate precision of the IEEE 754 + // standard, we need to round the product of feerate and + // feebase. + feeRateFixed = uint32(math.Round(req.FeeRate * feeBase)) + + // Otherwise, we use the fee_rate_ppm parameter. + case req.FeeRatePpm != 0: + feeRateFixed = req.FeeRatePpm + } // We'll also ensure that the user isn't setting a CLTV delta that // won't give outgoing HTLCs enough time to fully resolve if needed. - case req.TimeLockDelta < minTimeLockDelta: + if req.TimeLockDelta < minTimeLockDelta { return nil, fmt.Errorf("time lock delta of %v is too small, "+ "minimum supported is %v", req.TimeLockDelta, minTimeLockDelta) } - // We'll also need to convert the floating point fee rate we accept - // over RPC to the fixed point rate that we use within the protocol. We - // do this by multiplying the passed fee rate by the fee base. This - // gives us the fixed point, scaled by 1 million that's used within the - // protocol. - feeRateFixed := uint32(req.FeeRate * feeBase) baseFeeMsat := lnwire.MilliSatoshi(req.BaseFeeMsat) feeSchema := routing.FeeSchema{ BaseFee: baseFeeMsat, @@ -6385,9 +6585,9 @@ func (r *rpcServer) UpdateChannelPolicy(ctx context.Context, } rpcsLog.Debugf("[updatechanpolicy] updating channel policy base_fee=%v, "+ - "rate_float=%v, rate_fixed=%v, time_lock_delta: %v, "+ + "rate_fixed=%v, time_lock_delta: %v, "+ "min_htlc=%v, max_htlc=%v, targets=%v", - req.BaseFeeMsat, req.FeeRate, feeRateFixed, req.TimeLockDelta, + req.BaseFeeMsat, feeRateFixed, req.TimeLockDelta, minHtlc, maxHtlc, spew.Sdump(targetChans)) @@ -6938,7 +7138,6 @@ func (r *rpcServer) BakeMacaroon(ctx context.Context, } else if !stringInSlice(op.Action, validActions) { return nil, fmt.Errorf("invalid permission action. %s", helpMsg) - } requestedPermissions[idx] = bakery.Op{ @@ -7100,7 +7299,6 @@ func (r *rpcServer) FundingStateStep(ctx context.Context, var pendingChanID [32]byte switch { - // If this is a message to register a new shim that is an external // channel point, then we'll contact the wallet to register this new // shim. A user will use this method to register a new channel funding @@ -7402,3 +7600,13 @@ func (r *rpcServer) SubscribeCustomMessages(req *lnrpc.SubscribeCustomMessagesRe } } } + +// rpcInitiator returns the correct lnrpc initiator for channels where we have +// a record of the opening channel. +func rpcInitiator(isInitiator bool) lnrpc.Initiator { + if isInitiator { + return lnrpc.Initiator_INITIATOR_LOCAL + } + + return lnrpc.Initiator_INITIATOR_REMOTE +} diff --git a/sample-lnd.conf b/sample-lnd.conf index 69533d338e..40e204edf4 100644 --- a/sample-lnd.conf +++ b/sample-lnd.conf @@ -77,11 +77,11 @@ ; another IP address to listen on, for example an IPv6 address. ; letsencryptlisten=localhost:8080 -; Request a Let's Encrypt certificate for this domain. Note that the certicate +; Request a Let's Encrypt certificate for this domain. Note that the certificate ; is only requested and stored when the first rpc connection comes in. ; letsencryptdomain=example.com -; Disable macaroon authentication. Macaroons are used are bearer credentials to +; Disable macaroon authentication. Macaroons are used as bearer credentials to ; authenticate all RPC access. If one wishes to opt out of macaroons, uncomment ; the line below. ; no-macaroons=true @@ -118,9 +118,9 @@ ; A period to wait before for closing channels with outgoing htlcs that have ; timed out and are a result of this nodes instead payment. In addition to our -; current block based deadline, is specified this grace period will also be taken -; into account. -; payments-expiration-grace-period=30 +; current block based deadline, if specified this grace period will also be taken +; into account. Valid time units are {s, m, h}. +; payments-expiration-grace-period=30s ; Specify the interfaces to listen on for p2p connections. One listen ; address per line. @@ -183,6 +183,9 @@ ; Disable TLS for the REST API. ; no-rest-tls=true +; Specify peer(s) to connect to first. +; addpeer= + ; The ping interval for REST based WebSocket connections, set to 0 to disable ; sending ping messages from the server side. Valid time units are {s, m, h}. ; ws-ping-interval=30s @@ -293,8 +296,16 @@ ; The maximum time that is allowed to pass between receiving a channel state ; update and signing the next commitment. Setting this to a longer duration -; allows for more efficient channel operations at the cost of latency. -; channel-commit-interval=50ms +; allows for more efficient channel operations at the cost of latency. This is +; capped at 1 hour. The default is 50 milliseconds. +; channel-commit-interval=1h + +; The maximum time that is allowed to pass while waiting for the remote party +; to revoke a locally initiated commitment state. Setting this to a longer +; duration if a slow response is expected from the remote party or large +; number of payments are attempted at the same time. +; (default: 1m0s) +; pending-commit-interval=5m ; The maximum number of channel state updates that is accumulated before signing ; a new commitment. @@ -306,7 +317,7 @@ ; default-remote-max-htlcs=483 ; The duration that a peer connection must be stable before attempting to send a -; channel update to reenable or cancel a pending disables of the peer's channels +; channel update to re-enable or cancel a pending disables of the peer's channels ; on the network. (default: 19m0s) ; chan-enable-timeout=22m @@ -346,6 +357,9 @@ ; used as a hop. ; rejecthtlc=true +; If true, all HTLCs will be held until they are handled by an interceptor +; requireinterceptor=true + ; If true, will apply a randomized staggering between 0s and 30s when ; reconnecting to persistent peers on startup. The first 10 reconnections will be ; attempted instantly, regardless of the flag's value @@ -421,6 +435,12 @@ ; Specify the interface to listen on for Prometheus connections. ; prometheus.listen=0.0.0.0:8989 +; If true, then we'll export additional information that allows users to plot +; the processing latency, and total time spent across each RPC calls+service. +; This generates additional memory load for the Prometheus server, and will end +; up using more disk space over time. +; prometheus.perfhistograms=true + ; The alias your node will use, which can be up to 32 UTF-8 characters in ; length. ; alias=My Lightning ☇ @@ -558,6 +578,12 @@ bitcoin.node=btcd ; etc. ; bitcoind.dir=~/.bitcoin +; Configuration filepath. +; bitcoind.config=~/.bitcoin/bitcoin.conf + +; Authentication cookie file for RPC connections. +; bitcoind.rpccookie=~/.bitcoin/.cookie + ; The host that your local bitcoind daemon is listening on. By default, this ; setting is assumed to be localhost with the default port for the current ; network. @@ -578,6 +604,15 @@ bitcoin.node=btcd ; likely won't need to be set (other than for a remote bitcoind instance). ; bitcoind.zmqpubrawblock=tcp://127.0.0.1:28332 ; bitcoind.zmqpubrawtx=tcp://127.0.0.1:28333 +; bitcoind.zmqreaddeadline=10s + +; Use bitcoind's rpc interface to get block and transaction notifications +; instead of using the zmq interface. Only the rpcpolling option needs to +; be set in order to enable this, the rest of the options can be used to +; change the default values used for this configuration. +; bitcoind.rpcpolling +; bitcoind.blockpollinginterval=1m +; bitcoind.txpollinginterval=30s ; Fee estimate mode for bitcoind. It must be either "ECONOMICAL" or "CONSERVATIVE". ; If unset, the default value is "CONSERVATIVE". @@ -759,6 +794,12 @@ litecoin.node=ltcd ; etc. ; litecoind.dir=~/.litecoin +; Configuration filepath. +; litecoind.config=~/.litecoin/litecoin.conf + +; Authentication cookie file for RPC connections. +; litecoind.rpccookie=~/.litecoin/.cookie + ; The host that your local litecoind daemon is listening on. By default, this ; setting is assumed to be localhost with the default port for the current ; network. @@ -779,6 +820,15 @@ litecoin.node=ltcd ; likely won't need to be set (other than for a remote litecoind instance). ; litecoind.zmqpubrawblock=tcp://127.0.0.1:28332 ; litecoind.zmqpubrawtx=tcp://127.0.0.1:28333 +; litecoind.zmqreaddeadline=10s + +; Use litecoind's rpc interface to get block and transaction notifications +; instead of using the zmq interface. Only the rpcpolling option needs to +; be set in order to enable this, the rest of the options can be used to +; change the default values used for this configuration. +; litecoind.rpcpolling +; litecoind.blockpollinginterval=1m +; litecoind.txpollinginterval=30s ; Fee estimate mode for litecoind. It must be either "ECONOMICAL" or "CONSERVATIVE". ; If unset, the default value is "CONSERVATIVE". @@ -882,6 +932,8 @@ litecoin.node=ltcd ;The path to the private key of the watchtower onion service being created ; tor.watchtowerkeypath=/other/path/ +; Instructs lnd to encrypt the private key using the wallet's seed +; tor.encryptkey=true [watchtower] @@ -1006,6 +1058,23 @@ litecoin.node=ltcd ; value must be >= 1m. ; healthcheck.torconnection.interval=1m +; The number of times we should attempt to check our remote signer RPC +; connection before gracefully shutting down. Set this value to 0 to disable +; this health check. +; healthcheck.remotesigner.attempts=1 + +; The amount of time we allow a call to our remote signer RPC connection to take +; before we fail the attempt. This value must be >= 1s. +; healthcheck.remotesigner.timeout=1s + +; The amount of time we should backoff between failed attempts to check remote +; signer RPC connection. This value must be >= 1s. +; healthcheck.remotesigner.backoff=30s + +; The amount of time we should wait between remote signer RPC connection health +; checks. This value must be >= 1m. +; healthcheck.remotesigner.interval=1m + [signrpc] @@ -1088,6 +1157,10 @@ litecoin.node=ltcd ; roughly 2Kb. (default: 20000) ; caches.channel-cache-size=9000000 +; The duration that the response to DescribeGraph should be cached for. Setting +; the value to zero disables the cache. (default: 1m) +; caches.rpc-graph-cache-duration=10m + [protocol] @@ -1165,6 +1238,9 @@ litecoin.node=ltcd ; testing with embedded etcd. ; db.etcd.embedded_log_file=/path/etcd.log +; The maximum message size in bytes that we may send to etcd. Defaults to 32 MiB. +; db.etcd.max_msg_size=33554432 + [postgres] ; Postgres connection string. ; db.postgres.dsn=postgres://lnd:lnd@localhost:45432/lnd?sslmode=disable @@ -1173,6 +1249,11 @@ litecoin.node=ltcd ; disable. ; db.postgres.timeout= +; Postgres maximum number of connections. Set to zero for unlimited. It is +; recommended to set a limit that is below the server connection limit. +; Otherwise errors may occur in lnd under high-load conditions. +; db.postgres.maxconnections= + [bolt] ; If true, prevents the database from syncing its freelist to disk. @@ -1209,6 +1290,10 @@ litecoin.node=ltcd ; Defaults to the hostname. ; cluster.id=example.com +; The session TTL in seconds after which a new leader is elected if the old +; leader is shut down, crashed or becomes unreachable. +; cluster.leader-session-ttl=30 + [rpcmiddleware] ; Enable the RPC middleware interceptor functionality. @@ -1241,6 +1326,16 @@ litecoin.node=ltcd ; The TLS certificate to use for establishing the remote signer's identity. ; remotesigner.tlscertpath=/path/to/remote/signer/tls.cert +; The timeout for connecting to and signing requests with the remote signer. +; Valid time units are {s, m, h}. +; remotesigner.timeout=5s + +; If a wallet with private key material already exists, migrate it into a +; watch-only wallet on first startup. +; WARNING: This cannot be undone! Make sure you have backed up your seed before +; you use this flag! All private keys will be purged from the wallet after first +; unlock with this flag! +; remotesigner.migrate-wallet-to-watch-only=true [gossip] diff --git a/scripts/install_bitcoind.sh b/scripts/install_bitcoind.sh index 756fb8afc5..16ea5a3371 100755 --- a/scripts/install_bitcoind.sh +++ b/scripts/install_bitcoind.sh @@ -2,9 +2,9 @@ set -ev -BITCOIND_VERSION=${BITCOIN_VERSION:-0.20.1} +BITCOIND_VERSION=${BITCOIN_VERSION:-23.0} -docker pull ruimarinho/bitcoin-core:$BITCOIND_VERSION -CONTAINER_ID=$(docker create ruimarinho/bitcoin-core:$BITCOIND_VERSION) +docker pull lightninglabs/bitcoin-core:$BITCOIND_VERSION +CONTAINER_ID=$(docker create lightninglabs/bitcoin-core:$BITCOIND_VERSION) sudo docker cp $CONTAINER_ID:/opt/bitcoin-$BITCOIND_VERSION/bin/bitcoind /usr/local/bin/bitcoind docker rm $CONTAINER_ID diff --git a/scripts/keys/cfromknecht.asc b/scripts/keys/cfromknecht.asc deleted file mode 100644 index 2599a94186..0000000000 --- a/scripts/keys/cfromknecht.asc +++ /dev/null @@ -1,52 +0,0 @@ ------BEGIN PGP PUBLIC KEY BLOCK----- - -mQINBFqe/pgBEADGITCXMdzn7+o0rpm4FTJu+LbPSPH+vZi5lbh8vHJ4R0KOMqhx -zMcTLpcD962rmWD8aPq77iX91YXr+yqfpaI6Is2w8ZPjwUFP+Q541XN2djDhNFfu -kPxI4PdMqIuDjPPbS4GIZiRHdCVgzzyvpJTBwBGEZJ204TmNoLWZ9zCh0AzpsJxr -y4RHy+o6Gy4un1n2pu8tkCmaiEtzmRO+ahmNVHNeYUgUT4nDEMAU3VrkmNMctlxJ -5Ng8k1BZ9foQrsjZg7Ovrhi+/L7YWP73TmY4jWxM+P52UyKyPOJiTqg+Xe/1aGPR -ij1KJYoRqWNBG8euIQzcDt4uExndJ5SJtidF/8leCpQGmxDSYDcsAv3/93z/JGGz -Q50j4dwDc11hzPJ9PBq1MLaGEYpQgJpgLx7uXAgu5eRZpFYseFmulbSDwdft78wS -I6K4+9xvzG1LAx9AOomwXj5/I8tL8Cc+IusG6fQOGmHeQO8ekFTYBj9BU0vTLn6F -8/5Bky+zSjx+IRgLpacSjkQGHruDF7JZN1bbAjEGhmMwW0jGzs9JC+HW89w0Yq9G -sFjhyQEoMF9eWMVuhN9BxQ7DXAjXzuCl2MamwC0Aedv/qxExfRlbB0JrIPwl2w7B -37e/26BBlUCrke4yhk0kNHbj6PiLd4Fuv3Bn20nLw4TBaHjqxn4knhnpmQARAQAB -tDBDb25uZXIgRnJvbWtuZWNodCA8Y29ubmVyQGxpZ2h0bmluZy5lbmdpbmVlcmlu -Zz6JAlQEEwEIAD4CGwMFCwkIBwIGFQgJCgsCBBYCAwECHgECF4AWIQScjWGGinxJ -IAOydE7n1ze2f6WSxwUCYEPADgUJB4X09gAKCRDn1ze2f6WSx4dbD/94A8wo2aum -e7n5I6lj5OIp/fwGOFuT+6lizZtewPuFMHv8qo0D7Gx02YHglB47o/LmVptSavX8 -BXqVhA0ZopAP81vtvN/VYyax8Lo9VrnrVQ1nLPP1XOVeFDm7IxFUsmFE7AAEkJlv -cL1mmquxWfg40o6qxehoHy7mKqWI2ns1MaLFLJEdz38PVgGOrVjRR0o5vq2wBC2Q -PoaxdFKbcycnhLx1Kqq9a69GOt21EKg2JBOgwvhv/ejYzg0gH/OU9vvG82vIAA0b -Xee/nGrRSpsQmzfYl6g/TdmbCzjdv5ZK5+A0VnJvGIqEcuy8XhZZJ6jN9sM8YYAs -f4oDRaEOcqldD+jmNwCb88Jxt+L12k+2a9H3ww74WlbQ2SxDLwkG9LhklIA4Op1i -//LxTQ2kBZM747SE4UFvQYIQHvBgHD1zKvcXn5H9dk5diinAXCsULocEQ5xGIJro -AN4QOlJcF7Po/KcSgKmYHL5FCNW2lUJ1VmG3AWydErkbidFiKtPJmWJFeXxDxal9 -/a3vt5UBSaKyoHC1oWIhfljRkV4QONFr0viEKb+4QAJzYaDZ8HRjLaS00OjUWaIb -Bj/qoU+FUGxBLUaTBSoEDDJL26DezHP6J/cvlgmxi0lRiFkHKmTmtoNJEj/cxa7N -YDDsy2bknvVzhs8wPg+9lm00V8Niy8oNubkCDQRanv6YARAA3we2me6D9FmKwv2w -GmsoJUBoaX1E9gO/YKEyQT8638UMMjJIPH0uVovDk6OGNhykPP9Z2MGDZJNFxBh2 -dabxL/6QJLjYebseRurN8ruDewWhI0op3z0JkKvvUKNJeCLGQEqkbnpKDZ1SaIai -2rDlytsrSo37KFp+sd2+WSJUgk5nzjSA1d1CCPPUQVqWsez8cDuir4TdAANyWsne -XvxmTdnZAHpWFPjnjkypzBH7GIAudo7Gj0SGSPmkpmA2WZcw7vIYm2O1dUxfMVXQ -7+znEkckZkdGLcmABOgTAkm7GseCTvnfnt0+TNgKTUKXGKe08nRAbqgysnwFDyG2 -lCey1Ntsz+D3mZfrAa0M9tIQZnjUOLmfr5q2UPjeOJFFGqkpeTb62JLsZpobvoOm -gNmzGKr/djvihqVQM/bNIYE3OIrt+JOaB6Kb/bH4qgDd70TEXkX8efsmQtzcVT+9 -KLHyVvBjIQuoXqn84k26U5WQSEg889iA3eZ4rFvQrB4LZk0ryHC+bcroEEmU/LLD -zZEXyAfCes0lzg9ZIh+3LSqnvD/dIeaVgfaV5AThfKtOwpO8NZF/TkGaXfwC+S8R -/joyAWYJyIKs2ibu42CB+wnrJyb4ujJznCuzY5rW+1CC5+FlfYOaiWNovT9NU9S+ -jLprWDW1CydtfDdbjF9gjyQj0iUAEQEAAYkCPAQYAQgAJgIbDBYhBJyNYYaKfEkg -A7J0TufXN7Z/pZLHBQJeYdPFBQkFpAitAAoJEOfXN7Z/pZLHOCsP/jgEJXk8mZW8 -+d5shUWe3Hx10JiVPBymoDnv3NtJ8a9LGaJcsbKIjpzysMeZ5cW04l33NM+eaWb4 -/ogX98yak4JNYpaiDuloDyNAM7SqH1WdNMqVj3WXvH8Gw/azhDy7xudOGFQi6nTB -14LsgK7X2zAVmxTu9kdwYh64APF2X48WqFQKd85gX35V5OFPGQbpI7viMFC9Sq7U -E27VsyzNpt6hLfb1enEm3t8E6Fi7z5uxPhoX13FShm1ho6U3hQzc27JH08MWxxOE -w8mcwg5l7ktpQOzUkFHvaPNKKAEQGZ4miOuZFajDWdz3eDe5bRrUYo1h6NR5Unv9 -9ZgCfmVuuHYfw+uwEiQcXb4F6UXHwjp8iJtFM4AOcG93nD+1ZkX6U6pneSF5O3cf -EiEKQBu7MxiyWm7L1V+asR2uPWs9mXl1TNC2rlsHKCFJ8We0Xb1btfmlpTeINjPx -yrERZmWUl5bY+sziSfX0H07v49jgPxUilBMxQaqrw0pOhzodq+R+R1OJX9aV7gfz -KWov0Rq2/2TGoeIlVz0Y/mMDCz5ONRn21l1I+HBqjeEBFws8w41cQsKFl5+5dk4m -LZ+g6JVoJ8rlqbMKS2TwhXHwC7ZCWn6w1WI74nJAkn+ULTXFUSwrsJrvwVVqY5YE -P9FcOPI4UCyAsnvRuobOOGZjkARfXg/5 -=OCU6 ------END PGP PUBLIC KEY BLOCK----- diff --git a/scripts/keys/ellemouton.asc b/scripts/keys/ellemouton.asc new file mode 100644 index 0000000000..712bf45cbb --- /dev/null +++ b/scripts/keys/ellemouton.asc @@ -0,0 +1,52 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQINBGDR6hoBEADEf6B/D+W0vWJv/JMZitXCPla0YQaf88KKrPZZ5z9oIcmJwy1t +KXsDQScUoMf4X+tSKT01SELDNroGu1UWRgxGpRj/Z0lEFp16IOPqmG2AKKNmw/KG +CBeZg4tmmf13i50Q7E3hTQxP3f5rC0rNZiqFgNmqrSSiNi4wjZ0Ud6wjOZ6yZBSN +jP8mg9rmSx4KSTSudy3sdZtKj+X4qI9G6+bYQhVbeoN1Yjhd6JsNEraRwHcS9rXi +lx9gzRV6StNo0lXVs4ewxkilpzexCRwJ/fcVwN/0DQWo9mirah0y0M32LkVa+GU/ +lPBxFoPSpqkFuLDEuzinobWjibMxpbTzEveacLpM4/qpuIO2jfCO0v7RzKAOk/PR +/EcK5u7LPwe8voZpPi504ZhKGCeCUNiF8NevSvAHy6wUnxcTuDyd6M/TpiG+pTZw +EMn12hwPIky272TtJXCvmoiBJ4DQBfH13WSVbkwic0p/42B5KKtGgzVhtbJMd3Tw +Yo8LnhYBTzKw/iNreCsRb3KUm0gSSC2bWhgiekmCOxS4QLfHAaSFDTKZK5guP+S+ +6Hf3RuKyotFua61ijOEYjeSk48mLQ3kEhIwGB0ZpQgyXHBa1m5v/wmZU3IzbZ5aT +sSXGOMfrki+sGOFBklUFqnL5jlje9ctmykyoJz3KJ7Ps6dbq9h1yv3Q45wARAQAB +tCNFbGxlIE1vdXRvbiA8ZWxsZS5tb3V0b25AZ21haWwuY29tPokCVAQTAQgAPhYh +BCaYTLaeuMSiYZb3pNfZFjdgJvF3BQJg0eoaAhsDBQkFo5qABQsJCAcCBhUKCQgL +AgQWAgMBAh4BAheAAAoJENfZFjdgJvF3VfcP/RH5vGlRXcqVNja3IEEymaPXf91z +Bhp47Er++a4t4jhQSwy1b27P5XnetciH4ZBpCc+ZQOV/h+ec4kvLU8L7GCkcISET +PJbfNxu4QHYDNQ6YOKL5wwpgnnlD7jWnW5KJwBTFTwzP+AYNG9CbPnC24Vwa5vSt +Hq6KSmcOJltp/Q2/JDb7gmZyX/PlyrR3nelLcQHdEo6ltlkr1z/Eog2jxrpB2uTu +2vz01hN8w8mXUi6Qvt98KtsSvW4iZvNgX7NEyZJXDGPtLny9JvDHsNkVAhkvP21Y +3l4eRvgcRR1zSn+6fNQoDPvZflUVzHr0TPoccpD3ZuG6WYXpHIaRcFnvYgq0Ay10 +P7t/teVRXizbC+LAhO5F+HpDPgcl/bprrwy0+grpeXKCr86VTZcCPrkVY6c+Pb9/ +SSR/9wAoH0rVKIXzFekO3HPv/ED+SDIESqAbw3Apq9jHog/9dQSXYKZRAfqvr16K +Ff2fN0DXoqtP1O7wTxSkMbWVUdBaUTrxJrbP0ZjzSNKNjaZs061enV9UlIPZzHdx +kjzYngiioK3PDZoz2tCWOJIP/043m4CXffSK6Skqy8VTahVgwdojgJ4xVDhwlEoP +q3soQeekFkbJdUJCuc/gfnp6Rl+8qTpdkeSv/01TxJQ1uGWWxA9m6rqTMMg1d8ng +oOX1YE0O1dPSY/H8uQINBGDR6hoBEACsTggnYnu8mSBgMgQBCB1LhWElARFiU0bx +EuERS7eZzetGzrvVIwM70EGhH7BKOXIrw1LzwE4kBDZQui5vrLajghDA9wTkZ7R/ +zJM/tF1EkrmVv5b4daWZOfAhKG7FBh3fNRfumbdv5D5KSIu2vpo7+coBeC/NyIEo +V5Qi7wmeU2lHiU1Xn32GkaW14Jak9jeEYHkw+p3cKPloWshYEPrVCCM8P6HI5LUL +NrmzWPON9RWWWFbuDoW4wpejExJZWBsQ9k7txBOYfVJShELb/a+/6GNtTmN42cEM +YbgsI+KqCxSyEg2F0rn/s+n5D6hW1BaCHiBxucC1h49xBksTDxiyknfalBuXvXiX +gRHfskkhtiMMfopukNXABwvORcXNQ7LiFwD/sUc8KcykMmy9aXSsFO5u64BcO7BR +Ww8EwSds9L+1C0uAYL0ZaFUf5juSCNFWyLfUhwEJ4iKHA2E03zv27zcooZFIT0tU +RNaeHpHXYcXy5Hv3B+nD02stXkT8ACVCLMqgmoOJ5i4aOkOnaUyXeP1JFwW51qm8 +cC/LLc87mHZhh1gOsNHiy0a8VCI3KIY5trfcw9TXd2fs/bMTrbq3f8Ycu+1t0hXu +KdE+Rpk7GUqhX54ODhiHtpBmIW8Y8/zGTqekYSvmtRJZl1LTpXRzReglYR33K4uQ +8lkTbWAyEwARAQABiQI8BBgBCAAmFiEEJphMtp64xKJhlvek19kWN2Am8XcFAmDR +6hoCGwwFCQWjmoAACgkQ19kWN2Am8XfvgBAAgSqcWZ3+XcaJzyifmRb7Bjc9yZgm +u/k/yneEqHYxjvvX+1lbiBu8zIB24FnaHTmO+Likzn6cviL8HLN7z/OGamtE9LJE +/laUKDulqvvcIwkiQPb6dIQ7niML4vksfaeCY2Z2SwR2mpFDWmshxe4gRgN/7rNm +YGrBE5VF/FT7O4lHA9v32/MwDmlxUVPDxzH2N3ez2fHJGsm0VdjAgFmDhTrnfAE5 +8OccQdijzGeQughlp5wnam4rby3hp79fKbfsIL1yO5BVLIf204j6aP8VvYesYOCS +D4i87igcKxI9bAU9TXSPod8zXghtNOAKBKOQnOu72+nH3cBAcBSCQ1gqdn/6/pdv +d3+X5572BkvI8tnptiktcAwrtj5GTOkSXQZfBG2fEJK2h7pbqOK6btvC9qjDmtv6 +C/t0UpKRTPYOjIEmFgOj8d9jSQWQNQjMlCnTJK7HSassSfJsUpnjfKIGlK6ezr2Z +DbEvoAlqYcMV1Oe2rM16C6/ijgW1aAw5SrvDfvVpctzO1QS10A53OGJ4CkPaY0Uj +HYuiIux/0v80QSID+UfR4ZhRBmXYlBgiWtld7tiNOoq23NSgwKirqIqxQHlJpuRV +k98q4zTjD4q+RqpRVzBgK7KI/bm1yGoZ0tLbvE9RQXPRk6Ashs0HzZeEM4v8wvYj +hTV2kdJi+8+qqUY= +=0VfH +-----END PGP PUBLIC KEY BLOCK----- diff --git a/scripts/keys/halseth.asc b/scripts/keys/halseth.asc deleted file mode 100644 index c757139485..0000000000 --- a/scripts/keys/halseth.asc +++ /dev/null @@ -1,30 +0,0 @@ ------BEGIN PGP PUBLIC KEY BLOCK----- - -mQENBFmMHhMBCADJErQPmbQYFlVwtKD7UDLfm28ZKUyAt7WDzgYN/aPL3eQb1xOT -NHI20F8TN3GXb3yy+57ETLy4GXADrb7BxXbvYOsFMZuoadcbnRmgKV28R6VRsSGL -Zyp5Ol5KrR39I5G6FdIIP/qSER0oSe/2KySRgob8YQlF24NLI/5RKgyayj4kdaou -j7ghVpWv8MpMzM1TEKlerOBhU88B6l0vjmJcxHxbCkc5xqqDQya7Ll8VAnLIYYNF -WTduugWn8JC9Ys3+emLI78VMoaMEMkBeUS/zrTFDZB6DufDCsNtw9YhM/anok74d -5kvcQctnrINiHP5EJf7yJ1PREAmW22ZMla7TABEBAAG0I0pvaGFuIFQgSGFsc2V0 -aCA8am9oYW50aEBnbWFpbC5jb20+iQFUBBMBCAA+AhsDBQsJCAcCBhUICQoLAgQW -AgMBAh4BAheAFiEEerPX9ZEXCIQnllE0Fbqtop2iDSYFAl1jj2EFCQeZ2E4ACgkQ -Fbqtop2iDSaTYwf+IY9XAGbjyDO8mNM1WE0kSpE+uXcWW98RPhm38TMjIX/0IUU8 -MOsARvCEkbXyqUMiOiuicx2BoflFvGutRyqQtI2sXSR/uCb1q3kO8t7RypC3vOok -iJAYXVUaZZb4EyYY67gmjf6i+SHmy8fSIOo2I+vlDf1Ii4eZyp45+XzDcReN35fr -bkXLUdC83Fqk70/74PhPqDwR3JwuVauV8GKHz3jRxdtebKbF7RoiUI6J5oWtkixH -ZDpwlzPVLdB8aMj7vDW72h/Z2UrDZu0inmQdiLPeHy2NydXlDlm2xVviOMcCFY1O -CTiiD2vlgxWku3z5Nhcx+cX8VmySGWJqGMBdkbkBDQRZjB4TAQgA4TBIlqcqoiPL -HESiHQCEU0DhfC7BTRL73SxS6gK4MmE0jtz27AA/+rchcUv3om4vVNw4dvkaRa0v -mhydMlaF+0Y/7gqTqaZSuXrl36nqw63B1l//Kgg7TKNX4X+LZ8hxqKj9xxocpbR1 -Qtb8h3TXb/aaWJU/o1TydvQXSOyH1w8R1S3PF9nrJJZb8BNrJ/A3SFqnvihFvLdN -qE7IfpHAIoaXW66e6V1FnF22c4a0D90qV/vMojGJO/mdrFLy3OwoOjAPFLJlnQDo -NlIXl9U7F7APJjGKuFJAa2QkdwXvcqRbc5GC120xL8ymuDQKp2K/rYRm/AN9IQ3X -xfR/kcNnGwARAQABiQE8BBgBCAAmAhsMFiEEerPX9ZEXCIQnllE0Fbqtop2iDSYF -Al1jj4cFCQeZ2HQACgkQFbqtop2iDSYcjAgAv19HsT/9MZI7xDkuzAWDxCAT5FDh -XgmPy9Bl/EFR83ETony7gpd+AA2GoDa/zd0js1Jz3YK0HnThKSRxyOIJ4CX3zgGW -nDT+T4oBxNwqmUH5eJ+2f09OhTXY9kcgTWoGFKWhmJi9uHzTOJDQ7ltDYOFBB6vY -kWrrHg62P3QGD6dgeOZlirtNYvAI6239fT+W8R4CaE17tiwK727nmncWX+11J7wf -h6XkQY6QbE1bHC7UG45HLJQvnTHu7C72W0aKfPZF5s8sALm31jmbVMfNbu8vzo5Z -o2WNAaw1TEseC+HCVwco7xJcy4Enr6WEFoKAeYiv5VUSVCb56IMSJTpK2Q== -=0Zw/ ------END PGP PUBLIC KEY BLOCK----- diff --git a/scripts/verify-install.sh b/scripts/verify-install.sh index 98c2db8244..8f9e0dc2a0 100755 --- a/scripts/verify-install.sh +++ b/scripts/verify-install.sh @@ -21,14 +21,13 @@ MIN_REQUIRED_SIGNATURES=5 KEYS=() KEYS+=("F4FC70F07310028424EFC20A8E4256593F177720 guggero") KEYS+=("15E7ECF257098A4EF91655EB4CA7FE54A6213C91 carlaKC") -KEYS+=("9C8D61868A7C492003B2744EE7D737B67FA592C7 cfromknecht") KEYS+=("E4D85299674B2D31FAA1892E372CBD7633C61696 roasbeef") KEYS+=("729E9D9D92C75A5FBFEEE057B5DD717BEF7CA5B1 wpaulino") KEYS+=("7E81EF6B9989A9CC93884803118759E83439A9B1 Crypt-iQ") -KEYS+=("7AB3D7F5911708842796513415BAADA29DA20D26 halseth") KEYS+=("9FC6B0BFD597A94DBF09708280E5375C094198D8 bhandras") KEYS+=("E97A1AB6C77A1D2B72F50A6F90E00CCB1C74C611 arshbot") KEYS+=("EB13A98091E8D67CDD7FC5A7E9FE7FE00AD163A4 positiveblue") +KEYS+=("26984CB69EB8C4A26196F7A4D7D916376026F177 ellemouton") TEMP_DIR=$(mktemp -d /tmp/lnd-sig-verification-XXXXXX) diff --git a/server.go b/server.go index 354b90dbaa..b0575dcfc7 100644 --- a/server.go +++ b/server.go @@ -6,23 +6,23 @@ import ( "crypto/rand" "encoding/hex" "fmt" - "image/color" + //"image/color" + "io/ioutil" "math/big" prand "math/rand" "net" - "regexp" "strconv" "strings" "sync" "sync/atomic" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/connmgr" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/go-errors/errors" sphinx "github.com/lightningnetwork/lightning-onion" "github.com/lightningnetwork/lnd/autopilot" @@ -47,11 +47,13 @@ import ( "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lncfg" + "github.com/lightningnetwork/lnd/lnencrypt" "github.com/lightningnetwork/lnd/lnpeer" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet/chainfee" + "github.com/lightningnetwork/lnd/lnwallet/rpcwallet" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/nat" "github.com/lightningnetwork/lnd/netann" @@ -66,6 +68,7 @@ import ( "github.com/lightningnetwork/lnd/sweep" "github.com/lightningnetwork/lnd/ticker" "github.com/lightningnetwork/lnd/tor" + "github.com/lightningnetwork/lnd/tor/onionfile" "github.com/lightningnetwork/lnd/walletunlocker" "github.com/lightningnetwork/lnd/watchtower/blob" "github.com/lightningnetwork/lnd/watchtower/wtclient" @@ -95,6 +98,10 @@ const ( // value used or a particular peer will be chosen between 0s and this // value. maxInitReconnectDelay = 30 + + // multiAddrConnectionStagger is the number of seconds to wait between + // attempting to a peer with each of its advertised addresses. + multiAddrConnectionStagger = 10 * time.Second ) var ( @@ -111,10 +118,6 @@ var ( // gracefully exiting. ErrServerShuttingDown = errors.New("server is shutting down") - // validColorRegexp is a regexp that lets you check if a particular - // color string matches the standard hex color format #RRGGBB. - validColorRegexp = regexp.MustCompile("^#[A-Fa-f0-9]{6}$") - // MaxFundingAmount is a soft-limit of the maximum channel size // currently accepted within the Lightning Protocol. This is // defined in BOLT-0002, and serves as an initial precautionary limit @@ -123,7 +126,7 @@ var ( // At the moment, this value depends on which chain is active. It is set // to the value under the Bitcoin chain as default. // - // TODO(roasbeef): add command line param to modify + // TODO(roasbeef): add command line param to modify. MaxFundingAmount = funding.MaxBtcFundingAmount ) @@ -195,6 +198,10 @@ type server struct { peerConnectedListeners map[string][]chan<- lnpeer.Peer peerDisconnectedListeners map[string][]chan<- struct{} + // TODO(yy): the Brontide.Start doesn't know this value, which means it + // will continue to send messages even if there are no active channels + // and the value below is false. Once it's pruned, all its connections + // will be closed, thus the Brontide.Start will return an error. persistentPeers map[string]bool persistentPeersBackoff map[string]time.Duration persistentPeerAddrs map[string][]*lnwire.NetAddress @@ -220,6 +227,12 @@ type server struct { // intended to replace it. scheduledPeerConnection map[string]func() + // pongBuf is a shared pong reply buffer we'll use across all active + // peer goroutines. We know the max size of a pong message + // (lnwire.MaxPongBytes), so we can allocate this ahead of time, and + // avoid allocations each time we need to send a pong message. + pongBuf []byte + cc *chainreg.ChainControl fundingMgr *funding.Manager @@ -331,7 +344,6 @@ func (s *server) updatePersistentPeerAddrs() error { for { select { - case <-s.quit: return @@ -521,6 +533,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, NoAnchors: cfg.ProtocolOptions.NoAnchorCommitments(), NoWumbo: !cfg.ProtocolOptions.Wumbo(), NoScriptEnforcementLease: cfg.ProtocolOptions.NoScriptEnforcementLease(), + NoKeysend: !cfg.AcceptKeySend, }) if err != nil { return nil, err @@ -573,6 +586,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, peerErrors: make(map[string]*queue.CircularBuffer), ignorePeerTermination: make(map[*peer.Brontide]struct{}), scheduledPeerConnection: make(map[string]func()), + pongBuf: make([]byte, lnwire.MaxPongBytes), peersByPub: make(map[string]*peer.Brontide), inboundPeers: make(map[string]*peer.Brontide), @@ -586,11 +600,6 @@ func newServer(cfg *Config, listenAddrs []net.Addr, quit: make(chan struct{}), } - s.witnessBeacon = &preimageBeacon{ - wCache: dbs.ChanStateDB.NewWitnessCache(), - subscribers: make(map[uint64]*preimageSubscriber), - } - currentHash, currentHeight, err := s.cc.ChainIO.GetBestBlock() if err != nil { return nil, err @@ -645,7 +654,15 @@ func newServer(cfg *Config, listenAddrs []net.Addr, if err != nil { return nil, err } - s.interceptableSwitch = htlcswitch.NewInterceptableSwitch(s.htlcSwitch) + s.interceptableSwitch = htlcswitch.NewInterceptableSwitch( + s.htlcSwitch, lncfg.DefaultFinalCltvRejectDelta, + s.cfg.RequireInterceptor, + ) + + s.witnessBeacon = newPreimageBeacon( + dbs.ChanStateDB.NewWitnessCache(), + s.interceptableSwitch.ForwardPacket, + ) chanStatusMgrCfg := &netann.ChanStatusConfig{ ChanStatusSampleInterval: cfg.ChanStatusSampleInterval, @@ -756,7 +773,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, // the network. // // We'll start by parsing the node color from configuration. - color, err := parseHexColor(cfg.Color) + color, err := lncfg.ParseHexColor(cfg.Color) if err != nil { srvrLog.Errorf("unable to parse color: %v\n", err) return nil, err @@ -1010,6 +1027,20 @@ func newServer(cfg *Config, listenAddrs []net.Addr, // breach events from the ChannelArbitrator to the breachArbiter, contractBreaches := make(chan *contractcourt.ContractBreachEvent, 1) + s.breachArbiter = contractcourt.NewBreachArbiter(&contractcourt.BreachConfig{ + CloseLink: closeLink, + DB: s.chanStateDB, + Estimator: s.cc.FeeEstimator, + GenSweepScript: newSweepPkScriptGen(cc.Wallet), + Notifier: cc.ChainNotifier, + PublishTransaction: cc.Wallet.PublishTransaction, + ContractBreaches: contractBreaches, + Signer: cc.Wallet.Cfg.Signer, + Store: contractcourt.NewRetributionStore( + dbs.ChanStateDB, + ), + }) + s.chainArb = contractcourt.NewChainArbitrator(contractcourt.ChainArbitratorConfig{ ChainHash: *s.cfg.ActiveNetParams.GenesisHash, IncomingBroadcastDelta: lncfg.DefaultIncomingBroadcastDelta, @@ -1058,8 +1089,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, }, IsOurAddress: cc.Wallet.IsOurAddress, ContractBreach: func(chanPoint wire.OutPoint, - breachRet *lnwallet.BreachRetribution, - markClosed func() error) error { + breachRet *lnwallet.BreachRetribution) error { // processACK will handle the breachArbiter ACKing the // event. @@ -1071,8 +1101,9 @@ func newServer(cfg *Config, listenAddrs []net.Addr, } // If the breachArbiter successfully handled - // the event, we can mark the channel closed. - finalErr <- markClosed() + // the event, we can signal that the handoff + // was successful. + finalErr <- nil } event := &contractcourt.ContractBreachEvent{ @@ -1088,9 +1119,8 @@ func newServer(cfg *Config, listenAddrs []net.Addr, return ErrServerShuttingDown } - // We'll wait for a final error to be available, either - // from the breachArbiter or from our markClosed - // function closure. + // We'll wait for a final error to be available from + // the breachArbiter. select { case err := <-finalErr: return err @@ -1109,22 +1139,9 @@ func newServer(cfg *Config, listenAddrs []net.Addr, PaymentsExpirationGracePeriod: cfg.PaymentsExpirationGracePeriod, IsForwardedHTLC: s.htlcSwitch.IsForwardedHTLC, Clock: clock.NewDefaultClock(), + SubscribeBreachComplete: s.breachArbiter.SubscribeBreachComplete, }, dbs.ChanStateDB) - s.breachArbiter = contractcourt.NewBreachArbiter(&contractcourt.BreachConfig{ - CloseLink: closeLink, - DB: s.chanStateDB, - Estimator: s.cc.FeeEstimator, - GenSweepScript: newSweepPkScriptGen(cc.Wallet), - Notifier: cc.ChainNotifier, - PublishTransaction: cc.Wallet.PublishTransaction, - ContractBreaches: contractBreaches, - Signer: cc.Wallet.Cfg.Signer, - Store: contractcourt.NewRetributionStore( - dbs.ChanStateDB, - ), - }) - // Select the configuration and furnding parameters for Bitcoin or // Litecoin, depending on the primary registered chain. primaryChain := cfg.registeredChains.PrimaryChain() @@ -1485,20 +1502,28 @@ func newServer(cfg *Config, listenAddrs []net.Addr, // createLivenessMonitor creates a set of health checks using our configured // values and uses these checks to create a liveliness monitor. Available // health checks, -// - chainHealthCheck +// - chainHealthCheck (will be disabled for --nochainbackend mode) // - diskCheck // - tlsHealthCheck // - torController, only created when tor is enabled. // If a health check has been disabled by setting attempts to 0, our monitor // will not run it. func (s *server) createLivenessMonitor(cfg *Config, cc *chainreg.ChainControl) { + chainBackendAttempts := cfg.HealthChecks.ChainCheck.Attempts + if cfg.Bitcoin.Node == "nochainbackend" { + srvrLog.Info("Disabling chain backend checks for " + + "nochainbackend mode") + + chainBackendAttempts = 0 + } + chainHealthCheck := healthcheck.NewObservation( "chain backend", cc.HealthCheck, cfg.HealthChecks.ChainCheck.Interval, cfg.HealthChecks.ChainCheck.Timeout, cfg.HealthChecks.ChainCheck.Backoff, - cfg.HealthChecks.ChainCheck.Attempts, + chainBackendAttempts, ) diskCheck := healthcheck.NewObservation( @@ -1530,8 +1555,30 @@ func (s *server) createLivenessMonitor(cfg *Config, cc *chainreg.ChainControl) { tlsHealthCheck := healthcheck.NewObservation( "tls", func() error { + + var emptyKeyRing keychain.KeyRing + certBytes, err := ioutil.ReadFile(cfg.TLSCertPath) + if err != nil { + return err + } + keyBytes, err := ioutil.ReadFile(cfg.TLSKeyPath) + if err != nil { + return err + } + + // If key encryption is set, then decrypt the file. + // We don't need to do a file type check here because GenCertPair + // has been ran with the same value for cfg.TLSEncryptKey. + if cfg.TLSEncryptKey { + reader := bytes.NewReader(keyBytes) + keyBytes, err = lnencrypt.DecryptPayloadFromReader(reader, emptyKeyRing) + if err != nil { + return err + } + } + _, parsedCert, err := cert.LoadCert( - cfg.TLSCertPath, cfg.TLSKeyPath, + certBytes, keyBytes, ) if err != nil { return err @@ -1576,6 +1623,32 @@ func (s *server) createLivenessMonitor(cfg *Config, cc *chainreg.ChainControl) { checks = append(checks, torConnectionCheck) } + // If remote signing is enabled, add the healthcheck for the remote + // signing RPC interface. + if s.cfg.RemoteSigner != nil && s.cfg.RemoteSigner.Enable { + // Because we have two cascading timeouts here, we need to add + // some slack to the "outer" one of them in case the "inner" + // returns exactly on time. + overhead := time.Millisecond * 10 + + remoteSignerConnectionCheck := healthcheck.NewObservation( + "remote signer connection", + rpcwallet.HealthCheck( + s.cfg.RemoteSigner, + + // For the health check we might to be even + // stricter than the initial/normal connect, so + // we use the health check timeout here. + cfg.HealthChecks.RemoteSigner.Timeout, + ), + cfg.HealthChecks.RemoteSigner.Interval, + cfg.HealthChecks.RemoteSigner.Timeout+overhead, + cfg.HealthChecks.RemoteSigner.Backoff, + cfg.HealthChecks.RemoteSigner.Attempts, + ) + checks = append(checks, remoteSignerConnectionCheck) + } + // If we have not disabled all of our health checks, we create a // liveliness monitor with our configured checks. s.livelinessMonitor = healthcheck.NewMonitor( @@ -1595,16 +1668,16 @@ func (s *server) Started() bool { // cleaner is used to aggregate "cleanup" functions during an operation that // starts several subsystems. In case one of the subsystem fails to start // and a proper resource cleanup is required, the "run" method achieves this -// by running all these added "cleanup" functions +// by running all these added "cleanup" functions. type cleaner []func() error // add is used to add a cleanup function to be called when -// the run function is executed +// the run function is executed. func (c cleaner) add(cleanup func() error) cleaner { return append(c, cleanup) } -// run is used to run all the previousely added cleanup functions +// run is used to run all the previousely added cleanup functions. func (c cleaner) run() { for i := len(c) - 1; i >= 0; i-- { if err := c[i](); err != nil { @@ -1734,6 +1807,21 @@ func (s *server) Start() error { } cleanup = cleanup.add(s.fundingMgr.Stop) + // htlcSwitch must be started before chainArb since the latter + // relies on htlcSwitch to deliver resolution message upon + // start. + if err := s.htlcSwitch.Start(); err != nil { + startErr = err + return + } + cleanup = cleanup.add(s.htlcSwitch.Stop) + + if err := s.interceptableSwitch.Start(); err != nil { + startErr = err + return + } + cleanup = cleanup.add(s.interceptableSwitch.Stop) + if err := s.chainArb.Start(); err != nil { startErr = err return @@ -1764,12 +1852,6 @@ func (s *server) Start() error { } cleanup = cleanup.add(s.sphinx.Stop) - if err := s.htlcSwitch.Start(); err != nil { - startErr = err - return - } - cleanup = cleanup.add(s.htlcSwitch.Stop) - if err := s.chanStatusMgr.Start(); err != nil { startErr = err return @@ -1849,6 +1931,43 @@ func (s *server) Start() error { return nil }) + // If peers are specified as a config option, we'll add those + // peers first. + for _, peerAddrCfg := range s.cfg.AddPeers { + parsedPubkey, parsedHost, err := lncfg.ParseLNAddressPubkey( + peerAddrCfg, + ) + if err != nil { + startErr = fmt.Errorf("unable to parse peer "+ + "pubkey from config: %v", err) + return + } + addr, err := parseAddr(parsedHost, s.cfg.net) + if err != nil { + startErr = fmt.Errorf("unable to parse peer "+ + "address provided as a config option: "+ + "%v", err) + return + } + + peerAddr := &lnwire.NetAddress{ + IdentityKey: parsedPubkey, + Address: addr, + ChainNet: s.cfg.ActiveNetParams.Net, + } + + err = s.ConnectToPeer( + peerAddr, true, + s.cfg.ConnectionTimeout, + ) + if err != nil { + startErr = fmt.Errorf("unable to connect to "+ + "peer address provided as a config "+ + "option: %v", err) + return + } + } + // Subscribe to NodeAnnouncements that advertise new addresses // our persistent peers. if err := s.updatePersistentPeerAddrs(); err != nil { @@ -2299,6 +2418,39 @@ func initNetworkBootstrappers(s *server) ([]discovery.NetworkPeerBootstrapper, e return bootStrappers, nil } +// createBootstrapIgnorePeers creates a map of peers that the bootstrap process +// needs to ignore, which is made of three parts, +// - the node itself needs to be skipped as it doesn't make sense to connect +// to itself. +// - the peers that already have connections with, as in s.peersByPub. +// - the peers that we are attempting to connect, as in s.persistentPeers. +func (s *server) createBootstrapIgnorePeers() map[autopilot.NodeID]struct{} { + s.mu.RLock() + defer s.mu.RUnlock() + + ignore := make(map[autopilot.NodeID]struct{}) + + // We should ignore ourselves from bootstrapping. + selfKey := autopilot.NewNodeID(s.identityECDH.PubKey()) + ignore[selfKey] = struct{}{} + + // Ignore all connected peers. + for _, peer := range s.peersByPub { + nID := autopilot.NewNodeID(peer.IdentityKey()) + ignore[nID] = struct{}{} + } + + // Ignore all persistent peers as they have a dedicated reconnecting + // process. + for pubKeyStr := range s.persistentPeers { + var nID autopilot.NodeID + copy(nID[:], []byte(pubKeyStr)) + ignore[nID] = struct{}{} + } + + return ignore +} + // peerBootstrapper is a goroutine which is tasked with attempting to establish // and maintain a target minimum number of outbound connections. With this // invariant, we ensure that our node is connected to a diverse set of peers @@ -2309,13 +2461,12 @@ func (s *server) peerBootstrapper(numTargetPeers uint32, defer s.wg.Done() - // ignore is a set used to keep track of peers already retrieved from - // our bootstrappers in order to avoid duplicates. - ignore := make(map[autopilot.NodeID]struct{}) + // Before we continue, init the ignore peers map. + ignoreList := s.createBootstrapIgnorePeers() // We'll start off by aggressively attempting connections to peers in // order to be a part of the network as soon as possible. - s.initialPeerBootstrap(ignore, numTargetPeers, bootstrappers) + s.initialPeerBootstrap(ignoreList, numTargetPeers, bootstrappers) // Once done, we'll attempt to maintain our target minimum number of // peers. @@ -2387,13 +2538,10 @@ func (s *server) peerBootstrapper(numTargetPeers uint32, // With the number of peers we need calculated, we'll // query the network bootstrappers to sample a set of // random addrs for us. - s.mu.RLock() - ignoreList := make(map[autopilot.NodeID]struct{}) - for _, peer := range s.peersByPub { - nID := autopilot.NewNodeID(peer.IdentityKey()) - ignoreList[nID] = struct{}{} - } - s.mu.RUnlock() + // + // Before we continue, get a copy of the ignore peers + // map. + ignoreList = s.createBootstrapIgnorePeers() peerAddrs, err := discovery.MultiSourceBootstrap( ignoreList, numNeeded*2, bootstrappers..., @@ -2446,7 +2594,11 @@ const bootstrapBackOffCeiling = time.Minute * 5 // until the target number of peers has been reached. This ensures that nodes // receive an up to date network view as soon as possible. func (s *server) initialPeerBootstrap(ignore map[autopilot.NodeID]struct{}, - numTargetPeers uint32, bootstrappers []discovery.NetworkPeerBootstrapper) { + numTargetPeers uint32, + bootstrappers []discovery.NetworkPeerBootstrapper) { + + srvrLog.Debugf("Init bootstrap with targetPeers=%v, bootstrappers=%v, "+ + "ignore=%v", numTargetPeers, len(bootstrappers), len(ignore)) // We'll start off by waiting 2 seconds between failed attempts, then // double each time we fail until we hit the bootstrapBackOffCeiling. @@ -2567,7 +2719,10 @@ func (s *server) createNewHiddenService() error { onionCfg := tor.AddOnionConfig{ VirtualPort: defaultPeerPort, TargetPorts: listenPorts, - Store: tor.NewOnionFile(s.cfg.Tor.PrivateKeyPath, 0600), + Store: onionfile.NewOnionFile( + s.cfg.Tor.PrivateKeyPath, 0600, s.cfg.Tor.EncryptKey, + s.cc.KeyRing, + ), } switch { @@ -2647,6 +2802,59 @@ func (s *server) genNodeAnnouncement(refresh bool, return *s.currentNodeAnn, nil } +// updateAndBrodcastSelfNode generates a new node announcement +// applying the giving modifiers and updating the time stamp +// to ensure it propagates through the network. Then it brodcasts +// it to the network. +func (s *server) updateAndBrodcastSelfNode( + modifiers ...netann.NodeAnnModifier) error { + + newNodeAnn, err := s.genNodeAnnouncement(true, modifiers...) + if err != nil { + return fmt.Errorf("unable to generate new node "+ + "announcement: %v", err) + } + + // Update the on-disk version of our announcement. + // Load and modify self node istead of creating anew instance so we + // don't risk overwriting any existing values. + selfNode, err := s.graphDB.SourceNode() + if err != nil { + return fmt.Errorf("unable to get current source node: %v", err) + } + + selfNode.HaveNodeAnnouncement = true + selfNode.LastUpdate = time.Unix(int64(newNodeAnn.Timestamp), 0) + selfNode.Addresses = newNodeAnn.Addresses + selfNode.Alias = newNodeAnn.Alias.String() + selfNode.Features = lnwire.NewFeatureVector( + newNodeAnn.Features, lnwire.Features, + ) + selfNode.Color = newNodeAnn.RGBColor + selfNode.AuthSigBytes = newNodeAnn.Signature.ToSignatureBytes() + + copy(selfNode.PubKeyBytes[:], s.identityECDH.PubKey().SerializeCompressed()) + + if err := s.graphDB.SetSourceNode(selfNode); err != nil { + return fmt.Errorf("can't set self node: %v", err) + } + + // Update the feature bits for the SetNodeAnn in case they changed. + s.featureMgr.SetRaw( + feature.SetNodeAnn, selfNode.Features.RawFeatureVector, + ) + + // Finally, propagate it to the nodes in the network. + err = s.BroadcastMessage(nil, &newNodeAnn) + if err != nil { + rpcsLog.Debugf("Unable to broadcast new node "+ + "announcement to peers: %v", err) + return err + } + + return nil +} + type nodeAddresses struct { pubKey *btcec.PublicKey addresses []net.Addr @@ -2771,6 +2979,9 @@ func (s *server) establishPersistentConnections() error { return err } + srvrLog.Debugf("Establishing %v persistent connections on start", + len(nodeAddrsMap)) + // Acquire and hold server lock until all persistent connection requests // have been recorded and sent to the connection manager. s.mu.Lock() @@ -2798,34 +3009,24 @@ func (s *server) establishPersistentConnections() error { IdentityKey: nodeAddr.pubKey, Address: address, } - srvrLog.Debugf("Attempting persistent connection to "+ - "channel peer %v", lnAddr) - // Send the persistent connection request to the - // connection manager, saving the request itself so we - // can cancel/restart the process as needed. - connReq := &connmgr.ConnReq{ - Addr: lnAddr, - Permanent: true, - } + s.persistentPeerAddrs[pubStr] = append( + s.persistentPeerAddrs[pubStr], lnAddr) + } - s.persistentConnReqs[pubStr] = append( - s.persistentConnReqs[pubStr], connReq) - - // We'll connect to the first 10 peers immediately, then - // randomly stagger any remaining connections if the - // stagger initial reconnect flag is set. This ensures - // that mobile nodes or nodes with a small number of - // channels obtain connectivity quickly, but larger - // nodes are able to disperse the costs of connecting to - // all peers at once. - if numOutboundConns < numInstantInitReconnect || - !s.cfg.StaggerInitialReconnect { - - go s.connMgr.Connect(connReq) - } else { - go s.delayInitialReconnect(connReq) - } + // We'll connect to the first 10 peers immediately, then + // randomly stagger any remaining connections if the + // stagger initial reconnect flag is set. This ensures + // that mobile nodes or nodes with a small number of + // channels obtain connectivity quickly, but larger + // nodes are able to disperse the costs of connecting to + // all peers at once. + if numOutboundConns < numInstantInitReconnect || + !s.cfg.StaggerInitialReconnect { + + go s.connectToPersistentPeer(pubStr) + } else { + go s.delayInitialReconnect(pubStr) } numOutboundConns++ @@ -2834,16 +3035,15 @@ func (s *server) establishPersistentConnections() error { return nil } -// delayInitialReconnect will attempt a reconnection using the passed connreq -// after sampling a value for the delay between 0s and the -// maxInitReconnectDelay. +// delayInitialReconnect will attempt a reconnection to the given peer after +// sampling a value for the delay between 0s and the maxInitReconnectDelay. // // NOTE: This method MUST be run as a goroutine. -func (s *server) delayInitialReconnect(connReq *connmgr.ConnReq) { +func (s *server) delayInitialReconnect(pubStr string) { delay := time.Duration(prand.Intn(maxInitReconnectDelay)) * time.Second select { case <-time.After(delay): - s.connMgr.Connect(connReq) + s.connectToPersistentPeer(pubStr) case <-s.quit: } } @@ -2858,6 +3058,7 @@ func (s *server) prunePersistentPeerConnection(compressedPubKey [33]byte) { if perm, ok := s.persistentPeers[pubKeyStr]; ok && !perm { delete(s.persistentPeers, pubKeyStr) delete(s.persistentPeersBackoff, pubKeyStr) + delete(s.persistentPeerAddrs, pubKeyStr) s.cancelConnReqs(pubKeyStr, nil) s.mu.Unlock() @@ -3099,10 +3300,10 @@ func (s *server) InboundPeerConnected(conn net.Conn) { // If we already have an outbound connection to this peer, then ignore // this new connection. - if _, ok := s.outboundPeers[pubStr]; ok { - srvrLog.Debugf("Already have outbound connection for %x, "+ - "ignoring inbound connection", - nodePub.SerializeCompressed()) + if p, ok := s.outboundPeers[pubStr]; ok { + srvrLog.Debugf("Already have outbound connection for %v, "+ + "ignoring inbound connection from local=%v, remote=%v", + p, conn.LocalAddr(), conn.RemoteAddr()) conn.Close() return @@ -3111,8 +3312,9 @@ func (s *server) InboundPeerConnected(conn net.Conn) { // If we already have a valid connection that is scheduled to take // precedence once the prior peer has finished disconnecting, we'll // ignore this connection. - if _, ok := s.scheduledPeerConnection[pubStr]; ok { - srvrLog.Debugf("Ignoring connection, peer already scheduled") + if p, ok := s.scheduledPeerConnection[pubStr]; ok { + srvrLog.Debugf("Ignoring connection from %v, peer %v already "+ + "scheduled", conn.RemoteAddr(), p) conn.Close() return } @@ -3185,10 +3387,10 @@ func (s *server) OutboundPeerConnected(connReq *connmgr.ConnReq, conn net.Conn) // If we already have an inbound connection to this peer, then ignore // this new connection. - if _, ok := s.inboundPeers[pubStr]; ok { - srvrLog.Debugf("Already have inbound connection for %x, "+ - "ignoring outbound connection", - nodePub.SerializeCompressed()) + if p, ok := s.inboundPeers[pubStr]; ok { + srvrLog.Debugf("Already have inbound connection for %v, "+ + "ignoring outbound connection from local=%v, remote=%v", + p, conn.LocalAddr(), conn.RemoteAddr()) if connReq != nil { s.connMgr.Remove(connReq.ID()) @@ -3309,6 +3511,8 @@ func (s *server) cancelConnReqs(pubStr string, skip *uint64) { } for _, connReq := range connReqs { + srvrLog.Tracef("Canceling %s:", connReqs) + // Atomically capture the current request identifier. connID := connReq.ID() @@ -3427,6 +3631,8 @@ func (s *server) peerConnected(conn net.Conn, connReq *connmgr.ConnReq, DisconnectPeer: s.DisconnectPeer, GenNodeAnnouncement: s.genNodeAnnouncement, + PongBuf: s.pongBuf, + PrunePersistentPeerConnection: s.prunePersistentPeerConnection, FetchLastChanUpdate: s.fetchLastChanUpdate(), @@ -3441,6 +3647,7 @@ func (s *server) peerConnected(conn net.Conn, connReq *connmgr.ConnReq, MaxAnchorsCommitFeeRate: chainfee.SatPerKVByte( s.cfg.MaxCommitFeeRateAnchors * 1000).FeePerKWeight(), ChannelCommitInterval: s.cfg.ChannelCommitInterval, + PendingCommitInterval: s.cfg.PendingCommitInterval, ChannelCommitBatchSize: s.cfg.ChannelCommitBatchSize, HandleCustomMessage: s.handleCustomMessage, Quit: s.quit, @@ -3813,19 +4020,49 @@ func (s *server) connectToPersistentPeer(pubKeyStr string) { s.persistentConnReqs[pubKeyStr] = updatedConnReqs + cancelChan, ok := s.persistentRetryCancels[pubKeyStr] + if !ok { + cancelChan = make(chan struct{}) + s.persistentRetryCancels[pubKeyStr] = cancelChan + } + // Any addresses left in addrMap are new ones that we have not made // connection requests for. So create new connection requests for those. - for _, addr := range addrMap { - connReq := &connmgr.ConnReq{ - Addr: addr, - Permanent: true, - } + // If there is more than one address in the address map, stagger the + // creation of the connection requests for those. + go func() { + ticker := time.NewTicker(multiAddrConnectionStagger) + defer ticker.Stop() - s.persistentConnReqs[pubKeyStr] = append( - s.persistentConnReqs[pubKeyStr], connReq, - ) - go s.connMgr.Connect(connReq) - } + for _, addr := range addrMap { + // Send the persistent connection request to the + // connection manager, saving the request itself so we + // can cancel/restart the process as needed. + connReq := &connmgr.ConnReq{ + Addr: addr, + Permanent: true, + } + + s.mu.Lock() + s.persistentConnReqs[pubKeyStr] = append( + s.persistentConnReqs[pubKeyStr], connReq, + ) + s.mu.Unlock() + + srvrLog.Debugf("Attempting persistent connection to "+ + "channel peer %v", addr) + + go s.connMgr.Connect(connReq) + + select { + case <-s.quit: + return + case <-cancelChan: + return + case <-ticker.C: + } + } + }() } // removePeer removes the passed peer from the server's state of all active @@ -3974,6 +4211,9 @@ func (s *server) connectToPeer(addr *lnwire.NetAddress, close(errChan) + srvrLog.Tracef("Brontide dialer made local=%v, remote=%v", + conn.LocalAddr(), conn.RemoteAddr()) + s.OutboundPeerConnected(nil, conn) } @@ -4090,26 +4330,6 @@ func (s *server) Peers() []*peer.Brontide { return peers } -// parseHexColor takes a hex string representation of a color in the -// form "#RRGGBB", parses the hex color values, and returns a color.RGBA -// struct of the same color. -func parseHexColor(colorStr string) (color.RGBA, error) { - // Check if the hex color string is a valid color representation. - if !validColorRegexp.MatchString(colorStr) { - return color.RGBA{}, errors.New("Color must be specified " + - "using a hexadecimal value in the form #RRGGBB") - } - - // Decode the hex color string to bytes. - // The resulting byte array is in the form [R, G, B]. - colorBytes, err := hex.DecodeString(colorStr[1:]) - if err != nil { - return color.RGBA{}, err - } - - return color.RGBA{R: colorBytes[0], G: colorBytes[1], B: colorBytes[2]}, nil -} - // computeNextBackoff uses a truncated exponential backoff to compute the next // backoff using the value of the exiting backoff. The returned duration is // randomized in either direction by 1/20 to prevent tight loops from @@ -4240,13 +4460,15 @@ func newSweepPkScriptGen( } // shouldPeerBootstrap returns true if we should attempt to perform peer -// boostrapping to actively seek our peers using the set of active network -// bootsrappers. +// bootstrapping to actively seek our peers using the set of active network +// bootstrappers. func shouldPeerBootstrap(cfg *Config) bool { isSimnet := (cfg.Bitcoin.SimNet || cfg.Litecoin.SimNet) isSignet := (cfg.Bitcoin.SigNet || cfg.Litecoin.SigNet) isRegtest := (cfg.Bitcoin.RegTest || cfg.Litecoin.RegTest) isDevNetwork := isSimnet || isSignet || isRegtest + // TODO(yy): remove the check on simnet/regtest such that the itest is + // covering the bootstrapping process. return !cfg.NoNetBootstrap && !isDevNetwork } diff --git a/server_test.go b/server_test.go index 48b2f652d7..c4d3bb681b 100644 --- a/server_test.go +++ b/server_test.go @@ -19,138 +19,281 @@ import ( "testing" "time" + "github.com/btcsuite/btcd/btcec" + "github.com/lightningnetwork/lnd/cert" + "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lncfg" + "github.com/lightningnetwork/lnd/lnencrypt" + "github.com/lightningnetwork/lnd/lntest/channels" + "github.com/lightningnetwork/lnd/lntest/mock" ) -func TestParseHexColor(t *testing.T) { - var colorTestCases = []struct { - test string - valid bool // If valid format - R byte - G byte - B byte - }{ - {"#123", false, 0, 0, 0}, - {"#1234567", false, 0, 0, 0}, - {"$123456", false, 0, 0, 0}, - {"#12345+", false, 0, 0, 0}, - {"#fFGG00", false, 0, 0, 0}, - {"", false, 0, 0, 0}, - {"#123456", true, 0x12, 0x34, 0x56}, - {"#C0FfeE", true, 0xc0, 0xff, 0xee}, - } - - // Perform the table driven tests. - for _, ct := range colorTestCases { - - color, err := parseHexColor(ct.test) - if !ct.valid && err == nil { - t.Fatalf("Invalid color string: %s, should return "+ - "error, but did not", ct.test) - } +const ( + testTLSCertDuration = 42 * time.Hour +) - if ct.valid && err != nil { - t.Fatalf("Color %s valid to parse: %s", ct.test, err) - } +var ( + extraIPs = []string{"1.1.1.1", "123.123.123.1", "199.189.12.12"} + extraDomains = []string{"home", "and", "away"} - // Ensure that the string to hex decoding is working properly. - if color.R != ct.R || color.G != ct.G || color.B != ct.B { - t.Fatalf("Color %s incorrectly parsed as %v", ct.test, color) - } - } -} + privateKeyPrefix = []byte("-----BEGIN EC PRIVATE KEY-----") + + privKeyBytes = channels.AlicesPrivKey + + privKey, _ = btcec.PrivKeyFromBytes( + btcec.S256(), privKeyBytes[:], + ) +) // TestTLSAutoRegeneration creates an expired TLS certificate, to test that a // new TLS certificate pair is regenerated when the old pair expires. This is // necessary because the pair expires after a little over a year. func TestTLSAutoRegeneration(t *testing.T) { - tempDirPath, err := ioutil.TempDir("", ".testLnd") + // Write an expired certificate to disk. + removeFiles, certDir, certPath, keyPath, expiredCert, _ := writeTestCertFiles( + t, true, false, nil, + ) + defer removeFiles(certDir) + + rpcListener := net.IPAddr{IP: net.ParseIP("127.0.0.1"), Zone: ""} + rpcListeners := make([]net.Addr, 0) + rpcListeners = append(rpcListeners, &rpcListener) + + // Now let's run the TLSManager's getConfig. If it works properly, it + // should delete the cert and create a new one. + cfg := &Config{ + TLSCertPath: certPath, + TLSKeyPath: keyPath, + TLSCertDuration: testTLSCertDuration, + RPCListeners: rpcListeners, + } + tlsManager := TLSManager{ + cfg: cfg, + keyRing: &mock.SecretKeyRing{}, + } + _, _, _, cleanUp, err := tlsManager.getConfig() if err != nil { - t.Fatalf("couldn't create temporary cert directory") + t.Fatalf("couldn't retrieve TLS config: %+v", err) } - defer os.RemoveAll(tempDirPath) + defer cleanUp() - certPath := tempDirPath + "/tls.cert" - keyPath := tempDirPath + "/tls.key" + // Grab the certificate to test that getTLSConfig did its job correctly + // and generated a new cert. + newCertData, err := tls.LoadX509KeyPair(certPath, keyPath) + if err != nil { + t.Fatalf("couldn't grab new certificate") + } - certDerBytes, keyBytes := genExpiredCertPair(t, tempDirPath) - expiredCert, err := x509.ParseCertificate(certDerBytes) + newCert, err := x509.ParseCertificate(newCertData.Certificate[0]) if err != nil { - t.Fatalf("failed to parse certificate: %v", err) + t.Fatalf("couldn't parse new certificate") } - certBuf := bytes.Buffer{} - err = pem.Encode( - &certBuf, &pem.Block{ - Type: "CERTIFICATE", - Bytes: certDerBytes, - }, + // Check that the expired certificate was successfully deleted and + // replaced with a new one. + if !newCert.NotAfter.After(expiredCert.NotAfter) { + t.Fatalf("New certificate expiration is too old") + } +} + +// Test that the new TLS Manager loads correctly, whether the encrypted TLS key +// flag is set or not, after some refactoring. +func TestTLSManagerGenCert(t *testing.T) { + tempDirPath, removeFiles, certPath, keyPath := newTestDirectory(t) + + cfg := &Config{ + TLSCertPath: certPath, + TLSKeyPath: keyPath, + } + tlsManager := TLSManager{ + cfg: cfg, + } + + err := tlsManager.generateOrRenewCert() + if err != nil { + t.Fatalf("failed to generate new certificate: %v", err) + } + + // After this is run, a new certificate should be created and written + // to disk. Since the TLSEncryptKey flag isn't set, we should be able + // to read it in plaintext from disk. + _, keyBytes, err := cert.GetCertBytesFromPath( + cfg.TLSCertPath, cfg.TLSKeyPath, ) if err != nil { - t.Fatalf("failed to encode certificate: %v", err) + t.Fatalf("unable to load certificate files: %v", err) + } + if !bytes.HasPrefix(keyBytes, privateKeyPrefix) { + t.Fatal("key is encrypted, but shouldn't be") } - keyBuf := bytes.Buffer{} - err = pem.Encode( - &keyBuf, &pem.Block{ - Type: "EC PRIVATE KEY", - Bytes: keyBytes, + err = removeFiles(tempDirPath) + if err != nil { + t.Fatalf("could not remove temporary files: %v", err) + } + + // Now test that if the TLSEncryptKey flag is set, an encrypted key is + // created and written to disk. + tempDirPath, removeFiles, certPath, keyPath = newTestDirectory(t) + defer removeFiles(tempDirPath) + + cfg = &Config{ + TLSEncryptKey: true, + TLSCertPath: certPath, + TLSKeyPath: keyPath, + TLSCertDuration: testTLSCertDuration, + } + tlsManager = TLSManager{ + cfg: cfg, + keyRing: &mock.SecretKeyRing{ + RootKey: privKey, }, + } + + err = tlsManager.generateOrRenewCert() + if err != nil { + t.Fatalf("failed to generate new certificate: %v", err) + } + + _, keyBytes, err = cert.GetCertBytesFromPath( + certPath, keyPath, ) if err != nil { - t.Fatalf("failed to encode private key: %v", err) + t.Fatalf("unable to load certificate files: %v", err) + } + if bytes.HasPrefix(keyBytes, privateKeyPrefix) { + t.Fatal("key isn't encrypted, but should be") } +} - // Write cert and key files. - err = ioutil.WriteFile(tempDirPath+"/tls.cert", certBuf.Bytes(), 0644) +// TestTLSEncryptSetWhileKeyFileIsPlaintext tests that if we have +// cfg.TLSEncryptKey set, but the tls file saved to disk is not encrypted, +// generateOrRenewCert encrypts the file and rewrites it to disk. +func TestTLSEncryptSetWhileKeyFileIsPlaintext(t *testing.T) { + keyRing := &mock.SecretKeyRing{ + RootKey: privKey, + } + + // Write an unencrypted cert file to disk. + removeFiles, certDir, certPath, keyPath, _, + _ := writeTestCertFiles( + t, false, false, keyRing, + ) + defer removeFiles(certDir) + + cfg := &Config{ + TLSEncryptKey: true, + TLSCertPath: certPath, + TLSKeyPath: keyPath, + } + tlsManager := TLSManager{ + cfg: cfg, + keyRing: keyRing, + } + + // Check that the keyBytes are initially plaintext. + _, newKeyBytes, err := cert.GetCertBytesFromPath( + cfg.TLSCertPath, cfg.TLSKeyPath, + ) if err != nil { - t.Fatalf("failed to write cert file: %v", err) + t.Fatalf("unable to load certificate files: %v", err) } - err = ioutil.WriteFile(tempDirPath+"/tls.key", keyBuf.Bytes(), 0600) + if !bytes.HasPrefix(newKeyBytes, privateKeyPrefix) { + t.Fatal("key doesn't have correct plaintext prefix") + } + + // If we call the TLSManager's main function checking if a certificate + // exists, it should detect that the TLS key is in plaintext, + // encrypt it, and rewrite the encrypted version to disk. + err = tlsManager.generateOrRenewCert() if err != nil { - t.Fatalf("failed to write key file: %v", err) + t.Fatalf("failed to generate new certificate: %v", err) } - rpcListener := net.IPAddr{IP: net.ParseIP("127.0.0.1"), Zone: ""} - rpcListeners := make([]net.Addr, 0) - rpcListeners = append(rpcListeners, &rpcListener) + // Grab the file from disk to check that the key is no longer plaintext. + _, newKeyBytes, err = cert.GetCertBytesFromPath( + cfg.TLSCertPath, cfg.TLSKeyPath, + ) + if err != nil { + t.Fatalf("unable to load certificate files: %v", err) + } + if bytes.HasPrefix(newKeyBytes, privateKeyPrefix) { + t.Fatal("key isn't encrypted, but should be") + } +} - // Now let's run getTLSConfig. If it works properly, it should delete - // the cert and create a new one. +// TestGenerateEphemearlCert tests that an ephemeral certificate is created and +// stored to disk in a .tmp file when +func TestGenerateEphemeralCert(t *testing.T) { + tempDirFile, removeFiles, certPath, keyPath := newTestDirectory(t) + defer removeFiles(tempDirFile) + + var emptyKeyRing keychain.KeyRing cfg := &Config{ TLSCertPath: certPath, TLSKeyPath: keyPath, - TLSCertDuration: 42 * time.Hour, - RPCListeners: rpcListeners, + TLSEncryptKey: true, + TLSCertDuration: testTLSCertDuration, + } + tlsManager := TLSManager{ + cfg: cfg, + keyRing: emptyKeyRing, } - _, _, _, cleanUp, err := getTLSConfig(cfg) + + err := tlsManager.generateOrRenewCert() if err != nil { - t.Fatalf("couldn't retrieve TLS config") + t.Fatalf("failed to generate new certificate: %v", err) } - defer cleanUp() - // Grab the certificate to test that getTLSConfig did its job correctly - // and generated a new cert. - newCertData, err := tls.LoadX509KeyPair(certPath, keyPath) + // Make sure .tmp file is created at the tmp cert path. + _, err = ioutil.ReadFile(cfg.TLSCertPath + ".tmp") if err != nil { - t.Fatalf("couldn't grab new certificate") + t.Fatalf("couldn't find temp cert file: %v", err) } - newCert, err := x509.ParseCertificate(newCertData.Certificate[0]) + // But no key should be stored. + _, err = ioutil.ReadFile(cfg.TLSKeyPath) + if err == nil { + t.Fatal("should have thrown an error") + } + + // And no permanent cert file should be stored. + _, err = ioutil.ReadFile(cfg.TLSCertPath) + if err == nil { + t.Fatalf("shouldn't have found a permanent cert file") + } + + // Now test that when we reload the certificate, once we have a real + // non-empty keyring, it generates the new certificate properly. + keyRing := &mock.SecretKeyRing{ + RootKey: privKey, + } + tlsManager.keyRing = keyRing + err = tlsManager.reloadCertificate() if err != nil { - t.Fatalf("couldn't parse new certificate") + t.Fatalf("unable to reload certificate: %v", err) } - // Check that the expired certificate was successfully deleted and - // replaced with a new one. - if !newCert.NotAfter.After(expiredCert.NotAfter) { - t.Fatalf("New certificate expiration is too old") + // Make sure .tmp file is deleted. + _, _, err = cert.GetCertBytesFromPath( + cfg.TLSCertPath+".tmp", cfg.TLSKeyPath, + ) + if err == nil { + t.Fatal(".tmp file should have been deleted") + } + + // Make sure a certificate now exists at the permanent cert path. + _, _, err = cert.GetCertBytesFromPath( + cfg.TLSCertPath, cfg.TLSKeyPath, + ) + if err != nil { + t.Fatalf("error loading permanent certificate: %v", err) } } -// genExpiredCertPair generates an expired key/cert pair to test that expired -// certificates are being regenerated correctly. -func genExpiredCertPair(t *testing.T, certDirPath string) ([]byte, []byte) { +// genCertPair generates a key/cert pair, with the option of generating expired +// certificates to make sure they are being regenerated correctly. +func genCertPair(t *testing.T, expired bool) ([]byte, []byte) { // Max serial number. serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) @@ -167,6 +310,15 @@ func genExpiredCertPair(t *testing.T, certDirPath string) ([]byte, []byte) { dnsNames := []string{host, "unix", "unixpacket"} + var notBefore, notAfter time.Time + if expired { + notBefore = time.Now().Add(-time.Hour * 24) + notAfter = time.Now() + } else { + notBefore = time.Now() + notAfter = time.Now().Add(time.Hour * 24) + } + // Construct the certificate template. template := x509.Certificate{ SerialNumber: serialNumber, @@ -174,8 +326,8 @@ func genExpiredCertPair(t *testing.T, certDirPath string) ([]byte, []byte) { Organization: []string{"lnd autogenerated cert"}, CommonName: host, }, - NotBefore: time.Now().Add(-time.Hour * 24), - NotAfter: time.Now(), + NotBefore: notBefore, + NotAfter: notAfter, KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, @@ -207,6 +359,95 @@ func genExpiredCertPair(t *testing.T, certDirPath string) ([]byte, []byte) { return certDerBytes, keyBytes } +// writeTestCertFiles create test files and writes them to a temporary testing +// directory. +func writeTestCertFiles(t *testing.T, expiredCert, encryptTLSKey bool, + keyRing keychain.KeyRing) (func(string) error, string, string, string, + *x509.Certificate, []byte) { + + tempDirPath, err := ioutil.TempDir("", ".testLnd") + if err != nil { + t.Fatalf("couldn't create temporary cert directory") + } + removeFiles := os.RemoveAll + + certPath := tempDirPath + "/tls.cert" + keyPath := tempDirPath + "/tls.key" + + var certDerBytes, keyBytes []byte + // Either create a valid certificate or an expired certificiate pair, + // depending on the test. + if expiredCert { + certDerBytes, keyBytes = genCertPair(t, true) + } else { + certDerBytes, keyBytes = genCertPair(t, false) + } + + parsedCert, err := x509.ParseCertificate(certDerBytes) + if err != nil { + t.Fatalf("failed to parse certificate: %v", err) + } + + certBuf := bytes.Buffer{} + err = pem.Encode( + &certBuf, &pem.Block{ + Type: "CERTIFICATE", + Bytes: certDerBytes, + }, + ) + if err != nil { + t.Fatalf("failed to encode certificate: %v", err) + } + + var keyBuf *bytes.Buffer + if !encryptTLSKey { + keyBuf = &bytes.Buffer{} + err = pem.Encode( + keyBuf, &pem.Block{ + Type: "EC PRIVATE KEY", + Bytes: keyBytes, + }, + ) + if err != nil { + t.Fatalf("failed to encode private key: %v", err) + } + } else { + keyBuf = bytes.NewBuffer(keyBytes) + var b bytes.Buffer + err := lnencrypt.EncryptPayloadToWriter( + *keyBuf, &b, keyRing, + ) + if err != nil { + t.Fatalf("failed to encrypt private key: %v", err) + } + } + + // Write cert and key files. + err = ioutil.WriteFile(tempDirPath+"/tls.cert", certBuf.Bytes(), 0644) + if err != nil { + t.Fatalf("failed to write cert file: %v", err) + } + err = ioutil.WriteFile(tempDirPath+"/tls.key", keyBuf.Bytes(), 0600) + if err != nil { + t.Fatalf("failed to write key file: %v", err) + } + + return removeFiles, tempDirPath, certPath, keyPath, parsedCert, keyBuf.Bytes() +} + +func newTestDirectory(t *testing.T) (string, func(string) error, string, string) { + tempDirPath, err := ioutil.TempDir("", ".testLnd") + if err != nil { + t.Fatalf("couldn't create temporary cert directory") + } + removeFiles := os.RemoveAll + + certPath := tempDirPath + "/tls.cert" + keyPath := tempDirPath + "/tls.key" + + return tempDirPath, removeFiles, certPath, keyPath +} + // TestShouldPeerBootstrap tests that we properly skip network bootstrap for // the developer networks, and also if bootstrapping is explicitly disabled. func TestShouldPeerBootstrap(t *testing.T) { @@ -246,7 +487,7 @@ func TestShouldPeerBootstrap(t *testing.T) { }, }, - // Mainnet active, but boostrap disabled, no boostrap. + // Mainnet active, but bootstrap disabled, no bootstrap. { cfg: &Config{ Bitcoin: &lncfg.Chain{ @@ -257,7 +498,7 @@ func TestShouldPeerBootstrap(t *testing.T) { }, }, - // Mainnet active, should boostrap. + // Mainnet active, should bootstrap. { cfg: &Config{ Bitcoin: &lncfg.Chain{ @@ -268,7 +509,7 @@ func TestShouldPeerBootstrap(t *testing.T) { shouldBoostrap: true, }, - // Testnet active, should boostrap. + // Testnet active, should bootstrap. { cfg: &Config{ Bitcoin: &lncfg.Chain{ diff --git a/signal/signal.go b/signal/signal.go index a9fcaf70fb..8b4a01485c 100644 --- a/signal/signal.go +++ b/signal/signal.go @@ -1,7 +1,7 @@ // Copyright (c) 2013-2017 The btcsuite developers // Copyright (c) 2015-2016 The Decred developers // Heavily inspired by https://github.com/btcsuite/btcd/blob/master/signal.go -// Copyright (C) 2015-2017 The Lightning Network Developers +// Copyright (C) 2015-2022 The Lightning Network Developers package signal @@ -42,7 +42,8 @@ func systemdNotifyReady() error { if notified { log.Info("Systemd was notified about our readiness") } else { - log.Info("We're not running within systemd") + log.Info("We're not running within systemd or the service " + + "type is not 'notify'") } return nil } diff --git a/subrpcserver_config.go b/subrpcserver_config.go index 04853db767..362cbc3beb 100644 --- a/subrpcserver_config.go +++ b/subrpcserver_config.go @@ -2,6 +2,7 @@ package lnd import ( "fmt" + "net" "reflect" "github.com/btcsuite/btcd/chaincfg" @@ -14,7 +15,10 @@ import ( "github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lnrpc/autopilotrpc" "github.com/lightningnetwork/lnd/lnrpc/chainrpc" + "github.com/lightningnetwork/lnd/lnrpc/devrpc" "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc" + "github.com/lightningnetwork/lnd/lnrpc/neutrinorpc" + "github.com/lightningnetwork/lnd/lnrpc/peersrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lnrpc/signrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" @@ -59,6 +63,14 @@ type subRPCServerConfigs struct { // as a gRPC service. InvoicesRPC *invoicesrpc.Config `group:"invoicesrpc" namespace:"invoicesrpc"` + // PeersRPC is a sub-RPC server that exposes peer related methods + // as a gRPC service. + PeersRPC *peersrpc.Config `group:"peersrpc" namespace:"peersrpc"` + + // NeutrinoKitRPC is a sub-RPC server that exposes functionality allowing + // a client to interact with a running neutrino node. + NeutrinoKitRPC *neutrinorpc.Config `group:"neutrinorpc" namespace:"neutrinorpc"` + // RouterRPC is a sub-RPC server the exposes functionality that allows // clients to send payments on the network, and perform Lightning // payment related queries such as requests for estimates of off-chain @@ -74,6 +86,11 @@ type subRPCServerConfigs struct { // instance within lnd in order to add, remove, list registered client // towers, etc. WatchtowerClientRPC *wtclientrpc.Config `group:"wtclientrpc" namespace:"wtclientrpc"` + + // DevRPC is a sub-RPC server that exposes functionality that allows + // developers manipulate LND state that is normally not possible. + // Should only be used for development purposes. + DevRPC *devrpc.Config `group:"devrpc" namespace:"devrpc"` } // PopulateDependencies attempts to iterate through all the sub-server configs @@ -101,6 +118,9 @@ func (s *subRPCServerConfigs) PopulateDependencies(cfg *Config, tcpResolver lncfg.TCPResolver, genInvoiceFeatures func() *lnwire.FeatureVector, genAmpInvoiceFeatures func() *lnwire.FeatureVector, + getNodeAnnouncement func() (lnwire.NodeAnnouncement, error), + updateNodeAnnouncement func(modifiers ...netann.NodeAnnModifier) error, + parseAddr func(addr string) (net.Addr, error), rpcLogger btclog.Logger) error { // First, we'll use reflect to obtain a version of the config struct @@ -235,6 +255,13 @@ func (s *subRPCServerConfigs) PopulateDependencies(cfg *Config, reflect.ValueOf(genAmpInvoiceFeatures), ) + case *neutrinorpc.Config: + subCfgValue := extractReflectValue(subCfg) + + subCfgValue.FieldByName("NeutrinoCS").Set( + reflect.ValueOf(cc.Cfg.NeutrinoCS), + ) + // RouterRPC isn't conditionally compiled and doesn't need to be // populated using reflection. case *routerrpc.Config: @@ -270,6 +297,32 @@ func (s *subRPCServerConfigs) PopulateDependencies(cfg *Config, reflect.ValueOf(rpcLogger), ) + case *devrpc.Config: + subCfgValue := extractReflectValue(subCfg) + + subCfgValue.FieldByName("ActiveNetParams").Set( + reflect.ValueOf(activeNetParams), + ) + + subCfgValue.FieldByName("GraphDB").Set( + reflect.ValueOf(graphDB), + ) + + case *peersrpc.Config: + subCfgValue := extractReflectValue(subCfg) + + subCfgValue.FieldByName("GetNodeAnnouncement").Set( + reflect.ValueOf(getNodeAnnouncement), + ) + + subCfgValue.FieldByName("ParseAddr").Set( + reflect.ValueOf(parseAddr), + ) + + subCfgValue.FieldByName("UpdateNodeAnnouncement").Set( + reflect.ValueOf(updateNodeAnnouncement), + ) + default: return fmt.Errorf("unknown field: %v, %T", fieldName, cfg) diff --git a/sweep/backend_mock_test.go b/sweep/backend_mock_test.go index f8d039ba32..6abce171d3 100644 --- a/sweep/backend_mock_test.go +++ b/sweep/backend_mock_test.go @@ -89,6 +89,7 @@ func (b *mockBackend) PublishTransaction(tx *wire.MsgTx, _ string) error { func (b *mockBackend) ListUnspentWitnessFromDefaultAccount(minConfs, maxConfs int32) ( []*lnwallet.Utxo, error) { + b.lock.Lock() defer b.lock.Unlock() @@ -150,3 +151,11 @@ func (b *mockBackend) mine() { func (b *mockBackend) isDone() bool { return len(b.unconfirmedTxes) == 0 } + +func (b *mockBackend) RemoveDescendants(*wire.MsgTx) error { + return nil +} + +func (b *mockBackend) FetchTx(chainhash.Hash) (*wire.MsgTx, error) { + return nil, nil +} diff --git a/sweep/interface.go b/sweep/interface.go index df7e00aa76..d9a1e60944 100644 --- a/sweep/interface.go +++ b/sweep/interface.go @@ -1,6 +1,7 @@ package sweep import ( + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/lnwallet" ) @@ -25,4 +26,13 @@ type Wallet interface { // ability to execute a function closure under an exclusive coin // selection lock. WithCoinSelectLock(f func() error) error + + // RemoveDescendants removes any wallet transactions that spends + // outputs created by the specified transaction. + RemoveDescendants(*wire.MsgTx) error + + // FetchTx returns the transaction that corresponds to the transaction + // hash passed in. If the transaction can't be found then a nil + // transaction pointer is returned. + FetchTx(chainhash.Hash) (*wire.MsgTx, error) } diff --git a/sweep/setup_test.go b/sweep/setup_test.go new file mode 100644 index 0000000000..e2d22ce3b9 --- /dev/null +++ b/sweep/setup_test.go @@ -0,0 +1,11 @@ +package sweep + +import ( + "testing" + + "github.com/lightningnetwork/lnd/kvdb" +) + +func TestMain(m *testing.M) { + kvdb.RunTests(m) +} diff --git a/sweep/sweeper.go b/sweep/sweeper.go index 80feb94832..74f8df9cb4 100644 --- a/sweep/sweeper.go +++ b/sweep/sweeper.go @@ -9,9 +9,9 @@ import ( "sync/atomic" "time" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/input" @@ -338,7 +338,7 @@ func (s *UtxoSweeper) Start() error { return nil } - log.Tracef("Sweeper starting") + log.Info("Sweeper starting") // Retrieve last published tx from database. lastTx, err := s.cfg.Store.GetLastPublishedTx() @@ -510,6 +510,81 @@ func (s *UtxoSweeper) feeRateForPreference( return feeRate, nil } +// removeLastSweepDescendants removes any transactions from the wallet that +// spend outputs produced by the passed spendingTx. This needs to be done in +// cases where we're not the only ones that can sweep an output, but there may +// exist unconfirmed spends that spend outputs created by a sweep transaction. +// The most common case for this is when someone sweeps our anchor outputs +// after 16 blocks. +func (s *UtxoSweeper) removeLastSweepDescendants(spendingTx *wire.MsgTx) error { + // Obtain all the past sweeps that we've done so far. We'll need these + // to ensure that if the spendingTx spends any of the same inputs, then + // we remove any transaction that may be spending those inputs from the + // wallet. + // + // TODO(roasbeef): can be last sweep here if we remove anything confirmed + // from the store? + pastSweepHashes, err := s.cfg.Store.ListSweeps() + if err != nil { + return err + } + + log.Debugf("Attempting to remove descendant txns invalidated by "+ + "(txid=%v): %v", spendingTx.TxHash(), spew.Sdump(spendingTx)) + + // Construct a map of the inputs this transaction spends for each look + // up. + inputsSpent := make(map[wire.OutPoint]struct{}, len(spendingTx.TxIn)) + for _, txIn := range spendingTx.TxIn { + inputsSpent[txIn.PreviousOutPoint] = struct{}{} + } + + // We'll now go through each past transaction we published during this + // epoch and cross reference the spent inputs. If there're any inputs + // in common with the inputs the spendingTx spent, then we'll remove + // those. + // + // TODO(roasbeef): need to start to remove all transaction hashes after + // every N blocks (assumed point of no return) + for _, sweepHash := range pastSweepHashes { + sweepTx, err := s.cfg.Wallet.FetchTx(sweepHash) + if err != nil { + return err + } + + // Transaction wasn't found in the wallet, may have already + // been replaced/removed. + if sweepTx == nil { + continue + } + + // Check to see if this past sweep transaction spent any of the + // same inputs as spendingTx. + var isConflicting bool + for _, txIn := range sweepTx.TxIn { + if _, ok := inputsSpent[txIn.PreviousOutPoint]; ok { + isConflicting = true + break + } + } + + // If it did, then we'll signal the wallet to remove all the + // transactions that are descendants of outputs created by the + // sweepTx. + if isConflicting { + log.Debugf("Removing sweep txid=%v from wallet: %v", + sweepTx.TxHash(), spew.Sdump(sweepTx)) + + err := s.cfg.Wallet.RemoveDescendants(sweepTx) + if err != nil { + log.Warnf("unable to remove descendants: %v", err) + } + } + } + + return nil +} + // collector is the sweeper main loop. It processes new inputs, spend // notifications and counts down to publication of the sweep tx. func (s *UtxoSweeper) collector(blockEpochs <-chan *chainntnfs.BlockEpoch) { @@ -544,6 +619,7 @@ func (s *UtxoSweeper) collector(blockEpochs <-chan *chainntnfs.BlockEpoch) { var prevExclGroup *uint64 if pendInput.params.ExclusiveGroup != nil && input.params.ExclusiveGroup == nil { + prevExclGroup = new(uint64) *prevExclGroup = *pendInput.params.ExclusiveGroup } @@ -618,12 +694,29 @@ func (s *UtxoSweeper) collector(blockEpochs <-chan *chainntnfs.BlockEpoch) { continue } - log.Debugf("Detected spend related to in flight inputs "+ - "(is_ours=%v): %v", - newLogClosure(func() string { - return spew.Sdump(spend.SpendingTx) - }), isOurTx, - ) + // If this isn't our transaction, it means someone else + // swept outputs that we were attempting to sweep. This + // can happen for anchor outputs as well as justice + // transactions. In this case, we'll notify the wallet + // to remove any spends that a descent from this + // output. + if !isOurTx { + err := s.removeLastSweepDescendants( + spend.SpendingTx, + ) + if err != nil { + log.Warnf("unable to remove descendant "+ + "transactions due to tx %v: ", + spendHash) + } + + log.Debugf("Detected spend related to in flight inputs "+ + "(is_ours=%v): %v", + newLogClosure(func() string { + return spew.Sdump(spend.SpendingTx) + }), isOurTx, + ) + } // Signal sweep results for inputs in this confirmed // tx. @@ -801,7 +894,7 @@ func (s *UtxoSweeper) bucketForFeeRate( // createInputClusters creates a list of input clusters from the set of pending // inputs known by the UtxoSweeper. It clusters inputs by // 1) Required tx locktime -// 2) Similar fee rates +// 2) Similar fee rates. func (s *UtxoSweeper) createInputClusters() []inputCluster { inputs := s.pendingInputs @@ -982,7 +1075,6 @@ func zipClusters(as, bs []inputCluster) []inputCluster { a := as[i] switch { - // If the fee rate for the next one from bs is at least a's, we // merge. case j < len(bs) && bs[j].sweepFeeRate >= a.sweepFeeRate: @@ -992,7 +1084,7 @@ func zipClusters(as, bs []inputCluster) []inputCluster { // Increment j for the next round. j++ - // We did not merge, meaning all the remining clusters from bs + // We did not merge, meaning all the remaining clusters from bs // have lower fee rate. Instead we add a directly to the final // clusters. default: @@ -1017,7 +1109,6 @@ func mergeClusters(a, b inputCluster) []inputCluster { newCluster := inputCluster{} switch { - // Incompatible locktimes, return the sets without merging them. case a.lockTime != nil && b.lockTime != nil && *a.lockTime != *b.lockTime: return []inputCluster{a, b} diff --git a/sweep/sweeper_test.go b/sweep/sweeper_test.go index e015440662..5f4830711e 100644 --- a/sweep/sweeper_test.go +++ b/sweep/sweeper_test.go @@ -8,11 +8,11 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/build" "github.com/lightningnetwork/lnd/input" @@ -59,7 +59,7 @@ var ( 0xf8, 0x2e, 0x16, 0x0b, 0xfa, 0x9b, 0x8b, 0x64, 0xf9, 0xd4, 0xc0, 0x3f, 0x99, 0x9b, 0x86, 0x43, 0xf6, 0x56, 0xb4, 0x12, 0xa3, - }, btcec.S256()) + }) ) func createTestInput(value int64, witnessType input.WitnessType) input.BaseInput { @@ -355,7 +355,7 @@ func assertTxFeeRate(t *testing.T, tx *wire.MsgTx, outputAmt := tx.TxOut[0].Value fee := btcutil.Amount(inputAmt - outputAmt) - _, estimator := getWeightEstimate(inputs, nil, 0) + _, estimator := getWeightEstimate(inputs, nil, 0, nil) txWeight := estimator.weight() expectedFee := expectedFeeRate.FeeForWeight(int64(txWeight)) @@ -1623,7 +1623,9 @@ func (i *testInput) RequiredTxOut() *wire.TxOut { // encode the spending outpoint and the tx input index as part of the returned // witness. func (i *testInput) CraftInputScript(_ input.Signer, txn *wire.MsgTx, - hashCache *txscript.TxSigHashes, txinIdx int) (*input.Script, error) { + hashCache *txscript.TxSigHashes, + prevOutputFetcher txscript.PrevOutputFetcher, + txinIdx int) (*input.Script, error) { // We'll encode the outpoint in the witness, so we can assert that the // expected input was signed at the correct index. @@ -1763,7 +1765,6 @@ func TestLockTimes(t *testing.T) { t.Fatalf("Input required locktime %v, sweep "+ "tx had locktime %v", lt, sweepTx.LockTime) } - } } diff --git a/sweep/test_utils.go b/sweep/test_utils.go index 5e0c676262..475aaf5af6 100644 --- a/sweep/test_utils.go +++ b/sweep/test_utils.go @@ -174,7 +174,7 @@ func (m *MockNotifier) Start() error { return nil } -// Started checks if started +// Started checks if started. func (m *MockNotifier) Started() bool { return true } diff --git a/sweep/tx_input_set.go b/sweep/tx_input_set.go index 2f2ff97041..e215f67683 100644 --- a/sweep/tx_input_set.go +++ b/sweep/tx_input_set.go @@ -4,9 +4,9 @@ import ( "fmt" "math" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet/chainfee" @@ -210,7 +210,6 @@ func (t *txInputSet) addToState(inp input.Input, constraints addConstraints) *tx inputYield := s.totalOutput() - t.totalOutput() switch constraints { - // Don't sweep inputs that cost us more to sweep than they give us. case constraintsRegular: if inputYield <= 0 { diff --git a/sweep/tx_input_set_test.go b/sweep/tx_input_set_test.go index 4dc31285a9..c280f9f6a6 100644 --- a/sweep/tx_input_set_test.go +++ b/sweep/tx_input_set_test.go @@ -3,8 +3,8 @@ package sweep import ( "testing" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnwallet" "github.com/stretchr/testify/require" @@ -234,7 +234,6 @@ func TestTxInputSetRequiredOutput(t *testing.T) { change = set.changeOutput if change != 1000 { t.Fatalf("expected change to be %v, had %v", 1000, change) - } require.True(t, set.enoughInput()) } diff --git a/sweep/txgenerator.go b/sweep/txgenerator.go index 35e5adfcbc..95da8a7647 100644 --- a/sweep/txgenerator.go +++ b/sweep/txgenerator.go @@ -6,9 +6,9 @@ import ( "strings" "github.com/btcsuite/btcd/blockchain" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet/chainfee" @@ -139,7 +139,9 @@ func createSweepTx(inputs []input.Input, outputs []*wire.TxOut, feePerKw chainfee.SatPerKWeight, signer input.Signer) (*wire.MsgTx, error) { - inputs, estimator := getWeightEstimate(inputs, outputs, feePerKw) + inputs, estimator := getWeightEstimate( + inputs, outputs, feePerKw, changePkScript, + ) txFee := estimator.fee() var ( @@ -220,7 +222,9 @@ func createSweepTx(inputs []input.Input, outputs []*wire.TxOut, } if requiredOutput+txFee > totalInput { - return nil, fmt.Errorf("insufficient input to create sweep tx") + return nil, fmt.Errorf("insufficient input to create sweep "+ + "tx: input_sum=%v, output_sum=%v", totalInput, + requiredOutput+txFee) } // The value remaining after the required output and fees, go to @@ -262,13 +266,18 @@ func createSweepTx(inputs []input.Input, outputs []*wire.TxOut, return nil, err } - hashCache := txscript.NewTxSigHashes(sweepTx) + prevInputFetcher, err := input.MultiPrevOutFetcher(inputs) + if err != nil { + return nil, fmt.Errorf("error creating prev input fetcher "+ + "for hash cache: %v", err) + } + hashCache := txscript.NewTxSigHashes(sweepTx, prevInputFetcher) // With all the inputs in place, use each output's unique input script // function to generate the final witness required for spending. addInputScript := func(idx int, tso input.Input) error { inputScript, err := tso.CraftInputScript( - signer, sweepTx, hashCache, idx, + signer, sweepTx, hashCache, prevInputFetcher, idx, ) if err != nil { return err @@ -305,7 +314,8 @@ func createSweepTx(inputs []input.Input, outputs []*wire.TxOut, // getWeightEstimate returns a weight estimate for the given inputs. // Additionally, it returns counts for the number of csv and cltv inputs. func getWeightEstimate(inputs []input.Input, outputs []*wire.TxOut, - feeRate chainfee.SatPerKWeight) ([]input.Input, *weightEstimator) { + feeRate chainfee.SatPerKWeight, outputPkScript []byte) ([]input.Input, + *weightEstimator) { // We initialize a weight estimator so we can accurately asses the // amount of fees we need to pay for this sweep transaction. @@ -320,14 +330,18 @@ func getWeightEstimate(inputs []input.Input, outputs []*wire.TxOut, } // If there is any leftover change after paying to the given outputs - // and required outputs, it will go to a single segwit p2wkh address. - // This will be our change address, so ensure it contributes to our - // weight estimate. Note that if we have other outputs, we might end up - // creating a sweep tx without a change output. It is okay to add the + // and required outputs, it will go to a single segwit p2wkh or p2tr + // address. This will be our change address, so ensure it contributes to + // our weight estimate. Note that if we have other outputs, we might end + // up creating a sweep tx without a change output. It is okay to add the // change output to the weight estimate regardless, since the estimated // fee will just be subtracted from this already dust output, and // trimmed. - weightEstimate.addP2WKHOutput() + if txscript.IsPayToTaproot(outputPkScript) { + weightEstimate.addP2TROutput() + } else { + weightEstimate.addP2WKHOutput() + } // For each output, use its witness type to determine the estimate // weight of its witness, and add it to the proper set of spendable diff --git a/sweep/txgenerator_test.go b/sweep/txgenerator_test.go index f8a6b8ac4f..960da63d31 100644 --- a/sweep/txgenerator_test.go +++ b/sweep/txgenerator_test.go @@ -39,7 +39,7 @@ func TestWeightEstimate(t *testing.T) { )) } - _, estimator := getWeightEstimate(inputs, nil, 0) + _, estimator := getWeightEstimate(inputs, nil, 0, nil) weight := int64(estimator.weight()) if weight != expectedWeight { t.Fatalf("unexpected weight. expected %d but got %d.", diff --git a/sweep/walletsweep.go b/sweep/walletsweep.go index 61c34178af..6044b85f89 100644 --- a/sweep/walletsweep.go +++ b/sweep/walletsweep.go @@ -4,9 +4,9 @@ import ( "fmt" "math" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet/chainfee" @@ -255,6 +255,10 @@ func CraftSweepAllTx(feeRate chainfee.SatPerKWeight, blockHeight uint32, case lnwallet.NestedWitnessPubKey: witnessType = input.NestedWitnessKeyHash + case lnwallet.TaprootPubkey: + witnessType = input.TaprootPubKeySpend + signDesc.HashType = txscript.SigHashDefault + // All other output types we count as unknown and will fail to // sweep. default: diff --git a/sweep/walletsweep_test.go b/sweep/walletsweep_test.go index 8f7d46877f..2f5679c69d 100644 --- a/sweep/walletsweep_test.go +++ b/sweep/walletsweep_test.go @@ -5,10 +5,10 @@ import ( "fmt" "testing" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/lntest/mock" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet/chainfee" diff --git a/sweep/weight_estimator.go b/sweep/weight_estimator.go index 011094fef8..aba61cd92c 100644 --- a/sweep/weight_estimator.go +++ b/sweep/weight_estimator.go @@ -1,9 +1,9 @@ package sweep import ( + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnwallet/chainfee" ) @@ -77,6 +77,12 @@ func (w *weightEstimator) addP2WKHOutput() { w.estimator.AddP2WKHOutput() } +// addP2TROutput updates the weight estimate to account for an additional native +// SegWit v1 P2TR output. +func (w *weightEstimator) addP2TROutput() { + w.estimator.AddP2TROutput() +} + // addOutput updates the weight estimate to account for the known // output given. func (w *weightEstimator) addOutput(txOut *wire.TxOut) { diff --git a/sweep/weight_estimator_test.go b/sweep/weight_estimator_test.go index f64b8b897a..1952f531e8 100644 --- a/sweep/weight_estimator_test.go +++ b/sweep/weight_estimator_test.go @@ -3,10 +3,10 @@ package sweep import ( "testing" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/stretchr/testify/require" diff --git a/tlv/go.mod b/tlv/go.mod new file mode 100644 index 0000000000..df83fa6b44 --- /dev/null +++ b/tlv/go.mod @@ -0,0 +1,11 @@ +module github.com/lightningnetwork/lnd/tlv + +require ( + github.com/btcsuite/btcd v0.22.0-beta.0.20220207191057-4dc4ff7963b4 + github.com/btcsuite/btcd/btcec/v2 v2.1.0 + github.com/davecgh/go-spew v1.1.1 + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 + github.com/stretchr/testify v1.7.1 +) + +go 1.16 diff --git a/tlv/go.sum b/tlv/go.sum new file mode 100644 index 0000000000..204b90e4cf --- /dev/null +++ b/tlv/go.sum @@ -0,0 +1,96 @@ +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= +github.com/btcsuite/btcd v0.22.0-beta.0.20220207191057-4dc4ff7963b4 h1:CEGr/598C/0LZQUoioaT6sdGGcJgu4+ck0PDeJ/QkKs= +github.com/btcsuite/btcd v0.22.0-beta.0.20220207191057-4dc4ff7963b4/go.mod h1:7alexyj/lHlOtr2PJK7L/+HDJZpcGDn/pAU98r7DY08= +github.com/btcsuite/btcd/btcec/v2 v2.1.0 h1:Whmbo9yShKKG+WrUfYGFfgj77vYBiwhwBSJnM66TMKI= +github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= +github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= +github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/tlv/primitive.go b/tlv/primitive.go index e064e73a74..fbf9a2ca0a 100644 --- a/tlv/primitive.go +++ b/tlv/primitive.go @@ -5,7 +5,7 @@ import ( "fmt" "io" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" ) // ErrTypeForEncoding signals that an incorrect type was passed to an Encoder. @@ -275,7 +275,7 @@ func DPubKey(r io.Reader, val interface{}, _ *[8]byte, l uint64) error { return err } - p, err := btcec.ParsePubKey(b[:], btcec.S256()) + p, err := btcec.ParsePubKey(b[:]) if err != nil { return err } @@ -307,3 +307,58 @@ func DVarBytes(r io.Reader, val interface{}, _ *[8]byte, l uint64) error { } return NewTypeForDecodingErr(val, "[]byte", l, l) } + +// EBigSize encodes an uint32 or an uint64 using BigSize format. An error is +// returned if val is not either *uint32 or *uint64. +func EBigSize(w io.Writer, val interface{}, buf *[8]byte) error { + if i, ok := val.(*uint32); ok { + return WriteVarInt(w, uint64(*i), buf) + } + + if i, ok := val.(*uint64); ok { + return WriteVarInt(w, uint64(*i), buf) + } + + return NewTypeForEncodingErr(val, "BigSize") +} + +// DBigSize decodes an uint32 or an uint64 using BigSize format. An error is +// returned if val is not either *uint32 or *uint64. +func DBigSize(r io.Reader, val interface{}, buf *[8]byte, l uint64) error { + if i, ok := val.(*uint32); ok { + v, err := ReadVarInt(r, buf) + if err != nil { + return err + } + *i = uint32(v) + return nil + } + + if i, ok := val.(*uint64); ok { + v, err := ReadVarInt(r, buf) + if err != nil { + return err + } + *i = v + return nil + } + + return NewTypeForDecodingErr(val, "BigSize", l, 8) +} + +// SizeBigSize returns a SizeFunc that can compute the length of BigSize. +func SizeBigSize(val interface{}) SizeFunc { + var size uint64 + + if i, ok := val.(*uint32); ok { + size = VarIntSize(uint64(*i)) + } + + if i, ok := val.(*uint64); ok { + size = VarIntSize(uint64(*i)) + } + + return func() uint64 { + return size + } +} diff --git a/tlv/primitive_test.go b/tlv/primitive_test.go index dc4d7009d8..7034d4258b 100644 --- a/tlv/primitive_test.go +++ b/tlv/primitive_test.go @@ -5,7 +5,7 @@ import ( "reflect" "testing" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/lightningnetwork/lnd/tlv" ) @@ -14,7 +14,7 @@ var testPK, _ = btcec.ParsePubKey([]byte{0x02, 0xc0, 0x37, 0x17, 0x31, 0x80, 0xe9, 0xa4, 0x65, 0x47, 0x60, 0x26, 0xee, 0x50, 0xf9, 0x66, 0x95, 0x96, 0x3e, 0x8e, 0xfe, 0x43, 0x6f, 0x54, 0xeb, -}, btcec.S256()) +}) type primitive struct { u8 byte diff --git a/tlv/record.go b/tlv/record.go index 3807095687..474908ebf4 100644 --- a/tlv/record.go +++ b/tlv/record.go @@ -6,7 +6,7 @@ import ( "io" "sort" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" ) // Type is an 64-bit identifier for a TLV Record. @@ -249,3 +249,40 @@ func SortRecords(records []Record) { return records[i].Type() < records[j].Type() }) } + +// MakeBigSizeRecord creates a tlv record using the BigSize format. The only +// allowed values are uint64 and uint32. +// +// NOTE: for uint32, we would only gain space reduction if the encoded value is +// no greater than 65535, which requires at most 3 bytes to encode. +func MakeBigSizeRecord(typ Type, val interface{}) Record { + var ( + staticSize uint64 + sizeFunc SizeFunc + encoder Encoder + decoder Decoder + ) + switch val.(type) { + case *uint32: + sizeFunc = SizeBigSize(val) + encoder = EBigSize + decoder = DBigSize + + case *uint64: + sizeFunc = SizeBigSize(val) + encoder = EBigSize + decoder = DBigSize + + default: + panic(fmt.Sprintf("unknown supported compact type: %T", val)) + } + + return Record{ + value: val, + typ: typ, + staticSize: staticSize, + sizeFunc: sizeFunc, + encoder: encoder, + decoder: decoder, + } +} diff --git a/tlv/stream_test.go b/tlv/stream_test.go index 8f67a316f2..eb732d4db5 100644 --- a/tlv/stream_test.go +++ b/tlv/stream_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/lightningnetwork/lnd/tlv" + "github.com/stretchr/testify/require" ) type parsedTypeTest struct { @@ -88,3 +89,164 @@ func testParsedTypes(t *testing.T, test parsedTypeTest) { t.Fatalf("error mismatch on parsed types") } } + +var ( + smallValue = 1 + smallValueBytes = []byte{ + // uint32 tlv, value uses 1 byte. + 0xa, 0x1, 0x1, + // uint64 tlv, value uses 1 byte. + 0xb, 0x1, 0x1, + } + + medianValue = 255 + medianValueBytes = []byte{ + // uint32 tlv, value uses 3 byte. + 0xa, 0x3, 0xfd, 0x0, 0xff, + // uint64 tlv, value uses 3 byte. + 0xb, 0x3, 0xfd, 0x0, 0xff, + } + + largeValue = 65536 + largeValueBytes = []byte{ + // uint32 tlv, value uses 5 byte. + 0xa, 0x5, 0xfe, 0x0, 0x1, 0x0, 0x0, + // uint64 tlv, value uses 5 byte. + 0xb, 0x5, 0xfe, 0x0, 0x1, 0x0, 0x0, + } +) + +// TestEncodeBigSizeFormatTlvStream tests that the bigsize encoder works as +// expected. +func TestEncodeBigSizeFormatTlvStream(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + value int + expectedBytes []byte + }{ + { + // Test encode 1, which saves us space. + name: "encode small value", + value: smallValue, + expectedBytes: smallValueBytes, + }, + { + // Test encode 255, which still saves us space. + name: "encode median value", + value: medianValue, + expectedBytes: medianValueBytes, + }, + { + // Test encode 65536, which takes more space to encode + // an uint32. + name: "encode large value", + value: largeValue, + expectedBytes: largeValueBytes, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + testUint32 := uint32(tc.value) + testUint64 := uint64(tc.value) + ts := makeBigSizeFormatTlvStream( + t, &testUint32, &testUint64, + ) + + // Encode the tlv stream. + buf := bytes.NewBuffer([]byte{}) + require.NoError(t, ts.Encode(buf)) + + // Check the bytes are written as expected. + require.Equal(t, tc.expectedBytes, buf.Bytes()) + }) + } +} + +// TestDecodeBigSizeFormatTlvStream tests that the bigsize decoder works as +// expected. +func TestDecodeBigSizeFormatTlvStream(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + bytes []byte + expectedValue int + }{ + { + // Test decode 1. + name: "decode small value", + bytes: []byte{ + // uint32 tlv, value uses 1 byte. + 0xa, 0x1, 0x1, + // uint64 tlv, value uses 1 byte. + 0xb, 0x1, 0x1, + }, + expectedValue: smallValue, + }, + { + // Test decode 255. + name: "decode median value", + bytes: []byte{ + // uint32 tlv, value uses 3 byte. + 0xa, 0x3, 0xfd, 0x0, 0xff, + // uint64 tlv, value uses 3 byte. + 0xb, 0x3, 0xfd, 0x0, 0xff, + }, + expectedValue: medianValue, + }, + { + // Test decode 65536. + name: "decode value 65536", + bytes: []byte{ + // uint32 tlv, value uses 5 byte. + 0xa, 0x5, 0xfe, 0x0, 0x1, 0x0, 0x0, + // uint64 tlv, value uses 5 byte. + 0xb, 0x5, 0xfe, 0x0, 0x1, 0x0, 0x0, + }, + expectedValue: largeValue, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + var ( + testUint32 uint32 + testUint64 uint64 + ) + ts := makeBigSizeFormatTlvStream( + t, &testUint32, &testUint64, + ) + + // Decode the tlv stream. + buf := bytes.NewBuffer(tc.bytes) + require.NoError(t, ts.Decode(buf)) + + // Check the values are written as expected. + require.EqualValues(t, tc.expectedValue, testUint32) + require.EqualValues(t, tc.expectedValue, testUint64) + }) + } +} + +func makeBigSizeFormatTlvStream(t *testing.T, vUint32 *uint32, + vUint64 *uint64) *tlv.Stream { + + const ( + typeUint32 tlv.Type = 10 + typeUint64 tlv.Type = 11 + ) + + // Create a dummy tlv stream for testing. + ts, err := tlv.NewStream( + tlv.MakeBigSizeRecord(typeUint32, vUint32), + tlv.MakeBigSizeRecord(typeUint64, vUint64), + ) + require.NoError(t, err) + + return ts +} diff --git a/tlv/tlv_test.go b/tlv/tlv_test.go index e5d4734139..3dd520b32f 100644 --- a/tlv/tlv_test.go +++ b/tlv/tlv_test.go @@ -2,12 +2,12 @@ package tlv_test import ( "bytes" - "errors" "io" "reflect" "testing" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + secp "github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/lightningnetwork/lnd/tlv" ) @@ -304,7 +304,10 @@ var tlvDecodingFailureTests = []struct { 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, }, - expErr: errors.New("invalid magic in compressed pubkey string: 4"), + expErr: secp.Error{ + Err: secp.ErrPubKeyInvalidFormat, + Description: "invalid public key: unsupported format: 4", + }, skipN2: true, }, { @@ -528,7 +531,7 @@ func TestTLVDecodingSuccess(t *testing.T) { err = n2.Decode(r) if err != nil { - t.Fatalf("expected N2 decoding succes, got: %v", + t.Fatalf("expected N2 decoding success, got: %v", err) } }) diff --git a/tools.go b/tools.go deleted file mode 100644 index fad50c7e76..0000000000 --- a/tools.go +++ /dev/null @@ -1,14 +0,0 @@ -//go:build tools -// +build tools - -package lnd - -import ( - // This is a workaround to make sure go mod keeps around the btcd - // dependencies in the go.sum file that we only use during integration - // tests and only for certain operating systems. For example, this - // specific import makes sure the indirect dependency - // github.com/btcsuite/winsvc is kept in the go.sum file. Because of the - // build tag, this dependency never ends up in the final lnd binary. - _ "github.com/btcsuite/btcd" -) diff --git a/tools/Dockerfile b/tools/Dockerfile new file mode 100644 index 0000000000..452c927387 --- /dev/null +++ b/tools/Dockerfile @@ -0,0 +1,16 @@ +FROM golang:1.18.0-buster + +RUN apt-get update && apt-get install -y git +ENV GOCACHE=/tmp/build/.cache +ENV GOMODCACHE=/tmp/build/.modcache + +COPY . /tmp/tools + +RUN cd /tmp \ + && mkdir -p /tmp/build/.cache \ + && mkdir -p /tmp/build/.modcache \ + && cd /tmp/tools \ + && go install -trimpath -tags=tools github.com/golangci/golangci-lint/cmd/golangci-lint \ + && chmod -R 777 /tmp/build/ + +WORKDIR /build diff --git a/tools/go.mod b/tools/go.mod new file mode 100644 index 0000000000..7389fb925c --- /dev/null +++ b/tools/go.mod @@ -0,0 +1,16 @@ +module github.com/lightningnetwork/lnd/tools + +go 1.16 + +require ( + github.com/btcsuite/btcd v0.22.0-beta.0.20211005184431-e3449998be39 + github.com/dvyukov/go-fuzz v0.0.0-20210602112143-b1f3d6f4ef4e + github.com/elazarl/go-bindata-assetfs v1.0.1 // indirect + github.com/golangci/golangci-lint v1.44.0 + github.com/ory/go-acc v0.2.6 + github.com/rinchsan/gosimports v0.1.5 + github.com/stephens2424/writerset v1.0.2 // indirect +) + +// Can be removed once this version is included in golangci-lint. +replace github.com/ultraware/whitespace => github.com/ultraware/whitespace v0.0.5 diff --git a/tools/go.sum b/tools/go.sum new file mode 100644 index 0000000000..e264835661 --- /dev/null +++ b/tools/go.sum @@ -0,0 +1,1490 @@ +4d63.com/gochecknoglobals v0.1.0 h1:zeZSRqj5yCg28tCkIV/z/lWbwvNm5qnKVS15PI8nhD0= +4d63.com/gochecknoglobals v0.1.0/go.mod h1:wfdC5ZjKSPr7CybKEcgJhUOgeAQW1+7WcyK8OvUilfo= +bitbucket.org/creachadair/shell v0.0.6/go.mod h1:8Qqi/cYk7vPnsOePHroKXDJYmb5x7ENhtiFtfZq8K+M= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.60.0/go.mod h1:yw2G51M9IfRboUH61Us8GqCeF1PzPblB823Mn2q2eAU= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM= +cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.6.0/go.mod h1:afJwI0vaXwAG54kI7A//lP/lSPDkQORQuMkv56TxEPU= +cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/pubsub v1.5.0/go.mod h1:ZEwJccE3z93Z2HWvstpri00jOg7oO4UZDtKhwDwqF0w= +cloud.google.com/go/spanner v1.7.0/go.mod h1:sd3K2gZ9Fd0vMPLXzeCrF6fq4i63Q7aTLW/lBIfBkIk= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +contrib.go.opencensus.io/exporter/stackdriver v0.13.4/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Antonboom/errname v0.1.5 h1:IM+A/gz0pDhKmlt5KSNTVAvfLMb+65RxavBXpRtCUEg= +github.com/Antonboom/errname v0.1.5/go.mod h1:DugbBstvPFQbv/5uLcRRzfrNqKE9tVdVCqWCLp6Cifo= +github.com/Antonboom/nilnil v0.1.0 h1:DLDavmg0a6G/F4Lt9t7Enrbgb3Oph6LnDE6YVsmTt74= +github.com/Antonboom/nilnil v0.1.0/go.mod h1:PhHLvRPSghY5Y7mX4TW+BHZQYo1A8flE5H20D3IPZBo= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU= +github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM= +github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= +github.com/Julusian/godocdown v0.0.0-20170816220326-6d19f8ff2df8/go.mod h1:INZr5t32rG59/5xeltqoCJoNY7e5x/3xoY9WSWVWg74= +github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/sprig v2.15.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/OpenPeeDeeP/depguard v1.1.0 h1:pjK9nLPS1FwQYGGpPxoMYpe7qACHOhAWQMQzV71i49o= +github.com/OpenPeeDeeP/depguard v1.1.0/go.mod h1:JtAMzWkmFEzDPyAd+W0NHl1lvpQKTvT9jnRVsohBKpc= +github.com/aead/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg= +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pOcUuw= +github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE= +github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= +github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/ashanbrown/forbidigo v1.3.0 h1:VkYIwb/xxdireGAdJNZoo24O4lmnEWkactplBlWTShc= +github.com/ashanbrown/forbidigo v1.3.0/go.mod h1:vVW7PEdqEFqapJe95xHkTfB1+XvZXBFg8t0sG2FIxmI= +github.com/ashanbrown/makezero v1.1.0 h1:b2FVq4dTlBpy9f6qxhbyWH+6zy56IETE9cFbBGtDqs8= +github.com/ashanbrown/makezero v1.1.0/go.mod h1:oG9Dnez7/ESBqc4EdrdNlryeo7d0KcW1ftXHm7nU/UU= +github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.36.30/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bkielbasa/cyclop v1.2.0 h1:7Jmnh0yL2DjKfw28p86YTd/B4lRGcNuu12sKE35sM7A= +github.com/bkielbasa/cyclop v1.2.0/go.mod h1:qOI0yy6A7dYC4Zgsa72Ppm9kONl0RoIlPbzot9mhmeI= +github.com/blizzy78/varnamelen v0.5.0 h1:v9LpMwxzTqAJC4lsD/jR7zWb8a66trcqhTEH4Mk6Fio= +github.com/blizzy78/varnamelen v0.5.0/go.mod h1:Mc0nLBKI1/FP0Ga4kqMOgBig0eS5QtR107JnMAb1Wuc= +github.com/bombsimon/wsl/v3 v3.3.0 h1:Mka/+kRLoQJq7g2rggtgQsjuI/K5Efd87WX96EWFxjM= +github.com/bombsimon/wsl/v3 v3.3.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc= +github.com/breml/bidichk v0.2.1 h1:SRNtZuLdfkxtocj+xyHXKC1Uv3jVi6EPYx+NHSTNQvE= +github.com/breml/bidichk v0.2.1/go.mod h1:zbfeitpevDUGI7V91Uzzuwrn4Vls8MoBMrwtt78jmso= +github.com/breml/errchkjson v0.2.1 h1:QCToXnY9BNngrbJoW3qfCTt3BdtbnsI6wyP/WGrxxSE= +github.com/breml/errchkjson v0.2.1/go.mod h1:jZEATw/jF69cL1iy7//Yih8yp/mXp2CBoBr9GJwCAsY= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btcd v0.22.0-beta.0.20211005184431-e3449998be39 h1:o6qacOzpKubr16y0RrE2fBauRZN1rDZ1YsE26ixCgQ0= +github.com/btcsuite/btcd v0.22.0-beta.0.20211005184431-e3449998be39/go.mod h1:3PH+KbvLFfzBTCevQenPiDedjGQGt6aa70dVjJDWGTA= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce h1:YtWJF7RHm2pYCvA5t0RPmAaLUhREsKuKd+SLhxFbFeQ= +github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/goleveldb v1.0.0 h1:Tvd0BfvqX9o823q1j2UZ/epQo09eJh6dTcRp79ilIN4= +github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/snappy-go v1.0.0 h1:ZxaA6lo2EpxGddsA8JwWOcxlzRybb444sgmeJQMJGQE= +github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3SkEwmHoWBmX1DNXhXZqlTpq6s4tyJGc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0 h1:J9B4L7e3oqhXOcm+2IuNApwzQec85lE+QaikUcCs+dk= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/butuzov/ireturn v0.1.1 h1:QvrO2QF2+/Cx1WA/vETCIYBKtRjc30vesdoPUNo1EbY= +github.com/butuzov/ireturn v0.1.1/go.mod h1:Wh6Zl3IMtTpaIKbmwzqi6olnM9ptYQxxVacMsOEFPoc= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/charithe/durationcheck v0.0.9 h1:mPP4ucLrf/rKZiIG/a9IPXHGlh8p4CzgpyTy6EEutYk= +github.com/charithe/durationcheck v0.0.9/go.mod h1:SSbRIBVfMjCi/kEB6K65XEA83D6prSM8ap1UCpNKtgg= +github.com/chavacava/garif v0.0.0-20210405164556-e8a0a408d6af h1:spmv8nSH9h5oCQf40jt/ufBCt9j0/58u4G+rkeMqXGI= +github.com/chavacava/garif v0.0.0-20210405164556-e8a0a408d6af/go.mod h1:Qjyv4H3//PWVzTeCezG2b9IRn6myJxJSr4TD/xo6ojU= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190620071333-e64a0ec8b42a/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/daixiang0/gci v0.2.9 h1:iwJvwQpBZmMg31w+QQ6jsyZ54KEATn6/nfARbBNW294= +github.com/daixiang0/gci v0.2.9/go.mod h1:+4dZ7TISfSmqfAGv59ePaHfNzgGtIkHAhhdKggP1JAc= +github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/lru v1.0.0 h1:Kbsb1SFDsIlaupWPwsPp+dkxiBY1frcS07PCPgotKz8= +github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= +github.com/denis-tingajkin/go-header v0.4.2 h1:jEeSF4sdv8/3cT/WY8AgDHUoItNSoEZ7qg9dX7pc218= +github.com/denis-tingajkin/go-header v0.4.2/go.mod h1:eLRHAVXzE5atsKAnNRDB90WHCFFnBUn4RN0nRcs1LJA= +github.com/dgraph-io/ristretto v0.0.1/go.mod h1:T40EBc7CJke8TkpiYfGGKAeFjSaxuFXhuXRyumBd6RE= +github.com/dgraph-io/ristretto v0.0.2 h1:a5WaUrDa0qm0YrAAS1tUykT5El3kt62KNZZeMxQn3po= +github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dvyukov/go-fuzz v0.0.0-20210602112143-b1f3d6f4ef4e h1:qTP1telKJHlToHlwPQNmVg4yfMDMHe4Z3SYmzkrvA2M= +github.com/dvyukov/go-fuzz v0.0.0-20210602112143-b1f3d6f4ef4e/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= +github.com/elazarl/go-bindata-assetfs v1.0.1 h1:m0kkaHRKEu7tUIUFVwhGGGYClXvyl4RE03qmvRTNfbw= +github.com/elazarl/go-bindata-assetfs v1.0.1/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ= +github.com/envoyproxy/protoc-gen-validate v0.0.14/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= +github.com/esimonov/ifshort v1.0.4 h1:6SID4yGWfRae/M7hkVDVVyppy8q/v9OuxNdmjLQStBA= +github.com/esimonov/ifshort v1.0.4/go.mod h1:Pe8zjlRrJ80+q2CxHLfEOfTwxCZ4O+MuhcHcfgNWTk0= +github.com/ettle/strcase v0.1.1 h1:htFueZyVeE1XNnMEfbqp5r67qAN/4r6ya1ysq8Q+Zcw= +github.com/ettle/strcase v0.1.1/go.mod h1:hzDLsPC7/lwKyBOywSHEP89nt2pDgdy+No1NBA9o9VY= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= +github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= +github.com/frankban/quicktest v1.14.0 h1:+cqqvzZV87b4adx/5ayVOaYZ2CrvM4ejQvUdBzPPUss= +github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= +github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= +github.com/fullstorydev/grpcurl v1.6.0/go.mod h1:ZQ+ayqbKMJNhzLmbpCiurTVlaK2M/3nqZCxaQ2Ze/sM= +github.com/fzipp/gocyclo v0.4.0 h1:IykTnjwh2YLyYkGa0y92iTTEQcnyAz0r9zOo15EbJ7k= +github.com/fzipp/gocyclo v0.4.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-critic/go-critic v0.6.2 h1:L5SDut1N4ZfsWZY0sH4DCrsHLHnhuuWak2wa165t9gs= +github.com/go-critic/go-critic v0.6.2/go.mod h1:td1s27kfmLpe5G/DPjlnFI7o1UCzePptwU7Az0V5iCM= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-redis/redis v6.15.8+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-toolsmith/astcast v1.0.0 h1:JojxlmI6STnFVG9yOImLeGREv8W2ocNUM+iOhR6jE7g= +github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4= +github.com/go-toolsmith/astcopy v1.0.0 h1:OMgl1b1MEpjFQ1m5ztEO06rz5CUd3oBv9RF7+DyvdG8= +github.com/go-toolsmith/astcopy v1.0.0/go.mod h1:vrgyG+5Bxrnz4MZWPF+pI4R8h3qKRjjyvV/DSez4WVQ= +github.com/go-toolsmith/astequal v1.0.0/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY= +github.com/go-toolsmith/astequal v1.0.1 h1:JbSszi42Jiqu36Gnf363HWS9MTEAz67vTQLponh3Moc= +github.com/go-toolsmith/astequal v1.0.1/go.mod h1:4oGA3EZXTVItV/ipGiOx7NWkY5veFfcsOJVS2YxltLw= +github.com/go-toolsmith/astfmt v1.0.0 h1:A0vDDXt+vsvLEdbMFJAUBI/uTbRw1ffOPnxsILnFL6k= +github.com/go-toolsmith/astfmt v1.0.0/go.mod h1:cnWmsOAuq4jJY6Ct5YWlVLmcmLMn1JUPuQIHCY7CJDw= +github.com/go-toolsmith/astp v1.0.0 h1:alXE75TXgcmupDsMK1fRAy0YUzLzqPVvBKoyWV+KPXg= +github.com/go-toolsmith/astp v1.0.0/go.mod h1:RSyrtpVlfTFGDYRbrjyWP1pYu//tSFcvdYrA8meBmLI= +github.com/go-toolsmith/pkgload v1.0.2-0.20220101231613-e814995d17c5 h1:eD9POs68PHkwrx7hAB78z1cb6PfGq/jyWn3wJywsH1o= +github.com/go-toolsmith/pkgload v1.0.2-0.20220101231613-e814995d17c5/go.mod h1:3NAwwmD4uY/yggRxoEjk/S00MIV3A+H7rrE3i87eYxM= +github.com/go-toolsmith/strparse v1.0.0 h1:Vcw78DnpCAKlM20kSbAyO4mPfJn/lyYA4BJUDxe2Jb4= +github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= +github.com/go-toolsmith/typep v1.0.2 h1:8xdsa1+FSIH/RhEkgnD1j2CJOy5mNllW1Q9tRiYwvlk= +github.com/go-toolsmith/typep v1.0.2/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU= +github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b h1:khEcpUM4yFcxg4/FHQWkvVRmgijNXRfzkIDHh23ggEo= +github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= +github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 h1:23T5iq8rbUYlhpt5DB4XJkc6BU31uODLD1o1gKvZmD0= +github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4= +github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9uMCefW1WDie15eSP/4MssdenaM= +github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= +github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613 h1:9kfjN3AdxcbsZBf8NjltjWihK2QfBBBZuv91cMFfDHw= +github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613/go.mod h1:SyvUF2NxV+sN8upjjeVYr5W7tyxaT1JVtvhKhOn2ii8= +github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a h1:iR3fYXUjHCR97qWS8ch1y9zPNsgXThGwjKPrYfqMPks= +github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU= +github.com/golangci/golangci-lint v1.44.0 h1:YJPouGNQEdK+x2KsCpWMIBy0q6MSuxHjkWMxJMNj/DU= +github.com/golangci/golangci-lint v1.44.0/go.mod h1:aBolpzNkmYogKPynGKdOWDCEc8LlwnxZC6w/SJ1TaEs= +github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 h1:MfyDlzVjl1hoaPzPD4Gpb/QgoRfSBR0jdhwGyAWwMSA= +github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg= +github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca h1:kNY3/svz5T29MYHubXix4aDDuE3RWHkPvopM/EDv/MA= +github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o= +github.com/golangci/misspell v0.3.5 h1:pLzmVdl3VxTOncgzHcvLOKirdvcx/TydsClUQXTehjo= +github.com/golangci/misspell v0.3.5/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA= +github.com/golangci/revgrep v0.0.0-20210930125155-c22e5001d4f2 h1:SgM7GDZTxtTTQPU84heOxy34iG5Du7F2jcoZnvp+fXI= +github.com/golangci/revgrep v0.0.0-20210930125155-c22e5001d4f2/go.mod h1:LK+zW4MpyytAWQRz0M4xnzEk50lSvqDQKfx304apFkY= +github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 h1:zwtduBRr5SSWhqsYNgcuWO2kFlpdOZbP0+yRjmvPGys= +github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= +github.com/google/certificate-transparency-go v1.1.1/go.mod h1:FDKqPvSXawb2ecErVRrD+nfy23RCzyl7eqVCEmlT1Zs= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200507031123-427632fa3b1c/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/trillian v1.3.11/go.mod h1:0tPraVHrSDkA3BO6vKX67zgLXs6SsOAbHEivX+9mPgw= +github.com/google/uuid v0.0.0-20161128191214-064e2069ce9c/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU= +github.com/gordonklaus/ineffassign v0.0.0-20210914165742-4cc7213b9bc8 h1:PVRE9d4AQKmbelZ7emNig1+NT27DUmKZn5qXxfio54U= +github.com/gordonklaus/ineffassign v0.0.0-20210914165742-4cc7213b9bc8/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0= +github.com/gorhill/cronexpr v0.0.0-20180427100037-88b0669f7d75/go.mod h1:g2644b03hfBX9Ov0ZBDgXXens4rxSxmqFBbhvKv2yVA= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= +github.com/gostaticanalysis/analysisutil v0.0.3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= +github.com/gostaticanalysis/analysisutil v0.1.0/go.mod h1:dMhHRU9KTiDcuLGdy87/2gTR8WruwYZrKdRq9m1O6uw= +github.com/gostaticanalysis/analysisutil v0.4.1/go.mod h1:18U/DLpRgIUd459wGxVHE0fRgmo1UgHDcbw7F5idXu0= +github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk= +github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc= +github.com/gostaticanalysis/comment v1.3.0/go.mod h1:xMicKDx7XRXYdVwY9f9wQpDJVnqWxw9wCauCMKp+IBI= +github.com/gostaticanalysis/comment v1.4.1/go.mod h1:ih6ZxzTHLdadaiSnF5WY3dxUoXfXAlTaRzuaNDlSado= +github.com/gostaticanalysis/comment v1.4.2 h1:hlnx5+S2fY9Zo9ePo4AhgYsYHbM2+eAv8m/s1JiCd6Q= +github.com/gostaticanalysis/comment v1.4.2/go.mod h1:KLUTGDv6HOCotCH8h2erHKmpci2ZoR8VPu34YA2uzdM= +github.com/gostaticanalysis/forcetypeassert v0.1.0 h1:6eUflI3DiGusXGK6X7cCcIgVCpZ2CiZ1Q7jl6ZxNV70= +github.com/gostaticanalysis/forcetypeassert v0.1.0/go.mod h1:qZEedyP/sY1lTGV1uJ3VhWZ2mqag3IkWsDHVbplHXak= +github.com/gostaticanalysis/nilerr v0.1.1 h1:ThE+hJP0fEp4zWLkWHWcRyI2Od0p7DlgYG3Uqrmrcpk= +github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW0HU0GPE3+5PWN4A= +github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M= +github.com/gostaticanalysis/testutil v0.4.0 h1:nhdCmubdmDF6VEatUNjgUZBJKWRqugoISdUv3PPQgHY= +github.com/gostaticanalysis/testutil v0.4.0/go.mod h1:bLIoPefWXrRi/ssLFWX1dx7Repi5x3CuviD3dgAZaBU= +github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.12.1/go.mod h1:8XEsbTttt/W+VvjtQhLACqCisSPWTxCZ7sBRjU6iH9c= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= +github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= +github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0= +github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI= +github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= +github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= +github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= +github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo= +github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= +github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.4/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jgautheron/goconst v1.5.1 h1:HxVbL1MhydKs8R8n/HE5NPvzfaYmQJA3o879lE4+WcM= +github.com/jgautheron/goconst v1.5.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= +github.com/jhump/protoreflect v1.6.1/go.mod h1:RZQ/lnuN+zqeRVpQigTwO6o0AJUkxbnSnpuG7toUTG4= +github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs= +github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= +github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af h1:KA9BjwUk7KlCh6S9EAGWBt1oExIUv9WyNCiRz5amv48= +github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jonboulle/clockwork v0.2.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/josharian/txtarfs v0.0.0-20210218200122-0702f000015a/go.mod h1:izVPOvVRsHiKkeGCT6tYBNWyDVuzj9wAaBb5R9qamfw= +github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/juju/ratelimit v1.0.1/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julz/importas v0.1.0 h1:F78HnrsjY3cR7j0etXy5+TU1Zuy7Xt08X/1aJnH5xXY= +github.com/julz/importas v0.1.0/go.mod h1:oSFU2R4XK/P7kNBrnL/FEQlDGN1/6WoxXEjSSXO0DV0= +github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/errcheck v1.6.0 h1:YTDO4pNy7AUN/021p+JGHycQyYNIyMoenM1YDVK6RlY= +github.com/kisielk/errcheck v1.6.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23 h1:FOOIBWrEkLgmlgGfMuZT83xIwfPDxEI2OHu6xUmJMFE= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kulti/thelper v0.5.0 h1:CiEKStgoG4K9bjf/zk3eNX0D0J2iFWzxEY+h9UXmlJg= +github.com/kulti/thelper v0.5.0/go.mod h1:vMu2Cizjy/grP+jmsvOFDx1kYP6+PD1lqg4Yu5exl2U= +github.com/kunwardeep/paralleltest v1.0.3 h1:UdKIkImEAXjR1chUWLn+PNXqWUGs//7tzMeWuP7NhmI= +github.com/kunwardeep/paralleltest v1.0.3/go.mod h1:vLydzomDFpk7yu5UX02RmP0H8QfRPOV/oFhWN85Mjb4= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/kyoh86/exportloopref v0.1.8 h1:5Ry/at+eFdkX9Vsdw3qU4YkvGtzuVfzT4X7S77LoN/M= +github.com/kyoh86/exportloopref v0.1.8/go.mod h1:1tUcJeiioIs7VWe5gcOObrux3lb66+sBqGZrRkMwPgg= +github.com/ldez/gomoddirectives v0.2.2 h1:p9/sXuNFArS2RLc+UpYZSI4KQwGMEDWC/LbtF5OPFVg= +github.com/ldez/gomoddirectives v0.2.2/go.mod h1:cpgBogWITnCfRq2qGoDkKMEVSaarhdBr6g8G04uz6d0= +github.com/ldez/tagliatelle v0.3.0 h1:Aubm2ZsrsjIGFvdxemMPJaXrSJ5Cys6VWyTQFt9k2dI= +github.com/ldez/tagliatelle v0.3.0/go.mod h1:8s6WJQwEYHbKZDsp/LjArytKOG8qaMrKQQ3mFukHs88= +github.com/leonklingele/grouper v1.1.0 h1:tC2y/ygPbMFSBOs3DcyaEMKnnwH7eYKzohOtRrf0SAg= +github.com/leonklingele/grouper v1.1.0/go.mod h1:uk3I3uDfi9B6PeUjsCKi6ndcf63Uy7snXgR4yDYQVDY= +github.com/letsencrypt/pkcs11key/v4 v4.0.0/go.mod h1:EFUvBDay26dErnNb70Nd0/VW3tJiIbETBPTl9ATXQag= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= +github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/maratori/testpackage v1.0.1 h1:QtJ5ZjqapShm0w5DosRjg0PRlSdAdlx+W6cCKoALdbQ= +github.com/maratori/testpackage v1.0.1/go.mod h1:ddKdw+XG0Phzhx8BFDTKgpWP4i7MpApTE5fXSKAqwDU= +github.com/matoous/godox v0.0.0-20210227103229-6504466cf951 h1:pWxk9e//NbPwfxat7RXkts09K+dEBJWakUWwICVqYbA= +github.com/matoous/godox v0.0.0-20210227103229-6504466cf951/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s= +github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= +github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mbilski/exhaustivestruct v1.2.0 h1:wCBmUnSYufAHO6J4AVWY6ff+oxWxsVFrwgOdMUQePUo= +github.com/mbilski/exhaustivestruct v1.2.0/go.mod h1:OeTBVxQWoEmB2J2JCHmXWPJ0aksxSUOUy+nvtVEfzXc= +github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517 h1:zpIH83+oKzcpryru8ceC6BxnoG8TBrhgAvRg8obzup0= +github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517/go.mod h1:KQ7+USdGKfpPjXk4Ga+5XxQM4Lm4e3gAogrreFAYpOg= +github.com/mgechev/revive v1.1.3 h1:6tBZacs2/uv9UOpkBQhCtXh2NGgu2Ry97ZyjcN6uDCM= +github.com/mgechev/revive v1.1.3/go.mod h1:jMzDa13teAuv/KLeqgJw79NDe+1IT0ZO3Mht0vN1Yls= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= +github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= +github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= +github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= +github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/moricho/tparallel v0.2.1 h1:95FytivzT6rYzdJLdtfn6m1bfFJylOJK41+lgv/EHf4= +github.com/moricho/tparallel v0.2.1/go.mod h1:fXEIZxG2vdfl0ZF8b42f5a78EhjjD5mX8qUplsoSU4k= +github.com/mozilla/scribe v0.0.0-20180711195314-fb71baf557c1/go.mod h1:FIczTrinKo8VaLxe6PWTPEXRXDIHz2QAwiaBaP5/4a8= +github.com/mozilla/tls-observatory v0.0.0-20210609171429-7bc42856d2e5/go.mod h1:FUqVoUPHSEdDR0MnFM3Dh8AU0pZHLXUD127SAJGER/s= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-proto-validators v0.0.0-20180403085117-0950a7990007/go.mod h1:m2XC9Qq0AlmmVksL6FktJCdTYyLk7V3fKyp0sl1yWQo= +github.com/mwitkow/go-proto-validators v0.2.0/go.mod h1:ZfA1hW+UH/2ZHOWvQ3HnQaU0DtnpXu850MZiy+YUgcc= +github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U= +github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE= +github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 h1:4kuARK6Y6FxaNu/BnU2OAaLF86eTVhP2hjTB6iMvItA= +github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJerMDfblTH7p5MZaTt+8zaT2iEk3AkVb9PQdZuE8= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nishanths/exhaustive v0.7.11 h1:xV/WU3Vdwh5BUH4N06JNUznb6d5zhRPOnlgCrpNYNKA= +github.com/nishanths/exhaustive v0.7.11/go.mod h1:gX+MP7DWMKJmNa1HfMozK+u04hQd3na9i0hyqf3/dOI= +github.com/nishanths/predeclared v0.0.0-20190419143655-18a43bb90ffc/go.mod h1:62PewwiQTlm/7Rj+cxVYqZvDIUc+JjZq6GHAC1fsObQ= +github.com/nishanths/predeclared v0.2.1 h1:1TXtjmy4f3YCFjTxRd8zcFHOmoUir+gp0ESzjFzG2sw= +github.com/nishanths/predeclared v0.2.1/go.mod h1:HvkGJcA3naj4lOwnFXFDkFxVtSqQMB9sbB1usJ+xjQE= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo/v2 v2.0.0 h1:CcuG/HvWNkkaqCUpJifQY8z7qEMBJya6aLPx6ftGyjQ= +github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/ory/go-acc v0.2.6 h1:YfI+L9dxI7QCtWn2RbawqO0vXhiThdXu/RgizJBbaq0= +github.com/ory/go-acc v0.2.6/go.mod h1:4Kb/UnPcT8qRAk3IAxta+hvVapdxTLWtrr7bFLlEgpw= +github.com/ory/viper v1.7.5 h1:+xVdq7SU3e1vNaCsk/ixsfxE4zylk1TJUiJrY647jUE= +github.com/ory/viper v1.7.5/go.mod h1:ypOuyJmEUb3oENywQZRgeAMwqgOyDqwboO1tj3DjTaM= +github.com/otiai10/copy v1.2.0 h1:HvG945u96iNadPoG2/Ja2+AUJeW5YuFQMixq9yirC+k= +github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= +github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= +github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= +github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= +github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs= +github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= +github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d h1:CdDQnGF8Nq9ocOS/xlSptM1N3BbrA6/kmaep5ggwaIA= +github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/polyfloyd/go-errorlint v0.0.0-20211125173453-6d6d39c5bb8b h1:/BDyEJWLnDUYKGWdlNx/82qSaVu2bUok/EvPUtIGuvw= +github.com/polyfloyd/go-errorlint v0.0.0-20211125173453-6d6d39c5bb8b/go.mod h1:wi9BfjxjF/bwiZ701TzmfKu6UKC357IOAtNr0Td0Lvw= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_golang v1.7.1 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNjaVUwnjpdpA= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/pseudomuto/protoc-gen-doc v1.3.2/go.mod h1:y5+P6n3iGrbKG+9O04V5ld71in3v/bX88wUwgt+U8EA= +github.com/pseudomuto/protokit v0.2.0/go.mod h1:2PdH30hxVHsup8KpBTOXTBeMVhJZVio3Q8ViKSAXT0Q= +github.com/quasilyte/go-ruleguard v0.3.1-0.20210203134552-1b5a410e1cc8/go.mod h1:KsAh3x0e7Fkpgs+Q9pNLS5XpFSvYCEVl5gP9Pp1xp30= +github.com/quasilyte/go-ruleguard v0.3.15 h1:iWYzp1z72IlXTioET0+XI6SjQdPfMGfuAiZiKznOt7g= +github.com/quasilyte/go-ruleguard v0.3.15/go.mod h1:NhuWhnlVEM1gT1A4VJHYfy9MuYSxxwHgxWoPsn9llB4= +github.com/quasilyte/go-ruleguard/dsl v0.3.0/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= +github.com/quasilyte/go-ruleguard/dsl v0.3.12-0.20220101150716-969a394a9451/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= +github.com/quasilyte/go-ruleguard/dsl v0.3.12/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= +github.com/quasilyte/go-ruleguard/dsl v0.3.15/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= +github.com/quasilyte/go-ruleguard/rules v0.0.0-20201231183845-9e62ed36efe1/go.mod h1:7JTjp89EGyU1d6XfBiXihJNG37wB2VRkd125Q1u7Plc= +github.com/quasilyte/go-ruleguard/rules v0.0.0-20211022131956-028d6511ab71/go.mod h1:4cgAphtvu7Ftv7vOT2ZOYhC6CvBxZixcasr8qIOTA50= +github.com/quasilyte/gogrep v0.0.0-20220103110004-ffaa07af02e3 h1:P4QPNn+TK49zJjXKERt/vyPbv/mCHB/zQ4flDYOMN+M= +github.com/quasilyte/gogrep v0.0.0-20220103110004-ffaa07af02e3/go.mod h1:wSEyW6O61xRV6zb6My3HxrQ5/8ke7NE2OayqCHa3xRM= +github.com/quasilyte/regex/syntax v0.0.0-20200407221936-30656e2c4a95 h1:L8QM9bvf68pVdQ3bCFZMDmnt9yqcMBro1pC7F+IPYMY= +github.com/quasilyte/regex/syntax v0.0.0-20200407221936-30656e2c4a95/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0= +github.com/rinchsan/gosimports v0.1.5 h1:Z/l9lS79z0xgKC6fLJYmDdY44D0LFwo3MzaMtWvMKpY= +github.com/rinchsan/gosimports v0.1.5/go.mod h1:102/jU2cwf9fpa/YM9D9o4gSen2Vg8Jl80Sxctgd9N0= +github.com/robertkrimen/godocdown v0.0.0-20130622164427-0bfa04905481/go.mod h1:C9WhFzY47SzYBIvzFqSvHIR6ROgDo4TtdTuRaOMjF/s= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= +github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryancurrah/gomodguard v1.2.3 h1:ww2fsjqocGCAFamzvv/b8IsRduuHHeK2MHTcTxZTQX8= +github.com/ryancurrah/gomodguard v1.2.3/go.mod h1:rYbA/4Tg5c54mV1sv4sQTP5WOPBcoLtnBZ7/TEhXAbg= +github.com/ryanrolds/sqlclosecheck v0.3.0 h1:AZx+Bixh8zdUBxUA1NxbxVAS78vTPq4rCb8OUZI9xFw= +github.com/ryanrolds/sqlclosecheck v0.3.0/go.mod h1:1gREqxyTGR3lVtpngyFo3hZAgk0KCtEdgEkHwDbigdA= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sagikazarmark/crypt v0.1.0/go.mod h1:B/mN0msZuINBtQ1zZLEQcegFJJf9vnYIR88KRMEuODE= +github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= +github.com/sagikazarmark/crypt v0.4.0/go.mod h1:ALv2SRj7GxYV4HO9elxH9nS6M9gW+xDNxqmyJ6RfDFM= +github.com/sanposhiho/wastedassign/v2 v2.0.6 h1:+6/hQIHKNJAUixEj6EmOngGIisyeI+T3335lYTyxRoA= +github.com/sanposhiho/wastedassign/v2 v2.0.6/go.mod h1:KyZ0MWTwxxBmfwn33zh3k1dmsbF2ud9pAAGfoLfjhtI= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/securego/gosec/v2 v2.9.6 h1:ysfvgQBp2zmTgXQl65UkqEkYlQGbnVSRUGpCrJiiR4c= +github.com/securego/gosec/v2 v2.9.6/go.mod h1:EESY9Ywxo/Zc5NyF/qIj6Cop+4PSWM0F0OfGD7FdIXc= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c h1:W65qqJCIOVP4jpqPQ0YvHYKwcMEMVWIzWC5iNQQfBTU= +github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs= +github.com/shirou/gopsutil/v3 v3.21.12/go.mod h1:BToYZVTlSVlfazpDDYFnsVZLaoRG+g8ufT6fPQLdJzA= +github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sivchari/containedctx v1.0.1 h1:fJq44cX+tD+uT5xGrsg25GwiaY61NGybQk9WWKij3Uo= +github.com/sivchari/containedctx v1.0.1/go.mod h1:PwZOeqm4/DLoJOqMSIJs3aKqXRX4YO+uXww087KZ7Bw= +github.com/sivchari/tenv v1.4.7 h1:FdTpgRlTue5eb5nXIYgS/lyVXSjugU8UUVDwhP1NLU8= +github.com/sivchari/tenv v1.4.7/go.mod h1:5nF+bITvkebQVanjU6IuMbvIot/7ReNsUV7I5NbprB0= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sonatard/noctx v0.0.1 h1:VC1Qhl6Oxx9vvWo3UDgrGXYCeKCe3Wbw7qAWL6FrmTY= +github.com/sonatard/noctx v0.0.1/go.mod h1:9D2D/EoULe8Yy2joDHJj7bv3sZoq9AaSb8B4lqBjiZI= +github.com/sourcegraph/go-diff v0.6.1 h1:hmA1LzxW0n1c3Q4YbrFgg4P99GSnebYa3x8gr0HZqLQ= +github.com/sourcegraph/go-diff v0.6.1/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= +github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= +github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/cobra v1.3.0 h1:R7cSvGu+Vv+qX0gW5R/85dx2kmmJT5z5NM8ifdYjdn0= +github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/spf13/viper v1.9.0/go.mod h1:+i6ajR7OX2XaiBkrcZJFK21htRk7eDeLg7+O6bhUPP4= +github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM= +github.com/spf13/viper v1.10.1 h1:nuJZuYpG7gTj/XqiUwg8bA0cp1+M2mC3J4g5luUYBKk= +github.com/spf13/viper v1.10.1/go.mod h1:IGlFPqhNAPKRxohIzWpI5QEy4kuI7tcl5WvR+8qy1rU= +github.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YEwQ0= +github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= +github.com/stephens2424/writerset v1.0.2 h1:znRLgU6g8RS5euYRcy004XeE4W+Tu44kALzy7ghPif8= +github.com/stephens2424/writerset v1.0.2/go.mod h1:aS2JhsMn6eA7e82oNmW4rfsgAOp9COBTTl8mzkwADnc= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v0.0.0-20170130113145-4d4bfba8f1d1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/sylvia7788/contextcheck v1.0.4 h1:MsiVqROAdr0efZc/fOCt0c235qm9XJqHtWwM+2h2B04= +github.com/sylvia7788/contextcheck v1.0.4/go.mod h1:vuPKJMQ7MQ91ZTqfdyreNKwZjyUg6KO+IebVyQDedZQ= +github.com/tdakkota/asciicheck v0.1.1 h1:PKzG7JUTUmVspQTDqtkX9eSiLGossXTybutHwTXuO0A= +github.com/tdakkota/asciicheck v0.1.1/go.mod h1:yHp0ai0Z9gUljN3o0xMhYJnH/IcvkdTBOX2fmJ93JEM= +github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA= +github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= +github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag= +github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= +github.com/tetafro/godot v1.4.11 h1:BVoBIqAf/2QdbFmSwAWnaIqDivZdOV0ZRwEm6jivLKw= +github.com/tetafro/godot v1.4.11/go.mod h1:LR3CJpxDVGlYOWn3ZZg1PgNZdTUvzsZWu8xaEohUpn8= +github.com/timakin/bodyclose v0.0.0-20210704033933-f49887972144 h1:kl4KhGNsJIbDHS9/4U9yQo1UcPQM0kOMJHn29EoH/Ro= +github.com/timakin/bodyclose v0.0.0-20210704033933-f49887972144/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= +github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs= +github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20200427203606-3cfed13b9966/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tomarrell/wrapcheck/v2 v2.4.0 h1:mU4H9KsqqPZUALOUbVOpjy8qNQbWLoLI9fV68/1tq30= +github.com/tomarrell/wrapcheck/v2 v2.4.0/go.mod h1:68bQ/eJg55BROaRTbMjC7vuhL2OgfoG8bLp9ZyoBfyY= +github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4= +github.com/tommy-muehle/go-mnd/v2 v2.5.0 h1:iAj0a8e6+dXSL7Liq0aXPox36FiN1dBbjA6lt9fl65s= +github.com/tommy-muehle/go-mnd/v2 v2.5.0/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ultraware/funlen v0.0.3 h1:5ylVWm8wsNwH5aWo9438pwvsK0QiqVuUrt9bn7S/iLA= +github.com/ultraware/funlen v0.0.3/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA= +github.com/ultraware/whitespace v0.0.5 h1:hh+/cpIcopyMYbZNVov9iSxvJU3OYQg78Sfaqzi/CzI= +github.com/ultraware/whitespace v0.0.5/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/uudashr/gocognit v1.0.5 h1:rrSex7oHr3/pPLQ0xoWq108XMU8s678FJcQ+aSfOHa4= +github.com/uudashr/gocognit v1.0.5/go.mod h1:wgYz0mitoKOTysqxTDMOUXg+Jb5SvtihkfmugIZYpEA= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.30.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus= +github.com/valyala/quicktemplate v1.7.0/go.mod h1:sqKJnoaOF88V07vkO+9FL8fb9uZg/VPSJnLYn+LmLk8= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +github.com/viki-org/dnscache v0.0.0-20130720023526-c70c1f23c5d8/go.mod h1:dniwbG03GafCjFohMDmz6Zc6oCuiqgH6tGNyXTkHzXE= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM= +github.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk= +github.com/yeya24/promlinter v0.1.1-0.20210918184747-d757024714a1 h1:YAaOqqMTstELMMGblt6yJ/fcOt4owSYuw3IttMnKfAM= +github.com/yeya24/promlinter v0.1.1-0.20210918184747-d757024714a1/go.mod h1:rs5vtZzeBHqqMwXqFScncpCF6u06lezhZepno9AB1Oc= +github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= +github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +gitlab.com/bosi/decorder v0.2.1 h1:ehqZe8hI4w7O4b1vgsDZw1YU1PE7iJXrQWFMsocbQ1w= +gitlab.com/bosi/decorder v0.2.1/go.mod h1:6C/nhLSbF6qZbYD8bRmISBwc6vcWdNsiIBkRvjJFrH0= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.etcd.io/etcd v0.0.0-20200513171258-e048e166ab9c/go.mod h1:xCI7ZzBfRuGgBXyXO6yfWfDmlWd35khcWpUa4L0xI/k= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= +go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs= +go.mozilla.org/mozlog v0.0.0-20170222151521-4bb13139d403/go.mod h1:jHoPAGnDrCy6kaI2tAze5Prf0Nr0w/oNkROt2lw3n3o= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180501155221-613d6eafa307/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce h1:Roh6XWxHFKrPgC/EQhVubSAGQ6Ozk6IdxHSzt1mR0EI= +golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210915083310-ed5796bab164/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211213223007-03aa0b5f6827/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190307163923-6a08e3108db3/go.mod h1:25r3+/G6/xytQM8iWZKq3Hn0kr0rgFKPUNVEL/dr3z4= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190311215038-5c2858a9cfe5/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190321232350-e250d351ecad/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190916130336-e45ffcd953cc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117220505-0cba7a3a9ee9/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200324003944-a576cf524670/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200329025819-fd4102a86c65/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200414032229-332987a829c3/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200422022333-3d57cf2e726e/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200622203043-20e05c1c8ffa/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200624225443-88f3c62a19ff/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200625211823-6506e20df31f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200626171337-aa94e735be7f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200630154851-b2d8b0336632/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200706234117-b22de6825cf7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200812195022-5ae4c3c160a0/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200820010801-b793a1359eac/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200831203904-5a2aa26beb65/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201001104356-43ebab892c4c/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= +golang.org/x/tools v0.0.0-20201002184944-ecd9fd270d5d/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= +golang.org/x/tools v0.0.0-20201023174141-c8cfbd0f21e6/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201028025901-8cd080b735b3/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201114224030-61ea331ec02b/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201118003311-bd56c0adb394/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201230224404-63754364767c/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= +golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +golang.org/x/tools v0.1.9-0.20211228192929-ee1ca4ffc4da h1:Tno72dYE94v/7SyyIj9iBsc7OOjFu2PyNcl7yxxeZD8= +golang.org/x/tools v0.1.9-0.20211228192929-ee1ca4ffc4da/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.10.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= +google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU= +google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= +google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw= +google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181107211654-5fc9ac540362/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200626011028-ee7919e894b5/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200707001353-8e8330bf89df/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.0/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.63.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI= +gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.6/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.2.2 h1:MNh1AVMyVX23VUHE2O27jm6lNj3vjO5DexS4A1xvnzk= +honnef.co/go/tools v0.2.2/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY= +mvdan.cc/gofumpt v0.2.1 h1:7jakRGkQcLAJdT+C8Bwc9d0BANkVPSkHZkzNv07pJAs= +mvdan.cc/gofumpt v0.2.1/go.mod h1:a/rvZPhsNaedOJBzqRD9omnwVwHZsBdJirXHa9Gh9Ig= +mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed h1:WX1yoOaKQfddO/mLzdV4wptyWgoH/6hwLs7QHTixo0I= +mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= +mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b h1:DxJ5nJdkhDlLok9K6qO+5290kphDJbHOQO1DFFFTeBo= +mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= +mvdan.cc/unparam v0.0.0-20211214103731-d0ef000c54e5 h1:Jh3LAeMt1eGpxomyu3jVkmVZWW2MxZ1qIIV2TZ/nRio= +mvdan.cc/unparam v0.0.0-20211214103731-d0ef000c54e5/go.mod h1:b8RRCBm0eeiWR8cfN88xeq2G5SG3VKGO+5UPWi5FSOY= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= diff --git a/tools/tools.go b/tools/tools.go new file mode 100644 index 0000000000..fc0da400cb --- /dev/null +++ b/tools/tools.go @@ -0,0 +1,18 @@ +//go:build tools +// +build tools + +package lnd + +// The other imports represent our build tools. Instead of defining a commit we +// want to use for those golang based tools, we use the go mod versioning system +// to unify the way we manage dependencies. So we define our build tool +// dependencies here and pin the version in go.mod. +import ( + _ "github.com/btcsuite/btcd" + _ "github.com/dvyukov/go-fuzz/go-fuzz" + _ "github.com/dvyukov/go-fuzz/go-fuzz-build" + _ "github.com/dvyukov/go-fuzz/go-fuzz-dep" + _ "github.com/golangci/golangci-lint/cmd/golangci-lint" + _ "github.com/ory/go-acc" + _ "github.com/rinchsan/gosimports/cmd/gosimports" +) diff --git a/tor/cmd_info.go b/tor/cmd_info.go index ea6452e6c2..76414e4ada 100644 --- a/tor/cmd_info.go +++ b/tor/cmd_info.go @@ -3,6 +3,7 @@ package tor import ( "errors" "fmt" + "strings" ) var ( @@ -10,9 +11,9 @@ var ( // service while it's not been created yet. ErrServiceNotCreated = errors.New("onion service hasn't been created") - // ErrServiceIDUnmatch is used when the serviceID the controller has + // ErrServiceIDMismatch is used when the serviceID the controller has // doesn't match the serviceID the Tor daemon has. - ErrServiceIDUnmatch = errors.New("onion serviceIDs not match") + ErrServiceIDMismatch = errors.New("onion serviceIDs don't match") // ErrNoServiceFound is used when the Tor daemon replies no active // onion services found for the current control connection while we @@ -62,10 +63,14 @@ func (c *Controller) CheckOnionService() error { } // Check that our active service is indeed the service acknowledged by - // Tor daemon. - if c.activeServiceID != serviceID { + // Tor daemon. The controller is only aware of a single service but the + // Tor daemon might have multiple services registered (for example for + // the watchtower as well as the node p2p connections). So we just want + // to check that our current controller's ID is contained in the list of + // registered services. + if !strings.Contains(serviceID, c.activeServiceID) { return fmt.Errorf("%w: controller has: %v, Tor daemon has: %v", - ErrServiceIDUnmatch, c.activeServiceID, serviceID) + ErrServiceIDMismatch, c.activeServiceID, serviceID) } return nil diff --git a/tor/cmd_info_test.go b/tor/cmd_info_test.go index 81e9d051da..567c441aba 100644 --- a/tor/cmd_info_test.go +++ b/tor/cmd_info_test.go @@ -61,7 +61,30 @@ func TestCheckOnionServiceFailOnServiceIDNotMatch(t *testing.T) { require.NoError(t, err, "server failed to write") // Check the error returned from GetServiceInfo is expected. - require.ErrorIs(t, c.CheckOnionService(), ErrServiceIDUnmatch) + require.ErrorIs(t, c.CheckOnionService(), ErrServiceIDMismatch) +} + +func TestCheckOnionServiceSucceedOnMultipleServices(t *testing.T) { + t.Parallel() + + // Create mock server and client connection. + proxy := createTestProxy(t) + defer proxy.cleanUp() + server := proxy.serverConn + + // Assign a fake service ID to the controller. + c := &Controller{conn: proxy.clientConn, activeServiceID: "fakeID"} + + // Mock a response with a different serviceID. + serverResp := "250-onions/current=service1,fakeID,service2\n250 OK\n" + + // Let the server mocks a given response. + _, err := server.Write([]byte(serverResp)) + require.NoError(t, err, "server failed to write") + + // No error is expected, the controller's ID is contained within the + // list of active services. + require.NoError(t, c.CheckOnionService()) } func TestCheckOnionServiceFailOnClosedConnection(t *testing.T) { diff --git a/tor/cmd_onion.go b/tor/cmd_onion.go index 1e952428ee..bdf6cb828a 100644 --- a/tor/cmd_onion.go +++ b/tor/cmd_onion.go @@ -22,6 +22,12 @@ const ( // V3 denotes that the onion service is V3. V3 + + // V2KeyParam is a parameter that Tor accepts for a new V2 service. + V2KeyParam = "RSA1024" + + // V3KeyParam is a parameter that Tor accepts for a new V3 service. + V3KeyParam = "ED25519-V3" ) // OnionStore is a store containing information about a particular onion @@ -117,9 +123,9 @@ func (c *Controller) prepareKeyparam(cfg AddOnionConfig) (string, error) { switch cfg.Type { // TODO(yy): drop support for v2. case V2: - keyParam = "NEW:RSA1024" + keyParam = "NEW:" + V2KeyParam case V3: - keyParam = "NEW:ED25519-V3" + keyParam = "NEW:" + V3KeyParam } if cfg.Store != nil { @@ -142,11 +148,12 @@ func (c *Controller) prepareKeyparam(cfg AddOnionConfig) (string, error) { // prepareAddOnion constructs a cmd command string based on the specified // config. -func (c *Controller) prepareAddOnion(cfg AddOnionConfig) (string, error) { +func (c *Controller) prepareAddOnion(cfg AddOnionConfig) ( + string, string, error) { // Create the keyParam. keyParam, err := c.prepareKeyparam(cfg) if err != nil { - return "", err + return "", "", err } // Now, we'll create a mapping from the virtual port to each target @@ -178,7 +185,7 @@ func (c *Controller) prepareAddOnion(cfg AddOnionConfig) (string, error) { // await its response. cmd := fmt.Sprintf("ADD_ONION %s %s", keyParam, portParam) - return cmd, nil + return cmd, keyParam, nil } // AddOnion creates an ephemeral onion service and returns its onion address. @@ -201,7 +208,7 @@ func (c *Controller) AddOnion(cfg AddOnionConfig) (*OnionAddr, error) { } // Construct the cmd command. - cmd, err := c.prepareAddOnion(cfg) + cmd, keyParam, err := c.prepareAddOnion(cfg) if err != nil { return nil, err } @@ -233,11 +240,18 @@ func (c *Controller) AddOnion(cfg AddOnionConfig) (*OnionAddr, error) { return nil, errors.New("service id not found in reply") } - // If a new onion service was created and an onion store was provided, - // we'll store its private key to disk in the event that it needs to be - // recreated later on. - if privateKey, ok := replyParams["PrivateKey"]; cfg.Store != nil && ok { - err := cfg.Store.StorePrivateKey(cfg.Type, []byte(privateKey)) + // If a new onion service was created, use the new private key for storage + newPrivateKey, ok := replyParams["PrivateKey"] + if ok { + keyParam = newPrivateKey + } + + // If an onion store was provided and a key return wasn't requested, + // we'll store its private key to disk in the event that it needs to + // be recreated later on. We write the private key to disk every time + // in case the user toggles the --tor.encryptkey flag. + if cfg.Store != nil { + err := cfg.Store.StorePrivateKey(cfg.Type, []byte(keyParam)) if err != nil { return nil, fmt.Errorf("unable to write private key "+ "to file: %v", err) @@ -253,6 +267,7 @@ func (c *Controller) AddOnion(cfg AddOnionConfig) (*OnionAddr, error) { return &OnionAddr{ OnionService: serviceID + ".onion", Port: cfg.VirtualPort, + PrivateKey: keyParam, }, nil } diff --git a/tor/cmd_onion_test.go b/tor/cmd_onion_test.go index e7356cb074..23efa251ca 100644 --- a/tor/cmd_onion_test.go +++ b/tor/cmd_onion_test.go @@ -173,7 +173,7 @@ func TestPrepareAddOnion(t *testing.T) { controller := NewController("", tc.targetIPAddress, "") t.Run(tc.name, func(t *testing.T) { - cmd, err := controller.prepareAddOnion(tc.cfg) + cmd, _, err := controller.prepareAddOnion(tc.cfg) require.Equal(t, tc.expectedErr, err) require.Equal(t, tc.expectedCmd, cmd) @@ -181,7 +181,6 @@ func TestPrepareAddOnion(t *testing.T) { store.AssertExpectations(t) }) } - } // mockStore implements a mock of the interface OnionStore. diff --git a/tor/controller.go b/tor/controller.go index ad92625781..c69a568d04 100644 --- a/tor/controller.go +++ b/tor/controller.go @@ -582,7 +582,7 @@ func computeHMAC256(key, message []byte) []byte { } // supportsV3 is a helper function that parses the current version of the Tor -// server and determines whether it supports creationg v3 onion services through +// server and determines whether it supports creating v3 onion services through // Tor's control port. The version string should be of the format: // major.minor.revision.build func supportsV3(version string) error { diff --git a/tor/controller_test.go b/tor/controller_test.go index 8fab16e38d..64c6ae8bf2 100644 --- a/tor/controller_test.go +++ b/tor/controller_test.go @@ -99,7 +99,7 @@ type testProxy struct { // cleanUp is used after each test to properly close the ports/connections. func (tp *testProxy) cleanUp() { - // Don't bother cleanning if there's no a server created. + // Don't bother cleaning if there's no a server created. if tp.server == nil { return } @@ -148,7 +148,7 @@ func createTestProxy(t *testing.T) *testProxy { return tc } -// TestReadResponse contructs a series of possible responses returned by Tor +// TestReadResponse constructs a series of possible responses returned by Tor // and asserts the readResponse can handle them correctly. func TestReadResponse(t *testing.T) { // Create mock server and client connection. @@ -258,7 +258,6 @@ func TestReadResponse(t *testing.T) { for _, tc := range testCase { tc := tc t.Run(tc.name, func(t *testing.T) { - // Let the server mocks a given response. _, err := server.Write([]byte(tc.serverResp)) require.NoError(t, err, "server failed to write") diff --git a/tor/go.mod b/tor/go.mod new file mode 100644 index 0000000000..1a0865daac --- /dev/null +++ b/tor/go.mod @@ -0,0 +1,12 @@ +module github.com/lightningnetwork/lnd/tor + +require ( + github.com/btcsuite/btcd v0.22.0-beta.0.20220207191057-4dc4ff7963b4 + github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f + github.com/miekg/dns v1.1.43 + github.com/stretchr/testify v1.7.0 + golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f + golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect +) + +go 1.16 diff --git a/tor/go.sum b/tor/go.sum new file mode 100644 index 0000000000..a27c306d5c --- /dev/null +++ b/tor/go.sum @@ -0,0 +1,110 @@ +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= +github.com/btcsuite/btcd v0.22.0-beta.0.20220207191057-4dc4ff7963b4 h1:CEGr/598C/0LZQUoioaT6sdGGcJgu4+ck0PDeJ/QkKs= +github.com/btcsuite/btcd v0.22.0-beta.0.20220207191057-4dc4ff7963b4/go.mod h1:7alexyj/lHlOtr2PJK7L/+HDJZpcGDn/pAU98r7DY08= +github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= +github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= +github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg= +github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f h1:OfiFi4JbukWwe3lzw+xunroH1mnC1e2Gy5cxNJApiSY= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/tor/net.go b/tor/net.go index e96c79acd7..8664cefc63 100644 --- a/tor/net.go +++ b/tor/net.go @@ -101,7 +101,7 @@ func (p *ProxyNet) Dial(network, address string, timeout time.Duration) (net.Conn, error) { switch network { - case "tcp", "tcp4", "tcp6": + case "tcp", "tcp4", "tcp6", "onion": default: return nil, errors.New("cannot dial non-tcp network via Tor") } @@ -132,7 +132,7 @@ func (p *ProxyNet) LookupSRV(service, proto, // addresses over Tor. func (p *ProxyNet) ResolveTCPAddr(network, address string) (*net.TCPAddr, error) { switch network { - case "tcp", "tcp4", "tcp6": + case "tcp", "tcp4", "tcp6", "onion": default: return nil, errors.New("cannot dial non-tcp network via Tor") } diff --git a/tor/onionaddr.go b/tor/onionaddr.go index 7570aebc2c..ffc4bb8281 100644 --- a/tor/onionaddr.go +++ b/tor/onionaddr.go @@ -45,6 +45,9 @@ type OnionAddr struct { // Port is the port of the onion address. Port int + + // PrivateKey is the onion address' private key. Only returned if requested. + PrivateKey string } // A compile-time check to ensure that OnionAddr implements the net.Addr diff --git a/tor/onionfile/onion_file.go b/tor/onionfile/onion_file.go new file mode 100644 index 0000000000..7e8ecfda5b --- /dev/null +++ b/tor/onionfile/onion_file.go @@ -0,0 +1,111 @@ +package onionfile + +import ( + "bytes" + "errors" + "fmt" + "io/ioutil" + "os" + + "github.com/lightningnetwork/lnd/keychain" + "github.com/lightningnetwork/lnd/lnencrypt" + "github.com/lightningnetwork/lnd/tor" +) + +var ( + // ErrEncryptedTorPrivateKey is thrown when a tor private key is + // encrypted, but the user requested an unencrypted key. + ErrEncryptedTorPrivateKey = errors.New("it appears the Tor private key " + + "is encrypted but you didn't pass the --tor.encryptkey flag. " + + "Please restart lnd with the --tor.encryptkey flag or delete " + + "the Tor key file for regeneration") +) + +// OnionFile is a file-based implementation of the OnionStore interface that +// stores an onion service's private key. +type OnionFile struct { + privateKeyPath string + privateKeyPerm os.FileMode + encryptKey bool + keyRing keychain.KeyRing +} + +// A compile-time constraint to ensure OnionFile satisfies the OnionStore +// interface. +var _ tor.OnionStore = (*OnionFile)(nil) + +// NewOnionFile creates a file-based implementation of the OnionStore interface +// to store an onion service's private key. +func NewOnionFile(privateKeyPath string, privateKeyPerm os.FileMode, + encryptKey bool, keyRing keychain.KeyRing) *OnionFile { + + return &OnionFile{ + privateKeyPath: privateKeyPath, + privateKeyPerm: privateKeyPerm, + encryptKey: encryptKey, + keyRing: keyRing, + } +} + +// StorePrivateKey stores the private key at its expected path. It also +// encrypts the key before storing it if requested. +func (f *OnionFile) StorePrivateKey(_ tor.OnionType, privateKey []byte) error { + var b bytes.Buffer + var privateKeyContent []byte + payload := bytes.NewBuffer(privateKey) + + if f.encryptKey { + err := lnencrypt.EncryptPayloadToWriter(*payload, &b, f.keyRing) + if err != nil { + return err + } + privateKeyContent = b.Bytes() + } else { + privateKeyContent = privateKey + } + + err := ioutil.WriteFile( + f.privateKeyPath, privateKeyContent, f.privateKeyPerm, + ) + if err != nil { + return fmt.Errorf("unable to write private key "+ + "to file: %v", err) + } + return nil +} + +// PrivateKey retrieves the private key from its expected path. If the file does +// not exist, then ErrNoPrivateKey is returned. +func (f *OnionFile) PrivateKey(_ tor.OnionType) ([]byte, error) { + // Try to read the Tor private key to pass into the AddOnion call + if _, err := os.Stat(f.privateKeyPath); !errors.Is(err, os.ErrNotExist) { + privateKeyContent, err := ioutil.ReadFile(f.privateKeyPath) + if err != nil { + return nil, err + } + + // If the privateKey doesn't start with either v2 or v3 key params + // it's likely encrypted. + if !bytes.HasPrefix(privateKeyContent, []byte(tor.V2KeyParam)) && + !bytes.HasPrefix(privateKeyContent, []byte(tor.V3KeyParam)) { + // If the privateKeyContent is encrypted but --tor.encryptkey + // wasn't set we return an error + if !f.encryptKey { + return nil, ErrEncryptedTorPrivateKey + } + // Attempt to decrypt the key + reader := bytes.NewReader(privateKeyContent) + privateKeyContent, err = lnencrypt.DecryptPayloadFromReader(reader, f.keyRing) + if err != nil { + return nil, err + } + } + return privateKeyContent, nil + } + return nil, tor.ErrNoPrivateKey +} + +// DeletePrivateKey removes the file containing the private key. +func (f *OnionFile) DeletePrivateKey(_ tor.OnionType) error { + return os.Remove(f.privateKeyPath) +} diff --git a/tor/onionfile/onion_file_test.go b/tor/onionfile/onion_file_test.go new file mode 100644 index 0000000000..18c02d024a --- /dev/null +++ b/tor/onionfile/onion_file_test.go @@ -0,0 +1,86 @@ +package onionfile + +import ( + "bytes" + "io/ioutil" + "path/filepath" + "testing" + + "github.com/btcsuite/btcd/btcec" + "github.com/lightningnetwork/lnd/lntest/channels" + "github.com/lightningnetwork/lnd/lntest/mock" + "github.com/lightningnetwork/lnd/tor" +) + +// TestOnionFile tests that the OnionFile implementation of the OnionStore +// interface behaves as expected. +func TestOnionFile(t *testing.T) { + t.Parallel() + + tempDir, err := ioutil.TempDir("", "onion_store") + if err != nil { + t.Fatalf("unable to create temp dir: %v", err) + } + + privateKey := []byte("RSA1024 hide_me_plz") + privateKeyPath := filepath.Join(tempDir, "secret") + + // Create a new file-based onion store. A private key should not exist + // yet. + onionFile := NewOnionFile(privateKeyPath, 0600, false, &mock.SecretKeyRing{}) + if _, err := onionFile.PrivateKey(tor.V2); err != tor.ErrNoPrivateKey { + t.Fatalf("expected ErrNoPrivateKey, got \"%v\"", err) + } + + // Store the private key and ensure what's stored matches. + if err := onionFile.StorePrivateKey(tor.V2, privateKey); err != nil { + t.Fatalf("unable to store private key: %v", err) + } + storePrivateKey, err := onionFile.PrivateKey(tor.V2) + if err != nil { + t.Fatalf("unable to retrieve private key: %v", err) + } + if !bytes.Equal(storePrivateKey, privateKey) { + t.Fatalf("expected private key \"%v\", got \"%v\"", + string(privateKey), string(storePrivateKey)) + } + + // Finally, delete the private key. We should no longer be able to + // retrieve it. + if err := onionFile.DeletePrivateKey(tor.V2); err != nil { + t.Fatalf("unable to delete private key: %v", err) + } + if _, err := onionFile.PrivateKey(tor.V2); err != tor.ErrNoPrivateKey { + t.Fatal("found deleted private key") + } + + // Create a new file-based onion store that encrypts the key this time + // to ensure that an encrypted key is properly handled. + testPrivKeyBytes := channels.AlicesPrivKey + testPrivKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), + testPrivKeyBytes) + keyRing := &mock.SecretKeyRing{ + RootKey: testPrivKey, + } + + encryptedOnionFile := NewOnionFile( + privateKeyPath, 0600, true, keyRing, + ) + + if err = encryptedOnionFile.StorePrivateKey(tor.V2, privateKey); err != nil { + t.Fatalf("unable to store encrypted private key: %v", err) + } + + storedPrivateKey, err := encryptedOnionFile.PrivateKey(tor.V2) + if err != nil { + t.Fatalf("unable to retrieve encrypted private key: %v", err) + } + if !bytes.Equal(storedPrivateKey, privateKey) { + t.Fatalf("expected private key \"%v\", got \"%v\"", + string(privateKey), string(storePrivateKey)) + } + + if err := encryptedOnionFile.DeletePrivateKey(tor.V2); err != nil { + t.Fatalf("unable to delete private key: %v", err) + } +} diff --git a/walletunlocker/service.go b/walletunlocker/service.go index 92a4bed37e..ce39976669 100644 --- a/walletunlocker/service.go +++ b/walletunlocker/service.go @@ -8,8 +8,8 @@ import ( "os" "time" + "github.com/btcsuite/btcd/btcutil/hdkeychain" "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcutil/hdkeychain" "github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/wallet" "github.com/lightningnetwork/lnd/aezeed" diff --git a/walletunlocker/service_test.go b/walletunlocker/service_test.go index 001872383f..88274459bb 100644 --- a/walletunlocker/service_test.go +++ b/walletunlocker/service_test.go @@ -89,6 +89,7 @@ func createTestWalletWithPw(t *testing.T, pubPw, privPw []byte, dir string, func createSeedAndMnemonic(t *testing.T, pass []byte) (*aezeed.CipherSeed, aezeed.Mnemonic) { + cipherSeed, err := aezeed.New( keychain.KeyDerivationVersion, &testEntropy, time.Now(), ) diff --git a/watchtower/blob/justice_kit.go b/watchtower/blob/justice_kit.go index 20c44cefc5..56b8ae4d54 100644 --- a/watchtower/blob/justice_kit.go +++ b/watchtower/blob/justice_kit.go @@ -8,12 +8,11 @@ import ( "fmt" "io" - "golang.org/x/crypto/chacha20poly1305" - - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/txscript" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnwire" + "golang.org/x/crypto/chacha20poly1305" ) const ( @@ -154,14 +153,14 @@ type JusticeKit struct { // commitment to-local output. func (b *JusticeKit) CommitToLocalWitnessScript() ([]byte, error) { revocationPubKey, err := btcec.ParsePubKey( - b.RevocationPubKey[:], btcec.S256(), + b.RevocationPubKey[:], ) if err != nil { return nil, err } localDelayedPubKey, err := btcec.ParsePubKey( - b.LocalDelayPubKey[:], btcec.S256(), + b.LocalDelayPubKey[:], ) if err != nil { return nil, err @@ -207,9 +206,7 @@ func (b *JusticeKit) CommitToRemoteWitnessScript() ([]byte, error) { // If this is a blob for an anchor channel, we'll return the p2wsh // output containing a CSV delay of 1. if b.BlobType.IsAnchorChannel() { - pk, err := btcec.ParsePubKey( - b.CommitToRemotePubKey[:], btcec.S256(), - ) + pk, err := btcec.ParsePubKey(b.CommitToRemotePubKey[:]) if err != nil { return nil, err } diff --git a/watchtower/blob/justice_kit_test.go b/watchtower/blob/justice_kit_test.go index 07f3676f48..592a0df7e9 100644 --- a/watchtower/blob/justice_kit_test.go +++ b/watchtower/blob/justice_kit_test.go @@ -8,7 +8,8 @@ import ( "reflect" "testing" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/btcsuite/btcd/txscript" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnwire" @@ -255,7 +256,7 @@ func testJusticeKitRemoteWitnessConstruction( t *testing.T, test remoteWitnessTest) { // Generate the to-remote pubkey. - toRemotePrivKey, err := btcec.NewPrivateKey(btcec.S256()) + toRemotePrivKey, err := btcec.NewPrivateKey() require.Nil(t, err) // Copy the to-remote pubkey into the format expected by our justice @@ -266,8 +267,7 @@ func testJusticeKitRemoteWitnessConstruction( // Sign a message using the to-remote private key. The exact message // doesn't matter as we won't be validating the signature's validity. digest := bytes.Repeat([]byte("a"), 32) - rawToRemoteSig, err := toRemotePrivKey.Sign(digest) - require.Nil(t, err) + rawToRemoteSig := ecdsa.Sign(toRemotePrivKey, digest) // Convert the DER-encoded signature into a fixed-size sig. commitToRemoteSig, err := lnwire.NewSigFromSignature(rawToRemoteSig) @@ -323,10 +323,10 @@ func TestJusticeKitToLocalWitnessConstruction(t *testing.T) { csvDelay := uint32(144) // Generate the revocation and delay private keys. - revPrivKey, err := btcec.NewPrivateKey(btcec.S256()) + revPrivKey, err := btcec.NewPrivateKey() require.Nil(t, err) - delayPrivKey, err := btcec.NewPrivateKey(btcec.S256()) + delayPrivKey, err := btcec.NewPrivateKey() require.Nil(t, err) // Copy the revocation and delay pubkeys into the format expected by our @@ -340,8 +340,7 @@ func TestJusticeKitToLocalWitnessConstruction(t *testing.T) { // Sign a message using the revocation private key. The exact message // doesn't matter as we won't be validating the signature's validity. digest := bytes.Repeat([]byte("a"), 32) - rawRevSig, err := revPrivKey.Sign(digest) - require.Nil(t, err) + rawRevSig := ecdsa.Sign(revPrivKey, digest) // Convert the DER-encoded signature into a fixed-size sig. commitToLocalSig, err := lnwire.NewSigFromSignature(rawRevSig) diff --git a/watchtower/config.go b/watchtower/config.go index 4da7ba30f1..184cfc301f 100644 --- a/watchtower/config.go +++ b/watchtower/config.go @@ -5,9 +5,9 @@ import ( "net" "time" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/tor" "github.com/lightningnetwork/lnd/watchtower/lookout" @@ -98,6 +98,12 @@ type Config struct { // for a watchtower hidden service should be stored. WatchtowerKeyPath string + // EncryptKey will encrypt the Tor private key on disk + EncryptKey bool + + // KeyRing is the KeyRing to use when encrypting the Tor private key + KeyRing keychain.KeyRing + // Type specifies the hidden service type (V2 or V3) that the watchtower // will create. Type tor.OnionType diff --git a/watchtower/lookout/justice_descriptor.go b/watchtower/lookout/justice_descriptor.go index 016bc4aaea..dac18b048d 100644 --- a/watchtower/lookout/justice_descriptor.go +++ b/watchtower/lookout/justice_descriptor.go @@ -2,13 +2,14 @@ package lookout import ( "errors" + "fmt" "github.com/btcsuite/btcd/blockchain" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/txsort" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" - "github.com/btcsuite/btcutil/txsort" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/watchtower/blob" "github.com/lightningnetwork/lnd/watchtower/wtdb" @@ -121,7 +122,7 @@ func (p *JusticeDescriptor) commitToRemoteInput() (*breachedInput, error) { } else { // Since the to-remote witness script should just be a regular p2wkh // output, we'll parse it to retrieve the public key. - toRemotePubKey, err := btcec.ParsePubKey(toRemoteScript, btcec.S256()) + toRemotePubKey, err := btcec.ParsePubKey(toRemoteScript) if err != nil { return nil, err } @@ -177,11 +178,11 @@ func (p *JusticeDescriptor) assembleJusticeTxn(txWeight int64, // First, construct add the breached inputs to our justice transaction // and compute the total amount that will be swept. var totalAmt btcutil.Amount - for _, input := range inputs { - totalAmt += btcutil.Amount(input.txOut.Value) + for _, inp := range inputs { + totalAmt += btcutil.Amount(inp.txOut.Value) justiceTxn.AddTxIn(&wire.TxIn{ - PreviousOutPoint: input.outPoint, - Sequence: input.sequence, + PreviousOutPoint: inp.outPoint, + Sequence: inp.sequence, }) } @@ -217,17 +218,22 @@ func (p *JusticeDescriptor) assembleJusticeTxn(txWeight int64, } // Attach each of the provided witnesses to the transaction. - for _, input := range inputs { + prevOutFetcher, err := prevOutFetcher(inputs) + if err != nil { + return nil, fmt.Errorf("error creating previous output "+ + "fetcher: %v", err) + } + for _, inp := range inputs { // Lookup the input's new post-sort position. - i := inputIndex[input.outPoint] - justiceTxn.TxIn[i].Witness = input.witness + i := inputIndex[inp.outPoint] + justiceTxn.TxIn[i].Witness = inp.witness // Validate the reconstructed witnesses to ensure they are valid // for the breached inputs. vm, err := txscript.NewEngine( - input.txOut.PkScript, justiceTxn, i, + inp.txOut.PkScript, justiceTxn, i, txscript.StandardVerifyFlags, - nil, nil, input.txOut.Value, + nil, nil, inp.txOut.Value, prevOutFetcher, ) if err != nil { return nil, err @@ -342,3 +348,20 @@ func buildWitness(witnessStack [][]byte, witnessScript []byte) [][]byte { return witness } + +// prevOutFetcher returns a txscript.MultiPrevOutFetcher for the given set +// of inputs. +func prevOutFetcher(inputs []*breachedInput) (*txscript.MultiPrevOutFetcher, + error) { + + fetcher := txscript.NewMultiPrevOutFetcher(nil) + for _, inp := range inputs { + if inp.txOut == nil { + return nil, fmt.Errorf("missing input utxo information") + } + + fetcher.AddPrevOut(inp.outPoint, inp.txOut) + } + + return fetcher, nil +} diff --git a/watchtower/lookout/justice_descriptor_test.go b/watchtower/lookout/justice_descriptor_test.go index dcc79d3fdd..aa1b10f7bf 100644 --- a/watchtower/lookout/justice_descriptor_test.go +++ b/watchtower/lookout/justice_descriptor_test.go @@ -5,11 +5,11 @@ import ( "time" "github.com/btcsuite/btcd/blockchain" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/txsort" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" - "github.com/btcsuite/btcutil/txsort" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwire" @@ -93,13 +93,13 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) { // Parse the key pairs for all keys used in the test. revSK, revPK := btcec.PrivKeyFromBytes( - btcec.S256(), revPrivBytes, + revPrivBytes, ) _, toLocalPK := btcec.PrivKeyFromBytes( - btcec.S256(), toLocalPrivBytes, + toLocalPrivBytes, ) toRemoteSK, toRemotePK := btcec.PrivKeyFromBytes( - btcec.S256(), toRemotePrivBytes, + toRemotePrivBytes, ) // Create the signer, and add the revocation and to-remote privkeys. @@ -156,7 +156,6 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) { // As it should be. toRemoteSigningScript = toRemoteRedeemScript - } else { toRemoteRedeemScript = toRemotePK.SerializeCompressed() toRemoteScriptHash, err = input.CommitScriptUnencumbered( @@ -270,7 +269,7 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) { justiceTxn.TxOut = outputs txsort.InPlaceSort(justiceTxn) - hashCache := txscript.NewTxSigHashes(justiceTxn) + hashCache := input.NewTxSigHashesV0Only(justiceTxn) // Create the sign descriptor used to sign for the to-local input. toLocalSignDesc := &input.SignDescriptor{ diff --git a/watchtower/lookout/lookout.go b/watchtower/lookout/lookout.go index 1d9f911b38..22966ae93a 100644 --- a/watchtower/lookout/lookout.go +++ b/watchtower/lookout/lookout.go @@ -192,7 +192,7 @@ func (l *Lookout) processEpoch(epoch *chainntnfs.BlockEpoch, // For each match, use our index to retrieve the original transaction, // which corresponds to the breaching commitment transaction. If the - // decryption succeeds, we will accumlate the assembled justice + // decryption succeeds, we will accumulate the assembled justice // descriptors in a single slice var successes []*JusticeDescriptor for _, match := range matches { diff --git a/watchtower/standalone.go b/watchtower/standalone.go index 8e56d962f6..c89cebc239 100644 --- a/watchtower/standalone.go +++ b/watchtower/standalone.go @@ -4,9 +4,10 @@ import ( "net" "sync/atomic" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/lightningnetwork/lnd/brontide" "github.com/lightningnetwork/lnd/tor" + "github.com/lightningnetwork/lnd/tor/onionfile" "github.com/lightningnetwork/lnd/watchtower/lookout" "github.com/lightningnetwork/lnd/watchtower/wtserver" ) @@ -169,8 +170,10 @@ func (w *Standalone) createNewHiddenService() error { onionCfg := tor.AddOnionConfig{ VirtualPort: DefaultPeerPort, TargetPorts: listenPorts, - Store: tor.NewOnionFile(w.cfg.WatchtowerKeyPath, 0600), - Type: w.cfg.Type, + Store: onionfile.NewOnionFile( + w.cfg.WatchtowerKeyPath, 0600, w.cfg.EncryptKey, w.cfg.KeyRing, + ), + Type: w.cfg.Type, } addr, err := w.cfg.TorController.AddOnion(onionCfg) diff --git a/tlv/bench_test.go b/watchtower/tlv_bench_test.go similarity index 99% rename from tlv/bench_test.go rename to watchtower/tlv_bench_test.go index 132032b0f6..5ffabde083 100644 --- a/tlv/bench_test.go +++ b/watchtower/tlv_bench_test.go @@ -1,4 +1,4 @@ -package tlv_test +package watchtower_test import ( "bytes" diff --git a/watchtower/wtclient/backup_task.go b/watchtower/wtclient/backup_task.go index adc000a849..5ac179c0a5 100644 --- a/watchtower/wtclient/backup_task.go +++ b/watchtower/wtclient/backup_task.go @@ -4,11 +4,11 @@ import ( "fmt" "github.com/btcsuite/btcd/blockchain" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/txsort" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" - "github.com/btcsuite/btcutil/txsort" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnwallet" @@ -263,10 +263,14 @@ func (t *backupTask) craftSessionPayload( // information. This will either be contain both the to-local and // to-remote outputs, or only be the to-local output. inputs := t.inputs() - for prevOutPoint, input := range inputs { + prevOutputFetcher := txscript.NewMultiPrevOutFetcher(nil) + for prevOutPoint, inp := range inputs { + prevOutputFetcher.AddPrevOut( + prevOutPoint, inp.SignDesc().Output, + ) justiceTxn.AddTxIn(&wire.TxIn{ PreviousOutPoint: prevOutPoint, - Sequence: input.BlocksToMaturity(), + Sequence: inp.BlocksToMaturity(), }) } @@ -285,7 +289,7 @@ func (t *backupTask) craftSessionPayload( } // Construct a sighash cache to improve signing performance. - hashCache := txscript.NewTxSigHashes(justiceTxn) + hashCache := txscript.NewTxSigHashes(justiceTxn, prevOutputFetcher) // Since the transaction inputs could have been reordered as a result of // the BIP69 sort, create an index mapping each prevout to it's new @@ -304,7 +308,7 @@ func (t *backupTask) craftSessionPayload( // Construct the full witness required to spend this input. inputScript, err := inp.CraftInputScript( - signer, justiceTxn, hashCache, i, + signer, justiceTxn, hashCache, prevOutputFetcher, i, ) if err != nil { return hint, nil, err @@ -316,7 +320,7 @@ func (t *backupTask) craftSessionPayload( witness := inputScript.Witness rawSignature := witness[0][:len(witness[0])-1] - // Reencode the DER signature into a fixed-size 64 byte + // Re-encode the DER signature into a fixed-size 64 byte // signature. signature, err := lnwire.NewSigFromRawSignature(rawSignature) if err != nil { @@ -342,7 +346,7 @@ func (t *backupTask) craftSessionPayload( } } - breachTxID := t.breachInfo.BreachTransaction.TxHash() + breachTxID := t.breachInfo.BreachTxHash // Compute the breach key as SHA256(txid). hint, key := blob.NewBreachHintAndKeyFromHash(&breachTxID) diff --git a/watchtower/wtclient/backup_task_internal_test.go b/watchtower/wtclient/backup_task_internal_test.go index e3183dca96..661f5e144e 100644 --- a/watchtower/wtclient/backup_task_internal_test.go +++ b/watchtower/wtclient/backup_task_internal_test.go @@ -7,11 +7,11 @@ import ( "reflect" "testing" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/input" @@ -103,13 +103,13 @@ func genTaskTest( // Parse the key pairs for all keys used in the test. revSK, revPK := btcec.PrivKeyFromBytes( - btcec.S256(), revPrivBytes, + revPrivBytes, ) _, toLocalPK := btcec.PrivKeyFromBytes( - btcec.S256(), toLocalPrivBytes, + toLocalPrivBytes, ) toRemoteSK, toRemotePK := btcec.PrivKeyFromBytes( - btcec.S256(), toRemotePrivBytes, + toRemotePrivBytes, ) // Create the signer, and add the revocation and to-remote privkeys. @@ -124,8 +124,8 @@ func genTaskTest( // the breach transaction, which we will continue to modify. breachTxn := wire.NewMsgTx(2) breachInfo := &lnwallet.BreachRetribution{ - RevokedStateNum: stateNum, - BreachTransaction: breachTxn, + RevokedStateNum: stateNum, + BreachTxHash: breachTxn.TxHash(), KeyRing: &lnwallet.CommitmentKeyRing{ RevocationKey: revPK, ToLocalKey: toLocalPK, @@ -607,7 +607,7 @@ func testBackupTask(t *testing.T, test backupTaskTest) { } // Verify that the breach hint matches the breach txid's prefix. - breachTxID := test.breachInfo.BreachTransaction.TxHash() + breachTxID := test.breachInfo.BreachTxHash expHint := blob.NewBreachHintFromHash(&breachTxID) if hint != expHint { t.Fatalf("breach hint mismatch, want: %x, got: %v", diff --git a/watchtower/wtclient/candidate_iterator_test.go b/watchtower/wtclient/candidate_iterator_test.go index 63e3a72926..1435754f21 100644 --- a/watchtower/wtclient/candidate_iterator_test.go +++ b/watchtower/wtclient/candidate_iterator_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/watchtower/wtdb" ) @@ -34,12 +34,11 @@ func randAddr(t *testing.T) net.Addr { } func randTower(t *testing.T) *wtdb.Tower { - priv, err := btcec.NewPrivateKey(btcec.S256()) + priv, err := btcec.NewPrivateKey() if err != nil { t.Fatalf("unable to create private key: %v", err) } pubKey := priv.PubKey() - pubKey.Curve = nil return &wtdb.Tower{ ID: wtdb.TowerID(rand.Uint64()), IdentityKey: pubKey, diff --git a/watchtower/wtclient/client.go b/watchtower/wtclient/client.go index f81284d538..49838bc737 100644 --- a/watchtower/wtclient/client.go +++ b/watchtower/wtclient/client.go @@ -8,7 +8,7 @@ import ( "sync" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btclog" "github.com/lightningnetwork/lnd/build" @@ -439,7 +439,7 @@ func (c *TowerClient) buildHighestCommitHeights() { func (c *TowerClient) Start() error { var err error c.started.Do(func() { - c.log.Infof("Starting watchtower client") + c.log.Infof("Watchtower client starting") // First, restart a session queue for any sessions that have // committed but unacked state updates. This ensures that these diff --git a/watchtower/wtclient/client_test.go b/watchtower/wtclient/client_test.go index f20e52868e..fcd256cd1a 100644 --- a/watchtower/wtclient/client_test.go +++ b/watchtower/wtclient/client_test.go @@ -7,11 +7,12 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" @@ -67,7 +68,7 @@ var ( func randPrivKey(t *testing.T) *btcec.PrivateKey { t.Helper() - sk, err := btcec.NewPrivateKey(btcec.S256()) + sk, err := btcec.NewPrivateKey() if err != nil { t.Fatalf("unable to generate pubkey: %v", err) } @@ -300,7 +301,7 @@ func (c *mockChannel) createRemoteCommitTx(t *testing.T) { } retribution := &lnwallet.BreachRetribution{ - BreachTransaction: commitTxn, + BreachTxHash: commitTxn.TxHash(), RevokedStateNum: c.commitHeight, KeyRing: commitKeyRing, RemoteDelay: c.csvDelay, @@ -360,13 +361,15 @@ func (c *mockChannel) receivePayment(t *testing.T, amt lnwire.MilliSatoshi) { } // getState retrieves the channel's commitment and retribution at state i. -func (c *mockChannel) getState(i uint64) (*wire.MsgTx, *lnwallet.BreachRetribution) { +func (c *mockChannel) getState( + i uint64) (chainhash.Hash, *lnwallet.BreachRetribution) { + c.mu.Lock() defer c.mu.Unlock() retribution := c.retributions[i] - return retribution.BreachTransaction, retribution + return retribution.BreachTxHash, retribution } type testHarness struct { @@ -401,7 +404,7 @@ func newHarness(t *testing.T, cfg harnessCfg) *testHarness { t.Fatalf("Unable to resolve tower TCP addr: %v", err) } - privKey, err := btcec.NewPrivateKey(btcec.S256()) + privKey, err := btcec.NewPrivateKey() if err != nil { t.Fatalf("Unable to generate tower private key: %v", err) } @@ -608,8 +611,7 @@ func (h *testHarness) advanceChannelN(id uint64, n int) []blob.BreachHint { var hints []blob.BreachHint for i := uint64(0); i < uint64(n); i++ { channel.advanceState(h.t) - commitTx, _ := h.channel(id).getState(i) - breachTxID := commitTx.TxHash() + breachTxID, _ := h.channel(id).getState(i) hints = append(hints, blob.NewBreachHintFromHash(&breachTxID)) } @@ -654,8 +656,7 @@ func (h *testHarness) sendPayments(id, from, to uint64, var hints []blob.BreachHint for i := from; i < to; i++ { h.channel(id).sendPayment(h.t, amt) - commitTx, _ := channel.getState(i) - breachTxID := commitTx.TxHash() + breachTxID, _ := channel.getState(i) hints = append(hints, blob.NewBreachHintFromHash(&breachTxID)) } @@ -675,8 +676,7 @@ func (h *testHarness) recvPayments(id, from, to uint64, var hints []blob.BreachHint for i := from; i < to; i++ { channel.receivePayment(h.t, amt) - commitTx, _ := channel.getState(i) - breachTxID := commitTx.TxHash() + breachTxID, _ := channel.getState(i) hints = append(hints, blob.NewBreachHintFromHash(&breachTxID)) } diff --git a/watchtower/wtclient/interface.go b/watchtower/wtclient/interface.go index 2c766d004f..69f3672934 100644 --- a/watchtower/wtclient/interface.go +++ b/watchtower/wtclient/interface.go @@ -3,7 +3,7 @@ package wtclient import ( "net" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/tor" diff --git a/watchtower/wtclient/session_negotiator.go b/watchtower/wtclient/session_negotiator.go index efddfa9903..9ccaf5b791 100644 --- a/watchtower/wtclient/session_negotiator.go +++ b/watchtower/wtclient/session_negotiator.go @@ -118,9 +118,9 @@ var _ SessionNegotiator = (*sessionNegotiator)(nil) // newSessionNegotiator initializes a fresh sessionNegotiator instance. func newSessionNegotiator(cfg *NegotiatorConfig) *sessionNegotiator { - // Generate the set of features the negitator will present to the tower + // Generate the set of features the negotiator will present to the tower // upon connection. For anchor channels, we'll conditionally signal that - // we require support for anchor channels depdening on the requested + // we require support for anchor channels depending on the requested // policy. features := []lnwire.FeatureBit{ wtwire.AltruistSessionsRequired, diff --git a/watchtower/wtdb/client_db.go b/watchtower/wtdb/client_db.go index 4d4f1b5b8e..91df574d2c 100644 --- a/watchtower/wtdb/client_db.go +++ b/watchtower/wtdb/client_db.go @@ -7,7 +7,7 @@ import ( "math" "net" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/watchtower/blob" @@ -615,7 +615,7 @@ func (c *ClientDB) CreateClientSession(session *ClientSession) error { }, func() {}) } -// createSessionKeyIndexKey returns the indentifier used in the +// createSessionKeyIndexKey returns the identifier used in the // session-key-index index, created as tower-id||blob-type. // // NOTE: The original serialization only used tower-id, which prevents @@ -631,7 +631,7 @@ func createSessionKeyIndexKey(towerID TowerID, blobType blob.Type) []byte { return keyBytes[:] } -// getSessionKeyIndex is a helper method +// getSessionKeyIndex is a helper method. func getSessionKeyIndex(keyIndexes kvdb.RwBucket, towerID TowerID, blobType blob.Type) (uint32, error) { diff --git a/watchtower/wtdb/client_db_test.go b/watchtower/wtdb/client_db_test.go index 0f9235c4d9..266e65e7fd 100644 --- a/watchtower/wtdb/client_db_test.go +++ b/watchtower/wtdb/client_db_test.go @@ -10,7 +10,7 @@ import ( "reflect" "testing" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/watchtower/blob" @@ -749,7 +749,7 @@ func checkCommittedUpdates(t *testing.T, session *wtdb.ClientSession, } } -// checkAckedUpdates asserts that the AckedUpdates on a sessio match the +// checkAckedUpdates asserts that the AckedUpdates on a session match the // expUpdates provided. func checkAckedUpdates(t *testing.T, session *wtdb.ClientSession, expUpdates map[uint16]wtdb.BackupID) { diff --git a/watchtower/wtdb/codec_test.go b/watchtower/wtdb/codec_test.go index 69c7b0594a..7842b13bcf 100644 --- a/watchtower/wtdb/codec_test.go +++ b/watchtower/wtdb/codec_test.go @@ -10,13 +10,13 @@ import ( "testing" "testing/quick" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/lightningnetwork/lnd/tor" "github.com/lightningnetwork/lnd/watchtower/wtdb" ) func randPubKey() (*btcec.PublicKey, error) { - priv, err := btcec.NewPrivateKey(btcec.S256()) + priv, err := btcec.NewPrivateKey() if err != nil { return nil, err } diff --git a/watchtower/wtdb/session_id.go b/watchtower/wtdb/session_id.go index 7980b1f64b..a7fcd38f74 100644 --- a/watchtower/wtdb/session_id.go +++ b/watchtower/wtdb/session_id.go @@ -3,7 +3,7 @@ package wtdb import ( "encoding/hex" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" ) // SessionIDSize is 33-bytes; it is a serialized, compressed public key. diff --git a/watchtower/wtdb/session_info.go b/watchtower/wtdb/session_info.go index 2c8809a949..8361cdf750 100644 --- a/watchtower/wtdb/session_info.go +++ b/watchtower/wtdb/session_info.go @@ -72,7 +72,7 @@ func (s *SessionInfo) Encode(w io.Writer) error { ) } -// Decode deserializes the session infor from the given io.Reader. +// Decode deserializes the session info from the given io.Reader. func (s *SessionInfo) Decode(r io.Reader) error { return ReadElements(r, &s.ID, diff --git a/watchtower/wtdb/tower.go b/watchtower/wtdb/tower.go index 1924d9d56a..77f452fb5b 100644 --- a/watchtower/wtdb/tower.go +++ b/watchtower/wtdb/tower.go @@ -6,7 +6,7 @@ import ( "io" "net" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/lightningnetwork/lnd/lnwire" ) @@ -46,7 +46,7 @@ type Tower struct { // AddAddress adds the given address to the tower's in-memory list of addresses. // If the address's string is already present, the Tower will be left -// unmodified. Otherwise, the adddress is prepended to the beginning of the +// unmodified. Otherwise, the address is prepended to the beginning of the // Tower's addresses, on the assumption that it is fresher than the others. // // NOTE: This method is NOT safe for concurrent use. diff --git a/watchtower/wtdb/tower_db_test.go b/watchtower/wtdb/tower_db_test.go index 413f7c43bd..62e19e0180 100644 --- a/watchtower/wtdb/tower_db_test.go +++ b/watchtower/wtdb/tower_db_test.go @@ -420,7 +420,7 @@ func runStateUpdateTest(test stateUpdateTest) func(*towerDBHarness) { continue } - // Don't perform the following checks and modfications + // Don't perform the following checks and modifications // if we don't have an expected session to compare // against. if expSession == nil { diff --git a/watchtower/wtmock/client_db.go b/watchtower/wtmock/client_db.go index 35190d9357..28dafd04ce 100644 --- a/watchtower/wtmock/client_db.go +++ b/watchtower/wtmock/client_db.go @@ -5,7 +5,7 @@ import ( "sync" "sync/atomic" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/watchtower/blob" "github.com/lightningnetwork/lnd/watchtower/wtdb" diff --git a/watchtower/wtmock/keyring.go b/watchtower/wtmock/keyring.go index dc9eef12f1..7404a9d5c9 100644 --- a/watchtower/wtmock/keyring.go +++ b/watchtower/wtmock/keyring.go @@ -3,7 +3,7 @@ package wtmock import ( "sync" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/lightningnetwork/lnd/keychain" ) @@ -39,7 +39,7 @@ func (m *SecretKeyRing) DeriveKey( }, nil } - privKey, err := btcec.NewPrivateKey(btcec.S256()) + privKey, err := btcec.NewPrivateKey() if err != nil { return keychain.KeyDescriptor{}, err } diff --git a/watchtower/wtmock/peer.go b/watchtower/wtmock/peer.go index fc1ff9af11..636a2f72bc 100644 --- a/watchtower/wtmock/peer.go +++ b/watchtower/wtmock/peer.go @@ -5,7 +5,7 @@ import ( "net" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/lightningnetwork/lnd/watchtower/wtserver" ) diff --git a/watchtower/wtmock/signer.go b/watchtower/wtmock/signer.go index 89421d6adb..b41d8ceb5a 100644 --- a/watchtower/wtmock/signer.go +++ b/watchtower/wtmock/signer.go @@ -1,9 +1,13 @@ package wtmock import ( + "crypto/sha256" "sync" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/input" @@ -31,6 +35,7 @@ func NewMockSigner() *MockSigner { // signature without the signhash flag. func (s *MockSigner) SignOutputRaw(tx *wire.MsgTx, signDesc *input.SignDescriptor) (input.Signature, error) { + s.mu.Lock() defer s.mu.Unlock() @@ -50,7 +55,7 @@ func (s *MockSigner) SignOutputRaw(tx *wire.MsgTx, return nil, err } - return btcec.ParseDERSignature(sig[:len(sig)-1], btcec.S256()) + return ecdsa.ParseDERSignature(sig[:len(sig)-1]) } // ComputeInputScript is not implemented. @@ -59,6 +64,55 @@ func (s *MockSigner) ComputeInputScript(tx *wire.MsgTx, panic("not implemented") } +// MuSig2CreateSession creates a new MuSig2 signing session using the local +// key identified by the key locator. The complete list of all public keys of +// all signing parties must be provided, including the public key of the local +// signing key. If nonces of other parties are already known, they can be +// submitted as well to reduce the number of method calls necessary later on. +func (s *MockSigner) MuSig2CreateSession(keychain.KeyLocator, + []*btcec.PublicKey, *input.MuSig2Tweaks, + [][musig2.PubNonceSize]byte) (*input.MuSig2SessionInfo, error) { + + return nil, nil +} + +// MuSig2RegisterNonces registers one or more public nonces of other signing +// participants for a session identified by its ID. This method returns true +// once we have all nonces for all other signing participants. +func (s *MockSigner) MuSig2RegisterNonces(input.MuSig2SessionID, + [][musig2.PubNonceSize]byte) (bool, error) { + + return false, nil +} + +// MuSig2Sign creates a partial signature using the local signing key +// that was specified when the session was created. This can only be +// called when all public nonces of all participants are known and have +// been registered with the session. If this node isn't responsible for +// combining all the partial signatures, then the cleanup parameter +// should be set, indicating that the session can be removed from memory +// once the signature was produced. +func (s *MockSigner) MuSig2Sign(input.MuSig2SessionID, + [sha256.Size]byte, bool) (*musig2.PartialSignature, error) { + + return nil, nil +} + +// MuSig2CombineSig combines the given partial signature(s) with the +// local one, if it already exists. Once a partial signature of all +// participants is registered, the final signature will be combined and +// returned. +func (s *MockSigner) MuSig2CombineSig(input.MuSig2SessionID, + []*musig2.PartialSignature) (*schnorr.Signature, bool, error) { + + return nil, false, nil +} + +// MuSig2Cleanup removes a session from memory to free up resources. +func (s *MockSigner) MuSig2Cleanup(input.MuSig2SessionID) error { + return nil +} + // AddPrivKey records the passed privKey in the MockSigner's registry of keys it // can sign with in the future. A unique key locator is returned, allowing the // caller to sign with this key when presented via an input.SignDescriptor. diff --git a/watchtower/wtpolicy/policy.go b/watchtower/wtpolicy/policy.go index ba1c384597..6eb88657b9 100644 --- a/watchtower/wtpolicy/policy.go +++ b/watchtower/wtpolicy/policy.go @@ -4,8 +4,8 @@ import ( "errors" "fmt" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet/chainfee" diff --git a/watchtower/wtserver/interface.go b/watchtower/wtserver/interface.go index d23645431b..ec854f6aaa 100644 --- a/watchtower/wtserver/interface.go +++ b/watchtower/wtserver/interface.go @@ -5,7 +5,7 @@ import ( "net" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/lightningnetwork/lnd/watchtower/wtdb" ) diff --git a/watchtower/wtserver/server.go b/watchtower/wtserver/server.go index c46447b2a3..aca37635a2 100644 --- a/watchtower/wtserver/server.go +++ b/watchtower/wtserver/server.go @@ -8,9 +8,9 @@ import ( "sync" "time" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/connmgr" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/watchtower/wtdb" diff --git a/watchtower/wtserver/server_test.go b/watchtower/wtserver/server_test.go index 5915d2d4ba..fdf40af33d 100644 --- a/watchtower/wtserver/server_test.go +++ b/watchtower/wtserver/server_test.go @@ -6,10 +6,10 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/txscript" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/watchtower/blob" "github.com/lightningnetwork/lnd/watchtower/wtdb" @@ -35,7 +35,7 @@ var ( func randPubKey(t *testing.T) *btcec.PublicKey { t.Helper() - sk, err := btcec.NewPrivateKey(btcec.S256()) + sk, err := btcec.NewPrivateKey() if err != nil { t.Fatalf("unable to generate pubkey: %v", err) } diff --git a/watchtower/wtwire/wtwire.go b/watchtower/wtwire/wtwire.go index 4472556021..5b10fee330 100644 --- a/watchtower/wtwire/wtwire.go +++ b/watchtower/wtwire/wtwire.go @@ -5,7 +5,7 @@ import ( "fmt" "io" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/lnwallet/chainfee" @@ -228,7 +228,7 @@ func ReadElement(r io.Reader, element interface{}) error { return err } - pubKey, err := btcec.ParsePubKey(b[:], btcec.S256()) + pubKey, err := btcec.ParsePubKey(b[:]) if err != nil { return err } diff --git a/witness_beacon.go b/witness_beacon.go index 1593bee769..f3a22591e0 100644 --- a/witness_beacon.go +++ b/witness_beacon.go @@ -5,7 +5,10 @@ import ( "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/contractcourt" + "github.com/lightningnetwork/lnd/htlcswitch" + "github.com/lightningnetwork/lnd/htlcswitch/hop" "github.com/lightningnetwork/lnd/lntypes" + "github.com/lightningnetwork/lnd/lnwire" ) // preimageSubscriber reprints an active subscription to be notified once the @@ -16,21 +19,48 @@ type preimageSubscriber struct { quit chan struct{} } +type witnessCache interface { + // LookupSha256Witness attempts to lookup the preimage for a sha256 + // hash. If the witness isn't found, ErrNoWitnesses will be returned. + LookupSha256Witness(hash lntypes.Hash) (lntypes.Preimage, error) + + // AddSha256Witnesses adds a batch of new sha256 preimages into the + // witness cache. This is an alias for AddWitnesses that uses + // Sha256HashWitness as the preimages' witness type. + AddSha256Witnesses(preimages ...lntypes.Preimage) error +} + // preimageBeacon is an implementation of the contractcourt.WitnessBeacon // interface, and the lnwallet.PreimageCache interface. This implementation is // concerned with a single witness type: sha256 hahsh preimages. type preimageBeacon struct { sync.RWMutex - wCache *channeldb.WitnessCache + wCache witnessCache clientCounter uint64 subscribers map[uint64]*preimageSubscriber + + interceptor func(htlcswitch.InterceptedForward) error +} + +func newPreimageBeacon(wCache witnessCache, + interceptor func(htlcswitch.InterceptedForward) error) *preimageBeacon { + + return &preimageBeacon{ + wCache: wCache, + interceptor: interceptor, + subscribers: make(map[uint64]*preimageSubscriber), + } } // SubscribeUpdates returns a channel that will be sent upon *each* time a new // preimage is discovered. -func (p *preimageBeacon) SubscribeUpdates() *contractcourt.WitnessSubscription { +func (p *preimageBeacon) SubscribeUpdates( + chanID lnwire.ShortChannelID, htlc *channeldb.HTLC, + payload *hop.Payload, + nextHopOnionBlob []byte) (*contractcourt.WitnessSubscription, error) { + p.Lock() defer p.Unlock() @@ -47,7 +77,7 @@ func (p *preimageBeacon) SubscribeUpdates() *contractcourt.WitnessSubscription { srvrLog.Debugf("Creating new witness beacon subscriber, id=%v", p.clientCounter) - return &contractcourt.WitnessSubscription{ + sub := &contractcourt.WitnessSubscription{ WitnessUpdates: client.updateChan, CancelSubscription: func() { p.Lock() @@ -58,6 +88,32 @@ func (p *preimageBeacon) SubscribeUpdates() *contractcourt.WitnessSubscription { close(client.quit) }, } + + // Notify the htlc interceptor. There may be a client connected + // and willing to supply a preimage. + packet := &htlcswitch.InterceptedPacket{ + Hash: htlc.RHash, + IncomingExpiry: htlc.RefundTimeout, + IncomingAmount: htlc.Amt, + IncomingCircuit: channeldb.CircuitKey{ + ChanID: chanID, + HtlcID: htlc.HtlcIndex, + }, + OutgoingChanID: payload.FwdInfo.NextHop, + OutgoingExpiry: payload.FwdInfo.OutgoingCTLV, + OutgoingAmount: payload.FwdInfo.AmountToForward, + CustomRecords: payload.CustomRecords(), + } + copy(packet.OnionBlob[:], nextHopOnionBlob) + + fwd := newInterceptedForward(packet, p) + + err := p.interceptor(fwd) + if err != nil { + return nil, err + } + + return sub, nil } // LookupPreImage attempts to lookup a preimage in the global cache. True is diff --git a/witness_beacon_test.go b/witness_beacon_test.go new file mode 100644 index 0000000000..5f58eeac18 --- /dev/null +++ b/witness_beacon_test.go @@ -0,0 +1,57 @@ +package lnd + +import ( + "testing" + + "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/htlcswitch" + "github.com/lightningnetwork/lnd/htlcswitch/hop" + "github.com/lightningnetwork/lnd/lntypes" + "github.com/lightningnetwork/lnd/lnwire" + "github.com/stretchr/testify/require" +) + +// TestWitnessBeaconIntercept tests that the beacon passes on subscriptions to +// the interceptor correctly. +func TestWitnessBeaconIntercept(t *testing.T) { + var interceptedFwd htlcswitch.InterceptedForward + interceptor := func(fwd htlcswitch.InterceptedForward) error { + interceptedFwd = fwd + + return nil + } + + p := newPreimageBeacon( + &mockWitnessCache{}, interceptor, + ) + + preimage := lntypes.Preimage{1, 2, 3} + hash := preimage.Hash() + + subscription, err := p.SubscribeUpdates( + lnwire.NewShortChanIDFromInt(1), + &channeldb.HTLC{ + RHash: hash, + }, + &hop.Payload{}, + []byte{2}, + ) + require.NoError(t, err) + + defer subscription.CancelSubscription() + + require.NoError(t, interceptedFwd.Settle(preimage)) + + update := <-subscription.WitnessUpdates + require.Equal(t, preimage, update) +} + +type mockWitnessCache struct { + witnessCache +} + +func (w *mockWitnessCache) AddSha256Witnesses( + preimages ...lntypes.Preimage) error { + + return nil +} diff --git a/zpay32/decode.go b/zpay32/decode.go index 7077bfcb0a..b881d58694 100644 --- a/zpay32/decode.go +++ b/zpay32/decode.go @@ -8,11 +8,12 @@ import ( "strings" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/bech32" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcutil" - "github.com/btcsuite/btcutil/bech32" "github.com/lightningnetwork/lnd/lnwire" ) @@ -121,8 +122,7 @@ func Decode(invoice string, net *chaincfg.Params) (*Invoice, error) { } else { headerByte := recoveryID + 27 + 4 compactSign := append([]byte{headerByte}, sig[:]...) - pubkey, _, err := btcec.RecoverCompact(btcec.S256(), - compactSign, hash) + pubkey, _, err := ecdsa.RecoverCompact(compactSign, hash) if err != nil { return nil, err } @@ -229,6 +229,15 @@ func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) er } invoice.Description, err = parseDescription(base32Data) + case fieldTypeM: + if invoice.Metadata != nil { + // We skip the field if we have already seen a + // supported one. + continue + } + + invoice.Metadata, err = parseMetadata(base32Data) + case fieldTypeN: if invoice.Destination != nil { // We skip the field if we have already seen a @@ -345,6 +354,12 @@ func parseDescription(data []byte) (*string, error) { return &description, nil } +// parseMetadata converts the data (encoded in base32) into a byte slice to use +// as the metadata. +func parseMetadata(data []byte) ([]byte, error) { + return bech32.ConvertBits(data, 5, 8, false) +} + // parseDestination converts the data (encoded in base32) into a 33-byte public // key of the payee node. func parseDestination(data []byte) (*btcec.PublicKey, error) { @@ -359,7 +374,7 @@ func parseDestination(data []byte) (*btcec.PublicKey, error) { return nil, err } - return btcec.ParsePubKey(base256Data, btcec.S256()) + return btcec.ParsePubKey(base256Data) } // parseExpiry converts the data (encoded in base32) into the expiry time. @@ -461,7 +476,7 @@ func parseRouteHint(data []byte) ([]HopHint, error) { for len(base256Data) > 0 { hopHint := HopHint{} - hopHint.NodeID, err = btcec.ParsePubKey(base256Data[:33], btcec.S256()) + hopHint.NodeID, err = btcec.ParsePubKey(base256Data[:33]) if err != nil { return nil, err } diff --git a/zpay32/encode.go b/zpay32/encode.go index 847d2811a9..a30d0c3911 100644 --- a/zpay32/encode.go +++ b/zpay32/encode.go @@ -5,10 +5,10 @@ import ( "encoding/binary" "fmt" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/bech32" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcutil" - "github.com/btcsuite/btcutil/bech32" "github.com/lightningnetwork/lnd/lnwire" ) @@ -164,6 +164,17 @@ func writeTaggedFields(bufferBase32 *bytes.Buffer, invoice *Invoice) error { } } + if invoice.Metadata != nil { + base32, err := bech32.ConvertBits(invoice.Metadata, 8, 5, true) + if err != nil { + return err + } + err = writeTaggedField(bufferBase32, fieldTypeM, base32) + if err != nil { + return err + } + } + if invoice.minFinalCLTVExpiry != nil { finalDelta := uint64ToBase32(*invoice.minFinalCLTVExpiry) err := writeTaggedField(bufferBase32, fieldTypeC, finalDelta) diff --git a/zpay32/hophint.go b/zpay32/hophint.go index 067c8437d3..266c95d7d7 100644 --- a/zpay32/hophint.go +++ b/zpay32/hophint.go @@ -1,6 +1,6 @@ package zpay32 -import "github.com/btcsuite/btcd/btcec" +import "github.com/btcsuite/btcd/btcec/v2" const ( // DefaultAssumedFinalCLTVDelta is the default value to be used as the diff --git a/zpay32/invoice.go b/zpay32/invoice.go index 7f2d86c7a8..e0c05efc15 100644 --- a/zpay32/invoice.go +++ b/zpay32/invoice.go @@ -5,9 +5,9 @@ import ( "fmt" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/lnwire" ) @@ -46,6 +46,9 @@ const ( // fieldTypeD contains a short description of the payment. fieldTypeD = 13 + // fieldTypeM contains the payment metadata. + fieldTypeM = 27 + // fieldTypeN contains the pubkey of the target node. fieldTypeN = 19 @@ -103,7 +106,7 @@ type MessageSigner struct { // SignCompact signs the hash of the passed msg with the node's privkey. // The returned signature should be 65 bytes, where the last 64 are the // compact signature, and the first one is a header byte. This is the - // format returned by btcec.SignCompact. + // format returned by ecdsa.SignCompact. SignCompact func(msg []byte) ([]byte, error) } @@ -183,6 +186,10 @@ type Invoice struct { // Features represents an optional field used to signal optional or // required support for features by the receiver. Features *lnwire.FeatureVector + + // Metadata is additional data that is sent along with the payment to + // the payee. + Metadata []byte } // Amount is a functional option that allows callers of NewInvoice to set the @@ -266,13 +273,21 @@ func Features(features *lnwire.FeatureVector) func(*Invoice) { } // PaymentAddr is a functional option that allows callers of NewInvoice to set -// the desired payment address tht is advertised on the invoice. +// the desired payment address that is advertised on the invoice. func PaymentAddr(addr [32]byte) func(*Invoice) { return func(i *Invoice) { i.PaymentAddr = &addr } } +// Metadata is a functional option that allows callers of NewInvoice to set +// the desired payment Metadata tht is advertised on the invoice. +func Metadata(metadata []byte) func(*Invoice) { + return func(i *Invoice) { + i.Metadata = metadata + } +} + // NewInvoice creates a new Invoice object. The last parameter is a set of // variadic arguments for setting optional fields of the invoice. // diff --git a/zpay32/invoice_internal_test.go b/zpay32/invoice_internal_test.go index b72c72e08a..c93c80693c 100644 --- a/zpay32/invoice_internal_test.go +++ b/zpay32/invoice_internal_test.go @@ -7,10 +7,10 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/bech32" "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcutil" - "github.com/btcsuite/btcutil/bech32" "github.com/lightningnetwork/lnd/lnwire" ) @@ -452,7 +452,8 @@ func TestParseDestination(t *testing.T) { if test.valid && !comparePubkeys(destination, test.result) { t.Fatalf("test %d failed decoding destination: "+ "expected %x, got %x", - i, *test.result, *destination) + i, test.result.SerializeCompressed(), + destination.SerializeCompressed()) return } } diff --git a/zpay32/invoice_test.go b/zpay32/invoice_test.go index 14d2401a36..fa77d14c06 100644 --- a/zpay32/invoice_test.go +++ b/zpay32/invoice_test.go @@ -12,13 +12,13 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/lnwire" - litecoinCfg "github.com/ltcsuite/ltcd/chaincfg" ) @@ -27,6 +27,7 @@ var ( testMillisat2500uBTC = lnwire.MilliSatoshi(250000000) testMillisat25mBTC = lnwire.MilliSatoshi(2500000000) testMillisat20mBTC = lnwire.MilliSatoshi(2000000000) + testMillisat10mBTC = lnwire.MilliSatoshi(1000000000) testPaymentHash = [32]byte{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, @@ -49,14 +50,15 @@ var ( 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, } - testEmptyString = "" - testCupOfCoffee = "1 cup coffee" - testCoffeeBeans = "coffee beans" - testCupOfNonsense = "ナンセンス 1杯" - testPleaseConsider = "Please consider supporting this project" + testEmptyString = "" + testCupOfCoffee = "1 cup coffee" + testCoffeeBeans = "coffee beans" + testCupOfNonsense = "ナンセンス 1杯" + testPleaseConsider = "Please consider supporting this project" + testPaymentMetadata = "payment metadata inside" testPrivKeyBytes, _ = hex.DecodeString("e126f68f7eafcc8b74f54d269fe206be715000f94dac067d1c04a8ca3b2db734") - testPrivKey, testPubKey = btcec.PrivKeyFromBytes(btcec.S256(), testPrivKeyBytes) + testPrivKey, testPubKey = btcec.PrivKeyFromBytes(testPrivKeyBytes) testDescriptionHashSlice = chainhash.HashB([]byte("One piece of chocolate cake, one icecream cone, one pickle, one slice of swiss cheese, one slice of salami, one lollypop, one piece of cherry pie, one sausage, one cupcake, and one slice of watermelon")) @@ -70,9 +72,9 @@ var ( testAddrMainnetP2WSH, _ = btcutil.DecodeAddress("bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3", &chaincfg.MainNetParams) testHopHintPubkeyBytes1, _ = hex.DecodeString("029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255") - testHopHintPubkey1, _ = btcec.ParsePubKey(testHopHintPubkeyBytes1, btcec.S256()) + testHopHintPubkey1, _ = btcec.ParsePubKey(testHopHintPubkeyBytes1) testHopHintPubkeyBytes2, _ = hex.DecodeString("039e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255") - testHopHintPubkey2, _ = btcec.ParsePubKey(testHopHintPubkeyBytes2, btcec.S256()) + testHopHintPubkey2, _ = btcec.ParsePubKey(testHopHintPubkeyBytes2) testSingleHop = []HopHint{ { @@ -103,9 +105,7 @@ var ( testMessageSigner = MessageSigner{ SignCompact: func(msg []byte) ([]byte, error) { hash := chainhash.HashB(msg) - sig, err := btcec.SignCompact( - btcec.S256(), testPrivKey, hash, true, - ) + sig, err := ecdsa.SignCompact(testPrivKey, hash, true) if err != nil { return nil, fmt.Errorf("can't sign the "+ "message: %v", err) @@ -694,6 +694,33 @@ func TestDecodeEncode(t *testing.T) { } }, }, + { + // Please send 0.01 BTC with payment metadata 0x01fafaf0. + encodedInvoice: "lnbc10m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdp9wpshjmt9de6zqmt9w3skgct5vysxjmnnd9jx2mq8q8a04uqsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q2gqqqqqqsgq7hf8he7ecf7n4ffphs6awl9t6676rrclv9ckg3d3ncn7fct63p6s365duk5wrk202cfy3aj5xnnp5gs3vrdvruverwwq7yzhkf5a3xqpd05wjc", + valid: true, + decodedInvoice: func() *Invoice { + return &Invoice{ + Net: &chaincfg.MainNetParams, + MilliSat: &testMillisat10mBTC, + Timestamp: time.Unix(1496314658, 0), + PaymentHash: &testPaymentHash, + Description: &testPaymentMetadata, + Destination: testPubKey, + PaymentAddr: &specPaymentAddr, + Features: lnwire.NewFeatureVector( + lnwire.NewRawFeatureVector(8, 14, 48), + lnwire.Features, + ), + Metadata: []byte{0x01, 0xfa, 0xfa, 0xf0}, + } + }, + beforeEncoding: func(i *Invoice) { + // Since this destination pubkey was recovered + // from the signature, we must set it nil before + // encoding to get back the same invoice string. + i.Destination = nil + }, + }, } for i, test := range tests { @@ -915,11 +942,11 @@ func TestInvoiceChecksumMalleability(t *testing.T) { var payHash [32]byte ts := time.Unix(0, 0) - privKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), privKeyBytes) + privKey, _ := btcec.PrivKeyFromBytes(privKeyBytes) msgSigner := MessageSigner{ SignCompact: func(msg []byte) ([]byte, error) { hash := chainhash.HashB(msg) - return btcec.SignCompact(btcec.S256(), privKey, hash, true) + return ecdsa.SignCompact(privKey, hash, true) }, } opts := []func(*Invoice){Description("test")} @@ -952,7 +979,6 @@ func TestInvoiceChecksumMalleability(t *testing.T) { if err == nil { t.Fatalf("Did not get expected error when decoding invoice") } - } func compareInvoices(expected, actual *Invoice) error { @@ -983,7 +1009,8 @@ func compareInvoices(expected, actual *Invoice) error { if !comparePubkeys(expected.Destination, actual.Destination) { return fmt.Errorf("expected destination pubkey %x, got %x", - expected.Destination, actual.Destination) + expected.Destination.SerializeCompressed(), + actual.Destination.SerializeCompressed()) } if !compareHashes(expected.DescriptionHash, actual.DescriptionHash) { @@ -1056,7 +1083,8 @@ func compareRouteHints(a, b []HopHint) error { for i := 0; i < len(a); i++ { if !comparePubkeys(a[i].NodeID, b[i].NodeID) { return fmt.Errorf("expected routeHint nodeID %x, "+ - "got %x", a[i].NodeID, b[i].NodeID) + "got %x", a[i].NodeID.SerializeCompressed(), + b[i].NodeID.SerializeCompressed()) } if a[i].ChannelID != b[i].ChannelID {