• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hirosystems / stacks-blockchain-api / 4134334244

pending completion
4134334244

push

github

GitHub
Merge pull request #1543 from hirosystems/chore/master-into-dev

2038 of 3151 branches covered (64.68%)

77 of 129 new or added lines in 13 files covered. (59.69%)

7158 of 9352 relevant lines covered (76.54%)

1168.49 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

74.17
/src/api/controllers/db-controller.ts
1
import {
37✔
2
  abiFunctionToString,
3
  ChainID,
4
  ClarityAbi,
5
  ClarityAbiFunction,
6
  getTypeString,
7
} from '@stacks/transactions';
8
import {
37✔
9
  decodeClarityValueList,
10
  decodeClarityValueToRepr,
11
  decodeClarityValueToTypeName,
12
  decodePostConditions,
13
} from 'stacks-encoding-native-js';
14

15
import {
16
  AbstractMempoolTransaction,
17
  AbstractTransaction,
18
  BaseTransaction,
19
  Block,
20
  CoinbaseTransactionMetadata,
21
  ContractCallTransactionMetadata,
22
  MempoolTransaction,
23
  MempoolTransactionStatus,
24
  Microblock,
25
  PoisonMicroblockTransactionMetadata,
26
  RosettaBlock,
27
  RosettaParentBlockIdentifier,
28
  RosettaTransaction,
29
  SmartContractTransactionMetadata,
30
  TokenTransferTransactionMetadata,
31
  Transaction,
32
  TransactionAnchorModeType,
33
  TransactionEvent,
34
  TransactionEventFungibleAsset,
35
  TransactionEventNonFungibleAsset,
36
  TransactionEventSmartContractLog,
37
  TransactionEventStxAsset,
38
  TransactionEventStxLock,
39
  TransactionFound,
40
  TransactionList,
41
  TransactionMetadata,
42
  TransactionNotFound,
43
  TransactionStatus,
44
  TransactionType,
45
} from '@stacks/stacks-blockchain-api-types';
46

47
import {
37✔
48
  BlockIdentifier,
49
  DbAssetEventTypeId,
50
  DbBlock,
51
  DbEvent,
52
  DbEventTypeId,
53
  DbMempoolTx,
54
  DbMicroblock,
55
  DbTx,
56
  DbTxStatus,
57
  DbTxTypeId,
58
  DbSearchResultWithMetadata,
59
  BaseTx,
60
  DbMinerReward,
61
  StxUnlockEvent,
62
  DbPox2Event,
63
} from '../../datastore/common';
64
import { unwrapOptional, FoundOrNot, logger, unixEpochToIso, EMPTY_HASH_256 } from '../../helpers';
37✔
65
import { serializePostCondition, serializePostConditionMode } from '../serializers/post-conditions';
37✔
66
import { getOperations, parseTransactionMemo } from '../../rosetta-helpers';
37✔
67
import { PgStore } from '../../datastore/pg-store';
68
import { Pox2EventName } from '../../pox-helpers';
69

70
export function parseTxTypeStrings(values: string[]): TransactionType[] {
37✔
71
  return values.map(v => {
×
72
    switch (v) {
×
73
      case 'contract_call':
74
      case 'smart_contract':
75
      case 'token_transfer':
76
      case 'coinbase':
77
      case 'poison_microblock':
78
        return v;
×
79
      default:
80
        throw new Error(`Unexpected tx type: ${JSON.stringify(v)}`);
×
81
    }
82
  });
83
}
84

85
export function getTxTypeString(typeId: DbTxTypeId): Transaction['tx_type'] {
37✔
86
  switch (typeId) {
13,528!
87
    case DbTxTypeId.TokenTransfer:
88
      return 'token_transfer';
3,428✔
89
    case DbTxTypeId.SmartContract:
90
    case DbTxTypeId.VersionedSmartContract:
91
      return 'smart_contract';
3,296✔
92
    case DbTxTypeId.ContractCall:
93
      return 'contract_call';
3,291✔
94
    case DbTxTypeId.PoisonMicroblock:
95
      return 'poison_microblock';
3,276✔
96
    case DbTxTypeId.Coinbase:
97
    case DbTxTypeId.CoinbaseToAltRecipient:
98
      return 'coinbase';
237✔
99
    default:
100
      throw new Error(`Unexpected DbTxTypeId: ${typeId}`);
×
101
  }
102
}
103

104
function getTxAnchorModeString(anchorMode: number): TransactionAnchorModeType {
105
  switch (anchorMode) {
261!
106
    case 0x01:
107
      return 'on_chain_only';
4✔
108
    case 0x02:
109
      return 'off_chain_only';
×
110
    case 0x03:
111
      return 'any';
257✔
112
    default:
113
      throw new Error(`Unexpected anchor mode value ${anchorMode}`);
×
114
  }
115
}
116

117
export function getTxTypeId(typeString: Transaction['tx_type']): DbTxTypeId[] {
37✔
118
  switch (typeString) {
×
119
    case 'token_transfer':
120
      return [DbTxTypeId.TokenTransfer];
×
121
    case 'smart_contract':
122
      return [DbTxTypeId.SmartContract, DbTxTypeId.VersionedSmartContract];
×
123
    case 'contract_call':
124
      return [DbTxTypeId.ContractCall];
×
125
    case 'poison_microblock':
126
      return [DbTxTypeId.PoisonMicroblock];
×
127
    case 'coinbase':
128
      return [DbTxTypeId.Coinbase, DbTxTypeId.CoinbaseToAltRecipient];
×
129
    default:
130
      throw new Error(`Unexpected tx type string: ${typeString}`);
×
131
  }
132
}
133

134
function getTxStatusString(txStatus: DbTxStatus): TransactionStatus | MempoolTransactionStatus {
135
  switch (txStatus) {
617!
136
    case DbTxStatus.Pending:
137
      return 'pending';
40✔
138
    case DbTxStatus.Success:
139
      return 'success';
556✔
140
    case DbTxStatus.AbortByResponse:
141
      return 'abort_by_response';
2✔
142
    case DbTxStatus.AbortByPostCondition:
143
      return 'abort_by_post_condition';
2✔
144
    case DbTxStatus.DroppedReplaceByFee:
145
      return 'dropped_replace_by_fee';
3✔
146
    case DbTxStatus.DroppedReplaceAcrossFork:
147
      return 'dropped_replace_across_fork';
6✔
148
    case DbTxStatus.DroppedTooExpensive:
149
      return 'dropped_too_expensive';
3✔
150
    case DbTxStatus.DroppedStaleGarbageCollect:
151
    case DbTxStatus.DroppedApiGarbageCollect:
152
      return 'dropped_stale_garbage_collect';
5✔
153
    default:
154
      throw new Error(`Unexpected DbTxStatus: ${txStatus}`);
×
155
  }
156
}
157

