12
12
* Removal or modification of this copyright notice is prohibited.
13
13
*/
14
14
15
- import { Event , StateStore } from '@liskhq/lisk-chain' ;
15
+ import {
16
+ Block ,
17
+ BlockAssets ,
18
+ BlockHeader ,
19
+ Event ,
20
+ StateStore ,
21
+ Transaction ,
22
+ } from '@liskhq/lisk-chain' ;
16
23
import { utils } from '@liskhq/lisk-cryptography' ;
17
- import { Batch , Database , InMemoryDatabase } from '@liskhq/lisk-db' ;
24
+ import { Batch , Database , InMemoryDatabase , NotFoundError } from '@liskhq/lisk-db' ;
18
25
import {
19
26
EMPTY_KEY ,
20
27
MODULE_STORE_PREFIX_BFT ,
@@ -24,20 +31,64 @@ import {
24
31
import { bftParametersSchema , bftVotesSchema } from '../../../../src/engine/bft/schemas' ;
25
32
import { ChainEndpoint } from '../../../../src/engine/endpoint/chain' ;
26
33
import { createRequestContext } from '../../../utils/mocks/endpoint' ;
34
+ import * as bftUtils from '../../../../src/engine/bft/utils' ;
27
35
28
36
describe ( 'Chain endpoint' , ( ) => {
29
37
const DEFAULT_INTERVAL = 10 ;
30
38
let stateStore : StateStore ;
31
39
let endpoint : ChainEndpoint ;
32
40
let db : InMemoryDatabase ;
41
+ const transaction = new Transaction ( {
42
+ module : 'token' ,
43
+ command : 'transfer' ,
44
+ fee : BigInt ( 613000 ) ,
45
+ params : utils . getRandomBytes ( 100 ) ,
46
+ nonce : BigInt ( 2 ) ,
47
+ senderPublicKey : utils . getRandomBytes ( 32 ) ,
48
+ signatures : [ utils . getRandomBytes ( 64 ) ] ,
49
+ } ) ;
50
+ const blockAsset = new BlockAssets ( ) ;
51
+ const getBlockAttrs = ( attrs ?: Record < string , unknown > ) => ( {
52
+ version : 1 ,
53
+ timestamp : 1009988 ,
54
+ height : 1009988 ,
55
+ previousBlockID : Buffer . from ( '4a462ea57a8c9f72d866c09770e5ec70cef18727' , 'hex' ) ,
56
+ stateRoot : Buffer . from ( '7f9d96a09a3fd17f3478eb7bef3a8bda00e1238b' , 'hex' ) ,
57
+ transactionRoot : Buffer . from ( 'b27ca21f40d44113c2090ca8f05fb706c54e87dd' , 'hex' ) ,
58
+ assetRoot : Buffer . from ( 'b27ca21f40d44113c2090ca8f05fb706c54e87dd' , 'hex' ) ,
59
+ eventRoot : Buffer . from (
60
+ '30dda4fbc395828e5a9f2f8824771e434fce4945a1e7820012440d09dd1e2b6d' ,
61
+ 'hex' ,
62
+ ) ,
63
+ generatorAddress : Buffer . from ( 'be63fb1c0426573352556f18b21efd5b6183c39c' , 'hex' ) ,
64
+ maxHeightPrevoted : 1000988 ,
65
+ maxHeightGenerated : 1000988 ,
66
+ impliesMaxPrevotes : true ,
67
+ validatorsHash : utils . hash ( Buffer . alloc ( 0 ) ) ,
68
+ aggregateCommit : {
69
+ height : 0 ,
70
+ aggregationBits : Buffer . alloc ( 0 ) ,
71
+ certificateSignature : Buffer . alloc ( 0 ) ,
72
+ } ,
73
+ signature : Buffer . from ( '6da88e2fd4435e26e02682435f108002ccc3ddd5' , 'hex' ) ,
74
+ ...attrs ,
75
+ } ) ;
76
+ const blockHeader = new BlockHeader ( getBlockAttrs ( ) ) ;
77
+ const block = new Block ( blockHeader , [ transaction ] , blockAsset ) ;
78
+ const validBlockID = '215b667a32a5cd51a94c9c2046c11fffb08c65748febec099451e3b164452b' ;
33
79
34
80
beforeEach ( ( ) => {
35
81
stateStore = new StateStore ( new InMemoryDatabase ( ) ) ;
36
82
endpoint = new ChainEndpoint ( {
37
83
chain : {
38
84
dataAccess : {
39
85
getEvents : jest . fn ( ) ,
86
+ getBlockByID : jest . fn ( ) ,
87
+ getBlockByHeight : jest . fn ( ) ,
88
+ getBlocksByHeightBetween : jest . fn ( ) ,
89
+ getTransactionByID : jest . fn ( ) ,
40
90
} ,
91
+ lastBlock : block ,
41
92
} as any ,
42
93
bftMethod : {
43
94
getSlotNumber : jest . fn ( ) . mockReturnValue ( 0 ) ,
@@ -229,4 +280,254 @@ describe('Chain endpoint', () => {
229
280
expect ( list [ 1 ] . nextAllocatedTime - list [ 0 ] . nextAllocatedTime ) . toBe ( DEFAULT_INTERVAL ) ;
230
281
} ) ;
231
282
} ) ;
283
+
284
+ describe ( 'getBlockByID' , ( ) => {
285
+ it ( 'should throw if provided block id is not valid' , async ( ) => {
286
+ await expect (
287
+ endpoint . getBlockByID ( createRequestContext ( { id : 'invalid id' } ) ) ,
288
+ ) . rejects . toThrow ( 'Invalid parameters. id must be a valid hex string.' ) ;
289
+ } ) ;
290
+
291
+ it ( 'should return the block if provided id is valid' , async ( ) => {
292
+ jest . spyOn ( endpoint [ '_chain' ] . dataAccess , 'getBlockByID' ) . mockResolvedValue ( block ) ;
293
+ await expect (
294
+ endpoint . getBlockByID (
295
+ createRequestContext ( {
296
+ id : validBlockID ,
297
+ } ) ,
298
+ ) ,
299
+ ) . resolves . toEqual ( block . toJSON ( ) ) ;
300
+ } ) ;
301
+ } ) ;
302
+
303
+ describe ( 'getBlocksByIDs' , ( ) => {
304
+ it ( 'should throw if the provided block ids is an empty array or not a valid array' , async ( ) => {
305
+ await expect ( endpoint . getBlocksByIDs ( createRequestContext ( { ids : [ ] } ) ) ) . rejects . toThrow (
306
+ 'Invalid parameters. ids must be a non empty array.' ,
307
+ ) ;
308
+
309
+ await expect (
310
+ endpoint . getBlocksByIDs ( createRequestContext ( { ids : 'not an array' } ) ) ,
311
+ ) . rejects . toThrow ( 'Invalid parameters. ids must be a non empty array.' ) ;
312
+ } ) ;
313
+
314
+ it ( 'should throw if any of the provided block ids is not valid' , async ( ) => {
315
+ await expect (
316
+ endpoint . getBlocksByIDs ( createRequestContext ( { ids : [ validBlockID , 'invalid id' ] } ) ) ,
317
+ ) . rejects . toThrow ( 'Invalid parameters. id must a valid hex string.' ) ;
318
+ } ) ;
319
+
320
+ it ( 'should return empty result if the provided block ids are not found' , async ( ) => {
321
+ jest . spyOn ( endpoint [ '_chain' ] . dataAccess , 'getBlockByID' ) . mockImplementation ( ( ) => {
322
+ throw new NotFoundError ( ) ;
323
+ } ) ;
324
+
325
+ await expect (
326
+ endpoint . getBlocksByIDs ( createRequestContext ( { ids : [ validBlockID ] } ) ) ,
327
+ ) . resolves . toEqual ( [ ] ) ;
328
+ } ) ;
329
+
330
+ it ( 'should throw if dataAccess throws an error other than NotFoundError' , async ( ) => {
331
+ jest . spyOn ( endpoint [ '_chain' ] . dataAccess , 'getBlockByID' ) . mockImplementation ( ( ) => {
332
+ throw new Error ( ) ;
333
+ } ) ;
334
+
335
+ await expect (
336
+ endpoint . getBlocksByIDs ( createRequestContext ( { ids : [ validBlockID ] } ) ) ,
337
+ ) . rejects . toThrow ( ) ;
338
+ } ) ;
339
+
340
+ it ( 'should return a collection of blocks' , async ( ) => {
341
+ jest . spyOn ( endpoint [ '_chain' ] . dataAccess , 'getBlockByID' ) . mockResolvedValue ( block ) ;
342
+
343
+ await expect (
344
+ endpoint . getBlocksByIDs ( createRequestContext ( { ids : [ validBlockID ] } ) ) ,
345
+ ) . resolves . toEqual ( [ block . toJSON ( ) ] ) ;
346
+ } ) ;
347
+ } ) ;
348
+
349
+ describe ( 'getBlockByHeight' , ( ) => {
350
+ it ( 'should throw if provided height is invalid' , async ( ) => {
351
+ await expect (
352
+ endpoint . getBlockByHeight ( createRequestContext ( { height : 'incorrect height' } ) ) ,
353
+ ) . rejects . toThrow ( 'Invalid parameters. height must be a number.' ) ;
354
+ } ) ;
355
+
356
+ it ( 'should rerturn a block if the provided height is valid' , async ( ) => {
357
+ jest . spyOn ( endpoint [ '_chain' ] . dataAccess , 'getBlockByHeight' ) . mockResolvedValue ( block ) ;
358
+
359
+ await expect ( endpoint . getBlockByHeight ( createRequestContext ( { height : 1 } ) ) ) . resolves . toEqual (
360
+ block . toJSON ( ) ,
361
+ ) ;
362
+ } ) ;
363
+ } ) ;
364
+
365
+ describe ( 'getBlocksByHeightBetween' , ( ) => {
366
+ it ( 'should throw if provided heights are invalid' , async ( ) => {
367
+ await expect (
368
+ endpoint . getBlocksByHeightBetween (
369
+ createRequestContext ( { from : 'incorrect height' , to : 10 } ) ,
370
+ ) ,
371
+ ) . rejects . toThrow ( 'Invalid parameters. from and to must be a number.' ) ;
372
+
373
+ await expect (
374
+ endpoint . getBlocksByHeightBetween (
375
+ createRequestContext ( { from : 1 , to : 'incorrect height' } ) ,
376
+ ) ,
377
+ ) . rejects . toThrow ( 'Invalid parameters. from and to must be a number.' ) ;
378
+ } ) ;
379
+
380
+ it ( 'should return a collection of blocks' , async ( ) => {
381
+ jest
382
+ . spyOn ( endpoint [ '_chain' ] . dataAccess , 'getBlocksByHeightBetween' )
383
+ . mockResolvedValue ( [ block ] ) ;
384
+
385
+ await expect (
386
+ endpoint . getBlocksByHeightBetween ( createRequestContext ( { from : 1 , to : 10 } ) ) ,
387
+ ) . resolves . toEqual ( [ block . toJSON ( ) ] ) ;
388
+ } ) ;
389
+ } ) ;
390
+
391
+ describe ( 'getLastBlock' , ( ) => {
392
+ it ( 'should return the last block' , ( ) => {
393
+ expect ( endpoint . getLastBlock ( ) ) . toEqual ( block . toJSON ( ) ) ;
394
+ } ) ;
395
+ } ) ;
396
+
397
+ describe ( 'getTransactionByID' , ( ) => {
398
+ it ( 'should throw if provided id is not valid' , async ( ) => {
399
+ await expect (
400
+ endpoint . getTransactionByID ( createRequestContext ( { id : 'invalid id' } ) ) ,
401
+ ) . rejects . toThrow ( 'Invalid parameters. id must be a valid hex string.' ) ;
402
+ } ) ;
403
+
404
+ it ( 'should return a transaction if provided id is valid' , async ( ) => {
405
+ jest
406
+ . spyOn ( endpoint [ '_chain' ] . dataAccess , 'getTransactionByID' )
407
+ . mockResolvedValue ( transaction ) ;
408
+
409
+ await expect (
410
+ endpoint . getTransactionByID ( createRequestContext ( { id : transaction . id . toString ( 'hex' ) } ) ) ,
411
+ ) . resolves . toEqual ( transaction . toJSON ( ) ) ;
412
+ } ) ;
413
+ } ) ;
414
+
415
+ describe ( 'getTransactionsByIDs' , ( ) => {
416
+ it ( 'should throw if provided ids is empty or not an array' , async ( ) => {
417
+ await expect (
418
+ endpoint . getTransactionsByIDs ( createRequestContext ( { ids : [ ] } ) ) ,
419
+ ) . rejects . toThrow ( 'Invalid parameters. ids must be a non empty array' ) ;
420
+
421
+ await expect (
422
+ endpoint . getTransactionsByIDs ( createRequestContext ( { ids : 'invalid id' } ) ) ,
423
+ ) . rejects . toThrow ( 'Invalid parameters. ids must be a non empty array' ) ;
424
+ } ) ;
425
+
426
+ it ( 'should throw if any of the provided ids are not valid' , async ( ) => {
427
+ await expect (
428
+ endpoint . getTransactionsByIDs ( createRequestContext ( { ids : [ validBlockID , 'invalid ID' ] } ) ) ,
429
+ ) . rejects . toThrow ( 'Invalid parameters. id must be a valid hex string.' ) ;
430
+ } ) ;
431
+
432
+ it ( 'should return a collection of transactions' , async ( ) => {
433
+ jest
434
+ . spyOn ( endpoint [ '_chain' ] . dataAccess , 'getTransactionByID' )
435
+ . mockResolvedValue ( transaction ) ;
436
+
437
+ await expect (
438
+ endpoint . getTransactionsByIDs (
439
+ createRequestContext ( { ids : [ transaction . id . toString ( 'hex' ) ] } ) ,
440
+ ) ,
441
+ ) . resolves . toEqual ( [ transaction . toJSON ( ) ] ) ;
442
+ } ) ;
443
+ } ) ;
444
+
445
+ describe ( 'getTransactionsByHeight' , ( ) => {
446
+ it ( 'should throw if provided height is invalid' , async ( ) => {
447
+ await expect (
448
+ endpoint . getTransactionsByHeight ( createRequestContext ( { height : 'invalid height' } ) ) ,
449
+ ) . rejects . toThrow ( 'Invalid parameters. height must be zero or a positive number.' ) ;
450
+
451
+ await expect (
452
+ endpoint . getTransactionsByHeight ( createRequestContext ( { height : - 1 } ) ) ,
453
+ ) . rejects . toThrow ( 'Invalid parameters. height must be zero or a positive number.' ) ;
454
+ } ) ;
455
+
456
+ it ( 'should return a collection of transactions in the block at the provided height' , async ( ) => {
457
+ jest . spyOn ( endpoint [ '_chain' ] . dataAccess , 'getBlockByHeight' ) . mockResolvedValue ( block ) ;
458
+
459
+ await expect (
460
+ endpoint . getTransactionsByHeight ( createRequestContext ( { height : 1 } ) ) ,
461
+ ) . resolves . toEqual ( block . transactions . map ( t => t . toJSON ( ) ) ) ;
462
+ } ) ;
463
+ } ) ;
464
+
465
+ describe ( 'getAssetsByHeight' , ( ) => {
466
+ it ( 'should throw if provided height is invalid' , async ( ) => {
467
+ await expect (
468
+ endpoint . getAssetsByHeight ( createRequestContext ( { height : 'invalid height' } ) ) ,
469
+ ) . rejects . toThrow ( 'Invalid parameters. height must be zero or a positive number.' ) ;
470
+
471
+ await expect (
472
+ endpoint . getAssetsByHeight ( createRequestContext ( { height : - 1 } ) ) ,
473
+ ) . rejects . toThrow ( 'Invalid parameters. height must be zero or a positive number.' ) ;
474
+ } ) ;
475
+
476
+ it ( 'should return block assests at the provided height' , async ( ) => {
477
+ jest . spyOn ( endpoint [ '_chain' ] . dataAccess , 'getBlockByHeight' ) . mockResolvedValue ( block ) ;
478
+
479
+ await expect (
480
+ endpoint . getAssetsByHeight ( createRequestContext ( { height : 1 } ) ) ,
481
+ ) . resolves . toEqual ( block . assets . toJSON ( ) ) ;
482
+ } ) ;
483
+ } ) ;
484
+
485
+ describe ( 'areHeadersContradicting' , ( ) => {
486
+ it ( 'should throw if provided parameters are not valid' , async ( ) => {
487
+ await expect (
488
+ endpoint . areHeadersContradicting (
489
+ createRequestContext ( {
490
+ header1 : 'header1' ,
491
+ header2 : blockHeader . getBytes ( ) . toString ( 'hex' ) ,
492
+ } ) ,
493
+ ) ,
494
+ ) . rejects . toThrow ( `'.header1' must match format "hex"` ) ;
495
+
496
+ await expect (
497
+ endpoint . areHeadersContradicting (
498
+ createRequestContext ( {
499
+ header1 : block . getBytes ( ) . toString ( 'hex' ) ,
500
+ header2 : blockHeader . getBytes ( ) . toString ( ) ,
501
+ } ) ,
502
+ ) ,
503
+ ) . rejects . toThrow ( `'.header2' must match format "hex"` ) ;
504
+ } ) ;
505
+
506
+ it ( 'should invalidate if both headers have same id' , async ( ) => {
507
+ await expect (
508
+ endpoint . areHeadersContradicting (
509
+ createRequestContext ( {
510
+ header1 : blockHeader . getBytes ( ) . toString ( 'hex' ) ,
511
+ header2 : blockHeader . getBytes ( ) . toString ( 'hex' ) ,
512
+ } ) ,
513
+ ) ,
514
+ ) . resolves . toEqual ( { valid : false } ) ;
515
+ } ) ;
516
+
517
+ it ( 'should invoke areDistinctHeadersContradicting for the provided headers' , async ( ) => {
518
+ const contradictingBlockHeader = new BlockHeader ( getBlockAttrs ( { version : 2 } ) ) ;
519
+
520
+ jest . spyOn ( bftUtils , 'areDistinctHeadersContradicting' ) . mockReturnValue ( false ) ;
521
+ await expect (
522
+ endpoint . areHeadersContradicting (
523
+ createRequestContext ( {
524
+ header1 : blockHeader . getBytes ( ) . toString ( 'hex' ) ,
525
+ header2 : contradictingBlockHeader . getBytes ( ) . toString ( 'hex' ) ,
526
+ } ) ,
527
+ ) ,
528
+ ) . resolves . toEqual ( { valid : false } ) ;
529
+
530
+ expect ( bftUtils . areDistinctHeadersContradicting ) . toHaveBeenCalledTimes ( 1 ) ;
531
+ } ) ;
532
+ } ) ;
232
533
} ) ;
0 commit comments