-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #35 from hirosystems/feat/flood
feat: add `flood` script
- Loading branch information
Showing
6 changed files
with
291 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,211 @@ | ||
import { StacksTestnet } from '@stacks/network'; | ||
import { StackingClient } from '@stacks/stacking'; | ||
import { | ||
TransactionVersion, | ||
getAddressFromPrivateKey, | ||
getNonce, | ||
makeSTXTokenTransfer, | ||
broadcastTransaction, | ||
makeRandomPrivKey, | ||
StacksTransaction, | ||
makeContractDeploy, | ||
makeContractCall, | ||
tupleCV, | ||
uintCV, | ||
} from '@stacks/transactions'; | ||
import { readFileSync } from 'fs'; | ||
import { config } from 'dotenv'; | ||
|
||
if (process.argv.slice(2).length > 0) { | ||
config({ path: './tx-broadcaster.env' }); | ||
} | ||
import { bytesToHex } from '@stacks/common'; | ||
import { logger, parseEnvInt, contractsApi, accountsApi } from './common'; | ||
|
||
const broadcastInterval = parseInt(process.env.NAKAMOTO_BLOCK_INTERVAL ?? '2'); | ||
const url = `http://${process.env.STACKS_CORE_RPC_HOST}:${process.env.STACKS_CORE_RPC_PORT}`; | ||
const network = new StacksTestnet({ url }); | ||
const EPOCH_30_START = parseInt(process.env.STACKS_30_HEIGHT ?? '0'); | ||
|
||
const bootstrapperKey = process.env.BOOTSTRAPPER_KEY!; | ||
const bootstrapper = { | ||
privKey: bootstrapperKey, | ||
stxAddress: getAddressFromPrivateKey(bootstrapperKey, TransactionVersion.Testnet), | ||
}; | ||
|
||
const client = new StackingClient(bootstrapper.stxAddress, network); | ||
|
||
const floodContract = readFileSync('./flooder.clar', { encoding: 'utf-8' }); | ||
|
||
const NUM_FLOODERS = parseEnvInt('NUM_FLOODERS', false) ?? 0; | ||
// per account, how many tx to make per run | ||
const TX_PER_FLOOD = parseEnvInt('TX_PER_FLOOD', false) ?? 10; | ||
|
||
const flooders: { privKey: string; stxAddress: string; nonce: bigint }[] = []; | ||
|
||
for (let i = 0; i < NUM_FLOODERS; i++) { | ||
const privKey = makeRandomPrivKey(); | ||
flooders.push({ | ||
privKey: bytesToHex(privKey.data), | ||
stxAddress: getAddressFromPrivateKey(privKey.data, TransactionVersion.Testnet), | ||
nonce: BigInt(0), | ||
}); | ||
} | ||
|
||
let hasSentToFlooders = false; | ||
const floodContractDeployer = bootstrapper.stxAddress; | ||
|
||
async function bootstrapFlooders() { | ||
const nonce = await getNonce(bootstrapper.stxAddress, network); | ||
logger.info('Bootstrapping flooders'); | ||
// sync iterate | ||
let i = 0n; | ||
let allWorked = true; | ||
for (const flooder of flooders) { | ||
const tx = await makeSTXTokenTransfer({ | ||
recipient: flooder.stxAddress, | ||
amount: 1000000 * 10_000, // 10k STX | ||
senderKey: bootstrapper.privKey, | ||
network, | ||
nonce: nonce + i, | ||
fee: 300, | ||
anchorMode: 'any', | ||
}); | ||
i++; | ||
const ok = await broadcast(tx, bootstrapper.stxAddress); | ||
if (!ok) { | ||
allWorked = false; | ||
break; | ||
} | ||
} | ||
if (!(await isContractDeployed(floodContractDeployer))) { | ||
const ok = await broadcast( | ||
await makeContractDeploy({ | ||
senderKey: bootstrapper.privKey, | ||
nonce: nonce + i, | ||
contractName: 'flood', | ||
codeBody: floodContract, | ||
fee: 3000, | ||
anchorMode: 'any', | ||
network, | ||
}), | ||
bootstrapper.stxAddress | ||
); | ||
if (!ok) allWorked = false; | ||
} | ||
if (allWorked) { | ||
hasSentToFlooders = true; | ||
} | ||
} | ||
|
||
async function isContractDeployed(address: string) { | ||
try { | ||
const result = await contractsApi.getContractSource({ | ||
contractAddress: address, | ||
contractName: 'flood', | ||
}); | ||
return !!result.source; | ||
} catch (e) { | ||
return false; | ||
} | ||
} | ||
|
||
async function run() { | ||
if (hasSentToFlooders) { | ||
await flood(); | ||
} else { | ||
await bootstrapFlooders(); | ||
} | ||
} | ||
|
||
async function flood() { | ||
const accountFloods = flooders.map(async (flooder, n) => { | ||
// const nonce = await getNonce(flooder.stxAddress, network); | ||
const nonces = accountsApi.getAccountNonces({ | ||
principal: flooder.stxAddress, | ||
}); | ||
const nonce = ((await nonces).last_executed_tx_nonce ?? -1) + 1; | ||
logger.info(`Flooder ${n} has nonce ${nonce.toString()}`); | ||
// return { ...account, nonce }; | ||
let txFloods = new Array(TX_PER_FLOOD).fill(0).map(async (_, i) => { | ||
// const nonce = getFlooderNonce(flooder.stxAddress); | ||
const tx = await makeContractCall({ | ||
contractAddress: floodContractDeployer, | ||
contractName: 'flood', | ||
functionName: 'set-data', | ||
functionArgs: [uintCV(1), uintCV(2), uintCV(3)], | ||
senderKey: flooder.privKey, | ||
nonce: nonce + i, | ||
anchorMode: 'any', | ||
network, | ||
fee: 10000, | ||
}); | ||
await broadcast(tx, flooder.stxAddress); | ||
}); | ||
await Promise.all(txFloods); | ||
}); | ||
await Promise.all(accountFloods); | ||
} | ||
|
||
async function broadcast(tx: StacksTransaction, sender?: string) { | ||
const txType = tx.payload.payloadType; | ||
const label = sender ? accountLabel(sender) : 'Unknown'; | ||
const broadcastResult = await broadcastTransaction(tx, network); | ||
if (broadcastResult.error) { | ||
logger.error({ ...broadcastResult, account: label }, `Error broadcasting ${txType}`); | ||
return false; | ||
} else { | ||
if (label.includes('Flooder')) return; | ||
logger.info(`Broadcast ${txType} from ${label} tx=${broadcastResult.txid}`); | ||
return true; | ||
} | ||
} | ||
|
||
async function waitForNakamoto() { | ||
while (true) { | ||
try { | ||
const poxInfo = await client.getPoxInfo(); | ||
if (poxInfo.current_burnchain_block_height! <= EPOCH_30_START) { | ||
logger.info( | ||
`Nakamoto not activated yet, waiting... (current=${poxInfo.current_burnchain_block_height}), (epoch3=${EPOCH_30_START})` | ||
); | ||
} else { | ||
logger.info( | ||
`Nakamoto activation height reached, ready to submit txs for Nakamoto block production` | ||
); | ||
break; | ||
} | ||
} catch (error) { | ||
if (/(ECONNREFUSED|ENOTFOUND|SyntaxError)/.test(error.cause?.message)) { | ||
logger.info(`Stacks node not ready, waiting...`); | ||
} else { | ||
logger.error('Error getting pox info:', error); | ||
} | ||
} | ||
await new Promise(resolve => setTimeout(resolve, 3000)); | ||
} | ||
} | ||
|
||
function accountLabel(address: string) { | ||
if (address === bootstrapper.stxAddress) { | ||
return 'Bootstrapper'; | ||
} | ||
const flooderIndex = flooders.findIndex(flooder => flooder.stxAddress === address); | ||
if (flooderIndex !== -1) { | ||
return `Flooder #${flooderIndex}`; | ||
} | ||
return `Unknown (${address})`; | ||
} | ||
|
||
async function loop() { | ||
await waitForNakamoto(); | ||
while (true) { | ||
try { | ||
await run(); | ||
} catch (e) { | ||
logger.error(e, 'Error submitting stx-transfer tx:'); | ||
} | ||
await new Promise(resolve => setTimeout(resolve, broadcastInterval * 1000)); | ||
} | ||
} | ||
loop(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
(define-map big-map { | ||
a: uint, | ||
b: uint, | ||
c: uint | ||
} { | ||
a: uint, | ||
b: uint, | ||
c: uint, | ||
}) | ||
|
||
(define-public (set-data (a uint) (b uint) (c uint)) | ||
(begin | ||
(map-get? big-map { a: a, b: b, c: c }) | ||
(map-set big-map { a: a, b: b, c: c } { a: a, b: b, c: c }) | ||
(map-get? big-map { a: a, b: b, c: c }) | ||
(map-set big-map { a: a, b: b, c: c } { a: a, b: b, c: c }) | ||
(map-get? big-map { a: a, b: b, c: c }) | ||
(map-set big-map { a: a, b: b, c: c } { a: a, b: b, c: c }) | ||
(map-get? big-map { a: a, b: b, c: c }) | ||
(map-set big-map { a: a, b: b, c: c } { a: a, b: b, c: c }) | ||
(map-get? big-map { a: a, b: b, c: c }) | ||
(map-set big-map { a: a, b: b, c: c } { a: a, b: b, c: c }) | ||
(map-get? big-map { a: a, b: b, c: c }) | ||
(map-set big-map { a: a, b: b, c: c } { a: a, b: b, c: c }) | ||
(map-get? big-map { a: a, b: b, c: c }) | ||
(map-set big-map { a: a, b: b, c: c } { a: a, b: b, c: c }) | ||
(map-get? big-map { a: a, b: b, c: c }) | ||
(map-set big-map { a: a, b: b, c: c } { a: a, b: b, c: c }) | ||
(ok true) | ||
) | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
STACKS_CORE_RPC_HOST=localhost | ||
STACKS_CORE_RPC_PORT=3999 | ||
NAKAMOTO_BLOCK_INTERVAL=2 | ||
STACKS_30_HEIGHT=131 | ||
ACCOUNT_KEYS=0d2f965b472a82efd5a96e6513c8b9f7edc725d5c96c7d35d6c722cedeb80d1b01,975b251dd7809469ef0c26ec3917971b75c51cd73a022024df4bf3b232cc2dc001,c71700b07d520a8c9731e4d0f095aa6efb91e16e25fb27ce2b72e7b698f8127a01 | ||
NUM_FLOODERS=15 | ||
TX_PER_FLOOD=15 | ||
STACKS_25_HEIGHT=108 | ||
STACKING_KEYS: 6a1a754ba863d7bab14adbbc3f8ebb090af9e871ace621d3e5ab634e1422885e01,b463f0df6c05d2f156393eee73f8016c5372caa0e9e29a901bb7171d90dc4f1401,7036b29cb5e235e5fd9b09ae3e8eec4404e44906814d5d01cbca968a60ed4bfb01 | ||
POX_PREPARE_LENGTH=5 | ||
POX_REWARD_LENGTH=20 | ||
BOOTSTRAPPER_KEY=66b7a77a3e0abc2cddaa51ed38fc4553498e19d3620ef08eb141afcfd0e3f5b501 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters