Skip to content

Commit

Permalink
chain: use gettxspendingprevout in RPC clients
Browse files Browse the repository at this point in the history
This commit creates a new method `getTxSpendingPrevOut` that's used by
both ZMQ and RPC clients to lookup mempool spend for a given input.
  • Loading branch information
yyforyongyu committed Nov 28, 2023
1 parent 51ca19d commit f2da461
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 60 deletions.
19 changes: 10 additions & 9 deletions chain/bitcoind_events.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 " +
Expand All @@ -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
Expand All @@ -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)
}

Expand Down
15 changes: 12 additions & 3 deletions chain/bitcoind_rpc_events.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
103 changes: 55 additions & 48 deletions chain/bitcoind_zmq_events.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -541,3 +495,56 @@ 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
}

0 comments on commit f2da461

Please sign in to comment.