Skip to content

Thorchain sdk#192

Open
neavra wants to merge 10 commits intomainfrom
181-thorchain-sdk
Open

Thorchain sdk#192
neavra wants to merge 10 commits intomainfrom
181-thorchain-sdk

Conversation

@neavra
Copy link
Contributor

@neavra neavra commented Oct 29, 2025

Summary by CodeRabbit

  • New Features

    • Added a THORChain SDK to sign and broadcast transactions over CometBFT RPC with strict signature validation.
  • Refactor

    • Streamlined codec initialization to use a centralized codec factory for consistency.
  • Tests

    • Added a comprehensive test suite covering signing, signature edge cases, message hashing, broadcasting, mocks, and an optional testnet integration.

@neavra neavra linked an issue Oct 29, 2025 that may be closed by this pull request
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 29, 2025

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 46.67% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'Thorchain sdk' is vague and generic, using non-descriptive terms that don't clearly convey the specific changes made in the changeset. Use a more descriptive title that explains the primary change, such as 'Add Thorchain SDK with transaction signing and broadcasting' or 'Implement Thorchain codec and SDK components'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 181-thorchain-sdk

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@neavra neavra marked this pull request as ready for review November 14, 2025 08:57
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (5)
engine/thorchain/thorchain_test.go (1)

219-255: Check resource path consistency for thorchain_swap rules

In TestThorchain_Evaluate_Success_ThorchainSwap the rule resource is "thorchain.thorchain_swap", whereas other swap tests use "thorchain.thorchain_swap.swap". If util.ParseResource expects a fixed <chain>.<protocol>.<function> shape (as suggested by "thorchain.send.rune" elsewhere), this shorter resource might be relying on undefined or inconsistent parsing behavior.

Consider standardizing this test to the same resource shape as the others (or documenting that 2‑segment resources are intentionally supported) to avoid subtle mismatches between rule resources and protocol/function parsing.

sdk/thorchain/thorchain_test.go (2)

20-29: MockRPCClient correctly models the RPC interface

The mock cleanly mirrors the RPCClient interface and integrates with testify’s mock.Mock, which keeps Broadcast tests straightforward. Just be aware that args.Get(0).(*coretypes.ResultBroadcastTx) will panic if a future test uses Return(nil, err) with an untyped nil; using a typed nil in such cases will keep it safe.


30-44: Unused high_s_value vector could be removed or exercised

testSignatureVectors["high_s_value"] isn’t used in any test. Consider either:

  • adding a test that verifies normalizeLowS or signing behavior for a high‑S input, or
  • removing this vector to avoid confusion about untested cases.
sdk/thorchain/thorchain.go (2)

99-184: Signing flow and low‑S handling look correct; only minor polish opportunities

The Sign method:

  • enforces exactly one signature,
  • validates signer info structure,
  • parses hex R/S (with optional 0x prefix),
  • enforces 32‑byte R/S, validates R in [1, N-1], and normalizes S into low‑S form in [1, N-1],
  • constructs raw R||S and encodes via TxConfig/authtx.WrapTx.

This matches Cosmos expectations for SIGN_MODE_DIRECT secp256k1 signatures and should be robust.

Two minor improvements you might consider:

  • Use signing.SignMode_SIGN_MODE_DIRECT (with an extra import) in validateSignerInfo instead of comparing single.Mode.String() to "SIGN_MODE_DIRECT".
  • Optionally check S with validateCurveOrderValue for symmetry with R, even though normalizeLowS already enforces the same bounds.

281-327: validateSignerInfo enforces the expected THORChain signer shape

The signer validation correctly checks:

  • presence of AuthInfo and SignerInfos,
  • exactly one signer,
  • non‑nil PublicKey with secp256k1 type URL, and
  • non‑nil ModeInfo with single mode and SIGN_MODE_DIRECT.

Only minor nit: comparing single.Mode.String() to "SIGN_MODE_DIRECT" is slightly brittle versus comparing to the enum constant, but behavior is otherwise sound.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d47ef13 and 7899bec.

