@bsv/sdk 1.1.33 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/dist/cjs/mod.js +4 -0
  2. package/dist/cjs/mod.js.map +1 -1
  3. package/dist/cjs/package.json +4 -3
  4. package/dist/cjs/src/auth/Certificate.js +163 -0
  5. package/dist/cjs/src/auth/Certificate.js.map +1 -0
  6. package/dist/cjs/src/auth/index.js +9 -0
  7. package/dist/cjs/src/auth/index.js.map +1 -0
  8. package/dist/cjs/src/compat/BSM.js +17 -7
  9. package/dist/cjs/src/compat/BSM.js.map +1 -1
  10. package/dist/cjs/src/compat/ECIES.js +17 -7
  11. package/dist/cjs/src/compat/ECIES.js.map +1 -1
  12. package/dist/cjs/src/compat/HD.js +17 -7
  13. package/dist/cjs/src/compat/HD.js.map +1 -1
  14. package/dist/cjs/src/compat/Mnemonic.js +17 -7
  15. package/dist/cjs/src/compat/Mnemonic.js.map +1 -1
  16. package/dist/cjs/src/compat/index.js +17 -7
  17. package/dist/cjs/src/compat/index.js.map +1 -1
  18. package/dist/cjs/src/messages/index.js +17 -7
  19. package/dist/cjs/src/messages/index.js.map +1 -1
  20. package/dist/cjs/src/overlay-tools/LookupResolver.js +170 -0
  21. package/dist/cjs/src/overlay-tools/LookupResolver.js.map +1 -0
  22. package/dist/cjs/src/overlay-tools/OverlayAdminTokenTemplate.js +69 -0
  23. package/dist/cjs/src/overlay-tools/OverlayAdminTokenTemplate.js.map +1 -0
  24. package/dist/cjs/src/overlay-tools/SHIPBroadcaster.js +336 -0
  25. package/dist/cjs/src/overlay-tools/SHIPBroadcaster.js.map +1 -0
  26. package/dist/cjs/src/overlay-tools/index.js +29 -0
  27. package/dist/cjs/src/overlay-tools/index.js.map +1 -0
  28. package/dist/cjs/src/primitives/PrivateKey.js +17 -7
  29. package/dist/cjs/src/primitives/PrivateKey.js.map +1 -1
  30. package/dist/cjs/src/primitives/TransactionSignature.js +17 -7
  31. package/dist/cjs/src/primitives/TransactionSignature.js.map +1 -1
  32. package/dist/cjs/src/primitives/index.js +17 -7
  33. package/dist/cjs/src/primitives/index.js.map +1 -1
  34. package/dist/cjs/src/script/Spend.js +17 -7
  35. package/dist/cjs/src/script/Spend.js.map +1 -1
  36. package/dist/cjs/src/script/templates/PushDrop.js +218 -0
  37. package/dist/cjs/src/script/templates/PushDrop.js.map +1 -0
  38. package/dist/cjs/src/script/templates/index.js +3 -1
  39. package/dist/cjs/src/script/templates/index.js.map +1 -1
  40. package/dist/cjs/src/transaction/http/DefaultHttpClient.js +1 -1
  41. package/dist/cjs/src/transaction/http/DefaultHttpClient.js.map +1 -1
  42. package/dist/cjs/src/wallet/CachedKeyDeriver.js +177 -0
  43. package/dist/cjs/src/wallet/CachedKeyDeriver.js.map +1 -0
  44. package/dist/cjs/src/wallet/KeyDeriver.js +174 -0
  45. package/dist/cjs/src/wallet/KeyDeriver.js.map +1 -0
  46. package/dist/cjs/src/wallet/ProtoWallet.js +245 -0
  47. package/dist/cjs/src/wallet/ProtoWallet.js.map +1 -0
  48. package/dist/cjs/src/wallet/Wallet.interfaces.js +3 -0
  49. package/dist/cjs/src/wallet/Wallet.interfaces.js.map +1 -0
  50. package/dist/cjs/src/wallet/WalletClient.js +181 -0
  51. package/dist/cjs/src/wallet/WalletClient.js.map +1 -0
  52. package/dist/cjs/src/wallet/WalletError.js +28 -0
  53. package/dist/cjs/src/wallet/WalletError.js.map +1 -0
  54. package/dist/cjs/src/wallet/index.js +34 -0
  55. package/dist/cjs/src/wallet/index.js.map +1 -0
  56. package/dist/cjs/src/wallet/substrates/HTTPWalletWire.js +45 -0
  57. package/dist/cjs/src/wallet/substrates/HTTPWalletWire.js.map +1 -0
  58. package/dist/cjs/src/wallet/substrates/WalletWire.js +3 -0
  59. package/dist/cjs/src/wallet/substrates/WalletWire.js.map +1 -0
  60. package/dist/cjs/src/wallet/substrates/WalletWireCalls.js +36 -0
  61. package/dist/cjs/src/wallet/substrates/WalletWireCalls.js.map +1 -0
  62. package/dist/cjs/src/wallet/substrates/WalletWireProcessor.js +1821 -0
  63. package/dist/cjs/src/wallet/substrates/WalletWireProcessor.js.map +1 -0
  64. package/dist/cjs/src/wallet/substrates/WalletWireTransceiver.js +1305 -0
  65. package/dist/cjs/src/wallet/substrates/WalletWireTransceiver.js.map +1 -0
  66. package/dist/cjs/src/wallet/substrates/XDM.js +130 -0
  67. package/dist/cjs/src/wallet/substrates/XDM.js.map +1 -0
  68. package/dist/cjs/src/wallet/substrates/index.js +33 -0
  69. package/dist/cjs/src/wallet/substrates/index.js.map +1 -0
  70. package/dist/cjs/src/wallet/substrates/window.CWI.js +102 -0
  71. package/dist/cjs/src/wallet/substrates/window.CWI.js.map +1 -0
  72. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  73. package/dist/esm/mod.js +4 -0
  74. package/dist/esm/mod.js.map +1 -1
  75. package/dist/esm/src/auth/Certificate.js +185 -0
  76. package/dist/esm/src/auth/Certificate.js.map +1 -0
  77. package/dist/esm/src/auth/index.js +2 -0
  78. package/dist/esm/src/auth/index.js.map +1 -0
  79. package/dist/esm/src/overlay-tools/LookupResolver.js +167 -0
  80. package/dist/esm/src/overlay-tools/LookupResolver.js.map +1 -0
  81. package/dist/esm/src/overlay-tools/OverlayAdminTokenTemplate.js +64 -0
  82. package/dist/esm/src/overlay-tools/OverlayAdminTokenTemplate.js.map +1 -0
  83. package/dist/esm/src/overlay-tools/SHIPBroadcaster.js +335 -0
  84. package/dist/esm/src/overlay-tools/SHIPBroadcaster.js.map +1 -0
  85. package/dist/esm/src/overlay-tools/index.js +6 -0
  86. package/dist/esm/src/overlay-tools/index.js.map +1 -0
  87. package/dist/esm/src/script/templates/PushDrop.js +215 -0
  88. package/dist/esm/src/script/templates/PushDrop.js.map +1 -0
  89. package/dist/esm/src/script/templates/index.js +1 -0
  90. package/dist/esm/src/script/templates/index.js.map +1 -1
  91. package/dist/esm/src/transaction/http/DefaultHttpClient.js +1 -1
  92. package/dist/esm/src/transaction/http/DefaultHttpClient.js.map +1 -1
  93. package/dist/esm/src/wallet/CachedKeyDeriver.js +174 -0
  94. package/dist/esm/src/wallet/CachedKeyDeriver.js.map +1 -0
  95. package/dist/esm/src/wallet/KeyDeriver.js +172 -0
  96. package/dist/esm/src/wallet/KeyDeriver.js.map +1 -0
  97. package/dist/esm/src/wallet/ProtoWallet.js +207 -0
  98. package/dist/esm/src/wallet/ProtoWallet.js.map +1 -0
  99. package/dist/esm/src/wallet/Wallet.interfaces.js +2 -0
  100. package/dist/esm/src/wallet/Wallet.interfaces.js.map +1 -0
  101. package/dist/esm/src/wallet/WalletClient.js +177 -0
  102. package/dist/esm/src/wallet/WalletClient.js.map +1 -0
  103. package/dist/esm/src/wallet/WalletError.js +25 -0
  104. package/dist/esm/src/wallet/WalletError.js.map +1 -0
  105. package/dist/esm/src/wallet/index.js +9 -0
  106. package/dist/esm/src/wallet/index.js.map +1 -0
  107. package/dist/esm/src/wallet/substrates/HTTPWalletWire.js +42 -0
  108. package/dist/esm/src/wallet/substrates/HTTPWalletWire.js.map +1 -0
  109. package/dist/esm/src/wallet/substrates/WalletWire.js +2 -0
  110. package/dist/esm/src/wallet/substrates/WalletWire.js.map +1 -0
  111. package/dist/esm/src/wallet/substrates/WalletWireCalls.js +34 -0
  112. package/dist/esm/src/wallet/substrates/WalletWireCalls.js.map +1 -0
  113. package/dist/esm/src/wallet/substrates/WalletWireProcessor.js +1816 -0
  114. package/dist/esm/src/wallet/substrates/WalletWireProcessor.js.map +1 -0
  115. package/dist/esm/src/wallet/substrates/WalletWireTransceiver.js +1300 -0
  116. package/dist/esm/src/wallet/substrates/WalletWireTransceiver.js.map +1 -0
  117. package/dist/esm/src/wallet/substrates/XDM.js +128 -0
  118. package/dist/esm/src/wallet/substrates/XDM.js.map +1 -0
  119. package/dist/esm/src/wallet/substrates/index.js +8 -0
  120. package/dist/esm/src/wallet/substrates/index.js.map +1 -0
  121. package/dist/esm/src/wallet/substrates/window.CWI.js +100 -0
  122. package/dist/esm/src/wallet/substrates/window.CWI.js.map +1 -0
  123. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  124. package/dist/types/mod.d.ts +4 -0
  125. package/dist/types/mod.d.ts.map +1 -1
  126. package/dist/types/src/auth/Certificate.d.ts +76 -0
  127. package/dist/types/src/auth/Certificate.d.ts.map +1 -0
  128. package/dist/types/src/auth/index.d.ts +2 -0
  129. package/dist/types/src/auth/index.d.ts.map +1 -0
  130. package/dist/types/src/overlay-tools/LookupResolver.d.ts +71 -0
  131. package/dist/types/src/overlay-tools/LookupResolver.d.ts.map +1 -0
  132. package/dist/types/src/overlay-tools/OverlayAdminTokenTemplate.d.ts +44 -0
  133. package/dist/types/src/overlay-tools/OverlayAdminTokenTemplate.d.ts.map +1 -0
  134. package/dist/types/src/overlay-tools/SHIPBroadcaster.d.ts +90 -0
  135. package/dist/types/src/overlay-tools/SHIPBroadcaster.d.ts.map +1 -0
  136. package/dist/types/src/overlay-tools/index.d.ts +6 -0
  137. package/dist/types/src/overlay-tools/index.d.ts.map +1 -0
  138. package/dist/types/src/script/templates/PushDrop.d.ts +53 -0
  139. package/dist/types/src/script/templates/PushDrop.d.ts.map +1 -0
  140. package/dist/types/src/script/templates/index.d.ts +1 -0
  141. package/dist/types/src/script/templates/index.d.ts.map +1 -1
  142. package/dist/types/src/wallet/CachedKeyDeriver.d.ts +92 -0
  143. package/dist/types/src/wallet/CachedKeyDeriver.d.ts.map +1 -0
  144. package/dist/types/src/wallet/KeyDeriver.d.ts +72 -0
  145. package/dist/types/src/wallet/KeyDeriver.d.ts.map +1 -0
  146. package/dist/types/src/wallet/ProtoWallet.d.ts +415 -0
  147. package/dist/types/src/wallet/ProtoWallet.d.ts.map +1 -0
  148. package/dist/types/src/wallet/Wallet.interfaces.d.ts +996 -0
  149. package/dist/types/src/wallet/Wallet.interfaces.d.ts.map +1 -0
  150. package/dist/types/src/wallet/WalletClient.d.ts +182 -0
  151. package/dist/types/src/wallet/WalletClient.d.ts.map +1 -0
  152. package/dist/types/src/wallet/WalletError.d.ts +14 -0
  153. package/dist/types/src/wallet/WalletError.d.ts.map +1 -0
  154. package/dist/types/src/wallet/index.d.ts +9 -0
  155. package/dist/types/src/wallet/index.d.ts.map +1 -0
  156. package/dist/types/src/wallet/substrates/HTTPWalletWire.d.ts +9 -0
  157. package/dist/types/src/wallet/substrates/HTTPWalletWire.d.ts.map +1 -0
  158. package/dist/types/src/wallet/substrates/WalletWire.d.ts +7 -0
  159. package/dist/types/src/wallet/substrates/WalletWire.d.ts.map +1 -0
  160. package/dist/types/src/wallet/substrates/WalletWireCalls.d.ts +33 -0
  161. package/dist/types/src/wallet/substrates/WalletWireCalls.d.ts.map +1 -0
  162. package/dist/types/src/wallet/substrates/WalletWireProcessor.d.ts +18 -0
  163. package/dist/types/src/wallet/substrates/WalletWireProcessor.d.ts.map +1 -0
  164. package/dist/types/src/wallet/substrates/WalletWireTransceiver.d.ts +196 -0
  165. package/dist/types/src/wallet/substrates/WalletWireTransceiver.d.ts.map +1 -0
  166. package/dist/types/src/wallet/substrates/XDM.d.ts +412 -0
  167. package/dist/types/src/wallet/substrates/XDM.d.ts.map +1 -0
  168. package/dist/types/src/wallet/substrates/index.d.ts +8 -0
  169. package/dist/types/src/wallet/substrates/index.d.ts.map +1 -0
  170. package/dist/types/src/wallet/substrates/window.CWI.d.ts +410 -0
  171. package/dist/types/src/wallet/substrates/window.CWI.d.ts.map +1 -0
  172. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  173. package/dist/umd/bundle.js +1 -1
  174. package/docs/overlay-tools.md +551 -0
  175. package/docs/script.md +135 -0
  176. package/docs/totp.md +119 -0
  177. package/docs/wallet-substrates.md +10 -0
  178. package/docs/wallet.md +4182 -0
  179. package/mod.ts +5 -1
  180. package/package.json +44 -3
  181. package/src/auth/Certificate.ts +233 -0
  182. package/src/auth/__tests/Certificate.test.ts +282 -0
  183. package/src/auth/index.ts +1 -0
  184. package/src/overlay-tools/LookupResolver.ts +228 -0
  185. package/src/overlay-tools/OverlayAdminTokenTemplate.ts +79 -0
  186. package/src/overlay-tools/SHIPBroadcaster.ts +405 -0
  187. package/src/overlay-tools/__tests/LookupResolver.test.ts +1403 -0
  188. package/src/overlay-tools/__tests/OverlayAdminTokenTemplate.test.ts +69 -0
  189. package/src/overlay-tools/__tests/SHIPBroadcaster.test.ts +904 -0
  190. package/src/overlay-tools/index.ts +5 -0
  191. package/src/script/templates/PushDrop.ts +246 -0
  192. package/src/script/templates/__tests/PushDrop.test.ts +158 -0
  193. package/src/script/templates/index.ts +1 -0
  194. package/src/transaction/http/DefaultHttpClient.ts +1 -1
  195. package/src/wallet/CachedKeyDeriver.ts +193 -0
  196. package/src/wallet/KeyDeriver.ts +178 -0
  197. package/src/wallet/ProtoWallet.ts +732 -0
  198. package/src/wallet/Wallet.interfaces.ts +1170 -0
  199. package/src/wallet/WalletClient.ts +201 -0
  200. package/src/wallet/WalletError.ts +27 -0
  201. package/src/wallet/__tests/CachedKeyDeriver.test.ts +322 -0
  202. package/src/wallet/__tests/KeyDeriver.test.ts +118 -0
  203. package/src/wallet/__tests/ProtoWallet.test.ts +543 -0
  204. package/src/wallet/index.ts +8 -0
  205. package/src/wallet/substrates/HTTPWalletWire.ts +47 -0
  206. package/src/wallet/substrates/WalletWire.ts +6 -0
  207. package/src/wallet/substrates/WalletWireCalls.ts +34 -0
  208. package/src/wallet/substrates/WalletWireProcessor.ts +2046 -0
  209. package/src/wallet/substrates/WalletWireTransceiver.ts +1454 -0
  210. package/src/wallet/substrates/XDM.ts +157 -0
  211. package/src/wallet/substrates/__tests/WalletWire.integration.test.ts +2194 -0
  212. package/src/wallet/substrates/__tests/XDM.test.ts +659 -0
  213. package/src/wallet/substrates/index.ts +7 -0
  214. package/src/wallet/substrates/window.CWI.ts +133 -0
