Browse Source

sending payment always logs errors to find possible failure causes

master
nitowa 1 year 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
 {
1
 {
2
   "name": "xrpio",
2
   "name": "xrpio",
3
-  "version": "0.2.1",
3
+  "version": "0.2.2",
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"

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

17
     debug?: boolean
17
     debug?: boolean
18
     connectionTimeout?: number
18
     connectionTimeout?: number
19
     readMaxRetry?: number
19
     readMaxRetry?: number
20
-    readRetryTimeout?: number
20
+    readRetryTimeout?: number,
21
+    readFreshApi?:boolean,
21
 }
22
 }
22
 
23
 
23
 export const defaultOptions = {
24
 export const defaultOptions = {
24
     debug: false,
25
     debug: false,
25
     connectionTimeout: 100000,
26
     connectionTimeout: 100000,
26
     readFreshApi: true,
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
 import { defaultOptions, Memo, Options } from '../util/types'
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
 import * as zlib from 'zlib'
4
 import * as zlib from 'zlib'
5
 import * as util from 'util'
5
 import * as util from 'util'
29
     this.options.connectionTimeout = options.connectionTimeout ? Number(options.connectionTimeout) : defaultOptions.connectionTimeout
29
     this.options.connectionTimeout = options.connectionTimeout ? Number(options.connectionTimeout) : defaultOptions.connectionTimeout
30
     this.options.readMaxRetry = options.readMaxRetry ? Number(options.readMaxRetry) : defaultOptions.readMaxRetry
30
     this.options.readMaxRetry = options.readMaxRetry ? Number(options.readMaxRetry) : defaultOptions.readMaxRetry
31
     this.options.readRetryTimeout = options.readRetryTimeout ? Number(options.readRetryTimeout) : defaultOptions.readRetryTimeout
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
     this.api = new Client(server, {
34
     this.api = new Client(server, {
34
       connectionTimeout: this.options.connectionTimeout
35
       connectionTimeout: this.options.connectionTimeout
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
     const wallet = Wallet.fromSecret(secret)
72
     const wallet = Wallet.fromSecret(secret)
72
     this.dbg("Sending payment", wallet.address, '->', to)
73
     this.dbg("Sending payment", wallet.address, '->', to)
73
 
74
 
74
     const _api = await this.cloneApi()
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
       const response = await _api.submitAndWait(payment, { wallet })
92
       const response = await _api.submitAndWait(payment, { wallet })
93
-      await _api.disconnect()
94
       this.dbg("Tx finalized", response.result.hash, response.result.Sequence)
93
       this.dbg("Tx finalized", response.result.hash, response.result.Sequence)
95
       return response
94
       return response
96
     } catch (error: any) {
95
     } catch (error: any) {
97
       this.dbg("SENDPAYMENT ERROR", error)
96
       this.dbg("SENDPAYMENT ERROR", error)
98
-      await _api.disconnect()
97
+      console.log("SENDPAYMENT ERROR", error)
99
       throw error
98
       throw error
99
+    }finally{
100
+      await _api.disconnect()
100
     }
101
     }
101
   }
102
   }
102
 
103
 
106
     return tx.result.hash
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
     this.dbg("Getting Tx", hash)
115
     this.dbg("Getting Tx", hash)
116
+    
117
+    const _api = this.options.readFreshApi ? await this.cloneApi() : this.api
118
+
111
     try {
119
     try {
112
-      return await this.api.request({
120
+      return await _api.request({
113
         command: 'tx',
121
         command: 'tx',
114
         transaction: hash,
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
         throw e
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
         }
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
     it('getAccountSequence', async function(){
51
     it('getAccountSequence', async function(){
39
         this.timeout(10000)
52
         this.timeout(10000)
40
         const seq = await api.getAccountSequence(sendWallet.address)
53
         const seq = await api.getAccountSequence(sendWallet.address)
50
         expect(cost).to.be.greaterThan(30)
63
         expect(cost).to.be.greaterThan(30)
51
     })
64
     })
52
 
65
 
66
+    
67
+
53
     let txHash
68
     let txHash
54
     it('writeRaw', async function(){
69
     it('writeRaw', async function(){
55
         this.timeout(15000)
70
         this.timeout(15000)

Loading…
Cancel
Save