📒 Files selected for processing (4)
  • engine/thorchain/thorchain.go (2 hunks)
  • engine/thorchain/thorchain_test.go (5 hunks)
  • sdk/thorchain/thorchain.go (1 hunks)
  • sdk/thorchain/thorchain_test.go (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: neavra
Repo: vultisig/recipes PR: 123
File: engine/xrpl/xrpl.go:41-51
Timestamp: 2025-09-16T15:18:29.097Z
Learning: XRPL engine should support multiple functions including "send" and "thorchain_swap" rather than being restricted to just "send".
📚 Learning: 2025-07-28T17:33:10.162Z
Learnt from: webpiratt
Repo: vultisig/recipes PR: 50
File: ethereum/ethereum.go:165-166
Timestamp: 2025-07-28T17:33:10.162Z
Learning: The Protocol interface in types/protocol.go includes a ChainID() string method, so calling ChainID() on any Protocol implementation (like NewETH()) is valid and will not cause compilation errors.

Applied to files:

  • engine/thorchain/thorchain.go
🧬 Code graph analysis (3)
engine/thorchain/thorchain.go (1)
sdk/thorchain/thorchain.go (1)
  • MakeCodec (72-81)
sdk/thorchain/thorchain_test.go (1)
sdk/thorchain/thorchain.go (2)
  • NewSDK (47-55)
  • NewCometBFTRPCClient (58-67)
engine/thorchain/thorchain_test.go (1)
sdk/thorchain/thorchain.go (1)
  • MakeCodec (72-81)
🔇 Additional comments (21)
engine/thorchain/thorchain.go (1)

15-29: Centralizing codec creation via thorchain_sdk.MakeCodec is a good refactor

Using the shared codec factory removes duplicated registration logic, keeps engine/tests/SDK aligned, and reduces the risk of interface‑registry drift. No issues spotted with this change.

engine/thorchain/thorchain_test.go (4)

13-16: Tests now using shared MakeCodec keep them aligned with runtime behavior

Switching the MsgDeposit helper to thorchain_sdk.MakeCodec() means test transactions are encoded with the exact same interface registry as the engine/SDK, which avoids subtle Any/Msg registration mismatches. Looks good.

Also applies to: 323-372


375-412: createValidMsgSendTransaction codec change is consistent and correct

Using thorchain_sdk.MakeCodec() here mirrors the runtime codec used by NewThorchain and the SDK, so MsgSend‑based txs in tests will deserialize identically to production. No issues with the refactor.


425-471: TestThorchain_parseTransaction now uses the standardized codec – good alignment

Creating the protobuf transaction with thorchain_sdk.MakeCodec() ensures the bytes under test are exactly what the engine’s codec expects, tightening the realism of this test and preventing registry drift.


549-554: Protobuf parse test using MakeCodec is consistent with the new codec strategy

The dedicated protobuf parse test now reuses thorchain_sdk.MakeCodec(), which keeps it in sync with both engine and SDK codec wiring. This is the right direction.

sdk/thorchain/thorchain_test.go (10)

46-112: buildRealisticTHORChainTx is a solid end‑to‑end fixture

Building a real MsgSend + AuthInfo (with secp256k1 pubkey and SIGN_MODE_DIRECT) and encoding it via NewSDK(nil).codec gives you highly realistic unsigned tx bytes that exercise the same codec path as production. This is an excellent foundation for the signing and hashing tests.


135-163: Happy‑path signing test matches SDK expectations

TestSDK_Sign_WithRealisticTransaction validates unmarshalling, signer validation, signature injection, and that the body/memo survive signing intact. This directly exercises the core Sign logic and looks correct.


176-188: Multi‑signature rejection behavior is well‑specified

TestSDK_Sign_MultipleSignatures confirms the SDK fails when more than one TSS signature is provided, matching the single‑signer assumption. This is a useful guardrail and aligns with the Sign implementation.


206-225: Broadcast tests correctly assert RPC wiring and error propagation

TestSDK_Broadcast and TestSDK_Broadcast_NoRPCClient ensure successful broadcasts are passed through and that missing RPC configuration is surfaced as a clear error. The mock expectations and assertions look sound.


238-261: Send tests validate the sign‑then‑broadcast orchestration

TestSDK_Send_Success and the corresponding signing‑error test correctly verify that Send:

  • uses Sign internally,
  • propagates signing errors, and
  • does not call BroadcastTxSync when signing fails.

This nicely pins down the high‑level API behavior.


282-324: MessageHash tests thoroughly cover determinism and input sensitivity

TestSDK_MessageHash checks hash length, determinism for identical inputs, and variance across account number, sequence, and transaction body changes. This gives strong confidence that MessageHash is behaving as a proper SignDoc hash builder.


326-367: Raw signature format tests ensure flexible hex handling and 64‑byte output

TestSDK_Sign_RawSignatureFormat confirms that signatures with and without 0x prefixes decode correctly and always result in 64‑byte R||S signatures in the transaction. This is a valuable compatibility check against different TSS outputs.


369-391: Integration test is cautious and non‑disruptive

TestSDK_Integration_Testnet respects testing.Short() and skips cleanly when the testnet endpoint is unreachable. It avoids broadcasting, limiting itself to connectivity + hashing. This is a balanced approach for an integration‑style test.


393-460: Edge‑case signing tests align with curve‑order and length checks

TestSDK_Sign_EdgeCases exercises:

  • zero R/S,
  • oversized R/S (near all‑ones),
  • valid mixed patterns, and
  • short R values,

matching the constraints enforced in validateCurveOrderValue and normalizeLowS. The expected pass/fail outcomes look consistent with the implementation.


462-540: Signer‑info validation tests closely mirror the SDK’s error paths

TestSDK_SignerInfoValidation systematically mutates AuthInfo/SignerInfos/PublicKey/ModeInfo to confirm that validateSignerInfo emits the expected errors. This tightly couples tests to the validation logic and should catch regressions if that logic is changed.

sdk/thorchain/thorchain.go (6)

35-55: SDK struct and NewSDK constructor are well‑structured

The SDK cleanly encapsulates RPC client, codec, and TxConfig, and uses the shared MakeCodec() plus authtx.NewTxConfig, which is the right way to align encoding/signing behavior with Cosmos tooling. No issues here.


69-81: MakeCodec is a good single source of truth for THORChain codec setup

Centralizing interface registration (crypto, bank, MsgDeposit) into MakeCodec and reusing it across SDK/engine/tests significantly reduces the risk of Any/unpacking mismatches. This design looks solid.


83-97: Broadcast wrapper correctly surfaces CometBFT errors and ABCI codes

CometBFTRPCClient.BroadcastTxSync wraps network errors and rejects non‑zero ABCI codes with a detailed error while still returning the raw result. This is a sensible default behavior.


186-206: Broadcast and Send compose cleanly and handle signing errors correctly

Broadcast guards against a nil RPC client, and Send properly propagates signing errors and only calls Broadcast when signing succeeds. This matches the test expectations and is a clear high‑level API.


253-279: Curve‑order validation helper is straightforward and correct

validateCurveOrderValue cleanly enforces non‑zero and < N bounds for secp256k1 values using the global secpN. This matches the needs for validating R (and potentially S) and looks correct.


329-349: Low‑S normalization logic appears correct and preserves 32‑byte encoding

normalizeLowS:

  • rejects empty or out‑of‑range S,
  • reflects S into the lower half of the curve order when needed (S' = N - S),
  • left‑pads results to 32 bytes.

This matches standard low‑S normalization practices and should prevent malleability while keeping signatures in fixed‑width R||S format.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
sdk/thorchain/thorchain.go (2)

211-260: Chain ID still hard‑coded; consider making it configurable

MessageHash is now safe (no panics) and correctly builds a SignDoc and SHA‑256 hash. However, ChainId is still set via the hard‑coded thorchainChainID = "thorchain-1". This locks the SDK to mainnet and makes reuse on stagenet/testnet or any fork require code changes.

Consider:

  • Adding a ChainID string field on SDK and initializing it in NewSDK (defaulting to "thorchain-1"), or
  • Accepting chainID as an argument to MessageHash.

This would address the earlier concern while keeping the current default behavior.


262-269: Hex cleaning and marshal helpers are safe and address previous panic concerns

cleanHex is simple and safe for trimming optional 0x prefixes, and the new marshalTxBody/marshalAuthInfo helpers return errors instead of panicking on nil inputs or marshal failures. This directly addresses the earlier panic risk in public API paths while keeping the call sites (MessageHash) clean.

Also applies to: 362-384

🧹 Nitpick comments (5)
sdk/thorchain/thorchain.go (5)

189-209: Broadcast/Send flow is straightforward but could add more context on failure

Broadcast and Send are minimal and correct: Send composes Sign + Broadcast, and Broadcast enforces that rpcClient is configured.

If you want slightly richer errors, you could wrap the broadcast error with context (e.g., "failed to broadcast signed transaction"), but this is optional and not a blocker.


110-186: Minor naming nit: receiver sdk vs imported sdk alias

Using sdk as the method receiver name on *SDK while also importing sdk "github.com/cosmos/cosmos-sdk/types" can be a bit confusing, especially for future edits that might need the Cosmos sdk alias in these methods.

Optional: rename the receiver to something like s or tsdk to avoid shadowing and improve readability.

Also applies to: 190-209, 211-260


229-243: Clarify sequence contract between MessageHash and Sign

MessageHash mutates unsignedTx.AuthInfo.SignerInfos[0].Sequence in-memory, but this doesn't modify the original unsignedTxBytes parameter (byte slices are immutable in Go). When Sign is later called with those same unsignedTxBytes, it uses whatever sequence is embedded in them—not the value set in MessageHash.

The Sign doc comment requires "SignerInfos[0].Sequence matching the sequence used in MessageHash()", which is only satisfied if the caller ensures the bytes are already correct upfront. The mutation in MessageHash is therefore misleading and doesn't "fix up" the tx.

Consider either: (1) tightening the MessageHash doc to clarify that the sequence parameter must already match what's in unsignedTxBytes, with the mutation being for the hashing computation only, or (2) removing the mutation and just validating equality, making the dependency explicit.


290-336: Use enum constant for SignMode comparison instead of String() representation

The test suite already demonstrates the correct pattern at line 84 of thorchain_test.go: signing.SignMode_SIGN_MODE_DIRECT. The code in validateSignerInfo should follow this same approach.

Change the comparison from single.Mode.String() != "SIGN_MODE_DIRECT" to single.Mode != signing.SignMode_SIGN_MODE_DIRECT by:

  • Adding import: "github.com/cosmos/cosmos-sdk/types/tx/signing"
  • Replacing the string comparison with direct enum comparison

This aligns with the Cosmos SDK's protobuf design where SignMode values are stable numeric identifiers, making enum comparison the canonical and future-proof approach.


102-187: Use enum constant for SignMode comparison; consider symmetric R/S padding handling

The signing pipeline is sound, but one best-practice improvement is confirmed by Cosmos SDK conventions:

  • Line 328: Replace if single.Mode.String() != "SIGN_MODE_DIRECT" with if single.Mode != signingtypes.SignMode_SIGN_MODE_DIRECT. Enum comparison is type-safe, faster, and not dependent on string formatting.

Additionally, note the asymmetry in R/S validation: S values are left-padded to 32 bytes in normalizeLowS (lines 352–356), but R values hard-fail if not exactly 32 bytes. If TSS guarantees are documented as always providing 32-byte values, this is acceptable; otherwise, consider applying symmetric padding to both R and S for robustness.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7899bec and 525bd27.

📒 Files selected for processing (1)
  • sdk/thorchain/thorchain.go (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-17T22:17:33.321Z
Learnt from: webpiratt
Repo: vultisig/recipes PR: 129
File: engine/solana/assert.go:77-94
Timestamp: 2025-09-17T22:17:33.321Z
Learning: In engine/solana/assert.go, the functions assertFuncSelector and assertArgs correctly accept solana.Base58 parameters, not []byte. The instruction data (inst.Data) passed to these functions is already of the appropriate solana.Base58 type, so no type conversion is needed.

Applied to files:

  • sdk/thorchain/thorchain.go
🔇 Additional comments (3)
sdk/thorchain/thorchain.go (3)

35-40: SDK construction & shared codec/TxConfig look solid

The SDK struct, NewSDK, and MakeCodec give a clear, reusable setup for Tx encoding and type registration (including MsgDeposit). Centralizing codec/TxConfig this way should keep signing, hashing, and broadcasting consistent across engine and plugins.

Also applies to: 49-58, 72-84


30-33: CometBFTRPCClient and RPCClient abstraction are well-shaped

The RPCClient interface and CometBFTRPCClient wrapper cleanly separate transport from the SDK logic, and BroadcastTxSync surfaces node-side rejection via a descriptive error while still returning the raw ResultBroadcastTx. That’s a good balance for higher-level callers.

Also applies to: 60-70, 86-100


338-358: Curve-order checks and low‑S normalization look correct

validateCurveOrderValue and normalizeLowS correctly enforce R,S ∈ [1, N-1] and adjust S into the lower half of the curve order, with proper left‑padding to 32 bytes. This matches standard secp256k1 anti‑malleability practice and should interoperate cleanly with Cosmos/THORChain signature verification.

Also applies to: 271-288

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Create thorchain sdk for sign and broadcast

1 participant