@@ -14,117 +14,124 @@ GNU Lesser General Public License for more details.
14
14
You should have received a copy of the GNU Lesser General Public License
15
15
along with web3.js. If not, see <http://www.gnu.org/licenses/>.
16
16
*/
17
- import { Web3Context , Web3RequestManager } from 'web3-core' ;
18
- import { format } from 'web3-utils ' ;
19
- import { DEFAULT_RETURN_FORMAT , JsonRpcResponseWithResult , Web3EthExecutionAPI } from 'web3-types' ;
20
- import { ethRpcMethods } from 'web3-rpc-methods ' ;
21
- import { WebSocketProvider } from 'web3-providers-ws' ;
17
+
18
+ import { Web3Context } from 'web3-core ' ;
19
+ import { DEFAULT_RETURN_FORMAT , Web3EthExecutionAPI } from 'web3-types' ;
20
+ import WebSocketProvider from 'web3-providers-ws ' ;
21
+
22
22
import * as rpcMethodWrappers from '../../../src/rpc_method_wrappers' ;
23
- import * as WatchTransactionBySubscription from '../../../src/utils/watch_transaction_by_subscription' ;
24
23
import {
25
24
expectedTransactionReceipt ,
26
25
expectedTransactionHash ,
27
26
testData ,
28
27
} from '../rpc_method_wrappers/fixtures/send_signed_transaction' ;
29
- import { transactionReceiptSchema } from '../../../src/schemas ' ;
30
- import { registeredSubscriptions } from '../../../src' ;
28
+ import { blockMockResult } from '../../fixtures/transactions_data ' ;
29
+
31
30
32
- jest . mock ( 'web3-rpc-methods' ) ;
33
31
jest . mock ( 'web3-providers-ws' ) ;
34
- jest . mock ( '../../../src/utils/watch_transaction_by_polling' ) ;
35
32
36
33
const testMessage =
37
34
'Title: %s\ninputSignedTransaction: %s\nexpectedTransactionHash: %s\nexpectedTransactionReceipt: %s\n' ;
38
35
39
- async function waitUntilCalled ( mock : jest . Mock , timeout = 1000 ) : Promise < jest . Mock > {
40
- return new Promise ( ( resolve , reject ) => {
41
- let timeoutId : NodeJS . Timeout | undefined ;
42
- const intervalId = setInterval ( ( ) => {
43
- if ( mock . mock . calls . length > 0 ) {
44
- clearInterval ( intervalId ) ;
45
- if ( timeoutId ) {
46
- clearTimeout ( timeoutId ) ;
47
- }
48
- resolve ( mock ) ;
49
- }
50
- } , 100 ) ;
51
- timeoutId = setTimeout ( ( ) => {
52
- clearInterval ( intervalId ) ;
53
- if ( timeoutId ) {
54
- clearTimeout ( timeoutId ) ;
55
- }
56
- reject ( new Error ( 'timeout' ) ) ;
57
- } , timeout ) ;
58
- } ) ;
59
- }
60
36
61
37
describe ( 'watchTransactionBySubscription' , ( ) => {
38
+ const CONFIRMATION_BLOCKS = 5 ;
62
39
describe ( 'should revert to polling in cases where getting by subscription did not workout' , ( ) => {
63
40
let web3Context : Web3Context < Web3EthExecutionAPI > ;
64
41
65
42
beforeEach ( ( ) => {
66
- jest . spyOn ( Web3RequestManager . prototype , 'send' ) . mockImplementation ( async ( ) => {
67
- return { } as Promise < unknown > ;
68
- } ) ;
69
- jest . spyOn ( WebSocketProvider . prototype , 'request' ) . mockImplementation ( async ( ) => {
70
- return { } as Promise < JsonRpcResponseWithResult < unknown > > ;
71
- } ) ;
72
-
73
- ( ethRpcMethods . sendRawTransaction as jest . Mock ) . mockResolvedValue (
74
- expectedTransactionHash ,
75
- ) ;
76
- ( ethRpcMethods . getTransactionReceipt as jest . Mock ) . mockResolvedValue (
77
- expectedTransactionHash ,
78
- ) ;
79
43
web3Context = new Web3Context ( {
80
- // dummy provider that does supports subscription
81
- provider : new WebSocketProvider ( 'ws://localhost:8546' ) ,
82
- registeredSubscriptions,
83
- } ) ;
44
+ provider : new WebSocketProvider ( 'wss://localhost:8546' ) , }
45
+ ) ;
46
+
84
47
( web3Context . provider as any ) . supportsSubscriptions = ( ) => true ;
48
+ web3Context . transactionConfirmationBlocks = CONFIRMATION_BLOCKS ;
49
+ web3Context . enableExperimentalFeatures . useSubscriptionWhenCheckingBlockTimeout =
50
+ true ;
51
+
85
52
} ) ;
86
- afterEach ( ( ) => {
87
- // to clear the interval inside the subscription function:
88
- web3Context . transactionConfirmationBlocks = 0 ;
89
- } ) ;
90
- let counter = 0 ;
91
- it . each ( testData ) (
92
- `should call getBlockNumber if blockHeaderTimeout reached\n ${ testMessage } ` ,
93
- async ( _ , inputTransaction ) => {
94
- if ( counter > 0 ) {
95
- return ;
96
- }
97
- counter += 1 ;
98
- const formattedTransactionReceipt = format (
99
- transactionReceiptSchema ,
100
- expectedTransactionReceipt ,
101
- DEFAULT_RETURN_FORMAT ,
102
- ) ;
103
53
104
- web3Context . enableExperimentalFeatures . useSubscriptionWhenCheckingBlockTimeout =
105
- true ;
106
- // this will case the function to revert to polling:
107
- web3Context . blockHeaderTimeout = 0 ;
54
+ it . each ( testData ) (
55
+ `should call getBlockByNumber if blockHeaderTimeout reached\n ${ testMessage } ` ,
56
+ async ( _ , inputTransaction , ) => {
108
57
109
- web3Context . transactionSendTimeout = 2 ;
58
+ let blockNum = 100 ;
59
+ let ethGetBlockByNumberCount = 0 ;
60
+ web3Context . requestManager . send = jest . fn ( async ( request ) => {
61
+
62
+ if ( request . method === 'eth_getBlockByNumber' ) {
63
+ ethGetBlockByNumberCount += 1 ;
64
+ return Promise . resolve (
65
+ { ...blockMockResult . result ,
66
+ number : ( request as any ) . params [ 0 ]
67
+ } ) ;
68
+ }
69
+ if ( request . method === 'eth_call' ) {
70
+
71
+ return Promise . resolve ( "0x" ) ;
72
+ }
73
+ if ( request . method === 'eth_blockNumber' ) {
74
+ blockNum += 1 ;
75
+ return Promise . resolve ( blockNum . toString ( 16 ) ) ;
76
+ }
77
+ if ( request . method === 'eth_sendRawTransaction' ) {
78
+ return Promise . resolve ( expectedTransactionHash ) ;
79
+ }
80
+ if ( request . method === 'eth_getTransactionReceipt' ) {
81
+ return Promise . resolve ( expectedTransactionReceipt ) ;
82
+ }
83
+
84
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
85
+ return Promise . reject ( new Error ( "Unknown Request" ) ) as any ;
86
+ } ) ;
110
87
111
88
const promiEvent = rpcMethodWrappers . sendSignedTransaction (
112
89
web3Context ,
113
90
inputTransaction ,
114
91
DEFAULT_RETURN_FORMAT ,
115
92
) ;
116
- // await promiEvent;
117
- WatchTransactionBySubscription . watchTransactionBySubscription ( {
118
- web3Context,
119
- transactionReceipt : formattedTransactionReceipt ,
120
- transactionPromiEvent : promiEvent ,
121
- returnFormat : DEFAULT_RETURN_FORMAT ,
93
+
94
+ let confirmationsCount = 0 ;
95
+ const confirmationPromise = new Promise < void > ( ( resolve , reject ) => {
96
+
97
+ const handleConfirmation = ( confirmation : { confirmations : bigint } ) => {
98
+ confirmationsCount += 1 ;
99
+
100
+ if ( confirmation . confirmations >= CONFIRMATION_BLOCKS ) {
101
+ resolve ( ) ;
102
+ }
103
+ } ;
104
+
105
+ const handleError = ( _error : any ) => {
106
+ reject ( ) ;
107
+ } ;
108
+
109
+ promiEvent
110
+ . on ( 'confirmation' , handleConfirmation )
111
+ . on ( 'error' , handleError )
112
+ . then ( ( res ) => {
113
+ // eslint-disable-next-line jest/no-conditional-expect
114
+ expect ( res ) . toBeDefined ( ) ;
115
+ } )
116
+ . catch ( reject ) ;
117
+ } ) ;
118
+
119
+ // Wait for the confirmationPromise to resolve or timeout after 5 seconds
120
+ let timeoutId ;
121
+ const timeout = new Promise ( ( _res , reject ) => {
122
+ timeoutId = setTimeout ( ( ) => reject ( new Error ( 'Timeout waiting for confirmations' ) ) , 500000 ) ;
122
123
} ) ;
123
- await waitUntilCalled ( ethRpcMethods . getBlockNumber as jest . Mock , 5000 ) ;
124
124
125
- await promiEvent ;
126
- } ,
127
- 60000 ,
125
+ await Promise . race ( [ confirmationPromise , timeout ] ) ;
126
+
127
+ clearTimeout ( timeoutId ) ;
128
+
129
+ expect ( confirmationsCount ) . toBe ( CONFIRMATION_BLOCKS ) ;
130
+ expect ( ethGetBlockByNumberCount ) . toBe ( CONFIRMATION_BLOCKS - 1 ) ; // means polling called getblock 4 times as first confirmation is receipt it self
131
+
132
+ }
128
133
) ;
134
+
135
+
129
136
} ) ;
130
137
} ) ;
0 commit comments