feat: implement endpoint to get the latest account nonce based off mempool and unanchored or anchored tx data

This commit is contained in:
Matthew Little
2021-05-20 11:44:45 +02:00
parent 6f3aed942c
commit 0b33bcbfef
7 changed files with 106 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
{
"last_mempool_tx_nonce": 5,
"last_executed_tx_nonce": 4
}

View File

@@ -0,0 +1,23 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "The latest nonce values used by an account by inspecting the mempool, microblock transactions, and anchored transactions",
"title": "AddressNonces",
"type": "object",
"additionalProperties": false,
"required": [
"last_mempool_tx_nonce",
"last_executed_tx_nonce"
],
"properties": {
"last_mempool_tx_nonce": {
"type": "integer",
"nullable": true,
"description": "The latest nonce found within mempool transactions sent by this address. Will be null if there are no current mempool transactions for this address."
},
"last_executed_tx_nonce": {
"type": "integer",
"nullable": true,
"description": "The latest nonce found within transactions sent by this address, including unanchored microblock transactions. Will be null if there are no current transactions for this address."
}
}
}

View File

@@ -998,6 +998,29 @@ paths:
example:
$ref: ./api/address/get-address-transactions-with-transfers.example.json
/extended/v1/address/{principal}/nonces:
get:
summary: Get the latest nonce values used by an account by inspecting the mempool, microblock transactions, and anchored transactions.
tags:
- Accounts
operationId: get_account_nonces
parameters:
- name: principal
in: path
description: Stacks address (e.g. `SP31DA6FTSJX2WGTZ69SFY11BH51NZMB0ZW97B5P0`)
required: true
schema:
type: string
responses:
200:
description: Success
content:
application/json:
schema:
$ref: ./entities/address/address-nonces.schema.json
example:
$ref: ./entities/address/address-nonces.example.json
/extended/v1/address/{principal}/assets:
get:
summary: Get account assets

View File

@@ -24,6 +24,7 @@ import {
MempoolTransactionListResponse,
AddressTransactionWithTransfers,
AddressTransactionsWithTransfersListResponse,
AddressNonces,
} from '@stacks/stacks-blockchain-api-types';
import { ChainID, cvToString, deserializeCV } from '@stacks/transactions';
import { validate } from '../validate';
@@ -383,5 +384,21 @@ export function createAddressRouter(db: DataStore, chainId: ChainID): RouterWith
res.json(response);
});
router.getAsync('/:stx_address/nonces', async (req, res) => {
// get recent asset event associated with address
const stxAddress = req.params['stx_address'];
if (!isValidPrincipal(stxAddress)) {
return res.status(400).json({ error: `invalid STX address "${stxAddress}"` });
}
const nonces = await db.getAddressNonces({
stxAddress,
});
const results: AddressNonces = {
last_executed_tx_nonce: nonces.lastExecutedTxNonce as number,
last_mempool_tx_nonce: nonces.lastMempoolTxNonce as number,
};
res.json(results);
});
return router;
}

View File

@@ -631,6 +631,10 @@ export interface DataStore extends DataStoreEventEmitter {
offset: number;
}): Promise<{ results: DbEvent[]; total: number }>;
getAddressNonces(args: {
stxAddress: string;
}): Promise<{ lastExecutedTxNonce: number | null; lastMempoolTxNonce: number | null }>;
getInboundTransfers(args: {
stxAddress: string;
limit: number;

View File

@@ -516,6 +516,12 @@ export class MemoryDataStore
throw new Error('not yet implemented');
}
getAddressNonces(args: {
stxAddress: string;
}): Promise<{ lastExecutedTxNonce: number | null; lastMempoolTxNonce: number | null }> {
throw new Error('not yet implemented');
}
getInboundTransfers({
stxAddress,
}: {

View File

@@ -1178,6 +1178,35 @@ export class PgDataStore
this.emitAddressTxUpdates(data);
}
async getAddressNonces(args: {
stxAddress: string;
}): Promise<{ lastExecutedTxNonce: number | null; lastMempoolTxNonce: number | null }> {
return await this.queryTx(async client => {
const executedTxNonce = await client.query<{ nonce: number }>(
`
SELECT MAX(nonce) nonce
FROM txs
WHERE sender_address = $1
AND canonical = true AND microblock_canonical = true
`,
[args.stxAddress]
);
const mempoolTxNonce = await client.query<{ nonce: number }>(
`
SELECT MAX(nonce) nonce
FROM mempool_txs
WHERE sender_address = $1
AND pruned = false
`,
[args.stxAddress]
);
return {
lastExecutedTxNonce: executedTxNonce.rowCount === 1 ? executedTxNonce.rows[0].nonce : null,
lastMempoolTxNonce: mempoolTxNonce.rowCount === 1 ? mempoolTxNonce.rows[0].nonce : null,
};
});
}
getNameCanonical(txId: string, indexBlockHash: string): Promise<FoundOrNot<boolean>> {
return this.query(async client => {
const queryResult = await client.query(