Skip to content

Instantly share code, notes, and snippets.

@WietseWind
Last active October 10, 2023 13:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save WietseWind/fd6ebd9ffe54b8bef9879640e1a51fa9 to your computer and use it in GitHub Desktop.
Save WietseWind/fd6ebd9ffe54b8bef9879640e1a51fa9 to your computer and use it in GitHub Desktop.
Flood (performance testing) burn & mint (B2M)
// This script mass burns & mass (async) mints using B2M
// The script takes Sequence, etc. into account & auto-recovers
// if Sequence is no longer in sync due to a missed/fallen out tx.
import { derive, utils, signAndSubmit } from "xrpl-accountlib"
import WebSocket from 'ws'
import { XrplClient } from "xrpl-client"
import fetch from "node-fetch"
import ReconnectingWebSocket from "reconnecting-websocket"
import { EventEmitter } from 'node:events'
// import { TxData } from "xrpl-txdata"
process.on('uncaughtException', function ucErr (err) {
// Handle all
console.log('Caught exception: ' + err)
})
const burnInterval = 100 // ms
const mintInterval = 666 // ms
const bus = new EventEmitter()
const lastTxs = []
const { TESTNET, XAHAUTESTNET } = await (await fetch('https://xumm.app/api/v1/platform/rails')).json()
console.log('Obtaining XRPL Testnet account (faucet)')
const faucet = await (await fetch('https://faucet.altnet.rippletest.net/accounts', { method: 'POST' })).json()
console.log(' -->', faucet.account.address)
const account = derive.familySeed(faucet.account.secret)
const nodes = {
testnet: [
new XrplClient('wss://s.altnet.rippletest.net:51233'),
new XrplClient('wss://testnet.xrpl-labs.com'),
],
xahau: [
new XrplClient('wss://xahau-test.net'),
]
}
console.log('Waiting for network connections to be ready')
await Promise.all(Object.keys(nodes).map(k => Promise.race(nodes[k].map(n => n.ready()))))
console.log('Waiting for faucet account funding & obtaining network values...')
await new Promise(resolve => setTimeout(resolve, 4_000)) // Wait till ledger is closed
const [ testnetParams, xahauParams, ] = await Promise.all([
Promise.race(nodes.testnet.map(n => utils.accountAndLedgerSequence(n, account))),
Promise.race(nodes.xahau.map(n => utils.accountAndLedgerSequence(n, account))),
])
console.log('testnetParams', testnetParams)
console.log('xahauParams', xahauParams)
// We're not going to fetch tx results as it's a waste of time, let's burst!
// const txdata = new TxData(nodes.testnet.map(n => n.getState().server.uri), {
// AllowNoFullHistory: true,
// EndpointTimeoutMs: 2_000,
// OverallTimeoutMs: 10_000,
// })
/**
* This is where we burn
*/
const processBurn = async () => {
const testnetTx = {
...testnetParams.txValues,
LastLedgerSequence: undefined,
Sequence: testnetParams.txValues.Sequence++,
TransactionType: "AccountSet",
Fee: String(10_000),
OperationLimit: xahauParams.txValues.NetworkID,
NetworkID: undefined, // Testnet has a NetworkID < 1024, so none should be provided
}
const testnetSubmitted = await Promise.race(nodes.testnet.map(n => signAndSubmit(testnetTx, n, account)))
// console.log(testnetTx, testnetSubmitted)
console.log('[', testnetParams.txValues.Sequence, ']', 'Submitted burn to testnet, tx:', TESTNET.explorers[2].url_tx + testnetSubmitted.tx_id)
}
setInterval(() => processBurn(), burnInterval)
/**
* This is where we mint
*/
const toSubmit = []
const waterfallPromises = (promiseFunctions) => {
return promiseFunctions.reduce((accumulator, currentFunction) => {
return accumulator.then(resultArray => currentFunction().then(currentResult => [...resultArray, currentResult]))
}, Promise.resolve([]))
}
// Magic, waterfalls, waits for prev. waterfall to be ready
// Also awaits individual tx in case it triggers Sequence re-fetching
const processMints = () => {
setTimeout(async () => {
// Submit Mints
console.log('Processing mints', toSubmit.length)
const current = [ ...toSubmit ].sort((a, b) => a.sequence - b.sequence)
toSubmit.length = 0
await waterfallPromises(current.map(c => c.fn))
bus.emit('done-minting')
}, mintInterval)
}
// Use event bus to prevent nested setTimeout memleaks
bus.on('done-minting', processMints)
bus.emit('done-minting')
/**
* Subscribe on xPOPs & process & add to Queue
*/
const fetchSequence = async () => {
// This was the first tx, now fetch the actual account sequence
console.log('>>>> ⚠️ ⌛ ⚠️ Fetching new sequence (newly activated account or Sequence out of sync...)')
await new Promise(resolve => setTimeout(resolve, 4_000)) // Wait till ledger is closed
Object.assign(xahauParams, await Promise.race(nodes.xahau.map(async n => {
try {
return utils.accountAndLedgerSequence(n, account)
} catch (e) {
console.log('Err getting account sequence')
}
})))
console.log('<<<< 🚀 🚀 🚀 New sequence', xahauParams.txValues.Sequence)
}
// Fetch xPOP reporters (websockets pushing XPOPs as they come in)
const urls = (await (await fetch('https://xrpl.ws-stats.com/xpop/list?json=true')).json()).endpoints
.filter(r => !!r.urlverified)
.map(r => r.bestguess.replace(/^http/, 'ws'))
.map(r => `${r}/blob/${testnetParams.txValues.Account}`)
urls.map(xpopWssUrl => {
const rws = new ReconnectingWebSocket(xpopWssUrl, [], { WebSocket, })
rws.addEventListener('error', e => console.error(e.message))
rws.addEventListener('message', async message => {
const json = JSON.parse(message.data)
// No double tx
if (lastTxs.indexOf(json.origin.tx) < 0) {
lastTxs.unshift(json.origin.tx)
lastTxs.length = 500
let processEl = {
originTx: json.origin.tx,
async fn () {
try {
console.log(`<==[ 📮 XPOP(${json.xpop.blob.length}) ] ${json.xpop.binary}`)
const xahauTx = {
...xahauParams.txValues,
TransactionType: "Import",
Sequence: xahauParams.txValues.Sequence++,
Blob: json.xpop.blob,
Memos: [ { Memo: { MemoData: json.origin.tx, } } ], // Reference to the burn
Fee: '0',
}
// console.log(xahauTx, xahauTx.Sequence, json.sequence)
await Promise.race(nodes.xahau.map(n => signAndSubmit(xahauTx, n, account))).then(async submitted => {
const icon = submitted.response.engine_result === 'tesSUCCESS'
? '✅'
: '🚨'
console.log(` ✉️ ${icon} B2M submitted @ ${json.sequence} = ${submitted.response.engine_result_message}`)
console.log(` -> ${XAHAUTESTNET.explorers[0].url_tx + submitted?.tx_id}`)
console.log(` [ MINTS: ${json.origin.tx} ]`)
// Possible if sent on the edge of a closing ledger
// Didn't make it in, next ones already there, this one has to be added again
if (submitted.response.engine_result_message.match(/Missing.+prior/)) {
await fetchSequence()
// Retry, re-inject in the queue
toSubmit.push(processEl)
}
})
if (xahauTx.Sequence === 0) {
await fetchSequence()
}
} catch (e) {
console.log('Error', e.message)
}
},
...json
}
toSubmit.push(processEl)
}
})
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment