Merge pull request #8100 from bhandras/updateinvoice-refactor
channeldb: refactor `UpdateInvoice` to make it simpler to create SQL specific implementation
This commit is contained in:
12 files changed
+4793
-4245
No files matched your search
+1
-3158
File diff suppressed because it is too large.
Load diff
+344
-927
File diff suppressed because it is too large.
Load diff
@@ -324,6 +324,9 @@
|
|||||||
* [Update](https://github.com/lightningnetwork/lnd/pull/8419) the embedded
|
* [Update](https://github.com/lightningnetwork/lnd/pull/8419) the embedded
|
||||||
Postgres version and raise max connections.
|
Postgres version and raise max connections.
|
||||||
|
|
||||||
|
* [Refactor UpdateInvoice](https://github.com/lightningnetwork/lnd/pull/8100) to
|
||||||
|
make it simpler to adjust code to also support SQL InvoiceDB implementation.
|
||||||
|
|
||||||
## Code Health
|
## Code Health
|
||||||
|
|
||||||
* [Remove database pointers](https://github.com/lightningnetwork/lnd/pull/8117)
|
* [Remove database pointers](https://github.com/lightningnetwork/lnd/pull/8117)
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ require (
|
|||||||
github.com/jessevdk/go-flags v1.4.0
|
github.com/jessevdk/go-flags v1.4.0
|
||||||
github.com/jrick/logrotate v1.0.0
|
github.com/jrick/logrotate v1.0.0
|
||||||
github.com/kkdai/bstream v1.0.0
|
github.com/kkdai/bstream v1.0.0
|
||||||
github.com/lib/pq v1.10.3
|
github.com/lib/pq v1.10.4
|
||||||
github.com/lightninglabs/neutrino v0.16.0
|
github.com/lightninglabs/neutrino v0.16.0
|
||||||
github.com/lightninglabs/neutrino/cache v1.1.2
|
github.com/lightninglabs/neutrino/cache v1.1.2
|
||||||
github.com/lightningnetwork/lightning-onion v1.2.1-0.20230823005744-06182b1d7d2f
|
github.com/lightningnetwork/lightning-onion v1.2.1-0.20230823005744-06182b1d7d2f
|
||||||
@@ -41,7 +41,7 @@ require (
|
|||||||
github.com/lightningnetwork/lnd/clock v1.1.1
|
github.com/lightningnetwork/lnd/clock v1.1.1
|
||||||
github.com/lightningnetwork/lnd/fn v1.0.4
|
github.com/lightningnetwork/lnd/fn v1.0.4
|
||||||
github.com/lightningnetwork/lnd/healthcheck v1.2.3
|
github.com/lightningnetwork/lnd/healthcheck v1.2.3
|
||||||
github.com/lightningnetwork/lnd/kvdb v1.4.4
|
github.com/lightningnetwork/lnd/kvdb v1.4.5
|
||||||
github.com/lightningnetwork/lnd/queue v1.1.1
|
github.com/lightningnetwork/lnd/queue v1.1.1
|
||||||
github.com/lightningnetwork/lnd/ticker v1.1.1
|
github.com/lightningnetwork/lnd/ticker v1.1.1
|
||||||
github.com/lightningnetwork/lnd/tlv v1.2.1
|
github.com/lightningnetwork/lnd/tlv v1.2.1
|
||||||
@@ -93,9 +93,8 @@ require (
|
|||||||
github.com/docker/docker v24.0.7+incompatible // indirect
|
github.com/docker/docker v24.0.7+incompatible // indirect
|
||||||
github.com/docker/go-connections v0.4.0 // indirect
|
github.com/docker/go-connections v0.4.0 // indirect
|
||||||
github.com/docker/go-units v0.5.0 // indirect
|
github.com/docker/go-units v0.5.0 // indirect
|
||||||
github.com/dsnet/compress v0.0.1 // indirect
|
|
||||||
github.com/dustin/go-humanize v1.0.0 // indirect
|
github.com/dustin/go-humanize v1.0.0 // indirect
|
||||||
github.com/fergusstrange/embedded-postgres v1.10.0 // indirect
|
github.com/fergusstrange/embedded-postgres v1.25.0 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.5.4 // indirect
|
github.com/fsnotify/fsnotify v1.5.4 // indirect
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
github.com/golang-jwt/jwt/v4 v4.4.2 // indirect
|
github.com/golang-jwt/jwt/v4 v4.4.2 // indirect
|
||||||
@@ -120,24 +119,19 @@ require (
|
|||||||
github.com/juju/loggo v0.0.0-20210728185423-eebad3a902c4 // indirect
|
github.com/juju/loggo v0.0.0-20210728185423-eebad3a902c4 // indirect
|
||||||
github.com/juju/testing v0.0.0-20220203020004-a0ff61f03494 // indirect
|
github.com/juju/testing v0.0.0-20220203020004-a0ff61f03494 // indirect
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||||
github.com/klauspost/compress v1.13.6 // indirect
|
|
||||||
github.com/klauspost/pgzip v1.2.5 // indirect
|
|
||||||
github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf // indirect
|
github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf // indirect
|
||||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||||
github.com/mholt/archiver/v3 v3.5.0 // indirect
|
|
||||||
github.com/mitchellh/mapstructure v1.4.1 // indirect
|
github.com/mitchellh/mapstructure v1.4.1 // indirect
|
||||||
github.com/moby/term v0.5.0 // indirect
|
github.com/moby/term v0.5.0 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.1 // indirect
|
github.com/modern-go/reflect2 v1.0.1 // indirect
|
||||||
github.com/nwaples/rardecode v1.1.2 // indirect
|
|
||||||
github.com/nxadm/tail v1.4.8 // indirect
|
github.com/nxadm/tail v1.4.8 // indirect
|
||||||
github.com/onsi/gomega v1.26.0 // indirect
|
github.com/onsi/gomega v1.26.0 // indirect
|
||||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
github.com/opencontainers/image-spec v1.0.2 // indirect
|
github.com/opencontainers/image-spec v1.0.2 // indirect
|
||||||
github.com/opencontainers/runc v1.1.5 // indirect
|
github.com/opencontainers/runc v1.1.5 // indirect
|
||||||
github.com/pierrec/lz4/v4 v4.1.8 // indirect
|
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/prometheus/client_model v0.2.0 // indirect
|
github.com/prometheus/client_model v0.2.0 // indirect
|
||||||
@@ -154,7 +148,6 @@ require (
|
|||||||
github.com/stretchr/objx v0.5.0 // indirect
|
github.com/stretchr/objx v0.5.0 // indirect
|
||||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // 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/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect
|
||||||
github.com/ulikunitz/xz v0.5.11 // indirect
|
|
||||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
|
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
|
||||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||||
|
|||||||
@@ -61,7 +61,6 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy
|
|||||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
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/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||||
github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
|
||||||
github.com/andybalholm/brotli v1.0.3 h1:fpcw+r1N1h0Poc1F/pHbW40cUm/lMEQslZtCkBQ0UnM=
|
github.com/andybalholm/brotli v1.0.3 h1:fpcw+r1N1h0Poc1F/pHbW40cUm/lMEQslZtCkBQ0UnM=
|
||||||
github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||||
@@ -176,9 +175,6 @@ github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5Xh
|
|||||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
|
|
||||||
github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
|
|
||||||
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 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
@@ -190,8 +186,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.m
|
|||||||
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
|
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/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.10.1 h1:c0g45+xCJhdgFGw7a5QAfdS4byAbud7miNWJ1WwEVf8=
|
github.com/envoyproxy/protoc-gen-validate v0.10.1 h1:c0g45+xCJhdgFGw7a5QAfdS4byAbud7miNWJ1WwEVf8=
|
||||||
github.com/fergusstrange/embedded-postgres v1.10.0 h1:YnwF6xAQYmKLAXXrrRx4rHDLih47YJwVPvg8jeKfdNg=
|
github.com/fergusstrange/embedded-postgres v1.25.0 h1:sa+k2Ycrtz40eCRPOzI7Ry7TtkWXXJ+YRsxpKMDhxK0=
|
||||||
github.com/fergusstrange/embedded-postgres v1.10.0/go.mod h1:a008U8/Rws5FtIOTGYDYa7beVWsT3qVKyqExqYYjL+c=
|
github.com/fergusstrange/embedded-postgres v1.25.0/go.mod h1:t/MLs0h9ukYM6FSt99R7InCHs1nW0ordoVCcnzmpTYw=
|
||||||
github.com/frankban/quicktest v1.2.2/go.mod h1:Qh/WofXFeiAFII1aEBu529AtJo6Zg2VHscnEsbBnJ20=
|
github.com/frankban/quicktest v1.2.2/go.mod h1:Qh/WofXFeiAFII1aEBu529AtJo6Zg2VHscnEsbBnJ20=
|
||||||
github.com/frankban/quicktest v1.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebPhedY=
|
github.com/frankban/quicktest v1.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebPhedY=
|
||||||
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
|
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
|
||||||
@@ -251,7 +247,6 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS
|
|||||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
|
||||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
@@ -402,14 +397,6 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
|
|||||||
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
|
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
|
||||||
github.com/kkdai/bstream v1.0.0 h1:Se5gHwgp2VT2uHfDrkbbgbgEvV9cimLELwrPJctSjg8=
|
github.com/kkdai/bstream v1.0.0 h1:Se5gHwgp2VT2uHfDrkbbgbgEvV9cimLELwrPJctSjg8=
|
||||||
github.com/kkdai/bstream v1.0.0/go.mod h1:FDnDOHt5Yx4p3FaHcioFT0QjDOtgUpvjeZqAs+NVZZA=
|
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.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=
|
|
||||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
|
||||||
github.com/klauspost/pgzip v1.2.4/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
|
||||||
github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE=
|
|
||||||
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
|
||||||
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.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.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/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
@@ -424,10 +411,9 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|||||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
|
||||||
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/lib/pq v1.10.3 h1:v9QZf2Sn6AmjXtQeFpdoq/eaNtYP6IN+7lcrygsIAtg=
|
github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk=
|
||||||
github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.10.4/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 h1:HZKvJUHlcXI/f/O0Avg7t8sqkPo78HFzjmeYFl6DPnc=
|
||||||
github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf/go.mod h1:vxmQPeIQxPf6Jf9rM8R+B4rKBqLA2AjttNxkFBL2Plk=
|
github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf/go.mod h1:vxmQPeIQxPf6Jf9rM8R+B4rKBqLA2AjttNxkFBL2Plk=
|
||||||
github.com/lightninglabs/neutrino v0.16.0 h1:YNTQG32fPR/Zg0vvJVI65OBH8l3U18LSXXtX91hx0q0=
|
github.com/lightninglabs/neutrino v0.16.0 h1:YNTQG32fPR/Zg0vvJVI65OBH8l3U18LSXXtX91hx0q0=
|
||||||
@@ -446,8 +432,8 @@ github.com/lightningnetwork/lnd/fn v1.0.4 h1:n4iGRRoS+XHqNbOrsXIvweps/QfWk+moO7F
|
|||||||
github.com/lightningnetwork/lnd/fn v1.0.4/go.mod h1:K9gbvdl5z4XmRcqWUVqvvVcuRKtmq9BNQ+cWYlk+vjw=
|
github.com/lightningnetwork/lnd/fn v1.0.4/go.mod h1:K9gbvdl5z4XmRcqWUVqvvVcuRKtmq9BNQ+cWYlk+vjw=
|
||||||
github.com/lightningnetwork/lnd/healthcheck v1.2.3 h1:oqhOOy8WmIEa6RBkYKC0mmYZkhl8T2kGD97n9jpML8o=
|
github.com/lightningnetwork/lnd/healthcheck v1.2.3 h1:oqhOOy8WmIEa6RBkYKC0mmYZkhl8T2kGD97n9jpML8o=
|
||||||
github.com/lightningnetwork/lnd/healthcheck v1.2.3/go.mod h1:eDxH3dEwV9DeBW/6inrmlVh1qBOFV0AI14EEPnGt9gc=
|
github.com/lightningnetwork/lnd/healthcheck v1.2.3/go.mod h1:eDxH3dEwV9DeBW/6inrmlVh1qBOFV0AI14EEPnGt9gc=
|
||||||
github.com/lightningnetwork/lnd/kvdb v1.4.4 h1:bCv63rVCvzqj1BkagN/EWTov6NDDgYEG/t0z2HepRMk=
|
github.com/lightningnetwork/lnd/kvdb v1.4.5 h1:wwX3hbFTsnxEIL5X2Pszq1o3Fd2OZGdyWIMr9QrMxL8=
|
||||||
github.com/lightningnetwork/lnd/kvdb v1.4.4/go.mod h1:9SuaIqMA9ugrVkdvgQkYXa8CAKYNYd4vsEYORP4V698=
|
github.com/lightningnetwork/lnd/kvdb v1.4.5/go.mod h1:oaGL6R/qwazM7hPurg8jSPYsWw3cGEOt6YJDs5TUNos=
|
||||||
github.com/lightningnetwork/lnd/queue v1.1.1 h1:99ovBlpM9B0FRCGYJo6RSFDlt8/vOkQQZznVb18iNMI=
|
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/queue v1.1.1/go.mod h1:7A6nC1Qrm32FHuhx/mi1cieAiBZo5O6l8IBIoQxvkz4=
|
||||||
github.com/lightningnetwork/lnd/ticker v1.1.1 h1:J/b6N2hibFtC7JLV77ULQp++QLtCwT6ijJlbdiZFbSM=
|
github.com/lightningnetwork/lnd/ticker v1.1.1 h1:J/b6N2hibFtC7JLV77ULQp++QLtCwT6ijJlbdiZFbSM=
|
||||||
@@ -474,8 +460,6 @@ github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh
|
|||||||
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
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/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
github.com/mholt/archiver/v3 v3.5.0 h1:nE8gZIrw66cu4osS/U7UW7YDuGMHssxKutU8IfWxwWE=
|
|
||||||
github.com/mholt/archiver/v3 v3.5.0/go.mod h1:qqTTPUK/HZPFgFQ/TJ3BzvTpF/dPtFVJXdQbCmeMxwc=
|
|
||||||
github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
|
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/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
|
||||||
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
|
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
|
||||||
@@ -493,9 +477,6 @@ github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
|||||||
github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
|
github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
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/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
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/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
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 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||||
@@ -521,9 +502,6 @@ github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuh
|
|||||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||||
github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4=
|
github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4=
|
||||||
github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg=
|
github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg=
|
||||||
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=
|
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
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.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
@@ -592,7 +570,6 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
|
|||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
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.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.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/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
@@ -606,8 +583,6 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 h1:uruHq4
|
|||||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||||
github.com/tv42/zbase32 v0.0.0-20160707012821-501572607d02 h1:tcJ6OjwOMvExLlzrAVZute09ocAGa7KqOON60++Gz4E=
|
github.com/tv42/zbase32 v0.0.0-20160707012821-501572607d02 h1:tcJ6OjwOMvExLlzrAVZute09ocAGa7KqOON60++Gz4E=
|
||||||
github.com/tv42/zbase32 v0.0.0-20160707012821-501572607d02/go.mod h1:tHlrkM198S068ZqfrO6S8HsoJq2bF3ETfTL+kt4tInY=
|
github.com/tv42/zbase32 v0.0.0-20160707012821-501572607d02/go.mod h1:tHlrkM198S068ZqfrO6S8HsoJq2bF3ETfTL+kt4tInY=
|
||||||
github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
|
|
||||||
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
|
||||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||||
github.com/urfave/cli v1.22.9 h1:cv3/KhXGBGjEXLC4bH0sLuJ9BewaAbpk5oyMOveu4pw=
|
github.com/urfave/cli v1.22.9 h1:cv3/KhXGBGjEXLC4bH0sLuJ9BewaAbpk5oyMOveu4pw=
|
||||||
github.com/urfave/cli v1.22.9/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
github.com/urfave/cli v1.22.9/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||||
@@ -674,6 +649,7 @@ go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
|||||||
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
|
go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
|
||||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
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.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ package invoices
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||||
"github.com/lightningnetwork/lnd/lntypes"
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
"github.com/lightningnetwork/lnd/record"
|
"github.com/lightningnetwork/lnd/record"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -162,3 +164,37 @@ type InvoiceSlice struct {
|
|||||||
// CircuitKey is a tuple of channel ID and HTLC ID, used to uniquely identify
|
// CircuitKey is a tuple of channel ID and HTLC ID, used to uniquely identify
|
||||||
// HTLCs in a circuit.
|
// HTLCs in a circuit.
|
||||||
type CircuitKey = models.CircuitKey
|
type CircuitKey = models.CircuitKey
|
||||||
|
|
||||||
|
// InvoiceUpdater is an interface to abstract away the details of updating an
|
||||||
|
// invoice in the database. The methods of this interface are called during the
|
||||||
|
// in-memory update of an invoice when the database needs to be updated or the
|
||||||
|
// updated state needs to be marked as needing to be written to the database.
|
||||||
|
type InvoiceUpdater interface {
|
||||||
|
// AddHtlc adds a new htlc to the invoice.
|
||||||
|
AddHtlc(circuitKey CircuitKey, newHtlc *InvoiceHTLC) error
|
||||||
|
|
||||||
|
// ResolveHtlc marks an htlc as resolved with the given state.
|
||||||
|
ResolveHtlc(circuitKey CircuitKey, state HtlcState,
|
||||||
|
resolveTime time.Time) error
|
||||||
|
|
||||||
|
// AddAmpHtlcPreimage adds a preimage of an AMP htlc to the AMP invoice
|
||||||
|
// identified by the setID.
|
||||||
|
AddAmpHtlcPreimage(setID [32]byte, circuitKey CircuitKey,
|
||||||
|
preimage lntypes.Preimage) error
|
||||||
|
|
||||||
|
// UpdateInvoiceState updates the invoice state to the new state.
|
||||||
|
UpdateInvoiceState(newState ContractState,
|
||||||
|
preimage *lntypes.Preimage) error
|
||||||
|
|
||||||
|
// UpdateInvoiceAmtPaid updates the invoice amount paid to the new
|
||||||
|
// amount.
|
||||||
|
UpdateInvoiceAmtPaid(amtPaid lnwire.MilliSatoshi) error
|
||||||
|
|
||||||
|
// UpdateAmpState updates the state of the AMP invoice identified by
|
||||||
|
// the setID.
|
||||||
|
UpdateAmpState(setID [32]byte, newState InvoiceStateAMP,
|
||||||
|
circuitKey models.CircuitKey) error
|
||||||
|
|
||||||
|
// Finalize finalizes the update before it is written to the database.
|
||||||
|
Finalize(updateType UpdateType) error
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
|
|
||||||
"github.com/lightningnetwork/lnd/amp"
|
"github.com/lightningnetwork/lnd/amp"
|
||||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||||
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
"github.com/lightningnetwork/lnd/clock"
|
"github.com/lightningnetwork/lnd/clock"
|
||||||
invpkg "github.com/lightningnetwork/lnd/invoices"
|
invpkg "github.com/lightningnetwork/lnd/invoices"
|
||||||
"github.com/lightningnetwork/lnd/lntest/wait"
|
"github.com/lightningnetwork/lnd/lntest/wait"
|
||||||
@@ -20,11 +21,115 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestSettleInvoice tests settling of an invoice and related notifications.
|
// TestInvoiceRegistry is a master test which encompasses all tests using an
|
||||||
func TestSettleInvoice(t *testing.T) {
|
// InvoiceDB instance. The purpose of this test is to be able to run all tests
|
||||||
|
// with a custom DB instance, so that we can test the same logic with different
|
||||||
|
// DB implementations.
|
||||||
|
func TestInvoiceRegistry(t *testing.T) {
|
||||||
|
testList := []struct {
|
||||||
|
name string
|
||||||
|
test func(t *testing.T,
|
||||||
|
makeDB func(t *testing.T) (
|
||||||
|
invpkg.InvoiceDB, *clock.TestClock))
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "SettleInvoice",
|
||||||
|
test: testSettleInvoice,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "CancelInvoice",
|
||||||
|
test: testCancelInvoice,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "SettleHoldInvoice",
|
||||||
|
test: testSettleHoldInvoice,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "CancelHoldInvoice",
|
||||||
|
test: testCancelHoldInvoice,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "UnknownInvoice",
|
||||||
|
test: testUnknownInvoice,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "KeySend",
|
||||||
|
test: testKeySend,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "HoldKeysend",
|
||||||
|
test: testHoldKeysend,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "MppPayment",
|
||||||
|
test: testMppPayment,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "MppPaymentWithOverpayment",
|
||||||
|
test: testMppPaymentWithOverpayment,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "InvoiceExpiryWithRegistry",
|
||||||
|
test: testInvoiceExpiryWithRegistry,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "OldInvoiceRemovalOnStart",
|
||||||
|
test: testOldInvoiceRemovalOnStart,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "HeightExpiryWithRegistry",
|
||||||
|
test: testHeightExpiryWithRegistry,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "MultipleSetHeightExpiry",
|
||||||
|
test: testMultipleSetHeightExpiry,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "SettleInvoicePaymentAddrRequired",
|
||||||
|
test: testSettleInvoicePaymentAddrRequired,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "SettleInvoicePaymentAddrRequiredOptionalGrace",
|
||||||
|
test: testSettleInvoicePaymentAddrRequiredOptionalGrace,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "AMPWithoutMPPPayload",
|
||||||
|
test: testAMPWithoutMPPPayload,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "SpontaneousAmpPayment",
|
||||||
|
test: testSpontaneousAmpPayment,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
makeKeyValueDB := func(t *testing.T) (invpkg.InvoiceDB,
|
||||||
|
*clock.TestClock) {
|
||||||
|
|
||||||
|
testClock := clock.NewTestClock(testNow)
|
||||||
|
db, err := channeldb.MakeTestInvoiceDB(
|
||||||
|
t, channeldb.OptionClock(testClock),
|
||||||
|
)
|
||||||
|
require.NoError(t, err, "unable to make test db")
|
||||||
|
|
||||||
|
return db, testClock
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testList {
|
||||||
|
test := test
|
||||||
|
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
test.test(t, makeKeyValueDB)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// testSettleInvoice tests settling of an invoice and related notifications.
|
||||||
|
func testSettleInvoice(t *testing.T,
|
||||||
|
makeDB func(t *testing.T) (invpkg.InvoiceDB, *clock.TestClock)) {
|
||||||
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
ctx := newTestContext(t, nil)
|
ctx := newTestContext(t, nil, makeDB)
|
||||||
|
|
||||||
ctxb := context.Background()
|
ctxb := context.Background()
|
||||||
allSubscriptions, err := ctx.registry.SubscribeNotifications(ctxb, 0, 0)
|
allSubscriptions, err := ctx.registry.SubscribeNotifications(ctxb, 0, 0)
|
||||||
@@ -199,7 +304,9 @@ func TestSettleInvoice(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testCancelInvoice(t *testing.T, gc bool) {
|
func testCancelInvoiceImpl(t *testing.T, gc bool,
|
||||||
|
makeDB func(t *testing.T) (invpkg.InvoiceDB, *clock.TestClock)) {
|
||||||
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
cfg := defaultRegistryConfig()
|
cfg := defaultRegistryConfig()
|
||||||
@@ -207,7 +314,7 @@ func testCancelInvoice(t *testing.T, gc bool) {
|
|||||||
// If set to true, then also delete the invoice from the DB after
|
// If set to true, then also delete the invoice from the DB after
|
||||||
// cancellation.
|
// cancellation.
|
||||||
cfg.GcCanceledInvoicesOnTheFly = gc
|
cfg.GcCanceledInvoicesOnTheFly = gc
|
||||||
ctx := newTestContext(t, &cfg)
|
ctx := newTestContext(t, &cfg, makeDB)
|
||||||
|
|
||||||
ctxb := context.Background()
|
ctxb := context.Background()
|
||||||
allSubscriptions, err := ctx.registry.SubscribeNotifications(ctxb, 0, 0)
|
allSubscriptions, err := ctx.registry.SubscribeNotifications(ctxb, 0, 0)
|
||||||
@@ -329,36 +436,37 @@ func testCancelInvoice(t *testing.T, gc bool) {
|
|||||||
require.Equal(t, testCurrentHeight, failResolution.AcceptHeight)
|
require.Equal(t, testCurrentHeight, failResolution.AcceptHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestCancelInvoice tests cancellation of an invoice and related notifications.
|
// testCancelInvoice tests cancellation of an invoice and related notifications.
|
||||||
func TestCancelInvoice(t *testing.T) {
|
func testCancelInvoice(t *testing.T,
|
||||||
|
makeDB func(t *testing.T) (invpkg.InvoiceDB, *clock.TestClock)) {
|
||||||
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
// Test cancellation both with garbage collection (meaning that canceled
|
// Test cancellation both with garbage collection (meaning that canceled
|
||||||
// invoice will be deleted) and without (meaning it'll be kept).
|
// invoice will be deleted) and without (meaning it'll be kept).
|
||||||
t.Run("garbage collect", func(t *testing.T) {
|
t.Run("garbage collect", func(t *testing.T) {
|
||||||
testCancelInvoice(t, true)
|
testCancelInvoiceImpl(t, true, makeDB)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("no garbage collect", func(t *testing.T) {
|
t.Run("no garbage collect", func(t *testing.T) {
|
||||||
testCancelInvoice(t, false)
|
testCancelInvoiceImpl(t, false, makeDB)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestSettleHoldInvoice tests settling of a hold invoice and related
|
// testSettleHoldInvoice tests settling of a hold invoice and related
|
||||||
// notifications.
|
// notifications.
|
||||||
func TestSettleHoldInvoice(t *testing.T) {
|
func testSettleHoldInvoice(t *testing.T,
|
||||||
|
makeDB func(t *testing.T) (invpkg.InvoiceDB, *clock.TestClock)) {
|
||||||
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
defer timeout()()
|
defer timeout()()
|
||||||
|
|
||||||
idb, err := newTestChannelDB(t, clock.NewTestClock(time.Time{}))
|
idb, clock := makeDB(t)
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Instantiate and start the invoice ctx.registry.
|
// Instantiate and start the invoice ctx.registry.
|
||||||
cfg := invpkg.RegistryConfig{
|
cfg := invpkg.RegistryConfig{
|
||||||
FinalCltvRejectDelta: testFinalCltvRejectDelta,
|
FinalCltvRejectDelta: testFinalCltvRejectDelta,
|
||||||
Clock: clock.NewTestClock(testTime),
|
Clock: clock,
|
||||||
}
|
}
|
||||||
|
|
||||||
expiryWatcher := invpkg.NewInvoiceExpiryWatcher(
|
expiryWatcher := invpkg.NewInvoiceExpiryWatcher(
|
||||||
@@ -366,7 +474,7 @@ func TestSettleHoldInvoice(t *testing.T) {
|
|||||||
)
|
)
|
||||||
registry := invpkg.NewRegistry(idb, expiryWatcher, &cfg)
|
registry := invpkg.NewRegistry(idb, expiryWatcher, &cfg)
|
||||||
|
|
||||||
err = registry.Start()
|
err := registry.Start()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer registry.Stop()
|
defer registry.Stop()
|
||||||
|
|
||||||
@@ -511,15 +619,15 @@ func TestSettleHoldInvoice(t *testing.T) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestCancelHoldInvoice tests canceling of a hold invoice and related
|
// testCancelHoldInvoice tests canceling of a hold invoice and related
|
||||||
// notifications.
|
// notifications.
|
||||||
func TestCancelHoldInvoice(t *testing.T) {
|
func testCancelHoldInvoice(t *testing.T,
|
||||||
|
makeDB func(t *testing.T) (invpkg.InvoiceDB, *clock.TestClock)) {
|
||||||
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
defer timeout()()
|
defer timeout()()
|
||||||
|
|
||||||
testClock := clock.NewTestClock(testTime)
|
idb, testClock := makeDB(t)
|
||||||
idb, err := newTestChannelDB(t, testClock)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Instantiate and start the invoice ctx.registry.
|
// Instantiate and start the invoice ctx.registry.
|
||||||
cfg := invpkg.RegistryConfig{
|
cfg := invpkg.RegistryConfig{
|
||||||
@@ -531,7 +639,7 @@ func TestCancelHoldInvoice(t *testing.T) {
|
|||||||
)
|
)
|
||||||
registry := invpkg.NewRegistry(idb, expiryWatcher, &cfg)
|
registry := invpkg.NewRegistry(idb, expiryWatcher, &cfg)
|
||||||
|
|
||||||
err = registry.Start()
|
err := registry.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -587,14 +695,16 @@ func TestCancelHoldInvoice(t *testing.T) {
|
|||||||
require.Equal(t, testCurrentHeight, failResolution.AcceptHeight)
|
require.Equal(t, testCurrentHeight, failResolution.AcceptHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestUnknownInvoice tests that invoice registry returns an error when the
|
// testUnknownInvoice tests that invoice registry returns an error when the
|
||||||
// invoice is unknown. This is to guard against returning a cancel htlc
|
// invoice is unknown. This is to guard against returning a cancel htlc
|
||||||
// resolution for forwarded htlcs. In the link, NotifyExitHopHtlc is only called
|
// resolution for forwarded htlcs. In the link, NotifyExitHopHtlc is only called
|
||||||
// if we are the exit hop, but in htlcIncomingContestResolver it is called with
|
// if we are the exit hop, but in htlcIncomingContestResolver it is called with
|
||||||
// forwarded htlc hashes as well.
|
// forwarded htlc hashes as well.
|
||||||
func TestUnknownInvoice(t *testing.T) {
|
func testUnknownInvoice(t *testing.T,
|
||||||
|
makeDB func(t *testing.T) (invpkg.InvoiceDB, *clock.TestClock)) {
|
||||||
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
ctx := newTestContext(t, nil)
|
ctx := newTestContext(t, nil, makeDB)
|
||||||
|
|
||||||
// Notify arrival of a new htlc paying to this invoice. This should
|
// Notify arrival of a new htlc paying to this invoice. This should
|
||||||
// succeed.
|
// succeed.
|
||||||
@@ -611,28 +721,32 @@ func TestUnknownInvoice(t *testing.T) {
|
|||||||
checkFailResolution(t, resolution, invpkg.ResultInvoiceNotFound)
|
checkFailResolution(t, resolution, invpkg.ResultInvoiceNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestKeySend tests receiving a spontaneous payment with and without keysend
|
// testKeySend tests receiving a spontaneous payment with and without keysend
|
||||||
// enabled.
|
// enabled.
|
||||||
func TestKeySend(t *testing.T) {
|
func testKeySend(t *testing.T,
|
||||||
|
makeDB func(t *testing.T) (invpkg.InvoiceDB, *clock.TestClock)) {
|
||||||
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
t.Run("enabled", func(t *testing.T) {
|
t.Run("enabled", func(t *testing.T) {
|
||||||
testKeySend(t, true)
|
testKeySendImpl(t, true, makeDB)
|
||||||
})
|
})
|
||||||
t.Run("disabled", func(t *testing.T) {
|
t.Run("disabled", func(t *testing.T) {
|
||||||
testKeySend(t, false)
|
testKeySendImpl(t, false, makeDB)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// testKeySend is the inner test function that tests keysend for a particular
|
// testKeySendImpl is the inner test function that tests keysend for a
|
||||||
// enabled state on the receiver end.
|
// particular enabled state on the receiver end.
|
||||||
func testKeySend(t *testing.T, keySendEnabled bool) {
|
func testKeySendImpl(t *testing.T, keySendEnabled bool,
|
||||||
|
makeDB func(t *testing.T) (invpkg.InvoiceDB, *clock.TestClock)) {
|
||||||
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
defer timeout()()
|
defer timeout()()
|
||||||
|
|
||||||
cfg := defaultRegistryConfig()
|
cfg := defaultRegistryConfig()
|
||||||
cfg.AcceptKeySend = keySendEnabled
|
cfg.AcceptKeySend = keySendEnabled
|
||||||
ctx := newTestContext(t, &cfg)
|
ctx := newTestContext(t, &cfg, makeDB)
|
||||||
|
|
||||||
allSubscriptions, err := ctx.registry.SubscribeNotifications(
|
allSubscriptions, err := ctx.registry.SubscribeNotifications(
|
||||||
context.Background(), 0, 0,
|
context.Background(), 0, 0,
|
||||||
@@ -742,20 +856,24 @@ func testKeySend(t *testing.T, keySendEnabled bool) {
|
|||||||
checkSubscription()
|
checkSubscription()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestHoldKeysend tests receiving a spontaneous payment that is held.
|
// testHoldKeysend tests receiving a spontaneous payment that is held.
|
||||||
func TestHoldKeysend(t *testing.T) {
|
func testHoldKeysend(t *testing.T,
|
||||||
|
makeDB func(t *testing.T) (invpkg.InvoiceDB, *clock.TestClock)) {
|
||||||
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
t.Run("settle", func(t *testing.T) {
|
t.Run("settle", func(t *testing.T) {
|
||||||
testHoldKeysend(t, false)
|
testHoldKeysendImpl(t, false, makeDB)
|
||||||
})
|
})
|
||||||
t.Run("timeout", func(t *testing.T) {
|
t.Run("timeout", func(t *testing.T) {
|
||||||
testHoldKeysend(t, true)
|
testHoldKeysendImpl(t, true, makeDB)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// testHoldKeysend is the inner test function that tests hold-keysend.
|
// testHoldKeysendImpl is the inner test function that tests hold-keysend.
|
||||||
func testHoldKeysend(t *testing.T, timeoutKeysend bool) {
|
func testHoldKeysendImpl(t *testing.T, timeoutKeysend bool,
|
||||||
|
makeDB func(t *testing.T) (invpkg.InvoiceDB, *clock.TestClock)) {
|
||||||
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
defer timeout()()
|
defer timeout()()
|
||||||
|
|
||||||
@@ -764,7 +882,7 @@ func testHoldKeysend(t *testing.T, timeoutKeysend bool) {
|
|||||||
cfg := defaultRegistryConfig()
|
cfg := defaultRegistryConfig()
|
||||||
cfg.AcceptKeySend = true
|
cfg.AcceptKeySend = true
|
||||||
cfg.KeysendHoldTime = holdDuration
|
cfg.KeysendHoldTime = holdDuration
|
||||||
ctx := newTestContext(t, &cfg)
|
ctx := newTestContext(t, &cfg, makeDB)
|
||||||
|
|
||||||
ctxb := context.Background()
|
ctxb := context.Background()
|
||||||
allSubscriptions, err := ctx.registry.SubscribeNotifications(ctxb, 0, 0)
|
allSubscriptions, err := ctx.registry.SubscribeNotifications(ctxb, 0, 0)
|
||||||
@@ -844,14 +962,16 @@ func testHoldKeysend(t *testing.T, timeoutKeysend bool) {
|
|||||||
require.Equal(t, settledInvoice.State, invpkg.ContractSettled)
|
require.Equal(t, settledInvoice.State, invpkg.ContractSettled)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestMppPayment tests settling of an invoice with multiple partial payments.
|
// testMppPayment tests settling of an invoice with multiple partial payments.
|
||||||
// It covers the case where there is a mpp timeout before the whole invoice is
|
// It covers the case where there is a mpp timeout before the whole invoice is
|
||||||
// paid and the case where the invoice is settled in time.
|
// paid and the case where the invoice is settled in time.
|
||||||
func TestMppPayment(t *testing.T) {
|
func testMppPayment(t *testing.T,
|
||||||
|
makeDB func(t *testing.T) (invpkg.InvoiceDB, *clock.TestClock)) {
|
||||||
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
defer timeout()()
|
defer timeout()()
|
||||||
|
|
||||||
ctx := newTestContext(t, nil)
|
ctx := newTestContext(t, nil, makeDB)
|
||||||
ctxb := context.Background()
|
ctxb := context.Background()
|
||||||
|
|
||||||
// Add the invoice.
|
// Add the invoice.
|
||||||
@@ -940,15 +1060,17 @@ func TestMppPayment(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestMppPaymentWithOverpayment tests settling of an invoice with multiple
|
// testMppPaymentWithOverpayment tests settling of an invoice with multiple
|
||||||
// partial payments. It covers the case where the mpp overpays what is in the
|
// partial payments. It covers the case where the mpp overpays what is in the
|
||||||
// invoice.
|
// invoice.
|
||||||
func TestMppPaymentWithOverpayment(t *testing.T) {
|
func testMppPaymentWithOverpayment(t *testing.T,
|
||||||
|
makeDB func(t *testing.T) (invpkg.InvoiceDB, *clock.TestClock)) {
|
||||||
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
ctxb := context.Background()
|
ctxb := context.Background()
|
||||||
f := func(overpaymentRand uint64) bool {
|
f := func(overpaymentRand uint64) bool {
|
||||||
ctx := newTestContext(t, nil)
|
ctx := newTestContext(t, nil, makeDB)
|
||||||
|
|
||||||
// Add the invoice.
|
// Add the invoice.
|
||||||
testInvoice := newInvoice(t, false)
|
testInvoice := newInvoice(t, false)
|
||||||
@@ -1017,13 +1139,13 @@ func TestMppPaymentWithOverpayment(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests that invoices are canceled after expiration.
|
// testInvoiceExpiryWithRegistry tests that invoices are canceled after
|
||||||
func TestInvoiceExpiryWithRegistry(t *testing.T) {
|
// expiration.
|
||||||
t.Parallel()
|
func testInvoiceExpiryWithRegistry(t *testing.T,
|
||||||
|
makeDB func(t *testing.T) (invpkg.InvoiceDB, *clock.TestClock)) {
|
||||||
|
|
||||||
testClock := clock.NewTestClock(testTime)
|
t.Parallel()
|
||||||
idb, err := newTestChannelDB(t, testClock)
|
idb, testClock := makeDB(t)
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
cfg := invpkg.RegistryConfig{
|
cfg := invpkg.RegistryConfig{
|
||||||
FinalCltvRejectDelta: testFinalCltvRejectDelta,
|
FinalCltvRejectDelta: testFinalCltvRejectDelta,
|
||||||
@@ -1119,21 +1241,20 @@ func TestInvoiceExpiryWithRegistry(t *testing.T) {
|
|||||||
|
|
||||||
// Retrospectively check that all invoices that were expected to be
|
// Retrospectively check that all invoices that were expected to be
|
||||||
// canceled are indeed canceled.
|
// canceled are indeed canceled.
|
||||||
err = wait.NoError(canceled, testTimeout)
|
err := wait.NoError(canceled, testTimeout)
|
||||||
require.NoError(t, err, "timeout checking invoice state")
|
require.NoError(t, err, "timeout checking invoice state")
|
||||||
|
|
||||||
// Finally stop the registry.
|
// Finally stop the registry.
|
||||||
require.NoError(t, registry.Stop(), "failed to stop invoice registry")
|
require.NoError(t, registry.Stop(), "failed to stop invoice registry")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestOldInvoiceRemovalOnStart tests that we'll attempt to remove old canceled
|
// testOldInvoiceRemovalOnStart tests that we'll attempt to remove old canceled
|
||||||
// invoices upon start while keeping all settled ones.
|
// invoices upon start while keeping all settled ones.
|
||||||
func TestOldInvoiceRemovalOnStart(t *testing.T) {
|
func testOldInvoiceRemovalOnStart(t *testing.T,
|
||||||
t.Parallel()
|
makeDB func(t *testing.T) (invpkg.InvoiceDB, *clock.TestClock)) {
|
||||||
|
|
||||||
testClock := clock.NewTestClock(testTime)
|
t.Parallel()
|
||||||
idb, err := newTestChannelDB(t, testClock)
|
idb, testClock := makeDB(t)
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
cfg := invpkg.RegistryConfig{
|
cfg := invpkg.RegistryConfig{
|
||||||
FinalCltvRejectDelta: testFinalCltvRejectDelta,
|
FinalCltvRejectDelta: testFinalCltvRejectDelta,
|
||||||
@@ -1203,35 +1324,39 @@ func TestOldInvoiceRemovalOnStart(t *testing.T) {
|
|||||||
require.Equal(t, expected, response.Invoices)
|
require.Equal(t, expected, response.Invoices)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestHeightExpiryWithRegistry tests our height-based invoice expiry for
|
// testHeightExpiryWithRegistry tests our height-based invoice expiry for
|
||||||
// invoices paid with single and multiple htlcs, testing the case where the
|
// invoices paid with single and multiple htlcs, testing the case where the
|
||||||
// invoice is settled before expiry (and thus not canceled), and the case
|
// invoice is settled before expiry (and thus not canceled), and the case
|
||||||
// where the invoice is expired.
|
// where the invoice is expired.
|
||||||
func TestHeightExpiryWithRegistry(t *testing.T) {
|
func testHeightExpiryWithRegistry(t *testing.T,
|
||||||
|
makeDB func(t *testing.T) (invpkg.InvoiceDB, *clock.TestClock)) {
|
||||||
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
t.Run("single shot settled before expiry", func(t *testing.T) {
|
t.Run("single shot settled before expiry", func(t *testing.T) {
|
||||||
testHeightExpiryWithRegistry(t, 1, true)
|
testHeightExpiryWithRegistryImpl(t, 1, true, makeDB)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("single shot expires", func(t *testing.T) {
|
t.Run("single shot expires", func(t *testing.T) {
|
||||||
testHeightExpiryWithRegistry(t, 1, false)
|
testHeightExpiryWithRegistryImpl(t, 1, false, makeDB)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("mpp settled before expiry", func(t *testing.T) {
|
t.Run("mpp settled before expiry", func(t *testing.T) {
|
||||||
testHeightExpiryWithRegistry(t, 2, true)
|
testHeightExpiryWithRegistryImpl(t, 2, true, makeDB)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("mpp expires", func(t *testing.T) {
|
t.Run("mpp expires", func(t *testing.T) {
|
||||||
testHeightExpiryWithRegistry(t, 2, false)
|
testHeightExpiryWithRegistryImpl(t, 2, false, makeDB)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func testHeightExpiryWithRegistry(t *testing.T, numParts int, settle bool) {
|
func testHeightExpiryWithRegistryImpl(t *testing.T, numParts int, settle bool,
|
||||||
|
makeDB func(t *testing.T) (invpkg.InvoiceDB, *clock.TestClock)) {
|
||||||
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
defer timeout()()
|
defer timeout()()
|
||||||
|
|
||||||
ctx := newTestContext(t, nil)
|
ctx := newTestContext(t, nil, makeDB)
|
||||||
|
|
||||||
require.Greater(t, numParts, 0, "test requires at least one part")
|
require.Greater(t, numParts, 0, "test requires at least one part")
|
||||||
|
|
||||||
@@ -1337,15 +1462,17 @@ func testHeightExpiryWithRegistry(t *testing.T, numParts int, settle bool) {
|
|||||||
"hold invoice: %v, got: %v", expectedState, inv.State)
|
"hold invoice: %v, got: %v", expectedState, inv.State)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestMultipleSetHeightExpiry pays a hold invoice with two mpp sets, testing
|
// testMultipleSetHeightExpiry pays a hold invoice with two mpp sets, testing
|
||||||
// that the invoice expiry watcher only uses the expiry height of the second,
|
// that the invoice expiry watcher only uses the expiry height of the second,
|
||||||
// successful set to cancel the invoice, and does not cancel early using the
|
// successful set to cancel the invoice, and does not cancel early using the
|
||||||
// expiry height of the first set that was canceled back due to mpp timeout.
|
// expiry height of the first set that was canceled back due to mpp timeout.
|
||||||
func TestMultipleSetHeightExpiry(t *testing.T) {
|
func testMultipleSetHeightExpiry(t *testing.T,
|
||||||
|
makeDB func(t *testing.T) (invpkg.InvoiceDB, *clock.TestClock)) {
|
||||||
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
defer timeout()()
|
defer timeout()()
|
||||||
|
|
||||||
ctx := newTestContext(t, nil)
|
ctx := newTestContext(t, nil, makeDB)
|
||||||
|
|
||||||
// Add a hold invoice.
|
// Add a hold invoice.
|
||||||
testInvoice := newInvoice(t, true)
|
testInvoice := newInvoice(t, true)
|
||||||
@@ -1431,13 +1558,15 @@ func TestMultipleSetHeightExpiry(t *testing.T) {
|
|||||||
}, testTimeout, time.Millisecond*100, "invoice not canceled")
|
}, testTimeout, time.Millisecond*100, "invoice not canceled")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestSettleInvoicePaymentAddrRequired tests that if an incoming payment has
|
// testSettleInvoicePaymentAddrRequired tests that if an incoming payment has
|
||||||
// an invoice that requires the payment addr bit to be set, and the incoming
|
// an invoice that requires the payment addr bit to be set, and the incoming
|
||||||
// payment doesn't include an mpp payload, then the payment is rejected.
|
// payment doesn't include an mpp payload, then the payment is rejected.
|
||||||
func TestSettleInvoicePaymentAddrRequired(t *testing.T) {
|
func testSettleInvoicePaymentAddrRequired(t *testing.T,
|
||||||
|
makeDB func(t *testing.T) (invpkg.InvoiceDB, *clock.TestClock)) {
|
||||||
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
ctx := newTestContext(t, nil)
|
ctx := newTestContext(t, nil, makeDB)
|
||||||
ctxb := context.Background()
|
ctxb := context.Background()
|
||||||
|
|
||||||
allSubscriptions, err := ctx.registry.SubscribeNotifications(ctxb, 0, 0)
|
allSubscriptions, err := ctx.registry.SubscribeNotifications(ctxb, 0, 0)
|
||||||
@@ -1519,14 +1648,16 @@ func TestSettleInvoicePaymentAddrRequired(t *testing.T) {
|
|||||||
require.Equal(t, failResolution.Outcome, invpkg.ResultAddressMismatch)
|
require.Equal(t, failResolution.Outcome, invpkg.ResultAddressMismatch)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestSettleInvoicePaymentAddrRequiredOptionalGrace tests that if an invoice
|
// testSettleInvoicePaymentAddrRequiredOptionalGrace tests that if an invoice
|
||||||
// in the database has an optional payment addr required bit set, then we'll
|
// in the database has an optional payment addr required bit set, then we'll
|
||||||
// still allow it to be paid by an incoming HTLC that doesn't include the MPP
|
// still allow it to be paid by an incoming HTLC that doesn't include the MPP
|
||||||
// payload. This ensures we don't break payment for any invoices in the wild.
|
// payload. This ensures we don't break payment for any invoices in the wild.
|
||||||
func TestSettleInvoicePaymentAddrRequiredOptionalGrace(t *testing.T) {
|
func testSettleInvoicePaymentAddrRequiredOptionalGrace(t *testing.T,
|
||||||
|
makeDB func(t *testing.T) (invpkg.InvoiceDB, *clock.TestClock)) {
|
||||||
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
ctx := newTestContext(t, nil)
|
ctx := newTestContext(t, nil, makeDB)
|
||||||
ctxb := context.Background()
|
ctxb := context.Background()
|
||||||
|
|
||||||
allSubscriptions, err := ctx.registry.SubscribeNotifications(ctxb, 0, 0)
|
allSubscriptions, err := ctx.registry.SubscribeNotifications(ctxb, 0, 0)
|
||||||
@@ -1633,15 +1764,17 @@ func TestSettleInvoicePaymentAddrRequiredOptionalGrace(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestAMPWithoutMPPPayload asserts that we correctly reject an AMP HTLC that
|
// testAMPWithoutMPPPayload asserts that we correctly reject an AMP HTLC that
|
||||||
// does not include an MPP record.
|
// does not include an MPP record.
|
||||||
func TestAMPWithoutMPPPayload(t *testing.T) {
|
func testAMPWithoutMPPPayload(t *testing.T,
|
||||||
|
makeDB func(t *testing.T) (invpkg.InvoiceDB, *clock.TestClock)) {
|
||||||
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
defer timeout()()
|
defer timeout()()
|
||||||
|
|
||||||
cfg := defaultRegistryConfig()
|
cfg := defaultRegistryConfig()
|
||||||
cfg.AcceptAMP = true
|
cfg.AcceptAMP = true
|
||||||
ctx := newTestContext(t, &cfg)
|
ctx := newTestContext(t, &cfg, makeDB)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
shardAmt = lnwire.MilliSatoshi(10)
|
shardAmt = lnwire.MilliSatoshi(10)
|
||||||
@@ -1666,9 +1799,11 @@ func TestAMPWithoutMPPPayload(t *testing.T) {
|
|||||||
checkFailResolution(t, resolution, invpkg.ResultAmpError)
|
checkFailResolution(t, resolution, invpkg.ResultAmpError)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestSpontaneousAmpPayment tests receiving a spontaneous AMP payment with both
|
// testSpontaneousAmpPayment tests receiving a spontaneous AMP payment with both
|
||||||
// valid and invalid reconstructions.
|
// valid and invalid reconstructions.
|
||||||
func TestSpontaneousAmpPayment(t *testing.T) {
|
func testSpontaneousAmpPayment(t *testing.T,
|
||||||
|
makeDB func(t *testing.T) (invpkg.InvoiceDB, *clock.TestClock)) {
|
||||||
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
@@ -1712,24 +1847,25 @@ func TestSpontaneousAmpPayment(t *testing.T) {
|
|||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
test := test
|
test := test
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
testSpontaneousAmpPayment(
|
testSpontaneousAmpPaymentImpl(
|
||||||
t, test.ampEnabled, test.failReconstruction,
|
t, test.ampEnabled, test.failReconstruction,
|
||||||
test.numShards,
|
test.numShards, makeDB,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// testSpontaneousAmpPayment runs a specific spontaneous AMP test case.
|
// testSpontaneousAmpPayment runs a specific spontaneous AMP test case.
|
||||||
func testSpontaneousAmpPayment(
|
func testSpontaneousAmpPaymentImpl(
|
||||||
t *testing.T, ampEnabled, failReconstruction bool, numShards int) {
|
t *testing.T, ampEnabled, failReconstruction bool, numShards int,
|
||||||
|
makeDB func(t *testing.T) (invpkg.InvoiceDB, *clock.TestClock)) {
|
||||||
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
defer timeout()()
|
defer timeout()()
|
||||||
|
|
||||||
cfg := defaultRegistryConfig()
|
cfg := defaultRegistryConfig()
|
||||||
cfg.AcceptAMP = ampEnabled
|
cfg.AcceptAMP = ampEnabled
|
||||||
ctx := newTestContext(t, &cfg)
|
ctx := newTestContext(t, &cfg, makeDB)
|
||||||
ctxb := context.Background()
|
ctxb := context.Background()
|
||||||
|
|
||||||
allSubscriptions, err := ctx.registry.SubscribeNotifications(ctxb, 0, 0)
|
allSubscriptions, err := ctx.registry.SubscribeNotifications(ctxb, 0, 0)
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,11 @@
|
|||||||
|
package invoices
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/lightningnetwork/lnd/kvdb"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
kvdb.RunTests(m)
|
||||||
|
}
|
||||||
@@ -16,7 +16,6 @@ import (
|
|||||||
"github.com/btcsuite/btcd/chaincfg"
|
"github.com/btcsuite/btcd/chaincfg"
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
|
||||||
"github.com/lightningnetwork/lnd/clock"
|
"github.com/lightningnetwork/lnd/clock"
|
||||||
invpkg "github.com/lightningnetwork/lnd/invoices"
|
invpkg "github.com/lightningnetwork/lnd/invoices"
|
||||||
"github.com/lightningnetwork/lnd/lntypes"
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
@@ -131,26 +130,8 @@ var (
|
|||||||
testInvoiceCreationDate = testTime
|
testInvoiceCreationDate = testTime
|
||||||
)
|
)
|
||||||
|
|
||||||
func newTestChannelDB(t *testing.T, clock clock.Clock) (*channeldb.DB, error) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
// Create channeldb for the first time.
|
|
||||||
cdb, err := channeldb.Open(
|
|
||||||
t.TempDir(), channeldb.OptionClock(clock),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Cleanup(func() {
|
|
||||||
cdb.Close()
|
|
||||||
})
|
|
||||||
|
|
||||||
return cdb, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type testContext struct {
|
type testContext struct {
|
||||||
idb *channeldb.DB
|
idb invpkg.InvoiceDB
|
||||||
registry *invpkg.InvoiceRegistry
|
registry *invpkg.InvoiceRegistry
|
||||||
notifier *mockChainNotifier
|
notifier *mockChainNotifier
|
||||||
clock *clock.TestClock
|
clock *clock.TestClock
|
||||||
@@ -166,17 +147,13 @@ func defaultRegistryConfig() invpkg.RegistryConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func newTestContext(t *testing.T,
|
func newTestContext(t *testing.T,
|
||||||
registryCfg *invpkg.RegistryConfig) *testContext {
|
registryCfg *invpkg.RegistryConfig,
|
||||||
|
makeDB func(t *testing.T) (invpkg.InvoiceDB,
|
||||||
|
*clock.TestClock)) *testContext {
|
||||||
|
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
clock := clock.NewTestClock(testTime)
|
idb, clock := makeDB(t)
|
||||||
|
|
||||||
idb, err := newTestChannelDB(t, clock)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
notifier := newMockNotifier()
|
notifier := newMockNotifier()
|
||||||
|
|
||||||
expiryWatcher := invpkg.NewInvoiceExpiryWatcher(
|
expiryWatcher := invpkg.NewInvoiceExpiryWatcher(
|
||||||
@@ -192,10 +169,7 @@ func newTestContext(t *testing.T,
|
|||||||
// Instantiate and start the invoice ctx.registry.
|
// Instantiate and start the invoice ctx.registry.
|
||||||
registry := invpkg.NewRegistry(idb, expiryWatcher, &cfg)
|
registry := invpkg.NewRegistry(idb, expiryWatcher, &cfg)
|
||||||
|
|
||||||
err = registry.Start()
|
require.NoError(t, registry.Start())
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
require.NoError(t, registry.Stop())
|
require.NoError(t, registry.Stop())
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,826 @@
|
|||||||
|
package invoices
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||||
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
|
)
|
||||||
|
|
||||||
|
// updateHtlcsAmp takes an invoice, and a new HTLC to be added (along with its
|
||||||
|
// set ID), and updates the internal AMP state of an invoice, and also tallies
|
||||||
|
// the set of HTLCs to be updated on disk.
|
||||||
|
func acceptHtlcsAmp(invoice *Invoice, setID SetID,
|
||||||
|
circuitKey models.CircuitKey, htlc *InvoiceHTLC,
|
||||||
|
updater InvoiceUpdater) error {
|
||||||
|
|
||||||
|
newAmpState, err := getUpdatedInvoiceAmpState(
|
||||||
|
invoice, setID, circuitKey, HtlcStateAccepted, htlc.Amt,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
invoice.AMPState[setID] = newAmpState
|
||||||
|
|
||||||
|
// Mark the updates as needing to be written to disk.
|
||||||
|
return updater.UpdateAmpState(setID, newAmpState, circuitKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// cancelHtlcsAmp processes a cancellation of an HTLC that belongs to an AMP
|
||||||
|
// HTLC set. We'll need to update the meta data in the main invoice, and also
|
||||||
|
// apply the new update to the update MAP, since all the HTLCs for a given HTLC
|
||||||
|
// set need to be written in-line with each other.
|
||||||
|
func cancelHtlcsAmp(invoice *Invoice, circuitKey models.CircuitKey,
|
||||||
|
htlc *InvoiceHTLC, updater InvoiceUpdater) error {
|
||||||
|
|
||||||
|
setID := htlc.AMP.Record.SetID()
|
||||||
|
|
||||||
|
// First, we'll update the state of the entire HTLC set
|
||||||
|
// to cancelled.
|
||||||
|
newAmpState, err := getUpdatedInvoiceAmpState(
|
||||||
|
invoice, setID, circuitKey, HtlcStateCanceled,
|
||||||
|
htlc.Amt,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
invoice.AMPState[setID] = newAmpState
|
||||||
|
|
||||||
|
// Mark the updates as needing to be written to disk.
|
||||||
|
err = updater.UpdateAmpState(setID, newAmpState, circuitKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// We'll only decrement the total amount paid if the invoice was
|
||||||
|
// already in the accepted state.
|
||||||
|
if invoice.AmtPaid != 0 {
|
||||||
|
return updateInvoiceAmtPaid(
|
||||||
|
invoice, invoice.AmtPaid-htlc.Amt, updater,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// settleHtlcsAmp processes a new settle operation on an HTLC set for an AMP
|
||||||
|
// invoice. We'll update some meta data in the main invoice, and also signal
|
||||||
|
// that this HTLC set needs to be re-written back to disk.
|
||||||
|
func settleHtlcsAmp(invoice *Invoice, circuitKey models.CircuitKey,
|
||||||
|
htlc *InvoiceHTLC, updater InvoiceUpdater) error {
|
||||||
|
|
||||||
|
setID := htlc.AMP.Record.SetID()
|
||||||
|
|
||||||
|
// Next update the main AMP meta-data to indicate that this HTLC set
|
||||||
|
// has been fully settled.
|
||||||
|
newAmpState, err := getUpdatedInvoiceAmpState(
|
||||||
|
invoice, setID, circuitKey, HtlcStateSettled, 0,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
invoice.AMPState[setID] = newAmpState
|
||||||
|
|
||||||
|
// Mark the updates as needing to be written to disk.
|
||||||
|
return updater.UpdateAmpState(setID, newAmpState, circuitKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateInvoice fetches the invoice, obtains the update descriptor from the
|
||||||
|
// callback and applies the updates in a single db transaction.
|
||||||
|
func UpdateInvoice(hash *lntypes.Hash, invoice *Invoice,
|
||||||
|
updateTime time.Time, callback InvoiceUpdateCallback,
|
||||||
|
updater InvoiceUpdater) (*Invoice, error) {
|
||||||
|
|
||||||
|
// Create deep copy to prevent any accidental modification in the
|
||||||
|
// callback.
|
||||||
|
invoiceCopy, err := CopyInvoice(invoice)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the callback and obtain the update descriptor.
|
||||||
|
update, err := callback(invoiceCopy)
|
||||||
|
if err != nil {
|
||||||
|
return invoice, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is nothing to update, return early.
|
||||||
|
if update == nil {
|
||||||
|
return invoice, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch update.UpdateType {
|
||||||
|
case CancelHTLCsUpdate:
|
||||||
|
err := cancelHTLCs(invoice, updateTime, update, updater)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
case AddHTLCsUpdate:
|
||||||
|
err := addHTLCs(invoice, hash, updateTime, update, updater)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
case SettleHodlInvoiceUpdate:
|
||||||
|
err := settleHodlInvoice(
|
||||||
|
invoice, hash, updateTime, update.State, updater,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
case CancelInvoiceUpdate:
|
||||||
|
err := cancelInvoice(
|
||||||
|
invoice, hash, updateTime, update.State, updater,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unknown update type: %s",
|
||||||
|
update.UpdateType)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := updater.Finalize(update.UpdateType); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return invoice, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// cancelHTLCs tries to cancel the htlcs in the given InvoiceUpdateDesc.
|
||||||
|
//
|
||||||
|
// NOTE: cancelHTLCs updates will only use the `CancelHtlcs` field in the
|
||||||
|
// InvoiceUpdateDesc.
|
||||||
|
func cancelHTLCs(invoice *Invoice, updateTime time.Time,
|
||||||
|
update *InvoiceUpdateDesc, updater InvoiceUpdater) error {
|
||||||
|
|
||||||
|
for key := range update.CancelHtlcs {
|
||||||
|
htlc, exists := invoice.Htlcs[key]
|
||||||
|
|
||||||
|
// Verify that we don't get an action for htlcs that are not
|
||||||
|
// present on the invoice.
|
||||||
|
if !exists {
|
||||||
|
return fmt.Errorf("cancel of non-existent htlc")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := canCancelSingleHtlc(htlc, invoice.State)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = resolveHtlc(
|
||||||
|
key, htlc, HtlcStateCanceled, updateTime,
|
||||||
|
updater,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tally this into the set of HTLCs that need to be updated on
|
||||||
|
// disk, but once again, only if this is an AMP invoice.
|
||||||
|
if invoice.IsAMP() {
|
||||||
|
err := cancelHtlcsAmp(invoice, key, htlc, updater)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// addHTLCs tries to add the htlcs in the given InvoiceUpdateDesc.
|
||||||
|
//
|
||||||
|
//nolint:funlen
|
||||||
|
func addHTLCs(invoice *Invoice, hash *lntypes.Hash, updateTime time.Time,
|
||||||
|
update *InvoiceUpdateDesc, updater InvoiceUpdater) error {
|
||||||
|
|
||||||
|
var setID *[32]byte
|
||||||
|
invoiceIsAMP := invoice.IsAMP()
|
||||||
|
if invoiceIsAMP && update.State != nil {
|
||||||
|
setID = update.State.SetID
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, htlcUpdate := range update.AddHtlcs {
|
||||||
|
if _, exists := invoice.Htlcs[key]; exists {
|
||||||
|
return fmt.Errorf("duplicate add of htlc %v", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force caller to supply htlc without custom records in a
|
||||||
|
// consistent way.
|
||||||
|
if htlcUpdate.CustomRecords == nil {
|
||||||
|
return errors.New("nil custom records map")
|
||||||
|
}
|
||||||
|
|
||||||
|
htlc := &InvoiceHTLC{
|
||||||
|
Amt: htlcUpdate.Amt,
|
||||||
|
MppTotalAmt: htlcUpdate.MppTotalAmt,
|
||||||
|
Expiry: htlcUpdate.Expiry,
|
||||||
|
AcceptHeight: uint32(htlcUpdate.AcceptHeight),
|
||||||
|
AcceptTime: updateTime,
|
||||||
|
State: HtlcStateAccepted,
|
||||||
|
CustomRecords: htlcUpdate.CustomRecords,
|
||||||
|
}
|
||||||
|
|
||||||
|
if invoiceIsAMP {
|
||||||
|
if htlcUpdate.AMP == nil {
|
||||||
|
return fmt.Errorf("unable to add htlc "+
|
||||||
|
"without AMP data to AMP invoice(%v)",
|
||||||
|
invoice.AddIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
htlc.AMP = htlcUpdate.AMP.Copy()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := updater.AddHtlc(key, htlc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
invoice.Htlcs[key] = htlc
|
||||||
|
|
||||||
|
// Collect the set of new HTLCs so we can write them properly
|
||||||
|
// below, but only if this is an AMP invoice.
|
||||||
|
if invoiceIsAMP {
|
||||||
|
err := acceptHtlcsAmp(
|
||||||
|
invoice, htlcUpdate.AMP.Record.SetID(), key,
|
||||||
|
htlc, updater,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point, the set of accepted HTLCs should be fully
|
||||||
|
// populated with added HTLCs or removed of canceled ones. Update
|
||||||
|
// invoice state if the update descriptor indicates an invoice state
|
||||||
|
// change, which depends on having an accurate view of the accepted
|
||||||
|
// HTLCs.
|
||||||
|
if update.State != nil {
|
||||||
|
newState, err := getUpdatedInvoiceState(
|
||||||
|
invoice, hash, *update.State,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this isn't an AMP invoice, then we'll go ahead and update
|
||||||
|
// the invoice state directly here. For AMP invoices, we instead
|
||||||
|
// will keep the top-level invoice open, and update the state of
|
||||||
|
// each _htlc set_ instead. However, we'll allow the invoice to
|
||||||
|
// transition to the cancelled state regardless.
|
||||||
|
if !invoiceIsAMP || *newState == ContractCanceled {
|
||||||
|
err := updater.UpdateInvoiceState(*newState, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
invoice.State = *newState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The set of HTLC pre-images will only be set if we were actually able
|
||||||
|
// to reconstruct all the AMP pre-images.
|
||||||
|
var settleEligibleAMP bool
|
||||||
|
if update.State != nil {
|
||||||
|
settleEligibleAMP = len(update.State.HTLCPreimages) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// With any invoice level state transitions recorded, we'll now
|
||||||
|
// finalize the process by updating the state transitions for
|
||||||
|
// individual HTLCs
|
||||||
|
var amtPaid lnwire.MilliSatoshi
|
||||||
|
|
||||||
|
for key, htlc := range invoice.Htlcs {
|
||||||
|
// Set the HTLC preimage for any AMP HTLCs.
|
||||||
|
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:
|
||||||
|
err := updater.AddAmpHtlcPreimage(
|
||||||
|
htlc.AMP.Record.SetID(), key, preimage,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
htlc.AMP.Preimage = &preimage
|
||||||
|
|
||||||
|
// Otherwise, prevent over-writing an existing
|
||||||
|
// preimage. Ignore the case where the preimage is
|
||||||
|
// identical.
|
||||||
|
case ok && *htlc.AMP.Preimage != preimage:
|
||||||
|
return ErrHTLCPreimageAlreadyExists
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The invoice state may have changed and this could have
|
||||||
|
// implications for the states of the individual htlcs. Align
|
||||||
|
// the htlc state with the current invoice state.
|
||||||
|
//
|
||||||
|
// If we have all the pre-images for an AMP invoice, then we'll
|
||||||
|
// act as if we're able to settle the entire invoice. We need
|
||||||
|
// to do this since it's possible for us to settle AMP invoices
|
||||||
|
// while the contract state (on disk) is still in the accept
|
||||||
|
// state.
|
||||||
|
htlcContextState := invoice.State
|
||||||
|
if settleEligibleAMP {
|
||||||
|
htlcContextState = ContractSettled
|
||||||
|
}
|
||||||
|
htlcStateChanged, htlcState, err := getUpdatedHtlcState(
|
||||||
|
htlc, htlcContextState, setID,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if htlcStateChanged {
|
||||||
|
err = resolveHtlc(
|
||||||
|
key, htlc, htlcState, updateTime, updater,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
htlcSettled := htlcStateChanged &&
|
||||||
|
htlcState == HtlcStateSettled
|
||||||
|
|
||||||
|
// If the HTLC has being settled for the first time, and this
|
||||||
|
// is an AMP invoice, then we'll need to update some additional
|
||||||
|
// meta data state.
|
||||||
|
if htlcSettled && invoiceIsAMP {
|
||||||
|
err = settleHtlcsAmp(invoice, key, htlc, updater)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
accepted := htlc.State == HtlcStateAccepted
|
||||||
|
settled := htlc.State == HtlcStateSettled
|
||||||
|
invoiceStateReady := accepted || settled
|
||||||
|
|
||||||
|
if !invoiceIsAMP {
|
||||||
|
// Update the running amount paid to this invoice. We
|
||||||
|
// don't include accepted htlcs when the invoice is
|
||||||
|
// still open.
|
||||||
|
if invoice.State != ContractOpen &&
|
||||||
|
invoiceStateReady {
|
||||||
|
|
||||||
|
amtPaid += htlc.Amt
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// For AMP invoices, since we won't always be reading
|
||||||
|
// out the total invoice set each time, we'll instead
|
||||||
|
// accumulate newly added invoices to the total amount
|
||||||
|
// paid.
|
||||||
|
if _, ok := update.AddHtlcs[key]; !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the running amount paid to this invoice. AMP
|
||||||
|
// invoices never go to the settled state, so if it's
|
||||||
|
// open, then we tally the HTLC.
|
||||||
|
if invoice.State == ContractOpen &&
|
||||||
|
invoiceStateReady {
|
||||||
|
|
||||||
|
amtPaid += htlc.Amt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For non-AMP invoices we recalculate the amount paid from scratch
|
||||||
|
// each time, while for AMP invoices, we'll accumulate only based on
|
||||||
|
// newly added HTLCs.
|
||||||
|
if invoiceIsAMP {
|
||||||
|
amtPaid += invoice.AmtPaid
|
||||||
|
}
|
||||||
|
|
||||||
|
return updateInvoiceAmtPaid(invoice, amtPaid, updater)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveHtlc(circuitKey models.CircuitKey, htlc *InvoiceHTLC,
|
||||||
|
state HtlcState, resolveTime time.Time,
|
||||||
|
updater InvoiceUpdater) error {
|
||||||
|
|
||||||
|
err := updater.ResolveHtlc(circuitKey, state, resolveTime)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
htlc.State = state
|
||||||
|
htlc.ResolveTime = resolveTime
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateInvoiceAmtPaid(invoice *Invoice, amt lnwire.MilliSatoshi,
|
||||||
|
updater InvoiceUpdater) error {
|
||||||
|
|
||||||
|
err := updater.UpdateInvoiceAmtPaid(amt)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
invoice.AmtPaid = amt
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// settleHodlInvoice marks a hodl invoice as settled.
|
||||||
|
//
|
||||||
|
// NOTE: Currently it is not possible to have HODL AMP invoices.
|
||||||
|
func settleHodlInvoice(invoice *Invoice, hash *lntypes.Hash,
|
||||||
|
updateTime time.Time, update *InvoiceStateUpdateDesc,
|
||||||
|
updater InvoiceUpdater) error {
|
||||||
|
|
||||||
|
if !invoice.HodlInvoice {
|
||||||
|
return fmt.Errorf("unable to settle hodl invoice: %v is "+
|
||||||
|
"not a hodl invoice", invoice.AddIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(positiveblue): because NewState can only be ContractSettled we
|
||||||
|
// can remove it from the API and set it here directly.
|
||||||
|
switch {
|
||||||
|
case update == nil:
|
||||||
|
fallthrough
|
||||||
|
|
||||||
|
case update.NewState != ContractSettled:
|
||||||
|
return fmt.Errorf("unable to settle hodl invoice: "+
|
||||||
|
"not valid InvoiceUpdateDesc.State: %v", update)
|
||||||
|
|
||||||
|
case update.Preimage == nil:
|
||||||
|
return fmt.Errorf("unable to settle hodl invoice: " +
|
||||||
|
"preimage is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
newState, err := getUpdatedInvoiceState(
|
||||||
|
invoice, hash, *update,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if newState == nil || *newState != ContractSettled {
|
||||||
|
return fmt.Errorf("unable to settle hodl invoice: "+
|
||||||
|
"new computed state is not settled: %s", newState)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = updater.UpdateInvoiceState(
|
||||||
|
ContractSettled, update.Preimage,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
invoice.State = ContractSettled
|
||||||
|
invoice.Terms.PaymentPreimage = update.Preimage
|
||||||
|
|
||||||
|
// TODO(positiveblue): this logic can be further simplified.
|
||||||
|
var amtPaid lnwire.MilliSatoshi
|
||||||
|
for key, htlc := range invoice.Htlcs {
|
||||||
|
settled, _, err := getUpdatedHtlcState(
|
||||||
|
htlc, ContractSettled, nil,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if settled {
|
||||||
|
err = resolveHtlc(
|
||||||
|
key, htlc, HtlcStateSettled, updateTime,
|
||||||
|
updater,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
amtPaid += htlc.Amt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return updateInvoiceAmtPaid(invoice, amtPaid, updater)
|
||||||
|
}
|
||||||
|
|
||||||
|
// cancelInvoice attempts to cancel the given invoice. That includes changing
|
||||||
|
// the invoice state and the state of any relevant HTLC.
|
||||||
|
func cancelInvoice(invoice *Invoice, hash *lntypes.Hash,
|
||||||
|
updateTime time.Time, update *InvoiceStateUpdateDesc,
|
||||||
|
updater InvoiceUpdater) error {
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case update == nil:
|
||||||
|
fallthrough
|
||||||
|
|
||||||
|
case update.NewState != ContractCanceled:
|
||||||
|
return fmt.Errorf("unable to cancel invoice: "+
|
||||||
|
"InvoiceUpdateDesc.State not valid: %v", update)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
setID *[32]byte
|
||||||
|
invoiceIsAMP bool
|
||||||
|
)
|
||||||
|
|
||||||
|
invoiceIsAMP = invoice.IsAMP()
|
||||||
|
if invoiceIsAMP {
|
||||||
|
setID = update.SetID
|
||||||
|
}
|
||||||
|
|
||||||
|
newState, err := getUpdatedInvoiceState(invoice, hash, *update)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if newState == nil || *newState != ContractCanceled {
|
||||||
|
return fmt.Errorf("unable to cancel invoice(%v): new "+
|
||||||
|
"computed state is not canceled: %s", invoice.AddIndex,
|
||||||
|
newState)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = updater.UpdateInvoiceState(ContractCanceled, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
invoice.State = ContractCanceled
|
||||||
|
|
||||||
|
for key, htlc := range invoice.Htlcs {
|
||||||
|
canceled, _, err := getUpdatedHtlcState(
|
||||||
|
htlc, ContractCanceled, setID,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if canceled {
|
||||||
|
err = resolveHtlc(
|
||||||
|
key, htlc, HtlcStateCanceled, updateTime,
|
||||||
|
updater,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getUpdatedInvoiceState validates and processes an invoice state update. The
|
||||||
|
// new state to transition to is returned, so the caller is able to select
|
||||||
|
// exactly how the invoice state is updated. Note that for AMP invoices this
|
||||||
|
// function is only used to validate the state transition if we're cancelling
|
||||||
|
// the invoice.
|
||||||
|
func getUpdatedInvoiceState(invoice *Invoice, hash *lntypes.Hash,
|
||||||
|
update InvoiceStateUpdateDesc) (*ContractState, error) {
|
||||||
|
|
||||||
|
// Returning to open is never allowed from any state.
|
||||||
|
if update.NewState == ContractOpen {
|
||||||
|
return nil, ErrInvoiceCannotOpen
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
// same checks that we apply to open invoices.
|
||||||
|
case ContractAccepted:
|
||||||
|
if update.NewState == ContractAccepted {
|
||||||
|
return nil, ErrInvoiceCannotAccept
|
||||||
|
}
|
||||||
|
|
||||||
|
fallthrough
|
||||||
|
|
||||||
|
// If a contract is open, permit a state transition to accepted, settled
|
||||||
|
// or canceled. The only restriction is on transitioning to settled
|
||||||
|
// where we ensure the preimage is valid.
|
||||||
|
case ContractOpen:
|
||||||
|
if update.NewState == ContractCanceled {
|
||||||
|
return &update.NewState, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanity check that the user isn't trying to settle or accept a
|
||||||
|
// non-existent HTLC set.
|
||||||
|
set := invoice.HTLCSet(update.SetID, HtlcStateAccepted)
|
||||||
|
if len(set) == 0 {
|
||||||
|
return nil, ErrEmptyHTLCSet
|
||||||
|
}
|
||||||
|
|
||||||
|
// For AMP invoices, there are no invoice-level preimage checks.
|
||||||
|
// However, we still sanity check that we aren't trying to
|
||||||
|
// settle an AMP invoice with a preimage.
|
||||||
|
if update.SetID != nil {
|
||||||
|
if update.Preimage != nil {
|
||||||
|
return nil, errors.New("AMP set cannot have " +
|
||||||
|
"preimage")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &update.NewState, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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:
|
||||||
|
return nil, ErrUnexpectedInvoicePreimage
|
||||||
|
|
||||||
|
// Validate the supplied preimage for non-AMP invoices.
|
||||||
|
case update.Preimage != nil:
|
||||||
|
if update.Preimage.Hash() != *hash {
|
||||||
|
return nil, ErrInvoicePreimageMismatch
|
||||||
|
}
|
||||||
|
|
||||||
|
// Permit non-AMP invoices to be accepted without knowing the
|
||||||
|
// preimage. When trying to settle we'll have to pass through
|
||||||
|
// the above check in order to not hit the one below.
|
||||||
|
case update.NewState == ContractAccepted:
|
||||||
|
|
||||||
|
// Fail if we still don't have a preimage when transitioning to
|
||||||
|
// settle the non-AMP invoice.
|
||||||
|
case update.NewState == ContractSettled &&
|
||||||
|
invoice.Terms.PaymentPreimage == nil:
|
||||||
|
|
||||||
|
return nil, errors.New("unknown preimage")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &update.NewState, nil
|
||||||
|
|
||||||
|
// Once settled, we are in a terminal state.
|
||||||
|
case ContractSettled:
|
||||||
|
return nil, ErrInvoiceAlreadySettled
|
||||||
|
|
||||||
|
// Once canceled, we are in a terminal state.
|
||||||
|
case ContractCanceled:
|
||||||
|
return nil, ErrInvoiceAlreadyCanceled
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, errors.New("unknown state transition")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getUpdatedInvoiceAmpState returns the AMP state of an invoice (without
|
||||||
|
// applying it), given the new state, and the amount of the HTLC that is
|
||||||
|
// being updated.
|
||||||
|
func getUpdatedInvoiceAmpState(invoice *Invoice, setID SetID,
|
||||||
|
circuitKey models.CircuitKey, state HtlcState,
|
||||||
|
amt lnwire.MilliSatoshi) (InvoiceStateAMP, error) {
|
||||||
|
|
||||||
|
// Retrieve the AMP state for this set ID.
|
||||||
|
ampState, ok := invoice.AMPState[setID]
|
||||||
|
|
||||||
|
// If the state is accepted then we may need to create a new entry for
|
||||||
|
// this set ID, otherwise we expect that the entry already exists and
|
||||||
|
// we can update it.
|
||||||
|
if !ok && state != HtlcStateAccepted {
|
||||||
|
return InvoiceStateAMP{},
|
||||||
|
fmt.Errorf("unable to update AMP state for setID=%x ",
|
||||||
|
setID)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch state {
|
||||||
|
case HtlcStateAccepted:
|
||||||
|
if !ok {
|
||||||
|
// If an entry for this set ID doesn't already exist,
|
||||||
|
// then we'll need to create it.
|
||||||
|
ampState = InvoiceStateAMP{
|
||||||
|
State: HtlcStateAccepted,
|
||||||
|
InvoiceKeys: make(
|
||||||
|
map[models.CircuitKey]struct{},
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ampState.AmtPaid += amt
|
||||||
|
|
||||||
|
case HtlcStateCanceled:
|
||||||
|
ampState.State = HtlcStateCanceled
|
||||||
|
ampState.AmtPaid -= amt
|
||||||
|
|
||||||
|
case HtlcStateSettled:
|
||||||
|
ampState.State = HtlcStateSettled
|
||||||
|
}
|
||||||
|
|
||||||
|
ampState.InvoiceKeys[circuitKey] = struct{}{}
|
||||||
|
|
||||||
|
return ampState, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// canCancelSingleHtlc validates cancellation of a single HTLC. If nil is
|
||||||
|
// returned, then the HTLC can be cancelled.
|
||||||
|
func canCancelSingleHtlc(htlc *InvoiceHTLC,
|
||||||
|
invoiceState ContractState) error {
|
||||||
|
|
||||||
|
// It is only possible to cancel individual htlcs on an open invoice.
|
||||||
|
if invoiceState != ContractOpen {
|
||||||
|
return fmt.Errorf("htlc canceled on invoice in state %v",
|
||||||
|
invoiceState)
|
||||||
|
}
|
||||||
|
|
||||||
|
// It is only possible if the htlc is still pending.
|
||||||
|
if htlc.State != HtlcStateAccepted {
|
||||||
|
return fmt.Errorf("htlc canceled in state %v", htlc.State)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getUpdatedHtlcState aligns the state of an htlc with the given invoice state.
|
||||||
|
// A boolean indicating whether the HTLCs state need to be updated, along with
|
||||||
|
// the new state (or old state if no change is needed) is returned.
|
||||||
|
func getUpdatedHtlcState(htlc *InvoiceHTLC,
|
||||||
|
invoiceState ContractState, setID *[32]byte) (
|
||||||
|
bool, HtlcState, error) {
|
||||||
|
|
||||||
|
trySettle := func(persist bool) (bool, HtlcState, error) {
|
||||||
|
if htlc.State != HtlcStateAccepted {
|
||||||
|
return false, htlc.State, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Settle the HTLC if it matches the settled set id. If
|
||||||
|
// there're other HTLCs with distinct setIDs, then we'll leave
|
||||||
|
// them, as they may eventually be settled as we permit
|
||||||
|
// multiple settles to a single pay_addr for AMP.
|
||||||
|
settled := false
|
||||||
|
if htlc.IsInHTLCSet(setID) {
|
||||||
|
// Non-AMP HTLCs can be settled immediately since we
|
||||||
|
// already know the preimage is valid due to checks at
|
||||||
|
// 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.
|
||||||
|
case setID == nil:
|
||||||
|
|
||||||
|
// At this point, the setID is non-nil, meaning this is
|
||||||
|
// an AMP HTLC. We know that htlc.AMP cannot be nil,
|
||||||
|
// otherwise IsInHTLCSet would have returned false.
|
||||||
|
//
|
||||||
|
// Fail if an accepted AMP HTLC has no preimage.
|
||||||
|
case htlc.AMP.Preimage == nil:
|
||||||
|
return false, htlc.State,
|
||||||
|
ErrHTLCPreimageMissing
|
||||||
|
|
||||||
|
// Fail if the accepted AMP HTLC has an invalid
|
||||||
|
// preimage.
|
||||||
|
case !htlc.AMP.Preimage.Matches(htlc.AMP.Hash):
|
||||||
|
return false, htlc.State,
|
||||||
|
ErrHTLCPreimageMismatch
|
||||||
|
}
|
||||||
|
|
||||||
|
settled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only persist the changes if the invoice is moving to the
|
||||||
|
// settled state, and we're actually updating the state to
|
||||||
|
// settled.
|
||||||
|
newState := htlc.State
|
||||||
|
if settled {
|
||||||
|
newState = HtlcStateSettled
|
||||||
|
}
|
||||||
|
|
||||||
|
return persist && settled, newState, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if invoiceState == ContractSettled {
|
||||||
|
// Check that we can settle the HTLCs. For legacy and MPP HTLCs
|
||||||
|
// this will be a NOP, but for AMP HTLCs this asserts that we
|
||||||
|
// have a valid hash/preimage pair. Passing true permits the
|
||||||
|
// method to update the HTLC to HtlcStateSettled.
|
||||||
|
return trySettle(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We should never find a settled HTLC on an invoice that isn't in
|
||||||
|
// ContractSettled.
|
||||||
|
if htlc.State == HtlcStateSettled {
|
||||||
|
return false, htlc.State, ErrHTLCAlreadySettled
|
||||||
|
}
|
||||||
|
|
||||||
|
switch invoiceState {
|
||||||
|
case ContractCanceled:
|
||||||
|
htlcAlreadyCanceled := htlc.State == HtlcStateCanceled
|
||||||
|
return !htlcAlreadyCanceled, HtlcStateCanceled, nil
|
||||||
|
|
||||||
|
// TODO(roasbeef): never fully passed thru now?
|
||||||
|
case ContractAccepted:
|
||||||
|
// Check that we can settle the HTLCs. For legacy and MPP HTLCs
|
||||||
|
// this will be a NOP, but for AMP HTLCs this asserts that we
|
||||||
|
// have a valid hash/preimage pair. Passing false prevents the
|
||||||
|
// method from putting the HTLC in HtlcStateSettled, leaving it
|
||||||
|
// in HtlcStateAccepted.
|
||||||
|
return trySettle(false)
|
||||||
|
|
||||||
|
case ContractOpen:
|
||||||
|
return false, htlc.State, nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false, htlc.State, errors.New("unknown state transition")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,687 @@
|
|||||||
|
package invoices
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
|
"github.com/lightningnetwork/lnd/record"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
type updateHTLCTest struct {
|
||||||
|
name string
|
||||||
|
input InvoiceHTLC
|
||||||
|
invState ContractState
|
||||||
|
setID *[32]byte
|
||||||
|
output InvoiceHTLC
|
||||||
|
expErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestUpdateHTLC asserts the behavior of the updateHTLC method in various
|
||||||
|
// scenarios for MPP and AMP.
|
||||||
|
func TestUpdateHTLC(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testNow := time.Now()
|
||||||
|
setID := [32]byte{0x01}
|
||||||
|
ampRecord := record.NewAMP([32]byte{0x02}, setID, 3)
|
||||||
|
preimage := lntypes.Preimage{0x04}
|
||||||
|
hash := preimage.Hash()
|
||||||
|
|
||||||
|
diffSetID := [32]byte{0x05}
|
||||||
|
fakePreimage := lntypes.Preimage{0x06}
|
||||||
|
testAlreadyNow := time.Now()
|
||||||
|
|
||||||
|
tests := []updateHTLCTest{
|
||||||
|
{
|
||||||
|
name: "MPP accept",
|
||||||
|
input: InvoiceHTLC{
|
||||||
|
Amt: 5000,
|
||||||
|
MppTotalAmt: 5000,
|
||||||
|
AcceptHeight: 100,
|
||||||
|
AcceptTime: testNow,
|
||||||
|
ResolveTime: time.Time{},
|
||||||
|
Expiry: 40,
|
||||||
|
State: HtlcStateAccepted,
|
||||||
|
CustomRecords: make(record.CustomSet),
|
||||||
|
AMP: nil,
|
||||||
|
},
|
||||||
|
invState: ContractAccepted,
|
||||||
|
setID: nil,
|
||||||
|
output: InvoiceHTLC{
|
||||||
|
Amt: 5000,
|
||||||
|
MppTotalAmt: 5000,
|
||||||
|
AcceptHeight: 100,
|
||||||
|
AcceptTime: testNow,
|
||||||
|
ResolveTime: time.Time{},
|
||||||
|
Expiry: 40,
|
||||||
|
State: HtlcStateAccepted,
|
||||||
|
CustomRecords: make(record.CustomSet),
|
||||||
|
AMP: nil,
|
||||||
|
},
|
||||||
|
expErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "MPP settle",
|
||||||
|
input: InvoiceHTLC{
|
||||||
|
Amt: 5000,
|
||||||
|
MppTotalAmt: 5000,
|
||||||
|
AcceptHeight: 100,
|
||||||
|
AcceptTime: testNow,
|
||||||
|
ResolveTime: time.Time{},
|
||||||
|
Expiry: 40,
|
||||||
|
State: HtlcStateAccepted,
|
||||||
|
CustomRecords: make(record.CustomSet),
|
||||||
|
AMP: nil,
|
||||||
|
},
|
||||||
|
invState: ContractSettled,
|
||||||
|
setID: nil,
|
||||||
|
output: InvoiceHTLC{
|
||||||
|
Amt: 5000,
|
||||||
|
MppTotalAmt: 5000,
|
||||||
|
AcceptHeight: 100,
|
||||||
|
AcceptTime: testNow,
|
||||||
|
ResolveTime: testNow,
|
||||||
|
Expiry: 40,
|
||||||
|
State: HtlcStateSettled,
|
||||||
|
CustomRecords: make(record.CustomSet),
|
||||||
|
AMP: nil,
|
||||||
|
},
|
||||||
|
expErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "MPP cancel",
|
||||||
|
input: InvoiceHTLC{
|
||||||
|
Amt: 5000,
|
||||||
|
MppTotalAmt: 5000,
|
||||||
|
AcceptHeight: 100,
|
||||||
|
AcceptTime: testNow,
|
||||||
|
ResolveTime: time.Time{},
|
||||||
|
Expiry: 40,
|
||||||
|
State: HtlcStateAccepted,
|
||||||
|
CustomRecords: make(record.CustomSet),
|
||||||
|
AMP: nil,
|
||||||
|
},
|
||||||
|
invState: ContractCanceled,
|
||||||
|
setID: nil,
|
||||||
|
output: InvoiceHTLC{
|
||||||
|
Amt: 5000,
|
||||||
|
MppTotalAmt: 5000,
|
||||||
|
AcceptHeight: 100,
|
||||||
|
AcceptTime: testNow,
|
||||||
|
ResolveTime: testNow,
|
||||||
|
Expiry: 40,
|
||||||
|
State: HtlcStateCanceled,
|
||||||
|
CustomRecords: make(record.CustomSet),
|
||||||
|
AMP: nil,
|
||||||
|
},
|
||||||
|
expErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "AMP accept missing preimage",
|
||||||
|
input: InvoiceHTLC{
|
||||||
|
Amt: 5000,
|
||||||
|
MppTotalAmt: 5000,
|
||||||
|
AcceptHeight: 100,
|
||||||
|
AcceptTime: testNow,
|
||||||
|
ResolveTime: time.Time{},
|
||||||
|
Expiry: 40,
|
||||||
|
State: HtlcStateAccepted,
|
||||||
|
CustomRecords: make(record.CustomSet),
|
||||||
|
AMP: &InvoiceHtlcAMPData{
|
||||||
|
Record: *ampRecord,
|
||||||
|
Hash: hash,
|
||||||
|
Preimage: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
invState: ContractAccepted,
|
||||||
|
setID: &setID,
|
||||||
|
output: InvoiceHTLC{
|
||||||
|
Amt: 5000,
|
||||||
|
MppTotalAmt: 5000,
|
||||||
|
AcceptHeight: 100,
|
||||||
|
AcceptTime: testNow,
|
||||||
|
ResolveTime: time.Time{},
|
||||||
|
Expiry: 40,
|
||||||
|
State: HtlcStateAccepted,
|
||||||
|
CustomRecords: make(record.CustomSet),
|
||||||
|
AMP: &InvoiceHtlcAMPData{
|
||||||
|
Record: *ampRecord,
|
||||||
|
Hash: hash,
|
||||||
|
Preimage: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expErr: ErrHTLCPreimageMissing,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "AMP accept invalid preimage",
|
||||||
|
input: InvoiceHTLC{
|
||||||
|
Amt: 5000,
|
||||||
|
MppTotalAmt: 5000,
|
||||||
|
AcceptHeight: 100,
|
||||||
|
AcceptTime: testNow,
|
||||||
|
ResolveTime: time.Time{},
|
||||||
|
Expiry: 40,
|
||||||
|
State: HtlcStateAccepted,
|
||||||
|
CustomRecords: make(record.CustomSet),
|
||||||
|
AMP: &InvoiceHtlcAMPData{
|
||||||
|
Record: *ampRecord,
|
||||||
|
Hash: hash,
|
||||||
|
Preimage: &fakePreimage,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
invState: ContractAccepted,
|
||||||
|
setID: &setID,
|
||||||
|
output: InvoiceHTLC{
|
||||||
|
Amt: 5000,
|
||||||
|
MppTotalAmt: 5000,
|
||||||
|
AcceptHeight: 100,
|
||||||
|
AcceptTime: testNow,
|
||||||
|
ResolveTime: time.Time{},
|
||||||
|
Expiry: 40,
|
||||||
|
State: HtlcStateAccepted,
|
||||||
|
CustomRecords: make(record.CustomSet),
|
||||||
|
AMP: &InvoiceHtlcAMPData{
|
||||||
|
Record: *ampRecord,
|
||||||
|
Hash: hash,
|
||||||
|
Preimage: &fakePreimage,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expErr: ErrHTLCPreimageMismatch,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "AMP accept valid preimage",
|
||||||
|
input: InvoiceHTLC{
|
||||||
|
Amt: 5000,
|
||||||
|
MppTotalAmt: 5000,
|
||||||
|
AcceptHeight: 100,
|
||||||
|
AcceptTime: testNow,
|
||||||
|
ResolveTime: time.Time{},
|
||||||
|
Expiry: 40,
|
||||||
|
State: HtlcStateAccepted,
|
||||||
|
CustomRecords: make(record.CustomSet),
|
||||||
|
AMP: &InvoiceHtlcAMPData{
|
||||||
|
Record: *ampRecord,
|
||||||
|
Hash: hash,
|
||||||
|
Preimage: &preimage,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
invState: ContractAccepted,
|
||||||
|
setID: &setID,
|
||||||
|
output: InvoiceHTLC{
|
||||||
|
Amt: 5000,
|
||||||
|
MppTotalAmt: 5000,
|
||||||
|
AcceptHeight: 100,
|
||||||
|
AcceptTime: testNow,
|
||||||
|
ResolveTime: time.Time{},
|
||||||
|
Expiry: 40,
|
||||||
|
State: HtlcStateAccepted,
|
||||||
|
CustomRecords: make(record.CustomSet),
|
||||||
|
AMP: &InvoiceHtlcAMPData{
|
||||||
|
Record: *ampRecord,
|
||||||
|
Hash: hash,
|
||||||
|
Preimage: &preimage,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "AMP accept valid preimage different htlc set",
|
||||||
|
input: InvoiceHTLC{
|
||||||
|
Amt: 5000,
|
||||||
|
MppTotalAmt: 5000,
|
||||||
|
AcceptHeight: 100,
|
||||||
|
AcceptTime: testNow,
|
||||||
|
ResolveTime: time.Time{},
|
||||||
|
Expiry: 40,
|
||||||
|
State: HtlcStateAccepted,
|
||||||
|
CustomRecords: make(record.CustomSet),
|
||||||
|
AMP: &InvoiceHtlcAMPData{
|
||||||
|
Record: *ampRecord,
|
||||||
|
Hash: hash,
|
||||||
|
Preimage: &preimage,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
invState: ContractAccepted,
|
||||||
|
setID: &diffSetID,
|
||||||
|
output: InvoiceHTLC{
|
||||||
|
Amt: 5000,
|
||||||
|
MppTotalAmt: 5000,
|
||||||
|
AcceptHeight: 100,
|
||||||
|
AcceptTime: testNow,
|
||||||
|
ResolveTime: time.Time{},
|
||||||
|
Expiry: 40,
|
||||||
|
State: HtlcStateAccepted,
|
||||||
|
CustomRecords: make(record.CustomSet),
|
||||||
|
AMP: &InvoiceHtlcAMPData{
|
||||||
|
Record: *ampRecord,
|
||||||
|
Hash: hash,
|
||||||
|
Preimage: &preimage,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "AMP settle missing preimage",
|
||||||
|
input: InvoiceHTLC{
|
||||||
|
Amt: 5000,
|
||||||
|
MppTotalAmt: 5000,
|
||||||
|
AcceptHeight: 100,
|
||||||
|
AcceptTime: testNow,
|
||||||
|
ResolveTime: time.Time{},
|
||||||
|
Expiry: 40,
|
||||||
|
State: HtlcStateAccepted,
|
||||||
|
CustomRecords: make(record.CustomSet),
|
||||||
|
AMP: &InvoiceHtlcAMPData{
|
||||||
|
Record: *ampRecord,
|
||||||
|
Hash: hash,
|
||||||
|
Preimage: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
invState: ContractSettled,
|
||||||
|
setID: &setID,
|
||||||
|
output: InvoiceHTLC{
|
||||||
|
Amt: 5000,
|
||||||
|
MppTotalAmt: 5000,
|
||||||
|
AcceptHeight: 100,
|
||||||
|
AcceptTime: testNow,
|
||||||
|
ResolveTime: time.Time{},
|
||||||
|
Expiry: 40,
|
||||||
|
State: HtlcStateAccepted,
|
||||||
|
CustomRecords: make(record.CustomSet),
|
||||||
|
AMP: &InvoiceHtlcAMPData{
|
||||||
|
Record: *ampRecord,
|
||||||
|
Hash: hash,
|
||||||
|
Preimage: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expErr: ErrHTLCPreimageMissing,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "AMP settle invalid preimage",
|
||||||
|
input: InvoiceHTLC{
|
||||||
|
Amt: 5000,
|
||||||
|
MppTotalAmt: 5000,
|
||||||
|
AcceptHeight: 100,
|
||||||
|
AcceptTime: testNow,
|
||||||
|
ResolveTime: time.Time{},
|
||||||
|
Expiry: 40,
|
||||||
|
State: HtlcStateAccepted,
|
||||||
|
CustomRecords: make(record.CustomSet),
|
||||||
|
AMP: &InvoiceHtlcAMPData{
|
||||||
|
Record: *ampRecord,
|
||||||
|
Hash: hash,
|
||||||
|
Preimage: &fakePreimage,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
invState: ContractSettled,
|
||||||
|
setID: &setID,
|
||||||
|
output: InvoiceHTLC{
|
||||||
|
Amt: 5000,
|
||||||
|
MppTotalAmt: 5000,
|
||||||
|
AcceptHeight: 100,
|
||||||
|
AcceptTime: testNow,
|
||||||
|
ResolveTime: time.Time{},
|
||||||
|
Expiry: 40,
|
||||||
|
State: HtlcStateAccepted,
|
||||||
|
CustomRecords: make(record.CustomSet),
|
||||||
|
AMP: &InvoiceHtlcAMPData{
|
||||||
|
Record: *ampRecord,
|
||||||
|
Hash: hash,
|
||||||
|
Preimage: &fakePreimage,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expErr: ErrHTLCPreimageMismatch,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "AMP settle valid preimage",
|
||||||
|
input: InvoiceHTLC{
|
||||||
|
Amt: 5000,
|
||||||
|
MppTotalAmt: 5000,
|
||||||
|
AcceptHeight: 100,
|
||||||
|
AcceptTime: testNow,
|
||||||
|
ResolveTime: time.Time{},
|
||||||
|
Expiry: 40,
|
||||||
|
State: HtlcStateAccepted,
|
||||||
|
CustomRecords: make(record.CustomSet),
|
||||||
|
AMP: &InvoiceHtlcAMPData{
|
||||||
|
Record: *ampRecord,
|
||||||
|
Hash: hash,
|
||||||
|
Preimage: &preimage,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
invState: ContractSettled,
|
||||||
|
setID: &setID,
|
||||||
|
output: InvoiceHTLC{
|
||||||
|
Amt: 5000,
|
||||||
|
MppTotalAmt: 5000,
|
||||||
|
AcceptHeight: 100,
|
||||||
|
AcceptTime: testNow,
|
||||||
|
ResolveTime: testNow,
|
||||||
|
Expiry: 40,
|
||||||
|
State: HtlcStateSettled,
|
||||||
|
CustomRecords: make(record.CustomSet),
|
||||||
|
AMP: &InvoiceHtlcAMPData{
|
||||||
|
Record: *ampRecord,
|
||||||
|
Hash: hash,
|
||||||
|
Preimage: &preimage,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// With the newer AMP logic, this is now valid, as we
|
||||||
|
// want to be able to accept multiple settle attempts
|
||||||
|
// to a given pay_addr. In this case, the HTLC should
|
||||||
|
// remain in the accepted state.
|
||||||
|
name: "AMP settle valid preimage different htlc set",
|
||||||
|
input: InvoiceHTLC{
|
||||||
|
Amt: 5000,
|
||||||
|
MppTotalAmt: 5000,
|
||||||
|
AcceptHeight: 100,
|
||||||
|
AcceptTime: testNow,
|
||||||
|
ResolveTime: time.Time{},
|
||||||
|
Expiry: 40,
|
||||||
|
State: HtlcStateAccepted,
|
||||||
|
CustomRecords: make(record.CustomSet),
|
||||||
|
AMP: &InvoiceHtlcAMPData{
|
||||||
|
Record: *ampRecord,
|
||||||
|
Hash: hash,
|
||||||
|
Preimage: &preimage,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
invState: ContractSettled,
|
||||||
|
setID: &diffSetID,
|
||||||
|
output: InvoiceHTLC{
|
||||||
|
Amt: 5000,
|
||||||
|
MppTotalAmt: 5000,
|
||||||
|
AcceptHeight: 100,
|
||||||
|
AcceptTime: testNow,
|
||||||
|
ResolveTime: time.Time{},
|
||||||
|
Expiry: 40,
|
||||||
|
State: HtlcStateAccepted,
|
||||||
|
CustomRecords: make(record.CustomSet),
|
||||||
|
AMP: &InvoiceHtlcAMPData{
|
||||||
|
Record: *ampRecord,
|
||||||
|
Hash: hash,
|
||||||
|
Preimage: &preimage,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "accept invoice htlc already settled",
|
||||||
|
input: InvoiceHTLC{
|
||||||
|
Amt: 5000,
|
||||||
|
MppTotalAmt: 5000,
|
||||||
|
AcceptHeight: 100,
|
||||||
|
AcceptTime: testNow,
|
||||||
|
ResolveTime: testAlreadyNow,
|
||||||
|
Expiry: 40,
|
||||||
|
State: HtlcStateSettled,
|
||||||
|
CustomRecords: make(record.CustomSet),
|
||||||
|
AMP: &InvoiceHtlcAMPData{
|
||||||
|
Record: *ampRecord,
|
||||||
|
Hash: hash,
|
||||||
|
Preimage: &preimage,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
invState: ContractAccepted,
|
||||||
|
setID: &setID,
|
||||||
|
output: InvoiceHTLC{
|
||||||
|
Amt: 5000,
|
||||||
|
MppTotalAmt: 5000,
|
||||||
|
AcceptHeight: 100,
|
||||||
|
AcceptTime: testNow,
|
||||||
|
ResolveTime: testAlreadyNow,
|
||||||
|
Expiry: 40,
|
||||||
|
State: HtlcStateSettled,
|
||||||
|
CustomRecords: make(record.CustomSet),
|
||||||
|
AMP: &InvoiceHtlcAMPData{
|
||||||
|
Record: *ampRecord,
|
||||||
|
Hash: hash,
|
||||||
|
Preimage: &preimage,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expErr: ErrHTLCAlreadySettled,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "cancel invoice htlc already settled",
|
||||||
|
input: InvoiceHTLC{
|
||||||
|
Amt: 5000,
|
||||||
|
MppTotalAmt: 5000,
|
||||||
|
AcceptHeight: 100,
|
||||||
|
AcceptTime: testNow,
|
||||||
|
ResolveTime: testAlreadyNow,
|
||||||
|
Expiry: 40,
|
||||||
|
State: HtlcStateSettled,
|
||||||
|
CustomRecords: make(record.CustomSet),
|
||||||
|
AMP: &InvoiceHtlcAMPData{
|
||||||
|
Record: *ampRecord,
|
||||||
|
Hash: hash,
|
||||||
|
Preimage: &preimage,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
invState: ContractCanceled,
|
||||||
|
setID: &setID,
|
||||||
|
output: InvoiceHTLC{
|
||||||
|
Amt: 5000,
|
||||||
|
MppTotalAmt: 5000,
|
||||||
|
AcceptHeight: 100,
|
||||||
|
AcceptTime: testNow,
|
||||||
|
ResolveTime: testAlreadyNow,
|
||||||
|
Expiry: 40,
|
||||||
|
State: HtlcStateSettled,
|
||||||
|
CustomRecords: make(record.CustomSet),
|
||||||
|
AMP: &InvoiceHtlcAMPData{
|
||||||
|
Record: *ampRecord,
|
||||||
|
Hash: hash,
|
||||||
|
Preimage: &preimage,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expErr: ErrHTLCAlreadySettled,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "settle invoice htlc already settled",
|
||||||
|
input: InvoiceHTLC{
|
||||||
|
Amt: 5000,
|
||||||
|
MppTotalAmt: 5000,
|
||||||
|
AcceptHeight: 100,
|
||||||
|
AcceptTime: testNow,
|
||||||
|
ResolveTime: testAlreadyNow,
|
||||||
|
Expiry: 40,
|
||||||
|
State: HtlcStateSettled,
|
||||||
|
CustomRecords: make(record.CustomSet),
|
||||||
|
AMP: &InvoiceHtlcAMPData{
|
||||||
|
Record: *ampRecord,
|
||||||
|
Hash: hash,
|
||||||
|
Preimage: &preimage,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
invState: ContractSettled,
|
||||||
|
setID: &setID,
|
||||||
|
output: InvoiceHTLC{
|
||||||
|
Amt: 5000,
|
||||||
|
MppTotalAmt: 5000,
|
||||||
|
AcceptHeight: 100,
|
||||||
|
AcceptTime: testNow,
|
||||||
|
ResolveTime: testAlreadyNow,
|
||||||
|
Expiry: 40,
|
||||||
|
State: HtlcStateSettled,
|
||||||
|
CustomRecords: make(record.CustomSet),
|
||||||
|
AMP: &InvoiceHtlcAMPData{
|
||||||
|
Record: *ampRecord,
|
||||||
|
Hash: hash,
|
||||||
|
Preimage: &preimage,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "cancel invoice",
|
||||||
|
input: InvoiceHTLC{
|
||||||
|
Amt: 5000,
|
||||||
|
MppTotalAmt: 5000,
|
||||||
|
AcceptHeight: 100,
|
||||||
|
AcceptTime: testNow,
|
||||||
|
ResolveTime: time.Time{},
|
||||||
|
Expiry: 40,
|
||||||
|
State: HtlcStateAccepted,
|
||||||
|
CustomRecords: make(record.CustomSet),
|
||||||
|
AMP: &InvoiceHtlcAMPData{
|
||||||
|
Record: *ampRecord,
|
||||||
|
Hash: hash,
|
||||||
|
Preimage: &preimage,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
invState: ContractCanceled,
|
||||||
|
setID: &setID,
|
||||||
|
output: InvoiceHTLC{
|
||||||
|
Amt: 5000,
|
||||||
|
MppTotalAmt: 5000,
|
||||||
|
AcceptHeight: 100,
|
||||||
|
AcceptTime: testNow,
|
||||||
|
ResolveTime: testNow,
|
||||||
|
Expiry: 40,
|
||||||
|
State: HtlcStateCanceled,
|
||||||
|
CustomRecords: make(record.CustomSet),
|
||||||
|
AMP: &InvoiceHtlcAMPData{
|
||||||
|
Record: *ampRecord,
|
||||||
|
Hash: hash,
|
||||||
|
Preimage: &preimage,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "accept invoice htlc already canceled",
|
||||||
|
input: InvoiceHTLC{
|
||||||
|
Amt: 5000,
|
||||||
|
MppTotalAmt: 5000,
|
||||||
|
AcceptHeight: 100,
|
||||||
|
AcceptTime: testNow,
|
||||||
|
ResolveTime: testAlreadyNow,
|
||||||
|
Expiry: 40,
|
||||||
|
State: HtlcStateCanceled,
|
||||||
|
CustomRecords: make(record.CustomSet),
|
||||||
|
AMP: &InvoiceHtlcAMPData{
|
||||||
|
Record: *ampRecord,
|
||||||
|
Hash: hash,
|
||||||
|
Preimage: &preimage,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
invState: ContractAccepted,
|
||||||
|
setID: &setID,
|
||||||
|
output: InvoiceHTLC{
|
||||||
|
Amt: 5000,
|
||||||
|
MppTotalAmt: 5000,
|
||||||
|
AcceptHeight: 100,
|
||||||
|
AcceptTime: testNow,
|
||||||
|
ResolveTime: testAlreadyNow,
|
||||||
|
Expiry: 40,
|
||||||
|
State: HtlcStateCanceled,
|
||||||
|
CustomRecords: make(record.CustomSet),
|
||||||
|
AMP: &InvoiceHtlcAMPData{
|
||||||
|
Record: *ampRecord,
|
||||||
|
Hash: hash,
|
||||||
|
Preimage: &preimage,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "cancel invoice htlc already canceled",
|
||||||
|
input: InvoiceHTLC{
|
||||||
|
Amt: 5000,
|
||||||
|
MppTotalAmt: 5000,
|
||||||
|
AcceptHeight: 100,
|
||||||
|
AcceptTime: testNow,
|
||||||
|
ResolveTime: testAlreadyNow,
|
||||||
|
Expiry: 40,
|
||||||
|
State: HtlcStateCanceled,
|
||||||
|
CustomRecords: make(record.CustomSet),
|
||||||
|
AMP: &InvoiceHtlcAMPData{
|
||||||
|
Record: *ampRecord,
|
||||||
|
Hash: hash,
|
||||||
|
Preimage: &preimage,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
invState: ContractCanceled,
|
||||||
|
setID: &setID,
|
||||||
|
output: InvoiceHTLC{
|
||||||
|
Amt: 5000,
|
||||||
|
MppTotalAmt: 5000,
|
||||||
|
AcceptHeight: 100,
|
||||||
|
AcceptTime: testNow,
|
||||||
|
ResolveTime: testAlreadyNow,
|
||||||
|
Expiry: 40,
|
||||||
|
State: HtlcStateCanceled,
|
||||||
|
CustomRecords: make(record.CustomSet),
|
||||||
|
AMP: &InvoiceHtlcAMPData{
|
||||||
|
Record: *ampRecord,
|
||||||
|
Hash: hash,
|
||||||
|
Preimage: &preimage,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "settle invoice htlc already canceled",
|
||||||
|
input: InvoiceHTLC{
|
||||||
|
Amt: 5000,
|
||||||
|
MppTotalAmt: 5000,
|
||||||
|
AcceptHeight: 100,
|
||||||
|
AcceptTime: testNow,
|
||||||
|
ResolveTime: testAlreadyNow,
|
||||||
|
Expiry: 40,
|
||||||
|
State: HtlcStateCanceled,
|
||||||
|
CustomRecords: make(record.CustomSet),
|
||||||
|
AMP: &InvoiceHtlcAMPData{
|
||||||
|
Record: *ampRecord,
|
||||||
|
Hash: hash,
|
||||||
|
Preimage: &preimage,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
invState: ContractSettled,
|
||||||
|
setID: &setID,
|
||||||
|
output: InvoiceHTLC{
|
||||||
|
Amt: 5000,
|
||||||
|
MppTotalAmt: 5000,
|
||||||
|
AcceptHeight: 100,
|
||||||
|
AcceptTime: testNow,
|
||||||
|
ResolveTime: testAlreadyNow,
|
||||||
|
Expiry: 40,
|
||||||
|
State: HtlcStateCanceled,
|
||||||
|
CustomRecords: make(record.CustomSet),
|
||||||
|
AMP: &InvoiceHtlcAMPData{
|
||||||
|
Record: *ampRecord,
|
||||||
|
Hash: hash,
|
||||||
|
Preimage: &preimage,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expErr: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
test := test
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
testUpdateHTLC(t, test, testNow)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testUpdateHTLC(t *testing.T, test updateHTLCTest, now time.Time) {
|
||||||
|
htlc := test.input.Copy()
|
||||||
|
stateChanged, state, err := getUpdatedHtlcState(
|
||||||
|
htlc, test.invState, test.setID,
|
||||||
|
)
|
||||||
|
if stateChanged {
|
||||||
|
htlc.State = state
|
||||||
|
htlc.ResolveTime = now
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Equal(t, test.expErr, err)
|
||||||
|
require.Equal(t, test.output, *htlc)
|
||||||
|
}
|
||||||
Reference in new issue
Block a user