diff --git a/chain/bitcoind_client.go b/chain/bitcoind_client.go new file mode 100644 index 000000000..a33a93d36 --- /dev/null +++ b/chain/bitcoind_client.go @@ -0,0 +1,123 @@ +package chain + +import ( + "bytes" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + + "github.com/btcsuite/btcd/btcjson" + "github.com/btcsuite/btcd/rpcclient" + "github.com/btcsuite/btcd/wire" +) + +// BitcoindRPCClient extends the chain.BitcoindClient with additional methods +// that are not yet available in the standard btcd RPC client. +// +// TODO: remove this once https://github.com/btcsuite/btcwallet/pull/1009 is +// merged. +type BitcoindRPCClient struct { + rpcClient *rpcclient.Client +} + +// NewBitcoindRPCClient creates a new BitcoindRPCClient from an existing +// rpcclient.Client. +func NewBitcoindRPCClient(rpcClient *rpcclient.Client) *BitcoindRPCClient { + return &BitcoindRPCClient{ + rpcClient: rpcClient, + } +} + +// SubmitPackage broadcasts a parents+child package atomically. +// +// `parents` must contain at least one transaction. The returned slice +// holds the txids of every transaction that entered the mempool. +// +// `maxFeeRateBTCPerVByte` caps the effective feerate the node will +// accept for the entire package, expressed in BTC per virtual byte. +// Pass nil to leave the limit unset. +// +// If the active backend cannot relay packages, ErrUnimplemented is +// returned. +// +//nolint:err113 +func (c *BitcoindRPCClient) SubmitPackage(parents []*wire.MsgTx, + child *wire.MsgTx, maxFeeRateBTCPerVByte *float64) ( + *btcjson.SubmitPackageResult, error) { + + // Sanity check inputs. + if len(parents) == 0 { + return nil, errors.New("submitpackage: need at least one " + + "parent txn") + } + + if child == nil { + return nil, errors.New("submitpackage: child txn not defined") + } + + // Prepare the hex encoded transactions to that we'll add the request. + toHex := func(tx *wire.MsgTx) (string, error) { + var buf bytes.Buffer + if err := tx.Serialize(&buf); err != nil { + return "", err + } + + return hex.EncodeToString(buf.Bytes()), nil + } + + rawTxs := make([]string, 0, len(parents)+1) + + for i, ch := range parents { + h, err := toHex(ch) + if err != nil { + return nil, fmt.Errorf("cannot serialize parent txn "+ + "%d: %w", i, err) + } + + rawTxs = append(rawTxs, h) + } + + h, err := toHex(child) + if err != nil { + return nil, fmt.Errorf("cannot serialize child txn: %w", err) + } + + rawTxs = append(rawTxs, h) + + // Parameter 1: The package (array of hex strings). + rawTxsJSON, err := json.Marshal(rawTxs) + if err != nil { + return nil, fmt.Errorf("failed to marshal raw txs array: %w", + err) + } + + params := []json.RawMessage{rawTxsJSON} + + // Parameter 2: maxfeerate (optional, numeric, in BTC/kvB) + if maxFeeRateBTCPerVByte != nil { + // Convert BTC/vByte to BTC/kvB. + const btcPerVByteToKvB = 1000 + maxFeeRateBTCPerKvB := *maxFeeRateBTCPerVByte * btcPerVByteToKvB + maxFeeRateJSON, err := json.Marshal(maxFeeRateBTCPerKvB) + if err != nil { + return nil, fmt.Errorf("failed to marshal "+ + "maxfeerate: %w", err) + } + + params = append(params, maxFeeRateJSON) + } + + resp, err := c.rpcClient.RawRequest("submitpackage", params) + if err != nil { + return nil, err + } + + // Unmarshall the response. + var result btcjson.SubmitPackageResult + if err := result.UnmarshalJSON(resp); err != nil { + return nil, err + } + + return &result, nil +} diff --git a/go.mod b/go.mod index ed3e2da4e..5aaa709b9 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ go 1.25 replace github.com/golang-migrate/migrate/v4 => github.com/lightninglabs/migrate/v4 v4.18.2-9023d66a-fork-pr-2 require ( - github.com/btcsuite/btcd v0.24.3-0.20250318170759-4f4ea81776d6 + github.com/btcsuite/btcd v0.25.0 github.com/btcsuite/btcd/btcutil v1.1.5 github.com/btcsuite/btclog/v2 v2.0.1-0.20250728225537-6090e87c6c5b github.com/golang-migrate/migrate/v4 v4.17.0 @@ -35,9 +35,10 @@ require ( github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect github.com/aead/siphash v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.3.5 // indirect github.com/btcsuite/btcd/btcutil/psbt v1.1.8 // indirect github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect + github.com/btcsuite/btcd/v2transport v1.0.1 // indirect github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c // indirect github.com/btcsuite/btcwallet v0.16.17 // indirect github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5 // indirect @@ -54,8 +55,8 @@ require ( github.com/coreos/go-semver v0.3.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect + github.com/decred/dcrd/crypto/blake256 v1.1.0 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect github.com/decred/dcrd/lru v1.1.2 // indirect github.com/docker/cli v28.1.1+incompatible // indirect github.com/docker/docker v28.1.1+incompatible // indirect @@ -96,7 +97,7 @@ require ( github.com/kkdai/bstream v1.0.0 // indirect github.com/klauspost/compress v1.17.9 // indirect github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf // indirect - github.com/lightninglabs/neutrino v0.16.1 // indirect + github.com/lightninglabs/neutrino v0.16.2-0.20250820152345-800584718688 // indirect github.com/lightninglabs/neutrino/cache v1.1.2 // indirect github.com/lightningnetwork/lightning-onion v1.2.1-0.20240815225420-8b40adf04ab9 // indirect github.com/lightningnetwork/lnd/clock v1.1.1 // indirect @@ -124,7 +125,7 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.11.1 // indirect - github.com/prometheus/client_model v0.2.0 // indirect + github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.26.0 // indirect github.com/prometheus/procfs v0.6.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect diff --git a/go.sum b/go.sum index e19c8c373..9c837a6cd 100644 --- a/go.sum +++ b/go.sum @@ -32,12 +32,12 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A= -github.com/btcsuite/btcd v0.24.3-0.20250318170759-4f4ea81776d6 h1:8n9k3I7e8DkpdQ5YAP4j8ly/LSsbe6qX9vmVbrUGvVw= -github.com/btcsuite/btcd v0.24.3-0.20250318170759-4f4ea81776d6/go.mod h1:OmM4kFtB0klaG/ZqT86rQiyw/1iyXlJgc3UHClPhhbs= +github.com/btcsuite/btcd v0.25.0 h1:JPbjwvHGpSywBRuorFFqTjaVP4y6Qw69XJ1nQ6MyWJM= +github.com/btcsuite/btcd v0.25.0/go.mod h1:qbPE+pEiR9643E1s1xu57awsRhlCIm1ZIi6FfeRA4KE= github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= -github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ= -github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= +github.com/btcsuite/btcd/btcec/v2 v2.3.5 h1:dpAlnAwmT1yIBm3exhT1/8iUSD98RDJM5vqJVQDQLiU= +github.com/btcsuite/btcd/btcec/v2 v2.3.5/go.mod h1:m22FrOAiuxl/tht9wIqAoGHcbnCCaPWyauO8y2LGGtQ= github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= github.com/btcsuite/btcd/btcutil v1.1.5 h1:+wER79R5670vs/ZusMTF1yTcRYE5GUsFbdjdisflzM8= @@ -48,6 +48,8 @@ github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtyd github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ= github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcd/v2transport v1.0.1 h1:pIyyyBCPwd087K3Wdb/9tIvUubAQdzTJghjPgzTQVsE= +github.com/btcsuite/btcd/v2transport v1.0.1/go.mod h1:N6H0HGSElVVJKntzaYHYVbW71DtWDLMw2yhwVRO3ZOE= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c h1:4HxD1lBUGUddhzgaNgrCPsFWd7cGYNpeFUgd9ZIgyM0= github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c/go.mod h1:w7xnGOhwT3lmrS4H3b/D1XAXxvh+tbhUm8xeHN2y3TQ= @@ -105,11 +107,11 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= -github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= -github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8= +github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/decred/dcrd/lru v1.1.2 h1:KdCzlkxppuoIDGEvCGah1fZRicrDH36IipvlB1ROkFY= github.com/decred/dcrd/lru v1.1.2/go.mod h1:gEdCVgXs1/YoBvFWt7Scgknbhwik3FgVSzlnCcXL2N8= @@ -174,6 +176,7 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= @@ -331,8 +334,8 @@ github.com/lightninglabs/lndclient v0.20.0-3 h1:hL37EJHYupIEQRSgogA6AFbFQ/6l+A+D github.com/lightninglabs/lndclient v0.20.0-3/go.mod h1:GMqsKYJ/CkKY2DgPJfcvvHf98qiwr4fpKir8oAN7TiI= github.com/lightninglabs/migrate/v4 v4.18.2-9023d66a-fork-pr-2 h1:eFjp1dIB2BhhQp/THKrjLdlYuPugO9UU4kDqu91OX/Q= github.com/lightninglabs/migrate/v4 v4.18.2-9023d66a-fork-pr-2/go.mod h1:99BKpIi6ruaaXRM1A77eqZ+FWPQ3cfRa+ZVy5bmWMaY= -github.com/lightninglabs/neutrino v0.16.1 h1:5Kz4ToxncEVkpKC6fwUjXKtFKJhuxlG3sBB3MdJTJjs= -github.com/lightninglabs/neutrino v0.16.1/go.mod h1:L+5UAccpUdyM7yDgmQySgixf7xmwBgJtOfs/IP26jCs= +github.com/lightninglabs/neutrino v0.16.2-0.20250820152345-800584718688 h1:fqEeH1lSuYcx0kzr1nBc8LCooBu8iKOUsBPhpXS523g= +github.com/lightninglabs/neutrino v0.16.2-0.20250820152345-800584718688/go.mod h1:L+5UAccpUdyM7yDgmQySgixf7xmwBgJtOfs/IP26jCs= github.com/lightninglabs/neutrino/cache v1.1.2 h1:C9DY/DAPaPxbFC+xNNEI/z1SJY9GS3shmlu5hIQ798g= github.com/lightninglabs/neutrino/cache v1.1.2/go.mod h1:XJNcgdOw1LQnanGjw8Vj44CvguYA25IMKjWFZczwZuo= github.com/lightninglabs/protobuf-go-hex-display v1.33.0-hex-display h1:Y2WiPkBS/00EiEg0qp0FhehxnQfk3vv8U6Xt3nN+rTY= @@ -435,8 +438,9 @@ github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqr github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ= diff --git a/harness/harness.go b/harness/harness.go index f71e73887..228279db1 100644 --- a/harness/harness.go +++ b/harness/harness.go @@ -26,6 +26,7 @@ import ( "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/rpcclient" + "github.com/lightninglabs/darepo-client/chain" "github.com/lightninglabs/lndclient" "github.com/lightninglabs/taproot-assets/taprpc" lnrpc "github.com/lightningnetwork/lnd/lnrpc" @@ -1261,6 +1262,17 @@ func (h *Harness) BitcoinRPCClient() (*rpcclient.Client, error) { return rpcclient.New(connCfg, nil) } +// BitcoindClient returns a chain.BitcoindRPCClient that wraps the bitcoind +// RPC client with additional methods like SubmitPackage. +func (h *Harness) BitcoindClient() (*chain.BitcoindRPCClient, error) { + rpcClient, err := h.BitcoinRPCClient() + if err != nil { + return nil, err + } + + return chain.NewBitcoindRPCClient(rpcClient), nil +} + // bitcoinRPCCall makes a JSON-RPC call to bitcoind and returns the raw result // or an error. func (h *Harness) bitcoinRPCCall(method string,