158
export function getTxStatus(txStatus: DbTxStatus | string): string {
37✔
159
  if (txStatus == '') {
434✔
160
    return '';
78✔
161
  } else {
162
    return getTxStatusString(txStatus as DbTxStatus);
356✔
163
  }
164
}
165

166
type EventTypeString =
167
  | 'smart_contract_log'
168
  | 'stx_asset'
169
  | 'fungible_token_asset'
170
  | 'non_fungible_token_asset'
171
  | 'stx_lock';
172

173
export function getEventTypeString(eventTypeId: DbEventTypeId): EventTypeString {
37✔
174
  switch (eventTypeId) {
×
175
    case DbEventTypeId.SmartContractLog:
176
      return 'smart_contract_log';
×
177
    case DbEventTypeId.StxAsset:
178
      return 'stx_asset';
×
179
    case DbEventTypeId.FungibleTokenAsset:
180
      return 'fungible_token_asset';
×
181
    case DbEventTypeId.NonFungibleTokenAsset:
182
      return 'non_fungible_token_asset';
×
183
    case DbEventTypeId.StxLock:
184
      return 'stx_lock';
×
185
    default:
186
      throw new Error(`Unexpected DbEventTypeId: ${eventTypeId}`);
×
187
  }
188
}
189

190
export function getAssetEventTypeString(
37✔
191
  assetEventTypeId: DbAssetEventTypeId
192
): 'transfer' | 'mint' | 'burn' {
193
  switch (assetEventTypeId) {
222!
194
    case DbAssetEventTypeId.Transfer:
195
      return 'transfer';
115✔
196
    case DbAssetEventTypeId.Mint:
197
      return 'mint';
107✔
198
    case DbAssetEventTypeId.Burn:
199
      return 'burn';
×
200
    default:
201
      throw new Error(`Unexpected DbAssetEventTypeId: ${assetEventTypeId}`);
×
202
  }
203
}
204

205
export function parsePox2Event(poxEvent: DbPox2Event) {
37✔
206
  const baseInfo = {
×
207
    block_height: poxEvent.block_height,
208
    tx_id: poxEvent.tx_id,
209
    tx_index: poxEvent.tx_index,
210
    event_index: poxEvent.event_index,
211
    stacker: poxEvent.stacker,
212
    locked: poxEvent.locked.toString(),
213
    balance: poxEvent.balance.toString(),
214
    burnchain_unlock_height: poxEvent.burnchain_unlock_height.toString(),
215
    pox_addr: poxEvent.pox_addr,
216
    pox_addr_raw: poxEvent.pox_addr_raw,
217
    name: poxEvent.name,
218
  };
219
  switch (poxEvent.name) {
×
220
    case Pox2EventName.HandleUnlock: {
221
      return {
×
222
        ...baseInfo,
223
        data: {
224
          first_cycle_locked: poxEvent.data.first_cycle_locked.toString(),
225
          first_unlocked_cycle: poxEvent.data.first_unlocked_cycle.toString(),
226
        },
227
      };
228
    }
229
    case Pox2EventName.StackStx: {
230
      return {
×
231
        ...baseInfo,
232
        data: {
233
          lock_amount: poxEvent.data.lock_amount.toString(),
234
          lock_period: poxEvent.data.lock_period.toString(),
235
          start_burn_height: poxEvent.data.start_burn_height.toString(),
236
          unlock_burn_height: poxEvent.data.unlock_burn_height.toString(),
237
        },
238
      };
239
    }
240
    case Pox2EventName.StackIncrease: {
241
      return {
×
242
        ...baseInfo,
243
        data: {
244
          increase_by: poxEvent.data.increase_by.toString(),
245
          total_locked: poxEvent.data.total_locked.toString(),
246
        },
247
      };
248
    }
249
    case Pox2EventName.StackExtend: {
250
      return {
×
251
        ...baseInfo,
252
        data: {
253
          extend_count: poxEvent.data.extend_count.toString(),
254
          unlock_burn_height: poxEvent.data.unlock_burn_height.toString(),
255
        },
256
      };
257
    }
258
    case Pox2EventName.DelegateStx: {
NEW
259
      return {
×
260
        ...baseInfo,
261
        data: {
262
          amount_ustx: poxEvent.data.amount_ustx.toString(),
263
          delegate_to: poxEvent.data.delegate_to,
264
          unlock_burn_height: poxEvent.data.unlock_burn_height?.toString(),
265
        },
266
      };
267
    }
268
    case Pox2EventName.DelegateStackStx: {
269
      return {
×
270
        ...baseInfo,
271
        data: {
272
          lock_amount: poxEvent.data.lock_amount.toString(),
273
          unlock_burn_height: poxEvent.data.unlock_burn_height.toString(),
274
          start_burn_height: poxEvent.data.start_burn_height.toString(),
275
          lock_period: poxEvent.data.lock_period.toString(),
276
          delegator: poxEvent.data.delegator,
277
        },
278
      };
279
    }
280
    case Pox2EventName.DelegateStackIncrease: {
281
      return {
×
282
        ...baseInfo,
283
        data: {
284
          increase_by: poxEvent.data.increase_by.toString(),
285
          total_locked: poxEvent.data.total_locked.toString(),
286
          delegator: poxEvent.data.delegator,
287
        },
288
      };
289
    }
290
    case Pox2EventName.DelegateStackExtend: {
291
      return {
×
292
        ...baseInfo,
293
        data: {
294
          unlock_burn_height: poxEvent.data.unlock_burn_height.toString(),
295
          extend_count: poxEvent.data.extend_count.toString(),
296
          delegator: poxEvent.data.delegator,
297
        },
298
      };
299
    }
300
    case Pox2EventName.StackAggregationCommit: {
301
      return {
×
302
        ...baseInfo,
303
        data: {
304
          reward_cycle: poxEvent.data.reward_cycle.toString(),
305
          amount_ustx: poxEvent.data.amount_ustx.toString(),
306
        },
307
      };
308
    }
309
    case Pox2EventName.StackAggregationCommitIndexed: {
310
      return {
×
311
        ...baseInfo,
312
        data: {
313
          reward_cycle: poxEvent.data.reward_cycle.toString(),
314
          amount_ustx: poxEvent.data.amount_ustx.toString(),
315
        },
316
      };
317
    }
318
    case Pox2EventName.StackAggregationIncrease: {
319
      return {
×
320
        ...baseInfo,
321
        data: {
322
          reward_cycle: poxEvent.data.reward_cycle.toString(),
323
          amount_ustx: poxEvent.data.amount_ustx.toString(),
324
        },
325
      };
326
    }
327
    default:
328
      throw new Error(`Unexpected Pox2 event name ${(poxEvent as DbPox2Event).name}`);
×
329
  }
330
}
331

