Skip to content

Commit

Permalink
Implement GetTransaction function
Browse files Browse the repository at this point in the history
The function queries electrum for a details of the specific transaction and
converts it in a format expected by the bitcoin.Chain interface.

The simpliest solution is to use `GetTransaction` function to query electrum
as it returns the transaction details in verbose format. Unfortunatelly it's
not compatible with Esplora/Electrs implementation of ElectrumX server.
(see: Blockstream/electrs#36)

As a workaround to cover integration with Esplora/Electrs we use `GetRawTransaction`
and deserialize the transaction.
  • Loading branch information
nkuba committed Nov 16, 2022
1 parent 8b5b5b3 commit a573d52
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 2 deletions.
31 changes: 29 additions & 2 deletions pkg/bitcoin/electrum/electrum.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,11 +116,38 @@ func Connect(ctx context.Context, config Config) (bitcoin.Chain, error) {
}, nil
}

// GetTransaction gets the transaction with the given transaction hash.
// If the transaction with the given hash was not found on the chain,
// this function returns an error.
func (c *Connection) GetTransaction(
transactionHash bitcoin.Hash,
) (*bitcoin.Transaction, error) {
// TODO: Implementation.
panic("not implemented")
txID := transactionHash.Hex(bitcoin.ReversedByteOrder)

var rawTransaction string
err := wrappers.DoWithDefaultRetry(c.requestRetryTimeout, func(ctx context.Context) error {
// We cannot use `GetTransaction` to get the the transaction details
// as Esplora/Electrs doesn't support verbose transactions.
// See: https://github.com/Blockstream/electrs/pull/36
rawTx, err := c.client.GetRawTransaction(c.ctx, txID)
if err != nil {
return fmt.Errorf("GetRawTransaction failed: [%w]", err)
}

rawTransaction = rawTx

return nil
})
if err != nil {
return nil, fmt.Errorf("failed to get raw transaction [%s]: [%w]", txID, err)
}

result, err := convertRawTransaction(rawTransaction)
if err != nil {
return nil, fmt.Errorf("failed to convert transaction: [%w]", err)
}

return result, nil
}

func (c *Connection) GetTransactionConfirmations(
Expand Down
67 changes: 67 additions & 0 deletions pkg/bitcoin/electrum/transaction.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package electrum

import (
"bytes"
"encoding/hex"
"fmt"

"github.com/btcsuite/btcd/wire"

"github.com/keep-network/keep-core/pkg/bitcoin"
)

// decodeTransaction deserializes a transaction from the hexadecimal serialized
// string to a btcd message format.
func decodeTransaction(rawTx string) (*wire.MsgTx, error) {
headerBytes, err := hex.DecodeString(rawTx)
if err != nil {
return nil, fmt.Errorf("failed to decode a hex string: [%w]", err)
}

buf := bytes.NewBuffer(headerBytes)

var t wire.MsgTx
if err := t.Deserialize(buf); err != nil {
return nil, fmt.Errorf("failed to deserialize a transaction: [%w]", err)
}

return &t, nil
}

// convertRawTransaction transforms a transaction provided in the hexadecimal serialized
// string to the format expected by the bitcoin.Chain interface.
func convertRawTransaction(rawTx string) (*bitcoin.Transaction, error) {
t, err := decodeTransaction(rawTx)
if err != nil {
return nil, fmt.Errorf("failed to decode a transaction: [%w]", err)
}

result := &bitcoin.Transaction{
Version: int32(t.Version),
Locktime: t.LockTime,
}

for _, vin := range t.TxIn {
input := &bitcoin.TransactionInput{
Outpoint: &bitcoin.TransactionOutpoint{
TransactionHash: bitcoin.Hash(vin.PreviousOutPoint.Hash),
OutputIndex: vin.PreviousOutPoint.Index,
},
SignatureScript: vin.SignatureScript,
Sequence: vin.Sequence,
}

result.Inputs = append(result.Inputs, input)
}

for _, vout := range t.TxOut {
output := &bitcoin.TransactionOutput{
Value: vout.Value,
PublicKeyScript: vout.PkScript,
}

result.Outputs = append(result.Outputs, output)
}

return result, nil
}

0 comments on commit a573d52

Please sign in to comment.