From dfe82b82bbac337fb198a6da655ca2588a99d4c3 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Tue, 28 Nov 2023 17:34:54 +0800 Subject: [PATCH] chain: use `gettxspendingprevout` in RPC clients This commit creates a new method `getTxSpendingPrevOut` that's used by both ZMQ and RPC clients to lookup mempool spend for a given input. --- chain/bitcoind_events.go | 19 ++++--- chain/bitcoind_rpc_events.go | 15 ++++- chain/bitcoind_zmq_events.go | 104 +++++++++++++++++++---------------- 3 files changed, 78 insertions(+), 60 deletions(-) diff --git a/chain/bitcoind_events.go b/chain/bitcoind_events.go index dcce473cb2..6675e9476a 100644 --- a/chain/bitcoind_events.go +++ b/chain/bitcoind_events.go @@ -44,6 +44,15 @@ func NewBitcoindEventSubscriber(cfg *BitcoindConfig, client *rpcclient.Client, "should be specified, not both") } + // Check if the bitcoind node is on a version that has the + // gettxspendingprevout RPC. If it does, then we don't need to maintain + // a mempool for ZMQ clients and can maintain a smaller mempool for RPC + // clients. + hasRPC, err := hasSpendingPrevoutRPC(client) + if err != nil { + return nil, err + } + if cfg.PollingConfig != nil { if client == nil { return nil, fmt.Errorf("rpc client must be given " + @@ -52,7 +61,7 @@ func NewBitcoindEventSubscriber(cfg *BitcoindConfig, client *rpcclient.Client, } pollingEvents := newBitcoindRPCPollingEvents( - cfg.PollingConfig, client, bClient, + cfg.PollingConfig, client, bClient, hasRPC, ) return pollingEvents, nil @@ -63,14 +72,6 @@ func NewBitcoindEventSubscriber(cfg *BitcoindConfig, client *rpcclient.Client, "rpcpolling is disabled") } - // Check if the bitcoind node is on a version that has the - // gettxspendingprevout RPC. If it does, then we don't need to maintain - // a mempool for ZMQ clients. - hasRPC, err := hasSpendingPrevoutRPC(client) - if err != nil { - return nil, err - } - return newBitcoindZMQEvents(cfg.ZMQConfig, client, bClient, hasRPC) } diff --git a/chain/bitcoind_rpc_events.go b/chain/bitcoind_rpc_events.go index 3f9804f5ee..10fe1beb9d 100644 --- a/chain/bitcoind_rpc_events.go +++ b/chain/bitcoind_rpc_events.go @@ -77,7 +77,7 @@ var _ BitcoindEvents = (*bitcoindRPCPollingEvents)(nil) // newBitcoindRPCPollingEvents instantiates a new bitcoindRPCPollingEvents // object. func newBitcoindRPCPollingEvents(cfg *PollingConfig, client *rpcclient.Client, - bClient batchClient) *bitcoindRPCPollingEvents { + bClient batchClient, hasRPC bool) *bitcoindRPCPollingEvents { if cfg.BlockPollingInterval == 0 { cfg.BlockPollingInterval = defaultBlockPollInterval @@ -100,6 +100,7 @@ func newBitcoindRPCPollingEvents(cfg *PollingConfig, client *rpcclient.Client, client: bClient, getRawTxBatchSize: cfg.RPCBatchSize, batchWaitInterval: cfg.RPCBatchInterval, + hasPrevoutRPC: hasRPC, } if cfg.RPCBatchSize == 0 { @@ -165,8 +166,16 @@ func (b *bitcoindRPCPollingEvents) LookupInputSpend( b.mempool.RLock() defer b.mempool.RUnlock() - // Check whether the input is in mempool. - return b.mempool.containsInput(op) + // If `gettxspendingprevout` is not supported, we need to loop it up in + // our local mempool. + if !b.mempool.cfg.hasPrevoutRPC { + // Check whether the input is in mempool. + return b.mempool.containsInput(op) + } + + // Otherwise, we can use the `gettxspendingprevout` RPC to look up the + // input. + return getTxSpendingPrevOut(op, b.client) } // blockEventHandlerRPC is a goroutine that uses the rpc client to check if we diff --git a/chain/bitcoind_zmq_events.go b/chain/bitcoind_zmq_events.go index f7b31ff82c..f7938fc49f 100644 --- a/chain/bitcoind_zmq_events.go +++ b/chain/bitcoind_zmq_events.go @@ -249,54 +249,8 @@ func (b *bitcoindZMQEvents) LookupInputSpend( } // Otherwise, we aren't maintaining a mempool and can use the - // gettxspendingprevout RPC. Create an RPC-style prevout that will be - // the lone item in an array. - prevoutReq := &getTxSpendingPrevOutReq{ - Txid: op.Hash.String(), Vout: op.Index, - } - - // The RPC takes an array of prevouts so we have an array with a single - // item since we don't yet batch calls to LookupInputSpend. - prevoutArr := []*getTxSpendingPrevOutReq{prevoutReq} - - req, err := json.Marshal(prevoutArr) - if err != nil { - return chainhash.Hash{}, false - } - - resp, err := b.client.RawRequest( - "gettxspendingprevout", []json.RawMessage{req}, - ) - if err != nil { - return chainhash.Hash{}, false - } - - var prevoutResps []getTxSpendingPrevOutResp - err = json.Unmarshal(resp, &prevoutResps) - if err != nil { - return chainhash.Hash{}, false - } - - // We should only get a single item back since we only requested with a - // single item. - if len(prevoutResps) != 1 { - return chainhash.Hash{}, false - } - - result := prevoutResps[0] - - // If the "spendingtxid" field is empty, then the utxo has no spend in - // the mempool at the moment. - if result.SpendingTxid == "" { - return chainhash.Hash{}, false - } - - spendHash, err := chainhash.NewHashFromStr(result.SpendingTxid) - if err != nil { - return chainhash.Hash{}, false - } - - return *spendHash, true + // gettxspendingprevout RPC. + return getTxSpendingPrevOut(op, b.client) } // blockEventHandler reads raw blocks events from the ZMQ block socket and @@ -541,3 +495,57 @@ func (b *bitcoindZMQEvents) mempoolPoller() { } } } + +// getTxSpendingPrevOut makes an RPC call to `gettxspendingprevout` and returns +// the result. +func getTxSpendingPrevOut(op wire.OutPoint, + client *rpcclient.Client) (chainhash.Hash, bool) { + + prevoutReq := &getTxSpendingPrevOutReq{ + Txid: op.Hash.String(), Vout: op.Index, + } + + // The RPC takes an array of prevouts so we have an array with a single + // item since we don't yet batch calls to LookupInputSpend. + prevoutArr := []*getTxSpendingPrevOutReq{prevoutReq} + + req, err := json.Marshal(prevoutArr) + if err != nil { + return chainhash.Hash{}, false + } + + resp, err := client.RawRequest( + "gettxspendingprevout", []json.RawMessage{req}, + ) + if err != nil { + return chainhash.Hash{}, false + } + + var prevoutResps []getTxSpendingPrevOutResp + err = json.Unmarshal(resp, &prevoutResps) + if err != nil { + return chainhash.Hash{}, false + } + + // We should only get a single item back since we only requested with a + // single item. + if len(prevoutResps) != 1 { + return chainhash.Hash{}, false + } + + result := prevoutResps[0] + + // If the "spendingtxid" field is empty, then the utxo has no spend in + // the mempool at the moment. + if result.SpendingTxid == "" { + return chainhash.Hash{}, false + } + + spendHash, err := chainhash.NewHashFromStr(result.SpendingTxid) + if err != nil { + return chainhash.Hash{}, false + } + + return *spendHash, true + +}