332
export function parseDbEvent(dbEvent: DbEvent): TransactionEvent {
37✔
333
  switch (dbEvent.event_type) {
103!
334
    case DbEventTypeId.SmartContractLog: {
335
      const parsedClarityValue = decodeClarityValueToRepr(dbEvent.value);
7✔
336
      const event: TransactionEventSmartContractLog = {
7✔
337
        event_index: dbEvent.event_index,
338
        event_type: 'smart_contract_log',
339
        tx_id: dbEvent.tx_id,
340
        contract_log: {
341
          contract_id: dbEvent.contract_identifier,
342
          topic: dbEvent.topic,
343
          value: {
344
            hex: dbEvent.value,
345
            repr: parsedClarityValue,
346
          },
347
        },
348
      };
349
      return event;
7✔
350
    }
351
    case DbEventTypeId.StxLock: {
352
      const event: TransactionEventStxLock = {
6✔
353
        event_index: dbEvent.event_index,
354
        event_type: 'stx_lock',
355
        tx_id: dbEvent.tx_id,
356
        stx_lock_event: {
357
          locked_amount: dbEvent.locked_amount.toString(10),
358
          unlock_height: Number(dbEvent.unlock_height),
359
          locked_address: dbEvent.locked_address,
360
        },
361
      };
362
      return event;
6✔
363
    }
364
    case DbEventTypeId.StxAsset: {
365
      const event: TransactionEventStxAsset = {
17✔
366
        event_index: dbEvent.event_index,
367
        event_type: 'stx_asset',
368
        tx_id: dbEvent.tx_id,
369
        asset: {
370
          asset_event_type: getAssetEventTypeString(dbEvent.asset_event_type_id),
371
          sender: dbEvent.sender || '',
17!
372
          recipient: dbEvent.recipient || '',
17!
373
          amount: dbEvent.amount.toString(10),
374
        },
375
      };
376
      if (dbEvent.asset_event_type_id === DbAssetEventTypeId.Transfer && dbEvent.memo) {
17✔
377
        event.asset.memo = dbEvent.memo;
5✔
378
      }
379
      return event;
17✔
380
    }
381
    case DbEventTypeId.FungibleTokenAsset: {
382
      const event: TransactionEventFungibleAsset = {
22✔
383
        event_index: dbEvent.event_index,
384
        event_type: 'fungible_token_asset',
385
        tx_id: dbEvent.tx_id,
386
        asset: {
387
          asset_event_type: getAssetEventTypeString(dbEvent.asset_event_type_id),
388
          asset_id: dbEvent.asset_identifier,
389
          sender: dbEvent.sender || '',
22!
390
          recipient: dbEvent.recipient || '',
28✔
391
          amount: dbEvent.amount.toString(10),
392
        },
393
      };
394
      return event;
22✔
395
    }
396
    case DbEventTypeId.NonFungibleTokenAsset: {
397
      const parsedClarityValue = decodeClarityValueToRepr(dbEvent.value);
51✔
398
      const event: TransactionEventNonFungibleAsset = {
51✔
399
        event_index: dbEvent.event_index,
400
        event_type: 'non_fungible_token_asset',
401
        tx_id: dbEvent.tx_id,
402
        asset: {
403
          asset_event_type: getAssetEventTypeString(dbEvent.asset_event_type_id),
404
          asset_id: dbEvent.asset_identifier,
405
          sender: dbEvent.sender || '',
51!
406
          recipient: dbEvent.recipient || '',
57✔
407
          value: {
408
            hex: dbEvent.value,
409
            repr: parsedClarityValue,
410
          },
411
        },
412
      };
413
      return event;
51✔
414
    }
415
    default:
416
      throw new Error(`Unexpected event_type in: ${JSON.stringify(dbEvent)}`);
×
417
  }
418
}
419

420
/**
421
 * Fetch block from datastore by blockHash or blockHeight (index)
422
 * If both blockHeight and blockHash are provided, blockHeight is used.
423
 * If neither argument is present, the most recent block is returned.
424
 * @param db -- datastore
425
 * @param fetchTransactions -- return block transactions
426
 * @param chainId -- chain ID
427
 * @param blockHash -- hexadecimal hash string
428
 * @param blockHeight -- number
429
 */
430
export async function getRosettaBlockFromDataStore(
37✔
431
  db: PgStore,
432
  fetchTransactions: boolean,
433
  chainId: ChainID,
434
  blockHash?: string,
435
  blockHeight?: number
436
): Promise<FoundOrNot<RosettaBlock>> {
437
  return await db.sqlTransaction(async sql => {
311✔
438
    let query;
439
    if (blockHash) {
310✔
440
      query = db.getBlock({ hash: blockHash });
4✔
441
    } else if (blockHeight && blockHeight > 0) {
306✔
442
      query = db.getBlock({ height: blockHeight });
204✔
443
    } else {
444
      query = db.getCurrentBlock();
102✔
445
    }
446
    const blockQuery = await query;
310✔
447

448
    if (!blockQuery.found) {
310!
449
      return { found: false };
×
450
    }
451
    const dbBlock = blockQuery.result;
310✔
452
    let blockTxs = {} as FoundOrNot<RosettaTransaction[]>;
310✔
453
    blockTxs.found = false;
310✔
454
    if (fetchTransactions) {
310✔
455
      blockTxs = await getRosettaBlockTransactionsFromDataStore({
108✔
456
        blockHash: dbBlock.block_hash,
457
        indexBlockHash: dbBlock.index_block_hash,
458
        db,
459
        chainId,
460
      });
461
    }
462

463
    const parentBlockHash = dbBlock.parent_block_hash;
310✔
464
    let parent_block_identifier: RosettaParentBlockIdentifier;
465

466
    if (dbBlock.block_height <= 1) {
310✔
467
      // case for genesis block
468
      parent_block_identifier = {
102✔
469
        index: dbBlock.block_height,
470
        hash: dbBlock.block_hash,
471
      };
472
    } else {
473
      const parentBlockQuery = await db.getBlock({ hash: parentBlockHash });
208✔
474
      if (parentBlockQuery.found) {
208!
475
        const parentBlock = parentBlockQuery.result;
208✔
476
        parent_block_identifier = {
208✔
477
          index: parentBlock.block_height,
478
          hash: parentBlock.block_hash,
479
        };
480
      } else {
481
        return { found: false };
×
482
      }
483
    }
484

485
    const apiBlock: RosettaBlock = {
310✔
486
      block_identifier: { index: dbBlock.block_height, hash: dbBlock.block_hash },
487
      parent_block_identifier,
488
      timestamp: dbBlock.burn_block_time * 1000,
489
      transactions: blockTxs.found ? blockTxs.result : [],
310✔
490
    };
491
    return { found: true, result: apiBlock };
310✔
492
  });
493
}
494

495
export async function getUnanchoredTxsFromDataStore(db: PgStore): Promise<Transaction[]> {
37✔
496
  const dbTxs = await db.getUnanchoredTxs();
1✔
497
  const parsedTxs = dbTxs.txs.map(dbTx => parseDbTx(dbTx));
2✔
498
  return parsedTxs;
1✔
499
}
500

