@@ -24,7 +24,6 @@ const logger = new Logger(version);
24
24
25
25
import { Formatter } from "./formatter" ;
26
26
27
-
28
27
//////////////////////////////
29
28
// Event Serializeing
30
29
@@ -925,40 +924,139 @@ export class BaseProvider extends Provider implements EnsProvider {
925
924
}
926
925
927
926
async waitForTransaction ( transactionHash : string , confirmations ?: number , timeout ?: number ) : Promise < TransactionReceipt > {
928
- if ( confirmations == null ) { confirmations = 1 ; }
927
+ return this . _waitForTransaction ( transactionHash , ( confirmations == null ) ? 1 : confirmations , timeout || 0 , null ) ;
928
+ }
929
929
930
+ async _waitForTransaction ( transactionHash : string , confirmations : number , timeout : number , replaceable : { data : string , from : string , nonce : number , to : string , value : BigNumber , startBlock : number } ) : Promise < TransactionReceipt > {
930
931
const receipt = await this . getTransactionReceipt ( transactionHash ) ;
931
932
932
933
// Receipt is already good
933
934
if ( ( receipt ? receipt . confirmations : 0 ) >= confirmations ) { return receipt ; }
934
935
935
936
// Poll until the receipt is good...
936
937
return new Promise ( ( resolve , reject ) => {
937
- let timer : NodeJS . Timer = null ;
938
+ const cancelFuncs : Array < ( ) => void > = [ ] ;
939
+
938
940
let done = false ;
941
+ const alreadyDone = function ( ) {
942
+ if ( done ) { return true ; }
943
+ done = true ;
944
+ cancelFuncs . forEach ( ( func ) => { func ( ) ; } ) ;
945
+ return false ;
946
+ } ;
939
947
940
- const handler = ( receipt : TransactionReceipt ) => {
948
+ const minedHandler = ( receipt : TransactionReceipt ) => {
941
949
if ( receipt . confirmations < confirmations ) { return ; }
950
+ if ( alreadyDone ( ) ) { return ; }
951
+ resolve ( receipt ) ;
952
+ }
953
+ this . on ( transactionHash , minedHandler ) ;
954
+ cancelFuncs . push ( ( ) => { this . removeListener ( transactionHash , minedHandler ) ; } ) ;
955
+
956
+ if ( replaceable ) {
957
+ let lastBlockNumber = replaceable . startBlock ;
958
+ let scannedBlock : number = null ;
959
+ const replaceHandler = async ( blockNumber : number ) => {
960
+ if ( done ) { return ; }
961
+
962
+ // Wait 1 second; this is only used in the case of a fault, so
963
+ // we will trade off a little bit of latency for more consistent
964
+ // results and fewer JSON-RPC calls
965
+ await stall ( 1000 ) ;
966
+
967
+ this . getTransactionCount ( replaceable . from ) . then ( async ( nonce ) => {
968
+ if ( done ) { return ; }
969
+
970
+ if ( nonce <= replaceable . nonce ) {
971
+ lastBlockNumber = blockNumber ;
972
+
973
+ } else {
974
+ // First check if the transaction was mined
975
+ {
976
+ const mined = await this . getTransaction ( transactionHash ) ;
977
+ if ( mined && mined . blockNumber != null ) { return ; }
978
+ }
979
+
980
+ // First time scanning. We start a little earlier for some
981
+ // wiggle room here to handle the eventually consistent nature
982
+ // of blockchain (e.g. the getTransactionCount was for a
983
+ // different block)
984
+ if ( scannedBlock == null ) {
985
+ scannedBlock = lastBlockNumber - 3 ;
986
+ if ( scannedBlock < replaceable . startBlock ) {
987
+ scannedBlock = replaceable . startBlock ;
988
+ }
989
+ }
990
+
991
+ while ( scannedBlock <= blockNumber ) {
992
+ if ( done ) { return ; }
993
+
994
+ const block = await this . getBlockWithTransactions ( scannedBlock ) ;
995
+ for ( let ti = 0 ; ti < block . transactions . length ; ti ++ ) {
996
+ const tx = block . transactions [ ti ] ;
997
+
998
+ // Successfully mined!
999
+ if ( tx . hash === transactionHash ) { return ; }
1000
+
1001
+ // Matches our transaction from and nonce; its a replacement
1002
+ if ( tx . from === replaceable . from && tx . nonce === replaceable . nonce ) {
1003
+ if ( done ) { return ; }
1004
+
1005
+ // Get the receipt of the replacement
1006
+ const receipt = await this . waitForTransaction ( tx . hash , confirmations ) ;
1007
+
1008
+ // Already resolved or rejected (prolly a timeout)
1009
+ if ( alreadyDone ( ) ) { return ; }
1010
+
1011
+ // The reason we were replaced
1012
+ let reason = "replaced" ;
1013
+ if ( tx . data === replaceable . data && tx . to === replaceable . to && tx . value . eq ( replaceable . value ) ) {
1014
+ reason = "repriced" ;
1015
+ } else if ( tx . data === "0x" && tx . from === tx . to && tx . value . isZero ( ) ) {
1016
+ reason = "cancelled"
1017
+ }
1018
+
1019
+ // Explain why we were replaced
1020
+ reject ( logger . makeError ( "transaction was replaced" , Logger . errors . TRANSACTION_REPLACED , {
1021
+ cancelled : ( reason === "replaced" || reason === "cancelled" ) ,
1022
+ reason,
1023
+ replacement : this . _wrapTransaction ( tx ) ,
1024
+ hash : transactionHash ,
1025
+ receipt
1026
+ } ) ) ;
1027
+
1028
+ return ;
1029
+ }
1030
+ }
1031
+ scannedBlock ++ ;
1032
+ }
1033
+ }
1034
+
1035
+ if ( done ) { return ; }
1036
+ this . once ( "block" , replaceHandler ) ;
1037
+
1038
+ } , ( error ) => {
1039
+ if ( done ) { return ; }
1040
+ this . once ( "block" , replaceHandler ) ;
1041
+ } ) ;
1042
+ } ;
942
1043
943
- if ( timer ) { clearTimeout ( timer ) ; }
944
1044
if ( done ) { return ; }
945
- done = true ;
1045
+ this . once ( "block" , replaceHandler ) ;
946
1046
947
- this . removeListener ( transactionHash , handler ) ;
948
- resolve ( receipt ) ;
1047
+ cancelFuncs . push ( ( ) => {
1048
+ this . removeListener ( "block" , replaceHandler ) ;
1049
+ } ) ;
949
1050
}
950
- this . on ( transactionHash , handler ) ;
951
1051
952
1052
if ( typeof ( timeout ) === "number" && timeout > 0 ) {
953
- timer = setTimeout ( ( ) => {
954
- if ( done ) { return ; }
955
- timer = null ;
956
- done = true ;
957
-
958
- this . removeListener ( transactionHash , handler ) ;
1053
+ const timer = setTimeout ( ( ) => {
1054
+ if ( alreadyDone ( ) ) { return ; }
959
1055
reject ( logger . makeError ( "timeout exceeded" , Logger . errors . TIMEOUT , { timeout : timeout } ) ) ;
960
1056
} , timeout ) ;
961
1057
if ( timer . unref ) { timer . unref ( ) ; }
1058
+
1059
+ cancelFuncs . push ( ( ) => { clearTimeout ( timer ) ; } ) ;
962
1060
}
963
1061
} ) ;
964
1062
}
@@ -1054,7 +1152,7 @@ export class BaseProvider extends Provider implements EnsProvider {
1054
1152
}
1055
1153
1056
1154
// This should be called by any subclass wrapping a TransactionResponse
1057
- _wrapTransaction ( tx : Transaction , hash ?: string ) : TransactionResponse {
1155
+ _wrapTransaction ( tx : Transaction , hash ?: string , startBlock ?: number ) : TransactionResponse {
1058
1156
if ( hash != null && hexDataLength ( hash ) !== 32 ) { throw new Error ( "invalid response - sendTransaction" ) ; }
1059
1157
1060
1158
const result = < TransactionResponse > tx ;
@@ -1064,18 +1162,25 @@ export class BaseProvider extends Provider implements EnsProvider {
1064
1162
logger . throwError ( "Transaction hash mismatch from Provider.sendTransaction." , Logger . errors . UNKNOWN_ERROR , { expectedHash : tx . hash , returnedHash : hash } ) ;
1065
1163
}
1066
1164
1067
- // @TODO : (confirmations? number, timeout? number)
1068
- result . wait = async ( confirmations ?: number ) => {
1069
-
1070
- // We know this transaction *must* exist (whether it gets mined is
1071
- // another story), so setting an emitted value forces us to
1072
- // wait even if the node returns null for the receipt
1073
- if ( confirmations !== 0 ) {
1074
- this . _emitted [ "t:" + tx . hash ] = "pending" ;
1165
+ result . wait = async ( confirms ?: number , timeout ?: number ) => {
1166
+ if ( confirms == null ) { confirms = 1 ; }
1167
+ if ( timeout == null ) { timeout = 0 ; }
1168
+
1169
+ // Get the details to detect replacement
1170
+ let replacement = undefined ;
1171
+ if ( confirms !== 0 && startBlock != null ) {
1172
+ replacement = {
1173
+ data : tx . data ,
1174
+ from : tx . from ,
1175
+ nonce : tx . nonce ,
1176
+ to : tx . to ,
1177
+ value : tx . value ,
1178
+ startBlock
1179
+ } ;
1075
1180
}
1076
1181
1077
- const receipt = await this . waitForTransaction ( tx . hash , confirmations )
1078
- if ( receipt == null && confirmations === 0 ) { return null ; }
1182
+ const receipt = await this . _waitForTransaction ( tx . hash , confirms , timeout , replacement ) ;
1183
+ if ( receipt == null && confirms === 0 ) { return null ; }
1079
1184
1080
1185
// No longer pending, allow the polling loop to garbage collect this
1081
1186
this . _emitted [ "t:" + tx . hash ] = receipt . blockNumber ;
@@ -1097,9 +1202,10 @@ export class BaseProvider extends Provider implements EnsProvider {
1097
1202
await this . getNetwork ( ) ;
1098
1203
const hexTx = await Promise . resolve ( signedTransaction ) . then ( t => hexlify ( t ) ) ;
1099
1204
const tx = this . formatter . transaction ( signedTransaction ) ;
1205
+ const blockNumber = await this . _getInternalBlockNumber ( 100 + 2 * this . pollingInterval ) ;
1100
1206
try {
1101
1207
const hash = await this . perform ( "sendTransaction" , { signedTransaction : hexTx } ) ;
1102
- return this . _wrapTransaction ( tx , hash ) ;
1208
+ return this . _wrapTransaction ( tx , hash , blockNumber ) ;
1103
1209
} catch ( error ) {
1104
1210
( < any > error ) . transaction = tx ;
1105
1211
( < any > error ) . transactionHash = tx . hash ;
0 commit comments