diff --git a/config.go b/config.go index 208565812..b2eff8aed 100644 --- a/config.go +++ b/config.go @@ -15,6 +15,7 @@ import ( "github.com/lightninglabs/taproot-assets/rfq" "github.com/lightninglabs/taproot-assets/tapchannel" "github.com/lightninglabs/taproot-assets/tapdb" + "github.com/lightninglabs/taproot-assets/tapfeatures" "github.com/lightninglabs/taproot-assets/tapfreighter" "github.com/lightninglabs/taproot-assets/tapgarden" "github.com/lightninglabs/taproot-assets/universe" @@ -221,6 +222,8 @@ type Config struct { AuxTrafficShaper *tapchannel.AuxTrafficShaper + AuxChanNegotiator *tapfeatures.AuxChannelNegotiator + AuxInvoiceManager *tapchannel.AuxInvoiceManager AuxChanCloser *tapchannel.AuxChanCloser diff --git a/go.mod b/go.mod index 40be67915..5d07fbb86 100644 --- a/go.mod +++ b/go.mod @@ -87,9 +87,9 @@ require ( github.com/docker/go-units v0.5.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/fergusstrange/embedded-postgres v1.25.0 // indirect - github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-viper/mapstructure/v2 v2.3.0 // indirect + github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/goccy/go-yaml v1.15.23 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect @@ -128,11 +128,11 @@ require ( github.com/lightninglabs/lightning-node-connect/gbn v1.0.1 // indirect github.com/lightninglabs/lightning-node-connect/mailbox v1.0.1 // indirect github.com/lightninglabs/neutrino v0.16.1 // indirect - github.com/lightningnetwork/lightning-onion v1.2.1-0.20240712235311-98bd56499dfb // indirect + github.com/lightningnetwork/lightning-onion v1.2.1-0.20240815225420-8b40adf04ab9 // indirect github.com/lightningnetwork/lnd/healthcheck v1.2.6 // indirect github.com/lightningnetwork/lnd/kvdb v1.4.16 // indirect github.com/lightningnetwork/lnd/queue v1.1.1 // indirect - github.com/lightningnetwork/lnd/sqldb v1.0.10-0.20250812192515-dd1d57d82de1 // indirect + github.com/lightningnetwork/lnd/sqldb v1.0.11-0.20250820143531-0c2f045f5def // indirect github.com/lightningnetwork/lnd/ticker v1.1.1 // indirect github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -179,18 +179,18 @@ require ( go.etcd.io/etcd/server/v3 v3.5.12 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/sdk v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/sdk v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.23.0 // indirect golang.org/x/mod v0.21.0 // indirect - golang.org/x/sys v0.32.0 // indirect + golang.org/x/sys v0.33.0 // indirect golang.org/x/text v0.24.0 // indirect golang.org/x/tools v0.24.0 // indirect google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect @@ -217,3 +217,9 @@ replace github.com/golang-migrate/migrate/v4 => github.com/lightninglabs/migrate // Note this is a temproary replace and will be removed when taprpc is tagged. replace github.com/lightninglabs/taproot-assets/taprpc => ./taprpc + +replace github.com/lightningnetwork/lnd => github.com/GeorgeTsagk/lnd v0.0.0-20250901145709-e24e285e3e38 + +replace github.com/lightningnetwork/lnd/sqldb => github.com/GeorgeTsagk/lnd/sqldb v0.0.0-20250901145709-e24e285e3e38 + +replace github.com/lightninglabs/lndclient => github.com/GeorgeTsagk/lndclient v0.0.0-20250828124156-45b539c2e6b7 diff --git a/go.sum b/go.sum index 9b566bedc..cfcdc2bec 100644 --- a/go.sum +++ b/go.sum @@ -603,6 +603,12 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/GeorgeTsagk/lnd v0.0.0-20250901145709-e24e285e3e38 h1:X6TAjwTQATJ+NYOK/yjfPTbNPMytg383By0bqsXNkSc= +github.com/GeorgeTsagk/lnd v0.0.0-20250901145709-e24e285e3e38/go.mod h1:XRqKVX+6acMyufAgG00/Opbqz9Ua+C+XMWdE4ZmvjSw= +github.com/GeorgeTsagk/lnd/sqldb v0.0.0-20250901145709-e24e285e3e38 h1:kWMrBmh1was1GSesi5SXgU1Z2GopfpVL2/AwZn7+4tI= +github.com/GeorgeTsagk/lnd/sqldb v0.0.0-20250901145709-e24e285e3e38/go.mod h1:ydgziaBJ1U2jusPrTzQWVHub9M0eXu0iI+XJr6/ucB0= +github.com/GeorgeTsagk/lndclient v0.0.0-20250828124156-45b539c2e6b7 h1:L2Oyma8E+cdklh2ihH05ZDSGVVT/fJLVFHNVN874qwk= +github.com/GeorgeTsagk/lndclient v0.0.0-20250828124156-45b539c2e6b7/go.mod h1:YhclzH6MbWw+guWxgcf3jsbAxy1Xo/Egy0iT0iGM8gU= github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= @@ -827,8 +833,8 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= @@ -843,8 +849,8 @@ github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GO github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk= -github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= +github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= @@ -1140,8 +1146,6 @@ github.com/lightninglabs/lightning-node-connect/hashmailrpc v1.0.3 h1:NuDp6Z+QNM github.com/lightninglabs/lightning-node-connect/hashmailrpc v1.0.3/go.mod h1:bDnEKRN1u13NFBuy/C+bFLhxA5bfd3clT25y76QY0AM= github.com/lightninglabs/lightning-node-connect/mailbox v1.0.1 h1:RWmohykp3n/DTMWY8b18RNTEcLDf+KT/AZHKYdOObkM= github.com/lightninglabs/lightning-node-connect/mailbox v1.0.1/go.mod h1:NYtNexZE9gO1eOeegTxmIW9fqanl7eZ9cOrE9yewSAk= -github.com/lightninglabs/lndclient v0.19.0-9 h1:ell27omDoks79upoAsO/7QY40O93ud4tAtBXXZilqok= -github.com/lightninglabs/lndclient v0.19.0-9/go.mod h1:35d50tEMFxlJlKTZGYA6EdOllPsbxS4FUmEVbETUx+Q= github.com/lightninglabs/migrate/v4 v4.18.2-9023d66a-fork-pr-2 h1:eFjp1dIB2BhhQp/THKrjLdlYuPugO9UU4kDqu91OX/Q= github.com/lightninglabs/migrate/v4 v4.18.2-9023d66a-fork-pr-2/go.mod h1:99BKpIi6ruaaXRM1A77eqZ+FWPQ3cfRa+ZVy5bmWMaY= github.com/lightninglabs/neutrino v0.16.1 h1:5Kz4ToxncEVkpKC6fwUjXKtFKJhuxlG3sBB3MdJTJjs= @@ -1150,10 +1154,8 @@ github.com/lightninglabs/neutrino/cache v1.1.2 h1:C9DY/DAPaPxbFC+xNNEI/z1SJY9GS3 github.com/lightninglabs/neutrino/cache v1.1.2/go.mod h1:XJNcgdOw1LQnanGjw8Vj44CvguYA25IMKjWFZczwZuo= github.com/lightninglabs/protobuf-go-hex-display v1.34.2-hex-display h1:w7FM5LH9Z6CpKxl13mS48idsu6F+cEZf0lkyiV+Dq9g= github.com/lightninglabs/protobuf-go-hex-display v1.34.2-hex-display/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= -github.com/lightningnetwork/lightning-onion v1.2.1-0.20240712235311-98bd56499dfb h1:yfM05S8DXKhuCBp5qSMZdtSwvJ+GFzl94KbXMNB1JDY= -github.com/lightningnetwork/lightning-onion v1.2.1-0.20240712235311-98bd56499dfb/go.mod h1:c0kvRShutpj3l6B9WtTsNTBUtjSmjZXbJd9ZBRQOSKI= -github.com/lightningnetwork/lnd v0.19.3-beta.rc1.0.20250812194315-c3226e8c2223 h1:CBN1ju+LQL+jTfneGabUTFoHYuAnFeHv3j5+09wtOCo= -github.com/lightningnetwork/lnd v0.19.3-beta.rc1.0.20250812194315-c3226e8c2223/go.mod h1:MNRzea8Yrgk+ohyUhK7JSpoigE4T9JgerMQQUxMbl9I= +github.com/lightningnetwork/lightning-onion v1.2.1-0.20240815225420-8b40adf04ab9 h1:6D3LrdagJweLLdFm1JNodZsBk6iU4TTsBBFLQ4yiXfI= +github.com/lightningnetwork/lightning-onion v1.2.1-0.20240815225420-8b40adf04ab9/go.mod h1:EDqJ3MuZIbMq0QI1czTIKDJ/GS8S14RXPwapHw8cw6w= github.com/lightningnetwork/lnd/cert v1.2.2 h1:71YK6hogeJtxSxw2teq3eGeuy4rHGKcFf0d0Uy4qBjI= github.com/lightningnetwork/lnd/cert v1.2.2/go.mod h1:jQmFn/Ez4zhDgq2hnYSw8r35bqGVxViXhX6Cd7HXM6U= github.com/lightningnetwork/lnd/clock v1.1.1 h1:OfR3/zcJd2RhH0RU+zX/77c0ZiOnIMsDIBjgjWdZgA0= @@ -1166,8 +1168,6 @@ github.com/lightningnetwork/lnd/kvdb v1.4.16 h1:9BZgWdDfjmHRHLS97cz39bVuBAqMc4/p github.com/lightningnetwork/lnd/kvdb v1.4.16/go.mod h1:HW+bvwkxNaopkz3oIgBV6NEnV4jCEZCACFUcNg4xSjM= github.com/lightningnetwork/lnd/queue v1.1.1 h1:99ovBlpM9B0FRCGYJo6RSFDlt8/vOkQQZznVb18iNMI= github.com/lightningnetwork/lnd/queue v1.1.1/go.mod h1:7A6nC1Qrm32FHuhx/mi1cieAiBZo5O6l8IBIoQxvkz4= -github.com/lightningnetwork/lnd/sqldb v1.0.10-0.20250812192515-dd1d57d82de1 h1:Qf0lRLvj5xmcJGIRlREPi0c1h9fMxGe2u098Hn13eAc= -github.com/lightningnetwork/lnd/sqldb v1.0.10-0.20250812192515-dd1d57d82de1/go.mod h1:c/vWoQfcxu6FAfHzGajkIQi7CEIeIZFhhH4DYh1BJpc= github.com/lightningnetwork/lnd/ticker v1.1.1 h1:J/b6N2hibFtC7JLV77ULQp++QLtCwT6ijJlbdiZFbSM= github.com/lightningnetwork/lnd/ticker v1.1.1/go.mod h1:waPTRAAcwtu7Ji3+3k+u/xH5GHovTsCoSVpho0KDvdA= github.com/lightningnetwork/lnd/tlv v1.3.2 h1:MO4FCk7F4k5xPMqVZF6Nb/kOpxlwPrUQpYjmyKny5s0= @@ -1403,20 +1403,20 @@ go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJyS go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 h1:dIIDULZJpgdiHz5tXrTgKIMLkus6jEFa7x5SOKcyR7E= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0/go.mod h1:jlRVBe7+Z1wyxFSUs48L6OBQZ5JwH2Hg/Vbl+t9rAgI= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0 h1:gvmNvqrPYovvyRmCSygkUDyL8lC5Tl845MLEwqpxhEU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0/go.mod h1:vNUq47TGFioo+ffTSnKNdob241vePmtNZnAODKapKd0= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= -go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= @@ -1751,8 +1751,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= -golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 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/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= diff --git a/rfq/manager.go b/rfq/manager.go index de27084c8..86bd858db 100644 --- a/rfq/manager.go +++ b/rfq/manager.go @@ -19,6 +19,7 @@ import ( "github.com/lightninglabs/taproot-assets/fn" "github.com/lightninglabs/taproot-assets/rfqmath" "github.com/lightninglabs/taproot-assets/rfqmsg" + "github.com/lightninglabs/taproot-assets/tapfeatures" "github.com/lightninglabs/taproot-assets/taprpc/rfqrpc" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnutils" @@ -112,6 +113,11 @@ type ManagerCfg struct { // into the manager once lnd and tapd are hooked together. AliasManager ScidAliasManager + // AuxChannelNegotiator is responsible for producing the extra tlv blob + // that is encapsulated in the init and reestablish peer messages. This + // helps us communicate custom feature bits with our peer. + AuxChanNegotiator *tapfeatures.AuxChannelNegotiator + // AcceptPriceDeviationPpm is the price deviation in // parts per million that is accepted by the RFQ negotiator. // @@ -251,12 +257,13 @@ func (m *Manager) startSubsystems(ctx context.Context) error { // Initialise and start the order handler. m.orderHandler, err = NewOrderHandler(OrderHandlerCfg{ - CleanupInterval: CacheCleanupInterval, - HtlcInterceptor: m.cfg.HtlcInterceptor, - HtlcSubscriber: m.cfg.HtlcSubscriber, - AcceptHtlcEvents: m.acceptHtlcEvents, - SpecifierChecker: m.AssetMatchesSpecifier, - NoOpHTLCs: m.cfg.NoOpHTLCs, + CleanupInterval: CacheCleanupInterval, + HtlcInterceptor: m.cfg.HtlcInterceptor, + HtlcSubscriber: m.cfg.HtlcSubscriber, + AcceptHtlcEvents: m.acceptHtlcEvents, + SpecifierChecker: m.AssetMatchesSpecifier, + NoOpHTLCs: m.cfg.NoOpHTLCs, + AuxChanNegotiator: m.cfg.AuxChanNegotiator, }) if err != nil { return fmt.Errorf("error initializing RFQ order handler: %w", diff --git a/rfq/order.go b/rfq/order.go index e6c87e618..77fbcb183 100644 --- a/rfq/order.go +++ b/rfq/order.go @@ -14,11 +14,13 @@ import ( "github.com/lightninglabs/taproot-assets/fn" "github.com/lightninglabs/taproot-assets/rfqmath" "github.com/lightninglabs/taproot-assets/rfqmsg" + "github.com/lightninglabs/taproot-assets/tapfeatures" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lnutils" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/tlv" ) @@ -125,13 +127,23 @@ type AssetSalePolicy struct { // wants us to produce NoOp HTLCs. NoOpHTLCs bool + // auxChannelNegotiator is used to query the supported feature bits that + // are supported by a peer, or a channel. + auxChanNegotiator *tapfeatures.AuxChannelNegotiator + + // peer is the peer pub key of the peer we established this policy with. + peer route.Vertex + // expiry is the policy's expiry unix timestamp after which the policy // is no longer valid. expiry uint64 } // NewAssetSalePolicy creates a new asset sale policy. -func NewAssetSalePolicy(quote rfqmsg.BuyAccept, noop bool) *AssetSalePolicy { +func NewAssetSalePolicy(quote rfqmsg.BuyAccept, noop bool, + chanNegotiator *tapfeatures.AuxChannelNegotiator, + peer route.Vertex) *AssetSalePolicy { + htlcToAmtMap := make(map[models.CircuitKey]lnwire.MilliSatoshi) return &AssetSalePolicy{ @@ -142,6 +154,8 @@ func NewAssetSalePolicy(quote rfqmsg.BuyAccept, noop bool) *AssetSalePolicy { expiry: uint64(quote.AssetRate.Expiry.Unix()), htlcToAmt: htlcToAmtMap, NoOpHTLCs: noop, + auxChanNegotiator: chanNegotiator, + peer: peer, } } @@ -290,10 +304,13 @@ func (c *AssetSalePolicy) GenerateInterceptorResponse( fn.None[[]rfqmsg.ID](), ) + peerFeatures := c.auxChanNegotiator.GetPeerFeatures(c.peer) + supportNoOp := peerFeatures.HasFeature(tapfeatures.NoOpHTLCsOptional) + // We are about to create an outgoing HTLC that carries assets. Let's // set the noop flag in order to eventually only settle the assets but // never settle the sats anchor amount that will carry them. - if c.NoOpHTLCs { + if c.NoOpHTLCs && supportNoOp { htlcRecord.SetNoopAdd(rfqmsg.UseNoOpHTLCs) } @@ -686,6 +703,11 @@ type OrderHandlerCfg struct { // NoOpHTLCs is a boolean indicating whether the daemon configuration // wants us to produce NoOp HTLCs. NoOpHTLCs bool + + // AuxChannelNegotiator is responsible for producing the extra tlv blob + // that is encapsulated in the init and reestablish peer messages. This + // helps us communicate custom feature bits with our peer. + AuxChanNegotiator *tapfeatures.AuxChannelNegotiator } // OrderHandler orchestrates management of accepted quote bundles. It monitors @@ -940,7 +962,11 @@ func (h *OrderHandler) RegisterAssetSalePolicy(buyAccept rfqmsg.BuyAccept) { log.Debugf("Order handler is registering an asset sale policy given a "+ "buy accept message: %s", buyAccept.String()) - policy := NewAssetSalePolicy(buyAccept, h.cfg.NoOpHTLCs) + policy := NewAssetSalePolicy( + buyAccept, h.cfg.NoOpHTLCs, h.cfg.AuxChanNegotiator, + buyAccept.Peer, + ) + h.policies.Store(policy.AcceptedQuoteId.Scid(), policy) } diff --git a/server.go b/server.go index 8d958062c..5bf70bc5d 100644 --- a/server.go +++ b/server.go @@ -1279,3 +1279,57 @@ func (s *Server) NotifyBroadcast(req *sweep.BumpRequest, return s.cfg.AuxSweeper.NotifyBroadcast(req, tx, fee, outpointToTxIndex) } + +// GetInitFeatures is called when sending an init message to a peer. It returns +// custom feature bits to include in the init message TLVs. The implementation +// can decide which features to advertise based on the peer's identity. +func (s *Server) GetInitFeatures(peer route.Vertex) (tlv.Blob, error) { + // There's no need to wait for the server to be ready, this action acts + // only within the aux chan negotiator instance. + return s.cfg.AuxChanNegotiator.GetInitFeatures(peer) +} + +// ProcessInitFeatures handles received init feature TLVs from a peer. The +// implementation can store state internally to affect future channel operations +// with this peer. +func (s *Server) ProcessInitFeatures(peer route.Vertex, + features tlv.Blob) error { + + // There's no need to wait for the server to be ready, this action acts + // only within the aux chan negotiator instance. + return s.cfg.AuxChanNegotiator.ProcessInitFeatures(peer, features) +} + +// GetReestablishFeatures is called when sending a channel_reestablish message. +// It returns feature bits based on the specific channel identified by its +// funding outpoint and aux channel blob. +func (s *Server) GetReestablishFeatures(cid lnwire.ChannelID, + auxChanBlob tlv.Blob) (tlv.Blob, error) { + + // There's no need to wait for the server to be ready, this action acts + // only within the aux chan negotiator instance. + return s.cfg.AuxChanNegotiator.GetReestablishFeatures( + cid, auxChanBlob, + ) +} + +// ProcessReestablishFeatures handles received channel_reestablish feature TLVs. +// This is a blocking call - the channel link will wait for this method to +// complete before continuing channel operations. The implementation can modify +// aux channel behavior based on the negotiated features. +func (s *Server) ProcessReestablishFeatures(cid lnwire.ChannelID, + features tlv.Blob, auxChanBlob tlv.Blob) error { + + // There's no need to wait for the server to be ready, this action acts + // only within the aux chan negotiator instance. + return s.cfg.AuxChanNegotiator.ProcessReestablishFeatures( + cid, features, auxChanBlob, + ) +} + +// ProcessChannelReady handles the event of marking a channel identified by its +// channel ID as ready to use. We also provide the peer the channel was +// established with. +func (s *Server) ProcessChannelReady(cid lnwire.ChannelID, peer route.Vertex) { + s.cfg.AuxChanNegotiator.ProcessChannelReady(cid, peer) +} diff --git a/tapcfg/server.go b/tapcfg/server.go index d715315de..63b948e4b 100644 --- a/tapcfg/server.go +++ b/tapcfg/server.go @@ -20,6 +20,7 @@ import ( "github.com/lightninglabs/taproot-assets/tapchannel" "github.com/lightninglabs/taproot-assets/tapdb" "github.com/lightninglabs/taproot-assets/tapdb/sqlc" + "github.com/lightninglabs/taproot-assets/tapfeatures" "github.com/lightninglabs/taproot-assets/tapfreighter" "github.com/lightninglabs/taproot-assets/tapgarden" "github.com/lightninglabs/taproot-assets/tapscript" @@ -455,6 +456,9 @@ func genServerConfig(cfg *Config, cfgLogger btclog.Logger, } } + // Construct the AuxChannelNegotiator. + auxChanNegotiator := tapfeatures.NewAuxChannelNegotiator() + // Construct the RFQ manager. rfqManager, err := rfq.NewManager(rfq.ManagerCfg{ PeerMessenger: msgTransportClient, @@ -463,6 +467,7 @@ func genServerConfig(cfg *Config, cfgLogger btclog.Logger, PriceOracle: priceOracle, ChannelLister: lndServices.Client, GroupLookup: tapdbAddrBook, + AuxChanNegotiator: auxChanNegotiator, AliasManager: lndRouterClient, AcceptPriceDeviationPpm: rfqCfg.AcceptPriceDeviationPpm, SkipAcceptQuotePriceCheck: rfqCfg.SkipAcceptQuotePriceCheck, @@ -536,9 +541,10 @@ func genServerConfig(cfg *Config, cfgLogger btclog.Logger, ) auxTrafficShaper := tapchannel.NewAuxTrafficShaper( &tapchannel.TrafficShaperConfig{ - ChainParams: &tapChainParams, - RfqManager: rfqManager, - NoopHTLCs: cfg.Channel.NoopHTLCs, + ChainParams: &tapChainParams, + RfqManager: rfqManager, + NoopHTLCs: cfg.Channel.NoopHTLCs, + AuxChanNegotiator: auxChanNegotiator, }, ) auxInvoiceManager := tapchannel.NewAuxInvoiceManager( @@ -693,6 +699,7 @@ func genServerConfig(cfg *Config, cfgLogger btclog.Logger, AuxFundingController: auxFundingController, AuxChanCloser: auxChanCloser, AuxTrafficShaper: auxTrafficShaper, + AuxChanNegotiator: auxChanNegotiator, AuxInvoiceManager: auxInvoiceManager, AuxSweeper: auxSweeper, LogWriter: cfg.LogWriter, diff --git a/tapchannel/aux_traffic_shaper.go b/tapchannel/aux_traffic_shaper.go index 114b5344b..02b3dd628 100644 --- a/tapchannel/aux_traffic_shaper.go +++ b/tapchannel/aux_traffic_shaper.go @@ -14,6 +14,7 @@ import ( "github.com/lightninglabs/taproot-assets/rfqmath" "github.com/lightninglabs/taproot-assets/rfqmsg" cmsg "github.com/lightninglabs/taproot-assets/tapchannelmsg" + "github.com/lightninglabs/taproot-assets/tapfeatures" lfn "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnutils" @@ -30,6 +31,8 @@ type TrafficShaperConfig struct { RfqManager *rfq.Manager + AuxChanNegotiator *tapfeatures.AuxChannelNegotiator + // NoOpHTLCs is a boolean indicating whether the daemon configuration // wants us to produce NoOp HTLCs. NoopHTLCs bool @@ -464,6 +467,9 @@ func (s *AuxTrafficShaper) ProduceHtlcExtraData(totalAmount lnwire.MilliSatoshi, return totalAmount, nil, nil } + peerFeatures := s.cfg.AuxChanNegotiator.GetPeerFeatures(peer) + supportNoOp := peerFeatures.HasFeature(tapfeatures.NoOpHTLCsOptional) + // We need to do a round trip to convert the custom records to a blob // that we can then parse into the correct struct again. htlc, err := rfqmsg.HtlcFromCustomRecords(htlcCustomRecords) @@ -478,7 +484,7 @@ func (s *AuxTrafficShaper) ProduceHtlcExtraData(totalAmount lnwire.MilliSatoshi, log.Tracef("Already have asset amount (sum %d) in HTLC, not "+ "producing extra data", htlc.Amounts.Val.Sum()) - if s.cfg.NoopHTLCs { + if s.cfg.NoopHTLCs && supportNoOp { htlc.SetNoopAdd(rfqmsg.UseNoOpHTLCs) } @@ -587,7 +593,7 @@ func (s *AuxTrafficShaper) ProduceHtlcExtraData(totalAmount lnwire.MilliSatoshi, // Now we set the flag that marks this HTLC as a noop_add, which means // that the above dust will eventually return to us. This means that // only the assets will be sent and not any btc balance. - if s.cfg.NoopHTLCs { + if s.cfg.NoopHTLCs && supportNoOp { htlc.SetNoopAdd(rfqmsg.UseNoOpHTLCs) } diff --git a/tapfeatures/aux_channel_negotiator.go b/tapfeatures/aux_channel_negotiator.go new file mode 100644 index 000000000..21ab971a3 --- /dev/null +++ b/tapfeatures/aux_channel_negotiator.go @@ -0,0 +1,191 @@ +package tapfeatures + +import ( + "bytes" + "fmt" + + "github.com/lightningnetwork/lnd/lnutils" + "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/routing/route" + "github.com/lightningnetwork/lnd/tlv" +) + +// AuxChannelNegotiator is responsible for producing the extra tlv blob that is +// encapsulated in the init and reestablish peer messages. This helps us +// communicate custom feature bits with our peer. +type AuxChannelNegotiator struct { + // peerFeatures keeps track of the supported features of our peers. This + // map will be used for lookups by other subsystems, when some features + // need to be supported by both parties to take effect. + peerFeatures lnutils.SyncMap[route.Vertex, *lnwire.RawFeatureVector] + + // chanFeatures keeps track of the supported features of each channel. + // This map will be used for lookups by other subsystems to check + // whether certain custom channel features are supported. + chanFeatures lnutils.SyncMap[lnwire.ChannelID, *lnwire.RawFeatureVector] +} + +// NewAuxChannelNegotiator returns a new instance of the aux channel negotiator. +func NewAuxChannelNegotiator() *AuxChannelNegotiator { + return &AuxChannelNegotiator{} +} + +// GetInitFeatures is called when sending an init message to a peer. It returns +// custom feature bits to include in the init message TLVs. The implementation +// can decide which features to advertise based on the peer's identity. +func (n *AuxChannelNegotiator) GetInitFeatures( + _ route.Vertex) (tlv.Blob, error) { + + var buf bytes.Buffer + + // Grab the "static" feature vector that denotes the supported features + // of our node. If our peer can read this message they will keep track + // of our features just like we do below in `ProcessInitFeatures`. + features := getLocalFeatureVec() + err := features.Encode(&buf) + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +// ProcessInitFeatures handles received init feature TLVs from a peer. The +// implementation can store state internally to affect future channel operations +// with this peer. +func (n *AuxChannelNegotiator) ProcessInitFeatures(peer route.Vertex, + features tlv.Blob) error { + + buf := bytes.NewBuffer(features) + peerVec := lnwire.NewRawFeatureVector() + err := peerVec.Decode(buf) + if err != nil { + return err + } + + // Before we store this peer's supported features we need to first check + // if our required features are supported by that peer. If a locally + // required feature is not supported by the remote peer we have to + // return an error and drop the connection. Whether we support all of + // the remote required features is a responsibility of the remote peer. + // If we fail to support a remotely required feature they are the ones + // to drop the connection (by returning an error right here). + err = checkRequiredBits(getLocalFeatureVec(), peerVec) + if err != nil { + return err + } + + // Store this peer's features. + n.peerFeatures.Store(peer, peerVec) + + return nil +} + +// ProcessChannelReady handles the reception of the ChannelReady message, which +// signals that a newly established channel is now ready to use. This helps us +// correlate a peer's features with a channel outpoint +func (n *AuxChannelNegotiator) ProcessChannelReady(cid lnwire.ChannelID, + peer route.Vertex) { + + features, ok := n.peerFeatures.Load(peer) + if ok { + n.chanFeatures.Store(cid, features) + } +} + +// GetReestablishFeatures is called when sending a channel_reestablish message. +// It returns feature bits based on the specific channel identified by its +// funding outpoint and aux channel blob. +// +// We're currently not using the cid and aux blob of the channel as we're +// always using the same feature vectors regardless of the channel in question. +func (n *AuxChannelNegotiator) GetReestablishFeatures( + _ lnwire.ChannelID, _ tlv.Blob) (tlv.Blob, error) { + + var buf bytes.Buffer + + // We could store per-channel features, but currently there's no need to + // support that. We report the same feature vector over all of our + // custom channels. + features := getLocalFeatureVec() + err := features.Encode(&buf) + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +// ProcessReestablishFeatures handles received channel_reestablish feature TLVs. +// This is a blocking call - the channel link will wait for this method to +// complete before continuing channel operations. The implementation can modify +// aux channel behavior based on the negotiated features. +func (n *AuxChannelNegotiator) ProcessReestablishFeatures(cid lnwire.ChannelID, + features tlv.Blob, _ tlv.Blob) error { + + buf := bytes.NewBuffer(features) + peerVec := lnwire.NewRawFeatureVector() + err := peerVec.Decode(buf) + if err != nil { + return err + } + + // Before we store this channel's supported features we need to first + // check if our required features are supported by the remote party. If + // a locally required feature is not supported we have to return an + // error and drop the connection. Whether we support all of the remote + // required features is a responsibility of the remote peer. If we fail + // to support a remotely required feature they are the ones to drop the + // connection (by returning an error right here). + err = checkRequiredBits(getLocalFeatureVec(), peerVec) + if err != nil { + return err + } + + n.chanFeatures.Store(cid, peerVec) + + return nil +} + +// GetPeerFeatures returns the negotiated feature bit vector that was +// established with the given peer. +func (n *AuxChannelNegotiator) GetPeerFeatures( + peer route.Vertex) *lnwire.FeatureVector { + + rawfeatures, ok := n.peerFeatures.Load(peer) + if !ok { + rawfeatures = lnwire.NewRawFeatureVector() + } + + return lnwire.NewFeatureVector(rawfeatures, featureNames) +} + +// GetChannelFeatures returns the negotiated feature bits vector for the channel +// identified by the given channelID. +func (n *AuxChannelNegotiator) GetChannelFeatures( + cid lnwire.ChannelID) *lnwire.FeatureVector { + + rawfeatures, ok := n.chanFeatures.Load(cid) + if !ok { + rawfeatures = lnwire.NewRawFeatureVector() + } + + return lnwire.NewFeatureVector(rawfeatures, featureNames) +} + +// checkRequiredBits is a helper method that checks if all of the required bits +// of the first vector are supported by the second vector. +func checkRequiredBits(local, remote *lnwire.RawFeatureVector) error { + localBits, remoteBits := + lnwire.NewFeatureVector(local, featureNames), + lnwire.NewFeatureVector(remote, featureNames) + + for _, f := range ourFeatures() { + if localBits.RequiresFeature(f) && !remoteBits.HasFeature(f) { + return fmt.Errorf("peer does not support required "+ + "feature: %v", localBits.Name(f)) + } + } + + return nil +} diff --git a/tapfeatures/aux_channel_negotiator_test.go b/tapfeatures/aux_channel_negotiator_test.go new file mode 100644 index 000000000..b810a486e --- /dev/null +++ b/tapfeatures/aux_channel_negotiator_test.go @@ -0,0 +1,58 @@ +package tapfeatures + +import ( + "testing" + + "github.com/lightningnetwork/lnd/lnwire" + "github.com/stretchr/testify/require" +) + +// TestFeatureBits tests that the behavior of the feature vector matches our +// expectations when using the custom feature bits for taproot asset channels. +func TestFeatureBits(t *testing.T) { + featuresA := lnwire.NewFeatureVector( + lnwire.NewRawFeatureVector(NoOpHTLCsOptional), featureNames, + ) + + featuresB := lnwire.NewFeatureVector( + lnwire.NewRawFeatureVector(STXOOptional), featureNames, + ) + + require.True(t, featuresA.HasFeature(NoOpHTLCsOptional)) + require.True(t, featuresB.HasFeature(STXOOptional)) + + require.False(t, featuresA.HasFeature(STXOOptional)) + require.False(t, featuresB.HasFeature(NoOpHTLCsOptional)) + + require.False(t, featuresA.RequiresFeature(NoOpHTLCsOptional)) + require.False(t, featuresB.RequiresFeature(STXOOptional)) + + err := checkRequiredBits( + featuresA.RawFeatureVector, featuresB.RawFeatureVector, + ) + + require.NoError(t, err) + + featuresA = lnwire.NewFeatureVector( + lnwire.NewRawFeatureVector(NoOpHTLCsRequired), featureNames, + ) + + featuresB = lnwire.NewFeatureVector( + lnwire.NewRawFeatureVector(STXORequired), featureNames, + ) + + require.True(t, featuresA.HasFeature(NoOpHTLCsOptional)) + require.True(t, featuresB.HasFeature(STXOOptional)) + + require.False(t, featuresA.HasFeature(STXOOptional)) + require.False(t, featuresB.HasFeature(NoOpHTLCsOptional)) + + require.True(t, featuresA.RequiresFeature(NoOpHTLCsOptional)) + require.True(t, featuresB.RequiresFeature(STXOOptional)) + + err = checkRequiredBits( + featuresA.RawFeatureVector, featuresB.RawFeatureVector, + ) + + require.Error(t, err) +} diff --git a/tapfeatures/aux_feature_bits.go b/tapfeatures/aux_feature_bits.go new file mode 100644 index 000000000..135e1e3a1 --- /dev/null +++ b/tapfeatures/aux_feature_bits.go @@ -0,0 +1,51 @@ +package tapfeatures + +import "github.com/lightningnetwork/lnd/lnwire" + +const ( + // NoOpHTLCsRequired is a feature bit that declares the noop-htlcs as a + // required feature. + NoOpHTLCsRequired lnwire.FeatureBit = 0 + + // NoOpHTLCsOptional is a feature bit that declares the noop-htlcs as an + // optional feature. + NoOpHTLCsOptional lnwire.FeatureBit = 1 + + // STXORequired is a feature bit that declares STXO proofs as a required + // feature. + STXORequired lnwire.FeatureBit = 2 + + // STXOOptional is a feature bit that declares the STXO proofs as an + // optional feature. + STXOOptional lnwire.FeatureBit = 3 +) + +// featureNames keeps track of the string description of known features. +var featureNames = map[lnwire.FeatureBit]string{ + NoOpHTLCsRequired: "noop-htlcs", + NoOpHTLCsOptional: "noop-htlcs", + STXORequired: "stxo-proofs", + STXOOptional: "stxo-proofs", +} + +// ourFeatures returns a slice containing all of the locally supported features. +func ourFeatures() []lnwire.FeatureBit { + // TODO(george): instead of hosting the supported features in the + // following slice we could make something more explicit / modular. + return []lnwire.FeatureBit{ + NoOpHTLCsOptional, + STXOOptional, + } +} + +// getLocalFeatureVec returns the feature vector of the currently supported +// features. This set of features may change between different versions of tapd, +// and that exactly is its purpose. This feature vector denotes which features +// of tap channels are currently supported, in order to maintain compatibility +// with our peers. +func getLocalFeatureVec() *lnwire.RawFeatureVector { + ourFeatures := ourFeatures() + return lnwire.NewRawFeatureVector( + ourFeatures..., + ) +}