501
function parseDbMicroblock(mb: DbMicroblock, txs: string[]): Microblock {
502
  const microblock: Microblock = {
6✔
503
    canonical: mb.canonical,
504
    microblock_canonical: mb.microblock_canonical,
505
    microblock_hash: mb.microblock_hash,
506
    microblock_sequence: mb.microblock_sequence,
507
    microblock_parent_hash: mb.microblock_parent_hash,
508
    block_height: mb.block_height,
509
    parent_block_height: mb.parent_block_height,
510
    parent_block_hash: mb.parent_block_hash,
511
    block_hash: mb.block_hash,
512
    txs: txs,
513
    parent_burn_block_height: mb.parent_burn_block_height,
514
    parent_burn_block_hash: mb.parent_burn_block_hash,
515
    parent_burn_block_time: mb.parent_burn_block_time,
516
    parent_burn_block_time_iso:
517
      mb.parent_burn_block_time > 0 ? unixEpochToIso(mb.parent_burn_block_time) : '',
6!
518
  };
519
  return microblock;
6✔
520
}
521

522
export async function getMicroblockFromDataStore({
37✔
523
  db,
524
  microblockHash,
525
}: {
526
  db: PgStore;
527
  microblockHash: string;
528
}): Promise<FoundOrNot<Microblock>> {
529
  const query = await db.getMicroblock({ microblockHash: microblockHash });
7✔
530
  if (!query.found) {
6✔
531
    return {
2✔
532
      found: false,
533
    };
534
  }
535
  const microblock = parseDbMicroblock(query.result.microblock, query.result.txs);
4✔
536
  return {
4✔
537
    found: true,
538
    result: microblock,
539
  };
540
}
541

542
export async function getMicroblocksFromDataStore(args: {
37✔
543
  db: PgStore;
544
  limit: number;
545
  offset: number;
546
}): Promise<{ total: number; result: Microblock[] }> {
547
  const query = await args.db.getMicroblocks({
2✔
548
    limit: args.limit,
549
    offset: args.offset,
550
  });
551
  const result = query.result.map(r => parseDbMicroblock(r.microblock, r.txs));
2✔
552
  return {
2✔
553
    total: query.total,
554
    result: result,
555
  };
556
}
557

558
export async function getBlocksWithMetadata(args: { limit: number; offset: number; db: PgStore }) {
37✔
559
  const blocks = await args.db.getBlocksWithMetadata({
4✔
560
    limit: args.limit,
561
    offset: args.offset,
562
  });
563
  const results = blocks.results.map(block =>
3✔
564
    parseDbBlock(
2✔
565
      block.block,
566
      block.txs,
567
      block.microblocks_accepted,
568
      block.microblocks_streamed,
569
      block.microblock_tx_count
570
    )
571
  );
572
  return { results, total: blocks.total };
3✔
573
}
574

575
export async function getBlockFromDataStore({
37✔
576
  blockIdentifer,
577
  db,
578
}: {
579
  blockIdentifer: BlockIdentifier;
580
  db: PgStore;
581
}): Promise<FoundOrNot<Block>> {
582
  const blockQuery = await db.getBlockWithMetadata(blockIdentifer, {
21✔
583
    txs: true,
584
    microblocks: true,
585
  });
586
  if (!blockQuery.found) {
20✔
587
    return { found: false };
2✔
588
  }
589
  const result = blockQuery.result;
18✔
590
  const apiBlock = parseDbBlock(
18✔
591
    result.block,
592
    result.txs.map(tx => tx.tx_id),
20✔
593
    result.microblocks.accepted.map(mb => mb.microblock_hash),
1✔
594
    result.microblocks.streamed.map(mb => mb.microblock_hash),
3✔
595
    result.microblock_tx_count
596
  );
597
  return { found: true, result: apiBlock };
18✔
598
}
599

600
function parseDbBlock(
601
  dbBlock: DbBlock,
602
  txIds: string[],
603
  microblocksAccepted: string[],
604
  microblocksStreamed: string[],
605
  microblock_tx_count: Record<string, number>
606
): Block {
607
  const apiBlock: Block = {
23✔
608
    canonical: dbBlock.canonical,
609
    height: dbBlock.block_height,
610
    hash: dbBlock.block_hash,
611
    index_block_hash: dbBlock.index_block_hash,
612
    parent_block_hash: dbBlock.parent_block_hash,
613
    burn_block_time: dbBlock.burn_block_time,
614
    burn_block_time_iso: unixEpochToIso(dbBlock.burn_block_time),
615
    burn_block_hash: dbBlock.burn_block_hash,
616
    burn_block_height: dbBlock.burn_block_height,
617
    miner_txid: dbBlock.miner_txid,
618
    parent_microblock_hash:
619
      dbBlock.parent_microblock_hash === EMPTY_HASH_256 ? '' : dbBlock.parent_microblock_hash,
23!
620
    parent_microblock_sequence:
621
      dbBlock.parent_microblock_hash === EMPTY_HASH_256 ? -1 : dbBlock.parent_microblock_sequence,
23!
622
    txs: [...txIds],
623
    microblocks_accepted: [...microblocksAccepted],
624
    microblocks_streamed: [...microblocksStreamed],
625
    execution_cost_read_count: dbBlock.execution_cost_read_count,
626
    execution_cost_read_length: dbBlock.execution_cost_read_length,
627
    execution_cost_runtime: dbBlock.execution_cost_runtime,
628
    execution_cost_write_count: dbBlock.execution_cost_write_count,
629
    execution_cost_write_length: dbBlock.execution_cost_write_length,
630
    microblock_tx_count,
631
  };
632
  return apiBlock;
23✔
633
}
634

635
async function parseRosettaTxDetail(opts: {
636
  block_height: number;
637
  indexBlockHash: string;
638
  tx: DbTx;
639
  db: PgStore;
640
  minerRewards: DbMinerReward[];
641
  unlockingEvents: StxUnlockEvent[];
642
  chainId: ChainID;
643
}): Promise<RosettaTransaction> {
644
  return await opts.db.sqlTransaction(async sql => {
193✔
645
    let events: DbEvent[] = [];
193✔
646
    if (opts.block_height > 1) {
193✔
647
      // only return events of blocks at height greater than 1
648
      const eventsQuery = await opts.db.getTxEvents({
183✔
649
        txId: opts.tx.tx_id,
650
        indexBlockHash: opts.indexBlockHash,
651
        limit: 5000,
652
        offset: 0,
653
      });
654
      events = eventsQuery.results;
183✔
655
    }
656
    const operations = await getOperations(
193✔
657
      opts.tx,
658
      opts.db,
659
      opts.chainId,
660
      opts.minerRewards,
661
      events,
662
      opts.unlockingEvents
663
    );
664
    const txMemo = parseTransactionMemo(opts.tx.token_transfer_memo);
192✔
665
    const rosettaTx: RosettaTransaction = {
192✔
666
      transaction_identifier: { hash: opts.tx.tx_id },
667
      operations: operations,
668
    };
669
    if (txMemo) {
192✔
670
      rosettaTx.metadata = {
51✔
671
        memo: txMemo,
672
      };
673
    }
674
    return rosettaTx;
192✔
675
  });
676
}
677

