From 64efe3f938133dbd91badb927966c8bf7f2d4383 Mon Sep 17 00:00:00 2001 From: Joshua Sing Date: Wed, 4 Sep 2024 00:40:37 +1000 Subject: [PATCH] tbc: add block-by-hash command to RPC, use chainhash (#224) --- Makefile | 3 +- api/tbcapi/tbcapi.go | 156 +++++++++++++++++++++------------ database/tbcd/level/level.go | 2 +- service/tbc/crawler.go | 2 +- service/tbc/rpc.go | 163 +++++++++++++++++++++++++---------- service/tbc/rpc_test.go | 99 ++++++--------------- service/tbc/tbc.go | 28 +++--- service/tbc/tbc_test.go | 8 +- 8 files changed, 266 insertions(+), 195 deletions(-) diff --git a/Makefile b/Makefile index ac439dd4..1c64c062 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,8 @@ PROJECTPATH = $(abspath $(dir $(realpath $(firstword $(MAKEFILE_LIST))))) export GOBIN=$(PROJECTPATH)/bin export GOCACHE=$(PROJECTPATH)/.gocache export GOPKG=$(PROJECTPATH)/pkg -GO_LDFLAGS="" + +GO_LDFLAGS= DIST=$(PROJECTPATH)/dist project = heminetwork diff --git a/api/tbcapi/tbcapi.go b/api/tbcapi/tbcapi.go index 6fa0f94c..4111b91d 100644 --- a/api/tbcapi/tbcapi.go +++ b/api/tbcapi/tbcapi.go @@ -10,6 +10,8 @@ import ( "maps" "reflect" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/hemilabs/heminetwork/api" "github.com/hemilabs/heminetwork/api/protocol" ) @@ -20,6 +22,12 @@ const ( CmdPingRequest = "tbcapi-ping-request" CmdPingResponse = "tbcapi-ping-response" + CmdBlockByHashRawRequest = "tbcapi-block-by-hash-raw-request" + CmdBlockByHashRawResponse = "tbcapi-block-by-hash-raw-response" + + CmdBlockByHashRequest = "tbcapi-block-by-hash-request" + CmdBlockByHashResponse = "tbcapi-block-by-hash-response" + CmdBlockHeadersByHeightRawRequest = "tbcapi-block-headers-by-height-raw-request" CmdBlockHeadersByHeightRawResponse = "tbcapi-block-headers-by-height-raw-response" @@ -35,11 +43,11 @@ const ( CmdBalanceByAddressRequest = "tbcapi-balance-by-address-request" CmdBalanceByAddressResponse = "tbcapi-balance-by-address-response" - CmdUtxosByAddressRawRequest = "tbcapi-utxos-by-address-raw-request" - CmdUtxosByAddressRawResponse = "tbcapi-utxos-by-address-raw-response" + CmdUTXOsByAddressRawRequest = "tbcapi-utxos-by-address-raw-request" + CmdUTXOsByAddressRawResponse = "tbcapi-utxos-by-address-raw-response" - CmdUtxosByAddressRequest = "tbcapi-utxos-by-address-request" - CmdUtxosByAddressResponse = "tbcapi-utxos-by-address-response" + CmdUTXOsByAddressRequest = "tbcapi-utxos-by-address-request" + CmdUTXOsByAddressResponse = "tbcapi-utxos-by-address-response" CmdTxByIdRawRequest = "tbcapi-tx-by-id-raw-request" CmdTxByIdRawResponse = "tbcapi-tx-by-id-raw-response" @@ -61,13 +69,81 @@ type ( PingResponse protocol.PingResponse ) +// TxWitness represents a Bitcoin transaction witness. +type TxWitness []api.ByteSlice + +// TxIn represents a Bitcoin transaction input. +type TxIn struct { + PreviousOutPoint OutPoint `json:"outpoint"` + SignatureScript api.ByteSlice `json:"signature_script"` + Witness TxWitness `json:"tx_witness"` + Sequence uint32 `json:"sequence"` +} + +// OutPoint is a transaction out point. +type OutPoint struct { + Hash chainhash.Hash `json:"hash"` + Index uint32 `json:"index"` +} + +// TxOut represents a Bitcoin transaction output. +type TxOut struct { + Value int64 `json:"value"` + PkScript api.ByteSlice `json:"pk_script"` +} + +// Tx represents a Bitcoin transaction. +type Tx struct { + Version int32 `json:"version"` + LockTime uint32 `json:"lock_time"` + TxIn []*TxIn `json:"tx_in"` + TxOut []*TxOut `json:"tx_out"` +} + +// UTXO represents a Bitcoin unspent transaction output. +type UTXO struct { + TxId chainhash.Hash `json:"tx_id"` + Value uint64 `json:"value"` + OutIndex uint32 `json:"out_index"` +} + +// BlockHeader represents a Bitcoin block header. type BlockHeader struct { - Version int32 `json:"version"` - PrevHash api.ByteSlice `json:"prev_hash"` // reverse order - MerkleRoot api.ByteSlice `json:"merkle_root"` // reverse order - Timestamp int64 `json:"timestamp"` - Bits string `json:"bits"` - Nonce uint32 `json:"nonce"` + Version int32 `json:"version"` + PrevHash chainhash.Hash `json:"prev_hash"` + MerkleRoot chainhash.Hash `json:"merkle_root"` + Timestamp int64 `json:"timestamp"` + Bits string `json:"bits"` + Nonce uint32 `json:"nonce"` +} + +// Block represents a Bitcoin block. +type Block struct { + Hash chainhash.Hash `json:"hash"` + Header BlockHeader `json:"header"` + Txs []Tx `json:"txs"` +} + +// BlockByHashRequest requests a [Block] by its hash. +type BlockByHashRequest struct { + Hash *chainhash.Hash `json:"hash"` +} + +// BlockByHashResponse is the response for [BlockByHashRequest]. +type BlockByHashResponse struct { + Block *Block `json:"block"` + Error *protocol.Error `json:"error,omitempty"` +} + +// BlockByHashRawRequest requests a raw block by its hash. +type BlockByHashRawRequest struct { + Hash *chainhash.Hash `json:"hash"` +} + +// BlockByHashRawResponse is the response for [BlockByHashRawRequest]. +type BlockByHashRawResponse struct { + Block api.ByteSlice `json:"block"` + Error *protocol.Error `json:"error,omitempty"` } type BlockHeadersByHeightRawRequest struct { @@ -113,36 +189,30 @@ type BalanceByAddressResponse struct { Error *protocol.Error `json:"error,omitempty"` } -type UtxosByAddressRawRequest struct { +type UTXOsByAddressRawRequest struct { Address string `json:"address"` Start uint `json:"start"` Count uint `json:"count"` } -type UtxosByAddressRawResponse struct { - Utxos []api.ByteSlice `json:"utxos"` +type UTXOsByAddressRawResponse struct { + UTXOs []api.ByteSlice `json:"utxos"` Error *protocol.Error `json:"error,omitempty"` } -type UtxosByAddressRequest struct { +type UTXOsByAddressRequest struct { Address string `json:"address"` Start uint `json:"start"` Count uint `json:"count"` } -type Utxo struct { - TxId api.ByteSlice `json:"tx_id"` // reverse order - Value uint64 `json:"value"` - OutIndex uint32 `json:"out_index"` -} - -type UtxosByAddressResponse struct { - Utxos []*Utxo `json:"utxos"` +type UTXOsByAddressResponse struct { + UTXOs []*UTXO `json:"utxos"` Error *protocol.Error `json:"error,omitempty"` } type TxByIdRawRequest struct { - TxId api.ByteSlice `json:"tx_id"` // natural order + TxID *chainhash.Hash `json:"tx_id"` } type TxByIdRawResponse struct { @@ -151,7 +221,7 @@ type TxByIdRawResponse struct { } type TxByIdRequest struct { - TxId api.ByteSlice `json:"tx_id"` // reverse order + TxID *chainhash.Hash `json:"tx_id"` } type TxByIdResponse struct { @@ -159,35 +229,13 @@ type TxByIdResponse struct { Error *protocol.Error `json:"error,omitempty"` } -type OutPoint struct { - Hash api.ByteSlice `json:"hash"` // reverse order - Index uint32 `json:"index"` -} - -type TxWitness []api.ByteSlice - -type TxIn struct { - PreviousOutPoint OutPoint `json:"outpoint"` - SignatureScript api.ByteSlice `json:"signature_script"` - Witness TxWitness `json:"tx_witness"` - Sequence uint32 `json:"sequence"` -} - -type TxOut struct { - Value int64 `json:"value"` - PkScript api.ByteSlice `json:"pk_script"` -} - -type Tx struct { - Version int32 `json:"version"` - LockTime uint32 `json:"lock_time"` - TxIn []*TxIn `json:"tx_in"` - TxOut []*TxOut `json:"tx_out"` -} - var commands = map[protocol.Command]reflect.Type{ CmdPingRequest: reflect.TypeOf(PingRequest{}), CmdPingResponse: reflect.TypeOf(PingResponse{}), + CmdBlockByHashRequest: reflect.TypeOf(BlockByHashRequest{}), + CmdBlockByHashResponse: reflect.TypeOf(BlockByHashResponse{}), + CmdBlockByHashRawRequest: reflect.TypeOf(BlockByHashRawRequest{}), + CmdBlockByHashRawResponse: reflect.TypeOf(BlockByHashRawResponse{}), CmdBlockHeadersByHeightRawRequest: reflect.TypeOf(BlockHeadersByHeightRawRequest{}), CmdBlockHeadersByHeightRawResponse: reflect.TypeOf(BlockHeadersByHeightRawResponse{}), CmdBlockHeadersByHeightRequest: reflect.TypeOf(BlockHeadersByHeightRequest{}), @@ -198,10 +246,10 @@ var commands = map[protocol.Command]reflect.Type{ CmdBlockHeaderBestResponse: reflect.TypeOf(BlockHeaderBestResponse{}), CmdBalanceByAddressRequest: reflect.TypeOf(BalanceByAddressRequest{}), CmdBalanceByAddressResponse: reflect.TypeOf(BalanceByAddressResponse{}), - CmdUtxosByAddressRawRequest: reflect.TypeOf(UtxosByAddressRawRequest{}), - CmdUtxosByAddressRawResponse: reflect.TypeOf(UtxosByAddressRawResponse{}), - CmdUtxosByAddressRequest: reflect.TypeOf(UtxosByAddressRequest{}), - CmdUtxosByAddressResponse: reflect.TypeOf(UtxosByAddressResponse{}), + CmdUTXOsByAddressRawRequest: reflect.TypeOf(UTXOsByAddressRawRequest{}), + CmdUTXOsByAddressRawResponse: reflect.TypeOf(UTXOsByAddressRawResponse{}), + CmdUTXOsByAddressRequest: reflect.TypeOf(UTXOsByAddressRequest{}), + CmdUTXOsByAddressResponse: reflect.TypeOf(UTXOsByAddressResponse{}), CmdTxByIdRawRequest: reflect.TypeOf(TxByIdRawRequest{}), CmdTxByIdRawResponse: reflect.TypeOf(TxByIdRawResponse{}), CmdTxByIdRequest: reflect.TypeOf(TxByIdRequest{}), diff --git a/database/tbcd/level/level.go b/database/tbcd/level/level.go index aa5d9d82..77255acf 100644 --- a/database/tbcd/level/level.go +++ b/database/tbcd/level/level.go @@ -36,7 +36,7 @@ import ( // Blocks // // Balances -// Utxos +// UTXOs const ( ldbVersion = 1 diff --git a/service/tbc/crawler.go b/service/tbc/crawler.go index 7f0a2620..d57fd889 100644 --- a/service/tbc/crawler.go +++ b/service/tbc/crawler.go @@ -1134,7 +1134,7 @@ func (s *Server) SyncIndexersToHash(ctx context.Context, hash *chainhash.Hash) e log.Debugf("Syncing indexes to: %v", hash) - // Utxos + // UTXOs if err := s.UtxoIndexer(ctx, hash); err != nil { return fmt.Errorf("utxo indexer: %w", err) } diff --git a/service/tbc/rpc.go b/service/tbc/rpc.go index 4dad00f0..079ec8e1 100644 --- a/service/tbc/rpc.go +++ b/service/tbc/rpc.go @@ -12,7 +12,6 @@ import ( "fmt" "io" "net/http" - "slices" "sync" "time" @@ -62,6 +61,20 @@ func (s *Server) handleWebsocketRead(ctx context.Context, ws *tbcWs) { switch cmd { case tbcapi.CmdPingRequest: err = s.handlePingRequest(ctx, ws, payload, id) + case tbcapi.CmdBlockByHashRequest: + handler := func(ctx context.Context) (any, error) { + req := payload.(*tbcapi.BlockByHashRequest) + return s.handleBlockByHashRequest(ctx, req) + } + + go s.handleRequest(ctx, ws, id, cmd, handler) + case tbcapi.CmdBlockByHashRawRequest: + handler := func(ctx context.Context) (any, error) { + req := payload.(*tbcapi.BlockByHashRawRequest) + return s.handleBlockByHashRawRequest(ctx, req) + } + + go s.handleRequest(ctx, ws, id, cmd, handler) case tbcapi.CmdBlockHeadersByHeightRequest: handler := func(ctx context.Context) (any, error) { req := payload.(*tbcapi.BlockHeadersByHeightRequest) @@ -97,16 +110,16 @@ func (s *Server) handleWebsocketRead(ctx context.Context, ws *tbcWs) { } go s.handleRequest(ctx, ws, id, cmd, handler) - case tbcapi.CmdUtxosByAddressRawRequest: + case tbcapi.CmdUTXOsByAddressRawRequest: handler := func(ctx context.Context) (any, error) { - req := payload.(*tbcapi.UtxosByAddressRawRequest) + req := payload.(*tbcapi.UTXOsByAddressRawRequest) return s.handleUtxosByAddressRawRequest(ctx, req) } go s.handleRequest(ctx, ws, id, cmd, handler) - case tbcapi.CmdUtxosByAddressRequest: + case tbcapi.CmdUTXOsByAddressRequest: handler := func(ctx context.Context) (any, error) { - req := payload.(*tbcapi.UtxosByAddressRequest) + req := payload.(*tbcapi.UTXOsByAddressRequest) return s.handleUtxosByAddressRequest(ctx, req) } @@ -195,6 +208,60 @@ func (s *Server) handlePingRequest(ctx context.Context, ws *tbcWs, payload any, return nil } +func (s *Server) handleBlockByHashRequest(ctx context.Context, req *tbcapi.BlockByHashRequest) (any, error) { + log.Tracef("handleBlockByHashRequest") + defer log.Tracef("handleBlockByHashRequest exit") + + block, err := s.BlockByHash(ctx, req.Hash) + if err != nil { + if errors.Is(err, database.ErrNotFound) { + return &tbcapi.BlockByHashResponse{ + Error: protocol.RequestErrorf("block not found with hash %s", req.Hash), + }, nil + } + + e := protocol.NewInternalError(err) + return &tbcapi.BlockByHashResponse{ + Error: e.ProtocolError(), + }, e + } + + return &tbcapi.BlockByHashResponse{ + Block: wireBlockToTBC(block.MsgBlock()), + }, nil +} + +func (s *Server) handleBlockByHashRawRequest(ctx context.Context, req *tbcapi.BlockByHashRawRequest) (any, error) { + log.Tracef("handleBlockByHashRawRequest") + defer log.Tracef("handleBlockByHashRawRequest exit") + + block, err := s.BlockByHash(ctx, req.Hash) + if err != nil { + if errors.Is(err, database.ErrNotFound) { + return &tbcapi.BlockByHashRawResponse{ + Error: protocol.RequestErrorf("block not found with hash: %s", req.Hash), + }, nil + } + + e := protocol.NewInternalError(err) + return &tbcapi.BlockByHashRawResponse{ + Error: e.ProtocolError(), + }, e + } + + b, err := block.Bytes() + if err != nil { + e := protocol.NewInternalError(err) + return &tbcapi.BlockByHashRawResponse{ + Error: e.ProtocolError(), + }, e + } + + return &tbcapi.BlockByHashRawResponse{ + Block: b, + }, nil +} + func (s *Server) handleBlockHeadersByHeightRequest(ctx context.Context, req *tbcapi.BlockHeadersByHeightRequest) (any, error) { log.Tracef("handleBtcBlockHeadersByHeightRequest") defer log.Tracef("handleBtcBlockHeadersByHeightRequest exit") @@ -294,7 +361,7 @@ func (s *Server) handleBalanceByAddressRequest(ctx context.Context, req *tbcapi. }, nil } -func (s *Server) handleUtxosByAddressRawRequest(ctx context.Context, req *tbcapi.UtxosByAddressRawRequest) (any, error) { +func (s *Server) handleUtxosByAddressRawRequest(ctx context.Context, req *tbcapi.UTXOsByAddressRawRequest) (any, error) { log.Tracef("handleUtxosByAddressRawRequest") defer log.Tracef("handleUtxosByAddressRawRequest exit") @@ -302,12 +369,12 @@ func (s *Server) handleUtxosByAddressRawRequest(ctx context.Context, req *tbcapi if err != nil { if errors.Is(err, level.ErrIterator) { e := protocol.NewInternalError(err) - return &tbcapi.UtxosByAddressRawResponse{ + return &tbcapi.UTXOsByAddressRawResponse{ Error: e.ProtocolError(), }, err } - return &tbcapi.UtxosByAddressRawResponse{ + return &tbcapi.UTXOsByAddressRawResponse{ Error: protocol.RequestErrorf("error getting utxos for address: %s", req.Address), }, nil } @@ -317,12 +384,12 @@ func (s *Server) handleUtxosByAddressRawRequest(ctx context.Context, req *tbcapi responseUtxos = append(responseUtxos, utxo[:]) } - return &tbcapi.UtxosByAddressRawResponse{ - Utxos: responseUtxos, + return &tbcapi.UTXOsByAddressRawResponse{ + UTXOs: responseUtxos, }, nil } -func (s *Server) handleUtxosByAddressRequest(ctx context.Context, req *tbcapi.UtxosByAddressRequest) (any, error) { +func (s *Server) handleUtxosByAddressRequest(ctx context.Context, req *tbcapi.UTXOsByAddressRequest) (any, error) { log.Tracef("handleUtxosByAddressRequest") defer log.Tracef("handleUtxosByAddressRequest exit") @@ -330,27 +397,35 @@ func (s *Server) handleUtxosByAddressRequest(ctx context.Context, req *tbcapi.Ut if err != nil { if errors.Is(err, level.ErrIterator) { e := protocol.NewInternalError(err) - return &tbcapi.UtxosByAddressResponse{ + return &tbcapi.UTXOsByAddressResponse{ Error: e.ProtocolError(), }, e } - return &tbcapi.UtxosByAddressResponse{ + return &tbcapi.UTXOsByAddressResponse{ Error: protocol.RequestErrorf("error getting utxos for address: %s", req.Address), }, nil } - var responseUtxos []*tbcapi.Utxo + var responseUtxos []*tbcapi.UTXO for _, utxo := range utxos { - responseUtxos = append(responseUtxos, &tbcapi.Utxo{ - TxId: reverseBytes(utxo.ScriptHashSlice()), + txId, err := chainhash.NewHash(utxo.ScriptHashSlice()) + if err != nil { + e := protocol.NewInternalError(err) + return &tbcapi.UTXOsByAddressResponse{ + Error: e.ProtocolError(), + }, e + } + + responseUtxos = append(responseUtxos, &tbcapi.UTXO{ + TxId: *txId, Value: utxo.Value(), OutIndex: utxo.OutputIndex(), }) } - return &tbcapi.UtxosByAddressResponse{ - Utxos: responseUtxos, + return &tbcapi.UTXOsByAddressResponse{ + UTXOs: responseUtxos, }, nil } @@ -358,18 +433,10 @@ func (s *Server) handleTxByIdRawRequest(ctx context.Context, req *tbcapi.TxByIdR log.Tracef("handleTxByIdRawRequest") defer log.Tracef("handleTxByIdRawRequest exit") - txId, err := chainhash.NewHash(req.TxId) - if err != nil { - responseErr := protocol.RequestErrorf("invalid tx id") - return &tbcapi.TxByIdRawResponse{ - Error: responseErr, - }, nil - } - - tx, err := s.TxById(ctx, txId) + tx, err := s.TxById(ctx, req.TxID) if err != nil { if errors.Is(err, database.ErrNotFound) { - responseErr := protocol.RequestErrorf("tx not found: %s", req.TxId) + responseErr := protocol.RequestErrorf("tx not found: %s", req.TxID) return &tbcapi.TxByIdRawResponse{ Error: responseErr, }, nil @@ -398,18 +465,10 @@ func (s *Server) handleTxByIdRequest(ctx context.Context, req *tbcapi.TxByIdRequ log.Tracef("handleTxByIdRequest") defer log.Tracef("handleTxByIdRequest exit") - txId, err := chainhash.NewHash(req.TxId) - if err != nil { - responseErr := protocol.RequestErrorf("invalid tx id") - return &tbcapi.TxByIdResponse{ - Error: responseErr, - }, nil - } - - tx, err := s.TxById(ctx, txId) + tx, err := s.TxById(ctx, req.TxID) if err != nil { if errors.Is(err, database.ErrNotFound) { - responseErr := protocol.RequestErrorf("tx not found: %s", req.TxId) + responseErr := protocol.RequestErrorf("tx not found: %s", req.TxID) return &tbcapi.TxByIdResponse{ Error: responseErr, }, nil @@ -513,6 +572,20 @@ func randHexId(length int) (string, error) { return hex.EncodeToString(b), nil } +// wireBlockToTBC converts a wire.MsgBlock to a tbcapi.Block. +func wireBlockToTBC(block *wire.MsgBlock) *tbcapi.Block { + txs := make([]tbcapi.Tx, len(block.Transactions)) + for i, tx := range block.Transactions { + txs[i] = *wireTxToTBC(tx) + } + return &tbcapi.Block{ + Hash: block.BlockHash(), + Header: *wireBlockHeaderToTBC(&block.Header), + Txs: txs, + } +} + +// wireBlockHeadersToBTC converts []*wire.BlockHeader to []*tbcapi.BlockHeader. func wireBlockHeadersToTBC(bhs []*wire.BlockHeader) []*tbcapi.BlockHeader { blockHeaders := make([]*tbcapi.BlockHeader, len(bhs)) for i, bh := range bhs { @@ -521,17 +594,19 @@ func wireBlockHeadersToTBC(bhs []*wire.BlockHeader) []*tbcapi.BlockHeader { return blockHeaders } +// wireBlockHeaderToTBC converts a wire.BlockHeader to a tbcapi.BlockHeader. func wireBlockHeaderToTBC(bh *wire.BlockHeader) *tbcapi.BlockHeader { return &tbcapi.BlockHeader{ Version: bh.Version, - PrevHash: reverseBytes(bh.PrevBlock[:]), - MerkleRoot: reverseBytes(bh.MerkleRoot[:]), + PrevHash: bh.PrevBlock, + MerkleRoot: bh.MerkleRoot, Timestamp: bh.Timestamp.Unix(), Bits: fmt.Sprintf("%x", bh.Bits), Nonce: bh.Nonce, } } +// wireTxToTBC converts a wire.MsgTx to tbcapi.Tx. func wireTxToTBC(w *wire.MsgTx) *tbcapi.Tx { tx := &tbcapi.Tx{ Version: w.Version, @@ -545,7 +620,7 @@ func wireTxToTBC(w *wire.MsgTx) *tbcapi.Tx { Sequence: txIn.Sequence, SignatureScript: txIn.SignatureScript, PreviousOutPoint: tbcapi.OutPoint{ - Hash: reverseBytes(txIn.PreviousOutPoint.Hash[:]), + Hash: txIn.PreviousOutPoint.Hash, Index: txIn.PreviousOutPoint.Index, }, Witness: make(tbcapi.TxWitness, len(txIn.Witness)), @@ -565,9 +640,3 @@ func wireTxToTBC(w *wire.MsgTx) *tbcapi.Tx { return tx } - -// XXX this probably should not exist, it means the code is busted instead -func reverseBytes(b []byte) []byte { - slices.Reverse(b) - return b -} diff --git a/service/tbc/rpc_test.go b/service/tbc/rpc_test.go index 5fee8ca7..4c277269 100644 --- a/service/tbc/rpc_test.go +++ b/service/tbc/rpc_test.go @@ -6,16 +6,13 @@ package tbc import ( "context" - "encoding/hex" "encoding/json" - "slices" "strings" "testing" "time" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/coder/websocket" "github.com/coder/websocket/wsjson" "github.com/davecgh/go-spew/spew" @@ -707,7 +704,7 @@ func TestUtxosByAddressRaw(t *testing.T) { conn: protocol.NewWSConn(c), } - var response tbcapi.UtxosByAddressRawResponse + var response tbcapi.UTXOsByAddressRawResponse select { case <-time.After(1 * time.Second): case <-ctx.Done(): @@ -715,7 +712,7 @@ func TestUtxosByAddressRaw(t *testing.T) { } indexAll(ctx, t, tbcServer) - if err := tbcapi.Write(ctx, tws.conn, "someid", tbcapi.UtxosByAddressRawRequest{ + if err := tbcapi.Write(ctx, tws.conn, "someid", tbcapi.UTXOsByAddressRawRequest{ Address: tti.address(), Start: uint(tti.start), Count: uint(tti.limit), @@ -728,7 +725,7 @@ func TestUtxosByAddressRaw(t *testing.T) { t.Fatal(err) } - if v.Header.Command != tbcapi.CmdUtxosByAddressRawResponse { + if v.Header.Command != tbcapi.CmdUTXOsByAddressRawResponse { t.Fatalf("received unexpected command: %s", v.Header.Command) } @@ -743,9 +740,9 @@ func TestUtxosByAddressRaw(t *testing.T) { expectedCount = tti.limit } - if !tti.doNotGenerate && len(response.Utxos) != int(expectedCount) { - t.Fatalf("should have %d utxos, received: %d", expectedCount, len(response.Utxos)) - } else if tti.doNotGenerate && len(response.Utxos) != 0 { + if !tti.doNotGenerate && len(response.UTXOs) != int(expectedCount) { + t.Fatalf("should have %d utxos, received: %d", expectedCount, len(response.UTXOs)) + } else if tti.doNotGenerate && len(response.UTXOs) != 0 { t.Fatalf("did not generate any blocks for address, should not have utxos") } }) @@ -919,7 +916,7 @@ func TestUtxosByAddress(t *testing.T) { conn: protocol.NewWSConn(c), } - var response tbcapi.UtxosByAddressResponse + var response tbcapi.UTXOsByAddressResponse select { case <-time.After(1 * time.Second): case <-ctx.Done(): @@ -927,7 +924,7 @@ func TestUtxosByAddress(t *testing.T) { } indexAll(ctx, t, tbcServer) - if err := tbcapi.Write(ctx, tws.conn, "someid", tbcapi.UtxosByAddressRequest{ + if err := tbcapi.Write(ctx, tws.conn, "someid", tbcapi.UTXOsByAddressRequest{ Address: tti.address(), Start: uint(tti.start), Count: uint(tti.limit), @@ -940,7 +937,7 @@ func TestUtxosByAddress(t *testing.T) { t.Fatal(err) } - if v.Header.Command != tbcapi.CmdUtxosByAddressResponse { + if v.Header.Command != tbcapi.CmdUTXOsByAddressResponse { t.Fatalf("received unexpected command: %s", v.Header.Command) } @@ -955,9 +952,9 @@ func TestUtxosByAddress(t *testing.T) { expectedCount = tti.limit } - if !tti.doNotGenerate && len(response.Utxos) != int(expectedCount) { - t.Fatalf("should have %d utxos, received: %d", expectedCount, len(response.Utxos)) - } else if tti.doNotGenerate && len(response.Utxos) != 0 { + if !tti.doNotGenerate && len(response.UTXOs) != int(expectedCount) { + t.Fatalf("should have %d utxos, received: %d", expectedCount, len(response.UTXOs)) + } else if tti.doNotGenerate && len(response.UTXOs) != 0 { t.Fatalf("did not generate any blocks for address, should not have utxos") } }) @@ -1007,15 +1004,9 @@ func TestTxByIdRaw(t *testing.T) { indexAll(ctx, t, tbcServer) txId := getRandomTxId(ctx, t, bitcoindContainer) - txIdBytes, err := hex.DecodeString(txId) - if err != nil { - t.Fatal(err) - } - - slices.Reverse(txIdBytes) // convert to natural order if err := tbcapi.Write(ctx, tws.conn, "someid", tbcapi.TxByIdRawRequest{ - TxId: txIdBytes, + TxID: txId, }); err != nil { t.Fatal(err) } @@ -1047,8 +1038,9 @@ func TestTxByIdRaw(t *testing.T) { } // is the hash equal to what we queried for? - if tx.TxHash().String() != txId { - t.Fatalf("id mismatch: %s != %s", tx.TxHash().String(), txId) + txHash := tx.TxHash() + if !txId.IsEqual(&txHash) { + t.Fatalf("id mismatch: %s != %s", txHash, txId) } } @@ -1094,17 +1086,10 @@ func TestTxByIdRawInvalid(t *testing.T) { indexAll(ctx, t, tbcServer) txId := getRandomTxId(ctx, t, bitcoindContainer) - txIdBytes, err := hex.DecodeString(txId) - if err != nil { - t.Fatal(err) - } - - txIdBytes[0]++ - - slices.Reverse(txIdBytes) // convert to natural order + txId[0]++ if err := tbcapi.Write(ctx, tws.conn, "someid", tbcapi.TxByIdRawRequest{ - TxId: txIdBytes, + TxID: txId, }); err != nil { t.Fatal(err) } @@ -1190,17 +1175,10 @@ func TestTxByIdRawNotFound(t *testing.T) { indexAll(ctx, t, tbcServer) txId := getRandomTxId(ctx, t, bitcoindContainer) - txIdBytes, err := hex.DecodeString(txId) - if err != nil { - t.Fatal(err) - } - - txIdBytes = append(txIdBytes, 8) - - slices.Reverse(txIdBytes) // convert to natural order + txId[len(txId)-1] = 8 if err := tbcapi.Write(ctx, tws.conn, "someid", tbcapi.TxByIdRawRequest{ - TxId: txIdBytes, + TxID: txId, }); err != nil { t.Fatal(err) } @@ -1223,7 +1201,7 @@ func TestTxByIdRawNotFound(t *testing.T) { } if response.Error != nil { - if !strings.Contains(response.Error.Message, "invalid tx id") { + if !strings.Contains(response.Error.Message, "tx not found") { t.Fatalf("incorrect error found: %s", response.Error.Message) } } @@ -1274,19 +1252,8 @@ func TestTxById(t *testing.T) { indexAll(ctx, t, tbcServer) txId := getRandomTxId(ctx, t, bitcoindContainer) - txIdBytes, err := hex.DecodeString(txId) - if err != nil { - t.Fatal(err) - } - - slices.Reverse(txIdBytes) - - ctxid, err := chainhash.NewHash(txIdBytes) - if err != nil { - t.Fatal(err) - } if err := tbcapi.Write(ctx, tws.conn, "someid", tbcapi.TxByIdRequest{ - TxId: ctxid[:], + TxID: txId, }); err != nil { t.Fatal(err) } @@ -1308,7 +1275,7 @@ func TestTxById(t *testing.T) { t.Fatal(response.Error.Message) } - tx, err := tbcServer.TxById(ctx, ctxid) + tx, err := tbcServer.TxById(ctx, txId) if err != nil { t.Fatal(err) } @@ -1362,15 +1329,10 @@ func TestTxByIdInvalid(t *testing.T) { indexAll(ctx, t, tbcServer) txId := getRandomTxId(ctx, t, bitcoindContainer) - txIdBytes, err := hex.DecodeString(txId) - if err != nil { - t.Fatal(err) - } - - txIdBytes[0]++ + txId[0]++ if err := tbcapi.Write(ctx, tws.conn, "someid", tbcapi.TxByIdRequest{ - TxId: txIdBytes, + TxID: txId, }); err != nil { t.Fatal(err) } @@ -1457,15 +1419,10 @@ func TestTxByIdNotFound(t *testing.T) { indexAll(ctx, t, tbcServer) txId := getRandomTxId(ctx, t, bitcoindContainer) - txIdBytes, err := hex.DecodeString(txId) - if err != nil { - t.Fatal(err) - } - - txIdBytes = append(txIdBytes, 8) + txId[len(txId)-1] = 8 if err := tbcapi.Write(ctx, tws.conn, "someid", tbcapi.TxByIdRequest{ - TxId: txIdBytes, + TxID: txId, }); err != nil { t.Fatal(err) } @@ -1488,7 +1445,7 @@ func TestTxByIdNotFound(t *testing.T) { } if response.Error != nil { - if !strings.Contains(response.Error.Message, "invalid tx id") { + if !strings.Contains(response.Error.Message, "tx not found") { t.Fatalf("incorrect error found: %s", response.Error.Message) } } diff --git a/service/tbc/tbc.go b/service/tbc/tbc.go index 7cc4002d..ddce28fa 100644 --- a/service/tbc/tbc.go +++ b/service/tbc/tbc.go @@ -1333,7 +1333,12 @@ func (s *Server) insertGenesis(ctx context.Context) error { return nil } -// +// BlockByHash returns a block with the given hash. +func (s *Server) BlockByHash(ctx context.Context, hash *chainhash.Hash) (*btcutil.Block, error) { + log.Tracef("BlockByHash") + defer log.Tracef("BlockByHash exit") + return s.db.BlockByHash(ctx, hash) +} func (s *Server) BlockHeaderByHash(ctx context.Context, hash *chainhash.Hash) (*wire.BlockHeader, uint64, error) { log.Tracef("BlockHeaderByHash") @@ -1350,32 +1355,19 @@ func (s *Server) BlockHeaderByHash(ctx context.Context, hash *chainhash.Hash) (* return bhw, bh.Height, nil } -func (s *Server) blockHeadersByHeight(ctx context.Context, height uint64) ([]tbcd.BlockHeader, error) { - log.Tracef("blockHeadersByHeight") - defer log.Tracef("blockHeadersByHeight exit") - - bhs, err := s.db.BlockHeadersByHeight(ctx, height) - if err != nil { - return nil, fmt.Errorf("db block header by height: %w", err) - } - - return bhs, nil -} - func (s *Server) RawBlockHeadersByHeight(ctx context.Context, height uint64) ([]api.ByteSlice, error) { log.Tracef("RawBlockHeadersByHeight") defer log.Tracef("RawBlockHeadersByHeight exit") - bhs, err := s.blockHeadersByHeight(ctx, height) + bhs, err := s.db.BlockHeadersByHeight(ctx, height) if err != nil { return nil, err } var headers []api.ByteSlice for _, bh := range bhs { - headers = append(headers, []byte(bh.Header[:])) + headers = append(headers, bh.Header[:]) } - return headers, nil } @@ -1383,7 +1375,7 @@ func (s *Server) BlockHeadersByHeight(ctx context.Context, height uint64) ([]*wi log.Tracef("BlockHeadersByHeight") defer log.Tracef("BlockHeadersByHeight exit") - blockHeaders, err := s.blockHeadersByHeight(ctx, height) + blockHeaders, err := s.db.BlockHeadersByHeight(ctx, height) if err != nil { return nil, err } @@ -1409,7 +1401,7 @@ func (s *Server) RawBlockHeaderBest(ctx context.Context) (uint64, api.ByteSlice, if err != nil { return 0, nil, err } - return bhb.Height, api.ByteSlice(bhb.Header[:]), nil + return bhb.Height, bhb.Header[:], nil } func (s *Server) DifficultyAtHash(ctx context.Context, hash *chainhash.Hash) (*big.Int, error) { diff --git a/service/tbc/tbc_test.go b/service/tbc/tbc_test.go index 765465e8..80ceef75 100644 --- a/service/tbc/tbc_test.go +++ b/service/tbc/tbc_test.go @@ -740,7 +740,7 @@ func runBitcoinCommand(ctx context.Context, t *testing.T, bitcoindContainer test return buf.String()[8 : len(buf.String())-1], nil } -func getRandomTxId(ctx context.Context, t *testing.T, bitcoindContainer testcontainers.Container) string { +func getRandomTxId(ctx context.Context, t *testing.T, bitcoindContainer testcontainers.Container) *chainhash.Hash { blockHash, err := runBitcoinCommand( ctx, t, @@ -780,7 +780,11 @@ func getRandomTxId(ctx context.Context, t *testing.T, bitcoindContainer testcont t.Fatal("was expecting at least 1 transaction") } - return parsed.Tx[0] + hash, err := chainhash.NewHashFromStr(parsed.Tx[0]) + if err != nil { + t.Fatalf("failed to parse tx hash: %v", err) + } + return hash } func nextPort(ctx context.Context, t *testing.T) int {