Skip to content

Commit a82583c

Browse files
committed
Merge branch 'master' into develop
2 parents 37939a0 + 8438fc0 commit a82583c

File tree

7 files changed

+338
-8
lines changed

7 files changed

+338
-8
lines changed

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
1+
## [8.1.2](https://github.com/hirosystems/stacks-blockchain-api/compare/v8.1.1...v8.1.2) (2024-10-21)
2+
3+
4+
### Bug Fixes
5+
6+
* **rosetta:** support tenure change transactions ([#2128](https://github.com/hirosystems/stacks-blockchain-api/issues/2128)) ([bfbf65c](https://github.com/hirosystems/stacks-blockchain-api/commit/bfbf65c6f3a7baf869e3d5124e53b7c5861c5afb))
7+
* **rosetta:** use Nakamoto block timestamps for epoch3/Nakamoto block responses ([#2132](https://github.com/hirosystems/stacks-blockchain-api/issues/2132)) ([bd13962](https://github.com/hirosystems/stacks-blockchain-api/commit/bd13962dacc4023a247da40e06c6861cd1e8f2bf))
8+
9+
## [8.1.1](https://github.com/hirosystems/stacks-blockchain-api/compare/v8.1.0...v8.1.1) (2024-10-18)
10+
11+
12+
### Bug Fixes
13+
14+
* identify mempool transactions separately when calculating principal etag ([#2126](https://github.com/hirosystems/stacks-blockchain-api/issues/2126)) ([b9dee2a](https://github.com/hirosystems/stacks-blockchain-api/commit/b9dee2a85cb6e733cb0ab2f4d1c7c12cd303bec4))
15+
116
## [8.1.0](https://github.com/hirosystems/stacks-blockchain-api/compare/v8.0.4...v8.1.0) (2024-10-16)
217

318

src/api/controllers/db-controller.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ export function getTxTypeString(typeId: DbTxTypeId): Transaction['tx_type'] {
124124
return 'poison_microblock';
125125
case DbTxTypeId.Coinbase:
126126
case DbTxTypeId.CoinbaseToAltRecipient:
127+
case DbTxTypeId.NakamotoCoinbase:
127128
return 'coinbase';
128129
case DbTxTypeId.TenureChange:
129130
return 'tenure_change';
@@ -145,7 +146,7 @@ function getTxAnchorModeString(anchorMode: number): TransactionAnchorModeType {
145146
}
146147
}
147148

148-
function getTxTenureChangeCauseString(cause: number) {
149+
export function getTxTenureChangeCauseString(cause: number) {
149150
switch (cause) {
150151
case 0:
151152
return 'block_found';
@@ -540,10 +541,17 @@ export async function getRosettaBlockFromDataStore(
540541
}
541542
}
542543

544+
// In epoch2.x, only the burn_block_time is consensus-level. Starting in epoch3, Stacks blocks include a consensus-level timestamp.
545+
// Use `signer_bitvec` field to determine if the block is from epoch3.
546+
let timestamp = dbBlock.burn_block_time * 1000;
547+
if (dbBlock.signer_bitvec) {
548+
timestamp = dbBlock.block_time * 1000;
549+
}
550+
543551
const apiBlock: RosettaBlock = {
544552
block_identifier: { index: dbBlock.block_height, hash: dbBlock.block_hash },
545553
parent_block_identifier,
546-
timestamp: dbBlock.burn_block_time * 1000,
554+
timestamp: timestamp,
547555
transactions: blockTxs.found ? blockTxs.result : [],
548556
metadata: {
549557
burn_block_height: dbBlock.burn_block_height,

src/api/rosetta-constants.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export enum RosettaOperationType {
5252
StackStx = 'stack_stx',
5353
DelegateStx = 'delegate_stx',
5454
RevokeDelegateStx = 'revoke_delegate_stx',
55-
// todo: add new pox-2 methods
55+
TenureChange = 'tenure_change',
5656
}
5757

5858
type RosettaOperationTypeUnion = `${RosettaOperationType}`;
@@ -77,6 +77,7 @@ export const RosettaOperationTypes = arrayOfAllOpTypes([
7777
'stack_stx',
7878
'delegate_stx',
7979
'revoke_delegate_stx',
80+
'tenure_change',
8081
]) as RosettaOperationType[];
8182

8283
export const RosettaOperationStatuses = [

src/datastore/pg-store.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4444,15 +4444,15 @@ export class PgStore extends BasePgStore {
44444444
const result = await this.sql<{ tx_id: string }[]>`
44454445
WITH activity AS (
44464446
(
4447-
SELECT tx_id
4447+
SELECT '0x' || encode(tx_id, 'hex') AS tx_id
44484448
FROM principal_stx_txs
44494449
WHERE principal = ${principal} AND canonical = true AND microblock_canonical = true
44504450
ORDER BY block_height DESC, microblock_sequence DESC, tx_index DESC
44514451
LIMIT 1
44524452
)
44534453
UNION
44544454
(
4455-
SELECT tx_id
4455+
SELECT '0x' || encode(tx_id, 'hex') AS tx_id
44564456
FROM ft_events
44574457
WHERE (sender = ${principal} OR recipient = ${principal})
44584458
AND canonical = true
@@ -4462,7 +4462,7 @@ export class PgStore extends BasePgStore {
44624462
)
44634463
UNION
44644464
(
4465-
SELECT tx_id
4465+
SELECT '0x' || encode(tx_id, 'hex') AS tx_id
44664466
FROM nft_events
44674467
WHERE (sender = ${principal} OR recipient = ${principal})
44684468
AND canonical = true
@@ -4474,7 +4474,7 @@ export class PgStore extends BasePgStore {
44744474
includeMempool
44754475
? this.sql`UNION
44764476
(
4477-
SELECT tx_id
4477+
SELECT 'mempool-' || '0x' || encode(tx_id, 'hex') AS tx_id
44784478
FROM mempool_txs
44794479
WHERE pruned = false AND
44804480
(sender_address = ${principal}
@@ -4486,7 +4486,7 @@ export class PgStore extends BasePgStore {
44864486
: this.sql``
44874487
}
44884488
)
4489-
SELECT DISTINCT tx_id FROM activity WHERE tx_id IS NOT NULL
4489+
SELECT tx_id FROM activity WHERE tx_id IS NOT NULL
44904490
`;
44914491
return result.map(r => r.tx_id);
44924492
}

src/rosetta/rosetta-helpers.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import * as btc from 'bitcoinjs-lib';
2525
import {
2626
getTxFromDataStore,
2727
getTxStatus,
28+
getTxTenureChangeCauseString,
2829
getTxTypeString,
2930
parseContractCallMetadata,
3031
} from '../api/controllers/db-controller';
@@ -170,6 +171,9 @@ async function getOperationsInternal(
170171
case RosettaOperationType.PoisonMicroblock:
171172
operations.push(makePoisonMicroblockOperation(tx, 0));
172173
break;
174+
case RosettaOperationType.TenureChange:
175+
operations.push(makeTenureChangeOperation(tx, operations.length));
176+
break;
173177
default:
174178
throw new Error(`Unexpected tx type: ${JSON.stringify(txType)}`);
175179
}
@@ -727,6 +731,23 @@ function makePoisonMicroblockOperation(tx: BaseTx, index: number): RosettaOperat
727731
return sender;
728732
}
729733

734+
function makeTenureChangeOperation(tx: BaseTx, index: number): RosettaOperation {
735+
return {
736+
operation_identifier: { index: index },
737+
type: RosettaOperationType.TenureChange,
738+
status: getTxStatus(tx.status),
739+
metadata: {
740+
tenure_consensus_hash: tx.tenure_change_tenure_consensus_hash as string,
741+
prev_tenure_consensus_hash: tx.tenure_change_prev_tenure_consensus_hash as string,
742+
burn_view_consensus_hash: tx.tenure_change_burn_view_consensus_hash as string,
743+
previous_tenure_end: tx.tenure_change_previous_tenure_end as string,
744+
previous_tenure_blocks: tx.tenure_change_previous_tenure_blocks as number,
745+
cause: getTxTenureChangeCauseString(tx.tenure_change_cause as number),
746+
pubkey_hash: tx.tenure_change_pubkey_hash as string,
747+
},
748+
};
749+
}
750+
730751
export function publicKeyToBitcoinAddress(publicKey: string, network: string): string | undefined {
731752
const publicKeyBuffer = Buffer.from(publicKey, 'hex');
732753

tests/api/cache-control.test.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -819,6 +819,76 @@ describe('cache-control tests', () => {
819819
expect(request8.text).toBe('');
820820
});
821821

822+
test('principal mempool cache on received tx balance confirmation', async () => {
823+
const address = 'SP3FXEKSA6D4BW3TFP2BWTSREV6FY863Y90YY7D8G';
824+
const url = `/extended/v1/address/${address}/balances`;
825+
await db.update(
826+
new TestBlockBuilder({
827+
block_height: 1,
828+
index_block_hash: '0x01',
829+
parent_index_block_hash: '0x00',
830+
}).build()
831+
);
832+
833+
// ETag zero.
834+
const request1 = await supertest(api.server).get(url);
835+
expect(request1.status).toBe(200);
836+
expect(request1.type).toBe('application/json');
837+
const etag0 = request1.headers['etag'];
838+
839+
// Add receiving STX tx.
840+
await db.updateMempoolTxs({
841+
mempoolTxs: [
842+
testMempoolTx({
843+
tx_id: '0x0001',
844+
token_transfer_amount: 2000n,
845+
token_transfer_recipient_address: address,
846+
}),
847+
],
848+
});
849+
850+
// Valid ETag.
851+
const request2 = await supertest(api.server).get(url);
852+
expect(request2.status).toBe(200);
853+
expect(request2.type).toBe('application/json');
854+
expect(request2.headers['etag']).toBeTruthy();
855+
const json2 = JSON.parse(request2.text);
856+
expect(json2.stx.balance).toBe('0');
857+
expect(json2.stx.estimated_balance).toBe('2000');
858+
const etag1 = request2.headers['etag'];
859+
expect(etag1).not.toEqual(etag0);
860+
861+
// Cache works with valid ETag.
862+
const request3 = await supertest(api.server).get(url).set('If-None-Match', etag1);
863+
expect(request3.status).toBe(304);
864+
expect(request3.text).toBe('');
865+
866+
// Confirm mempool tx.
867+
await db.update(
868+
new TestBlockBuilder({
869+
block_height: 2,
870+
index_block_hash: '0x02',
871+
parent_index_block_hash: '0x01',
872+
})
873+
.addTx({
874+
tx_id: '0x0001',
875+
token_transfer_amount: 2000n,
876+
token_transfer_recipient_address: address,
877+
})
878+
.addTxStxEvent({ amount: 2000n, recipient: address })
879+
.build()
880+
);
881+
882+
// Cache is now a miss.
883+
const request4 = await supertest(api.server).get(url).set('If-None-Match', etag1);
884+
expect(request4.status).toBe(200);
885+
expect(request4.type).toBe('application/json');
886+
expect(request4.headers['etag']).not.toEqual(etag1);
887+
const json4 = JSON.parse(request4.text);
888+
expect(json4.stx.balance).toBe('2000');
889+
expect(json4.stx.estimated_balance).toBe('2000');
890+
});
891+
822892
test('block cache control', async () => {
823893
await db.update(
824894
new TestBlockBuilder({

0 commit comments

Comments
 (0)