Browse Source

sending payment always logs errors to find possible failure causes

master
nitowa 11 months ago
parent
commit
b229e70504
4 changed files with 85 additions and 33 deletions
  1. 1
    1
      package.json
  2. 4
    3
      src/util/types.ts
  3. 65
    29
      src/xrpIO/xrpl-binding.ts
  4. 15
    0
      test/primitives.ts

+ 1
- 1
package.json View File

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

+ 4
- 3
src/util/types.ts View File

@@ -17,13 +17,14 @@ export type Options = {
17 17
     debug?: boolean
18 18
     connectionTimeout?: number
19 19
     readMaxRetry?: number
20
-    readRetryTimeout?: number
20
+    readRetryTimeout?: number,
21
+    readFreshApi?:boolean,
21 22
 }
22 23
 
23 24
 export const defaultOptions = {
24 25
     debug: false,
25 26
     connectionTimeout: 100000,
26 27
     readFreshApi: true,
27
-    readMaxRetry: -1,
28
-    readRetryTimeout: 1000
28
+    readMaxRetry: 50,
29
+    readRetryTimeout: 750
29 30
   }

+ 65
- 29
src/xrpIO/xrpl-binding.ts View File

@@ -1,5 +1,5 @@
1 1
 import { defaultOptions, Memo, Options } from '../util/types'
2
-import { Client, Payment, TxResponse, Wallet } from 'xrpl'
2
+import { Client, Payment, RippledError, TxResponse, Wallet } from 'xrpl'
3 3
 
4 4
 import * as zlib from 'zlib'
5 5
 import * as util from 'util'
@@ -29,6 +29,7 @@ export class xrpIO {
29 29
     this.options.connectionTimeout = options.connectionTimeout ? Number(options.connectionTimeout) : defaultOptions.connectionTimeout
30 30
     this.options.readMaxRetry = options.readMaxRetry ? Number(options.readMaxRetry) : defaultOptions.readMaxRetry
31 31
     this.options.readRetryTimeout = options.readRetryTimeout ? Number(options.readRetryTimeout) : defaultOptions.readRetryTimeout
32
+    this.options.readFreshApi = options.readFreshApi ? Boolean(options.readFreshApi) : defaultOptions.readFreshApi
32 33
 
33 34
     this.api = new Client(server, {
34 35
       connectionTimeout: this.options.connectionTimeout
@@ -67,36 +68,36 @@ export class xrpIO {
67 68
     }
68 69
   }
69 70
 
70
-  private async sendPayment(data: Memo, to: string, secret: string, sequence?: number, amount: string = "1"): Promise<TxResponse> {
71
+  public async sendPayment(data: Memo, to: string, secret: string, sequence?: number, amount: string = "1"): Promise<TxResponse> {
71 72
     const wallet = Wallet.fromSecret(secret)
72 73
     this.dbg("Sending payment", wallet.address, '->', to)
73 74
 
74 75
     const _api = await this.cloneApi()
75
-    try {
76
-      const payment: Payment = await _api.autofill({
77
-        TransactionType: 'Payment',
78
-        Account: wallet.address,
79
-        Destination: to,
80
-        Sequence: sequence,
81
-        Amount: amount,
82
-        Memos: [{
83
-          Memo: {
84
-            MemoData: hexEncode(data.data || ""),
85
-            MemoFormat: hexEncode(data.format || ""),
86
-            MemoType: hexEncode(data.type || "")
87
-          }
88
-        }]
89
-      })
90
-
76
+    const payment: Payment = await _api.autofill({
77
+      TransactionType: 'Payment',
78
+      Account: wallet.address,
79
+      Destination: to,
80
+      Sequence: sequence,
81
+      Amount: amount,
82
+      Memos: [{
83
+        Memo: {
84
+          MemoData: hexEncode(data.data || ""),
85
+          MemoFormat: hexEncode(data.format || ""),
86
+          MemoType: hexEncode(data.type || "")
87
+        }
88
+      }]
89
+    })
91 90
 
91
+    try {
92 92
       const response = await _api.submitAndWait(payment, { wallet })
93
-      await _api.disconnect()
94 93
       this.dbg("Tx finalized", response.result.hash, response.result.Sequence)
95 94
       return response
96 95
     } catch (error: any) {
97 96
       this.dbg("SENDPAYMENT ERROR", error)
98
-      await _api.disconnect()
97
+      console.log("SENDPAYMENT ERROR", error)
99 98
       throw error
99
+    }finally{
100
+      await _api.disconnect()
100 101
     }
101 102
   }
102 103
 
@@ -106,22 +107,57 @@ export class xrpIO {
106 107
     return tx.result.hash
107 108
   }
108 109
 
109
-  private async getTransaction(hash: string, retry = 0): Promise<TxResponse> {
110
+  public async getTransaction(hash: string, retry = 0): Promise<TxResponse> {
111
+    if (!NON_ZERO_TX_HASH.test(hash)) {
112
+      throw ERR_BAD_TX_HASH(hash)
113
+    }
114
+
110 115
     this.dbg("Getting Tx", hash)
116
+    
117
+    const _api = this.options.readFreshApi ? await this.cloneApi() : this.api
118
+
111 119
     try {
112
-      return await this.api.request({
120
+      return await _api.request({
113 121
         command: 'tx',
114 122
         transaction: hash,
115 123
       })
116
-    } catch (e) {
117
-      this.dbg(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")
124
+    } catch (e: any) {
125
+      this.dbg("getTransaction err", e)
126
+      
127
+      if(e.data){ //RippledError 
128
+        switch(e.data.error){
129
+          //irrecoverable errors
130
+          case 'amendmentBlocked': //server is amendment blocked and needs to be updated to the latest version to stay synced with the XRP Ledger network.
131
+          case 'invalid_API_version': //The server does not support the API version number from the request.
132
+          case 'jsonInvalid': //(WebSocket only) The request is not a proper JSON object.
133
+          case 'missingCommand': //(WebSocket only) The request did not specify a command field
134
+          case 'noClosed': //The server does not have a closed ledger, typically because it has not finished starting up.
135
+          case 'txnNotFound': //Either the transaction does not exist, or it was part of an ledger version that rippled does not have available
136
+          case 'unknownCmd': //The request does not contain a command that the rippled server recognizes
137
+          case 'wsTextRequired': //(WebSocket only) The request's opcode  is not text.
138
+          case 'invalidParams': //One or more fields are specified incorrectly, or one or more required fields are missing.
139
+          case 'excessiveLgrRange': //The min_ledger and max_ledger fields of the request are more than 1000 apart
140
+          case 'invalidLgrRange': //The specified min_ledger is larger than the max_ledger, or one of those parameters is not a valid ledger index
141
+            throw e
142
+
143
+          //potentially recoverable errors
144
+          case 'failedToForward': //(Reporting Mode servers only) The server tried to forward this request to a P2P Mode server, but the connection failed
145
+          case 'noCurrent': //The server does not know what the current ledger is, due to high load, network problems, validator failures, incorrect configuration, or some other problem.
146
+          case 'noNetwork': //The server is having trouble connecting to the rest of the XRP Ledger peer-to-peer network (and is not running in stand-alone mode).
147
+          case 'tooBusy': //The server is under too much load to do this command right now. Generally not returned if you are connected as an admin
148
+          default: //some undocumented error, might as well give it a re-try
149
+            //fall through
150
+        }
151
+      }
152
+      //some other error, potentially recoverable
153
+      if (this.options.readMaxRetry != -1 && retry >= this.options.readMaxRetry) { //not doing infinite retries and exhausted retry quota
121 154
         throw e
155
+      }else{
156
+        await new Promise(res => setTimeout(res, this.options.readRetryTimeout))
157
+        return await this.getTransaction(hash, retry + 1)
122 158
       }
123
-      await new Promise(res => setTimeout(res, this.options.readRetryTimeout))
124
-      return await this.getTransaction(hash, retry + 1)
159
+    }finally{
160
+      if(this.options.readFreshApi) await _api.disconnect()
125 161
     }
126 162
   }
127 163
 

+ 15
- 0
test/primitives.ts View File

@@ -35,6 +35,19 @@ describe('XRPIO', () => {
35 35
         }
36 36
     })
37 37
 
38
+    it('getTransaction with bad hash', function(done){
39
+        this.timeout(10000)
40
+        api.getTransaction('73FECDA37ABBB2FC17460C5C2467BE6A0A8E1F4EB081FFFFFFFFFFFFFFFFFFFF') //technically this hash could exist, but probably never will
41
+        .then(_ => done(new Error('Expected error but succeeded')))
42
+        .catch(_ => done())
43
+    })
44
+
45
+    it('sendPayment errors on bad request sequence', function(done){
46
+        api.sendPayment({}, receiveWallet.address, sendWallet.secret, -12)
47
+        .then(_ => done(new Error('Expected error but succeeded')))
48
+        .catch(_ => done())
49
+    })
50
+
38 51
     it('getAccountSequence', async function(){
39 52
         this.timeout(10000)
40 53
         const seq = await api.getAccountSequence(sendWallet.address)
@@ -50,6 +63,8 @@ describe('XRPIO', () => {
50 63
         expect(cost).to.be.greaterThan(30)
51 64
     })
52 65
 
66
+    
67
+
53 68
     let txHash
54 69
     it('writeRaw', async function(){
55 70
         this.timeout(15000)

Loading…
Cancel
Save