678
async function getRosettaBlockTxFromDataStore(opts: {
679
  tx: DbTx;
680
  block: DbBlock;
681
  db: PgStore;
682
  chainId: ChainID;
683
}): Promise<FoundOrNot<RosettaTransaction>> {
684
  return await opts.db.sqlTransaction(async sql => {
14✔
685
    let minerRewards: DbMinerReward[] = [],
14✔
686
      unlockingEvents: StxUnlockEvent[] = [];
14✔
687

688
    if (
14✔
689
      opts.tx.type_id === DbTxTypeId.Coinbase ||
21✔
690
      opts.tx.type_id === DbTxTypeId.CoinbaseToAltRecipient
691
    ) {
692
      minerRewards = await opts.db.getMinersRewardsAtHeight({
8✔
693
        blockHeight: opts.block.block_height,
694
      });
695
      unlockingEvents = await opts.db.getUnlockedAddressesAtBlock(opts.block);
8✔
696
    }
697

698
    const rosettaTx = await parseRosettaTxDetail({
14✔
699
      block_height: opts.block.block_height,
700
      indexBlockHash: opts.tx.index_block_hash,
701
      tx: opts.tx,
702
      db: opts.db,
703
      minerRewards,
704
      unlockingEvents,
705
      chainId: opts.chainId,
706
    });
707
    return { found: true, result: rosettaTx };
13✔
708
  });
709
}
710

711
async function getRosettaBlockTransactionsFromDataStore(opts: {
712
  blockHash: string;
713
  indexBlockHash: string;
714
  db: PgStore;
715
  chainId: ChainID;
716
}): Promise<FoundOrNot<RosettaTransaction[]>> {
717
  return await opts.db.sqlTransaction(async sql => {
108✔
718
    const blockQuery = await opts.db.getBlock({ hash: opts.blockHash });
108✔
719
    if (!blockQuery.found) {
108!
720
      return { found: false };
×
721
    }
722

723
    const txsQuery = await opts.db.getBlockTxsRows(opts.blockHash);
108✔
724
    const minerRewards = await opts.db.getMinersRewardsAtHeight({
108✔
725
      blockHeight: blockQuery.result.block_height,
726
    });
727

728
    if (!txsQuery.found) {
108!
729
      return { found: false };
×
730
    }
731

732
    const unlockingEvents = await opts.db.getUnlockedAddressesAtBlock(blockQuery.result);
108✔
733

734
    const transactions: RosettaTransaction[] = [];
108✔
735

736
    for (const tx of txsQuery.result) {
108✔
737
      const rosettaTx = await parseRosettaTxDetail({
179✔
738
        block_height: blockQuery.result.block_height,
739
        indexBlockHash: opts.indexBlockHash,
740
        tx,
741
        db: opts.db,
742
        minerRewards,
743
        unlockingEvents,
744
        chainId: opts.chainId,
745
      });
746
      transactions.push(rosettaTx);
179✔
747
    }
748

749
    return { found: true, result: transactions };
108✔
750
  });
751
}
752

753
export async function getRosettaTransactionFromDataStore(
37✔
754
  txId: string,
755
  db: PgStore,
756
  chainId: ChainID
757
): Promise<FoundOrNot<RosettaTransaction>> {
758
  return await db.sqlTransaction(async sql => {
14✔
759
    const txQuery = await db.getTx({ txId, includeUnanchored: false });
14✔
760
    if (!txQuery.found) {
14!
761
      return { found: false };
×
762
    }
763

764
    const blockQuery = await db.getBlock({ hash: txQuery.result.block_hash });
14✔
765
    if (!blockQuery.found) {
14!
766
      throw new Error(
×
767
        `Could not find block for tx: ${txId}, block_hash: ${txQuery.result.block_hash}, index_block_hash: ${txQuery.result.index_block_hash}`
768
      );
769
    }
770

771
    const rosettaTx = await getRosettaBlockTxFromDataStore({
14✔
772
      tx: txQuery.result,
773
      block: blockQuery.result,
774
      db,
775
      chainId,
776
    });
777

778
    if (!rosettaTx.found) {
13!
779
      throw new Error(
×
780
        `Rosetta block missing operations for tx: ${txId}, block_hash: ${txQuery.result.block_hash}, index_block_hash: ${txQuery.result.index_block_hash}`
781
      );
782
    }
783

784
    return rosettaTx;
13✔
785
  });
786
}
787

788
interface GetTxArgs {
789
  txId: string;
790
  includeUnanchored: boolean;
791
}
792

793
interface GetTxFromDbTxArgs extends GetTxArgs {
794
  dbTx: DbTx;
795
}
796

797
interface GetTxsWithEventsArgs extends GetTxsArgs {
798
  eventLimit: number;
799
  eventOffset: number;
800
}
801

802
interface GetTxsArgs {
803
  txIds: string[];
804
  includeUnanchored: boolean;
805
}
806

807
interface GetTxWithEventsArgs extends GetTxArgs {
808
  eventLimit: number;
809
  eventOffset: number;
810
}
811

812
function parseDbBaseTx(dbTx: DbTx | DbMempoolTx): BaseTransaction {
813
  const decodedPostConditions = decodePostConditions(dbTx.post_conditions);
261✔
814
  const normalizedPostConditions = decodedPostConditions.post_conditions.map(pc =>
261✔
815
    serializePostCondition(pc)
9✔
816
  );
817
  const tx: BaseTransaction = {
261✔
818
    tx_id: dbTx.tx_id,
819
    nonce: dbTx.nonce,
820
    sponsor_nonce: dbTx.sponsor_nonce,
821
    fee_rate: dbTx.fee_rate.toString(10),
822
    sender_address: dbTx.sender_address,
823
    sponsored: dbTx.sponsored,
824
    sponsor_address: dbTx.sponsor_address,
825
    post_condition_mode: serializePostConditionMode(decodedPostConditions.post_condition_mode),
826
    post_conditions: normalizedPostConditions,
827
    anchor_mode: getTxAnchorModeString(dbTx.anchor_mode),
828
  };
829
  return tx;
261✔
830
}
831

