psf-bch-api 1.1.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.
- package/.env-local +4 -0
- package/LICENSE.md +8 -0
- package/README.md +8 -0
- package/apidoc.json +9 -0
- package/bin/server.js +183 -0
- package/dev-docs/README.md +4 -0
- package/dev-docs/creation-prompt.md +34 -0
- package/dev-docs/rest2nostr-poxy-api.plan.md +163 -0
- package/dev-docs/test-plan-for-rest2nostr.plan.md +161 -0
- package/dev-docs/unit-test-prompt.md +13 -0
- package/examples/01-create-account.js +67 -0
- package/examples/02-read-posts.js +44 -0
- package/examples/03-write-post.js +55 -0
- package/examples/04-read-alice-posts.js +49 -0
- package/examples/05-get-follow-list.js +53 -0
- package/examples/06-update-follow-list.js +63 -0
- package/examples/07-liking-event.js +59 -0
- package/examples/README.md +90 -0
- package/index.js +11 -0
- package/package.json +37 -0
- package/production/docker/Dockerfile +85 -0
- package/production/docker/cleanup-images.sh +5 -0
- package/production/docker/docker-compose.yml +19 -0
- package/production/docker/start-rest2nostr.sh +3 -0
- package/src/adapters/full-node-rpc.js +133 -0
- package/src/adapters/index.js +217 -0
- package/src/adapters/wlogger.js +79 -0
- package/src/config/env/common.js +64 -0
- package/src/config/env/development.js +7 -0
- package/src/config/env/production.js +7 -0
- package/src/config/index.js +14 -0
- package/src/controllers/index.js +56 -0
- package/src/controllers/rest-api/full-node/blockchain/controller.js +553 -0
- package/src/controllers/rest-api/full-node/blockchain/index.js +66 -0
- package/src/controllers/rest-api/index.js +55 -0
- package/src/controllers/timer-controller.js +72 -0
- package/src/entities/event.js +71 -0
- package/src/use-cases/full-node-blockchain-use-cases.js +134 -0
- package/src/use-cases/index.js +29 -0
- package/test/integration/api/event-integration.js +250 -0
- package/test/integration/api/req-integration.js +173 -0
- package/test/integration/api/subscription-integration.js +198 -0
- package/test/integration/use-cases/manage-subscription-integration.js +163 -0
- package/test/integration/use-cases/publish-event-integration.js +104 -0
- package/test/integration/use-cases/query-events-integration.js +95 -0
- package/test/unit/adapters/full-node-rpc-unit.js +122 -0
- package/test/unit/bin/server-unit.js +63 -0
- package/test/unit/controllers/blockchain-controller-unit.js +215 -0
- package/test/unit/controllers/rest-api-index-unit.js +85 -0
- package/test/unit/entities/event-unit.js +139 -0
- package/test/unit/mocks/controller-mocks.js +98 -0
- package/test/unit/mocks/event-mocks.js +194 -0
- package/test/unit/use-cases/full-node-blockchain-use-cases-unit.js +137 -0
|
@@ -0,0 +1,553 @@
|
|
|
1
|
+
/*
|
|
2
|
+
REST API Controller for the /full-node/blockchain routes.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import wlogger from '../../../../adapters/wlogger.js'
|
|
6
|
+
|
|
7
|
+
class BlockchainRESTController {
|
|
8
|
+
constructor (localConfig = {}) {
|
|
9
|
+
this.adapters = localConfig.adapters
|
|
10
|
+
if (!this.adapters) {
|
|
11
|
+
throw new Error(
|
|
12
|
+
'Instance of Adapters library required when instantiating Blockchain REST Controller.'
|
|
13
|
+
)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
this.useCases = localConfig.useCases
|
|
17
|
+
if (!this.useCases || !this.useCases.blockchain) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
'Instance of Blockchain use cases required when instantiating Blockchain REST Controller.'
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
this.blockchainUseCases = this.useCases.blockchain
|
|
24
|
+
|
|
25
|
+
// Bind functions
|
|
26
|
+
this.root = this.root.bind(this)
|
|
27
|
+
this.getBestBlockHash = this.getBestBlockHash.bind(this)
|
|
28
|
+
this.getBlockchainInfo = this.getBlockchainInfo.bind(this)
|
|
29
|
+
this.getBlockCount = this.getBlockCount.bind(this)
|
|
30
|
+
this.getBlockHeaderSingle = this.getBlockHeaderSingle.bind(this)
|
|
31
|
+
this.getBlockHeaderBulk = this.getBlockHeaderBulk.bind(this)
|
|
32
|
+
this.getChainTips = this.getChainTips.bind(this)
|
|
33
|
+
this.getDifficulty = this.getDifficulty.bind(this)
|
|
34
|
+
this.getMempoolEntrySingle = this.getMempoolEntrySingle.bind(this)
|
|
35
|
+
this.getMempoolEntryBulk = this.getMempoolEntryBulk.bind(this)
|
|
36
|
+
this.getMempoolAncestorsSingle = this.getMempoolAncestorsSingle.bind(this)
|
|
37
|
+
this.getMempoolInfo = this.getMempoolInfo.bind(this)
|
|
38
|
+
this.getRawMempool = this.getRawMempool.bind(this)
|
|
39
|
+
this.getTxOut = this.getTxOut.bind(this)
|
|
40
|
+
this.getTxOutPost = this.getTxOutPost.bind(this)
|
|
41
|
+
this.getTxOutProofSingle = this.getTxOutProofSingle.bind(this)
|
|
42
|
+
this.getTxOutProofBulk = this.getTxOutProofBulk.bind(this)
|
|
43
|
+
this.verifyTxOutProofSingle = this.verifyTxOutProofSingle.bind(this)
|
|
44
|
+
this.verifyTxOutProofBulk = this.verifyTxOutProofBulk.bind(this)
|
|
45
|
+
this.getBlock = this.getBlock.bind(this)
|
|
46
|
+
this.getBlockHash = this.getBlockHash.bind(this)
|
|
47
|
+
this.handleError = this.handleError.bind(this)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @api {get} /full-node/blockchain/ Service status
|
|
52
|
+
* @apiName BlockchainRoot
|
|
53
|
+
* @apiGroup Blockchain
|
|
54
|
+
*
|
|
55
|
+
* @apiDescription Returns the status of the blockchain service.
|
|
56
|
+
*
|
|
57
|
+
* @apiSuccess {String} status Service identifier
|
|
58
|
+
*/
|
|
59
|
+
async root (req, res) {
|
|
60
|
+
return res.status(200).json({ status: 'blockchain' })
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @api {get} /full-node/blockchain/getBestBlockHash Get best block hash
|
|
65
|
+
* @apiName GetBestBlockHash
|
|
66
|
+
* @apiGroup Blockchain
|
|
67
|
+
* @apiDescription Returns the hash of the best (tip) block in the longest block chain.
|
|
68
|
+
*
|
|
69
|
+
* @apiExample Example usage:
|
|
70
|
+
* curl -X GET "https://api.fullstack.cash/v5/blockchain/getBestBlockHash" -H "accept: application/json"
|
|
71
|
+
*
|
|
72
|
+
* @apiSuccess {String} bestBlockHash Hash of the best block
|
|
73
|
+
*/
|
|
74
|
+
async getBestBlockHash (req, res) {
|
|
75
|
+
try {
|
|
76
|
+
const result = await this.blockchainUseCases.getBestBlockHash()
|
|
77
|
+
return res.status(200).json(result)
|
|
78
|
+
} catch (err) {
|
|
79
|
+
return this.handleError(err, res)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @api {get} /full-node/blockchain/getBlockchainInfo Get blockchain info
|
|
85
|
+
* @apiName GetBlockchainInfo
|
|
86
|
+
* @apiGroup Blockchain
|
|
87
|
+
* @apiDescription Returns various state info regarding blockchain processing.
|
|
88
|
+
*/
|
|
89
|
+
async getBlockchainInfo (req, res) {
|
|
90
|
+
try {
|
|
91
|
+
const result = await this.blockchainUseCases.getBlockchainInfo()
|
|
92
|
+
return res.status(200).json(result)
|
|
93
|
+
} catch (err) {
|
|
94
|
+
return this.handleError(err, res)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* @api {get} /full-node/blockchain/getBlockCount Get block count
|
|
100
|
+
* @apiName GetBlockCount
|
|
101
|
+
* @apiGroup Blockchain
|
|
102
|
+
* @apiDescription Returns the number of blocks in the longest blockchain.
|
|
103
|
+
*/
|
|
104
|
+
async getBlockCount (req, res) {
|
|
105
|
+
try {
|
|
106
|
+
const result = await this.blockchainUseCases.getBlockCount()
|
|
107
|
+
return res.status(200).json(result)
|
|
108
|
+
} catch (err) {
|
|
109
|
+
return this.handleError(err, res)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* @api {get} /full-node/blockchain/getBlockHeader/:hash Get single block header
|
|
115
|
+
* @apiName GetSingleBlockHeader
|
|
116
|
+
* @apiGroup Blockchain
|
|
117
|
+
* @apiDescription Returns serialized block header data.
|
|
118
|
+
*
|
|
119
|
+
* @apiParam {String} hash Block hash
|
|
120
|
+
* @apiParam {Boolean} verbose Return verbose data (default false)
|
|
121
|
+
*/
|
|
122
|
+
async getBlockHeaderSingle (req, res) {
|
|
123
|
+
try {
|
|
124
|
+
const hash = req.params.hash
|
|
125
|
+
if (!hash) {
|
|
126
|
+
return res.status(400).json({ error: 'hash can not be empty' })
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const verbose = req.query.verbose?.toString() === 'true'
|
|
130
|
+
const result = await this.blockchainUseCases.getBlockHeader({ hash, verbose })
|
|
131
|
+
|
|
132
|
+
return res.status(200).json(result)
|
|
133
|
+
} catch (err) {
|
|
134
|
+
return this.handleError(err, res)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* @api {post} /full-node/blockchain/getBlockHeader Get multiple block headers
|
|
140
|
+
* @apiName GetBulkBlockHeader
|
|
141
|
+
* @apiGroup Blockchain
|
|
142
|
+
* @apiDescription Returns serialized block header data for multiple hashes.
|
|
143
|
+
*
|
|
144
|
+
* @apiParam {String[]} hashes Block hashes
|
|
145
|
+
* @apiParam {Boolean} verbose Return verbose data (default false)
|
|
146
|
+
*/
|
|
147
|
+
async getBlockHeaderBulk (req, res) {
|
|
148
|
+
try {
|
|
149
|
+
const hashes = req.body.hashes
|
|
150
|
+
const verbose = !!req.body.verbose
|
|
151
|
+
|
|
152
|
+
if (!Array.isArray(hashes)) {
|
|
153
|
+
return res.status(400).json({
|
|
154
|
+
error: 'hashes needs to be an array. Use GET for single hash.'
|
|
155
|
+
})
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (!this.adapters.fullNode.validateArraySize(hashes.length, { isProUser: Boolean(req.locals?.proLimit) })) {
|
|
159
|
+
return res.status(400).json({ error: 'Array too large.' })
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
for (const hash of hashes) {
|
|
163
|
+
if (!hash || hash.length !== 64) {
|
|
164
|
+
return res.status(400).json({ error: `This is not a hash: ${hash}` })
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const result = await this.blockchainUseCases.getBlockHeaders({ hashes, verbose })
|
|
169
|
+
return res.status(200).json(result)
|
|
170
|
+
} catch (err) {
|
|
171
|
+
return this.handleError(err, res)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* @api {get} /full-node/blockchain/getChainTips Get chain tips
|
|
177
|
+
* @apiName GetChainTips
|
|
178
|
+
* @apiGroup Blockchain
|
|
179
|
+
* @apiDescription Returns information about known tips in the block tree.
|
|
180
|
+
*/
|
|
181
|
+
async getChainTips (req, res) {
|
|
182
|
+
try {
|
|
183
|
+
const result = await this.blockchainUseCases.getChainTips()
|
|
184
|
+
return res.status(200).json(result)
|
|
185
|
+
} catch (err) {
|
|
186
|
+
return this.handleError(err, res)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* @api {get} /full-node/blockchain/getDifficulty Get difficulty
|
|
192
|
+
* @apiName GetDifficulty
|
|
193
|
+
* @apiGroup Blockchain
|
|
194
|
+
* @apiDescription Returns the current difficulty value.
|
|
195
|
+
*/
|
|
196
|
+
async getDifficulty (req, res) {
|
|
197
|
+
try {
|
|
198
|
+
const result = await this.blockchainUseCases.getDifficulty()
|
|
199
|
+
return res.status(200).json(result)
|
|
200
|
+
} catch (err) {
|
|
201
|
+
return this.handleError(err, res)
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* @api {get} /full-node/blockchain/getMempoolEntry/:txid Get single mempool entry
|
|
207
|
+
* @apiName GetMempoolEntry
|
|
208
|
+
* @apiGroup Blockchain
|
|
209
|
+
* @apiDescription Returns mempool data for a transaction.
|
|
210
|
+
*/
|
|
211
|
+
async getMempoolEntrySingle (req, res) {
|
|
212
|
+
try {
|
|
213
|
+
const txid = req.params.txid
|
|
214
|
+
if (!txid) {
|
|
215
|
+
return res.status(400).json({ error: 'txid can not be empty' })
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const result = await this.blockchainUseCases.getMempoolEntry({ txid })
|
|
219
|
+
return res.status(200).json(result)
|
|
220
|
+
} catch (err) {
|
|
221
|
+
return this.handleError(err, res)
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* @api {post} /full-node/blockchain/getMempoolEntry Get bulk mempool entry
|
|
227
|
+
* @apiName GetMempoolEntryBulk
|
|
228
|
+
* @apiGroup Blockchain
|
|
229
|
+
* @apiDescription Returns mempool data for multiple transactions.
|
|
230
|
+
*/
|
|
231
|
+
async getMempoolEntryBulk (req, res) {
|
|
232
|
+
try {
|
|
233
|
+
const txids = req.body.txids
|
|
234
|
+
|
|
235
|
+
if (!Array.isArray(txids)) {
|
|
236
|
+
return res.status(400).json({
|
|
237
|
+
error: 'txids needs to be an array. Use GET for single txid.'
|
|
238
|
+
})
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (!this.adapters.fullNode.validateArraySize(txids.length, { isProUser: Boolean(req.locals?.proLimit) })) {
|
|
242
|
+
return res.status(400).json({ error: 'Array too large.' })
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
for (const txid of txids) {
|
|
246
|
+
if (!txid || txid.length !== 64) {
|
|
247
|
+
return res.status(400).json({ error: 'This is not a txid' })
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const result = await this.blockchainUseCases.getMempoolEntries({ txids })
|
|
252
|
+
return res.status(200).json(result)
|
|
253
|
+
} catch (err) {
|
|
254
|
+
return this.handleError(err, res)
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* @api {get} /full-node/blockchain/getMempoolAncestors/:txid Get mempool ancestors
|
|
260
|
+
* @apiName GetMempoolAncestors
|
|
261
|
+
* @apiGroup Blockchain
|
|
262
|
+
* @apiDescription Returns mempool ancestor data for a transaction.
|
|
263
|
+
*/
|
|
264
|
+
async getMempoolAncestorsSingle (req, res) {
|
|
265
|
+
try {
|
|
266
|
+
const txid = req.params.txid
|
|
267
|
+
if (!txid) {
|
|
268
|
+
return res.status(400).json({ error: 'txid can not be empty' })
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
let verbose = false
|
|
272
|
+
if (req.query.verbose && req.query.verbose.toString() === 'true') {
|
|
273
|
+
verbose = true
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const result = await this.blockchainUseCases.getMempoolAncestors({ txid, verbose })
|
|
277
|
+
return res.status(200).json(result)
|
|
278
|
+
} catch (err) {
|
|
279
|
+
return this.handleError(err, res)
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* @api {get} /full-node/blockchain/getMempoolInfo Get mempool info
|
|
285
|
+
* @apiName GetMempoolInfo
|
|
286
|
+
* @apiGroup Blockchain
|
|
287
|
+
* @apiDescription Returns details on the state of the mempool.
|
|
288
|
+
*/
|
|
289
|
+
async getMempoolInfo (req, res) {
|
|
290
|
+
try {
|
|
291
|
+
const result = await this.blockchainUseCases.getMempoolInfo()
|
|
292
|
+
return res.status(200).json(result)
|
|
293
|
+
} catch (err) {
|
|
294
|
+
return this.handleError(err, res)
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* @api {get} /full-node/blockchain/getRawMempool Get raw mempool
|
|
300
|
+
* @apiName GetRawMempool
|
|
301
|
+
* @apiGroup Blockchain
|
|
302
|
+
* @apiDescription Returns all transaction ids in the mempool.
|
|
303
|
+
*
|
|
304
|
+
* @apiParam {Boolean} verbose Return verbose data (default false)
|
|
305
|
+
*/
|
|
306
|
+
async getRawMempool (req, res) {
|
|
307
|
+
try {
|
|
308
|
+
const verbose = req.query.verbose === 'true'
|
|
309
|
+
const result = await this.blockchainUseCases.getRawMempool({ verbose })
|
|
310
|
+
return res.status(200).json(result)
|
|
311
|
+
} catch (err) {
|
|
312
|
+
return this.handleError(err, res)
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* @api {get} /full-node/blockchain/getTxOut/:txid/:n Get transaction output
|
|
318
|
+
* @apiName GetTxOut
|
|
319
|
+
* @apiGroup Blockchain
|
|
320
|
+
* @apiDescription Returns details about an unspent transaction output.
|
|
321
|
+
*/
|
|
322
|
+
async getTxOut (req, res) {
|
|
323
|
+
try {
|
|
324
|
+
const txid = req.params.txid
|
|
325
|
+
if (!txid) {
|
|
326
|
+
return res.status(400).json({ error: 'txid can not be empty' })
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const nRaw = req.params.n
|
|
330
|
+
if (nRaw === undefined || nRaw === '') {
|
|
331
|
+
return res.status(400).json({ error: 'n can not be empty' })
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const n = parseInt(nRaw)
|
|
335
|
+
const includeMempool = req.query.includeMempool === 'true'
|
|
336
|
+
|
|
337
|
+
const result = await this.blockchainUseCases.getTxOut({
|
|
338
|
+
txid,
|
|
339
|
+
n,
|
|
340
|
+
includeMempool
|
|
341
|
+
})
|
|
342
|
+
|
|
343
|
+
return res.status(200).json(result)
|
|
344
|
+
} catch (err) {
|
|
345
|
+
return this.handleError(err, res)
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* @api {post} /full-node/blockchain/getTxOut Validate a UTXO
|
|
351
|
+
* @apiName GetTxOutPost
|
|
352
|
+
* @apiGroup Blockchain
|
|
353
|
+
* @apiDescription Returns details about an unspent transaction output.
|
|
354
|
+
*/
|
|
355
|
+
async getTxOutPost (req, res) {
|
|
356
|
+
try {
|
|
357
|
+
const txid = req.body.txid
|
|
358
|
+
if (!txid) {
|
|
359
|
+
return res.status(400).json({ error: 'txid can not be empty' })
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const voutRaw = req.body.vout
|
|
363
|
+
if (voutRaw === undefined || voutRaw === '') {
|
|
364
|
+
return res.status(400).json({ error: 'vout can not be empty' })
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const n = parseInt(voutRaw)
|
|
368
|
+
const mempool = req.body.mempool !== undefined ? !!req.body.mempool : true
|
|
369
|
+
|
|
370
|
+
const result = await this.blockchainUseCases.getTxOut({
|
|
371
|
+
txid,
|
|
372
|
+
n,
|
|
373
|
+
includeMempool: mempool
|
|
374
|
+
})
|
|
375
|
+
|
|
376
|
+
return res.status(200).json(result)
|
|
377
|
+
} catch (err) {
|
|
378
|
+
return this.handleError(err, res)
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* @api {get} /full-node/blockchain/getTxOutProof/:txid Get TxOut proof
|
|
384
|
+
* @apiName GetTxOutProofSingle
|
|
385
|
+
* @apiGroup Blockchain
|
|
386
|
+
* @apiDescription Returns a hex-encoded proof that the transaction was included in a block.
|
|
387
|
+
*/
|
|
388
|
+
async getTxOutProofSingle (req, res) {
|
|
389
|
+
try {
|
|
390
|
+
const txid = req.params.txid
|
|
391
|
+
if (!txid) {
|
|
392
|
+
return res.status(400).json({ error: 'txid can not be empty' })
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const result = await this.blockchainUseCases.getTxOutProof({ txid })
|
|
396
|
+
return res.status(200).json(result)
|
|
397
|
+
} catch (err) {
|
|
398
|
+
return this.handleError(err, res)
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* @api {post} /full-node/blockchain/getTxOutProof Get TxOut proofs
|
|
404
|
+
* @apiName GetTxOutProofBulk
|
|
405
|
+
* @apiGroup Blockchain
|
|
406
|
+
* @apiDescription Returns hex-encoded proofs for transactions.
|
|
407
|
+
*/
|
|
408
|
+
async getTxOutProofBulk (req, res) {
|
|
409
|
+
try {
|
|
410
|
+
const txids = req.body.txids
|
|
411
|
+
|
|
412
|
+
if (!Array.isArray(txids)) {
|
|
413
|
+
return res.status(400).json({
|
|
414
|
+
error: 'txids needs to be an array. Use GET for single txid.'
|
|
415
|
+
})
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (!this.adapters.fullNode.validateArraySize(txids.length, { isProUser: Boolean(req.locals?.proLimit) })) {
|
|
419
|
+
return res.status(400).json({ error: 'Array too large.' })
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
for (const txid of txids) {
|
|
423
|
+
if (!txid || txid.length !== 64) {
|
|
424
|
+
return res.status(400).json({
|
|
425
|
+
error: `Invalid txid. Double check your txid is valid: ${txid}`
|
|
426
|
+
})
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const result = await this.blockchainUseCases.getTxOutProofs({ txids })
|
|
431
|
+
return res.status(200).json(result)
|
|
432
|
+
} catch (err) {
|
|
433
|
+
return this.handleError(err, res)
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* @api {get} /full-node/blockchain/verifyTxOutProof/:proof Verify TxOut proof
|
|
439
|
+
* @apiName VerifyTxOutProofSingle
|
|
440
|
+
* @apiGroup Blockchain
|
|
441
|
+
* @apiDescription Verifies a hex-encoded proof was included in a block.
|
|
442
|
+
*/
|
|
443
|
+
async verifyTxOutProofSingle (req, res) {
|
|
444
|
+
try {
|
|
445
|
+
const proof = req.params.proof
|
|
446
|
+
if (!proof) {
|
|
447
|
+
return res.status(400).json({ error: 'proof can not be empty' })
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const result = await this.blockchainUseCases.verifyTxOutProof({ proof })
|
|
451
|
+
return res.status(200).json(result)
|
|
452
|
+
} catch (err) {
|
|
453
|
+
return this.handleError(err, res)
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* @api {post} /full-node/blockchain/verifyTxOutProof Verify TxOut proofs
|
|
459
|
+
* @apiName VerifyTxOutProofBulk
|
|
460
|
+
* @apiGroup Blockchain
|
|
461
|
+
* @apiDescription Verifies hex-encoded proofs were included in blocks.
|
|
462
|
+
*/
|
|
463
|
+
async verifyTxOutProofBulk (req, res) {
|
|
464
|
+
try {
|
|
465
|
+
const proofs = req.body.proofs
|
|
466
|
+
|
|
467
|
+
if (!Array.isArray(proofs)) {
|
|
468
|
+
return res.status(400).json({
|
|
469
|
+
error: 'proofs needs to be an array. Use GET for single proof.'
|
|
470
|
+
})
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (!this.adapters.fullNode.validateArraySize(proofs.length, { isProUser: Boolean(req.locals?.proLimit) })) {
|
|
474
|
+
return res.status(400).json({ error: 'Array too large.' })
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
for (const proof of proofs) {
|
|
478
|
+
if (!proof) {
|
|
479
|
+
return res.status(400).json({ error: `proof can not be empty: ${proof}` })
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const result = await this.blockchainUseCases.verifyTxOutProofs({ proofs })
|
|
484
|
+
const flattened = result.map(entry => Array.isArray(entry) ? entry[0] : entry)
|
|
485
|
+
|
|
486
|
+
return res.status(200).json(flattened)
|
|
487
|
+
} catch (err) {
|
|
488
|
+
return this.handleError(err, res)
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* @api {post} /full-node/blockchain/getBlock Get block details
|
|
494
|
+
* @apiName GetBlock
|
|
495
|
+
* @apiGroup Blockchain
|
|
496
|
+
* @apiDescription Returns block details for a hash.
|
|
497
|
+
*/
|
|
498
|
+
async getBlock (req, res) {
|
|
499
|
+
try {
|
|
500
|
+
const blockhash = req.body.blockhash
|
|
501
|
+
if (!blockhash) {
|
|
502
|
+
return res.status(400).json({ error: 'blockhash can not be empty' })
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
let verbosity = req.body.verbosity
|
|
506
|
+
if (verbosity === undefined || verbosity === null) {
|
|
507
|
+
verbosity = 1
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
const result = await this.blockchainUseCases.getBlock({
|
|
511
|
+
blockhash,
|
|
512
|
+
verbosity
|
|
513
|
+
})
|
|
514
|
+
|
|
515
|
+
return res.status(200).json(result)
|
|
516
|
+
} catch (err) {
|
|
517
|
+
return this.handleError(err, res)
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* @api {get} /full-node/blockchain/getBlockHash/:height Get block hash
|
|
523
|
+
* @apiName GetBlockHash
|
|
524
|
+
* @apiGroup Blockchain
|
|
525
|
+
* @apiDescription Returns the hash of a block by height.
|
|
526
|
+
*/
|
|
527
|
+
async getBlockHash (req, res) {
|
|
528
|
+
try {
|
|
529
|
+
const heightRaw = req.params.height
|
|
530
|
+
if (!heightRaw) {
|
|
531
|
+
return res.status(400).json({ error: 'height can not be empty' })
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
const height = parseInt(heightRaw)
|
|
535
|
+
const result = await this.blockchainUseCases.getBlockHash({ height })
|
|
536
|
+
|
|
537
|
+
return res.status(200).json(result)
|
|
538
|
+
} catch (err) {
|
|
539
|
+
return this.handleError(err, res)
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
handleError (err, res) {
|
|
544
|
+
wlogger.error('Error in BlockchainRESTController:', err)
|
|
545
|
+
|
|
546
|
+
const status = err.status || 500
|
|
547
|
+
const message = err.message || 'Internal server error'
|
|
548
|
+
|
|
549
|
+
return res.status(status).json({ error: message })
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
export default BlockchainRESTController
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/*
|
|
2
|
+
REST API router for /full-node/blockchain routes.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import express from 'express'
|
|
6
|
+
import BlockchainRESTController from './controller.js'
|
|
7
|
+
|
|
8
|
+
class BlockchainRouter {
|
|
9
|
+
constructor (localConfig = {}) {
|
|
10
|
+
this.adapters = localConfig.adapters
|
|
11
|
+
if (!this.adapters) {
|
|
12
|
+
throw new Error(
|
|
13
|
+
'Instance of Adapters library required when instantiating Blockchain REST Router.'
|
|
14
|
+
)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
this.useCases = localConfig.useCases
|
|
18
|
+
if (!this.useCases) {
|
|
19
|
+
throw new Error(
|
|
20
|
+
'Instance of Use Cases library required when instantiating Blockchain REST Router.'
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const dependencies = {
|
|
25
|
+
adapters: this.adapters,
|
|
26
|
+
useCases: this.useCases
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
this.blockchainController = new BlockchainRESTController(dependencies)
|
|
30
|
+
|
|
31
|
+
this.baseUrl = '/full-node/blockchain'
|
|
32
|
+
this.router = express.Router()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
attach (app) {
|
|
36
|
+
if (!app) {
|
|
37
|
+
throw new Error('Must pass app object when attaching REST API controllers.')
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
this.router.get('/', this.blockchainController.root)
|
|
41
|
+
this.router.get('/getBestBlockHash', this.blockchainController.getBestBlockHash)
|
|
42
|
+
this.router.get('/getBlockchainInfo', this.blockchainController.getBlockchainInfo)
|
|
43
|
+
this.router.get('/getBlockCount', this.blockchainController.getBlockCount)
|
|
44
|
+
this.router.get('/getBlockHeader/:hash', this.blockchainController.getBlockHeaderSingle)
|
|
45
|
+
this.router.post('/getBlockHeader', this.blockchainController.getBlockHeaderBulk)
|
|
46
|
+
this.router.get('/getChainTips', this.blockchainController.getChainTips)
|
|
47
|
+
this.router.get('/getDifficulty', this.blockchainController.getDifficulty)
|
|
48
|
+
this.router.get('/getMempoolEntry/:txid', this.blockchainController.getMempoolEntrySingle)
|
|
49
|
+
this.router.post('/getMempoolEntry', this.blockchainController.getMempoolEntryBulk)
|
|
50
|
+
this.router.get('/getMempoolAncestors/:txid', this.blockchainController.getMempoolAncestorsSingle)
|
|
51
|
+
this.router.get('/getMempoolInfo', this.blockchainController.getMempoolInfo)
|
|
52
|
+
this.router.get('/getRawMempool', this.blockchainController.getRawMempool)
|
|
53
|
+
this.router.get('/getTxOut/:txid/:n', this.blockchainController.getTxOut)
|
|
54
|
+
this.router.post('/getTxOut', this.blockchainController.getTxOutPost)
|
|
55
|
+
this.router.get('/getTxOutProof/:txid', this.blockchainController.getTxOutProofSingle)
|
|
56
|
+
this.router.post('/getTxOutProof', this.blockchainController.getTxOutProofBulk)
|
|
57
|
+
this.router.get('/verifyTxOutProof/:proof', this.blockchainController.verifyTxOutProofSingle)
|
|
58
|
+
this.router.post('/verifyTxOutProof', this.blockchainController.verifyTxOutProofBulk)
|
|
59
|
+
this.router.post('/getBlock', this.blockchainController.getBlock)
|
|
60
|
+
this.router.get('/getBlockHash/:height', this.blockchainController.getBlockHash)
|
|
61
|
+
|
|
62
|
+
app.use(this.baseUrl, this.router)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export default BlockchainRouter
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/*
|
|
2
|
+
This index file for the Clean Architecture Controllers loads dependencies,
|
|
3
|
+
creates instances, and attaches the controller to REST API endpoints for
|
|
4
|
+
Express.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Local libraries
|
|
8
|
+
// import EventRouter from './event/index.js'
|
|
9
|
+
// import ReqRouter from './req/index.js'
|
|
10
|
+
import BlockchainRouter from './full-node/blockchain/index.js'
|
|
11
|
+
import config from '../../config/index.js'
|
|
12
|
+
|
|
13
|
+
class RESTControllers {
|
|
14
|
+
constructor (localConfig = {}) {
|
|
15
|
+
// Dependency Injection.
|
|
16
|
+
this.adapters = localConfig.adapters
|
|
17
|
+
if (!this.adapters) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
'Instance of Adapters library required when instantiating REST Controller libraries.'
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
this.useCases = localConfig.useCases
|
|
23
|
+
if (!this.useCases) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
'Instance of Use Cases library required when instantiating REST Controller libraries.'
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Bind 'this' object to all subfunctions.
|
|
30
|
+
this.attachRESTControllers = this.attachRESTControllers.bind(this)
|
|
31
|
+
|
|
32
|
+
// Encapsulate dependencies
|
|
33
|
+
this.config = config
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
attachRESTControllers (app) {
|
|
37
|
+
const dependencies = {
|
|
38
|
+
adapters: this.adapters,
|
|
39
|
+
useCases: this.useCases
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Attach the REST API Controllers associated with the /event route
|
|
43
|
+
// const eventRouter = new EventRouter(dependencies)
|
|
44
|
+
// eventRouter.attach(app)
|
|
45
|
+
|
|
46
|
+
// Attach the REST API Controllers associated with the /req route
|
|
47
|
+
// const reqRouter = new ReqRouter(dependencies)
|
|
48
|
+
// reqRouter.attach(app)
|
|
49
|
+
|
|
50
|
+
const blockchainRouter = new BlockchainRouter(dependencies)
|
|
51
|
+
blockchainRouter.attach(app)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export default RESTControllers
|