Skip to content

Commit

Permalink
Merge branch 'master' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
zone117x committed Oct 23, 2024
2 parents 37939a0 + 8438fc0 commit a82583c
Show file tree
Hide file tree
Showing 7 changed files with 338 additions and 8 deletions.
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
## [8.1.2](https://github.com/hirosystems/stacks-blockchain-api/compare/v8.1.1...v8.1.2) (2024-10-21)


### Bug Fixes

* **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))
* **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.1.1](https://github.com/hirosystems/stacks-blockchain-api/compare/v8.1.0...v8.1.1) (2024-10-18)


### Bug Fixes

* 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))

## [8.1.0](https://github.com/hirosystems/stacks-blockchain-api/compare/v8.0.4...v8.1.0) (2024-10-16)


Expand Down
12 changes: 10 additions & 2 deletions src/api/controllers/db-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ export function getTxTypeString(typeId: DbTxTypeId): Transaction['tx_type'] {
return 'poison_microblock';
case DbTxTypeId.Coinbase:
case DbTxTypeId.CoinbaseToAltRecipient:
case DbTxTypeId.NakamotoCoinbase:
return 'coinbase';
case DbTxTypeId.TenureChange:
return 'tenure_change';
Expand All @@ -145,7 +146,7 @@ function getTxAnchorModeString(anchorMode: number): TransactionAnchorModeType {
}
}

