Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "stacks",
"version": "1.0.0",
"description": "",
"main": "index.js",
"devDependencies": {
"@types/node": "^18.11.12",
"prettier": "2.8.1",
"ts-node": "^10.9.1",
"typescript": "^4.9.4"
},
"scripts": {
"test": "ts-node ./script/index.ts"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"axios": "^1.2.1"
}
}
14 changes: 14 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# apiTest
apiTest is a node script for testing transactions on micro block api and anchor block api.

### Prerequisites

- npm installed and Node v16.*
- Stacks CLI on your machine [@stacks/cli](https://www.npmjs.com/package/@stacks/cli)

### Run script
1. Go to the root of the project and do `npm install`. Make sure you have satisfied the above Prerequisites.
2. Run the script:
```sh
$ npm run test
```
9 changes: 9 additions & 0 deletions script/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { triggerAnchorBlockTests } from "./tests/anchor.blocks.spec";

const anchorBlockOptions = {
accountId: "ST3C4YVASFG37P8E6210J9KHZJB4MF8ZRBQFKH41N",
recipient: "ST297VG59W96DPGBT13SGD542QE1XS954X4ZSWW2J",
};

// trigger anchor block testing script
triggerAnchorBlockTests(anchorBlockOptions);
119 changes: 119 additions & 0 deletions script/tests/anchor.blocks.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import * as util from "util";
import * as child_process from "child_process";
const exec = util.promisify(child_process.exec);
let interval,
triggerCount = 0,
foundMicroBlock = false;

import { getAccountDetail, getMicroBlock, getAnchorBlock } from "../utils/curl";
import { wait } from "../utils/helper";

const runAnchorBlockTest = async (options: any) => {
triggerCount++;
console.log(`Trigger script count: ${triggerCount}`);

const { accountId, recipient } = options;
try {
// step 1: Fetch nonce from account id
console.log("Fetching Nonce...");
const nonce = await fetchNonce(accountId);
console.log("Nonce fetched", nonce);

// step 2: Run the stx command for send_tokens to fetch the TXID
console.log("Sending tokens and fetching TXID...");
const txid = await sendTokens(nonce, recipient);
console.log("TXID fetched", txid);

// step 3: CURL the microblock API to make sure that TXID exist
if (!foundMicroBlock) {
await wait(10000);
console.log("Calling microblock API...");
const isTXIDExistMB = await checkTXIDInMicroBlock(txid);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There could be some situations when the transaction doesn't get picked up by any microblock but only by an anchor block.

I know you're currently not failing the test if you don't see the tx in a microblock, but I wanted to point that out in case you wanted to create a secondary test later surrounding microblocks.

Copy link
Owner Author

@timstackblock timstackblock Jan 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a great point I can definitely create a better check for that I have an assertion for it currently but let me make sure it works accurately.

if (isTXIDExistMB) {
foundMicroBlock = true;
console.log(`The transaction ${txid} is present in Micro Block API`);
} else {
console.log(
`The transaction ${txid} is not present in Micro Block API`
);
}
}

// step 4: CURL the Anchor block API to make sure that TXID exist
console.log("Calling Anchor block API...");
const isTXIDExistAB = await checkTXIDInAnchorBlock(txid);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think another valuable API request would be to check if https://stacks-node-api.mainnet.stacks.co/extended/v1/tx/{tx_id} confirms the transaction exists in the same anchor block and with the correct properties

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can add that as well

if (isTXIDExistAB) {
console.log(`The transaction ${txid} is present in Anchor block API`);
process.exit(0);
} else {
console.log(`The transaction ${txid} is not present in Anchor block API`);
}
if (triggerCount === 5) {
console.log("Time limit reached: 30 minutes");
clearInterval(interval);
process.exit(0);
}
} catch (e) {
console.error(
"An error occurred while processing the anchor block script",
e
);
clearInterval(interval);
process.exit(1);
}
};

export function triggerAnchorBlockTests(options: any) {
runAnchorBlockTest(options);

interval = setInterval(() => {
runAnchorBlockTest(options);
}, 6 * 60 * 1000); // every 6 minutes
}

async function fetchNonce(accountId: string): Promise<number> {
const accountDetail = await getAccountDetail(accountId);
if (!accountDetail) {
console.log(`No account detail found for account id - ${accountId}`);
return 0;
}
return accountDetail.nonce;
}

async function sendTokens(nonce: number, recipient: string): Promise<any> {
const { stdout, stderr } = await exec(
`stx -t send_tokens ${recipient} 12345 999 ${nonce} fd3609e8b9352facf360c950d362afe8f0f108b9041750f54b017efc479efeb001`

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not too familiar with the stx cli... is any of these inputs sensitive info? In that case you could save it as a Github actions secret in the repo and use it as an input via an ENV var

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure I can do that this is a test account so I just threw the private key into the script but I do see your point.

);
if (stderr) {
console.error(
`An error occurred while sending the tokens to recipient ${recipient}`
);
return;
}
const filteredResponse = stdout.replace(/(\r\n|\n|\r|\s)/gm, "");
return filteredResponse.split(",")[0].split(":")[1].replace(/\'/g, "");
}

async function checkTXIDInMicroBlock(txid: string): Promise<boolean> {
const microBlockData = await getMicroBlock();
if (!microBlockData) {
console.log(`No micro block data found`);
return false;
}
const isTXIDExist = microBlockData.results.some(
(result) => result.tx_id === txid
);
return isTXIDExist;
}

async function checkTXIDInAnchorBlock(txid: string): Promise<boolean> {
const anchorBlockData = await getAnchorBlock();
if (!anchorBlockData) {
console.log(`No anchor block data found`);
return false;
}
const isTXIDExist = anchorBlockData.results.some(
(result) => result.tx_id === txid
);
return isTXIDExist;
}
40 changes: 40 additions & 0 deletions script/utils/curl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import axios from "axios";

export async function getAccountDetail(accountId: string): Promise<any> {
return new Promise((resolve, reject) => {
axios
.get(`https://stacks-node-api.testnet.stacks.co/v2/accounts/${accountId}`)
.then((res) => {
resolve(res ? res.data: '');
})
.catch((error) => {
reject(error);
});
});
}

export async function getMicroBlock(): Promise<any> {
return new Promise((resolve, reject) => {
axios
.get('https://stacks-node-api.testnet.stacks.co/extended/v1/microblock/unanchored/txs')
.then((res) => {
resolve(res ? res.data: '');
})
.catch((error) => {
reject(error);
});
});
}

export async function getAnchorBlock(): Promise<any> {
return new Promise((resolve, reject) => {
axios
.get('https://stacks-node-api.testnet.stacks.co/extended/v1/tx?limit=200')

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A block may contain more than 200 transactions, there could be cases where you don't see the transaction because of this paging limit.

A better approach would be to record the current block_height before sending the tx, and then trying to retrieve the next blocks via /extended/v1/block until you see the tx_id listed

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please elaborate a bit on how to do this, this is my first time taking this approach and I want to make sure I clearly understand the steps.

.then((res) => {
resolve(res ? res.data: '');
})
.catch((error) => {
reject(error);
});
});
}
7 changes: 7 additions & 0 deletions script/utils/helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const wait = async (ms: number) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, ms);
});
};
14 changes: 14 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"lib": ["es6"],
"allowJs": true,
"outDir": "build",
"rootDir": "script",
"strict": true,
"noImplicitAny": false,
"esModuleInterop": true,
"resolveJsonModule": true
}
}