mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 01:43:16 +01:00
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:
commit
72764b1473
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -324,6 +324,9 @@
|
||||
* [Update](https://github.com/lightningnetwork/lnd/pull/8419) the embedded
|
||||
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
|
||||
|
||||
* [Remove database pointers](https://github.com/lightningnetwork/lnd/pull/8117)
|
||||
|
13
go.mod
13
go.mod
@ -33,7 +33,7 @@ require (
|
||||
github.com/jessevdk/go-flags v1.4.0
|
||||
github.com/jrick/logrotate 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/cache v1.1.2
|
||||
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/fn v1.0.4
|
||||
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/ticker v1.1.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/go-connections v0.4.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/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/gogo/protobuf v1.3.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/testing v0.0.0-20220203020004-a0ff61f03494 // 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/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||
github.com/mholt/archiver/v3 v3.5.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.1 // indirect
|
||||
github.com/moby/term v0.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.1 // indirect
|
||||
github.com/nwaples/rardecode v1.1.2 // indirect
|
||||
github.com/nxadm/tail v1.4.8 // indirect
|
||||
github.com/onsi/gomega v1.26.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.2 // 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/pmezard/go-difflib v1.0.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/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect
|
||||
github.com/ulikunitz/xz v0.5.11 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||
|
38
go.sum
38
go.sum
@ -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-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
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/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
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.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
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/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
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/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/fergusstrange/embedded-postgres v1.10.0 h1:YnwF6xAQYmKLAXXrrRx4rHDLih47YJwVPvg8jeKfdNg=
|
||||
github.com/fergusstrange/embedded-postgres v1.10.0/go.mod h1:a008U8/Rws5FtIOTGYDYa7beVWsT3qVKyqExqYYjL+c=
|
||||
github.com/fergusstrange/embedded-postgres v1.25.0 h1:sa+k2Ycrtz40eCRPOzI7Ry7TtkWXXJ+YRsxpKMDhxK0=
|
||||
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.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebPhedY=
|
||||
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.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
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/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
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 v1.0.0 h1:Se5gHwgp2VT2uHfDrkbbgbgEvV9cimLELwrPJctSjg8=
|
||||
github.com/kkdai/bstream v1.0.0/go.mod h1:FDnDOHt5Yx4p3FaHcioFT0QjDOtgUpvjeZqAs+NVZZA=
|
||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.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.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=
|
||||
@ -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.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.8.0/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.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk=
|
||||
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/go.mod h1:vxmQPeIQxPf6Jf9rM8R+B4rKBqLA2AjttNxkFBL2Plk=
|
||||
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/healthcheck v1.2.3 h1:oqhOOy8WmIEa6RBkYKC0mmYZkhl8T2kGD97n9jpML8o=
|
||||
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.4/go.mod h1:9SuaIqMA9ugrVkdvgQkYXa8CAKYNYd4vsEYORP4V698=
|
||||
github.com/lightningnetwork/lnd/kvdb v1.4.5 h1:wwX3hbFTsnxEIL5X2Pszq1o3Fd2OZGdyWIMr9QrMxL8=
|
||||
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/go.mod h1:7A6nC1Qrm32FHuhx/mi1cieAiBZo5O6l8IBIoQxvkz4=
|
||||
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/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mholt/archiver/v3 v3.5.0 h1:nE8gZIrw66cu4osS/U7UW7YDuGMHssxKutU8IfWxwWE=
|
||||
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/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
|
||||
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/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/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.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
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/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4=
|
||||
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.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/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=
|
||||
@ -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/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/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.9 h1:cv3/KhXGBGjEXLC4bH0sLuJ9BewaAbpk5oyMOveu4pw=
|
||||
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.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||
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.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||
|
@ -2,9 +2,11 @@ package invoices
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"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
|
||||
// HTLCs in a circuit.
|
||||
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/chainntnfs"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/clock"
|
||||
invpkg "github.com/lightningnetwork/lnd/invoices"
|
||||
"github.com/lightningnetwork/lnd/lntest/wait"
|
||||
@ -20,11 +21,115 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestSettleInvoice tests settling of an invoice and related notifications.
|
||||
func TestSettleInvoice(t *testing.T) {
|
||||
// TestInvoiceRegistry is a master test which encompasses all tests using an
|
||||
// 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()
|
||||
|
||||
ctx := newTestContext(t, nil)
|
||||
ctx := newTestContext(t, nil, makeDB)
|
||||
|
||||
ctxb := context.Background()
|
||||
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()
|
||||
|
||||
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
|
||||
// cancellation.
|
||||
cfg.GcCanceledInvoicesOnTheFly = gc
|
||||
ctx := newTestContext(t, &cfg)
|
||||
ctx := newTestContext(t, &cfg, makeDB)
|
||||
|
||||
ctxb := context.Background()
|
||||
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)
|
||||
}
|
||||
|
||||
// TestCancelInvoice tests cancellation of an invoice and related notifications.
|
||||
func TestCancelInvoice(t *testing.T) {
|
||||
// testCancelInvoice tests cancellation of an invoice and related notifications.
|
||||
func testCancelInvoice(t *testing.T,
|
||||
makeDB func(t *testing.T) (invpkg.InvoiceDB, *clock.TestClock)) {
|
||||
|
||||
t.Parallel()
|
||||
|
||||
// Test cancellation both with garbage collection (meaning that canceled
|
||||
// invoice will be deleted) and without (meaning it'll be kept).
|
||||
t.Run("garbage collect", func(t *testing.T) {
|
||||
testCancelInvoice(t, true)
|
||||
testCancelInvoiceImpl(t, true, makeDB)
|
||||
})
|
||||
|
||||
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.
|
||||
func TestSettleHoldInvoice(t *testing.T) {
|
||||
func testSettleHoldInvoice(t *testing.T,
|
||||
makeDB func(t *testing.T) (invpkg.InvoiceDB, *clock.TestClock)) {
|
||||
|
||||
t.Parallel()
|
||||
defer timeout()()
|
||||
|
||||
idb, err := newTestChannelDB(t, clock.NewTestClock(time.Time{}))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
idb, clock := makeDB(t)
|
||||
|
||||
// Instantiate and start the invoice ctx.registry.
|
||||
cfg := invpkg.RegistryConfig{
|
||||
FinalCltvRejectDelta: testFinalCltvRejectDelta,
|
||||
Clock: clock.NewTestClock(testTime),
|
||||
Clock: clock,
|
||||
}
|
||||
|
||||
expiryWatcher := invpkg.NewInvoiceExpiryWatcher(
|
||||
@ -366,7 +474,7 @@ func TestSettleHoldInvoice(t *testing.T) {
|
||||
)
|
||||
registry := invpkg.NewRegistry(idb, expiryWatcher, &cfg)
|
||||
|
||||
err = registry.Start()
|
||||
err := registry.Start()
|
||||
require.NoError(t, err)
|
||||
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.
|
||||
func TestCancelHoldInvoice(t *testing.T) {
|
||||
func testCancelHoldInvoice(t *testing.T,
|
||||
makeDB func(t *testing.T) (invpkg.InvoiceDB, *clock.TestClock)) {
|
||||
|
||||
t.Parallel()
|
||||
defer timeout()()
|
||||
|
||||
testClock := clock.NewTestClock(testTime)
|
||||
idb, err := newTestChannelDB(t, testClock)
|
||||
require.NoError(t, err)
|
||||
idb, testClock := makeDB(t)
|
||||
|
||||
// Instantiate and start the invoice ctx.registry.
|
||||
cfg := invpkg.RegistryConfig{
|
||||
@ -531,7 +639,7 @@ func TestCancelHoldInvoice(t *testing.T) {
|
||||
)
|
||||
registry := invpkg.NewRegistry(idb, expiryWatcher, &cfg)
|
||||
|
||||
err = registry.Start()
|
||||
err := registry.Start()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -587,14 +695,16 @@ func TestCancelHoldInvoice(t *testing.T) {
|
||||
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
|
||||
// resolution for forwarded htlcs. In the link, NotifyExitHopHtlc is only called
|
||||
// if we are the exit hop, but in htlcIncomingContestResolver it is called with
|
||||
// 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()
|
||||
ctx := newTestContext(t, nil)
|
||||
ctx := newTestContext(t, nil, makeDB)
|
||||
|
||||
// Notify arrival of a new htlc paying to this invoice. This should
|
||||
// succeed.
|
||||
@ -611,28 +721,32 @@ func TestUnknownInvoice(t *testing.T) {
|
||||
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.
|
||||
func TestKeySend(t *testing.T) {
|
||||
func testKeySend(t *testing.T,
|
||||
makeDB func(t *testing.T) (invpkg.InvoiceDB, *clock.TestClock)) {
|
||||
|
||||
t.Parallel()
|
||||
|
||||
t.Run("enabled", func(t *testing.T) {
|
||||
testKeySend(t, true)
|
||||
testKeySendImpl(t, true, makeDB)
|
||||
})
|
||||
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
|
||||
// enabled state on the receiver end.
|
||||
func testKeySend(t *testing.T, keySendEnabled bool) {
|
||||
// testKeySendImpl is the inner test function that tests keysend for a
|
||||
// particular enabled state on the receiver end.
|
||||
func testKeySendImpl(t *testing.T, keySendEnabled bool,
|
||||
makeDB func(t *testing.T) (invpkg.InvoiceDB, *clock.TestClock)) {
|
||||
|
||||
t.Parallel()
|
||||
defer timeout()()
|
||||
|
||||
cfg := defaultRegistryConfig()
|
||||
cfg.AcceptKeySend = keySendEnabled
|
||||
ctx := newTestContext(t, &cfg)
|
||||
ctx := newTestContext(t, &cfg, makeDB)
|
||||
|
||||
allSubscriptions, err := ctx.registry.SubscribeNotifications(
|
||||
context.Background(), 0, 0,
|
||||
@ -742,20 +856,24 @@ func testKeySend(t *testing.T, keySendEnabled bool) {
|
||||
checkSubscription()
|
||||
}
|
||||
|
||||
// TestHoldKeysend tests receiving a spontaneous payment that is held.
|
||||
func TestHoldKeysend(t *testing.T) {
|
||||
// testHoldKeysend tests receiving a spontaneous payment that is held.
|
||||
func testHoldKeysend(t *testing.T,
|
||||
makeDB func(t *testing.T) (invpkg.InvoiceDB, *clock.TestClock)) {
|
||||
|
||||
t.Parallel()
|
||||
|
||||
t.Run("settle", func(t *testing.T) {
|
||||
testHoldKeysend(t, false)
|
||||
testHoldKeysendImpl(t, false, makeDB)
|
||||
})
|
||||
t.Run("timeout", func(t *testing.T) {
|
||||
testHoldKeysend(t, true)
|
||||
testHoldKeysendImpl(t, true, makeDB)
|
||||
})
|
||||
}
|
||||
|
||||
// testHoldKeysend is the inner test function that tests hold-keysend.
|
||||
func testHoldKeysend(t *testing.T, timeoutKeysend bool) {
|
||||
// testHoldKeysendImpl is the inner test function that tests hold-keysend.
|
||||
func testHoldKeysendImpl(t *testing.T, timeoutKeysend bool,
|
||||
makeDB func(t *testing.T) (invpkg.InvoiceDB, *clock.TestClock)) {
|
||||
|
||||
t.Parallel()
|
||||
defer timeout()()
|
||||
|
||||
@ -764,7 +882,7 @@ func testHoldKeysend(t *testing.T, timeoutKeysend bool) {
|
||||
cfg := defaultRegistryConfig()
|
||||
cfg.AcceptKeySend = true
|
||||
cfg.KeysendHoldTime = holdDuration
|
||||
ctx := newTestContext(t, &cfg)
|
||||
ctx := newTestContext(t, &cfg, makeDB)
|
||||
|
||||
ctxb := context.Background()
|
||||
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)
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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()
|
||||
defer timeout()()
|
||||
|
||||
ctx := newTestContext(t, nil)
|
||||
ctx := newTestContext(t, nil, makeDB)
|
||||
ctxb := context.Background()
|
||||
|
||||
// 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
|
||||
// invoice.
|
||||
func TestMppPaymentWithOverpayment(t *testing.T) {
|
||||
func testMppPaymentWithOverpayment(t *testing.T,
|
||||
makeDB func(t *testing.T) (invpkg.InvoiceDB, *clock.TestClock)) {
|
||||
|
||||
t.Parallel()
|
||||
|
||||
ctxb := context.Background()
|
||||
f := func(overpaymentRand uint64) bool {
|
||||
ctx := newTestContext(t, nil)
|
||||
ctx := newTestContext(t, nil, makeDB)
|
||||
|
||||
// Add the invoice.
|
||||
testInvoice := newInvoice(t, false)
|
||||
@ -1017,13 +1139,13 @@ func TestMppPaymentWithOverpayment(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that invoices are canceled after expiration.
|
||||
func TestInvoiceExpiryWithRegistry(t *testing.T) {
|
||||
t.Parallel()
|
||||
// testInvoiceExpiryWithRegistry tests that invoices are canceled after
|
||||
// expiration.
|
||||
func testInvoiceExpiryWithRegistry(t *testing.T,
|
||||
makeDB func(t *testing.T) (invpkg.InvoiceDB, *clock.TestClock)) {
|
||||
|
||||
testClock := clock.NewTestClock(testTime)
|
||||
idb, err := newTestChannelDB(t, testClock)
|
||||
require.NoError(t, err)
|
||||
t.Parallel()
|
||||
idb, testClock := makeDB(t)
|
||||
|
||||
cfg := invpkg.RegistryConfig{
|
||||
FinalCltvRejectDelta: testFinalCltvRejectDelta,
|
||||
@ -1119,21 +1241,20 @@ func TestInvoiceExpiryWithRegistry(t *testing.T) {
|
||||
|
||||
// Retrospectively check that all invoices that were expected to be
|
||||
// canceled are indeed canceled.
|
||||
err = wait.NoError(canceled, testTimeout)
|
||||
err := wait.NoError(canceled, testTimeout)
|
||||
require.NoError(t, err, "timeout checking invoice state")
|
||||
|
||||
// Finally stop the 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.
|
||||
func TestOldInvoiceRemovalOnStart(t *testing.T) {
|
||||
t.Parallel()
|
||||
func testOldInvoiceRemovalOnStart(t *testing.T,
|
||||
makeDB func(t *testing.T) (invpkg.InvoiceDB, *clock.TestClock)) {
|
||||
|
||||
testClock := clock.NewTestClock(testTime)
|
||||
idb, err := newTestChannelDB(t, testClock)
|
||||
require.NoError(t, err)
|
||||
t.Parallel()
|
||||
idb, testClock := makeDB(t)
|
||||
|
||||
cfg := invpkg.RegistryConfig{
|
||||
FinalCltvRejectDelta: testFinalCltvRejectDelta,
|
||||
@ -1203,35 +1324,39 @@ func TestOldInvoiceRemovalOnStart(t *testing.T) {
|
||||
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
|
||||
// invoice is settled before expiry (and thus not canceled), and the case
|
||||
// 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.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) {
|
||||
testHeightExpiryWithRegistry(t, 1, false)
|
||||
testHeightExpiryWithRegistryImpl(t, 1, false, makeDB)
|
||||
})
|
||||
|
||||
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) {
|
||||
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()
|
||||
defer timeout()()
|
||||
|
||||
ctx := newTestContext(t, nil)
|
||||
ctx := newTestContext(t, nil, makeDB)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// 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,
|
||||
// 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.
|
||||
func TestMultipleSetHeightExpiry(t *testing.T) {
|
||||
func testMultipleSetHeightExpiry(t *testing.T,
|
||||
makeDB func(t *testing.T) (invpkg.InvoiceDB, *clock.TestClock)) {
|
||||
|
||||
t.Parallel()
|
||||
defer timeout()()
|
||||
|
||||
ctx := newTestContext(t, nil)
|
||||
ctx := newTestContext(t, nil, makeDB)
|
||||
|
||||
// Add a hold invoice.
|
||||
testInvoice := newInvoice(t, true)
|
||||
@ -1431,13 +1558,15 @@ func TestMultipleSetHeightExpiry(t *testing.T) {
|
||||
}, 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
|
||||
// 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()
|
||||
|
||||
ctx := newTestContext(t, nil)
|
||||
ctx := newTestContext(t, nil, makeDB)
|
||||
ctxb := context.Background()
|
||||
|
||||
allSubscriptions, err := ctx.registry.SubscribeNotifications(ctxb, 0, 0)
|
||||
@ -1519,14 +1648,16 @@ func TestSettleInvoicePaymentAddrRequired(t *testing.T) {
|
||||
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
|
||||
// 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.
|
||||
func TestSettleInvoicePaymentAddrRequiredOptionalGrace(t *testing.T) {
|
||||
func testSettleInvoicePaymentAddrRequiredOptionalGrace(t *testing.T,
|
||||
makeDB func(t *testing.T) (invpkg.InvoiceDB, *clock.TestClock)) {
|
||||
|
||||
t.Parallel()
|
||||
|
||||
ctx := newTestContext(t, nil)
|
||||
ctx := newTestContext(t, nil, makeDB)
|
||||
ctxb := context.Background()
|
||||
|
||||
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.
|
||||
func TestAMPWithoutMPPPayload(t *testing.T) {
|
||||
func testAMPWithoutMPPPayload(t *testing.T,
|
||||
makeDB func(t *testing.T) (invpkg.InvoiceDB, *clock.TestClock)) {
|
||||
|
||||
t.Parallel()
|
||||
defer timeout()()
|
||||
|
||||
cfg := defaultRegistryConfig()
|
||||
cfg.AcceptAMP = true
|
||||
ctx := newTestContext(t, &cfg)
|
||||
ctx := newTestContext(t, &cfg, makeDB)
|
||||
|
||||
const (
|
||||
shardAmt = lnwire.MilliSatoshi(10)
|
||||
@ -1666,9 +1799,11 @@ func TestAMPWithoutMPPPayload(t *testing.T) {
|
||||
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.
|
||||
func TestSpontaneousAmpPayment(t *testing.T) {
|
||||
func testSpontaneousAmpPayment(t *testing.T,
|
||||
makeDB func(t *testing.T) (invpkg.InvoiceDB, *clock.TestClock)) {
|
||||
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
@ -1712,24 +1847,25 @@ func TestSpontaneousAmpPayment(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
testSpontaneousAmpPayment(
|
||||
testSpontaneousAmpPaymentImpl(
|
||||
t, test.ampEnabled, test.failReconstruction,
|
||||
test.numShards,
|
||||
test.numShards, makeDB,
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// testSpontaneousAmpPayment runs a specific spontaneous AMP test case.
|
||||
func testSpontaneousAmpPayment(
|
||||
t *testing.T, ampEnabled, failReconstruction bool, numShards int) {
|
||||
func testSpontaneousAmpPaymentImpl(
|
||||
t *testing.T, ampEnabled, failReconstruction bool, numShards int,
|
||||
makeDB func(t *testing.T) (invpkg.InvoiceDB, *clock.TestClock)) {
|
||||
|
||||
t.Parallel()
|
||||
defer timeout()()
|
||||
|
||||
cfg := defaultRegistryConfig()
|
||||
cfg.AcceptAMP = ampEnabled
|
||||
ctx := newTestContext(t, &cfg)
|
||||
ctx := newTestContext(t, &cfg, makeDB)
|
||||
ctxb := context.Background()
|
||||
|
||||
allSubscriptions, err := ctx.registry.SubscribeNotifications(ctxb, 0, 0)
|
||||
|
2646
invoices/invoices_test.go
Normal file
2646
invoices/invoices_test.go
Normal file
File diff suppressed because it is too large
Load Diff
11
invoices/setup_test.go
Normal file
11
invoices/setup_test.go
Normal file
@ -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/chainhash"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/clock"
|
||||
invpkg "github.com/lightningnetwork/lnd/invoices"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
@ -131,26 +130,8 @@ var (
|
||||
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 {
|
||||
idb *channeldb.DB
|
||||
idb invpkg.InvoiceDB
|
||||
registry *invpkg.InvoiceRegistry
|
||||
notifier *mockChainNotifier
|
||||
clock *clock.TestClock
|
||||
@ -166,17 +147,13 @@ func defaultRegistryConfig() invpkg.RegistryConfig {
|
||||
}
|
||||
|
||||
func newTestContext(t *testing.T,
|
||||
registryCfg *invpkg.RegistryConfig) *testContext {
|
||||
registryCfg *invpkg.RegistryConfig,
|
||||
makeDB func(t *testing.T) (invpkg.InvoiceDB,
|
||||
*clock.TestClock)) *testContext {
|
||||
|
||||
t.Helper()
|
||||
|
||||
clock := clock.NewTestClock(testTime)
|
||||
|
||||
idb, err := newTestChannelDB(t, clock)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
idb, clock := makeDB(t)
|
||||
notifier := newMockNotifier()
|
||||
|
||||
expiryWatcher := invpkg.NewInvoiceExpiryWatcher(
|
||||
@ -192,10 +169,7 @@ func newTestContext(t *testing.T,
|
||||
// Instantiate and start the invoice ctx.registry.
|
||||
registry := invpkg.NewRegistry(idb, expiryWatcher, &cfg)
|
||||
|
||||
err = registry.Start()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, registry.Start())
|
||||
t.Cleanup(func() {
|
||||
require.NoError(t, registry.Stop())
|
||||
})
|
||||
|
826
invoices/update_invoice.go
Normal file
826
invoices/update_invoice.go
Normal file
@ -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")
|
||||
}
|
||||
}
|
687
invoices/update_invoice_test.go
Normal file
687
invoices/update_invoice_test.go
Normal file
@ -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)
|
||||
}
|
Loading…
Reference in New Issue
Block a user