832
function parseDbTxTypeMetadata(dbTx: DbTx | DbMempoolTx): TransactionMetadata {
833
  switch (dbTx.type_id) {
261!
834
    case DbTxTypeId.TokenTransfer: {
835
      const metadata: TokenTransferTransactionMetadata = {
56✔
836
        tx_type: 'token_transfer',
837
        token_transfer: {
838
          recipient_address: unwrapOptional(
839
            dbTx.token_transfer_recipient_address,
840
            () => 'Unexpected nullish token_transfer_recipient_address'
×
841
          ),
842
          amount: unwrapOptional(
843
            dbTx.token_transfer_amount,
844
            () => 'Unexpected nullish token_transfer_amount'
×
845
          ).toString(10),
846
          memo: unwrapOptional(
847
            dbTx.token_transfer_memo,
848
            () => 'Unexpected nullish token_transfer_memo'
×
849
          ),
850
        },
851
      };
852
      return metadata;
56✔
853
    }
854
    case DbTxTypeId.SmartContract: {
855
      const metadata: SmartContractTransactionMetadata = {
7✔
856
        tx_type: 'smart_contract',
857
        smart_contract: {
858
          clarity_version: null as any,
859
          contract_id: unwrapOptional(
860
            dbTx.smart_contract_contract_id,
861
            () => 'Unexpected nullish smart_contract_contract_id'
×
862
          ),
863
          source_code: unwrapOptional(
864
            dbTx.smart_contract_source_code,
865
            () => 'Unexpected nullish smart_contract_source_code'
×
866
          ),
867
        },
868
      };
869
      return metadata;
7✔
870
    }
871
    case DbTxTypeId.VersionedSmartContract: {
872
      const metadata: SmartContractTransactionMetadata = {
4✔
873
        tx_type: 'smart_contract',
874
        smart_contract: {
875
          clarity_version: unwrapOptional(
876
            dbTx.smart_contract_clarity_version,
877
            () => 'Unexpected nullish smart_contract_clarity_version'
×
878
          ),
879
          contract_id: unwrapOptional(
880
            dbTx.smart_contract_contract_id,
881
            () => 'Unexpected nullish smart_contract_contract_id'
×
882
          ),
883
          source_code: unwrapOptional(
884
            dbTx.smart_contract_source_code,
885
            () => 'Unexpected nullish smart_contract_source_code'
×
886
          ),
887
        },
888
      };
889
      return metadata;
4✔
890
    }
891
    case DbTxTypeId.ContractCall: {
892
      return parseContractCallMetadata(dbTx);
32✔
893
    }
894
    case DbTxTypeId.PoisonMicroblock: {
895
      const metadata: PoisonMicroblockTransactionMetadata = {
×
896
        tx_type: 'poison_microblock',
897
        poison_microblock: {
898
          microblock_header_1: unwrapOptional(dbTx.poison_microblock_header_1),
899
          microblock_header_2: unwrapOptional(dbTx.poison_microblock_header_2),
900
        },
901
      };
902
      return metadata;
×
903
    }
904
    case DbTxTypeId.Coinbase: {
905
      const metadata: CoinbaseTransactionMetadata = {
158✔
906
        tx_type: 'coinbase',
907
        coinbase_payload: {
908
          data: unwrapOptional(dbTx.coinbase_payload, () => 'Unexpected nullish coinbase_payload'),
×
909
          alt_recipient: null as any,
910
        },
911
      };
912
      return metadata;
158✔
913
    }
914
    case DbTxTypeId.CoinbaseToAltRecipient: {
915
      const metadata: CoinbaseTransactionMetadata = {
4✔
916
        tx_type: 'coinbase',
917
        coinbase_payload: {
918
          data: unwrapOptional(dbTx.coinbase_payload, () => 'Unexpected nullish coinbase_payload'),
×
919
          alt_recipient: unwrapOptional(
920
            dbTx.coinbase_alt_recipient,
921
            () => 'Unexpected nullish coinbase_alt_recipient'
×
922
          ),
923
        },
924
      };
925
      return metadata;
4✔
926
    }
927
    default: {
928
      throw new Error(`Unexpected DbTxTypeId: ${dbTx.type_id}`);
×
929
    }
930
  }
931
}
932

933
export function parseContractCallMetadata(tx: BaseTx): ContractCallTransactionMetadata {
37✔
934
  const contractId = unwrapOptional(
35✔
935
    tx.contract_call_contract_id,
936
    () => 'Unexpected nullish contract_call_contract_id'
×
937
  );
938
  const functionName = unwrapOptional(
35✔
939
    tx.contract_call_function_name,
940
    () => 'Unexpected nullish contract_call_function_name'
×
941
  );
942
  let functionAbi: ClarityAbiFunction | undefined;
943
  const abi = tx.abi;
35✔
944
  if (abi) {
35✔
945
    const contractAbi: ClarityAbi = JSON.parse(abi);
32✔
946
    functionAbi = contractAbi.functions.find(fn => fn.name === functionName);
108✔
947
    if (!functionAbi) {
32!
948
      throw new Error(`Could not find function name "${functionName}" in ABI for ${contractId}`);
×
949
    }
950
  }
951

952
  const functionArgs = tx.contract_call_function_args
35✔
953
    ? decodeClarityValueList(tx.contract_call_function_args).map((c, fnArgIndex) => {
954
        const functionArgAbi = functionAbi
62✔
955
          ? functionAbi.args[fnArgIndex++]
956
          : { name: '', type: undefined };
957
        return {
62✔
958
          hex: c.hex,
959
          repr: c.repr,
960
          name: functionArgAbi.name,
961
          type: functionArgAbi.type
62✔
962
            ? getTypeString(functionArgAbi.type)
963
            : decodeClarityValueToTypeName(c.hex),
964
        };
965
      })
966
    : undefined;
967

968
  const metadata: ContractCallTransactionMetadata = {
35✔
969
    tx_type: 'contract_call',
970
    contract_call: {
971
      contract_id: contractId,
972
      function_name: functionName,
973
      function_signature: functionAbi ? abiFunctionToString(functionAbi) : '',
35✔
974
      function_args: functionArgs,
975
    },
976
  };
977
  return metadata;
35✔
978
}
979

980
function parseDbAbstractTx(dbTx: DbTx, baseTx: BaseTransaction): AbstractTransaction {
981
  const abstractTx: AbstractTransaction = {
200✔
982
    ...baseTx,
983
    is_unanchored: dbTx.block_hash === '0x',
984
    block_hash: dbTx.block_hash,
985
    parent_block_hash: dbTx.parent_block_hash,
986
    block_height: dbTx.block_height,
987
    burn_block_time: dbTx.burn_block_time,
988
    burn_block_time_iso: dbTx.burn_block_time > 0 ? unixEpochToIso(dbTx.burn_block_time) : '',
200✔
989
    parent_burn_block_time: dbTx.parent_burn_block_time,
990
    parent_burn_block_time_iso:
991
      dbTx.parent_burn_block_time > 0 ? unixEpochToIso(dbTx.parent_burn_block_time) : '',
200!
992
    canonical: dbTx.canonical,
993
    tx_index: dbTx.tx_index,
994
    tx_status: getTxStatusString(dbTx.status) as TransactionStatus,
995
    tx_result: {
996
      hex: dbTx.raw_result,
997
      repr: decodeClarityValueToRepr(dbTx.raw_result),
998
    },
999
    microblock_hash: dbTx.microblock_hash,
1000
    microblock_sequence: dbTx.microblock_sequence,
1001
    microblock_canonical: dbTx.microblock_canonical,
1002
    event_count: dbTx.event_count,
1003
    events: [],
1004
    execution_cost_read_count: dbTx.execution_cost_read_count,
1005
    execution_cost_read_length: dbTx.execution_cost_read_length,
1006
    execution_cost_runtime: dbTx.execution_cost_runtime,
1007
    execution_cost_write_count: dbTx.execution_cost_write_count,
1008
    execution_cost_write_length: dbTx.execution_cost_write_length,
1009
  };
1010
  return abstractTx;
200✔
1011
}
1012

1013
function parseDbAbstractMempoolTx(
1014
  dbMempoolTx: DbMempoolTx,
1015
  baseTx: BaseTransaction
1016
): AbstractMempoolTransaction {
1017
  const abstractMempoolTx: AbstractMempoolTransaction = {
61✔
1018
    ...baseTx,
1019
    tx_status: getTxStatusString(dbMempoolTx.status) as MempoolTransactionStatus,
1020
    receipt_time: dbMempoolTx.receipt_time,
1021
    receipt_time_iso: unixEpochToIso(dbMempoolTx.receipt_time),
1022
  };
1023
  return abstractMempoolTx;
61✔
1024
}
1025

1026
export function parseDbTx(dbTx: DbTx): Transaction {
37✔
1027
  const baseTx = parseDbBaseTx(dbTx);
200✔
1028
  const abstractTx = parseDbAbstractTx(dbTx, baseTx);
200✔
1029
  const txMetadata = parseDbTxTypeMetadata(dbTx);
200✔
1030
  const result: Transaction = {
200✔
1031
    ...abstractTx,
1032
    ...txMetadata,
1033
  };
1034
  return result;
200✔
1035
}
1036

1037
export function parseDbMempoolTx(dbMempoolTx: DbMempoolTx): MempoolTransaction {
37✔
1038
  const baseTx = parseDbBaseTx(dbMempoolTx);
61✔
1039
  const abstractTx = parseDbAbstractMempoolTx(dbMempoolTx, baseTx);
61✔
1040
  const txMetadata = parseDbTxTypeMetadata(dbMempoolTx);
61✔
1041
  const result: MempoolTransaction = {
61✔
1042
    ...abstractTx,
1043
    ...txMetadata,
1044
  };
1045
  return result;
61✔
1046
}
1047

1048
export async function getMempoolTxsFromDataStore(
37✔
1049
  db: PgStore,
1050
  args: GetTxsArgs
1051
): Promise<MempoolTransaction[]> {
1052
  const mempoolTxsQuery = await db.getMempoolTxs({
33✔
1053
    txIds: args.txIds,
1054
    includePruned: true,
1055
    includeUnanchored: args.includeUnanchored,
1056
  });
1057
  if (mempoolTxsQuery.length === 0) {
32✔
1058
    return [];
8✔
1059
  }
1060

1061
  const parsedMempoolTxs = mempoolTxsQuery.map(tx => parseDbMempoolTx(tx));
24✔
1062

1063
  return parsedMempoolTxs;
24✔
1064
}
1065

1066
async function getTxsFromDataStore(
1067
  db: PgStore,
1068
  args: GetTxsArgs | GetTxsWithEventsArgs
1069
): Promise<Transaction[]> {
1070
  return await db.sqlTransaction(async sql => {
35✔
1071
    // fetching all requested transactions from db
1072
    const txQuery = await db.getTxListDetails({
35✔
1073
      txIds: args.txIds,
1074
      includeUnanchored: args.includeUnanchored,
1075
    });
1076

1077
    // returning empty array if no transaction was found
1078
    if (txQuery.length === 0) {
35✔
1079
      return [];
16✔
1080
    }
1081

1082
    // parsing txQuery
1083
    const parsedTxs = txQuery.map(tx => parseDbTx(tx));
21✔
1084

1085
    // incase transaction events are requested
1086
    if ('eventLimit' in args) {
19!
1087
      const txIdsAndIndexHash = txQuery.map(tx => {
19✔
1088
        return {
21✔
1089
          txId: tx.tx_id,
1090
          indexBlockHash: tx.index_block_hash,
1091
        };
1092
      });
1093
      const txListEvents = await db.getTxListEvents({
19✔
1094
        txs: txIdsAndIndexHash,
1095
        limit: args.eventLimit,
1096
        offset: args.eventOffset,
1097
      });
1098
      // this will insert all events in a single parsedTransaction. Only specific ones are to be added.
1099
      const txsWithEvents: Transaction[] = parsedTxs.map(ptx => {
19✔
1100
        return {
21✔
1101
          ...ptx,
1102
          events: txListEvents.results
1103
            .filter(event => event.tx_id === ptx.tx_id)
6✔
1104
            .map(event => parseDbEvent(event)),
6✔
1105
        };
1106
      });
1107
      return txsWithEvents;
19✔
1108
    } else {
1109
      return parsedTxs;
×
1110
    }
1111
  });
1112
}
1113

1114
export async function getTxFromDataStore(
37✔
1115
  db: PgStore,
1116
  args: GetTxArgs | GetTxWithEventsArgs | GetTxFromDbTxArgs
1117
): Promise<FoundOrNot<Transaction>> {
1118
  return await db.sqlTransaction(async sql => {
29✔
1119
    let dbTx: DbTx;
1120
    if ('dbTx' in args) {
29✔
1121
      dbTx = args.dbTx;
10✔
1122
    } else {
1123
      const txQuery = await db.getTx({
19✔
1124
        txId: args.txId,
1125
        includeUnanchored: args.includeUnanchored,
1126
      });
1127
      if (!txQuery.found) {
19✔
1128
        return { found: false };
2✔
1129
      }
1130
      dbTx = txQuery.result;
17✔
1131
    }
1132

1133
    const parsedTx = parseDbTx(dbTx);
27✔
1134

1135
    // If tx events are requested
1136
    if ('eventLimit' in args) {
27!
1137
      const eventsQuery = await db.getTxEvents({
×
1138
        txId: args.txId,
1139
        indexBlockHash: dbTx.index_block_hash,
1140
        limit: args.eventLimit,
1141
        offset: args.eventOffset,
1142
      });
1143
      const txWithEvents: Transaction = {
×
1144
        ...parsedTx,
1145
        events: eventsQuery.results.map(event => parseDbEvent(event)),
×
1146
      };
1147
      return { found: true, result: txWithEvents };
×
1148
    } else {
1149
      return {
27✔
1150
        found: true,
1151
        result: parsedTx,
1152
      };
1153
    }
1154
  });
1155
}
1156