@@ -0,0 +1,1403 @@
1
+ import LookupResolver, { HTTPSOverlayLookupFacilitator } from '../../../dist/cjs/src/overlay-tools/LookupResolver.js'
2
+ import OverlayAdminTokenTemplate from '../../../dist/cjs/src//overlay-tools/OverlayAdminTokenTemplate.js'
3
+ import ProtoWallet from '../../../dist/cjs/src/wallet/ProtoWallet.js'
4
+ import { PrivateKey } from '../../../dist/cjs/src/primitives/index.js'
5
+ import { Transaction } from '../../../dist/cjs/src/transaction/index.js'
6
+ import { LockingScript } from '../../../dist/cjs/src/script/index.js'
7
+
8
+ const mockFacilitator = {
9
+ lookup: jest.fn()
10
+ }
11
+
12
+ const sampleBeef1 = new Transaction(1, [], [{ lockingScript: LockingScript.fromHex('88'), satoshis: 1 }], 0).toBEEF()
13
+ const sampleBeef2 = new Transaction(1, [], [{ lockingScript: LockingScript.fromHex('88'), satoshis: 2 }], 0).toBEEF()
14
+ const sampleBeef3 = new Transaction(1, [], [{ lockingScript: LockingScript.fromHex('88'), satoshis: 3 }], 0).toBEEF()
15
+ const sampleBeef4 = new Transaction(1, [], [{ lockingScript: LockingScript.fromHex('88'), satoshis: 4 }], 0).toBEEF()
16
+
17
+ describe('LookupResolver', () => {
18
+ beforeEach(() => {
19
+ mockFacilitator.lookup.mockReset()
20
+ })
21
+
22
+ it('should query the host and return the response when a single host is found via SLAP', async () => {
23
+ const slapHostKey = new PrivateKey(42)
24
+ const slapWallet = new ProtoWallet(slapHostKey)
25
+ const slapLib = new OverlayAdminTokenTemplate(slapWallet)
26
+ const slapScript = await slapLib.lock('SLAP', 'https://slaphost.com', 'ls_foo')
27
+ const slapTx = new Transaction(1, [], [{
28
+ lockingScript: slapScript,
29
+ satoshis: 1
30
+ }], 0)
31
+
32
+ mockFacilitator.lookup.mockReturnValueOnce({
33
+ type: 'output-list',
34
+ outputs: [{
35
+ outputIndex: 0,
36
+ beef: slapTx.toBEEF()
37
+ }]
38
+ }).mockReturnValueOnce({
39
+ type: 'output-list',
40
+ outputs: [{
41
+ beef: sampleBeef1,
42
+ outputIndex: 0
43
+ }]
44
+ })
45
+
46
+ const r = new LookupResolver({ facilitator: mockFacilitator, slapTrackers: ['https://mock.slap'] })
47
+ const res = await r.query({
48
+ service: 'ls_foo',
49
+ query: { test: 1 }
50
+ })
51
+ expect(res).toEqual({
52
+ type: 'output-list',
53
+ outputs: [{
54
+ beef: sampleBeef1,
55
+ outputIndex: 0
56
+ }]
57
+ })
58
+ expect(mockFacilitator.lookup.mock.calls).toEqual([
59
+ [
60
+ 'https://mock.slap',
61
+ {
62
+ service: 'ls_slap',
63
+ query: {
64
+ service: 'ls_foo'
65
+ }
66
+ }
67
+ ],
68
+ [
69
+ 'https://slaphost.com',
70
+ {
71
+ service: 'ls_foo',
72
+ query: {
73
+ test: 1
74
+ }
75
+ }
76
+ ]
77
+ ])
78
+ })
79
+
80
+ it('should query from provided additional hosts while still making use of SLAP', async () => {
81
+ const slapHostKey = new PrivateKey(42)
82
+ const slapWallet = new ProtoWallet(slapHostKey)
83
+ const slapLib = new OverlayAdminTokenTemplate(slapWallet)
84
+ const slapScript = await slapLib.lock('SLAP', 'https://slaphost.com', 'ls_foo')
85
+ const slapTx = new Transaction(1, [], [{
86
+ lockingScript: slapScript,
87
+ satoshis: 1
88
+ }], 0)
89
+
90
+ mockFacilitator.lookup.mockReturnValueOnce({
91
+ type: 'output-list',
92
+ outputs: [{
93
+ outputIndex: 0,
94
+ beef: slapTx.toBEEF()
95
+ }]
96
+ }).mockReturnValueOnce({
97
+ type: 'output-list',
98
+ outputs: [{
99
+ beef: sampleBeef1,
100
+ outputIndex: 0
101
+ }]
102
+ }).mockReturnValueOnce({
103
+ type: 'output-list',
104
+ outputs: [{ // duplicate the output the other host knows about
105
+ beef: sampleBeef1,
106
+ outputIndex: 0
107
+ }, { // the additional host also knows about a second output
108
+ beef: sampleBeef2,
109
+ outputIndex: 1033
110
+ }]
111
+ })
112
+
113
+ const r = new LookupResolver({
114
+ facilitator: mockFacilitator,
115
+ slapTrackers: ['https://mock.slap'],
116
+ additionalHosts: {
117
+ ls_foo: ['https://additional.host']
118
+ }
119
+ })
120
+ const res = await r.query({
121
+ service: 'ls_foo',
122
+ query: { test: 1 }
123
+ })
124
+ expect(res).toEqual({
125
+ type: 'output-list',
126
+ outputs: [{ // expect the first output to appear only once, and be de-duplicated
127
+ beef: sampleBeef1,
128
+ outputIndex: 0
129
+ }, { // also expect the second output from the additional host
130
+ beef: sampleBeef2,
131
+ outputIndex: 1033
132
+ }]
133
+ })
134
+ expect(mockFacilitator.lookup.mock.calls).toEqual([
135
+ [
136
+ 'https://mock.slap',
137
+ {
138
+ service: 'ls_slap',
139
+ query: {
140
+ service: 'ls_foo'
141
+ }
142
+ }
143
+ ],
144
+ [
145
+ 'https://slaphost.com',
146
+ {
147
+ service: 'ls_foo',
148
+ query: {
149
+ test: 1
150
+ }
151
+ }
152
+ ],
153
+ [ // additional host should also have been queried
154
+ 'https://additional.host',
155
+ {
156
+ service: 'ls_foo',
157
+ query: {
158
+ test: 1
159
+ }
160
+ }
161
+ ]
162
+ ])
163
+ })
164
+
165
+ it('should utilize host overrides instead of SLAP', async () => {
166
+ mockFacilitator.lookup.mockReturnValueOnce({
167
+ type: 'output-list',
168
+ outputs: [{
169
+ beef: sampleBeef1,
170
+ outputIndex: 0
171
+ }]
172
+ })
173
+
174
+ const r = new LookupResolver({
175
+ facilitator: mockFacilitator,
176
+ slapTrackers: ['https://mock.slap'],
177
+ hostOverrides: {
178
+ ls_foo: ['https://override.host']
179
+ }
180
+ })
181
+ const res = await r.query({
182
+ service: 'ls_foo',
183
+ query: { test: 1 }
184
+ })
185
+ expect(res).toEqual({
186
+ type: 'output-list',
187
+ outputs: [{
188
+ beef: sampleBeef1,
189
+ outputIndex: 0
190
+ }]
191
+ })
192
+ expect(mockFacilitator.lookup.mock.calls).toEqual([
193
+ [
194
+ 'https://override.host',
195
+ {
196
+ service: 'ls_foo',
197
+ query: {
198
+ test: 1
199
+ }
200
+ }
201
+ ]
202
+ ])
203
+ })
204
+
205
+ it('should allow using host overrides with additional hosts at the same time', async () => {
206
+ mockFacilitator.lookup.mockReturnValueOnce({
207
+ type: 'output-list', // from the override host
208
+ outputs: [{
209
+ beef: sampleBeef1,
210
+ outputIndex: 0
211
+ }]
212
+ }).mockReturnValueOnce({
213
+ type: 'output-list', // from the additional host
214
+ outputs: [{
215
+ beef: sampleBeef1,
216
+ outputIndex: 0
217
+ }, {
218
+ beef: sampleBeef2,
219
+ outputIndex: 1033
220
+ }]
221
+ })
222
+
223
+ const r = new LookupResolver({
224
+ facilitator: mockFacilitator,
225
+ slapTrackers: ['https://mock.slap'],
226
+ additionalHosts: {
227
+ ls_foo: ['https://additional.host']
228
+ },
229
+ hostOverrides: {
230
+ ls_foo: ['https://override.host']
231
+ }
232
+ })
233
+ const res = await r.query({
234
+ service: 'ls_foo',
235
+ query: { test: 1 }
236
+ })
237
+ expect(res).toEqual({
238
+ type: 'output-list',
239
+ outputs: [{ // expect the first output to appear only once, and be de-duplicated
240
+ beef: sampleBeef1,
241
+ outputIndex: 0
242
+ }, { // also expect the second output from the additional host
243
+ beef: sampleBeef2,
244
+ outputIndex: 1033
245
+ }]
246
+ })
247
+ expect(mockFacilitator.lookup.mock.calls).toEqual([
248
+ [
249
+ 'https://override.host',
250
+ {
251
+ service: 'ls_foo',
252
+ query: {
253
+ test: 1
254
+ }
255
+ }
256
+ ],
257
+ [ // additional host should also have been queried
258
+ 'https://additional.host',
259
+ {
260
+ service: 'ls_foo',
261
+ query: {
262
+ test: 1
263
+ }
264
+ }
265
+ ]
266
+ ])
267
+ })
268
+
269
+ it('should handle multiple SLAP trackers and aggregate results from multiple hosts', async () => {
270
+ const slapHostKey1 = new PrivateKey(42)
271
+ const slapWallet1 = new ProtoWallet(slapHostKey1)
272
+ const slapLib1 = new OverlayAdminTokenTemplate(slapWallet1)
273
+ const slapScript1 = await slapLib1.lock('SLAP', 'https://slaphost1.com', 'ls_foo')
274
+ const slapTx1 = new Transaction(1, [], [{
275
+ lockingScript: slapScript1,
276
+ satoshis: 1
277
+ }], 0)
278
+
279
+ const slapHostKey2 = new PrivateKey(43)
280
+ const slapWallet2 = new ProtoWallet(slapHostKey2)
281
+ const slapLib2 = new OverlayAdminTokenTemplate(slapWallet2)
282
+ const slapScript2 = await slapLib2.lock('SLAP', 'https://slaphost2.com', 'ls_foo')
283
+ const slapTx2 = new Transaction(1, [], [{
284
+ lockingScript: slapScript2,
285
+ satoshis: 1
286
+ }], 0)
287
+
288
+ // SLAP trackers return hosts
289
+ mockFacilitator.lookup.mockReturnValueOnce({
290
+ type: 'output-list',
291
+ outputs: [{
292
+ outputIndex: 0,
293
+ beef: slapTx1.toBEEF()
294
+ }]
295
+ }).mockReturnValueOnce({
296
+ type: 'output-list',
297
+ outputs: [{
298
+ outputIndex: 0,
299
+ beef: slapTx2.toBEEF()
300
+ }]
301
+ })
302
+
303
+ // Hosts respond to the query
304
+ mockFacilitator.lookup.mockReturnValueOnce({
305
+ type: 'output-list',
306
+ outputs: [{
307
+ beef: sampleBeef3,
308
+ outputIndex: 0
309
+ }]
310
+ }).mockReturnValueOnce({
311
+ type: 'output-list',
312
+ outputs: [{
313
+ beef: sampleBeef4,
314
+ outputIndex: 1
315
+ }]
316
+ })
317
+
318
+ const r = new LookupResolver({
319
+ facilitator: mockFacilitator,
320
+ slapTrackers: ['https://mock.slap1', 'https://mock.slap2']
321
+ })
322
+
323
+ const res = await r.query({
324
+ service: 'ls_foo',
325
+ query: { test: 1 }
326
+ })
327
+
328
+ expect(res).toEqual({
329
+ type: 'output-list',
330
+ outputs: [
331
+ { beef: sampleBeef3, outputIndex: 0 },
332
+ { beef: sampleBeef4, outputIndex: 1 }
333
+ ]
334
+ })
335
+
336
+ expect(mockFacilitator.lookup.mock.calls).toEqual([
337
+ [
338
+ 'https://mock.slap1',
339
+ {
340
+ service: 'ls_slap',
341
+ query: {
342
+ service: 'ls_foo'
343
+ }
344
+ }
345
+ ],
346
+ [
347
+ 'https://mock.slap2',
348
+ {
349
+ service: 'ls_slap',
350
+ query: {
351
+ service: 'ls_foo'
352
+ }
353
+ }
354
+ ],
355
+ [
356
+ 'https://slaphost1.com',
357
+ {
358
+ service: 'ls_foo',
359
+ query: {
360
+ test: 1
361
+ }
362
+ }
363
+ ],
364
+ [
365
+ 'https://slaphost2.com',
366
+ {
367
+ service: 'ls_foo',
368
+ query: {
369
+ test: 1
370
+ }
371
+ }
372
+ ]
373
+ ])
374
+ })
375
+
376
+ it('should de-duplicate outputs from multiple hosts', async () => {
377
+ const slapHostKey1 = new PrivateKey(42)
378
+ const slapWallet1 = new ProtoWallet(slapHostKey1)
379
+ const slapLib1 = new OverlayAdminTokenTemplate(slapWallet1)
380
+ const slapScript1 = await slapLib1.lock('SLAP', 'https://slaphost1.com', 'ls_foo')
381
+ const slapTx1 = new Transaction(1, [], [{
382
+ lockingScript: slapScript1,
383
+ satoshis: 1
384
+ }], 0)
385
+
386
+ const slapHostKey2 = new PrivateKey(43)
387
+ const slapWallet2 = new ProtoWallet(slapHostKey2)
388
+ const slapLib2 = new OverlayAdminTokenTemplate(slapWallet2)
389
+ const slapScript2 = await slapLib2.lock('SLAP', 'https://slaphost2.com', 'ls_foo')
390
+ const slapTx2 = new Transaction(1, [], [{
391
+ lockingScript: slapScript2,
392
+ satoshis: 1
393
+ }], 0)
394
+
395
+ // SLAP tracker returns two hosts
396
+ mockFacilitator.lookup.mockReturnValueOnce({
397
+ type: 'output-list',
398
+ outputs: [
399
+ { outputIndex: 0, beef: slapTx1.toBEEF() },
400
+ { outputIndex: 0, beef: slapTx2.toBEEF() }
401
+ ]
402
+ })
403
+
404
+ // Both hosts return the same output
405
+ const duplicateOutput = { beef: sampleBeef3, outputIndex: 0 }
406
+ mockFacilitator.lookup.mockReturnValueOnce({
407
+ type: 'output-list',
408
+ outputs: [duplicateOutput]
409
+ }).mockReturnValueOnce({
410
+ type: 'output-list',
411
+ outputs: [duplicateOutput]
412
+ })
413
+
414
+ const r = new LookupResolver({
415
+ facilitator: mockFacilitator,
416
+ slapTrackers: ['https://mock.slap']
417
+ })
418
+
419
+ const res = await r.query({
420
+ service: 'ls_foo',
421
+ query: { test: 1 }
422
+ })
423
+
424
+ expect(res).toEqual({
425
+ type: 'output-list',
426
+ outputs: [duplicateOutput]
427
+ })
428
+
429
+ expect(mockFacilitator.lookup.mock.calls).toEqual([
430
+ [
431
+ 'https://mock.slap',
432
+ {
433
+ service: 'ls_slap',
434
+ query: {
435
+ service: 'ls_foo'
436
+ }
437
+ }
438
+ ],
439
+ [
440
+ 'https://slaphost1.com',
441
+ {
442
+ service: 'ls_foo',
443
+ query: {
444
+ test: 1
445
+ }
446
+ }
447
+ ],
448
+ [
449
+ 'https://slaphost2.com',
450
+ {
451
+ service: 'ls_foo',
452
+ query: {
453
+ test: 1
454
+ }
455
+ }
456
+ ]
457
+ ])
458
+ })
459
+
460
+ it('should handle hosts returning different response types', async () => {
461
+ const slapHostKey1 = new PrivateKey(42)
462
+ const slapWallet1 = new ProtoWallet(slapHostKey1)
463
+ const slapLib1 = new OverlayAdminTokenTemplate(slapWallet1)
464
+ const slapScript1 = await slapLib1.lock('SLAP', 'https://slaphost1.com', 'ls_foo')
465
+ const slapTx1 = new Transaction(1, [], [{
466
+ lockingScript: slapScript1,
467
+ satoshis: 1
468
+ }], 0)
469
+
470
+ const slapHostKey2 = new PrivateKey(43)
471
+ const slapWallet2 = new ProtoWallet(slapHostKey2)
472
+ const slapLib2 = new OverlayAdminTokenTemplate(slapWallet2)
473
+ const slapScript2 = await slapLib2.lock('SLAP', 'https://slaphost2.com', 'ls_foo')
474
+ const slapTx2 = new Transaction(1, [], [{
475
+ lockingScript: slapScript2,
476
+ satoshis: 1
477
+ }], 0)
478
+
479
+ // SLAP tracker returns two hosts
480
+ mockFacilitator.lookup.mockReturnValueOnce({
481
+ type: 'output-list',
482
+ outputs: [
483
+ { outputIndex: 0, beef: slapTx1.toBEEF() },
484
+ { outputIndex: 0, beef: slapTx2.toBEEF() }
485
+ ]
486
+ })
487
+
488
+ // First host returns 'freeform' response
489
+ mockFacilitator.lookup.mockReturnValueOnce({
490
+ type: 'freeform',
491
+ result: { message: 'Freeform response from host1' }
492
+ })
493
+
494
+ // Second host returns 'output-list' response
495
+ mockFacilitator.lookup.mockReturnValueOnce({
496
+ type: 'output-list',
497
+ outputs: [{ beef: sampleBeef3, outputIndex: 0 }]
498
+ })
499
+
500
+ const r = new LookupResolver({
501
+ facilitator: mockFacilitator,
502
+ slapTrackers: ['https://mock.slap']
503
+ })
504
+
505
+ const res = await r.query({
506
+ service: 'ls_foo',
507
+ query: { test: 1 }
508
+ })
509
+
510
+ // Since the first response is 'freeform', it should return that response
511
+ expect(res).toEqual({
512
+ type: 'freeform',
513
+ result: { message: 'Freeform response from host1' }
514
+ })
515
+
516
+ expect(mockFacilitator.lookup.mock.calls).toEqual([
517
+ [
518
+ 'https://mock.slap',
519
+ {
520
+ service: 'ls_slap',
521
+ query: {
522
+ service: 'ls_foo'
523
+ }
524
+ }
525
+ ],
526
+ [
527
+ 'https://slaphost1.com',
528
+ {
529
+ service: 'ls_foo',
530
+ query: {
531
+ test: 1
532
+ }
533
+ }
534
+ ],
535
+ [
536
+ 'https://slaphost2.com',
537
+ {
538
+ service: 'ls_foo',
539
+ query: {
540
+ test: 1
541
+ }
542
+ }
543
+ ]
544
+ ])
545
+ })
546
+
547
+ it('should ignore freeform responses when first response is output-list', async () => {
548
+ const slapHostKey1 = new PrivateKey(42)
549
+ const slapWallet1 = new ProtoWallet(slapHostKey1)
550
+ const slapLib1 = new OverlayAdminTokenTemplate(slapWallet1)
551
+ const slapScript1 = await slapLib1.lock('SLAP', 'https://slaphost1.com', 'ls_foo')
552
+ const slapTx1 = new Transaction(1, [], [{
553
+ lockingScript: slapScript1,
554
+ satoshis: 1
555
+ }], 0)
556
+
557
+ const slapHostKey2 = new PrivateKey(43)
558
+ const slapWallet2 = new ProtoWallet(slapHostKey2)
559
+ const slapLib2 = new OverlayAdminTokenTemplate(slapWallet2)
560
+ const slapScript2 = await slapLib2.lock('SLAP', 'https://slaphost2.com', 'ls_foo')
561
+ const slapTx2 = new Transaction(1, [], [{
562
+ lockingScript: slapScript2,
563
+ satoshis: 1
564
+ }], 0)
565
+
566
+ // SLAP tracker returns two hosts
567
+ mockFacilitator.lookup.mockReturnValueOnce({
568
+ type: 'output-list',
569
+ outputs: [
570
+ { outputIndex: 0, beef: slapTx1.toBEEF() },
571
+ { outputIndex: 0, beef: slapTx2.toBEEF() }
572
+ ]
573
+ })
574
+
575
+ // First host returns 'output-list' response
576
+ mockFacilitator.lookup.mockReturnValueOnce({
577
+ type: 'output-list',
578
+ outputs: [{ beef: sampleBeef3, outputIndex: 0 }]
579
+ })
580
+
581
+ // Second host returns 'freeform' response
582
+ mockFacilitator.lookup.mockReturnValueOnce({
583
+ type: 'freeform',
584
+ result: { message: 'Freeform response from host2' }
585
+ })
586
+
587
+ const r = new LookupResolver({
588
+ facilitator: mockFacilitator,
589
+ slapTrackers: ['https://mock.slap']
590
+ })
591
+
592
+ const res = await r.query({
593
+ service: 'ls_foo',
594
+ query: { test: 1 }
595
+ })
596
+
597
+ expect(res).toEqual({
598
+ type: 'output-list',
599
+ outputs: [{ beef: sampleBeef3, outputIndex: 0 }]
600
+ })
601
+
602
+ expect(mockFacilitator.lookup.mock.calls).toEqual([
603
+ [
604
+ 'https://mock.slap',
605
+ {
606
+ service: 'ls_slap',
607
+ query: {
608
+ service: 'ls_foo'
609
+ }
610
+ }
611
+ ],
612
+ [
613
+ 'https://slaphost1.com',
614
+ {
615
+ service: 'ls_foo',
616
+ query: {
617
+ test: 1
618
+ }
619
+ }
620
+ ],
621
+ [
622
+ 'https://slaphost2.com',
623
+ {
624
+ service: 'ls_foo',
625
+ query: {
626
+ test: 1
627
+ }
628
+ }
629
+ ]
630
+ ])
631
+ })
632
+
633
+ it('should throw an error when no competent hosts are found', async () => {
634
+ // SLAP tracker returns empty output-list
635
+ mockFacilitator.lookup.mockReturnValueOnce({
636
+ type: 'output-list',
637
+ outputs: []
638
+ })
639
+
640
+ const r = new LookupResolver({
641
+ facilitator: mockFacilitator,
642
+ slapTrackers: ['https://mock.slap']
643
+ })
644
+
645
+ await expect(r.query({
646
+ service: 'ls_foo',
647
+ query: { test: 1 }
648
+ })).rejects.toThrow('No competent hosts found by the SLAP trackers for lookup service: ls_foo')
649
+
650
+ expect(mockFacilitator.lookup.mock.calls).toEqual([
651
+ [
652
+ 'https://mock.slap',
653
+ {
654
+ service: 'ls_slap',
655
+ query: {
656
+ service: 'ls_foo'
657
+ }
658
+ }
659
+ ]
660
+ ])
661
+ })
662
+
663
+ it('should not throw an error when one host fails to respond', async () => {
664
+ const slapHostKey1 = new PrivateKey(42)
665
+ const slapWallet1 = new ProtoWallet(slapHostKey1)
666
+ const slapLib1 = new OverlayAdminTokenTemplate(slapWallet1)
667
+ const slapScript1 = await slapLib1.lock('SLAP', 'https://slaphost1.com', 'ls_foo')
668
+ const slapTx1 = new Transaction(1, [], [{
669
+ lockingScript: slapScript1,
670
+ satoshis: 1
671
+ }], 0)
672
+
673
+ const slapHostKey2 = new PrivateKey(43)
674
+ const slapWallet2 = new ProtoWallet(slapHostKey2)
675
+ const slapLib2 = new OverlayAdminTokenTemplate(slapWallet2)
676
+ const slapScript2 = await slapLib2.lock('SLAP', 'https://slaphost2.com', 'ls_foo')
677
+ const slapTx2 = new Transaction(1, [], [{
678
+ lockingScript: slapScript2,
679
+ satoshis: 1
680
+ }], 0)
681
+
682
+ // SLAP tracker returns two hosts
683
+ mockFacilitator.lookup.mockReturnValueOnce({
684
+ type: 'output-list',
685
+ outputs: [
686
+ { outputIndex: 0, beef: slapTx1.toBEEF() },
687
+ { outputIndex: 0, beef: slapTx2.toBEEF() }
688
+ ]
689
+ })
690
+
691
+ // First host responds successfully
692
+ mockFacilitator.lookup.mockReturnValueOnce({
693
+ type: 'output-list',
694
+ outputs: [{ beef: sampleBeef3, outputIndex: 0 }]
695
+ })
696
+
697
+ // Second host fails to respond
698
+ mockFacilitator.lookup.mockImplementationOnce(async () => {
699
+ throw new Error('Host2 failed to respond')
700
+ })
701
+
702
+ const r = new LookupResolver({
703
+ facilitator: mockFacilitator,
704
+ slapTrackers: ['https://mock.slap']
705
+ })
706
+
707
+ const res = await r.query({
708
+ service: 'ls_foo',
709
+ query: { test: 1 }
710
+ })
711
+
712
+ expect(res).toEqual({
713
+ type: 'output-list',
714
+ outputs: [{
715
+ beef: sampleBeef3,
716
+ outputIndex: 0
717
+ }]
718
+ })
719
+
720
+ expect(mockFacilitator.lookup.mock.calls).toEqual([
721
+ [
722
+ 'https://mock.slap',
723
+ {
724
+ service: 'ls_slap',
725
+ query: {
726
+ service: 'ls_foo'
727
+ }
728
+ }
729
+ ],
730
+ [
731
+ 'https://slaphost1.com',
732
+ {
733
+ service: 'ls_foo',
734
+ query: {
735
+ test: 1
736
+ }
737
+ }
738
+ ],
739
+ [
740
+ 'https://slaphost2.com',
741
+ {
742
+ service: 'ls_foo',
743
+ query: {
744
+ test: 1
745
+ }
746
+ }
747
+ ]
748
+ ])
749
+ })
750
+
751
+ it('Directly uses SLAP resolvers to facilitate SLAP queries', async () => {
752
+ mockFacilitator.lookup.mockReturnValueOnce({
753
+ type: 'output-list',
754
+ outputs: [{
755
+ beef: sampleBeef1,
756
+ outputIndex: 0
757
+ }]
758
+ })
759
+
760
+ const r = new LookupResolver({ facilitator: mockFacilitator, slapTrackers: ['https://mock.slap'] })
761
+ const res = await r.query({
762
+ service: 'ls_slap',
763
+ query: { test: 1 }
764
+ })
765
+ expect(res).toEqual({
766
+ type: 'output-list',
767
+ outputs: [{
768
+ beef: sampleBeef1,
769
+ outputIndex: 0
770
+ }]
771
+ })
772
+ expect(mockFacilitator.lookup.mock.calls).toEqual([
773
+ [
774
+ 'https://mock.slap',
775
+ {
776
+ service: 'ls_slap',
777
+ query: {
778
+ test: 1
779
+ }
780
+ }
781
+ ]
782
+ ])
783
+ })
784
+
785
+ it('should throw an error when SLAP tracker returns invalid response', async () => {
786
+ // SLAP tracker returns 'freeform' response
787
+ mockFacilitator.lookup.mockReturnValueOnce({
788
+ type: 'freeform',
789
+ result: { message: 'Invalid response' }
790
+ })
791
+
792
+ const r = new LookupResolver({
793
+ facilitator: mockFacilitator,
794
+ slapTrackers: ['https://mock.slap']
795
+ })
796
+
797
+ // Because a freeform response is not valid, the SLAP trackers have not found any competent hosts.
798
+ await expect(r.query({
799
+ service: 'ls_foo',
800
+ query: { test: 1 }
801
+ })).rejects.toThrow('No competent hosts found by the SLAP trackers for lookup service: ls_foo')
802
+
803
+ expect(mockFacilitator.lookup.mock.calls).toEqual([
804
+ [
805
+ 'https://mock.slap',
806
+ {
807
+ service: 'ls_slap',
808
+ query: {
809
+ service: 'ls_foo'
810
+ }
811
+ }
812
+ ]
813
+ ])
814
+ })
815
+
816
+ it('should throw an error when HTTPSOverlayLookupFacilitator is used with non-HTTPS URL', async () => {
817
+ const facilitator = new HTTPSOverlayLookupFacilitator()
818
+ await expect(facilitator.lookup('http://insecure.url', { service: 'test', query: {} }))
819
+ .rejects.toThrow('HTTPS facilitator can only use URLs that start with "https:"')
820
+ })
821
+ describe('LookupResolver Resiliency', () => {
822
+ beforeEach(() => {
823
+ mockFacilitator.lookup.mockReset()
824
+ })
825
+
826
+ it('should continue to function when one SLAP tracker fails', async () => {
827
+ const slapHostKey = new PrivateKey(42)
828
+ const slapWallet = new ProtoWallet(slapHostKey)
829
+ const slapLib = new OverlayAdminTokenTemplate(slapWallet)
830
+ const slapScript = await slapLib.lock('SLAP', 'https://slaphost.com', 'ls_foo')
831
+ const slapTx = new Transaction(1, [], [{
832
+ lockingScript: slapScript,
833
+ satoshis: 1
834
+ }], 0)
835
+
836
+ // First SLAP tracker fails
837
+ mockFacilitator.lookup.mockImplementationOnce(async () => {
838
+ throw new Error('SLAP tracker failed')
839
+ })
840
+
841
+ // Second SLAP tracker succeeds
842
+ mockFacilitator.lookup.mockReturnValueOnce({
843
+ type: 'output-list',
844
+ outputs: [{
845
+ outputIndex: 0,
846
+ beef: slapTx.toBEEF()
847
+ }]
848
+ })
849
+
850
+ // Host responds successfully
851
+ mockFacilitator.lookup.mockReturnValueOnce({
852
+ type: 'output-list',
853
+ outputs: [{
854
+ beef: sampleBeef3,
855
+ outputIndex: 0
856
+ }]
857
+ })
858
+
859
+ const r = new LookupResolver({
860
+ facilitator: mockFacilitator,
861
+ slapTrackers: ['https://mock.slap1', 'https://mock.slap2']
862
+ })
863
+
864
+ const res = await r.query({
865
+ service: 'ls_foo',
866
+ query: { test: 1 }
867
+ })
868
+
869
+ expect(res).toEqual({
870
+ type: 'output-list',
871
+ outputs: [{
872
+ beef: sampleBeef3,
873
+ outputIndex: 0
874
+ }]
875
+ })
876
+
877
+ expect(mockFacilitator.lookup.mock.calls).toEqual([
878
+ [
879
+ 'https://mock.slap1',
880
+ {
881
+ service: 'ls_slap',
882
+ query: {
883
+ service: 'ls_foo'
884
+ }
885
+ }
886
+ ],
887
+ [
888
+ 'https://mock.slap2',
889
+ {
890
+ service: 'ls_slap',
891
+ query: {
892
+ service: 'ls_foo'
893
+ }
894
+ }
895
+ ],
896
+ [
897
+ 'https://slaphost.com',
898
+ {
899
+ service: 'ls_foo',
900
+ query: {
901
+ test: 1
902
+ }
903
+ }
904
+ ]
905
+ ])
906
+ })
907
+
908
+ it('should aggregate outputs from hosts that respond, even if some SLAP trackers lie to our face', async () => {
909
+ const slapHostKey1 = new PrivateKey(42)
910
+ const slapWallet1 = new ProtoWallet(slapHostKey1)
911
+ const slapLib1 = new OverlayAdminTokenTemplate(slapWallet1)
912
+ const slapScript1 = await slapLib1.lock('SLAP', 'https://slaphost1.com', 'ls_foo')
913
+ const slapTx1 = new Transaction(1, [], [{
914
+ lockingScript: slapScript1,
915
+ satoshis: 1
916
+ }], 0)
917
+
918
+ const slapHostKey2 = new PrivateKey(43)
919
+ const slapWallet2 = new ProtoWallet(slapHostKey2)
920
+ const slapLib2 = new OverlayAdminTokenTemplate(slapWallet2)
921
+ const slapScript2 = await slapLib2.lock('SLAP', 'https://slaphost2.com', 'ls_foo')
922
+ const slapTx2 = new Transaction(1, [], [{
923
+ lockingScript: slapScript2,
924
+ satoshis: 1
925
+ }], 0)
926
+
927
+ const slapHostKey3 = new PrivateKey(44)
928
+ const slapWallet3 = new ProtoWallet(slapHostKey3)
929
+ const slapLib3 = new OverlayAdminTokenTemplate(slapWallet3)
930
+ const slapScript3 = await slapLib3.lock('SLAP', 'https://slaphost3.pantsonfire.com', 'ls_not_what_i_asked_you_for')
931
+ const slapTx3 = new Transaction(1, [], [{
932
+ lockingScript: slapScript3,
933
+ satoshis: 1
934
+ }], 0)
935
+
936
+ // SLAP trackers return hosts
937
+ mockFacilitator.lookup.mockReturnValueOnce({
938
+ type: 'output-list',
939
+ outputs: [
940
+ { outputIndex: 0, beef: slapTx1.toBEEF() },
941
+ { outputIndex: 0, beef: slapTx2.toBEEF() },
942
+ { outputIndex: 0, beef: slapTx3.toBEEF() }
943
+ ]
944
+ })
945
+
946
+ // First host responds successfully
947
+ mockFacilitator.lookup.mockReturnValueOnce({
948
+ type: 'output-list',
949
+ outputs: [{ beef: sampleBeef3, outputIndex: 0 }]
950
+ })
951
+
952
+ // Second host fails
953
+ mockFacilitator.lookup.mockImplementationOnce(async () => {
954
+ throw new Error('Host2 failed')
955
+ })
956
+
957
+ const r = new LookupResolver({
958
+ facilitator: mockFacilitator,
959
+ slapTrackers: ['https://mock.slap']
960
+ })
961
+
962
+ const res = await r.query({
963
+ service: 'ls_foo',
964
+ query: { test: 1 }
965
+ })
966
+
967
+ expect(res).toEqual({
968
+ type: 'output-list',
969
+ outputs: [{ beef: sampleBeef3, outputIndex: 0 }]
970
+ })
971
+
972
+ expect(mockFacilitator.lookup.mock.calls).toEqual([
973
+ [
974
+ 'https://mock.slap',
975
+ {
976
+ service: 'ls_slap',
977
+ query: {
978
+ service: 'ls_foo'
979
+ }
980
+ }
981
+ ],
982
+ [
983
+ 'https://slaphost1.com',
984
+ {
985
+ service: 'ls_foo',
986
+ query: {
987
+ test: 1
988
+ }
989
+ }
990
+ ],
991
+ [
992
+ 'https://slaphost2.com',
993
+ {
994
+ service: 'ls_foo',
995
+ query: {
996
+ test: 1
997
+ }
998
+ }
999
+ ]
1000
+ ])
1001
+ })
1002
+
1003
+ it('should aggregate outputs from hosts that respond, even if some SLAP trackers give us rotten BEEF', async () => {
1004
+ const slapHostKey1 = new PrivateKey(42)
1005
+ const slapWallet1 = new ProtoWallet(slapHostKey1)
1006
+ const slapLib1 = new OverlayAdminTokenTemplate(slapWallet1)
1007
+ const slapScript1 = await slapLib1.lock('SLAP', 'https://slaphost1.com', 'ls_foo')
1008
+ const slapTx1 = new Transaction(1, [], [{
1009
+ lockingScript: slapScript1,
1010
+ satoshis: 1
1011
+ }], 0)
1012
+
1013
+ const slapHostKey2 = new PrivateKey(43)
1014
+ const slapWallet2 = new ProtoWallet(slapHostKey2)
1015
+ const slapLib2 = new OverlayAdminTokenTemplate(slapWallet2)
1016
+ const slapScript2 = await slapLib2.lock('SLAP', 'https://slaphost2.com', 'ls_foo')
1017
+ const slapTx2 = new Transaction(1, [], [{
1018
+ lockingScript: slapScript2,
1019
+ satoshis: 1
1020
+ }], 0)
1021
+
1022
+ // SLAP trackers return hosts
1023
+ mockFacilitator.lookup.mockReturnValueOnce({
1024
+ type: 'output-list',
1025
+ outputs: [
1026
+ { outputIndex: 0, beef: slapTx1.toBEEF() },
1027
+ { outputIndex: 0, beef: slapTx2.toBEEF() },
1028
+ { outputIndex: 0, beef: [0] } // "rotten" (corrupted) BEEF
1029
+ ]
1030
+ })
1031
+
1032
+ // First host responds successfully
1033
+ mockFacilitator.lookup.mockReturnValueOnce({
1034
+ type: 'output-list',
1035
+ outputs: [{ beef: sampleBeef3, outputIndex: 0 }]
1036
+ })
1037
+
1038
+ // Second host fails
1039
+ mockFacilitator.lookup.mockImplementationOnce(async () => {
1040
+ throw new Error('Host2 failed')
1041
+ })
1042
+
1043
+ const r = new LookupResolver({
1044
+ facilitator: mockFacilitator,
1045
+ slapTrackers: ['https://mock.slap']
1046
+ })
1047
+
1048
+ const res = await r.query({
1049
+ service: 'ls_foo',
1050
+ query: { test: 1 }
1051
+ })
1052
+
1053
+ expect(res).toEqual({
1054
+ type: 'output-list',
1055
+ outputs: [{ beef: sampleBeef3, outputIndex: 0 }]
1056
+ })
1057
+
1058
+ expect(mockFacilitator.lookup.mock.calls).toEqual([
1059
+ [
1060
+ 'https://mock.slap',
1061
+ {
1062
+ service: 'ls_slap',
1063
+ query: {
1064
+ service: 'ls_foo'
1065
+ }
1066
+ }
1067
+ ],
1068
+ [
1069
+ 'https://slaphost1.com',
1070
+ {
1071
+ service: 'ls_foo',
1072
+ query: {
1073
+ test: 1
1074
+ }
1075
+ }
1076
+ ],
1077
+ [
1078
+ 'https://slaphost2.com',
1079
+ {
1080
+ service: 'ls_foo',
1081
+ query: {
1082
+ test: 1
1083
+ }
1084
+ }
1085
+ ]
1086
+ ])
1087
+ })
1088
+
1089
+ it('should aggregate outputs from hosts that respond, even if some fail', async () => {
1090
+ const slapHostKey1 = new PrivateKey(42)
1091
+ const slapWallet1 = new ProtoWallet(slapHostKey1)
1092
+ const slapLib1 = new OverlayAdminTokenTemplate(slapWallet1)
1093
+ const slapScript1 = await slapLib1.lock('SLAP', 'https://slaphost1.com', 'ls_foo')
1094
+ const slapTx1 = new Transaction(1, [], [{
1095
+ lockingScript: slapScript1,
1096
+ satoshis: 1
1097
+ }], 0)
1098
+
1099
+ const slapHostKey2 = new PrivateKey(43)
1100
+ const slapWallet2 = new ProtoWallet(slapHostKey2)
1101
+ const slapLib2 = new OverlayAdminTokenTemplate(slapWallet2)
1102
+ const slapScript2 = await slapLib2.lock('SLAP', 'https://slaphost2.com', 'ls_foo')
1103
+ const slapTx2 = new Transaction(1, [], [{
1104
+ lockingScript: slapScript2,
1105
+ satoshis: 1
1106
+ }], 0)
1107
+
1108
+ // SLAP trackers return hosts
1109
+ mockFacilitator.lookup.mockReturnValueOnce({
1110
+ type: 'output-list',
1111
+ outputs: [
1112
+ { outputIndex: 0, beef: slapTx1.toBEEF() },
1113
+ { outputIndex: 0, beef: slapTx2.toBEEF() }
1114
+ ]
1115
+ })
1116
+
1117
+ // First host responds successfully
1118
+ mockFacilitator.lookup.mockReturnValueOnce({
1119
+ type: 'output-list',
1120
+ outputs: [{ beef: sampleBeef3, outputIndex: 0 }]
1121
+ })
1122
+
1123
+ // Second host fails
1124
+ mockFacilitator.lookup.mockImplementationOnce(async () => {
1125
+ throw new Error('Host2 failed')
1126
+ })
1127
+
1128
+ const r = new LookupResolver({
1129
+ facilitator: mockFacilitator,
1130
+ slapTrackers: ['https://mock.slap']
1131
+ })
1132
+
1133
+ const res = await r.query({
1134
+ service: 'ls_foo',
1135
+ query: { test: 1 }
1136
+ })
1137
+
1138
+ expect(res).toEqual({
1139
+ type: 'output-list',
1140
+ outputs: [{ beef: sampleBeef3, outputIndex: 0 }]
1141
+ })
1142
+
1143
+ expect(mockFacilitator.lookup.mock.calls).toEqual([
1144
+ [
1145
+ 'https://mock.slap',
1146
+ {
1147
+ service: 'ls_slap',
1148
+ query: {
1149
+ service: 'ls_foo'
1150
+ }
1151
+ }
1152
+ ],
1153
+ [
1154
+ 'https://slaphost1.com',
1155
+ {
1156
+ service: 'ls_foo',
1157
+ query: {
1158
+ test: 1
1159
+ }
1160
+ }
1161
+ ],
1162
+ [
1163
+ 'https://slaphost2.com',
1164
+ {
1165
+ service: 'ls_foo',
1166
+ query: {
1167
+ test: 1
1168
+ }
1169
+ }
1170
+ ]
1171
+ ])
1172
+ })
1173
+
1174
+ it('should handle invalid responses from some hosts and continue with valid ones', async () => {
1175
+ const slapHostKey = new PrivateKey(42)
1176
+ const slapWallet = new ProtoWallet(slapHostKey)
1177
+ const slapLib = new OverlayAdminTokenTemplate(slapWallet)
1178
+ const slapScript = await slapLib.lock('SLAP', 'https://slaphost.com', 'ls_foo')
1179
+ const slapTx = new Transaction(1, [], [{
1180
+ lockingScript: slapScript,
1181
+ satoshis: 1
1182
+ }], 0)
1183
+
1184
+ // SLAP tracker returns host
1185
+ mockFacilitator.lookup.mockReturnValueOnce({
1186
+ type: 'output-list',
1187
+ outputs: [{ outputIndex: 0, beef: slapTx.toBEEF() }]
1188
+ })
1189
+
1190
+ // Host returns invalid response
1191
+ mockFacilitator.lookup.mockReturnValueOnce({
1192
+ type: 'invalid-type',
1193
+ data: {}
1194
+ })
1195
+
1196
+ const r = new LookupResolver({
1197
+ facilitator: mockFacilitator,
1198
+ slapTrackers: ['https://mock.slap']
1199
+ })
1200
+
1201
+ const res = await r.query({
1202
+ service: 'ls_foo',
1203
+ query: { test: 1 }
1204
+ })
1205
+
1206
+ // Since there are no valid outputs, expect an error
1207
+ expect(res).toEqual({
1208
+ type: 'output-list',
1209
+ outputs: []
1210
+ })
1211
+
1212
+ expect(mockFacilitator.lookup.mock.calls).toEqual([
1213
+ [
1214
+ 'https://mock.slap',
1215
+ {
1216
+ service: 'ls_slap',
1217
+ query: {
1218
+ service: 'ls_foo'
1219
+ }
1220
+ }
1221
+ ],
1222
+ [
1223
+ 'https://slaphost.com',
1224
+ {
1225
+ service: 'ls_foo',
1226
+ query: {
1227
+ test: 1
1228
+ }
1229
+ }
1230
+ ]
1231
+ ])
1232
+ })
1233
+
1234
+ it('should handle all SLAP trackers failing and throw an error', async () => {
1235
+ // Both SLAP trackers fail
1236
+ mockFacilitator.lookup.mockImplementation(async () => {
1237
+ throw new Error('SLAP tracker failed')
1238
+ })
1239
+
1240
+ const r = new LookupResolver({
1241
+ facilitator: mockFacilitator,
1242
+ slapTrackers: ['https://mock.slap1', 'https://mock.slap2']
1243
+ })
1244
+
1245
+ await expect(r.query({
1246
+ service: 'ls_foo',
1247
+ query: { test: 1 }
1248
+ })).rejects.toThrow('No competent hosts found by the SLAP trackers for lookup service: ls_foo')
1249
+
1250
+ expect(mockFacilitator.lookup.mock.calls.length).toBe(2)
1251
+ })
1252
+
1253
+ it('should handle all hosts failing and throw an error', async () => {
1254
+ const slapHostKey = new PrivateKey(42)
1255
+ const slapWallet = new ProtoWallet(slapHostKey)
1256
+ const slapLib = new OverlayAdminTokenTemplate(slapWallet)
1257
+ const slapScript = await slapLib.lock('SLAP', 'https://slaphost.com', 'ls_foo')
1258
+ const slapTx = new Transaction(1, [], [{
1259
+ lockingScript: slapScript,
1260
+ satoshis: 1
1261
+ }], 0)
1262
+
1263
+ // SLAP tracker returns host
1264
+ mockFacilitator.lookup.mockReturnValueOnce({
1265
+ type: 'output-list',
1266
+ outputs: [{ outputIndex: 0, beef: slapTx.toBEEF() }]
1267
+ })
1268
+
1269
+ // Host fails
1270
+ mockFacilitator.lookup.mockImplementationOnce(async () => {
1271
+ throw new Error('Host failed')
1272
+ })
1273
+
1274
+ const r = new LookupResolver({
1275
+ facilitator: mockFacilitator,
1276
+ slapTrackers: ['https://mock.slap']
1277
+ })
1278
+
1279
+ await expect(r.query({
1280
+ service: 'ls_foo',
1281
+ query: { test: 1 }
1282
+ })).rejects.toThrow('No successful responses from any hosts')
1283
+
1284
+ expect(mockFacilitator.lookup.mock.calls.length).toBe(2)
1285
+ })
1286
+
1287
+ it('should continue to aggregate outputs when some hosts return invalid outputs', async () => {
1288
+ const slapHostKey1 = new PrivateKey(42)
1289
+ const slapWallet1 = new ProtoWallet(slapHostKey1)
1290
+ const slapLib1 = new OverlayAdminTokenTemplate(slapWallet1)
1291
+ const slapScript1 = await slapLib1.lock('SLAP', 'https://slaphost1.com', 'ls_foo')
1292
+ const slapTx1 = new Transaction(1, [], [{
1293
+ lockingScript: slapScript1,
1294
+ satoshis: 1
1295
+ }], 0)
1296
+
1297
+ const slapHostKey2 = new PrivateKey(43)
1298
+ const slapWallet2 = new ProtoWallet(slapHostKey2)
1299
+ const slapLib2 = new OverlayAdminTokenTemplate(slapWallet2)
1300
+ const slapScript2 = await slapLib2.lock('SLAP', 'https://slaphost2.com', 'ls_foo')
1301
+ const slapTx2 = new Transaction(1, [], [{
1302
+ lockingScript: slapScript2,
1303
+ satoshis: 1
1304
+ }], 0)
1305
+
1306
+ // SLAP tracker returns two hosts
1307
+ mockFacilitator.lookup.mockReturnValueOnce({
1308
+ type: 'output-list',
1309
+ outputs: [
1310
+ { outputIndex: 0, beef: slapTx1.toBEEF() },
1311
+ { outputIndex: 0, beef: slapTx2.toBEEF() }
1312
+ ]
1313
+ })
1314
+
1315
+ // First host returns valid output
1316
+ mockFacilitator.lookup.mockReturnValueOnce({
1317
+ type: 'output-list',
1318
+ outputs: [{ beef: sampleBeef3, outputIndex: 0 }]
1319
+ })
1320
+
1321
+ // Second host returns invalid output
1322
+ mockFacilitator.lookup.mockReturnValueOnce({
1323
+ type: 'output-list',
1324
+ outputs: [{ invalid: true }]
1325
+ })
1326
+
1327
+ const r = new LookupResolver({
1328
+ facilitator: mockFacilitator,
1329
+ slapTrackers: ['https://mock.slap']
1330
+ })
1331
+
1332
+ const res = await r.query({
1333
+ service: 'ls_foo',
1334
+ query: { test: 1 }
1335
+ })
1336
+
1337
+ expect(res).toEqual({
1338
+ type: 'output-list',
1339
+ outputs: [{ beef: sampleBeef3, outputIndex: 0 }]
1340
+ })
1341
+
1342
+ expect(mockFacilitator.lookup.mock.calls.length).toBe(3)
1343
+ })
1344
+
1345
+ it('should continue to aggregate outputs when some hosts return malformed malarkie', async () => {
1346
+ const slapHostKey1 = new PrivateKey(42)
1347
+ const slapWallet1 = new ProtoWallet(slapHostKey1)
1348
+ const slapLib1 = new OverlayAdminTokenTemplate(slapWallet1)
1349
+ const slapScript1 = await slapLib1.lock('SLAP', 'https://slaphost1.com', 'ls_foo')
1350
+ const slapTx1 = new Transaction(1, [], [{
1351
+ lockingScript: slapScript1,
1352
+ satoshis: 1
1353
+ }], 0)
1354
+
1355
+ const slapHostKey2 = new PrivateKey(43)
1356
+ const slapWallet2 = new ProtoWallet(slapHostKey2)
1357
+ const slapLib2 = new OverlayAdminTokenTemplate(slapWallet2)
1358
+ const slapScript2 = await slapLib2.lock('SLAP', 'https://slaphost2.com', 'ls_foo')
1359
+ const slapTx2 = new Transaction(1, [], [{
1360
+ lockingScript: slapScript2,
1361
+ satoshis: 1
1362
+ }], 0)
1363
+
1364
+ // SLAP tracker returns two hosts
1365
+ mockFacilitator.lookup.mockReturnValueOnce({
1366
+ type: 'output-list',
1367
+ outputs: [
1368
+ { outputIndex: 0, beef: slapTx1.toBEEF() },
1369
+ { outputIndex: 0, beef: slapTx2.toBEEF() }
1370
+ ]
1371
+ })
1372
+
1373
+ // First host returns valid output
1374
+ mockFacilitator.lookup.mockReturnValueOnce({
1375
+ type: 'output-list',
1376
+ outputs: [{ beef: sampleBeef3, outputIndex: 0 }]
1377
+ })
1378
+
1379
+ // Second host returns invalid output
1380
+ mockFacilitator.lookup.mockReturnValueOnce({
1381
+ type: 'output-list',
1382
+ output: 'document.createElement('
1383
+ })
1384
+
1385
+ const r = new LookupResolver({
1386
+ facilitator: mockFacilitator,
1387
+ slapTrackers: ['https://mock.slap']
1388
+ })
1389
+
1390
+ const res = await r.query({
1391
+ service: 'ls_foo',
1392
+ query: { test: 1 }
1393
+ })
1394
+
1395
+ expect(res).toEqual({
1396
+ type: 'output-list',
1397
+ outputs: [{ beef: sampleBeef3, outputIndex: 0 }]
1398
+ })
1399
+
1400
+ expect(mockFacilitator.lookup.mock.calls.length).toBe(3)
1401
+ })
1402
+ })
1403
+ })