add more utility functions and improved tests

This commit is contained in:
nitowa
2023-05-01 20:15:00 +02:00
parent 26e4989f82
commit 50fa056fe3
4 changed files with 71 additions and 22 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "xrpio", "name": "xrpio",
"version": "0.1.8", "version": "0.2.0",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://gitea.nitowa.xyz/npm-packages/xrpio.git" "url": "https://gitea.nitowa.xyz/npm-packages/xrpio.git"
+1
View File
@@ -0,0 +1 @@
export const ERR_BAD_TX_HASH = (hash:string) => new Error(`Bad tx hash format: "${hash}"`)
+38 -6
View File
@@ -3,14 +3,18 @@ import { Client, Payment, TxResponse, Wallet } from 'xrpl'
import * as zlib from 'zlib' import * as zlib from 'zlib'
import * as util from 'util' import * as util from 'util'
import { NON_ZERO_TX_HASH } from '../util/protocol.constants'
import { ERR_BAD_TX_HASH } from '../util/errors'
const compressB64 = async (data: string) => (await util.promisify(zlib.deflate)(Buffer.from(data, 'utf-8'))).toString('base64') const compressB64 = async (data: string) => (await util.promisify(zlib.deflate)(Buffer.from(data, 'utf-8'))).toString('base64')
const decompressB64 = async (data: string) => (await util.promisify(zlib.inflate)(Buffer.from(data, 'base64'))).toString('utf-8') const decompressB64 = async (data: string) => (await util.promisify(zlib.inflate)(Buffer.from(data, 'base64'))).toString('utf-8')
const hexDecode = (str: string) => Buffer.from(str, 'hex').toString('utf8') const hexDecode = (str: string) => Buffer.from(str, 'hex').toString('utf8')
const hexEncode = (str: string) => Buffer.from(str, 'utf8').toString('hex').toUpperCase() const hexEncode = (str: string) => Buffer.from(str, 'utf8').toString('hex').toUpperCase()
const chunkString = (str: string, length: number) => str.match(new RegExp('.{1,' + length + '}', 'gs')); const chunkString = (str: string, length: number) => str.match(new RegExp('.{1,' + length + '}', 'gs'));
const genRandHex = size => [...Array(size)].map(() => Math.floor(Math.random() * 16).toString(16)).join('');
const PAYLOAD_SIZE = 925 const PAYLOAD_SIZE = 925
const XRP_PER_DROP = 0.000001
const DROP_PER_XRP = 1000000
export class xrpIO { export class xrpIO {
@@ -63,7 +67,7 @@ export class xrpIO {
} }
} }
private async sendPayment(data: Memo, to: string, secret: string, sequence?: number): Promise<TxResponse> { private async sendPayment(data: Memo, to: string, secret: string, sequence?: number, amount: string = "1"): Promise<TxResponse> {
const wallet = Wallet.fromSecret(secret) const wallet = Wallet.fromSecret(secret)
this.dbg("Sending payment", wallet.address, '->', to) this.dbg("Sending payment", wallet.address, '->', to)
@@ -74,7 +78,7 @@ export class xrpIO {
Account: wallet.address, Account: wallet.address,
Destination: to, Destination: to,
Sequence: sequence, Sequence: sequence,
Amount: "1", Amount: amount,
Memos: [{ Memos: [{
Memo: { Memo: {
MemoData: hexEncode(data.data || ""), MemoData: hexEncode(data.data || ""),
@@ -96,9 +100,9 @@ export class xrpIO {
} }
} }
public async writeRaw(data: Memo, to: string, secret: string, sequence?: number): Promise<string> { public async writeRaw(data: Memo, to: string, secret: string, sequence?: number, amount: string = "1"): Promise<string> {
this.dbg("Writing data", data) this.dbg("Writing data", data)
const tx = await this.sendPayment(data, to, secret, sequence) const tx = await this.sendPayment(data, to, secret, sequence, amount)
return tx.result.hash return tx.result.hash
} }
@@ -113,7 +117,7 @@ export class xrpIO {
this.dbg(e) this.dbg(e)
if (this.options.readMaxRetry != -1) { if (this.options.readMaxRetry != -1) {
if (retry >= this.options.readMaxRetry) if (retry >= this.options.readMaxRetry)
console.error("Retry limit exceeded for", hash, ". this is an irrecoverable error") console.error("Retry limit exceeded for", hash, ". This is an irrecoverable error")
throw e throw e
} }
await new Promise(res => setTimeout(res, this.options.readRetryTimeout)) await new Promise(res => setTimeout(res, this.options.readRetryTimeout))
@@ -122,6 +126,11 @@ export class xrpIO {
} }
public async readRaw(hash: string): Promise<Memo> { public async readRaw(hash: string): Promise<Memo> {
if (!NON_ZERO_TX_HASH.test(hash)) {
throw ERR_BAD_TX_HASH(hash)
}
const tx = await this.getTransaction(hash) const tx = await this.getTransaction(hash)
const memo = tx.result.Memos[0].Memo const memo = tx.result.Memos[0].Memo
const memoParsed = { const memoParsed = {
@@ -148,6 +157,10 @@ export class xrpIO {
} }
public async treeRead(hashes: string[]): Promise<string> { public async treeRead(hashes: string[]): Promise<string> {
const bad_hash = hashes.find(hash => !NON_ZERO_TX_HASH.test(hash))
if (bad_hash)
throw ERR_BAD_TX_HASH(bad_hash)
const memos = await Promise.all(hashes.map(hash => this.readRaw(hash))) const memos = await Promise.all(hashes.map(hash => this.readRaw(hash)))
const payload: string = await decompressB64(memos.map(memo => memo.data).join('')) const payload: string = await decompressB64(memos.map(memo => memo.data).join(''))
@@ -169,6 +182,25 @@ export class xrpIO {
return Number(accountInfo.result.account_data.Sequence) return Number(accountInfo.result.account_data.Sequence)
} }
public async estimateFee(data: string, denomination: 'XRP' | 'DROPS' = 'DROPS', cost = 0): Promise<number> {
data = await compressB64(data)
const chunks = chunkString(data, PAYLOAD_SIZE)
if (chunks.length === 1) {
return (denomination === "DROPS" ? (cost + 1) : this.dropsToXrp(cost + 1))
}
return this.estimateFee(JSON.stringify(chunks.map(_ => genRandHex(64))), denomination, cost + chunks.length)
}
public xrpToDrops(xrp: number): number {
return xrp * DROP_PER_XRP
}
public dropsToXrp(drops: number): number {
return drops * XRP_PER_DROP
}
private dbg(...args: any[]) { private dbg(...args: any[]) {
if (this.options.debug) { if (this.options.debug) {
console.log.apply(console, args) console.log.apply(console, args)
+18 -2
View File
@@ -1,8 +1,8 @@
var wtf = require('wtfnode');
import { htmlTxt, longText, makeTestnetWallet, TEST_CONFIG, TEST_DATA } from './CONSTANTS' import { htmlTxt, longText, makeTestnetWallet, TEST_CONFIG, TEST_DATA } from './CONSTANTS'
import { xrpIO } from '../src/xrpIO/xrpl-binding' import { xrpIO } from '../src/xrpIO/xrpl-binding'
import * as chai from 'chai'; import * as chai from 'chai';
import { Wallet } from '../src/util/types'; import { Wallet } from '../src/util/types';
var wtf = require('wtfnode');
const expect = chai.expect const expect = chai.expect
let sendWallet: Wallet let sendWallet: Wallet
@@ -43,6 +43,14 @@ describe('XRPIO', () => {
expect(seq).to.be.a('number') expect(seq).to.be.a('number')
}) })
it('estimateFee', async function () {
this.timeout(2000)
const cost = await api.estimateFee(longText)
expect(cost).to.be.a('number')
expect(cost).to.be.lessThan(50)
expect(cost).to.be.greaterThan(30)
})
let txHash let txHash
it('writeRaw', async function(){ it('writeRaw', async function(){
//this.skip() //this.skip()
@@ -60,6 +68,13 @@ describe('XRPIO', () => {
expect(memo.data).to.be.equal(TEST_DATA) expect(memo.data).to.be.equal(TEST_DATA)
}) })
it('readRaw bad hash', function (done){
this.timeout(150000)
api.readRaw("123")
.then(_ => done(new Error('Expected error but succeeded')))
.catch(_ => done())
})
it('treeWrite', async function(){ it('treeWrite', async function(){
// this.skip() // this.skip()
this.timeout(45000) this.timeout(45000)
@@ -93,7 +108,8 @@ describe('XRPIO', () => {
}) })
it('print open handles', function(){ it('print open handles', function(){
api.disconnect().then(_ => {
wtf.dump() wtf.dump()
console.log(txHash) })
}) })
}) })