mirror of
https://github.com/btcsuite/btcd.git
synced 2026-08-18 13:08:09 +02:00
rpcclient: add GetTxOutProof{,Async} methods
This commit is contained in:
parent
51e9b53c78
commit
d1a428e381
2 changed files with 200 additions and 0 deletions
|
|
@ -9,6 +9,8 @@ import (
|
|||
"bytes"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/btcsuite/btcd/btcjson"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
|
|
@ -1102,6 +1104,49 @@ func (c *Client) GetTxOutSetInfo() (*btcjson.GetTxOutSetInfoResult, error) {
|
|||
return c.GetTxOutSetInfoAsync().Receive()
|
||||
}
|
||||
|
||||
// FutureGetTxOutProofResult is a future promise to deliver the result of a
|
||||
// GetTxOutProofAsync RPC invocation (or an applicable error).
|
||||
type FutureGetTxOutProofResult chan *Response
|
||||
|
||||
// Receive waits for the Response promised by the future and returns the
|
||||
// results of GetTxOutSetInfoAsync RPC invocation.
|
||||
func (r FutureGetTxOutProofResult) Receive() (*wire.MsgMerkleBlock, error) {
|
||||
res, err := ReceiveFuture(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var merkleBlock wire.MsgMerkleBlock
|
||||
err = merkleBlock.BtcDecode(
|
||||
hex.NewDecoder(parseJSONStringReader(res)),
|
||||
wire.ProtocolVersion, wire.WitnessEncoding,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &merkleBlock, nil
|
||||
}
|
||||
|
||||
// GetTxOutProofAsync returns an instance of a type that can be used to get
|
||||
// the result of the RPC at some future time by invoking the Receive function on
|
||||
// the returned instance.
|
||||
//
|
||||
// See GetTxOutProof for the blocking version and more details.
|
||||
func (c *Client) GetTxOutProofAsync(txIDs []string,
|
||||
blockHash *string) FutureGetTxOutProofResult {
|
||||
|
||||
cmd := btcjson.NewGetTxOutProofCmd(txIDs, blockHash)
|
||||
return c.SendCmd(cmd)
|
||||
}
|
||||
|
||||
// GetTxOutProof returns the proof that a transaction was included in a block.
|
||||
func (c *Client) GetTxOutProof(txIDs []string,
|
||||
blockHash *string) (*wire.MsgMerkleBlock, error) {
|
||||
|
||||
return c.GetTxOutProofAsync(txIDs, blockHash).Receive()
|
||||
}
|
||||
|
||||
// FutureRescanBlocksResult is a future promise to deliver the result of a
|
||||
// RescanBlocksAsync RPC invocation (or an applicable error).
|
||||
//
|
||||
|
|
@ -1455,3 +1500,24 @@ func (c *Client) ReconsiderBlockAsync(
|
|||
func (c *Client) ReconsiderBlock(blockHash *chainhash.Hash) error {
|
||||
return c.ReconsiderBlockAsync(blockHash).Receive()
|
||||
}
|
||||
|
||||
// parseJSONString parses a JSON byte slice as a JSON string by removing leading
|
||||
// and trailing double quotes.
|
||||
func parseJSONString(jsonWithQuotes []byte) string {
|
||||
// The result is just a single hex string. So we don't need to unmarshal
|
||||
// it into a string, replacing the quotes achieves the same result, just
|
||||
// much faster and with fewer allocations.
|
||||
return strings.TrimPrefix(
|
||||
strings.TrimSuffix(string(jsonWithQuotes), "\""), "\"",
|
||||
)
|
||||
}
|
||||
|
||||
// parseJSONStringReader parses a JSON byte slice as a JSON string by removing
|
||||
// leading and trailing double quotes and returning it as a reader for further
|
||||
// processing.
|
||||
func parseJSONStringReader(jsonWithQuotes []byte) io.Reader {
|
||||
quotes := []byte("\"")
|
||||
return bytes.NewReader(bytes.TrimPrefix(
|
||||
bytes.TrimSuffix(jsonWithQuotes, quotes), quotes,
|
||||
))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
package rpcclient
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
|
|
@ -9,7 +13,9 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var upgrader = websocket.Upgrader{}
|
||||
|
|
@ -254,6 +260,134 @@ func TestClientConnectedToWSServerRunner(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestJSONStringParsing tests the old vs. the two new methods for parsing a
|
||||
// JSON string.
|
||||
func TestJSONStringParsing(t *testing.T) {
|
||||
testCases := []string{
|
||||
"\"\"",
|
||||
"\"foo\"",
|
||||
"\"5152dcbe2d94dd2c66008b1281157ff44156d146c83bf3a182ba5b32ce" +
|
||||
"e79f5d\"",
|
||||
"\"010000004860eb18bf1b1620e37e9490fc8a427514416fd75159ab8668" +
|
||||
"8e9a8300000000d5fdcc541e25de1c7a5addedf24858b8bb665c" +
|
||||
"9f36ef744ee42c316022c90f9bb0bc6649ffff001d08d2bd6101" +
|
||||
"0100000001000000000000000000000000000000000000000000" +
|
||||
"0000000000000000000000ffffffff0704ffff001d010bffffff" +
|
||||
"ff0100f2052a010000004341047211a824f55b505228e4c3d519" +
|
||||
"4c1fcfaa15a456abdf37f9b9d97a4040afc073dee6c89064984f" +
|
||||
"03385237d92167c13e236446b417ab79a0fcae412ae3316b77ac" +
|
||||
"00000000\"",
|
||||
}
|
||||
|
||||
oldMethod := func(res []byte) (string, error) {
|
||||
// Unmarshal result as a string.
|
||||
var resultStr string
|
||||
err := json.Unmarshal(res, &resultStr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return resultStr, nil
|
||||
}
|
||||
newMethod := func(res []byte) (string, error) {
|
||||
return parseJSONString(res), nil
|
||||
}
|
||||
newMethodReader := func(res []byte) (string, error) {
|
||||
allBytes, err := io.ReadAll(parseJSONStringReader(res))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(allBytes), nil
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase, func(t *testing.T) {
|
||||
oldResult, err := oldMethod([]byte(testCase))
|
||||
require.NoError(t, err)
|
||||
|
||||
newResult, err := newMethod([]byte(testCase))
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, oldResult, newResult)
|
||||
|
||||
newResult2, err := newMethodReader([]byte(testCase))
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, oldResult, newResult2)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkParseJSONString benchmarks the old vs. the two new methods for
|
||||
// parsing a JSON string.
|
||||
func BenchmarkParseJSONString(b *testing.B) {
|
||||
// This is block 2 from mainnet.
|
||||
payload := []byte(
|
||||
"\"010000004860eb18bf1b1620e37e9490fc8a427514416fd75159ab8668" +
|
||||
"8e9a8300000000d5fdcc541e25de1c7a5addedf24858b8bb665c" +
|
||||
"9f36ef744ee42c316022c90f9bb0bc6649ffff001d08d2bd6101" +
|
||||
"0100000001000000000000000000000000000000000000000000" +
|
||||
"0000000000000000000000ffffffff0704ffff001d010bffffff" +
|
||||
"ff0100f2052a010000004341047211a824f55b505228e4c3d519" +
|
||||
"4c1fcfaa15a456abdf37f9b9d97a4040afc073dee6c89064984f" +
|
||||
"03385237d92167c13e236446b417ab79a0fcae412ae3316b77ac" +
|
||||
"00000000\"",
|
||||
)
|
||||
|
||||
b.ResetTimer()
|
||||
b.Run("unmarshal", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
var s string
|
||||
if err := json.Unmarshal(payload, &s); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
serializedBlock, err := hex.DecodeString(s)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
var msgBlock wire.MsgBlock
|
||||
err = msgBlock.Deserialize(bytes.NewReader(
|
||||
serializedBlock,
|
||||
))
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("parseJSONString", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
s := parseJSONString(payload)
|
||||
|
||||
var msgBlock wire.MsgBlock
|
||||
err := msgBlock.Deserialize(hex.NewDecoder(
|
||||
strings.NewReader(s)),
|
||||
)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("parseJSONStringReader", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
r := parseJSONStringReader(payload)
|
||||
|
||||
var msgBlock wire.MsgBlock
|
||||
err := msgBlock.Deserialize(hex.NewDecoder(r))
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func makeClient(t *testing.T) (*Client, chan string, func()) {
|
||||
serverReceivedChannel := make(chan string)
|
||||
s := httptest.NewServer(http.HandlerFunc(makeUpgradeOnConnect(serverReceivedChannel)))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue