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

hirosystems / stacks-blockchain-api / 3759933007

pending completion
3759933007

Pull #1498

github

GitHub
Merge 884b9ca06 into 2f9cb0c21
Pull Request #1498: API v7 RC (2.1-capable release)

2075 of 3094 branches covered (67.07%)

482 of 751 new or added lines in 25 files covered. (64.18%)

7 existing lines in 3 files now uncovered.

7256 of 9239 relevant lines covered (78.54%)

1199.74 hits per line

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

79.53
/src/api/controllers/db-controller.ts
1
import {
58✔
2
  abiFunctionToString,
3
  ChainID,
4
  ClarityAbi,
5
  ClarityAbiFunction,
6
  getTypeString,
7
} from '@stacks/transactions';
8
import {
58✔
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 {
58✔
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';
58✔
65
import { serializePostCondition, serializePostConditionMode } from '../serializers/post-conditions';
58✔
66
import { getOperations, parseTransactionMemo } from '../../rosetta-helpers';
58✔
67
import { PgStore } from '../../datastore/pg-store';
68
import { Pox2EventName } from '../../pox-helpers';
69

70
export function parseTxTypeStrings(values: string[]): TransactionType[] {
58✔
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'] {
58✔
86
  switch (typeId) {
13,090!
87
    case DbTxTypeId.TokenTransfer:
88
      return 'token_transfer';
3,312✔
89
    case DbTxTypeId.SmartContract:
90
    case DbTxTypeId.VersionedSmartContract:
91
      return 'smart_contract';
3,180✔
92
    case DbTxTypeId.ContractCall:
93
      return 'contract_call';
3,175✔
94
    case DbTxTypeId.PoisonMicroblock:
95
      return 'poison_microblock';
3,160✔
96
    case DbTxTypeId.Coinbase:
97
    case DbTxTypeId.CoinbaseToAltRecipient:
98
      return 'coinbase';
263✔
99
    default:
100
      throw new Error(`Unexpected DbTxTypeId: ${typeId}`);
×
101
  }
102
}
103

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

117
export function getTxTypeId(typeString: Transaction['tx_type']): DbTxTypeId[] {
58✔
118
  switch (typeString) {
×
119
    case 'token_transfer':
NEW
120
      return [DbTxTypeId.TokenTransfer];
×
121
    case 'smart_contract':
NEW
122
      return [DbTxTypeId.SmartContract, DbTxTypeId.VersionedSmartContract];
×
123
    case 'contract_call':
NEW
124
      return [DbTxTypeId.ContractCall];
×
125
    case 'poison_microblock':
NEW
126
      return [DbTxTypeId.PoisonMicroblock];
×
127
    case 'coinbase':
NEW
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) {
638!
136
    case DbTxStatus.Pending:
137
      return 'pending';
40✔
138
    case DbTxStatus.Success:
139
      return 'success';
577✔
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 {
58✔
159
  if (txStatus == '') {
447✔
160
    return '';
78✔
161
  } else {
162
    return getTxStatusString(txStatus as DbTxStatus);
369✔
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 {
58✔
UNCOV
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:
UNCOV
184
      return 'stx_lock';
×
185
    default:
186
      throw new Error(`Unexpected DbEventTypeId: ${eventTypeId}`);
×
187
  }
188
}
189

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

205
export function parsePox2Event(poxEvent: DbPox2Event) {
58✔
206
  const baseInfo = {
14✔
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) {
14!
220
    case Pox2EventName.HandleUnlock: {
221
      return {
1✔
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 {
3✔
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 {
1✔
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 {
1✔
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.DelegateStackStx: {
259
      return {
2✔
260
        ...baseInfo,
261
        data: {
262
          lock_amount: poxEvent.data.lock_amount.toString(),
263
          unlock_burn_height: poxEvent.data.unlock_burn_height.toString(),
264
          start_burn_height: poxEvent.data.start_burn_height.toString(),
265
          lock_period: poxEvent.data.lock_period.toString(),
266
          delegator: poxEvent.data.delegator,
267
        },
268
      };
269
    }
270
    case Pox2EventName.DelegateStackIncrease: {
271
      return {
2✔
272
        ...baseInfo,
273
        data: {
274
          increase_by: poxEvent.data.increase_by.toString(),
275
          total_locked: poxEvent.data.total_locked.toString(),
276
          delegator: poxEvent.data.delegator,
277
        },
278
      };
279
    }
280
    case Pox2EventName.DelegateStackExtend: {
281
      return {
1✔
282
        ...baseInfo,
283
        data: {
284
          unlock_burn_height: poxEvent.data.unlock_burn_height.toString(),
285
          extend_count: poxEvent.data.extend_count.toString(),
286
          delegator: poxEvent.data.delegator,
287
        },
288
      };
289
    }
290
    case Pox2EventName.StackAggregationCommit: {
291
      return {
1✔
292
        ...baseInfo,
293
        data: {
294
          reward_cycle: poxEvent.data.reward_cycle.toString(),
295
          amount_ustx: poxEvent.data.amount_ustx.toString(),
296
        },
297
      };
298
    }
299
    case Pox2EventName.StackAggregationCommitIndexed: {
300
      return {
1✔
301
        ...baseInfo,
302
        data: {
303
          reward_cycle: poxEvent.data.reward_cycle.toString(),
304
          amount_ustx: poxEvent.data.amount_ustx.toString(),
305
        },
306
      };
307
    }
308
    case Pox2EventName.StackAggregationIncrease: {
309
      return {
1✔
310
        ...baseInfo,
311
        data: {
312
          reward_cycle: poxEvent.data.reward_cycle.toString(),
313
          amount_ustx: poxEvent.data.amount_ustx.toString(),
314
        },
315
      };
316
    }
317
    default:
NEW
318
      throw new Error(`Unexpected Pox2 event name ${(poxEvent as DbPox2Event).name}`);
×
319
  }
320
}
321

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

410
/**
411
 * Fetch block from datastore by blockHash or blockHeight (index)
412
 * If both blockHeight and blockHash are provided, blockHeight is used.
413
 * If neither argument is present, the most recent block is returned.
414
 * @param db -- datastore
415
 * @param fetchTransactions -- return block transactions
416
 * @param chainId -- chain ID
417
 * @param blockHash -- hexadecimal hash string
418
 * @param blockHeight -- number
419
 */
420
export async function getRosettaBlockFromDataStore(
58✔
421
  db: PgStore,
422
  fetchTransactions: boolean,
423
  chainId: ChainID,
424
  blockHash?: string,
425
  blockHeight?: number
426
): Promise<FoundOrNot<RosettaBlock>> {
427
  return await db.sqlTransaction(async sql => {
326✔
428
    let query;
429
    if (blockHash) {
325✔
430
      query = db.getBlock({ hash: blockHash });
13✔
431
    } else if (blockHeight && blockHeight > 0) {
312✔
432
      query = db.getBlock({ height: blockHeight });
209✔
433
    } else {
434
      query = db.getCurrentBlock();
103✔
435
    }
436
    const blockQuery = await query;
325✔
437

438
    if (!blockQuery.found) {
325!
439
      return { found: false };
×
440
    }
441
    const dbBlock = blockQuery.result;
325✔
442
    let blockTxs = {} as FoundOrNot<RosettaTransaction[]>;
325✔
443
    blockTxs.found = false;
325✔
444
    if (fetchTransactions) {
325✔
445
      blockTxs = await getRosettaBlockTransactionsFromDataStore({
121✔
446
        blockHash: dbBlock.block_hash,
447
        indexBlockHash: dbBlock.index_block_hash,
448
        db,
449
        chainId,
450
      });
451
    }
452

453
    const parentBlockHash = dbBlock.parent_block_hash;
325✔
454
    let parent_block_identifier: RosettaParentBlockIdentifier;
455

456
    if (dbBlock.block_height <= 1) {
325✔
457
      // case for genesis block
458
      parent_block_identifier = {
103✔
459
        index: dbBlock.block_height,
460
        hash: dbBlock.block_hash,
461
      };
462
    } else {
463
      const parentBlockQuery = await db.getBlock({ hash: parentBlockHash });
222✔
464
      if (parentBlockQuery.found) {
222!
465
        const parentBlock = parentBlockQuery.result;
222✔
466
        parent_block_identifier = {
222✔
467
          index: parentBlock.block_height,
468
          hash: parentBlock.block_hash,
469
        };
470
      } else {
471
        return { found: false };
×
472
      }
473
    }
474

475
    const apiBlock: RosettaBlock = {
325✔
476
      block_identifier: { index: dbBlock.block_height, hash: dbBlock.block_hash },
477
      parent_block_identifier,
478
      timestamp: dbBlock.burn_block_time * 1000,
479
      transactions: blockTxs.found ? blockTxs.result : [],
325✔
480
    };
481
    return { found: true, result: apiBlock };
325✔
482
  });
483
}
484

485
export async function getUnanchoredTxsFromDataStore(db: PgStore): Promise<Transaction[]> {
58✔
486
  const dbTxs = await db.getUnanchoredTxs();
1✔
487
  const parsedTxs = dbTxs.txs.map(dbTx => parseDbTx(dbTx));
2✔
488
  return parsedTxs;
1✔
489
}
490

491
function parseDbMicroblock(mb: DbMicroblock, txs: string[]): Microblock {
492
  const microblock: Microblock = {
6✔
493
    canonical: mb.canonical,
494
    microblock_canonical: mb.microblock_canonical,
495
    microblock_hash: mb.microblock_hash,
496
    microblock_sequence: mb.microblock_sequence,
497
    microblock_parent_hash: mb.microblock_parent_hash,
498
    block_height: mb.block_height,
499
    parent_block_height: mb.parent_block_height,
500
    parent_block_hash: mb.parent_block_hash,
501
    block_hash: mb.block_hash,
502
    txs: txs,
503
    parent_burn_block_height: mb.parent_burn_block_height,
504
    parent_burn_block_hash: mb.parent_burn_block_hash,
505
    parent_burn_block_time: mb.parent_burn_block_time,
506
    parent_burn_block_time_iso:
507
      mb.parent_burn_block_time > 0 ? unixEpochToIso(mb.parent_burn_block_time) : '',
6!
508
  };
509
  return microblock;
6✔
510
}
511

512
export async function getMicroblockFromDataStore({
58✔
513
  db,
514
  microblockHash,
515
}: {
516
  db: PgStore;
517
  microblockHash: string;
518
}): Promise<FoundOrNot<Microblock>> {
519
  const query = await db.getMicroblock({ microblockHash: microblockHash });
7✔
520
  if (!query.found) {
6✔
521
    return {
2✔
522
      found: false,
523
    };
524
  }
525
  const microblock = parseDbMicroblock(query.result.microblock, query.result.txs);
4✔
526
  return {
4✔
527
    found: true,
528
    result: microblock,
529
  };
530
}
531

532
export async function getMicroblocksFromDataStore(args: {
58✔
533
  db: PgStore;
534
  limit: number;
535
  offset: number;
536
}): Promise<{ total: number; result: Microblock[] }> {
537
  const query = await args.db.getMicroblocks({
2✔
538
    limit: args.limit,
539
    offset: args.offset,
540
  });
541
  const result = query.result.map(r => parseDbMicroblock(r.microblock, r.txs));
2✔
542
  return {
2✔
543
    total: query.total,
544
    result: result,
545
  };
546
}
547

548
export async function getBlocksWithMetadata(args: { limit: number; offset: number; db: PgStore }) {
58✔
549
  const blocks = await args.db.getBlocksWithMetadata({
4✔
550
    limit: args.limit,
551
    offset: args.offset,
552
  });
553
  const results = blocks.results.map(block =>
3✔
554
    parseDbBlock(
2✔
555
      block.block,
556
      block.txs,
557
      block.microblocks_accepted,
558
      block.microblocks_streamed,
559
      block.microblock_tx_count
560
    )
561
  );
562
  return { results, total: blocks.total };
3✔
563
}
564

565
export async function getBlockFromDataStore({
58✔
566
  blockIdentifer,
567
  db,
568
}: {
569
  blockIdentifer: BlockIdentifier;
570
  db: PgStore;
571
}): Promise<FoundOrNot<Block>> {
572
  const blockQuery = await db.getBlockWithMetadata(blockIdentifer, {
22✔
573
    txs: true,
574
    microblocks: true,
575
  });
576
  if (!blockQuery.found) {
21✔
577
    return { found: false };
2✔
578
  }
579
  const result = blockQuery.result;
19✔
580
  const apiBlock = parseDbBlock(
19✔
581
    result.block,
582
    result.txs.map(tx => tx.tx_id),
28✔
583
    result.microblocks.accepted.map(mb => mb.microblock_hash),
1✔
584
    result.microblocks.streamed.map(mb => mb.microblock_hash),
3✔
585
    result.microblock_tx_count
586
  );
587
  return { found: true, result: apiBlock };
19✔
588
}
589

590
function parseDbBlock(
591
  dbBlock: DbBlock,
592
  txIds: string[],
593
  microblocksAccepted: string[],
594
  microblocksStreamed: string[],
595
  microblock_tx_count: Record<string, number>
596
): Block {
597
  const apiBlock: Block = {
24✔
598
    canonical: dbBlock.canonical,
599
    height: dbBlock.block_height,
600
    hash: dbBlock.block_hash,
601
    index_block_hash: dbBlock.index_block_hash,
602
    parent_block_hash: dbBlock.parent_block_hash,
603
    burn_block_time: dbBlock.burn_block_time,
604
    burn_block_time_iso: unixEpochToIso(dbBlock.burn_block_time),
605
    burn_block_hash: dbBlock.burn_block_hash,
606
    burn_block_height: dbBlock.burn_block_height,
607
    miner_txid: dbBlock.miner_txid,
608
    parent_microblock_hash:
609
      dbBlock.parent_microblock_hash === EMPTY_HASH_256 ? '' : dbBlock.parent_microblock_hash,
24✔
610
    parent_microblock_sequence:
611
      dbBlock.parent_microblock_hash === EMPTY_HASH_256 ? -1 : dbBlock.parent_microblock_sequence,
24✔
612
    txs: [...txIds],
613
    microblocks_accepted: [...microblocksAccepted],
614
    microblocks_streamed: [...microblocksStreamed],
615
    execution_cost_read_count: dbBlock.execution_cost_read_count,
616
    execution_cost_read_length: dbBlock.execution_cost_read_length,
617
    execution_cost_runtime: dbBlock.execution_cost_runtime,
618
    execution_cost_write_count: dbBlock.execution_cost_write_count,
619
    execution_cost_write_length: dbBlock.execution_cost_write_length,
620
    microblock_tx_count,
621
  };
622
  return apiBlock;
24✔
623
}
624

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

668
async function getRosettaBlockTxFromDataStore(opts: {
669
  tx: DbTx;
670
  block: DbBlock;
671
  db: PgStore;
672
  chainId: ChainID;
673
}): Promise<FoundOrNot<RosettaTransaction>> {
674
  return await opts.db.sqlTransaction(async sql => {
14✔
675
    let minerRewards: DbMinerReward[] = [],
14✔
676
      unlockingEvents: StxUnlockEvent[] = [];
14✔
677

678
    if (
14✔
679
      opts.tx.type_id === DbTxTypeId.Coinbase ||
21✔
680
      opts.tx.type_id === DbTxTypeId.CoinbaseToAltRecipient
681
    ) {
682
      minerRewards = await opts.db.getMinersRewardsAtHeight({
8✔
683
        blockHeight: opts.block.block_height,
684
      });
685
      unlockingEvents = await opts.db.getUnlockedAddressesAtBlock(opts.block);
8✔
686
    }
687

688
    const rosettaTx = await parseRosettaTxDetail({
14✔
689
      block_height: opts.block.block_height,
690
      indexBlockHash: opts.tx.index_block_hash,
691
      tx: opts.tx,
692
      db: opts.db,
693
      minerRewards,
694
      unlockingEvents,
695
      chainId: opts.chainId,
696
    });
697
    return { found: true, result: rosettaTx };
13✔
698
  });
699
}
700

701
async function getRosettaBlockTransactionsFromDataStore(opts: {
702
  blockHash: string;
703
  indexBlockHash: string;
704
  db: PgStore;
705
  chainId: ChainID;
706
}): Promise<FoundOrNot<RosettaTransaction[]>> {
707
  return await opts.db.sqlTransaction(async sql => {
121✔
708
    const blockQuery = await opts.db.getBlock({ hash: opts.blockHash });
121✔
709
    if (!blockQuery.found) {
121!
710
      return { found: false };
×
711
    }
712

713
    const txsQuery = await opts.db.getBlockTxsRows(opts.blockHash);
121✔
714
    const minerRewards = await opts.db.getMinersRewardsAtHeight({
121✔
715
      blockHeight: blockQuery.result.block_height,
716
    });
717

718
    if (!txsQuery.found) {
121!
719
      return { found: false };
×
720
    }
721

722
    const unlockingEvents = await opts.db.getUnlockedAddressesAtBlock(blockQuery.result);
121✔
723

724
    const transactions: RosettaTransaction[] = [];
121✔
725

726
    for (const tx of txsQuery.result) {
121✔
727
      const rosettaTx = await parseRosettaTxDetail({
192✔
728
        block_height: blockQuery.result.block_height,
729
        indexBlockHash: opts.indexBlockHash,
730
        tx,
731
        db: opts.db,
732
        minerRewards,
733
        unlockingEvents,
734
        chainId: opts.chainId,
735
      });
736
      transactions.push(rosettaTx);
192✔
737
    }
738

739
    return { found: true, result: transactions };
121✔
740
  });
741
}
742

743
export async function getRosettaTransactionFromDataStore(
58✔
744
  txId: string,
745
  db: PgStore,
746
  chainId: ChainID
747
): Promise<FoundOrNot<RosettaTransaction>> {
748
  return await db.sqlTransaction(async sql => {
14✔
749
    const txQuery = await db.getTx({ txId, includeUnanchored: false });
14✔
750
    if (!txQuery.found) {
14!
751
      return { found: false };
×
752
    }
753

754
    const blockQuery = await db.getBlock({ hash: txQuery.result.block_hash });
14✔
755
    if (!blockQuery.found) {
14!
756
      throw new Error(
×
757
        `Could not find block for tx: ${txId}, block_hash: ${txQuery.result.block_hash}, index_block_hash: ${txQuery.result.index_block_hash}`
758
      );
759
    }
760

761
    const rosettaTx = await getRosettaBlockTxFromDataStore({
14✔
762
      tx: txQuery.result,
763
      block: blockQuery.result,
764
      db,
765
      chainId,
766
    });
767

768
    if (!rosettaTx.found) {
13!
769
      throw new Error(
×
770
        `Rosetta block missing operations for tx: ${txId}, block_hash: ${txQuery.result.block_hash}, index_block_hash: ${txQuery.result.index_block_hash}`
771
      );
772
    }
773

774
    return rosettaTx;
13✔
775
  });
776
}
777

778
interface GetTxArgs {
779
  txId: string;
780
  includeUnanchored: boolean;
781
}
782

783
interface GetTxFromDbTxArgs extends GetTxArgs {
784
  dbTx: DbTx;
785
}
786

787
interface GetTxsWithEventsArgs extends GetTxsArgs {
788
  eventLimit: number;
789
  eventOffset: number;
790
}
791

792
interface GetTxsArgs {
793
  txIds: string[];
794
  includeUnanchored: boolean;
795
}
796

797
interface GetTxWithEventsArgs extends GetTxArgs {
798
  eventLimit: number;
799
  eventOffset: number;
800
}
801

802
function parseDbBaseTx(dbTx: DbTx | DbMempoolTx): BaseTransaction {
803
  const decodedPostConditions = decodePostConditions(dbTx.post_conditions);
269✔
804
  const normalizedPostConditions = decodedPostConditions.post_conditions.map(pc =>
269✔
805
    serializePostCondition(pc)
9✔
806
  );
807
  const tx: BaseTransaction = {
269✔
808
    tx_id: dbTx.tx_id,
809
    nonce: dbTx.nonce,
810
    sponsor_nonce: dbTx.sponsor_nonce,
811
    fee_rate: dbTx.fee_rate.toString(10),
812
    sender_address: dbTx.sender_address,
813
    sponsored: dbTx.sponsored,
814
    sponsor_address: dbTx.sponsor_address,
815
    post_condition_mode: serializePostConditionMode(decodedPostConditions.post_condition_mode),
816
    post_conditions: normalizedPostConditions,
817
    anchor_mode: getTxAnchorModeString(dbTx.anchor_mode),
818
  };
819
  return tx;
269✔
820
}
821

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

923
export function parseContractCallMetadata(tx: BaseTx): ContractCallTransactionMetadata {
58✔
924
  const contractId = unwrapOptional(
35✔
925
    tx.contract_call_contract_id,
926
    () => 'Unexpected nullish contract_call_contract_id'
×
927
  );
928
  const functionName = unwrapOptional(
35✔
929
    tx.contract_call_function_name,
930
    () => 'Unexpected nullish contract_call_function_name'
×
931
  );
932
  let functionAbi: ClarityAbiFunction | undefined;
933
  const abi = tx.abi;
35✔
934
  if (abi) {
35✔
935
    const contractAbi: ClarityAbi = JSON.parse(abi);
32✔
936
    functionAbi = contractAbi.functions.find(fn => fn.name === functionName);
108✔
937
    if (!functionAbi) {
32!
938
      throw new Error(`Could not find function name "${functionName}" in ABI for ${contractId}`);
×
939
    }
940
  }
941

942
  const functionArgs = tx.contract_call_function_args
35✔
943
    ? decodeClarityValueList(tx.contract_call_function_args).map((c, fnArgIndex) => {
944
        const functionArgAbi = functionAbi
62✔
945
          ? functionAbi.args[fnArgIndex++]
946
          : { name: '', type: undefined };
947
        return {
62✔
948
          hex: c.hex,
949
          repr: c.repr,
950
          name: functionArgAbi.name,
951
          type: functionArgAbi.type
62✔
952
            ? getTypeString(functionArgAbi.type)
953
            : decodeClarityValueToTypeName(c.hex),
954
        };
955
      })
956
    : undefined;
957

958
  const metadata: ContractCallTransactionMetadata = {
35✔
959
    tx_type: 'contract_call',
960
    contract_call: {
961
      contract_id: contractId,
962
      function_name: functionName,
963
      function_signature: functionAbi ? abiFunctionToString(functionAbi) : '',
35✔
964
      function_args: functionArgs,
965
    },
966
  };
967
  return metadata;
35✔
968
}
969

970
function parseDbAbstractTx(dbTx: DbTx, baseTx: BaseTransaction): AbstractTransaction {
971
  const abstractTx: AbstractTransaction = {
208✔
972
    ...baseTx,
973
    is_unanchored: !dbTx.block_hash,
974
    block_hash: dbTx.block_hash,
975
    parent_block_hash: dbTx.parent_block_hash,
976
    block_height: dbTx.block_height,
977
    burn_block_time: dbTx.burn_block_time,
978
    burn_block_time_iso: dbTx.burn_block_time > 0 ? unixEpochToIso(dbTx.burn_block_time) : '',
208✔
979
    parent_burn_block_time: dbTx.parent_burn_block_time,
980
    parent_burn_block_time_iso:
981
      dbTx.parent_burn_block_time > 0 ? unixEpochToIso(dbTx.parent_burn_block_time) : '',
208✔
982
    canonical: dbTx.canonical,
983
    tx_index: dbTx.tx_index,
984
    tx_status: getTxStatusString(dbTx.status) as TransactionStatus,
985
    tx_result: {
986
      hex: dbTx.raw_result,
987
      repr: decodeClarityValueToRepr(dbTx.raw_result),
988
    },
989
    microblock_hash: dbTx.microblock_hash,
990
    microblock_sequence: dbTx.microblock_sequence,
991
    microblock_canonical: dbTx.microblock_canonical,
992
    event_count: dbTx.event_count,
993
    events: [],
994
    execution_cost_read_count: dbTx.execution_cost_read_count,
995
    execution_cost_read_length: dbTx.execution_cost_read_length,
996
    execution_cost_runtime: dbTx.execution_cost_runtime,
997
    execution_cost_write_count: dbTx.execution_cost_write_count,
998
    execution_cost_write_length: dbTx.execution_cost_write_length,
999
  };
1000
  return abstractTx;
208✔
1001
}
1002

1003
function parseDbAbstractMempoolTx(
1004
  dbMempoolTx: DbMempoolTx,
1005
  baseTx: BaseTransaction
1006
): AbstractMempoolTransaction {
1007
  const abstractMempoolTx: AbstractMempoolTransaction = {
61✔
1008
    ...baseTx,
1009
    tx_status: getTxStatusString(dbMempoolTx.status) as MempoolTransactionStatus,
1010
    receipt_time: dbMempoolTx.receipt_time,
1011
    receipt_time_iso: unixEpochToIso(dbMempoolTx.receipt_time),
1012
  };
1013
  return abstractMempoolTx;
61✔
1014
}
1015

1016
export function parseDbTx(dbTx: DbTx): Transaction {
58✔
1017
  const baseTx = parseDbBaseTx(dbTx);
208✔
1018
  const abstractTx = parseDbAbstractTx(dbTx, baseTx);
208✔
1019
  const txMetadata = parseDbTxTypeMetadata(dbTx);
208✔
1020
  const result: Transaction = {
208✔
1021
    ...abstractTx,
1022
    ...txMetadata,
1023
  };
1024
  return result;
208✔
1025
}
1026

1027
export function parseDbMempoolTx(dbMempoolTx: DbMempoolTx): MempoolTransaction {
58✔
1028
  const baseTx = parseDbBaseTx(dbMempoolTx);
61✔
1029
  const abstractTx = parseDbAbstractMempoolTx(dbMempoolTx, baseTx);
61✔
1030
  const txMetadata = parseDbTxTypeMetadata(dbMempoolTx);
61✔
1031
  const result: MempoolTransaction = {
61✔
1032
    ...abstractTx,
1033
    ...txMetadata,
1034
  };
1035
  return result;
61✔
1036
}
1037

1038
export async function getMempoolTxsFromDataStore(
58✔
1039
  db: PgStore,
1040
  args: GetTxsArgs
1041
): Promise<MempoolTransaction[]> {
1042
  const mempoolTxsQuery = await db.getMempoolTxs({
33✔
1043
    txIds: args.txIds,
1044
    includePruned: true,
1045
    includeUnanchored: args.includeUnanchored,
1046
  });
1047
  if (mempoolTxsQuery.length === 0) {
32✔
1048
    return [];
8✔
1049
  }
1050

1051
  const parsedMempoolTxs = mempoolTxsQuery.map(tx => parseDbMempoolTx(tx));
24✔
1052

1053
  return parsedMempoolTxs;
24✔
1054
}
1055

1056
async function getTxsFromDataStore(
1057
  db: PgStore,
1058
  args: GetTxsArgs | GetTxsWithEventsArgs
1059
): Promise<Transaction[]> {
1060
  return await db.sqlTransaction(async sql => {
43✔
1061
    // fetching all requested transactions from db
1062
    const txQuery = await db.getTxListDetails({
43✔
1063
      txIds: args.txIds,
1064
      includeUnanchored: args.includeUnanchored,
1065
    });
1066

1067
    // returning empty array if no transaction was found
1068
    if (txQuery.length === 0) {
43✔
1069
      return [];
16✔
1070
    }
1071

1072
    // parsing txQuery
1073
    const parsedTxs = txQuery.map(tx => parseDbTx(tx));
29✔
1074

1075
    // incase transaction events are requested
1076
    if ('eventLimit' in args) {
27!
1077
      const txIdsAndIndexHash = txQuery.map(tx => {
27✔
1078
        return {
29✔
1079
          txId: tx.tx_id,
1080
          indexBlockHash: tx.index_block_hash,
1081
        };
1082
      });
1083
      const txListEvents = await db.getTxListEvents({
27✔
1084
        txs: txIdsAndIndexHash,
1085
        limit: args.eventLimit,
1086
        offset: args.eventOffset,
1087
      });
1088
      // this will insert all events in a single parsedTransaction. Only specific ones are to be added.
1089
      const txsWithEvents: Transaction[] = parsedTxs.map(ptx => {
27✔
1090
        return {
29✔
1091
          ...ptx,
1092
          events: txListEvents.results
1093
            .filter(event => event.tx_id === ptx.tx_id)
28✔
1094
            .map(event => parseDbEvent(event)),
28✔
1095
        };
1096
      });
1097
      return txsWithEvents;
27✔
1098
    } else {
1099
      return parsedTxs;
×
1100
    }
1101
  });
1102
}
1103

1104
export async function getTxFromDataStore(
58✔
1105
  db: PgStore,
1106
  args: GetTxArgs | GetTxWithEventsArgs | GetTxFromDbTxArgs
1107
): Promise<FoundOrNot<Transaction>> {
1108
  return await db.sqlTransaction(async sql => {
29✔
1109
    let dbTx: DbTx;
1110
    if ('dbTx' in args) {
29✔
1111
      dbTx = args.dbTx;
10✔
1112
    } else {
1113
      const txQuery = await db.getTx({
19✔
1114
        txId: args.txId,
1115
        includeUnanchored: args.includeUnanchored,
1116
      });
1117
      if (!txQuery.found) {
19✔
1118
        return { found: false };
2✔
1119
      }
1120
      dbTx = txQuery.result;
17✔
1121
    }
1122

1123
    const parsedTx = parseDbTx(dbTx);
27✔
1124

1125
    // If tx events are requested
1126
    if ('eventLimit' in args) {
27!
1127
      const eventsQuery = await db.getTxEvents({
×
1128
        txId: args.txId,
1129
        indexBlockHash: dbTx.index_block_hash,
1130
        limit: args.eventLimit,
1131
        offset: args.eventOffset,
1132
      });
1133
      const txWithEvents: Transaction = {
×
1134
        ...parsedTx,
1135
        events: eventsQuery.results.map(event => parseDbEvent(event)),
×
1136
      };
1137
      return { found: true, result: txWithEvents };
×
1138
    } else {
1139
      return {
27✔
1140
        found: true,
1141
        result: parsedTx,
1142
      };
1143
    }
1144
  });
1145
}
1146

1147
export async function searchTxs(
58✔
1148
  db: PgStore,
1149
  args: GetTxsArgs | GetTxsWithEventsArgs
1150
): Promise<TransactionList> {
1151
  return await db.sqlTransaction(async sql => {
2✔
1152
    const minedTxs = await getTxsFromDataStore(db, args);
2✔
1153

1154
    const foundTransactions: TransactionFound[] = [];
2✔
1155
    const mempoolTxs: string[] = [];
2✔
1156
    minedTxs.forEach(tx => {
2✔
1157
      // filtering out mined transactions in canonical chain
1158
      if (tx.canonical && tx.microblock_canonical) {
4✔
1159
        foundTransactions.push({ found: true, result: tx });
4✔
1160
      }
1161
      // filtering out non canonical transactions to look into mempool table
1162
      if (!tx.canonical && !tx.microblock_canonical) {
4!
1163
        mempoolTxs.push(tx.tx_id);
×
1164
      }
1165
    });
1166

1167
    // filtering out tx_ids that were not mined / found
1168
    const notMinedTransactions: string[] = args.txIds.filter(
2✔
1169
      txId => !minedTxs.find(minedTx => txId === minedTx.tx_id)
13✔
1170
    );
1171

1172
    // finding transactions that are not mined and are not canonical in mempool
1173
    mempoolTxs.push(...notMinedTransactions);
2✔
1174
    const mempoolTxsQuery = await getMempoolTxsFromDataStore(db, {
2✔
1175
      txIds: mempoolTxs,
1176
      includeUnanchored: args.includeUnanchored,
1177
    });
1178

1179
    // merging found mempool transaction in found transactions object
1180
    foundTransactions.push(
2✔
1181
      ...mempoolTxsQuery.map((mtx: Transaction | MempoolTransaction) => {
1182
        return { found: true, result: mtx } as TransactionFound;
1✔
1183
      })
1184
    );
1185

1186
    // filtering out transactions that were not found anywhere
1187
    const notFoundTransactions: TransactionNotFound[] = args.txIds
2✔
1188
      .filter(txId => foundTransactions.findIndex(ftx => ftx.result?.tx_id === txId) < 0)
15✔
1189
      .map(txId => {
1190
        return { found: false, result: { tx_id: txId } };
1✔
1191
      });
1192

1193
    // generating response
1194
    const resp = [...foundTransactions, ...notFoundTransactions].reduce(
2✔
1195
      (map: TransactionList, obj) => {
1196
        if (obj.result) {
6✔
1197
          map[obj.result.tx_id] = obj;
6✔
1198
        }
1199
        return map;
6✔
1200
      },
1201
      {}
1202
    );
1203
    return resp;
2✔
1204
  });
1205
}
1206

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

1240
export async function searchHashWithMetadata(
58✔
1241
  hash: string,
1242
  db: PgStore
1243
): Promise<FoundOrNot<DbSearchResultWithMetadata>> {
1244
  return await db.sqlTransaction(async sql => {
8✔
1245
    // checking for tx
1246
    const txQuery = await db.getTxListDetails({ txIds: [hash], includeUnanchored: true });
8✔
1247
    if (txQuery.length > 0) {
8✔
1248
      // tx found
1249
      const tx = txQuery[0];
2✔
1250
      return {
2✔
1251
        found: true,
1252
        result: {
1253
          entity_type: 'tx_id',
1254
          entity_id: tx.tx_id,
1255
          entity_data: tx,
1256
        },
1257
      };
1258
    }
1259
    // checking for mempool tx
1260
    const mempoolTxQuery = await db.getMempoolTxs({
6✔
1261
      txIds: [hash],
1262
      includeUnanchored: true,
1263
      includePruned: true,
1264
    });
1265
    if (mempoolTxQuery.length > 0) {
6✔
1266
      // mempool tx found
1267
      const mempoolTx = mempoolTxQuery[0];
2✔
1268
      return {
2✔
1269
        found: true,
1270
        result: {
1271
          entity_type: 'mempool_tx_id',
1272
          entity_id: mempoolTx.tx_id,
1273
          entity_data: mempoolTx,
1274
        },
1275
      };
1276
    }
1277
    // checking for block
1278
    const blockQuery = await db.getBlockWithMetadata({ hash }, { txs: true, microblocks: true });
4✔
1279
    if (blockQuery.found) {
4✔
1280
      // block found
1281
      const result = parseDbBlock(
3✔
1282
        blockQuery.result.block,
1283
        blockQuery.result.txs.map(tx => tx.tx_id),
3✔
1284
        blockQuery.result.microblocks.accepted.map(mb => mb.microblock_hash),
×
1285
        blockQuery.result.microblocks.streamed.map(mb => mb.microblock_hash),
×
1286
        blockQuery.result.microblock_tx_count
1287
      );
1288
      return {
3✔
1289
        found: true,
1290
        result: {
1291
          entity_type: 'block_hash',
1292
          entity_id: result.hash,
1293
          entity_data: result,
1294
        },
1295
      };
1296
    }
1297
    // found nothing
1298
    return { found: false };
1✔
1299
  });
1300
}
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