@@ -2,7 +2,9 @@ package electrum
2
2
3
3
import (
4
4
"context"
5
+ "crypto/sha256"
5
6
"crypto/tls"
7
+ "encoding/hex"
6
8
"fmt"
7
9
"strings"
8
10
"time"
@@ -14,6 +16,7 @@ import (
14
16
15
17
"github.com/keep-network/keep-common/pkg/wrappers"
16
18
"github.com/keep-network/keep-core/pkg/bitcoin"
19
+ "github.com/keep-network/keep-core/pkg/internal/byteutils"
17
20
)
18
21
19
22
var (
@@ -150,11 +153,121 @@ func (c *Connection) GetTransaction(
150
153
return result , nil
151
154
}
152
155
156
+ // GetTransactionConfirmations gets the number of confirmations for the
157
+ // transaction with the given transaction hash. If the transaction with the
158
+ // given hash was not found on the chain, this function returns an error.
153
159
func (c * Connection ) GetTransactionConfirmations (
154
160
transactionHash bitcoin.Hash ,
155
161
) (uint , error ) {
156
- // TODO: Implementation.
157
- panic ("not implemented" )
162
+ txID := transactionHash .Hex (bitcoin .ReversedByteOrder )
163
+
164
+ txLogger := logger .With (
165
+ zap .String ("txID" , txID ),
166
+ )
167
+
168
+ var rawTransaction string
169
+ err := wrappers .DoWithDefaultRetry (c .requestRetryTimeout , func (ctx context.Context ) error {
170
+ // We cannot use `GetTransaction` to get the the transaction details
171
+ // as Esplora/Electrs doesn't support verbose transactions.
172
+ // See: https://github.com/Blockstream/electrs/pull/36
173
+ rawTx , err := c .client .GetRawTransaction (c .ctx , txID )
174
+ if err != nil {
175
+ return fmt .Errorf (
176
+ "GetRawTransaction failed: [%w]" ,
177
+ err ,
178
+ )
179
+ }
180
+ rawTransaction = rawTx
181
+ return nil
182
+ })
183
+ if err != nil {
184
+ return 0 , fmt .Errorf ("failed to get raw transaction [%s]: [%w]" , txID , err )
185
+ }
186
+
187
+ tx , err := decodeTransaction (rawTransaction )
188
+ if err != nil {
189
+ return 0 , fmt .Errorf (
190
+ "failed to decode the transaction [%s]: [%w]" ,
191
+ rawTransaction ,
192
+ err ,
193
+ )
194
+ }
195
+
196
+ // As a workaround for the problem described in https://github.com/Blockstream/electrs/pull/36
197
+ // we need to calculate the number of confirmations based on the latest
198
+ // block height and block height of the transaction.
199
+ // Electrum protocol doesn't expose a function to get the transaction's block
200
+ // height (other that the `GetTransaction` that is unsupported by Esplora/Electrs).
201
+ // To get the block height of the transaction we query the history of transactions
202
+ // for the output script hash, as the history contains the transaction's block
203
+ // height.
204
+
205
+ // Initialize txBlockHeigh with minimum int32 value to identify a problem when
206
+ // a block height was not found in a history of any of the script hashes.
207
+ //
208
+ // The history is expected to return a block height for confirmed transaction.
209
+ // If a transaction is unconfirmed (is still in the mempool) the height will
210
+ // have a value of `0` or `-1`.
211
+ txBlockHeight := int32 (math .MinInt32 )
212
+ txOutLoop:
213
+ for _ , txOut := range tx .TxOut {
214
+ script := txOut .PkScript
215
+ scriptHash := sha256 .Sum256 (script )
216
+ reversedScriptHash := byteutils .Reverse (scriptHash [:])
217
+ reversedScriptHashString := hex .EncodeToString (reversedScriptHash )
218
+
219
+ var scriptHashHistory []* electrum.GetMempoolResult
220
+ err := wrappers .DoWithDefaultRetry (c .requestRetryTimeout , func (ctx context.Context ) error {
221
+ history , err := c .client .GetHistory (c .ctx , reversedScriptHashString )
222
+ if err != nil {
223
+ return fmt .Errorf ("GetHistory failed: [%w]" , err )
224
+ }
225
+
226
+ scriptHashHistory = history
227
+
228
+ return nil
229
+ })
230
+ if err != nil {
231
+ // Don't return an error, but continue to the next TxOut entry.
232
+ txLogger .Errorf ("failed to get history for script hash [%s]: [%w]" , err )
233
+ continue txOutLoop
234
+ }
235
+
236
+ for _ , transaction := range scriptHashHistory {
237
+ if transaction .Hash == txID {
238
+ txBlockHeight = transaction .Height
239
+ break txOutLoop
240
+ }
241
+ }
242
+ }
243
+
244
+ // History querying didn't come up with the transaction's block height. Return
245
+ // an error.
246
+ if txBlockHeight == math .MinInt32 {
247
+ return 0 , fmt .Errorf (
248
+ "failed to find the transaction block height in script hashes' histories" ,
249
+ )
250
+ }
251
+
252
+ // If the block height is greater than `0` the transaction is confirmed.
253
+ if txBlockHeight > 0 {
254
+ latestBlockHeight , err := c .GetLatestBlockHeight ()
255
+ if err != nil {
256
+ return 0 , fmt .Errorf (
257
+ "failed to get the latest block height: [%w]" ,
258
+ err ,
259
+ )
260
+ }
261
+
262
+ if latestBlockHeight >= uint (txBlockHeight ) {
263
+ // Add `1` to the calculated difference as if the transaction block
264
+ // height equals the latest block height the transaction is already
265
+ // confirmed, so it has one confirmation.
266
+ return latestBlockHeight - uint (txBlockHeight ) + 1 , nil
267
+ }
268
+ }
269
+
270
+ return 0 , nil
158
271
}
159
272
160
273
// BroadcastTransaction broadcasts the given transaction over the
0 commit comments