From 9246c32fa99868aa379b78810d1070f778e93024 Mon Sep 17 00:00:00 2001 From: Rafa Cardenas <253999660+rafa-stacks@users.noreply.github.com> Date: Mon, 28 Sep 2026 16:13:51 -0600 Subject: [PATCH] update architecture --- .../stacks-blockchain-api/architecture.mdx | 156 ++++++++++++++---- .../apis/stacks-blockchain-api/pagination.mdx | 2 +- .../accounts/get-principal-nonces.mdx | 2 +- .../v1-to-v3-migration.mdx | 20 +-- .../apis/stacks-blockchain-api/websockets.mdx | 5 +- 5 files changed, 130 insertions(+), 55 deletions(-) diff --git a/content/docs/en/apis/stacks-blockchain-api/architecture.mdx b/content/docs/en/apis/stacks-blockchain-api/architecture.mdx index 31307bc24..2dc51f170 100644 --- a/content/docs/en/apis/stacks-blockchain-api/architecture.mdx +++ b/content/docs/en/apis/stacks-blockchain-api/architecture.mdx @@ -5,52 +5,140 @@ description: Understand the architecture of the Stacks Blockchain API. ![Stacks Blockchain API architecture](/images/api/architecture.svg) -## RPC Endpoints +## Overview -The `stacks-node` has its own minimal set of http endpoints referred to as `RPC endpoints`. +The Stacks Blockchain API sits between Stacks nodes and the apps that use chain data. It does three +things: -- The `stacks-blockchain-api` allows clients to access RPC endpoints by proxying requests to a load-balanced pool of `stacks-nodes`. -- For more details on RPC endpoints, see: [RPC Endpoints Documentation](https://github.com/blockstack/stacks-blockchain/blob/master/docs/rpc-endpoints.md) -- Common RPC endpoints include: - - `POST /v2/transactions` - Broadcast a transaction - - `GET /v2/pox` - Retrieve current Proof of Transfer (PoX) relevant information - - `POST /v2/contracts/call-read//` - Evaluate and return the result of calling a Clarity function - - `POST /v2/fees/transaction` - Evaluate a given transaction and provide transaction fee estimation data - - `GET /v2/accounts/
` - Fetch the current nonce required for creating transactions +- **Indexes the chain.** It receives events from a Stacks node and stores them as relational data + in PostgreSQL. +- **Serves that data.** Its REST endpoints and real-time subscriptions read from PostgreSQL, so + they can answer questions a node cannot, such as the full transaction history of an account. +- **Proxies the node.** Requests to the node's own `/v2/*` RPC endpoints pass through the API to a + Stacks node. + +## Event ingestion -## Additional Endpoints +A Stacks node doesn't keep an account's transaction history, and it isn't built to serve many +clients at once. The API fills that gap by indexing everything the node reports. -The Stacks Blockchain API implements additional endpoints that provide data unavailable directly from Stacks nodes due to various constraints. +The API runs an event server (port `3700` by default) that a Stacks node pushes events to over +HTTP. To connect a node, add an event observer to its configuration: + +```toml +[[events_observer]] +endpoint = "localhost:3700" +events_keys = ["*"] +timeout_ms = 60_000 +``` -- The `stacks-node` may not persist certain data or may not serve it efficiently to many clients. For instance, while it can return the current STX balance of an account, it cannot provide a history of account transactions. -- The API implements the Rosetta specification by Coinbase, an open standard designed to simplify blockchain deployment and interaction. More information can be found at [Rosetta API](https://www.rosetta-api.org/). -- The API includes support for the Blockchain Naming System (BNS) endpoints. Details are available at [BNS Documentation](https://docs.stacks.co/clarity/example-contracts/bns). -- For Express.js routes, see the directory `/src/api/routes`. +The node then sends: -The API creates an "event observer" http server which listens for events from a stacks-node "event emitter". +- **New blocks**, with each transaction and what it produced: asset transfers, smart contract logs, + and execution costs +- **New burn blocks** from the Bitcoin chain +- **Mempool activity**: newly received transactions, and transactions dropped from the mempool +- **Signer data**: StackerDB chunks and block proposal responses -Events are HTTP POST requests containing: - - Blocks - - Transactions +The API decodes these and writes them to PostgreSQL. The ingestion code is in +[`src/event-stream`](https://github.com/hirosystems/stacks-blockchain-api/tree/main/src/event-stream). -Byproducts of executed transactions such as: - - Asset transfers - - Smart-contract log data - - Execution cost data +### Ingesting from Stacks Node Publisher -The API processes and stores these events as relational data in PostgreSQL. For the "event observer" code, see `/src/event-stream`. +Instead of receiving events directly from a node, the API can consume them from +[Stacks Node Publisher](https://github.com/stx-labs/stacks-node-publisher) (SNP), which stores +every node event in PostgreSQL and streams it to consumers over Redis. This decouples the API from +any single node, and SNP can replay history from any block, so a new API instance can sync without +a node re-sending events. -## OpenAPI and JSON Schema +Set `SNP_EVENT_STREAMING=true` and `SNP_REDIS_URL` to enable it. `SNP_BLOCKS_ONLY_STREAMING=true` +limits the stream to blocks and burn blocks, which speeds up a sync from genesis. -All http endpoints and responses are defined in OpenAPI and JSON Schema. +## Serving data -- See `/docs/openapi.yaml` -- These are used to auto-generate the docs at https://hirosystems.github.io/stacks-blockchain-api/ -- JSON Schemas are converted into TypeScript interfaces, which are used internally by the db controller module to transform SQL query results into the correct object shapes. -- OpenAPI and JSON Schemas are also used to generate a standalone `@stacks/blockchain-api-client`. +Endpoints are built with [Fastify](https://fastify.dev/), with request and response schemas +defined in [TypeBox](https://github.com/sinclairzx81/typebox). Route definitions are in +[`src/api/routes`](https://github.com/hirosystems/stacks-blockchain-api/tree/main/src/api/routes). -## Development Setup +- **REST endpoints** under `/extended/v3`, plus the `/extended/v2` endpoints that have no v3 + successor, read from PostgreSQL. See the [API reference](/apis/stacks-blockchain-api) for the full + list. Older `/extended/v1` endpoints are deprecated; see + [Migrating from v1 to v3](/apis/stacks-blockchain-api/v1-to-v3-migration). +- **Real-time subscriptions** for blocks, mempool transactions, transaction updates, address + activity, and NFT events are available over WebSocket (JSON-RPC 2.0) and Socket.IO. See + [WebSockets](/apis/stacks-blockchain-api/websockets). -The easiest/quickest way to develop in this repo is using the VS Code debugger. It uses docker-compose to set up a stacks-node and Postgres instance. +Most responses carry an `ETag` tied to the state they were computed from, such as the chain tip, the +mempool, or a specific transaction. Send it back in an `If-None-Match` header, and the API answers +`304 Not Modified` when nothing has changed, without recomputing the response. -Alternatively, you can run `npm run dev:integrated` which does the same thing but without a debugger. +## Stacks node RPC proxy + +A Stacks node exposes its own set of HTTP endpoints, referred to as RPC endpoints. The API forwards +requests for the node's `/v2/*` endpoints to the Stacks node it is configured with, and returns the +node's response unchanged. Commonly used RPC endpoints include: + +| Endpoint | Purpose | +| --- | --- | +| `POST /v2/transactions` | Broadcast a transaction | +| `POST /v2/fees/transaction` | Estimate the fee for a transaction | +| `POST /v2/contracts/call-read/{deployer_address}/{contract_name}/{function_name}` | Call a read-only Clarity function | +| `GET /v2/accounts/{principal}` | Get an account's balance and nonce | +| `GET /v2/pox` | Get current Proof of Transfer information | + +See the [Stacks Node RPC API](/apis/stacks-node-rpc-api) for every RPC endpoint. + +Proxied requests take longer than requests the API answers from its own database, because each one +round-trips to a node. Avoid calling them at high frequency when an API endpoint serves the same +data. + +The API forwards to a single configured node address (`STACKS_CORE_PROXY_HOST` and +`STACKS_CORE_PROXY_PORT`, falling back to the node's RPC host and port). To spread proxied traffic +across several nodes, point it at a load balancer. + +:::callout +### Fee estimation can differ from the node's +When `STACKS_CORE_FEE_ESTIMATOR_ENABLED=true`, the API adjusts `POST /v2/fees/transaction` +responses. If recent blocks had spare capacity, it returns the minimum fee for the transaction's +size. Otherwise it returns the node's estimate scaled by a configurable multiplier, never below +that minimum. It is disabled by default. +::: + +## Run modes + +The same codebase can run as a single instance or be split up to scale, controlled by +`STACKS_API_MODE`: + +| Mode | Runs | Use | +| --- | --- | --- | +| Default | Event server and API server | A single self-contained instance | +| `readonly` | API server only | Serve traffic from a database another instance writes to | +| `writeonly` | Event server only | Ingest into PostgreSQL without serving endpoints | + +A common production layout is one write-only instance ingesting events and several read-only +instances behind a load balancer, all sharing one PostgreSQL database. Read-only instances fully +support WebSocket and Socket.IO subscriptions. + +## OpenAPI specification and client + +The OpenAPI specification is generated from the Fastify route schemas, so it can't drift from the +endpoints it describes. The committed +[`openapi.yaml`](https://github.com/hirosystems/stacks-blockchain-api/blob/main/openapi.yaml) is +regenerated with each release and excludes deprecated endpoints. + +The specification powers: + +- The [API reference](/apis/stacks-blockchain-api) in these docs +- [`@stacks/blockchain-api-client`](https://www.npmjs.com/package/@stacks/blockchain-api-client), a + typed TypeScript client for the REST and real-time APIs + +## Running the API yourself + +For local development, [Clarinet](https://github.com/stx-labs/clarinet) runs a full devnet — Bitcoin node, Stacks node, +API, and PostgreSQL — with `clarinet devnet start`. + +For production, use the +[`hirosystems/stacks-blockchain-api`](https://hub.docker.com/r/hirosystems/stacks-blockchain-api) +Docker image. The +[repository README](https://github.com/hirosystems/stacks-blockchain-api#readme) covers +configuration, run modes, event replay, and development setup. diff --git a/content/docs/en/apis/stacks-blockchain-api/pagination.mdx b/content/docs/en/apis/stacks-blockchain-api/pagination.mdx index 0f0dbccb8..244998750 100644 --- a/content/docs/en/apis/stacks-blockchain-api/pagination.mdx +++ b/content/docs/en/apis/stacks-blockchain-api/pagination.mdx @@ -14,7 +14,7 @@ Cursor-paginated endpoints accept two query parameters: - `limit`: The number of items to return per page. Defaults to `20`, with a maximum of `50`. - `cursor`: The cursor pointing at the page to fetch. Omit it on your first request to get the first page. -Treat the cursor as an opaque token — copy the value from a response and pass it back unchanged. (Its internal format is endpoint-specific; for example, the transactions endpoint encodes `block_height:microblock_sequence:tx_index`.) +Treat the cursor as an opaque token — copy the value from a response and pass it back unchanged. Its internal format is endpoint-specific and may change, so don't construct or parse cursors yourself. ## Response body diff --git a/content/docs/en/apis/stacks-blockchain-api/reference/accounts/get-principal-nonces.mdx b/content/docs/en/apis/stacks-blockchain-api/reference/accounts/get-principal-nonces.mdx index 9e3defa75..8224d8a2b 100644 --- a/content/docs/en/apis/stacks-blockchain-api/reference/accounts/get-principal-nonces.mdx +++ b/content/docs/en/apis/stacks-blockchain-api/reference/accounts/get-principal-nonces.mdx @@ -1,7 +1,7 @@ --- title: Get principal nonces sidebarTitle: Principal nonces -description: "Get a Stacks account's latest nonce state by inspecting its confirmed (anchored + microblock) transactions and the mempool, including the nonce to use for its next transaction." +description: "Get a Stacks account's latest nonce state by inspecting its confirmed transactions and the mempool, including the nonce to use for its next transaction. Only standard principals have nonces; contract principals are not valid." full: true --- diff --git a/content/docs/en/apis/stacks-blockchain-api/v1-to-v3-migration.mdx b/content/docs/en/apis/stacks-blockchain-api/v1-to-v3-migration.mdx index af68305b8..2bd748b68 100644 --- a/content/docs/en/apis/stacks-blockchain-api/v1-to-v3-migration.mdx +++ b/content/docs/en/apis/stacks-blockchain-api/v1-to-v3-migration.mdx @@ -58,11 +58,9 @@ v1 flattened everything into the top level (`block_height`, `burn_block_time`, `execution_cost_runtime`, `pending_balance_inbound`). v3 groups them (`block.height`, `bitcoin_block.time`, `execution_cost.runtime`, `mempool.inbound`). -### Microblock and unanchored fields are gone +### Only canonical data is returned -Microblocks were removed in the Nakamoto upgrade. v3 has no `microblock_hash`, -`microblock_sequence`, `microblock_canonical`, `is_unanchored`, or `unanchored` query parameter. -There is also no `canonical` field. v3 only returns canonical data. +v1 marked records with a `canonical` flag. v3 has no such field: it only returns canonical data. ### ISO timestamp duplicates are gone @@ -123,8 +121,6 @@ block height, a block hash, or the literal `latest`. | `post_condition_mode` | Removed | | `anchor_mode` | Removed | | `canonical` | Removed — v3 only returns canonical data | -| `is_unanchored` | Removed | -| `microblock_*` | Removed — microblocks no longer exist | | — | `block.index_hash` (new) | | — | `vm_error` (new) | @@ -198,8 +194,7 @@ In v1, `estimated_balance` was the **total** balance plus the pending mempool de locked STX is excluded. If you were subtracting `locked` yourself, stop. ::: -v1 accepted `until_block` and `unanchored` on the balance endpoints. v3 always reports the -current chain tip. +v1 accepted `until_block` on the balance endpoints. v3 always reports the current chain tip. ### FT and NFT balances @@ -371,8 +366,7 @@ The quantity itself is also defined differently. v1 `total_stx` was the circulat given block height, with `unlocked_stx` tracking the unlocked portion separately. v3 `total` is the total **liquid** supply at the current chain tip: all STX minted (vesting unlocks included) plus matured miner coinbase rewards, minus burned STX. There is no separate locked/unlocked -split, and v3 always reports the chain tip — the v1 `height` and `unanchored` query parameters -are gone. +split, and v3 always reports the chain tip — the v1 `height` query parameter is gone. ::: ## Fungible tokens @@ -465,8 +459,7 @@ type: warn ### `tx_metadata` has no v3 equivalent v1 could inline a full transaction object into each row via `tx_metadata=true`. v3 returns the transaction id and event index only; fetch the transaction separately from -`GET /extended/v3/transactions/{tx_id}` when you need its detail. The `unanchored` parameter is -also gone, as everywhere else in v3. +`GET /extended/v3/transactions/{tx_id}` when you need its detail. ::: ## Network block times @@ -514,9 +507,6 @@ These are deprecated with no successor. Plan around them rather than swapping a | `GET /extended/v1/tx/events` | Global event feed filtered by principal, transaction, or event type. Several v3 endpoints cover parts of it: per-transaction events at `GET /extended/v3/transactions/{tx_id}/events`, a principal's history for one fungible token at `GET /extended/v3/principals/{principal}/transfers/ft/{asset_identifier}`, a principal's STX transfers at `GET /extended/v3/principals/{principal}/transfers/stx/{inbound,outbound}`, and per-principal asset movement at `GET /extended/v3/principals/{principal}/balance-changes`. There is no single global event feed. | | `GET /extended/v1/address/{principal}/assets` | Closest equivalent is `GET /extended/v3/principals/{principal}/balance-changes`, which reports net balance deltas rather than raw asset events. | | `GET /extended/v1/contract/by_trait` | Searches deployed contracts by Clarity trait ABI. No v3 equivalent. To look up a specific contract you already know, use `GET /extended/v3/smart-contracts/{contract_id}`. | -| `GET /extended/v1/microblock` | Microblocks were removed in the Nakamoto upgrade and are no longer produced. | -| `GET /extended/v1/microblock/{hash}` | Same. | -| `GET /extended/v1/microblock/unanchored/txs` | Same. | | `GET /extended/v1/tokens/nft/mints` | Mint events for an asset class. Retired rather than migrated — the endpoint serves negligible traffic. A single instance's mint is the oldest entry in `GET /extended/v3/tokens/nft/{asset_identifier}/{value}/history`, but there is no collection-wide mint feed. | | `GET /extended/v1/faucets/btc/{address}` | Testnet-only BTC balance helper. No replacement. | diff --git a/content/docs/en/apis/stacks-blockchain-api/websockets.mdx b/content/docs/en/apis/stacks-blockchain-api/websockets.mdx index 56286a37c..8299658b2 100644 --- a/content/docs/en/apis/stacks-blockchain-api/websockets.mdx +++ b/content/docs/en/apis/stacks-blockchain-api/websockets.mdx @@ -11,7 +11,7 @@ Instead of polling REST endpoints, you can subscribe to blockchain events and ha - **WebSockets**: A standard WebSocket connection speaking JSON-RPC 2.0, available at the `/extended/v1/ws` path. - **Socket.IO**: A [Socket.IO](https://socket.io/) server on the API's root URL, which adds automatic reconnection and fallback transports on top of WebSockets. -Both channels deliver the same events: new blocks, microblocks, mempool transactions, transaction status updates, address activity, and NFT events. +Both channels deliver the same events: new blocks, mempool transactions, transaction status updates, address activity, and NFT events. | Network | WebSockets | Socket.IO | |---------|------------|-----------| @@ -73,7 +73,6 @@ Both clients cover the same events: | Event | WebSocket client | Socket.IO client | |-------|------------------|------------------| | Blocks | `subscribeBlocks(handler)` | `subscribeBlocks(handler)` | -| Microblocks | `subscribeMicroblocks(handler)` | `subscribeMicroblocks(handler)` | | Mempool transactions | `subscribeMempool(handler)` | `subscribeMempool(handler)` | | Transaction updates | `subscribeTxUpdates(txId, handler)` | `subscribeTransaction(txId, handler)` | | Address transactions | `subscribeAddressTransactions(address, handler)` | `subscribeAddressTransactions(address, handler)` | @@ -93,7 +92,6 @@ Open a WebSocket to `/extended/v1/ws` and send JSON-RPC 2.0 messages with the `s | `event` | Additional params | |---------|-------------------| | `block` | — | -| `microblock` | — | | `mempool` | — | | `tx_update` | `tx_id` | | `address_tx_update` | `address` | @@ -144,7 +142,6 @@ Connect any standard Socket.IO client to the API's root URL. Subscriptions are m | Topic | Description | |-------|-------------| | `block` | New blocks | -| `microblock` | New microblocks | | `mempool` | New mempool transactions | | `transaction:{txId}` | Updates for a specific transaction | | `address-transaction:{address}` | Transactions involving an address |