diff --git a/README.md b/README.md index 93af08c..694b68c 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,26 @@ -[![No Maintenance Intended](http://unmaintained.tech/badge.svg)](http://unmaintained.tech/) - -# NO LONGER MAINTAINED - -Since the launch of Kaia Blockchain this repository has been parked in favour of the new open-source projects in [Kaia's Github](https://github.com/kaia-blockchain). Contributors have now moved there continuing with massive open-source contributions to our blockchain ecosystem. A big thank you to everyone who has contributed to this repository. For more information about Klaytn's chain merge with Finschia blockchain please refer to the launching of Kaia blockchain - [kaia.io](http://kaia.io/). - --------- - - # Transaction Latency Measurement ### 1) Introduction -Using this repo, you can measure transaction latency on differenct blockchain platforms(ex: Solana mainnet-beta, Avalanche C-chain, Polygon PoS, and Klaytn). Transaction latency is measured by sending a simple value transfer transaction through public RPC url provided by each chain. Each subdirectory is for each different blockchain platform. Codes for other chains will be updated. +Using this repo, you can measure transaction latency on differenct blockchain platforms(ex: Solana mainnet-beta, Avalanche C-chain, Polygon PoS, and Kaia). Transaction latency is measured by sending a simple value transfer transaction through public RPC url provided by each chain. Each subdirectory is for each different blockchain platform. Codes for other chains will be updated. ### 2) Prerequisite This project uses NodeJS v16.14.2. ### 3) Getting Started 1. Open terminal -2. Clone the repo by running `https://github.com/klaytn/tx-latency-measurement.git` +2. Clone the repo by running `https://github.com/kaiachain/tx-latency-measurement.git` 3. `cd tx-latency-measurement/{BlockchainName}-tx-latency-measurement` by selecting which blockchain you want to measure. 4. Run `npm install` to install node packages. 5. Copy and paste `.env.template` file. Then rename it to `.env` and update variables with your Private key, url of blockchain explorer, and public rpc url. You should also decide whether to upload to GCS/S3, and provide appropriate credentials. 6. Run `node sendtx_{BlockchainName}.js`. -### 4) Simple Test with Testnet (Klaytn) +### 4) Simple Test with Testnet (Kaia) 1. Open terminal -2. Clone the repo by running `https://github.com/klaytn/tx-latency-measurement.git` +2. Clone the repo by running `https://github.com/kaiachain/tx-latency-measurement.git` 3. Run `npm install` to install node packages. ``` -cd tx-latency-measurement/klaytn-tx-latency-measurement +cd tx-latency-measurement/kaia-tx-latency-measurement npm install ``` 3. Copy and paste `.env.template` file. Then rename it to `.env`. @@ -39,19 +30,19 @@ cp .env.template .env 4. Update `.env` and make sure PRIVATE_KEY and S3_BUCKET is empty as below: ``` PRIVATE_KEY= -CAVER_URL=https://public-node-api.klaytnapi.com/v1/baobab +CAVER_URL=https://public-en.kairos.node.kaia.io S3_BUCKET= ``` -5. Run `node sendtx_klaytn.js`. Then the output will give you new Private Key and Address. +5. Run `node sendtx_kaia.js`. Then the output will give you new Private Key and Address. ```shell starting tx latency measurement... start time = 1661338618926 Private key is not defined. Use this new private key({NEW_PRIVATE_KEY}). -Get test KLAY from the faucet: https://baobab.wallet.klaytn.foundation/faucet -Your Klaytn address = {NEW_ADDRESS} +Get test KAIA from the faucet: https://baobab.wallet.klaytn.foundation/faucet +Your Kaia address = {NEW_ADDRESS} ``` -- With `NEW_ADDRESS`, get test KLAY from faucet page. +- With `NEW_ADDRESS`, get test KAIA from faucet page. - Update PRIVATE_KEY in .env file with this `NEW_PRIVATE_KEY`. -6. Run `node sendtx_klaytn.js`. You can see the result as below: +6. Run `node sendtx_kaia.js`. You can see the result as below: ``` starting tx latency measurement... start time = 1661339036754 failed to s3.upload! Printing instead! undefined bucket name @@ -64,39 +55,44 @@ failed to s3.upload! Printing instead! undefined bucket name 2. Build a docker image in a folder you would like to measure. ```bash - > docker build -t klaytn-tx-latency-measurement:latest . + > docker build -t kaia-tx-latency-measurement:latest . ``` 3. Run a container out of the image ```bash - > docker run klaytn-tx-latency-measurement:latest + > docker run kaia-tx-latency-measurement:latest ``` *Note: You need to provide credentials JSON inside a directory if you wish to upload to GCS* ### 6) List of Blockchain Platforms (unchecked: to be updated) -- [x] Klaytn +- [x] Kaia - [x] Polygon PoS - [x] Avalanche C-chain - [x] Solana - [x] Near Protocol -- [x] EOS +- [] EOS - [x] Fantom - [x] Polkadot - [ ] Cosmos - [x] BNB -- [x] Hedera -- [x] Elrond +- [] Hedera +- [] Elrond - [x] Harmony +- [x] Aptos +- [x] Arbitrium +- [x] Sui +- [x] Optimism + ### 7) When you'd like to contribute this repository (ex: to add another chain) 1. Please find out ways to collect data like other chains in this repo. You might be able to use javascript sdk for other chains. 2. What should be included in your code - 1. Use same structure(ex: [sendtx_klaytn.js](https://github.com/klaytn/tx-latency-measurement/blob/dev/klaytn-tx-latency-measurement/sendtx_klaytn.js) & [.env.template](https://github.com/klaytn/tx-latency-measurement/blob/dev/klaytn-tx-latency-measurement/.env.template)) and functions(ex: uploadToS3, uploadToGCS, uploadChoice, makeParquetFile, loadConfig, sendSlackMsg in [sendtx_klaytn.js](https://github.com/klaytn/tx-latency-measurement/blob/dev/klaytn-tx-latency-measurement/sendtx_klaytn.js)) + 1. Use same structure(ex: [sendtx_kaia.js](https://github.com/kaiachain/tx-latency-measurement/blob/dev/kaia-tx-latency-measurement/sendtx_kaia.js) & [.env.template](https://github.com/kaiachain/tx-latency-measurement/blob/dev/kaia-tx-latency-measurement/.env.template)) and functions(ex: uploadToS3, uploadToGCS, uploadChoice, makeParquetFile, loadConfig, sendSlackMsg in [sendtx_kaia.js](https://github.com/kaiachain/tx-latency-measurement/blob/dev/kaia-tx-latency-measurement/sendtx_kaia.js)) 2. In sendTx function, check if balance of the account is enough to send transaction and set `chainId`. 3. Measure pingtime using simple rpc api (like getBlockNumber()) - 4. Measure `resourceUsedOfLatestBlock`‎ & `numOfTxInLatestBlock`‎ from the latest block info. + 4. Measure `resourceUsedOfLatestBlock` & `numOfTxInLatestBlock` from the latest block info. 5. Configure the transaction and sign it with private key. 6. Measure the time it took for the signed transaction to be confirmed and receive a receipt. Enter the `txHash`, `start time`, `end time`, and `latency`(time in between) into the data. 7. Calculate `txFeeInUSD` using CoinGecko API, then record `txFee`(in Native Coin) and `txFeeInUSD` into data. diff --git a/aptos-tx-latency-measurement/Dockerfile b/aptos-tx-latency-measurement/Dockerfile index 4462e01..4bfe525 100644 --- a/aptos-tx-latency-measurement/Dockerfile +++ b/aptos-tx-latency-measurement/Dockerfile @@ -1,6 +1,6 @@ FROM node:16 -WORKDIR /usr/src/klaytn +WORKDIR /usr/src/aptos COPY . . diff --git a/arbitrium-tx-latency-measurement/Dockerfile b/arbitrium-tx-latency-measurement/Dockerfile index f478e8d..96147d5 100644 --- a/arbitrium-tx-latency-measurement/Dockerfile +++ b/arbitrium-tx-latency-measurement/Dockerfile @@ -1,6 +1,6 @@ FROM node:18 -WORKDIR /usr/src/klaytn +WORKDIR /usr/src/arbitrium COPY . . diff --git a/elrond-tx-latency-measurement/sendtx_elrond.js b/elrond-tx-latency-measurement/sendtx_elrond.js index 721cfaa..79ea9c4 100644 --- a/elrond-tx-latency-measurement/sendtx_elrond.js +++ b/elrond-tx-latency-measurement/sendtx_elrond.js @@ -1,24 +1,20 @@ -// Elrond transaction latency measurement. -// API reference: https://api.elrond.com/ -// Reference for signing transaction: https://github.com/ElrondNetwork/elrond-sdk-erdjs/blob/a9b33e90ba7df70e11898cc0b8149966a0a61f29/src/transaction.local.net.spec.ts - -const { ApiNetworkProvider } = require("@elrondnetwork/erdjs-network-providers"); -const { TokenPayment, TransactionWatcher } = require("@elrondnetwork/erdjs/out"); -const { Transaction, Address } = require("@elrondnetwork/erdjs"); -const { UserSigner, UserSecretKey } = require("@elrondnetwork/erdjs-walletcore"); -const core = require("@elrondnetwork/elrond-core-js"); -const fs = require('fs'); -const AWS = require('aws-sdk'); -const parquet = require('parquetjs-lite'); +// BNB transaction latency measurement. +// Web3 connection reference: https://docs.binance.org/smart-chain/developer/create-wallet.html#connect-to-bsc-network + +const Web3 = require('web3'); +const fs = require('fs') +const AWS = require('aws-sdk') +const parquet = require('parquetjs-lite') const moment = require('moment'); -const axios = require("axios"); +const axios = require('axios'); const CoinGecko = require('coingecko-api'); const CoinGeckoClient = new CoinGecko(); const {Storage} = require('@google-cloud/storage'); require('dotenv').config(); -const networkProvider = new ApiNetworkProvider(process.env.PUBLIC_API_URL, {timeout : 5000}); -var signer; +const web3 = new Web3(process.env.PUBLIC_RPC_URL_WEB3); +const privateKey = process.env.SIGNER_PRIVATE_KEY; +var PrevNonce = null; async function makeParquetFile(data) { var schema = new parquet.ParquetSchema({ @@ -35,23 +31,23 @@ async function makeParquetFile(data) { numOfTxInLatestBlock:{type:'INT64'}, pingTime:{type:'INT64'} }) - + var d = new Date() //20220101_032921 var datestring = moment().format('YYYYMMDD_HHmmss') - + var filename = `${datestring}_${data.chainId}.parquet` - + // create new ParquetWriter that writes to 'filename' var writer = await parquet.ParquetWriter.openFile(schema, filename); - + await writer.appendRow(data) - + await writer.close() - + return filename; } - + async function sendSlackMsg(msg) { await axios.post(process.env.SLACK_API_URL, { 'channel':process.env.SLACK_CHANNEL, @@ -64,7 +60,7 @@ async function sendSlackMsg(msg) { } }) } - + async function uploadToS3(data){ if(process.env.S3_BUCKET === "") { throw "undefined bucket name" @@ -79,8 +75,8 @@ async function uploadToS3(data){ 'ContentType':'application/octet-stream' } await s3.upload(param).promise() - - fs.unlinkSync(filename) + + fs.unlinkSync(filename) } async function uploadToGCS(data) { @@ -94,7 +90,7 @@ async function uploadToGCS(data) { }); const filename = await makeParquetFile(data) - const destFileName = `tx-latency-measurement/elrond/${filename}`; + const destFileName = `tx-latency-measurement/bnb/${filename}`; async function uploadFile() { const options = { @@ -127,145 +123,124 @@ async function sendTx(){ txhash: '', startTime: 0, endTime: 0, - chainId: process.env.CHAIN_ID, + chainId: 0, latency:0, error:'', - txFee: 0.0, - txFeeInUSD: 0.0, + txFee: 0.0, + txFeeInUSD: 0.0, resourceUsedOfLatestBlock: 0, numOfTxInLatestBlock: 0, - pingTime:0 + pingTime:0 } try{ - const address = signer.getAddress() + const signer = web3.eth.accounts.privateKeyToAccount( + privateKey + ); + const balance = (await web3.eth.getBalance(signer.address))*(10**(-18)) - const account = await networkProvider.getAccount(address); - const balance = account.balance.toNumber()*(10**(-18)) - - if(balance < parseFloat(process.env.BALANCE_ALERT_CONDITION_IN_EGLD)) + if(balance < parseFloat(process.env.BALANCE_ALERT_CONDITION_IN_BNB)) { const now = new Date(); - await sendSlackMsg(`${now}, Current balance of <${process.env.SCOPE_URL}/accounts/${address.toString()}|${address.toString()}> is less than ${process.env.BALANCE_ALERT_CONDITION_IN_EGLD} EGLD! balance=${balance} EGLD`) + await sendSlackMsg(`${now}, Current balance of <${process.env.SCOPE_URL}/address/${signer.address}|${signer.address}> is less than ${process.env.BALANCE_ALERT_CONDITION_IN_BNB} BNB! balance=${balance} BNB`) } - const networkConfig = await networkProvider.getNetworkConfig(); - + await web3.eth.net.getId().then((id)=>{ + data.chainId = id + }) const startGetBlock = new Date().getTime() - const networkStatus = await networkProvider.getNetworkStatus(); + const latestBlockNumber = await web3.eth.getBlockNumber(); const endGetBlock = new Date().getTime() data.pingTime = endGetBlock - startGetBlock - - // blocks from different shard(currently 3 shards) are included in metachain block (hyperblock) - var gasUsed = 0.0; - var txCount = 0; - const metachainShardId = 4294967295; - let response = await axios.get(process.env.PUBLIC_API_URL + `/blocks?nonce=${networkStatus.HighestFinalNonce}&shard=${metachainShardId}`,{ - headers:{ - "Content-Type": "application/json" - } - }); - - //Calc reference: https://explorer.elrond.com/blocks/562f5a24b79f2b383881c1e42f2759d90ad47458187c4af92e03af8d45fcad49 - gasUsed += response.data[0].gasConsumed - response.data[0].gasRefunded - response.data[0].gasPenalized; - txCount += response.data[0].txCount; - - let hyperblockInfo = await axios.get(process.env.PUBLIC_API_URL + `/blocks/${response.data[0].hash}`,{ - headers:{ - "Content-Type": "application/json" - } - }); - - for await(blockhash of hyperblockInfo.data.notarizedBlocksHashes) + await web3.eth.getBlock(latestBlockNumber).then((blockInfo)=>{ + data.resourceUsedOfLatestBlock = blockInfo.gasUsed + data.numOfTxInLatestBlock = blockInfo.transactions.length + }) + + const gasPrice = await web3.eth.getGasPrice(); //in Wei + const latestNonce = await web3.eth.getTransactionCount(signer.address); + if (latestNonce == PrevNonce) { - let shardBlockInfo = await axios.get(process.env.PUBLIC_API_URL + `/blocks/${blockhash}`,{ - headers:{ - "Content-Type": "application/json" - } - }); - gasUsed += (Number(shardBlockInfo.data.gasConsumed) - Number(shardBlockInfo.data.gasRefunded) - Number(shardBlockInfo.data.gasPenalized)) - txCount += shardBlockInfo.data.txCount + // console.log(`Nonce ${latestNonce} = ${PrevNonce}`) + return; } - data.resourceUsedOfLatestBlock = gasUsed - data.numOfTxInLatestBlock = txCount - - // Create Transaction - let tx = new Transaction({ - sender: address, - receiver: address, - gasLimit : 50000, - value: TokenPayment.egldFromAmount(0), - chainID: networkConfig.ChainID, - nonce: account.nonce + + const rawTx = { + from: signer.address, + to: signer.address, + value: Web3.utils.toHex(Web3.utils.toWei("0", 'ether')), + gasLimit: Web3.utils.toHex(21000), + gasPrice: Web3.utils.toHex(gasPrice), + nonce: Web3.utils.toHex(latestNonce) + } + + var RLPEncodedTx; + await web3.eth.accounts.signTransaction(rawTx, privateKey) + .then((result)=> + { + RLPEncodedTx = result.rawTransaction // RLP encoded transaction & already HEX value + data.txhash = result.transactionHash // the transaction hash of the RLP encoded transaction. }) - - // Sign Transaction - await signer.sign(tx); - - let watcher = new TransactionWatcher(networkProvider); + + const originalPrevNonce = PrevNonce + // Send signed transaction (ref: https://web3js.readthedocs.io/en/v1.2.11/callbacks-promises-events.html#promievent) const start = new Date().getTime() data.startTime = start - const txHash = await networkProvider.sendTransaction(tx); - - // Wait for transaction completion (Ref: https://docs.elrond.com/sdk-and-tools/erdjs/erdjs-cookbook/) - await watcher.awaitCompleted(tx); - const end = new Date().getTime() - data.endTime = end - data.latency = end-start - data.txhash = txHash - - // Get gasPrice and gasUsed of transaction - let txDetails = await axios.get(process.env.PUBLIC_API_URL + `/transactions/${txHash}`, { - headers:{ - "Content-Type": "application/json" - } + await web3.eth + .sendSignedTransaction(RLPEncodedTx) + .on('sent', function(){ + PrevNonce = latestNonce }) - data.txFee = txDetails.data.gasPrice * txDetails.data.gasUsed * (10**(-18)) - - // Calculate Transaction Fee and Get Tx Fee in USD - var EGLDtoUSD; + .on('error', function(err){ + PrevNonce = originalPrevNonce + }) + .then(function(receipt){ + // will be fired once the receipt is mined + data.txhash = receipt.transactionHash + const end = new Date().getTime() + data.endTime = end + data.latency = end-start + const gasPriceInBNB = web3.utils.fromWei(gasPrice) + data.txFee = receipt.gasUsed * gasPriceInBNB + }); - await axios.get(`https://api.coingecko.com/api/v3/simple/price?ids=elrond-erd-2&vs_currencies=usd&x_cg_demo_api_key=${process.env.COIN_GECKO_API_KEY}`) + // Calculate Transaction Fee and Get Tx Fee in USD + var BNBtoUSD; + + await axios.get(`https://api.coingecko.com/api/v3/simple/price?ids=binancecoin&vs_currencies=usd&x_cg_demo_api_key=${process.env.COIN_GECKO_API_KEY}`) .then(response => { - EGLDtoUSD = response.data["elrond-erd-2"].usd; + BNBtoUSD = response.data["binancecoin"].usd; }); - data.txFeeInUSD = data.txFee * EGLDtoUSD + data.txFeeInUSD = data.txFee * BNBtoUSD // console.log(`${data.executedAt},${data.chainId},${data.txhash},${data.startTime},${data.endTime},${data.latency},${data.txFee},${data.txFeeInUSD},${data.resourceUsedOfLatestBlock},${data.numOfTxInLatestBlock},${data.pingTime},${data.error}`) try{ - await uploadToS3(data) + await uploadChoice(data) } catch(err){ - await sendSlackMsg(`failed to upload elrond, ${err.toString()}`); - console.log('failed to s3.upload! Printing instead!', err.toString()) + await sendSlackMsg(`failed to upload bnb, ${err.toString()}`); + console.log(`failed to ${process.env.UPLOAD_METHOD === 'AWS'? 's3': 'gcs'}.upload!! Printing instead!`, err.toString()) console.log(JSON.stringify(data)) } } catch(err){ const now = new Date(); - await sendSlackMsg(`${now}, failed to execute elrond, ${err.toString()}, ${err.stack}`); + await sendSlackMsg(`${now}, failed to execute bnb, ${err.toString()}, ${err.stack}`); console.log("failed to execute.", err.toString(), err.stack) data.error = err.toString() console.log(`${data.executedAt},${data.chainId},${data.txhash},${data.startTime},${data.endTime},${data.latency},${data.txFee},${data.txFeeInUSD},${data.resourceUsedOfLatestBlock},${data.numOfTxInLatestBlock},${data.pingTime},${data.error}`) } + } async function main(){ const start = new Date().getTime() console.log(`starting tx latency measurement... start time = ${start}`) - if (process.env.SIGNER_SECRET_KEY === ""){ - let account = new core.account(); - let mnemonic = account.generateMnemonic(); - let privateKeyHex = account.privateKeyFromMnemonic(mnemonic, false, "0", ""); - let privateKey = Buffer.from(privateKeyHex, "hex"); - account.generateKeyFileFromPrivateKey(privateKey, 'password'); - const address = new UserSigner(UserSecretKey.fromString(privateKeyHex)).getAddress().bech32() - console.log(`Private Key is not defined. Use this new private key (${privateKeyHex})`) - console.log(`Get test dEGLD from the faucet: https://r3d4.fr/faucet`) - console.log(`Your Elrond address = ${address}`) - console.log(`OR you can create wallet account from Devnet Wallet: https://devnet-wallet.elrond.com/`) - return; + if(privateKey === "") { + const account = web3.eth.accounts.create(web3.utils.randomHex(32)); + console.log(`Private key is not defined. Use this new private key(${account.privateKey}).`) + console.log(`Get test BNB from the faucet: https://testnet.binance.org/faucet-smart`) + console.log(`Your BNB Smart Chain address = ${account.address}`) + return } - - signer = new UserSigner(UserSecretKey.fromString(process.env.SIGNER_SECRET_KEY)) // run sendTx every SEND_TX_INTERVAL const interval = eval(process.env.SEND_TX_INTERVAL) diff --git a/klaytn-tx-latency-measurement/.dockerignore b/kaia-tx-latency-measurement/.dockerignore similarity index 100% rename from klaytn-tx-latency-measurement/.dockerignore rename to kaia-tx-latency-measurement/.dockerignore diff --git a/klaytn-tx-latency-measurement/.env.template b/kaia-tx-latency-measurement/.env.template similarity index 85% rename from klaytn-tx-latency-measurement/.env.template rename to kaia-tx-latency-measurement/.env.template index 25899f6..8ea9b41 100644 --- a/klaytn-tx-latency-measurement/.env.template +++ b/kaia-tx-latency-measurement/.env.template @@ -1,5 +1,5 @@ PRIVATE_KEY= -CAVER_URL=https://public-node-api.klaytnapi.com/v1/baobab +CAVER_URL=https://public-en.kairos.node.kaia.io UPLOAD_METHOD=AWS S3_BUCKET= GCP_PROJECT_ID= @@ -8,7 +8,7 @@ GCP_BUCKET= SLACK_CHANNEL=SLACK_CHANNEL SLACK_API_URL=https://slack.com/api/chat.postMessage SLACK_AUTH=xoxb-SLACK_AUTH_TOKEN -BALANCE_ALERT_CONDITION_IN_KLAY=10 +BALANCE_ALERT_CONDITION_IN_KAIA=10 SCOPE_URL=https://scope.klaytn.com AWS_PROFILE=default diff --git a/kaia-tx-latency-measurement/Dockerfile b/kaia-tx-latency-measurement/Dockerfile new file mode 100644 index 0000000..2f73f5a --- /dev/null +++ b/kaia-tx-latency-measurement/Dockerfile @@ -0,0 +1,9 @@ +FROM node:16 + +WORKDIR /usr/src/kaia + +COPY . . + +RUN npm install + +CMD [ "node", "sendtx_kaia.js" ] diff --git a/klaytn-tx-latency-measurement/package-lock.json b/kaia-tx-latency-measurement/package-lock.json similarity index 100% rename from klaytn-tx-latency-measurement/package-lock.json rename to kaia-tx-latency-measurement/package-lock.json diff --git a/klaytn-tx-latency-measurement/package.json b/kaia-tx-latency-measurement/package.json similarity index 100% rename from klaytn-tx-latency-measurement/package.json rename to kaia-tx-latency-measurement/package.json diff --git a/klaytn-tx-latency-measurement/sendtx_klaytn.js b/kaia-tx-latency-measurement/sendtx_kaia.js similarity index 90% rename from klaytn-tx-latency-measurement/sendtx_klaytn.js rename to kaia-tx-latency-measurement/sendtx_kaia.js index 3297140..eaf4b13 100644 --- a/klaytn-tx-latency-measurement/sendtx_klaytn.js +++ b/kaia-tx-latency-measurement/sendtx_kaia.js @@ -1,4 +1,4 @@ -// Klaytn transaction latency measurement. +// Kaia transaction latency measurement. // Reference of Sending Transaction using CaverJS: https://docs.kaikas.io/02_api_reference/02_caver_methods const fs = require('fs') const Caver = require('caver-js') @@ -42,7 +42,7 @@ async function uploadToGCS(data) { }); const filename = await makeParquetFile(data) - const destFileName = `tx-latency-measurement/klaytn/${filename}`; + const destFileName = `tx-latency-measurement/kaia/${filename}`; async function uploadFile() { const options = { @@ -127,10 +127,10 @@ async function sendSlackMsg(msg) { async function checkBalance(addr) { const caver = new Caver(process.env.CAVER_URL) const balance = await caver.rpc.klay.getBalance(addr) - const balanceInKLAY = caver.utils.convertFromPeb(balance, 'KLAY') + const balanceInKAIA = caver.utils.convertFromPeb(balance, 'KLAY') const now = new Date(); - if(parseFloat(balanceInKLAY) < parseFloat(process.env.BALANCE_ALERT_CONDITION_IN_KLAY)) { - await sendSlackMsg(`${now}, Current balance of <${process.env.SCOPE_URL}/account/${addr}|${addr}> is less than ${process.env.BALANCE_ALERT_CONDITION_IN_KLAY} KLAY! balance=${balanceInKLAY}`) + if(parseFloat(balanceInKAIA) < parseFloat(process.env.BALANCE_ALERT_CONDITION_IN_KAIA)) { + await sendSlackMsg(`${now}, Current balance of <${process.env.SCOPE_URL}/account/${addr}|${addr}> is less than ${process.env.BALANCE_ALERT_CONDITION_IN_KAIA} KAIA! balance=${balanceInKAIA}`) } } @@ -186,7 +186,7 @@ async function sendTx() { const start = new Date().getTime() data.startTime = start - // Send transaction to the Klaytn blockchain platform (Klaytn) + // Send transaction to the Kaia blockchain platform (Kaia) const receipt = await caver.rpc.klay.sendRawTransaction(signed) const end = new Date().getTime() @@ -194,26 +194,26 @@ async function sendTx() { data.endTime = end data.latency = end-start - var KLAYtoUSD; + var KAIAtoUSD; await axios.get(`https://api.coingecko.com/api/v3/simple/price?ids=klay-token&vs_currencies=usd&x_cg_demo_api_key=${process.env.COIN_GECKO_API_KEY}`) .then(response => { - KLAYtoUSD = response.data["klay-token"].usd; + KAIAtoUSD = response.data["klay-token"].usd; }); data.txFee = caver.utils.convertFromPeb(receipt.gasPrice, 'KLAY') * receipt.gasUsed - data.txFeeInUSD = KLAYtoUSD * data.txFee + data.txFeeInUSD = KAIAtoUSD * data.txFee // console.log(`${data.executedAt},${data.chainId},${data.txhash},${data.startTime},${data.endTime},${data.latency},${data.txFee},${data.txFeeInUSD},${data.resourceUsedOfLatestBlock},${data.numOfTxInLatestBlock},${data.pingTime},${data.error}`) try{ await uploadChoice(data) } catch(err){ - await sendSlackMsg(`failed to upload Klaytn, ${err.toString()}`); + await sendSlackMsg(`failed to upload Kaia, ${err.toString()}`); console.log(`failed to ${process.env.UPLOAD_METHOD === 'AWS'? 's3': 'gcs'}.upload!! Printing instead!`, err.toString()) console.log(JSON.stringify(data)) } } catch (err) { const now = new Date(); - await sendSlackMsg(`${now}, failed to execute Klaytn, ${err.toString()}, ${err.stack}`); + await sendSlackMsg(`${now}, failed to execute Kaia, ${err.toString()}, ${err.stack}`); console.log("failed to execute.", err.toString(), err.stack) data.error = err.toString() console.log(`${data.executedAt},${data.chainId},${data.txhash},${data.startTime},${data.endTime},${data.latency},${data.txFee},${data.txFeeInUSD},${data.resourceUsedOfLatestBlock},${data.numOfTxInLatestBlock},${data.pingTime},${data.error}`) @@ -228,8 +228,8 @@ async function main() { const caver = new Caver(process.env.CAVER_URL) const keyring = caver.wallet.keyring.generate() console.log(`Private key is not defined. Use this new private key(${keyring.key.privateKey}).`) - console.log(`Get test KLAY from the faucet: https://baobab.wallet.klaytn.foundation/faucet`) - console.log(`Your Klaytn address = ${keyring.address}`) + console.log(`Get test KAIA from the faucet: https://baobab.wallet.klaytn.foundation/faucet`) + console.log(`Your Kaia address = ${keyring.address}`) return } diff --git a/klaytn-tx-latency-measurement/Dockerfile b/klaytn-tx-latency-measurement/Dockerfile deleted file mode 100644 index c288461..0000000 --- a/klaytn-tx-latency-measurement/Dockerfile +++ /dev/null @@ -1,9 +0,0 @@ -FROM node:16 - -WORKDIR /usr/src/klaytn - -COPY . . - -RUN npm install - -CMD [ "node", "sendtx_klaytn.js" ] diff --git a/optimism-tx-latency-measurement/Dockerfile b/optimism-tx-latency-measurement/Dockerfile index 5595fb0..3e9cb91 100644 --- a/optimism-tx-latency-measurement/Dockerfile +++ b/optimism-tx-latency-measurement/Dockerfile @@ -1,6 +1,6 @@ FROM node:18 -WORKDIR /usr/src/klaytn +WORKDIR /usr/src/optimism COPY . . diff --git a/polygon-tx-latency-measurement/sendtx_polygon.js b/polygon-tx-latency-measurement/sendtx_polygon.js index e453f11..8a7ef45 100644 --- a/polygon-tx-latency-measurement/sendtx_polygon.js +++ b/polygon-tx-latency-measurement/sendtx_polygon.js @@ -1,13 +1,13 @@ // Polygon PoS transaction latency measurement. // Reference of Sending Transaction using Javascript: https://docs.polygon.technology/docs/develop/eip1559-transactions/how-to-send-eip1559-transactions/ -const {Web3} = require('web3') +const { Web3 } = require('web3') const fs = require('fs') const AWS = require('aws-sdk') const parquet = require('parquetjs-lite') const moment = require('moment'); const axios = require('axios'); const CoinGecko = require('coingecko-api'); -const {Storage} = require('@google-cloud/storage'); +const { Storage } = require('@google-cloud/storage'); require("dotenv").config(); let rpc = process.env.PUBLIC_RPC_URL; @@ -20,18 +20,18 @@ var PrevNonce = null; async function makeParquetFile(data) { var schema = new parquet.ParquetSchema({ - executedAt:{type:'TIMESTAMP_MILLIS'}, - txhash:{type:'UTF8'}, - startTime:{type:'TIMESTAMP_MILLIS'}, - endTime:{type:'TIMESTAMP_MILLIS'}, - chainId:{type:'INT64'}, - latency:{type:'INT64'}, - error:{type:'UTF8'}, - txFee:{type:'DOUBLE'}, - txFeeInUSD:{type:'DOUBLE'}, - resourceUsedOfLatestBlock:{type:'INT64'}, - numOfTxInLatestBlock:{type:'INT64'}, - pingTime:{type:'INT64'} + executedAt: { type: 'TIMESTAMP_MILLIS' }, + txhash: { type: 'UTF8' }, + startTime: { type: 'TIMESTAMP_MILLIS' }, + endTime: { type: 'TIMESTAMP_MILLIS' }, + chainId: { type: 'INT64' }, + latency: { type: 'INT64' }, + error: { type: 'UTF8' }, + txFee: { type: 'DOUBLE' }, + txFeeInUSD: { type: 'DOUBLE' }, + resourceUsedOfLatestBlock: { type: 'INT64' }, + numOfTxInLatestBlock: { type: 'INT64' }, + pingTime: { type: 'INT64' } }) var d = new Date() @@ -52,19 +52,19 @@ async function makeParquetFile(data) { async function sendSlackMsg(msg) { await axios.post(process.env.SLACK_API_URL, { - 'channel':process.env.SLACK_CHANNEL, - 'mrkdown':true, - 'text':msg + 'channel': process.env.SLACK_CHANNEL, + 'mrkdown': true, + 'text': msg }, { - headers: { - 'Content-type':'application/json', - 'Authorization':`Bearer ${process.env.SLACK_AUTH}` - } + headers: { + 'Content-type': 'application/json', + 'Authorization': `Bearer ${process.env.SLACK_AUTH}` + } }) } -async function uploadToS3(data){ - if(process.env.S3_BUCKET === "") { +async function uploadToS3(data) { + if (process.env.S3_BUCKET === "") { throw "undefined bucket name" } @@ -72,10 +72,10 @@ async function uploadToS3(data){ const filename = await makeParquetFile(data) const param = { - 'Bucket':process.env.S3_BUCKET, - 'Key':filename, - 'Body':fs.createReadStream(filename), - 'ContentType':'application/octet-stream' + 'Bucket': process.env.S3_BUCKET, + 'Key': filename, + 'Body': fs.createReadStream(filename), + 'ContentType': 'application/octet-stream' } await s3.upload(param).promise() @@ -84,75 +84,73 @@ async function uploadToS3(data){ } async function uploadToGCS(data) { - if(process.env.GCP_PROJECT_ID === "" || process.env.GCP_KEY_FILE_PATH === "" || process.env.GCP_BUCKET === "") { - throw "undefined parameters" - } + if (process.env.GCP_PROJECT_ID === "" || process.env.GCP_KEY_FILE_PATH === "" || process.env.GCP_BUCKET === "") { + throw "undefined parameters" + } - const storage = new Storage({ - projectId: process.env.GCP_PROJECT_ID, - keyFilename: process.env.GCP_KEY_FILE_PATH - }); + const storage = new Storage({ + projectId: process.env.GCP_PROJECT_ID, + keyFilename: process.env.GCP_KEY_FILE_PATH + }); - const filename = await makeParquetFile(data) - const destFileName = `tx-latency-measurement/polygon/${filename}`; + const filename = await makeParquetFile(data) + const destFileName = `tx-latency-measurement/polygon/${filename}`; - async function uploadFile() { - const options = { - destination: destFileName, + async function uploadFile() { + const options = { + destination: destFileName, }; await storage.bucket(process.env.GCP_BUCKET).upload(filename, options); console.log(`${filename} uploaded to ${process.env.GCP_BUCKET}`); } - await uploadFile().catch(console.error); - fs.unlinkSync(filename) + await uploadFile().catch(console.error); + fs.unlinkSync(filename) } async function uploadChoice(data) { - if (process.env.UPLOAD_METHOD === "AWS") { - await uploadToS3(data) - } - else if (process.env.UPLOAD_METHOD === "GCP") { - await uploadToGCS(data) - } - else { - throw "Improper upload method" - } + if (process.env.UPLOAD_METHOD === "AWS") { + await uploadToS3(data) + } + else if (process.env.UPLOAD_METHOD === "GCP") { + await uploadToGCS(data) + } + else { + throw "Improper upload method" + } } -async function sendTx(){ +async function sendTx() { var data = { executedAt: new Date().getTime(), txhash: '', startTime: 0, endTime: 0, chainId: 0, - latency:0, - error:'', + latency: 0, + error: '', txFee: 0.0, txFeeInUSD: 0.0, resourceUsedOfLatestBlock: 0, numOfTxInLatestBlock: 0, - pingTime:0 + pingTime: 0 } - try{ + try { // Add your private key const signer = web3.eth.accounts.privateKeyToAccount( privateKey ); - const balance = Number(await web3.eth.getBalance(signer.address)) * (10**(-18)); //in wei + const balance = Number(await web3.eth.getBalance(signer.address)) * (10 ** (-18)); //in wei - if(balance < parseFloat(process.env.BALANCE_ALERT_CONDITION_IN_MATIC)) - { + if (balance < parseFloat(process.env.BALANCE_ALERT_CONDITION_IN_MATIC)) { const now = new Date(); await sendSlackMsg(`${now}, Current balance of <${process.env.SCOPE_URL}/address/${signer.address}|${signer.address}> is less than ${process.env.BALANCE_ALERT_CONDITION_IN_MATIC} MATIC! balance=${balance} MATIC`) } const latestNonce = Number(await web3.eth.getTransactionCount(signer.address, 'pending')) - if (!!PrevNonce && latestNonce != (PrevNonce+1)) - { + if (!!PrevNonce && latestNonce != (PrevNonce + 1)) { // console.log(`Nonce ${latestNonce} = ${PrevNonce}`) return; } @@ -162,14 +160,14 @@ async function sendTx(){ const endGetBlock = new Date().getTime() data.pingTime = endGetBlock - startGetBlock - await web3.eth.getBlock(latestBlockNumber).then((result)=>{ + await web3.eth.getBlock(latestBlockNumber).then((result) => { data.resourceUsedOfLatestBlock = Number(result.gasUsed) data.numOfTxInLatestBlock = result.transactions.length }) // Option 1. Use gasstation https://docs.polygon.technology/docs/develop/tools/polygon-gas-station/ const gasStationResult = await axios.get(process.env.GAS_STATION_URL, { - headers:{ + headers: { "Content-Type": "application/json" } }) @@ -193,7 +191,7 @@ async function sendTx(){ type: 2, nonce: latestNonce, from: signer.address, - to: signer.address, + to: signer.address, value: web3.utils.toHex(web3.utils.toWei("0", "ether")), gas: 21000, maxPriorityFeePerGas, // 2.5 Gwei is a default @@ -203,12 +201,12 @@ async function sendTx(){ //Sign to the transaction var RLPEncodedTx; await web3.eth.accounts.signTransaction(tx, privateKey) - .then((result) => { - RLPEncodedTx = result.rawTransaction // RLP encoded transaction & already HEX value - data.txhash = result.transactionHash // the transaction hash of the RLP encoded transaction. - }); + .then((result) => { + RLPEncodedTx = result.rawTransaction // RLP encoded transaction & already HEX value + data.txhash = result.transactionHash // the transaction hash of the RLP encoded transaction. + }); - await web3.eth.net.getId().then((result)=>{ + await web3.eth.net.getId().then((result) => { data.chainId = Number(result) }) const start = new Date().getTime() @@ -216,47 +214,56 @@ async function sendTx(){ // Send signed transaction await web3.eth - .sendSignedTransaction(RLPEncodedTx) // Signed transaction data in HEX format - .then(function(receipt){ - PrevNonce = latestNonce - data.txhash = receipt.transactionHash - const end = new Date().getTime() - data.endTime = end - data.latency = end-start - data.txFee = Number(receipt.gasUsed) * Number(web3.utils.fromWei(Number(receipt.effectiveGasPrice), "ether")) - }) + .sendSignedTransaction(RLPEncodedTx) // Signed transaction data in HEX format + .then(function (receipt) { + PrevNonce = latestNonce + data.txhash = receipt.transactionHash + const end = new Date().getTime() + data.endTime = end + data.latency = end - start + data.txFee = Number(receipt.gasUsed) * Number(web3.utils.fromWei(Number(receipt.effectiveGasPrice), "ether")) + }) // Calculate Transaction Fee and Get Tx Fee in USD var MATICtoUSD; await axios.get(`https://api.coingecko.com/api/v3/simple/price?ids=matic-network&vs_currencies=usd&x_cg_demo_api_key=${process.env.COIN_GECKO_API_KEY}`) - .then(response => { - MATICtoUSD = response.data["matic-network"].usd; - }); + .then(response => { + MATICtoUSD = response.data["matic-network"].usd; + }); data.txFeeInUSD = data.txFee * MATICtoUSD // console.log(`${data.executedAt},${data.chainId},${data.txhash},${data.startTime},${data.endTime},${data.latency},${data.txFee},${data.txFeeInUSD},${data.resourceUsedOfLatestBlock},${data.numOfTxInLatestBlock},${data.pingTime},${data.error}`) - try{ + try { await uploadChoice(data) - } catch(err){ - await sendSlackMsg(`failed to upload polygon, ${err.toString()}`); - console.log(`failed to ${process.env.UPLOAD_METHOD === 'AWS'? 's3': 'gcs'}.upload!! Printing instead!`, err.toString()) + } catch (err) { + await sendSlackMsg(`failed to upload polygon, ${err.toString()}`); + console.log(`failed to ${process.env.UPLOAD_METHOD === 'AWS' ? 's3' : 'gcs'}.upload!! Printing instead!`, err.toString()) console.log(JSON.stringify(data)) - } - } catch(err){ - const now = new Date(); - await sendSlackMsg(`${now}, failed to execute polygon, ${err.toString()}, ${err.stack}`); - console.log("failed to execute.", err.toString(), err.stack) + } + } catch (err) { + data.error = err.toString() - console.log(`${data.executedAt},${data.chainId},${data.txhash},${data.startTime},${data.endTime},${data.latency},${data.txFee},${data.txFeeInUSD},${data.resourceUsedOfLatestBlock},${data.numOfTxInLatestBlock},${data.pingTime},${data.error}`) + const errorCode = `${data.executedAt},${data.chainId},${data.txhash},${data.startTime},${data.endTime},${data.latency},${data.txFee},${data.txFeeInUSD},${data.resourceUsedOfLatestBlock},${data.numOfTxInLatestBlock},${data.pingTime},${data.error}` + + if (err.toString().includes("TransactionBlockTimeoutError")) { + console.log(err) + sendSlackMsg(`Polygon TransactionBlockTimeoutError: ${err.toString()}`) + process.exit(1); + } else { + const now = new Date(); + await sendSlackMsg(`${now}, failed to execute polygon, ${err.toString()}, ${err.stack}`); + console.log("failed to execute.", err.toString(), err.stack) + console.log(errorCode) + } } } -async function main(){ +async function main() { const start = new Date().getTime() console.log(`starting tx latency measurement... start time = ${start}`) - if(privateKey === "") { + if (privateKey === "") { const account = web3.eth.accounts.create(web3.utils.randomHex(32)); console.log(`Private key is not defined. Use this new private key(${account.privateKey}).`) console.log(`Get test MATIC from the faucet: https://faucet.polygon.technology/`) @@ -266,22 +273,22 @@ async function main(){ // run sendTx every SEND_TX_INTERVAL const interval = eval(process.env.SEND_TX_INTERVAL) - setInterval(async()=>{ - try{ - await sendTx() - } catch(err){ - console.log("failed to execute sendTx", err.toString(), err.stack) + setInterval(async () => { + try { + await sendTx() + } catch (err) { + console.log("failed to execute sendTx", err.toString(), err.stack) } -}, interval) -try{ + }, interval) + try { await sendTx() -} catch(err){ + } catch (err) { console.log("failed to execute sendTx", err.toString(), err.stack) + } } +try { + main() } -try{ - main() -} -catch(err){ - console.log("failed to execute main", err.toString(), err.stack) +catch (err) { + console.log("failed to execute main", err.toString(), err.stack) } \ No newline at end of file diff --git a/sui-tx-latency-measurement/Dockerfile b/sui-tx-latency-measurement/Dockerfile index fe04475..59cc389 100644 --- a/sui-tx-latency-measurement/Dockerfile +++ b/sui-tx-latency-measurement/Dockerfile @@ -1,6 +1,6 @@ FROM node:16 -WORKDIR /usr/src/klaytn +WORKDIR /usr/src/sui COPY . .