@@ -2,7 +2,9 @@ package electrum
22
33import (
44 "context"
5+ "crypto/sha256"
56 "crypto/tls"
7+ "encoding/hex"
68 "fmt"
79 "strings"
810 "time"
@@ -14,6 +16,7 @@ import (
1416
1517 "github.com/keep-network/keep-common/pkg/wrappers"
1618 "github.com/keep-network/keep-core/pkg/bitcoin"
19+ "github.com/keep-network/keep-core/pkg/internal/byteutils"
1720)
1821
1922var (
@@ -150,11 +153,121 @@ func (c *Connection) GetTransaction(
150153 return result , nil
151154}
152155
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.
153159func (c * Connection ) GetTransactionConfirmations (
154160 transactionHash bitcoin.Hash ,
155161) (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
158271}
159272
160273// BroadcastTransaction broadcasts the given transaction over the
0 commit comments