Skip to content

Commit

Permalink
feat: support multiple STX faucet source accounts (#1946)
Browse files Browse the repository at this point in the history
* feat: support multiple STX faucet source accounts

* fix: always start attempt on index 0

* fix: guard against array overflow
  • Loading branch information
rafaelcr committed Apr 15, 2024
1 parent 49a4d25 commit 5d69c7c
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 18 deletions.
5 changes: 5 additions & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ STACKS_CORE_RPC_PORT=20443
# STACKS_FAUCET_NODE_HOST=<IP or hostname>
# STACKS_FAUCET_NODE_PORT=<port number>

# A comma-separated list of STX private keys which will send faucet transactions to accounts that
# request them. Attempts will always be made from the first account, only once transaction chaining
# gets too long the faucet will start using the next one.
# FAUCET_PRIVATE_KEY=

## configure the chainID/networkID; testnet: 0x80000000, mainnet: 0x00000001
STACKS_CHAIN_ID=0x00000001

Expand Down
48 changes: 30 additions & 18 deletions src/api/routes/faucets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export function getStxFaucetNetworks(): StacksNetwork[] {
enum TxSendResultStatus {
Success,
ConflictingNonce,
TooMuchChaining,
Error,
}

Expand All @@ -50,17 +51,12 @@ interface TxSendResultSuccess {
txId: string;
}

interface TxSendResultConflictingNonce {
status: TxSendResultStatus.ConflictingNonce;
error: Error;
}

interface TxSendResultError {
status: TxSendResultStatus.Error;
status: TxSendResultStatus;
error: Error;
}

type TxSendResult = TxSendResultSuccess | TxSendResultConflictingNonce | TxSendResultError;
type TxSendResult = TxSendResultSuccess | TxSendResultError;

function clientFromNetwork(network: StacksNetwork): StacksCoreRpcClient {
const coreUrl = new URL(network.coreApiUrl);
Expand Down Expand Up @@ -148,6 +144,9 @@ export function createFaucetRouter(db: PgWriteStore): express.Router {
const FAUCET_STACKING_WINDOW = 2 * 24 * 60 * 60 * 1000; // 2 days
const FAUCET_STACKING_TRIGGER_COUNT = 1;

const STX_FAUCET_NETWORKS = getStxFaucetNetworks();
const STX_FAUCET_KEYS = (process.env.FAUCET_PRIVATE_KEY ?? testnetKeys[0].secretKey).split(',');

router.post(
'/stx',
asyncHandler(async (req, res) => {
Expand All @@ -167,8 +166,6 @@ export function createFaucetRouter(db: PgWriteStore): express.Router {
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
const lastRequests = await db.getSTXFaucetRequests(address);

const privateKey = process.env.FAUCET_PRIVATE_KEY || testnetKeys[0].secretKey;

const isStackingReq = req.query['stacking'] === 'true';

// Guard condition: requests are limited to x times per y minutes.
Expand All @@ -191,10 +188,8 @@ export function createFaucetRouter(db: PgWriteStore): express.Router {
return;
}

const networks = getStxFaucetNetworks();

const stxAmounts: bigint[] = [];
for (const network of networks) {
for (const network of STX_FAUCET_NETWORKS) {
try {
let stxAmount = FAUCET_DEFAULT_STX_AMOUNT;
if (isStackingReq) {
Expand All @@ -216,13 +211,14 @@ export function createFaucetRouter(db: PgWriteStore): express.Router {

const generateTx = async (
network: StacksNetwork,
keyIndex: number,
nonce?: bigint,
fee?: bigint
): Promise<StacksTransaction> => {
const txOpts: SignedTokenTransferOptions = {
recipient: address,
amount: stxAmount,
senderKey: privateKey,
senderKey: STX_FAUCET_KEYS[keyIndex],
network: network,
memo: 'Faucet',
anchorMode: AnchorMode.Any,
Expand All @@ -242,7 +238,7 @@ export function createFaucetRouter(db: PgWriteStore): express.Router {
/estimating transaction fee|NoEstimateAvailable/.test(error.message)
) {
const defaultFee = 200n;
return await generateTx(network, nonce, defaultFee);
return await generateTx(network, keyIndex, nonce, defaultFee);
}
throw error;
}
Expand All @@ -251,9 +247,9 @@ export function createFaucetRouter(db: PgWriteStore): express.Router {
const nonces: bigint[] = [];
const fees: bigint[] = [];
let txGenFetchError: Error | undefined;
for (const network of networks) {
for (const network of STX_FAUCET_NETWORKS) {
try {
const tx = await generateTx(network);
const tx = await generateTx(network, 0);
nonces.push(tx.auth.spendingCondition?.nonce ?? BigInt(0));
fees.push(tx.auth.spendingCondition.fee);
} catch (error: any) {
Expand All @@ -270,10 +266,11 @@ export function createFaucetRouter(db: PgWriteStore): express.Router {
let retrySend = false;
let sendSuccess: { txId: string; txRaw: string } | undefined;
let lastSendError: Error | undefined;
let stxKeyIndex = 0;
do {
const tx = await generateTx(networks[0], nextNonce, fee);
const tx = await generateTx(STX_FAUCET_NETWORKS[0], stxKeyIndex, nextNonce, fee);
const rawTx = Buffer.from(tx.serialize());
for (const network of networks) {
for (const network of STX_FAUCET_NETWORKS) {
const rpcClient = clientFromNetwork(network);
try {
const res = await rpcClient.sendTransaction(rawTx);
Expand All @@ -289,6 +286,11 @@ export function createFaucetRouter(db: PgWriteStore): express.Router {
status: TxSendResultStatus.ConflictingNonce,
error,
});
} else if (error.message?.includes('TooMuchChaining')) {
sendTxResults.push({
status: TxSendResultStatus.TooMuchChaining,
error,
});
} else {
sendTxResults.push({
status: TxSendResultStatus.Error,
Expand All @@ -305,6 +307,16 @@ export function createFaucetRouter(db: PgWriteStore): express.Router {
retrySend = true;
sendTxResults.length = 0;
nextNonce = nextNonce + 1n;
} else if (
sendTxResults.every(res => res.status === TxSendResultStatus.TooMuchChaining)
) {
// Try with the next key in case we have one.
if (stxKeyIndex + 1 === STX_FAUCET_KEYS.length) {
retrySend = false;
} else {
retrySend = true;
stxKeyIndex++;
}
} else {
retrySend = false;
}
Expand Down

0 comments on commit 5d69c7c

Please sign in to comment.