Skip to content

Commit cfb2ab0

Browse files
authored
Merge pull request #1979 from hirosystems/develop
Cut beta release
2 parents e8ff517 + 521d771 commit cfb2ab0

24 files changed

+141
-53
lines changed

.github/workflows/ci.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -921,7 +921,7 @@ jobs:
921921
type=ref,event=pr
922922
type=semver,pattern={{version}},value=${{ steps.semantic.outputs.new_release_version }},enable=${{ steps.semantic.outputs.new_release_version != '' }}
923923
type=semver,pattern={{major}}.{{minor}},value=${{ steps.semantic.outputs.new_release_version }},enable=${{ steps.semantic.outputs.new_release_version != '' }}
924-
type=raw,value=latest,enable={{is_default_branch}}
924+
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'master') }}
925925
926926
- name: Login to DockerHub
927927
uses: docker/login-action@v3

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
[![CI](https://github.com/hirosystems/stacks-blockchain-api/actions/workflows/ci.yml/badge.svg)](https://github.com/hirosystems/stacks-blockchain-api/actions/workflows/ci.yml)
44
[![GitHub Releases](https://img.shields.io/github/v/release/hirosystems/stacks-blockchain-api?display_name=release)](https://github.com/hirosystems/stacks-blockchain-api/releases/latest)
5-
[![Docker Pulls](https://img.shields.io/docker/pulls/blockstack/stacks-blockchain-api-standalone)](https://hub.docker.com/r/hirosystems/stacks-blockchain-api-standalone/)
5+
[![Docker Pulls](https://img.shields.io/docker/pulls/blockstack/stacks-blockchain-api)](https://hub.docker.com/r/hirosystems/stacks-blockchain-api/)
66
[![NPM client package](https://img.shields.io/badge/npm-%40stacks%2Fblockchain--api--client-blue)](https://www.npmjs.org/package/@stacks/blockchain-api-client)
77

88
## Quick start

client/README.md

+4-6
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,12 @@ await sub.unsubscribe();
4545
### Socket.io
4646

4747
```js
48-
import { io } from "socket.io-client";
49-
import * as stacks from '@stacks/blockchain-api-client';
48+
import { StacksApiSocketClient } from '@stacks/blockchain-api-client';
5049

51-
// for testnet, replace with https://api.testnet.hiro.so/
52-
const socketUrl = "https://api.mainnet.hiro.so/";
50+
// for testnet, replace with https://api.testnet.hiro.so
51+
const socketUrl = "https://api.mainnet.hiro.so";
5352

54-
const socket = io(socketUrl);
55-
const sc = new stacks.StacksApiSocketClient(socket);
53+
const sc = new StacksApiSocketClient({ url: socketUrl });
5654

5755
sc.subscribeAddressTransactions('ST3GQB6WGCWKDNFNPSQRV8DY93JN06XPZ2ZE9EVMA', (address, tx) => {
5856
console.log('address:', address);

client/src/socket-io/index.ts

+3
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ function createStacksApiSocket(opts?: StacksApiSocketConnectionOptions) {
4646
subscriptions: Array.from(new Set(opts?.subscriptions)).join(','),
4747
},
4848
};
49+
if (!socketOpts.transports) {
50+
socketOpts.transports = ['websocket'];
51+
}
4952
const socket: StacksApiSocket = io(getWsUrl(opts?.url ?? BASE_PATH).href, socketOpts);
5053
return socket;
5154
}

docker/docker-compose.dev.stacks-blockchain.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
version: '3.7'
22
services:
33
stacks-blockchain:
4-
image: 'hirosystems/stacks-api-e2e:stacks3.0-4d11d85'
4+
image: 'hirosystems/stacks-api-e2e:stacks3.0-0a2c0e2'
55
restart: on-failure
66
environment:
77
STACKS_EVENT_OBSERVER: host.docker.internal:3700

docker/docker-compose.dev.stacks-krypton.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
version: '3.7'
22
services:
33
stacks-blockchain:
4-
image: 'hirosystems/stacks-api-e2e:stacks3.0-4d11d85'
4+
image: 'hirosystems/stacks-api-e2e:stacks3.0-0a2c0e2'
55
ports:
66
- '18443:18443' # bitcoin regtest JSON-RPC interface
77
- '18444:18444' # bitcoin regtest p2p

docs/api/rosetta/rosetta-network-status-response.schema.json

+6-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
"current_block_identifier",
77
"current_block_timestamp",
88
"genesis_block_identifier",
9-
"peers"
9+
"peers",
10+
"current_burn_block_height"
1011
],
1112
"properties": {
1213
"current_block_identifier": {
@@ -31,6 +32,10 @@
3132
"items": {
3233
"$ref": "./../../entities/rosetta/rosetta-network-peers.schema.json"
3334
}
35+
},
36+
"current_burn_block_height": {
37+
"type": "integer",
38+
"description": "The latest burn block height"
3439
}
3540
},
3641
"additionalProperties": false

docs/api/stacking/get-pox-cycle-signers.example.json

+3
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,23 @@
55
"results": [
66
{
77
"signing_key": "0x038e3c4529395611be9abf6fa3b6987e81d402385e3d605a073f42f407565a4a3d",
8+
"signer_address": "STRYYQQ9M8KAF4NS7WNZQYY59X93XEKR31JP64CP",
89
"stacked_amount": "686251350000000000",
910
"stacked_amount_percent": 50,
1011
"weight": 5,
1112
"weight_percent": 55.55555555555556
1213
},
1314
{
1415
"signing_key": "0x029874497a7952483aa23890e9d0898696f33864d3df90939930a1f45421fe3b09",
16+
"signer_address": "STF9B75ADQAVXQHNEQ6KGHXTG7JP305J2GRWF3A2",
1517
"stacked_amount": "457500900000000000",
1618
"stacked_amount_percent": 33.333333333333336,
1719
"weight": 3,
1820
"weight_percent": 33.33333333333333
1921
},
2022
{
2123
"signing_key": "0x02dcde79b38787b72d8e5e0af81cffa802f0a3c8452d6b46e08859165f49a72736",
24+
"signer_address": "ST18MDW2PDTBSCR1ACXYRJP2JX70FWNM6YY2VX4SS",
2225
"stacked_amount": "228750450000000000",
2326
"stacked_amount_percent": 16.666666666666668,
2427
"weight": 1,

docs/entities/rosetta/rosetta-block.schema.json

+4-8
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"type": "object",
33
"title": "RosettaBlock",
44
"description": "Blocks contain an array of Transactions that occurred at a particular BlockIdentifier. A hard requirement for blocks returned by Rosetta implementations is that they MUST be inalterable: once a client has requested and received a block identified by a specific BlockIndentifier, all future calls for that same BlockIdentifier must return the same block contents.",
5-
"required": ["block_identifier", "parent_block_identifier", "timestamp", "transactions"],
5+
"required": ["block_identifier", "parent_block_identifier", "timestamp", "transactions", "metadata"],
66
"additionalProperties": false,
77
"properties": {
88
"block_identifier": {
@@ -25,14 +25,10 @@
2525
"metadata": {
2626
"type": "object",
2727
"description": "meta data",
28-
"required": ["transactions_root", "difficulty"],
28+
"required": ["burn_block_height"],
2929
"properties": {
30-
"transactions_root": {
31-
"type": "string",
32-
"description": ""
33-
},
34-
"difficulty": {
35-
"type": "string",
30+
"burn_block_height": {
31+
"type": "number",
3632
"description": ""
3733
}
3834
}

docs/entities/stacking/signer.example.json

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"signing_key": "0x038e3c4529395611be9abf6fa3b6987e81d402385e3d605a073f42f407565a4a3d",
3+
"signer_address": "STRYYQQ9M8KAF4NS7WNZQYY59X93XEKR31JP64CP",
34
"stacked_amount": "686251350000000000",
45
"stacked_amount_percent": 50,
56
"weight": 5,

docs/entities/stacking/signer.schema.json

+5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"additionalProperties": false,
55
"required": [
66
"signing_key",
7+
"signer_address",
78
"weight",
89
"stacked_amount",
910
"weight_percent",
@@ -13,6 +14,10 @@
1314
"signing_key": {
1415
"type": "string"
1516
},
17+
"signer_address": {
18+
"type": "string",
19+
"description": "The Stacks address derived from the signing_key."
20+
},
1621
"weight": {
1722
"type": "integer"
1823
},

docs/generated.d.ts

+13-3
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,9 @@ export type AbstractTransaction = BaseTransaction & {
440440
* An ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ) indicating when this block was mined.
441441
*/
442442
block_time_iso: string;
443+
/**
444+
* Height of the anchor burn block.
445+
*/
443446
burn_block_height: number;
444447
/**
445448
* Unix timestamp (in seconds) indicating when this block was mined
@@ -2361,9 +2364,8 @@ export interface RosettaBlock {
23612364
/**
23622365
* meta data
23632366
*/
2364-
metadata?: {
2365-
transactions_root: string;
2366-
difficulty: string;
2367+
metadata: {
2368+
burn_block_height: number;
23672369
[k: string]: unknown | undefined;
23682370
};
23692371
}
@@ -3029,6 +3031,10 @@ export interface RosettaNetworkStatusResponse {
30293031
* Peers information
30303032
*/
30313033
peers: RosettaPeers[];
3034+
/**
3035+
* The latest burn block height
3036+
*/
3037+
current_burn_block_height: number;
30323038
}
30333039
/**
30343040
* The block_identifier uniquely identifies a block in a particular network.
@@ -3387,6 +3393,10 @@ export interface PoxCycleSignersListResponse {
33873393
}
33883394
export interface PoxSigner {
33893395
signing_key: string;
3396+
/**
3397+
* The Stacks address derived from the signing_key.
3398+
*/
3399+
signer_address: string;
33903400
weight: number;
33913401
stacked_amount: string;
33923402
weight_percent: number;

src/api/controllers/db-controller.ts

+3
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,9 @@ export async function getRosettaBlockFromDataStore(
518518
parent_block_identifier,
519519
timestamp: dbBlock.burn_block_time * 1000,
520520
transactions: blockTxs.found ? blockTxs.result : [],
521+
metadata: {
522+
burn_block_height: dbBlock.burn_block_height,
523+
},
521524
};
522525
return { found: true, result: apiBlock };
523526
});

src/api/init.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ export async function startApiServer(opts: {
242242
v2.use('/smart-contracts', createV2SmartContractsRouter(datastore));
243243
v2.use('/mempool', createMempoolRouter(datastore));
244244
v2.use('/addresses', createV2AddressesRouter(datastore));
245-
v2.use('/pox', createPoxRouter(datastore));
245+
v2.use('/pox', createPoxRouter(datastore, chainId));
246246
return v2;
247247
})()
248248
);

src/api/routes/rosetta/network.ts

+1
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ export function createRosettaNetworkRouter(db: PgStore, chainId: ChainID): expre
9898
hash: genesis.block_identifier.hash,
9999
},
100100
peers,
101+
current_burn_block_height: block.metadata?.burn_block_height ?? 0,
101102
};
102103
const nodeInfo = await stacksCoreRpcClient.getInfo();
103104
const referenceNodeTipHeight = nodeInfo.stacks_tip_height;

src/api/routes/v2/helpers.ts

+14-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
parseDbTx,
2828
} from '../../../api/controllers/db-controller';
2929
import { decodeClarityValueToRepr } from 'stacks-encoding-native-js';
30+
import { TransactionVersion, getAddressFromPublicKey } from '@stacks/transactions';
3031

3132
export function parseDbNakamotoBlock(block: DbBlock): NakamotoBlock {
3233
const apiBlock: NakamotoBlock = {
@@ -175,9 +176,14 @@ export function parseDbPoxCycle(cycle: DbPoxCycle): PoxCycle {
175176
return result;
176177
}
177178

178-
export function parseDbPoxSigner(signer: DbPoxCycleSigner): PoxSigner {
179+
export function parseDbPoxSigner(signer: DbPoxCycleSigner, isMainnet: boolean): PoxSigner {
180+
const signerAddress = getAddressFromPublicKey(
181+
Buffer.from(signer.signing_key.slice(2), 'hex'),
182+
isMainnet ? TransactionVersion.Mainnet : TransactionVersion.Testnet
183+
);
179184
const result: PoxSigner = {
180185
signing_key: signer.signing_key,
186+
signer_address: signerAddress,
181187
weight: signer.weight,
182188
stacked_amount: signer.stacked_amount,
183189
weight_percent: signer.weight_percent,
@@ -192,5 +198,12 @@ export function parseDbPoxSignerStacker(stacker: DbPoxCycleSignerStacker): PoxSt
192198
stacked_amount: stacker.locked,
193199
pox_address: stacker.pox_addr,
194200
};
201+
// Special handling for pool operator stackers
202+
if (
203+
stacker.name === 'stack-aggregation-commit-indexed' ||
204+
stacker.name === 'stack-aggregation-commit'
205+
) {
206+
result.stacked_amount = stacker.amount_ustx;
207+
}
195208
return result;
196209
}

src/api/routes/v2/pox.ts

+8-4
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,16 @@ import {
1818
PoxCycleListResponse,
1919
PoxCycleSignerStackersListResponse,
2020
PoxCycleSignersListResponse,
21-
} from 'docs/generated';
21+
PoxSigner,
22+
} from '@stacks/stacks-blockchain-api-types';
2223
import { parseDbPoxCycle, parseDbPoxSigner, parseDbPoxSignerStacker } from './helpers';
2324
import { InvalidRequestError } from '../../../errors';
25+
import { ChainID, getChainIDNetwork } from '../../../helpers';
2426

25-
export function createPoxRouter(db: PgStore): express.Router {
27+
export function createPoxRouter(db: PgStore, chainId: ChainID): express.Router {
2628
const router = express.Router();
2729
const cacheHandler = getETagCacheHandler(db);
30+
const isMainnet = getChainIDNetwork(chainId) === 'mainnet';
2831

2932
router.get(
3033
'/cycles',
@@ -83,7 +86,7 @@ export function createPoxRouter(db: PgStore): express.Router {
8386
limit,
8487
offset,
8588
total,
86-
results: results.map(r => parseDbPoxSigner(r)),
89+
results: results.map(r => parseDbPoxSigner(r, isMainnet)),
8790
};
8891
setETagCacheHeaders(res);
8992
res.json(response);
@@ -110,8 +113,9 @@ export function createPoxRouter(db: PgStore): express.Router {
110113
res.status(404).json({ error: `Not found` });
111114
return;
112115
}
116+
const response: PoxSigner = parseDbPoxSigner(signer, isMainnet);
113117
setETagCacheHeaders(res);
114-
res.json(parseDbPoxSigner(signer));
118+
res.json(response);
115119
} catch (error) {
116120
if (error instanceof InvalidRequestError) {
117121
res.status(404).json({ errors: error.message });

src/datastore/common.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1094,6 +1094,8 @@ export interface DbPoxCycleSignerStacker {
10941094
stacker: string;
10951095
locked: string;
10961096
pox_addr: string;
1097+
name: string;
1098+
amount_ustx: string;
10971099
}
10981100

10991101
interface ReOrgEntities {

src/datastore/pg-store-v2.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -688,7 +688,7 @@ export class PgStoreV2 extends BasePgStoreModule {
688688
);
689689
const results = await sql<(DbPoxCycleSignerStacker & { total: number })[]>`
690690
WITH stackers AS (
691-
SELECT DISTINCT ON (stacker) stacker, locked, pox_addr
691+
SELECT DISTINCT ON (stacker) stacker, locked, pox_addr, amount_ustx, name
692692
FROM pox4_events
693693
WHERE canonical = true
694694
AND microblock_canonical = true

src/datastore/pg-store.ts

+8-4
Original file line numberDiff line numberDiff line change
@@ -1938,8 +1938,10 @@ export class PgStore extends BasePgStore {
19381938
if (!dbTx.found) {
19391939
return { found: false };
19401940
}
1941+
const cols =
1942+
poxTable === 'pox4_events' ? POX4_SYNTHETIC_EVENT_COLUMNS : POX_SYNTHETIC_EVENT_COLUMNS;
19411943
const queryResults = await sql<PoxSyntheticEventQueryResult[]>`
1942-
SELECT ${sql(POX_SYNTHETIC_EVENT_COLUMNS)}
1944+
SELECT ${sql(cols)}
19431945
FROM ${sql(poxTable)}
19441946
WHERE canonical = true AND microblock_canonical = true AND tx_id = ${txId}
19451947
ORDER BY block_height DESC, microblock_sequence DESC, tx_index DESC, event_index DESC
@@ -1957,8 +1959,10 @@ export class PgStore extends BasePgStore {
19571959
poxTable: PoxSyntheticEventTable;
19581960
}): Promise<FoundOrNot<DbPoxSyntheticEvent[]>> {
19591961
return await this.sqlTransaction(async sql => {
1962+
const cols =
1963+
poxTable === 'pox4_events' ? POX4_SYNTHETIC_EVENT_COLUMNS : POX_SYNTHETIC_EVENT_COLUMNS;
19601964
const queryResults = await sql<PoxSyntheticEventQueryResult[]>`
1961-
SELECT ${sql(POX_SYNTHETIC_EVENT_COLUMNS)}
1965+
SELECT ${sql(cols)}
19621966
FROM ${sql(poxTable)}
19631967
WHERE canonical = true AND microblock_canonical = true AND stacker = ${principal}
19641968
ORDER BY block_height DESC, microblock_sequence DESC, tx_index DESC, event_index DESC
@@ -2349,7 +2353,7 @@ export class PgStore extends BasePgStore {
23492353
// Special case for `handle-unlock` which should be returned if it is the last received event.
23502354

23512355
const pox4EventQuery = await sql<PoxSyntheticEventQueryResult[]>`
2352-
SELECT ${sql(POX_SYNTHETIC_EVENT_COLUMNS)}
2356+
SELECT ${sql(POX4_SYNTHETIC_EVENT_COLUMNS)}
23532357
FROM pox4_events
23542358
WHERE canonical = true AND microblock_canonical = true AND stacker = ${stxAddress}
23552359
AND block_height <= ${blockHeight}
@@ -4243,7 +4247,7 @@ export class PgStore extends BasePgStore {
42434247

42444248
let poxV4Unlocks: StxLockEventResult[] = [];
42454249
const pox4EventQuery = await sql<PoxSyntheticEventQueryResult[]>`
4246-
SELECT DISTINCT ON (stacker) stacker, ${sql(POX_SYNTHETIC_EVENT_COLUMNS)}
4250+
SELECT DISTINCT ON (stacker) stacker, ${sql(POX4_SYNTHETIC_EVENT_COLUMNS)}
42474251
FROM pox4_events
42484252
WHERE canonical = true AND microblock_canonical = true
42494253
AND block_height <= ${block.block_height}

src/tests-2.5/pox-4-delegate-aggregation.ts

+20
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,26 @@ describe('PoX-4 - Delegate aggregation increase operations', () => {
319319
name: 'stack-aggregation-commit-indexed',
320320
pox_addr: delegateeAccount.btcTestnetAddr,
321321
stacker: delegatorAccount.stxAddr,
322+
data: expect.objectContaining({
323+
signer_key: `0x${signerPubKey}`,
324+
end_cycle_id: expect.stringMatching(/\d+/),
325+
start_cycle_id: expect.stringMatching(/\d+/),
326+
}),
327+
})
328+
);
329+
330+
const stackerRes: any = await fetchGet(`/extended/v1/pox4/stacker/${delegatorAccount.stxAddr}`);
331+
expect(stackerRes).toBeDefined();
332+
expect(stackerRes.results[0]).toEqual(
333+
expect.objectContaining({
334+
name: 'stack-aggregation-commit-indexed',
335+
pox_addr: delegateeAccount.btcTestnetAddr,
336+
stacker: delegatorAccount.stxAddr,
337+
data: expect.objectContaining({
338+
signer_key: `0x${signerPubKey}`,
339+
end_cycle_id: expect.stringMatching(/\d+/),
340+
start_cycle_id: expect.stringMatching(/\d+/),
341+
}),
322342
})
323343
);
324344
});

0 commit comments

Comments
 (0)