Browse Source

add more utility functions and improved tests

master
nitowa 1 year ago
parent
commit
50fa056fe3
4 changed files with 71 additions and 22 deletions
  1. 1
    1
      package.json
  2. 1
    0
      src/util/errors.ts
  3. 45
    13
      src/xrpIO/xrpl-binding.ts
  4. 24
    8
      test/primitives.ts

+ 1
- 1
package.json View File

1
 {
1
 {
2
   "name": "xrpio",
2
   "name": "xrpio",
3
-  "version": "0.1.8",
3
+  "version": "0.2.0",
4
   "repository": {
4
   "repository": {
5
     "type": "git",
5
     "type": "git",
6
     "url": "https://gitea.nitowa.xyz/npm-packages/xrpio.git"
6
     "url": "https://gitea.nitowa.xyz/npm-packages/xrpio.git"

+ 1
- 0
src/util/errors.ts View File

1
+export const ERR_BAD_TX_HASH = (hash:string) => new Error(`Bad tx hash format: "${hash}"`)

+ 45
- 13
src/xrpIO/xrpl-binding.ts View File

3
 
3
 
4
 import * as zlib from 'zlib'
4
 import * as zlib from 'zlib'
5
 import * as util from 'util'
5
 import * as util from 'util'
6
+import { NON_ZERO_TX_HASH } from '../util/protocol.constants'
7
+import { ERR_BAD_TX_HASH } from '../util/errors'
6
 
8
 
7
 const compressB64 = async (data: string) => (await util.promisify(zlib.deflate)(Buffer.from(data, 'utf-8'))).toString('base64')
9
 const compressB64 = async (data: string) => (await util.promisify(zlib.deflate)(Buffer.from(data, 'utf-8'))).toString('base64')
8
 const decompressB64 = async (data: string) => (await util.promisify(zlib.inflate)(Buffer.from(data, 'base64'))).toString('utf-8')
10
 const decompressB64 = async (data: string) => (await util.promisify(zlib.inflate)(Buffer.from(data, 'base64'))).toString('utf-8')
9
 const hexDecode = (str: string) => Buffer.from(str, 'hex').toString('utf8')
11
 const hexDecode = (str: string) => Buffer.from(str, 'hex').toString('utf8')
10
 const hexEncode = (str: string) => Buffer.from(str, 'utf8').toString('hex').toUpperCase()
12
 const hexEncode = (str: string) => Buffer.from(str, 'utf8').toString('hex').toUpperCase()
11
 const chunkString = (str: string, length: number) => str.match(new RegExp('.{1,' + length + '}', 'gs'));
13
 const chunkString = (str: string, length: number) => str.match(new RegExp('.{1,' + length + '}', 'gs'));
14
+const genRandHex = size => [...Array(size)].map(() => Math.floor(Math.random() * 16).toString(16)).join('');
12
 const PAYLOAD_SIZE = 925
15
 const PAYLOAD_SIZE = 925
13
-
16
+const XRP_PER_DROP = 0.000001
17
+const DROP_PER_XRP = 1000000
14
 
18
 
15
 
19
 
16
 export class xrpIO {
20
 export class xrpIO {
63
     }
67
     }
64
   }
68
   }
65
 
69
 
66
-  private async sendPayment(data: Memo, to: string, secret: string, sequence?: number): Promise<TxResponse> {
70
+  private async sendPayment(data: Memo, to: string, secret: string, sequence?: number, amount: string = "1"): Promise<TxResponse> {
67
     const wallet = Wallet.fromSecret(secret)
71
     const wallet = Wallet.fromSecret(secret)
68
     this.dbg("Sending payment", wallet.address, '->', to)
72
     this.dbg("Sending payment", wallet.address, '->', to)
69
 
73
 
74
         Account: wallet.address,
78
         Account: wallet.address,
75
         Destination: to,
79
         Destination: to,
76
         Sequence: sequence,
80
         Sequence: sequence,
77
-        Amount: "1",
81
+        Amount: amount,
78
         Memos: [{
82
         Memos: [{
79
           Memo: {
83
           Memo: {
80
             MemoData: hexEncode(data.data || ""),
84
             MemoData: hexEncode(data.data || ""),
96
     }
100
     }
97
   }
101
   }
98
 
102
 
99
-  public async writeRaw(data: Memo, to: string, secret: string, sequence?: number): Promise<string> {
103
+  public async writeRaw(data: Memo, to: string, secret: string, sequence?: number, amount: string = "1"): Promise<string> {
100
     this.dbg("Writing data", data)
104
     this.dbg("Writing data", data)
101
-    const tx = await this.sendPayment(data, to, secret, sequence)
105
+    const tx = await this.sendPayment(data, to, secret, sequence, amount)
102
     return tx.result.hash
106
     return tx.result.hash
103
   }
107
   }
104
 
108
 
105
-  private async getTransaction(hash: string, retry=0): Promise<TxResponse> {
109
+  private async getTransaction(hash: string, retry = 0): Promise<TxResponse> {
106
     this.dbg("Getting Tx", hash)
110
     this.dbg("Getting Tx", hash)
107
-    try{
111
+    try {
108
       return await this.api.request({
112
       return await this.api.request({
109
         command: 'tx',
113
         command: 'tx',
110
         transaction: hash,
114
         transaction: hash,
111
       })
115
       })
112
-    }catch(e){
116
+    } catch (e) {
113
       this.dbg(e)
117
       this.dbg(e)
114
-      if(this.options.readMaxRetry != -1){
115
-        if(retry >= this.options.readMaxRetry)
116
-          console.error("Retry limit exceeded for", hash, ". this is an irrecoverable error")
117
-          throw e
118
+      if (this.options.readMaxRetry != -1) {
119
+        if (retry >= this.options.readMaxRetry)
120
+          console.error("Retry limit exceeded for", hash, ". This is an irrecoverable error")
121
+        throw e
118
       }
122
       }
119
       await new Promise(res => setTimeout(res, this.options.readRetryTimeout))
123
       await new Promise(res => setTimeout(res, this.options.readRetryTimeout))
120
-      return await this.getTransaction(hash, retry+1)
124
+      return await this.getTransaction(hash, retry + 1)
121
     }
125
     }
122
   }
126
   }
123
 
127
 
124
   public async readRaw(hash: string): Promise<Memo> {
128
   public async readRaw(hash: string): Promise<Memo> {
129
+
130
+    if (!NON_ZERO_TX_HASH.test(hash)) {
131
+      throw ERR_BAD_TX_HASH(hash)
132
+    }
133
+
125
     const tx = await this.getTransaction(hash)
134
     const tx = await this.getTransaction(hash)
126
     const memo = tx.result.Memos[0].Memo
135
     const memo = tx.result.Memos[0].Memo
127
     const memoParsed = {
136
     const memoParsed = {
148
   }
157
   }
149
 
158
 
150
   public async treeRead(hashes: string[]): Promise<string> {
159
   public async treeRead(hashes: string[]): Promise<string> {
160
+    const bad_hash = hashes.find(hash => !NON_ZERO_TX_HASH.test(hash))
161
+    if (bad_hash)
162
+      throw ERR_BAD_TX_HASH(bad_hash)
163
+
151
     const memos = await Promise.all(hashes.map(hash => this.readRaw(hash)))
164
     const memos = await Promise.all(hashes.map(hash => this.readRaw(hash)))
152
     const payload: string = await decompressB64(memos.map(memo => memo.data).join(''))
165
     const payload: string = await decompressB64(memos.map(memo => memo.data).join(''))
153
 
166
 
169
     return Number(accountInfo.result.account_data.Sequence)
182
     return Number(accountInfo.result.account_data.Sequence)
170
   }
183
   }
171
 
184
 
185
+  public async estimateFee(data: string, denomination: 'XRP' | 'DROPS' = 'DROPS', cost = 0): Promise<number> {
186
+    data = await compressB64(data)
187
+    const chunks = chunkString(data, PAYLOAD_SIZE)
188
+
189
+    if (chunks.length === 1) {
190
+      return (denomination === "DROPS" ? (cost + 1) : this.dropsToXrp(cost + 1))
191
+    }
192
+
193
+    return this.estimateFee(JSON.stringify(chunks.map(_ => genRandHex(64))), denomination, cost + chunks.length)
194
+  }
195
+
196
+  public xrpToDrops(xrp: number): number {
197
+    return xrp * DROP_PER_XRP
198
+  }
199
+
200
+  public dropsToXrp(drops: number): number {
201
+    return drops * XRP_PER_DROP
202
+  }
203
+
172
   private dbg(...args: any[]) {
204
   private dbg(...args: any[]) {
173
     if (this.options.debug) {
205
     if (this.options.debug) {
174
       console.log.apply(console, args)
206
       console.log.apply(console, args)

+ 24
- 8
test/primitives.ts View File

1
+var wtf = require('wtfnode');
1
 import { htmlTxt, longText, makeTestnetWallet, TEST_CONFIG, TEST_DATA } from './CONSTANTS'
2
 import { htmlTxt, longText, makeTestnetWallet, TEST_CONFIG, TEST_DATA } from './CONSTANTS'
2
 import { xrpIO } from '../src/xrpIO/xrpl-binding'
3
 import { xrpIO } from '../src/xrpIO/xrpl-binding'
3
 import * as chai from 'chai';
4
 import * as chai from 'chai';
4
 import { Wallet } from '../src/util/types';
5
 import { Wallet } from '../src/util/types';
5
-var wtf = require('wtfnode');
6
 const expect = chai.expect
6
 const expect = chai.expect
7
 
7
 
8
 let sendWallet: Wallet
8
 let sendWallet: Wallet
36
     })
36
     })
37
 
37
 
38
     it('getAccountSequence', async function(){
38
     it('getAccountSequence', async function(){
39
-//      this.skip()
39
+        //this.skip()
40
         this.timeout(10000)
40
         this.timeout(10000)
41
         const seq = await api.getAccountSequence(sendWallet.address)
41
         const seq = await api.getAccountSequence(sendWallet.address)
42
         expect(seq).to.exist
42
         expect(seq).to.exist
43
         expect(seq).to.be.a('number')
43
         expect(seq).to.be.a('number')
44
     })
44
     })
45
 
45
 
46
+    it('estimateFee', async function () {
47
+        this.timeout(2000)
48
+        const cost = await api.estimateFee(longText)
49
+        expect(cost).to.be.a('number')
50
+        expect(cost).to.be.lessThan(50)
51
+        expect(cost).to.be.greaterThan(30)
52
+    })
53
+
46
     let txHash
54
     let txHash
47
     it('writeRaw', async function(){
55
     it('writeRaw', async function(){
48
-//        this.skip()
56
+        //this.skip()
49
         this.timeout(15000)
57
         this.timeout(15000)
50
         txHash = await api.writeRaw({data: TEST_DATA}, receiveWallet.address, sendWallet.secret);
58
         txHash = await api.writeRaw({data: TEST_DATA}, receiveWallet.address, sendWallet.secret);
51
         expect(txHash).to.exist
59
         expect(txHash).to.exist
53
     })
61
     })
54
 
62
 
55
     it('readRaw', async function () {
63
     it('readRaw', async function () {
56
-//        this.skip()
64
+        //this.skip()
57
         this.timeout(15000)
65
         this.timeout(15000)
58
         const memo = await api.readRaw(txHash)
66
         const memo = await api.readRaw(txHash)
59
         expect(memo).to.exist
67
         expect(memo).to.exist
60
         expect(memo.data).to.be.equal(TEST_DATA)
68
         expect(memo.data).to.be.equal(TEST_DATA)
61
     })
69
     })
62
 
70
 
71
+    it('readRaw bad hash', function (done){
72
+        this.timeout(150000)
73
+        api.readRaw("123")
74
+        .then(_ => done(new Error('Expected error but succeeded')))
75
+        .catch(_ => done())
76
+    })
77
+
63
     it('treeWrite', async function(){
78
     it('treeWrite', async function(){
64
 //        this.skip()
79
 //        this.skip()
65
         this.timeout(45000)
80
         this.timeout(45000)
77
     })
92
     })
78
 
93
 
79
     it('treeWrite XL', async function(){
94
     it('treeWrite XL', async function(){
80
-//        this.skip()
95
+        //this.skip()
81
         this.timeout(450000)
96
         this.timeout(450000)
82
         txHash = await api.treeWrite(htmlTxt, receiveWallet.address, sendWallet.secret)
97
         txHash = await api.treeWrite(htmlTxt, receiveWallet.address, sendWallet.secret)
83
         expect(txHash).to.exist
98
         expect(txHash).to.exist
85
     })
100
     })
86
 
101
 
87
     it('treeRead XL', async function(){
102
     it('treeRead XL', async function(){
88
-//        this.skip()  
103
+        //this.skip()  
89
         this.timeout(450000)
104
         this.timeout(450000)
90
         const data = await api.treeRead([txHash])
105
         const data = await api.treeRead([txHash])
91
         expect(data).to.exist
106
         expect(data).to.exist
93
     })
108
     })
94
     
109
     
95
     it('print open handles', function(){
110
     it('print open handles', function(){
96
-        wtf.dump()
97
-        console.log(txHash)
111
+        api.disconnect().then(_ => {
112
+            wtf.dump()
113
+        })
98
     })
114
     })
99
 })
115
 })

Loading…
Cancel
Save