function getTxTenureChangeCauseString(cause: number) {
export function getTxTenureChangeCauseString(cause: number) {
switch (cause) {
case 0:
return 'block_found';
Expand Down Expand Up @@ -540,10 +541,17 @@ export async function getRosettaBlockFromDataStore(
}
}

// In epoch2.x, only the burn_block_time is consensus-level. Starting in epoch3, Stacks blocks include a consensus-level timestamp.
// Use `signer_bitvec` field to determine if the block is from epoch3.
let timestamp = dbBlock.burn_block_time * 1000;
if (dbBlock.signer_bitvec) {
timestamp = dbBlock.block_time * 1000;
}

const apiBlock: RosettaBlock = {
block_identifier: { index: dbBlock.block_height, hash: dbBlock.block_hash },
parent_block_identifier,
timestamp: dbBlock.burn_block_time * 1000,
timestamp: timestamp,
transactions: blockTxs.found ? blockTxs.result : [],
metadata: {
burn_block_height: dbBlock.burn_block_height,
Expand Down
3 changes: 2 additions & 1 deletion src/api/rosetta-constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export enum RosettaOperationType {
StackStx = 'stack_stx',
DelegateStx = 'delegate_stx',
RevokeDelegateStx = 'revoke_delegate_stx',
// todo: add new pox-2 methods
TenureChange = 'tenure_change',
}

type RosettaOperationTypeUnion = `${RosettaOperationType}`;
Expand All @@ -77,6 +77,7 @@ export const RosettaOperationTypes = arrayOfAllOpTypes([
'stack_stx',
'delegate_stx',
'revoke_delegate_stx',
'tenure_change',
]) as RosettaOperationType[];

export const RosettaOperationStatuses = [
Expand Down
10 changes: 5 additions & 5 deletions src/datastore/pg-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4444,15 +4444,15 @@ export class PgStore extends BasePgStore {
const result = await this.sql<{ tx_id: string }[]>`
WITH activity AS (
(
SELECT tx_id
SELECT '0x' || encode(tx_id, 'hex') AS tx_id
FROM principal_stx_txs
WHERE principal = ${principal} AND canonical = true AND microblock_canonical = true
ORDER BY block_height DESC, microblock_sequence DESC, tx_index DESC
LIMIT 1
)
UNION
(
SELECT tx_id
SELECT '0x' || encode(tx_id, 'hex') AS tx_id
FROM ft_events
WHERE (sender = ${principal} OR recipient = ${principal})
AND canonical = true
Expand All @@ -4462,7 +4462,7 @@ export class PgStore extends BasePgStore {
)
UNION
(
SELECT tx_id
SELECT '0x' || encode(tx_id, 'hex') AS tx_id
FROM nft_events
WHERE (sender = ${principal} OR recipient = ${principal})
AND canonical = true
Expand All @@ -4474,7 +4474,7 @@ export class PgStore extends BasePgStore {
includeMempool
? this.sql`UNION
(
SELECT tx_id
SELECT 'mempool-' || '0x' || encode(tx_id, 'hex') AS tx_id
FROM mempool_txs
WHERE pruned = false AND
(sender_address = ${principal}
Expand All @@ -4486,7 +4486,7 @@ export class PgStore extends BasePgStore {
: this.sql``
}
)
SELECT DISTINCT tx_id FROM activity WHERE tx_id IS NOT NULL
SELECT tx_id FROM activity WHERE tx_id IS NOT NULL
`;
return result.map(r => r.tx_id);
}
Expand Down
21 changes: 21 additions & 0 deletions src/rosetta/rosetta-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import * as btc from 'bitcoinjs-lib';
import {
getTxFromDataStore,
getTxStatus,
getTxTenureChangeCauseString,
getTxTypeString,
parseContractCallMetadata,
} from '../api/controllers/db-controller';
Expand Down Expand Up @@ -170,6 +171,9 @@ async function getOperationsInternal(
case RosettaOperationType.PoisonMicroblock:
operations.push(makePoisonMicroblockOperation(tx, 0));
break;
case RosettaOperationType.TenureChange:
operations.push(makeTenureChangeOperation(tx, operations.length));
break;
default:
throw new Error(`Unexpected tx type: ${JSON.stringify(txType)}`);
}
Expand Down Expand Up @@ -727,6 +731,23 @@ function makePoisonMicroblockOperation(tx: BaseTx, index: number): RosettaOperat
return sender;
}

function makeTenureChangeOperation(tx: BaseTx, index: number): RosettaOperation {
return {
operation_identifier: { index: index },
type: RosettaOperationType.TenureChange,
status: getTxStatus(tx.status),
metadata: {
tenure_consensus_hash: tx.tenure_change_tenure_consensus_hash as string,
prev_tenure_consensus_hash: tx.tenure_change_prev_tenure_consensus_hash as string,
burn_view_consensus_hash: tx.tenure_change_burn_view_consensus_hash as string,
previous_tenure_end: tx.tenure_change_previous_tenure_end as string,
previous_tenure_blocks: tx.tenure_change_previous_tenure_blocks as number,
cause: getTxTenureChangeCauseString(tx.tenure_change_cause as number),
pubkey_hash: tx.tenure_change_pubkey_hash as string,
},
};
}

export function publicKeyToBitcoinAddress(publicKey: string, network: string): string | undefined {
const publicKeyBuffer = Buffer.from(publicKey, 'hex');

Expand Down
70 changes: 70 additions & 0 deletions tests/api/cache-control.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -819,6 +819,76 @@ describe('cache-control tests', () => {
expect(request8.text).toBe('');
});

test('principal mempool cache on received tx balance confirmation', async () => {
const address = 'SP3FXEKSA6D4BW3TFP2BWTSREV6FY863Y90YY7D8G';
const url = `/extended/v1/address/${address}/balances`;
await db.update(
new TestBlockBuilder({
block_height: 1,
index_block_hash: '0x01',
parent_index_block_hash: '0x00',
}).build()
);

// ETag zero.
const request1 = await supertest(api.server).get(url);
expect(request1.status).toBe(200);
expect(request1.type).toBe('application/json');
const etag0 = request1.headers['etag'];

// Add receiving STX tx.
await db.updateMempoolTxs({
mempoolTxs: [
testMempoolTx({
tx_id: '0x0001',
token_transfer_amount: 2000n,
token_transfer_recipient_address: address,
}),
],
});

// Valid ETag.
const request2 = await supertest(api.server).get(url);
expect(request2.status).toBe(200);
expect(request2.type).toBe('application/json');
expect(request2.headers['etag']).toBeTruthy();
const json2 = JSON.parse(request2.text);
expect(json2.stx.balance).toBe('0');
expect(json2.stx.estimated_balance).toBe('2000');
const etag1 = request2.headers['etag'];
expect(etag1).not.toEqual(etag0);

// Cache works with valid ETag.
const request3 = await supertest(api.server).get(url).set('If-None-Match', etag1);
expect(request3.status).toBe(304);
expect(request3.text).toBe('');

// Confirm mempool tx.
await db.update(
new TestBlockBuilder({
block_height: 2,
index_block_hash: '0x02',
parent_index_block_hash: '0x01',
})
.addTx({
tx_id: '0x0001',
token_transfer_amount: 2000n,
token_transfer_recipient_address: address,
})
.addTxStxEvent({ amount: 2000n, recipient: address })
.build()
);

// Cache is now a miss.
const request4 = await supertest(api.server).get(url).set('If-None-Match', etag1);
expect(request4.status).toBe(200);
expect(request4.type).toBe('application/json');
expect(request4.headers['etag']).not.toEqual(etag1);
const json4 = JSON.parse(request4.text);
expect(json4.stx.balance).toBe('2000');
expect(json4.stx.estimated_balance).toBe('2000');
});

test('block cache control', async () => {
await db.update(
new TestBlockBuilder({
Expand Down
Loading

0 comments on commit a82583c

Please sign in to comment.