1157
export async function searchTxs(
37✔
1158
  db: PgStore,
1159
  args: GetTxsArgs | GetTxsWithEventsArgs
1160
): Promise<TransactionList> {
1161
  return await db.sqlTransaction(async sql => {
2✔
1162
    const minedTxs = await getTxsFromDataStore(db, args);
2✔
1163

1164
    const foundTransactions: TransactionFound[] = [];
2✔
1165
    const mempoolTxs: string[] = [];
2✔
1166
    minedTxs.forEach(tx => {
2✔
1167
      // filtering out mined transactions in canonical chain
1168
      if (tx.canonical && tx.microblock_canonical) {
4✔
1169
        foundTransactions.push({ found: true, result: tx });
4✔
1170
      }
1171
      // filtering out non canonical transactions to look into mempool table
1172
      if (!tx.canonical && !tx.microblock_canonical) {
4!
1173
        mempoolTxs.push(tx.tx_id);
×
1174
      }
1175
    });
1176

1177
    // filtering out tx_ids that were not mined / found
1178
    const notMinedTransactions: string[] = args.txIds.filter(
2✔
1179
      txId => !minedTxs.find(minedTx => txId === minedTx.tx_id)
13✔
1180
    );
1181

1182
    // finding transactions that are not mined and are not canonical in mempool
1183
    mempoolTxs.push(...notMinedTransactions);
2✔
1184
    const mempoolTxsQuery = await getMempoolTxsFromDataStore(db, {
2✔
1185
      txIds: mempoolTxs,
1186
      includeUnanchored: args.includeUnanchored,
1187
    });
1188

1189
    // merging found mempool transaction in found transactions object
1190
    foundTransactions.push(
2✔
1191
      ...mempoolTxsQuery.map((mtx: Transaction | MempoolTransaction) => {
1192
        return { found: true, result: mtx } as TransactionFound;
1✔
1193
      })
1194
    );
1195

1196
    // filtering out transactions that were not found anywhere
1197
    const notFoundTransactions: TransactionNotFound[] = args.txIds
2✔
1198
      .filter(txId => foundTransactions.findIndex(ftx => ftx.result?.tx_id === txId) < 0)
15✔
1199
      .map(txId => {
1200
        return { found: false, result: { tx_id: txId } };
1✔
1201
      });
1202

1203
    // generating response
1204
    const resp = [...foundTransactions, ...notFoundTransactions].reduce(
2✔
1205
      (map: TransactionList, obj) => {
1206
        if (obj.result) {
6✔
1207
          map[obj.result.tx_id] = obj;
6✔
1208
        }
1209
        return map;
6✔
1210
      },
1211
      {}
1212
    );
1213
    return resp;
2✔
1214
  });
1215
}
1216

1217
export async function searchTx(
37✔
1218
  db: PgStore,
1219
  args: GetTxArgs | GetTxWithEventsArgs
1220
): Promise<FoundOrNot<Transaction | MempoolTransaction>> {
1221
  return await db.sqlTransaction(async sql => {
33✔
1222
    // First, check the happy path: the tx is mined and in the canonical chain.
1223
    const minedTxs = await getTxsFromDataStore(db, { ...args, txIds: [args.txId] });
33✔
1224
    const minedTx = minedTxs[0] ?? undefined;
33✔
1225
    if (minedTx && minedTx.canonical && minedTx.microblock_canonical) {
33✔
1226
      return { found: true, result: minedTx };
17✔
1227
    } else {
1228
      // Otherwise, if not mined or not canonical, check in the mempool.
1229
      const mempoolTxQuery = await getMempoolTxsFromDataStore(db, {
16✔
1230
        ...args,
1231
        txIds: [args.txId],
1232
      });
1233
      const mempoolTx = mempoolTxQuery[0] ?? undefined;
16✔
1234
      if (mempoolTx) {
16✔
1235
        return { found: true, result: mempoolTx };
13✔
1236
      }
1237
      // Fallback for a situation where the tx was only mined in a non-canonical chain, but somehow not in the mempool table.
1238
      else if (minedTx) {
3!
1239
        logger.warn(`Tx only exists in a non-canonical chain, missing from mempool: ${args.txId}`);
×
1240
        return { found: true, result: minedTx };
×
1241
      }
1242
      // Tx not found in db.
1243
      else {
1244
        return { found: false };
3✔
1245
      }
1246
    }
1247
  });
1248
}
1249

1250
export async function searchHashWithMetadata(
37✔
1251
  hash: string,
1252
  db: PgStore
1253
): Promise<FoundOrNot<DbSearchResultWithMetadata>> {
1254
  return await db.sqlTransaction(async sql => {
8✔
1255
    // checking for tx
1256
    const txQuery = await db.getTxListDetails({ txIds: [hash], includeUnanchored: true });
8✔
1257
    if (txQuery.length > 0) {
8✔
1258
      // tx found
1259
      const tx = txQuery[0];
2✔
1260
      return {
2✔
1261
        found: true,
1262
        result: {
1263
          entity_type: 'tx_id',
1264
          entity_id: tx.tx_id,
1265
          entity_data: tx,
1266
        },
1267
      };
1268
    }
1269
    // checking for mempool tx
1270
    const mempoolTxQuery = await db.getMempoolTxs({
6✔
1271
      txIds: [hash],
1272
      includeUnanchored: true,
1273
      includePruned: true,
1274
    });
1275
    if (mempoolTxQuery.length > 0) {
6✔
1276
      // mempool tx found
1277
      const mempoolTx = mempoolTxQuery[0];
2✔
1278
      return {
2✔
1279
        found: true,
1280
        result: {
1281
          entity_type: 'mempool_tx_id',
1282
          entity_id: mempoolTx.tx_id,
1283
          entity_data: mempoolTx,
1284
        },
1285
      };
1286
    }
1287
    // checking for block
1288
    const blockQuery = await db.getBlockWithMetadata({ hash }, { txs: true, microblocks: true });
4✔
1289
    if (blockQuery.found) {
4✔
1290
      // block found
1291
      const result = parseDbBlock(
3✔
1292
        blockQuery.result.block,
1293
        blockQuery.result.txs.map(tx => tx.tx_id),
3✔
1294
        blockQuery.result.microblocks.accepted.map(mb => mb.microblock_hash),
×
1295
        blockQuery.result.microblocks.streamed.map(mb => mb.microblock_hash),
×
1296
        blockQuery.result.microblock_tx_count
1297
      );
1298
      return {
3✔
1299
        found: true,
1300
        result: {
1301
          entity_type: 'block_hash',
1302
          entity_id: result.hash,
1303
          entity_data: result,
1304
        },
1305
      };
1306
    }
1307
    // found nothing
1308
    return { found: false };
1✔
1309
  });
1310
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc