What changed, and why it matters
This commit adds a new client-side method called SubmitPackage to btcd's RPC client. It does not change any server-side code, mempool logic, or consensus rules. It simply lets programs ask a Bitcoin Core node (version 24+) to submit a group of related transactions using an existing Bitcoin Core RPC. There is no obvious security vulnerability in the change itself.
No security action required. Reviewers may optionally verify that btcjson.NewJsonSubmitPackageCmd and btcjson.SubmitPackageResult handle the optional maxfeerate/maxburnamount arguments correctly, but this is a routine feature addition.
Security signals we found
No server-side handler added; only RPC client wrapper
Input validation limits transaction count to 1-25
Backend version gating prevents calls to unsupported backends
No cryptographic, consensus, or mempool logic changes
No unsafe deserialization or memory handling introduced
Evidence from the diff
The patch introduces FutureSubmitPackageResult, SubmitPackageAsync, and SubmitPackage in rpcclient/rawtransactions.go, plus backend-version predicates in rpcclient/backend_version.go. The new code serializes a slice of wire.MsgTx transactions to hex, forwards them via btcjson.NewJsonSubmitPackageCmd, and decodes the response into btcjson.SubmitPackageResult. It gates the call on backend version (bitcoind v24+; btcd returns false) and validates that 1-25 transactions are supplied. It is purely an RPC client wrapper; btcd still has no submitpackage handler.
Changed components
rpcclient/rawtransactions.gorpcclient/backend_version.goInspect captured patch +122 / −0
diff --git a/rpcclient/backend_version.go b/rpcclient/backend_version.go
index cb2a46f..53160c4 100644
--- a/rpcclient/backend_version.go
+++ b/rpcclient/backend_version.go
@@ -19,6 +19,10 @@ type BackendVersion interface {
// SupportGetTxSpendingPrevOut returns true if the backend supports the
// gettxspendingprevout RPC.
SupportGetTxSpendingPrevOut() bool
+
+ // SupportSubmitPackage returns true if the backend supports the
+ // submitpackage RPC.
+ SupportSubmitPackage() bool
}
// BitcoindVersion represents the version of the bitcoind the client is
@@ -88,6 +92,15 @@ func (b BitcoindVersion) SupportGetTxSpendingPrevOut() bool {
return b > BitcoindPre24
}
+// SupportSubmitPackage returns true if bitcoind version is 24.0.0 or above,
+// the release in which the submitpackage RPC was introduced.
+//
+// NOTE: the optional maxfeerate/maxburnamount arguments were only added in
+// v28; this predicate only gates the call itself, not those arguments.
+func (b BitcoindVersion) SupportSubmitPackage() bool {
+ return b > BitcoindPre24
+}
+
// Compile-time checks to ensure that BitcoindVersion satisfy the
// BackendVersion interface.
var _ BackendVersion = BitcoindVersion(0)
@@ -187,6 +200,11 @@ func (b BtcdVersion) SupportGetTxSpendingPrevOut() bool {
return b > BtcdPre2401
}
+// SupportSubmitPackage returns false: btcd has no submitpackage RPC handler.
+func (b BtcdVersion) SupportSubmitPackage() bool {
+ return false
+}
+
// Compile-time checks to ensure that BtcdVersion satisfy the BackendVersion
// interface.
var _ BackendVersion = BtcdVersion(0)
diff --git a/rpcclient/rawtransactions.go b/rpcclient/rawtransactions.go
index c72cabe..0cb0c85 100644
--- a/rpcclient/rawtransactions.go
+++ b/rpcclient/rawtransactions.go
@@ -1015,6 +1015,110 @@ func (c *Client) TestMempoolAccept(txns []*wire.MsgTx,
return c.TestMempoolAcceptAsync(txns, maxFeeRate).Receive()
}
+// FutureSubmitPackageResult is a future promise to deliver the result of a
+// SubmitPackage RPC invocation (or an applicable error).
+type FutureSubmitPackageResult chan *Response
+
+// Receive waits for the Response promised by the future and returns the result
+// of submitting the package.
+func (r FutureSubmitPackageResult) Receive() (*btcjson.SubmitPackageResult,
+ error) {
+
+ response, err := ReceiveFuture(r)
+ if err != nil {
+ return nil, err
+ }
+
+ // btcjson.SubmitPackageResult implements a custom UnmarshalJSON that
+ // maps the raw submitpackage response into higher-level types.
+ var result btcjson.SubmitPackageResult
+ if err := json.Unmarshal(response, &result); err != nil {
+ return nil, err
+ }
+
+ return &result, nil
+}
+
+// maxPackageTxns is the maximum number of transactions a single submitpackage
+// call may contain. It mirrors bitcoind's MAX_PACKAGE_COUNT (see
+// src/policy/packages.h), which caps a package at 25 transactions.
+const maxPackageTxns = 25
+
+// SubmitPackageAsync 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 SubmitPackage for the blocking version and more details.
+func (c *Client) SubmitPackageAsync(txns []*wire.MsgTx,
+ maxFeeRate, maxBurnAmount *float64) FutureSubmitPackageResult {
+
+ // Gate on the backend version so an unsupported backend (btcd, or
+ // Bitcoin Core before v24) returns a typed ErrBackendVersion rather
+ // than a raw method-not-found error the caller would have to
+ // string-match. This mirrors TestMempoolAccept/GetTxSpendingPrevOut.
+ version, err := c.BackendVersion()
+ if err != nil {
+ return newFutureError(err)
+ }
+
+ if !version.SupportSubmitPackage() {
+ err := fmt.Errorf("%w: %v", ErrBackendVersion, version)
+
+ return newFutureError(err)
+ }
+
+ // A package must contain at least one transaction (the child) and at
+ // most maxPackageTxns.
+ if len(txns) == 0 {
+ err := fmt.Errorf("%w: no transactions provided",
+ ErrInvalidParam)
+
+ return newFutureError(err)
+ }
+
+ if len(txns) > maxPackageTxns {
+ err := fmt.Errorf("%w: too many transactions provided",
+ ErrInvalidParam)
+
+ return newFutureError(err)
+ }
+
+ // Serialize each transaction to a hex string, preserving the
+ // topological order (parents first, child last) the caller provides.
+ rawTxns := make([]string, 0, len(txns))
+ for _, tx := range txns {
+ buf := bytes.NewBuffer(make([]byte, 0, tx.SerializeSize()))
+ if err := tx.Serialize(buf); err != nil {
+ err = fmt.Errorf("%w: %v", ErrInvalidParam, err)
+
+ return newFutureError(err)
+ }
+
+ rawTxns = append(rawTxns, hex.EncodeToString(buf.Bytes()))
+ }
+
+ cmd := btcjson.NewJsonSubmitPackageCmd(
+ rawTxns, maxFeeRate, maxBurnAmount,
+ )
+
+ return c.SendCmd(cmd)
+}
+
+// SubmitPackage submits a package of related, topologically-sorted
+// transactions (unconfirmed parents first, child last) to the backend's
+// mempool for atomic validation and acceptance via the submitpackage RPC. It
+// lets a zero-fee v3/TRUC parent be accepted via its fee-paying CPFP child,
+// which a standalone broadcast would reject.
+//
+// maxFeeRate (BTC/kvB) and maxBurnAmount (BTC) are optional limits; a nil
+// value uses the backend's RPC default.
+func (c *Client) SubmitPackage(txns []*wire.MsgTx,
+ maxFeeRate, maxBurnAmount *float64) (*btcjson.SubmitPackageResult,
+ error) {
+
+ return c.SubmitPackageAsync(txns, maxFeeRate, maxBurnAmount).Receive()
+}
+
// FutureGetTxSpendingPrevOut is a future promise to deliver the result of a
// GetTxSpendingPrevOut RPC invocation (or an applicable error).
type FutureGetTxSpendingPrevOut chan *Response
Why this scored 18/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.