Skip to content

feat: call TN action in ChainLink CRE#165

Merged
MicBun merged 5 commits intomainfrom
CreSupportEnabled
Dec 23, 2025
Merged

feat: call TN action in ChainLink CRE#165
MicBun merged 5 commits intomainfrom
CreSupportEnabled

Conversation

@MicBun
Copy link
Copy Markdown
Member

@MicBun MicBun commented Dec 23, 2025

resolves: https://github.com/truflation/website/issues/3055

Summary by CodeRabbit

  • New Features

    • Added a CRE-based transport for read/write JSON‑RPC calls, transaction signing, broadcast, and finalization polling; new options to configure transport and signer.
  • Chores

    • Bumped Go toolchain to 1.25.3 and added Chainlink CRE SDK dependencies.
  • Tests

    • Added structural tests for the CRE transport surface and transient error classification.
  • Documentation

    • Updated branding to TRUF.NETWORK and expanded SDK role-management and access guidance.

✏️ Tip: You can customize this high-level summary in your review settings.

@MicBun MicBun self-assigned this Dec 23, 2025
@MicBun MicBun added the enhancement New feature or request label Dec 23, 2025
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Dec 23, 2025

Walkthrough

Adds a CRE-based Transport implementation and two client option constructors for CRE, plus tests, Go toolchain and dependency bumps, and documentation text updates changing network naming to "TRUF.NETWORK".

Changes

Cohort / File(s) Summary
CRE Transport Core
core/tnclient/transport_cre.go, core/tnclient/options_cre.go
New CRETransport type and NewCRETransport(...) constructor; JSON‑RPC helper, Call, Execute (sign + broadcast), WaitTx (polling), lazy chain ID caching, signer exposure, and option constructors WithCRETransport and WithCRETransportAndSigner.
CRE Transport Testing
core/tnclient/transport_cre_test.go
New wasip1-guarded tests: constructor/signature presence, compile-time interface assertion, option-constructor signatures, and table-driven tests for transient vs permanent tx errors.
Transport Interface docs
core/tnclient/transport.go
Minor doc text change: "TRUF.NETWORK" naming in Transport interface comment.
Dependency / Toolchain
go.mod, examples/stream_cache_demo/go.mod
Bump Go toolchain to 1.25.3; add github.com/smartcontractkit/cre-sdk-go and related CRE modules.
Documentation & Tests comments
docs/api-reference.md, docs/stream-permissions.md, tests/integration/composed_actions_test.go
Text updates to use "TRUF.NETWORK"; added "How to get access" and "SDK Role Management API" guidance in stream-permissions; comment wording updated in integration test.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant CRETransport
    participant Signer
    participant CRE_HTTP as "CRE HTTP Client"
    participant Chain

    rect rgb(200,220,255)
    Note over Client,CRETransport: Read path — Call
    Client->>CRETransport: Call(ctx, namespace, action, inputs)
    CRETransport->>CRETransport: build JSON-RPC params
    CRETransport->>CRE_HTTP: POST user.call
    CRE_HTTP->>Chain: forward request
    Chain-->>CRE_HTTP: response
    CRETransport->>CRETransport: validate & unmarshal JSON-RPC
    CRETransport-->>Client: CallResult
    end

    rect rgb(220,250,220)
    Note over Client,Signer: Write path — Execute
    Client->>CRETransport: Execute(ctx, namespace, action, inputs, opts)
    CRETransport->>CRETransport: fetch ChainID (lazy, cached)
    CRETransport->>Signer: sign transaction payload
    Signer-->>CRETransport: signature
    CRETransport->>CRE_HTTP: POST user.broadcast (signed tx)
    CRE_HTTP->>Chain: broadcast tx
    Chain-->>CRE_HTTP: txHash
    CRETransport-->>Client: txHash
    end

    rect rgb(255,240,200)
    Note over Client,CRETransport: Polling — WaitTx
    Client->>CRETransport: WaitTx(ctx, txHash, interval)
    loop poll at interval
        CRETransport->>CRE_HTTP: POST user.tx_query
        CRE_HTTP->>Chain: query tx status
        Chain-->>CRE_HTTP: TxQueryResponse
        CRETransport->>CRETransport: check Height / transient errors
    end
    CRETransport-->>Client: final TxQueryResponse
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • pr-time-tracker
  • outerlook

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: call TN action in ChainLink CRE' accurately describes the main feature addition across the changeset, which implements CRETransport and related options to enable calling TN actions in ChainLink CRE environments.
✨ 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 CreSupportEnabled

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.

@holdex
Copy link
Copy Markdown

holdex bot commented Dec 23, 2025

Time Submission Status

Member Status Time Action Last Update
MicBun ✅ Submitted 4h Update time Dec 23, 2025, 12:34 PM

Copy link
Copy Markdown
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: 1

🧹 Nitpick comments (5)
core/tnclient/options_cre.go (2)

74-80: Silent error suppression may hide transport creation failures.

The error from NewCRETransport is discarded with _. While NewCRETransport currently always returns nil error, this pattern can hide failures if the constructor is modified in the future to return errors.

Consider either:

  1. Logging the error if it occurs
  2. Panicking on error (since this is configuration-time)
  3. Storing the error and surfacing it later during NewClient
🔎 Proposed fix to handle the error
 func WithCRETransport(runtime cre.NodeRuntime, endpoint string) Option {
 	return func(c *Client) {
 		// Note: Transport is created immediately with the current signer (if set)
 		// If WithSigner is applied after this option, the signer won't be available yet
 		// For guaranteed signer availability, use WithCRETransportAndSigner instead
-		c.transport, _ = NewCRETransport(runtime, endpoint, c.signer)
+		transport, err := NewCRETransport(runtime, endpoint, c.signer)
+		if err != nil {
+			// Store error to be returned by NewClient, or panic since this is config-time
+			panic(fmt.Sprintf("failed to create CRE transport: %v", err))
+		}
+		c.transport = transport
 	}
 }

101-108: Same silent error suppression concern.

Apply the same error handling improvement here for consistency.

🔎 Proposed fix to handle the error
 func WithCRETransportAndSigner(runtime cre.NodeRuntime, endpoint string, signer auth.Signer) Option {
 	return func(c *Client) {
 		// Set signer first
 		c.signer = signer
 
 		// Then create CRE transport with the signer
-		c.transport, _ = NewCRETransport(runtime, endpoint, signer)
+		transport, err := NewCRETransport(runtime, endpoint, signer)
+		if err != nil {
+			panic(fmt.Sprintf("failed to create CRE transport: %v", err))
+		}
+		c.transport = transport
 	}
 }
core/tnclient/transport_cre.go (3)

274-310: First transaction check is delayed by one interval.

The ticker-based polling waits for the first tick before checking transaction status, introducing an unnecessary delay equal to the polling interval. Consider checking immediately before entering the poll loop:

🔎 Proposed fix for immediate first check
 func (t *CRETransport) WaitTx(ctx context.Context, txHash types.Hash, interval time.Duration) (*types.TxQueryResponse, error) {
 	ticker := time.NewTicker(interval)
 	defer ticker.Stop()

 	for {
+		// Query transaction status
+		params := map[string]any{
+			"tx_hash": txHash,
+		}
+
+		var result types.TxQueryResponse
+		if err := t.callJSONRPC(ctx, "user.tx_query", params, &result); err != nil {
+			errMsg := err.Error()
+			isTransient := containsAny(errMsg, []string{"not found", "not indexed", "pending", "unknown transaction"})
+			if !isTransient {
+				return nil, fmt.Errorf("transaction query failed: %w", err)
+			}
+		} else if result.Height > 0 {
+			return &result, nil
+		}
+
 		select {
 		case <-ctx.Done():
 			return nil, ctx.Err()
 		case <-ticker.C:
-			// Query transaction status
-			params := map[string]any{
-				"tx_hash": txHash,
-			}
-
-			var result types.TxQueryResponse
-			if err := t.callJSONRPC(ctx, "user.tx_query", params, &result); err != nil {
-				// ... error handling
-			}
-
-			if result.Height > 0 {
-				return &result, nil
-			}
+			// Continue to next iteration for retry
 		}
 	}
 }

312-343: Consider using standard library for case-insensitive matching.

The custom implementation works but could be simplified with strings.ToLower and strings.Contains. If there's a specific reason to avoid the strings package in WASI (allocations, build constraints), please add a comment explaining the choice.

🔎 Simplified alternative
+import "strings"
+
 // containsAny checks if a string contains any of the specified substrings (case-insensitive)
 func containsAny(s string, substrings []string) bool {
-	lowerS := s
+	lowerS := strings.ToLower(s)
 	for _, substr := range substrings {
-		if len(substr) == 0 {
-			continue
-		}
-		// Simple case-insensitive substring check
-		for i := 0; i <= len(lowerS)-len(substr); i++ {
-			match := true
-			for j := 0; j < len(substr); j++ {
-				c1 := lowerS[i+j]
-				c2 := substr[j]
-				// Convert to lowercase for comparison
-				if c1 >= 'A' && c1 <= 'Z' {
-					c1 += 'a' - 'A'
-				}
-				if c2 >= 'A' && c2 <= 'Z' {
-					c2 += 'a' - 'A'
-				}
-				if c1 != c2 {
-					match = false
-					break
-				}
-			}
-			if match {
-				return true
-			}
+		if strings.Contains(lowerS, strings.ToLower(substr)) {
+			return true
 		}
 	}
 	return false
 }

368-377: Using context.Background() limits caller control.

This method uses context.Background() inside sync.Once, meaning callers cannot control timeout or cancellation for the chain ID fetch. Since this shares chainIDOnce with Execute, whichever method is called first determines the fetch behavior. Consider accepting a context parameter or documenting this behavior.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8cb4a98 and a780a0f.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (9)
  • core/tnclient/options_cre.go
  • core/tnclient/transport.go
  • core/tnclient/transport_cre.go
  • core/tnclient/transport_cre_test.go
  • docs/api-reference.md
  • docs/stream-permissions.md
  • examples/stream_cache_demo/go.mod
  • go.mod
  • tests/integration/composed_actions_test.go
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-10T15:39:48.576Z
Learnt from: MicBun
Repo: trufnetwork/sdk-go PR: 138
File: examples/cache_example.go:21-21
Timestamp: 2025-07-10T15:39:48.576Z
Learning: For example code in the trufnetwork/sdk-go repository, the maintainer MicBun prefers to keep examples simple and focused on demonstrating functionality rather than implementing production-ready security practices like environment variables for sensitive values.

Applied to files:

  • tests/integration/composed_actions_test.go
  • go.mod
  • core/tnclient/transport_cre_test.go
  • docs/api-reference.md
  • examples/stream_cache_demo/go.mod
🧬 Code graph analysis (3)
core/tnclient/options_cre.go (2)
core/tnclient/client.go (1)
  • Option (29-29)
core/tnclient/transport_cre.go (1)
  • NewCRETransport (81-89)
core/tnclient/transport_cre_test.go (3)
core/tnclient/transport_cre.go (2)
  • NewCRETransport (81-89)
  • CRETransport (50-59)
core/tnclient/transport.go (1)
  • Transport (38-95)
core/tnclient/options_cre.go (2)
  • WithCRETransport (74-81)
  • WithCRETransportAndSigner (101-109)
core/tnclient/transport_cre.go (2)
core/tnclient/transport.go (1)
  • Transport (38-95)
core/contractsapi/stream.go (1)
  • Action (15-17)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test
🔇 Additional comments (18)
core/tnclient/transport.go (1)

12-12: Documentation branding update looks good.

Consistent terminology update to "TRUF.NETWORK" aligns with the broader PR's documentation changes.

tests/integration/composed_actions_test.go (1)

16-16: Documentation branding update looks good.

Consistent terminology update to "TRUF.NETWORK (TN)" aligns with the broader PR's documentation changes.

docs/api-reference.md (1)

175-175: Documentation branding update looks good.

Consistent terminology update to "TRUF.NETWORK" aligns with other documentation changes in this PR.

docs/stream-permissions.md (1)

142-160: Documentation improvements look good.

The branding update and new sections for "How to get access" and "SDK Role Management API" provide clear guidance for partners and advanced users.

go.mod (2)

3-3: Verify Go version 1.25.3 exists.

Same concern as the example module - Go 1.25.3 may not be a released version.


12-13: CRE SDK dependencies are correctly added.

Both CRE SDK dependencies are properly specified in go.mod to support the new CRE transport feature. The versions v1.1.2 and v0.10.0 are valid and publicly available.

core/tnclient/options_cre.go (1)

1-8: Build tag and imports look correct.

The wasip1 build tag appropriately restricts this file to WASM environments where CRE runs. Imports are minimal and correct.

core/tnclient/transport_cre_test.go (4)

15-27: Basic structural test for CRE transport constructor.

The test appropriately documents that full integration testing requires the CRE environment. The approach of verifying the function signature exists is reasonable.


29-38: Compile-time interface check is good practice.

This documents and verifies that CRETransport implements Transport.


67-185: Example functions contain only comments.

The example functions have all code inside multi-line comments (/* ... */). While this serves as documentation, these won't be executed as Go examples by go test. This is intentional per the comment noting CRE environment requirement.


40-63: Option function tests verify API surface.

These tests ensure the option constructors exist and return the correct type. The implementations safely store the runtime parameter without dereferencing it during option application, so passing nil in the tests does not cause issues at this stage.

examples/stream_cache_demo/go.mod (1)

3-3: No action required. Go 1.25.3 is a valid, officially released version (released October 13, 2025).

core/tnclient/transport_cre.go (6)

23-59: Well-structured transport with clear documentation.

The struct is well-designed with appropriate concurrency primitives (sync.Once for lazy chainID init, atomic.Uint64 for request IDs). The usage example in the doc comment is helpful for CRE workflow integration.


61-89: LGTM!

Compile-time interface verification is good practice. The constructor is appropriately simple, and returning an error for future extensibility is acceptable.


97-160: Solid JSON-RPC implementation.

The error handling is comprehensive, covering marshal failures, HTTP errors, JSON-RPC errors, and version validation. Using HTTP 200 as the only success code is correct for JSON-RPC 2.0 specification.


162-181: LGTM!

Clean implementation of the read-only Call method matching the Transport interface.


345-361: LGTM!

The fetch implementation is straightforward. The sync.Once usage concern was addressed in the Execute method review.


379-384: LGTM!

Simple accessor correctly implements the Transport interface.

Copy link
Copy Markdown
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 (4)
core/tnclient/transport_cre.go (4)

81-89: Add parameter validation and reconsider error return.

The constructor has two issues:

  1. It always returns nil error, making the error return value misleading. Either return errors when validation fails or simplify the signature to func NewCRETransport(...) *CRETransport.
  2. No validation is performed on required parameters (runtime and endpoint).
🔎 Suggested validation improvements
 func NewCRETransport(runtime cre.NodeRuntime, endpoint string, signer auth.Signer) (*CRETransport, error) {
+	if runtime == nil {
+		return nil, fmt.Errorf("runtime cannot be nil")
+	}
+	if endpoint == "" {
+		return nil, fmt.Errorf("endpoint cannot be empty")
+	}
+
 	return &CRETransport{
 		runtime:  runtime,
 		client:   &http.Client{},
 		endpoint: endpoint,
 		signer:   signer,
-		chainID:  "", // Will be fetched on first call if needed
 	}, nil
 }

132-134: Consider accepting all 2xx HTTP status codes.

The current check only accepts status code 200, which may be overly strict. Standard HTTP practice treats all 2xx codes as successful responses (e.g., 201 Created, 204 No Content).

🔎 Suggested improvement
 	// Check HTTP status
-	if httpResp.StatusCode != 200 {
-		return fmt.Errorf("unexpected HTTP status code: %d", httpResp.StatusCode)
+	if httpResp.StatusCode < 200 || httpResp.StatusCode >= 300 {
+		return fmt.Errorf("unexpected HTTP status code: %d", httpResp.StatusCode)
 	}

328-358: Simplify with standard library functions.

The custom case-insensitive substring search is complex and error-prone. The standard library provides simpler alternatives.

🔎 Simplified implementation using strings package
+import "strings"
+
 // containsAny checks if a string contains any of the specified substrings (case-insensitive)
 func containsAny(s string, substrings []string) bool {
-	lowerS := s
+	lowerS := strings.ToLower(s)
 	for _, substr := range substrings {
-		if len(substr) == 0 {
-			continue
-		}
-		// Simple case-insensitive substring check
-		for i := 0; i <= len(lowerS)-len(substr); i++ {
-			match := true
-			for j := 0; j < len(substr); j++ {
-				c1 := lowerS[i+j]
-				c2 := substr[j]
-				// Convert to lowercase for comparison
-				if c1 >= 'A' && c1 <= 'Z' {
-					c1 += 'a' - 'A'
-				}
-				if c2 >= 'A' && c2 <= 'Z' {
-					c2 += 'a' - 'A'
-				}
-				if c1 != c2 {
-					match = false
-					break
-				}
-			}
-			if match {
-				return true
-			}
+		if strings.Contains(lowerS, strings.ToLower(substr)) {
+			return true
 		}
 	}
 	return false
 }

363-376: Validate chain ID is non-empty after fetch.

The fetchChainID method doesn't verify that the returned chain_id is non-empty. While the Execute method checks for this (line 249), it's better to validate at the point of fetching.

🔎 Add validation
 	if err := t.callJSONRPC(ctx, "user.chain_info", map[string]any{}, &result); err != nil {
 		return fmt.Errorf("failed to fetch chain ID: %w", err)
 	}
 
+	if result.ChainID == "" {
+		return fmt.Errorf("chain ID is empty in response")
+	}
+
 	// Cache the chain ID
 	t.chainID = result.ChainID
 	return nil
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a780a0f and be948c2.

📒 Files selected for processing (1)
  • core/tnclient/transport_cre.go
🧰 Additional context used
🧬 Code graph analysis (1)
core/tnclient/transport_cre.go (1)
core/tnclient/transport.go (1)
  • Transport (38-95)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test
🔇 Additional comments (5)
core/tnclient/transport_cre.go (5)

1-21: LGTM! Build constraint and imports are appropriate.

The wasip1 build constraint correctly limits this implementation to the WebAssembly System Interface environment where CRE operates, and the imports include all necessary dependencies for thread-safe JSON-RPC communication.


50-62: Past review concern successfully addressed!

The previous review flagged that sync.Once would permanently cache chain ID fetch failures. The current implementation uses a mutex-based pattern with chainIDMu and chainIDInitialized, allowing retries on transient failures. This is the correct approach.

Based on learnings from past review comments.


167-181: LGTM! Clean read-only call implementation.

The method correctly constructs the JSON-RPC parameters and delegates to the common callJSONRPC helper with proper error handling.


226-251: Double-checked locking correctly implements retry-on-failure pattern.

The chain ID initialization uses double-checked locking with proper mutex management. The pattern is complex but correctly addresses the past review concern about permanent failure caching:

  1. Fast path with read lock (lines 229-232)
  2. Slow path with write lock and double-check (lines 234-246)
  3. Safety check for empty chain ID (lines 249-251)

This ensures transient failures don't permanently prevent chain ID fetching while maintaining thread safety.

Based on learnings from past review comments.


417-419: LGTM! Simple and correct accessor.

The Signer() method correctly returns the configured signer or nil for read-only mode.

Copy link
Copy Markdown
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: 1

🧹 Nitpick comments (4)
core/tnclient/transport_cre.go (4)

81-89: Constructor never returns an error.

The function signature includes an error return but always returns nil. Consider either adding validation (e.g., endpoint format check) or documenting why the error is preserved for future extensibility.

🔎 Optional: Add basic validation or simplify
 func NewCRETransport(runtime cre.NodeRuntime, endpoint string, signer auth.Signer) (*CRETransport, error) {
+	if endpoint == "" {
+		return nil, fmt.Errorf("endpoint is required")
+	}
 	return &CRETransport{
 		runtime:  runtime,
 		client:   &http.Client{},
 		endpoint: endpoint,
 		signer:   signer,
 		chainID:  "", // Will be fetched on first call if needed
 	}, nil
 }

289-320: Initial poll delay adds unnecessary latency.

The ticker-based loop waits for the full interval before the first poll. For faster feedback, consider querying immediately before starting the ticker loop.

🔎 Optional: Poll immediately before ticker loop
 func (t *CRETransport) WaitTx(ctx context.Context, txHash types.Hash, interval time.Duration) (*types.TxQueryResponse, error) {
 	ticker := time.NewTicker(interval)
 	defer ticker.Stop()

+	// Helper to query transaction
+	queryTx := func() (*types.TxQueryResponse, error, bool) {
+		params := map[string]any{"tx_hash": txHash}
+		var result types.TxQueryResponse
+		if err := t.callJSONRPC(ctx, "user.tx_query", params, &result); err != nil {
+			if !isTransientTxError(err) {
+				return nil, fmt.Errorf("transaction query failed: %w", err), false
+			}
+			return nil, nil, true // transient, continue
+		}
+		if result.Height > 0 {
+			return &result, nil, false
+		}
+		return nil, nil, true // not finalized, continue
+	}
+
+	// Immediate first check
+	if result, err, cont := queryTx(); !cont {
+		return result, err
+	}
+
 	for {
 		select {
 		case <-ctx.Done():
 			return nil, ctx.Err()
 		case <-ticker.C:
-			// Query transaction status
-			params := map[string]any{
-				"tx_hash": txHash,
-			}
-
-			var result types.TxQueryResponse
-			if err := t.callJSONRPC(ctx, "user.tx_query", params, &result); err != nil {
-				// Distinguish between transient errors (retry-able) and permanent errors
-				if !isTransientTxError(err) {
-					// Permanent error - authentication failure, network issues, malformed request
-					return nil, fmt.Errorf("transaction query failed: %w", err)
-				}
-				// Transient error (tx not indexed yet) - continue polling
-				continue
-			}
-
-			// Check if transaction is finalized (either committed or rejected)
-			if result.Height > 0 {
-				return &result, nil
+			if result, err, cont := queryTx(); !cont {
+				return result, err
 			}
 		}
 	}
 }

413-429: Stale comment mentions sync.Once.

The comment at line 414 says "This is called once via sync.Once" but the implementation now uses mutex-based lazy initialization. Update the comment to reflect the current design.

🔎 Update comment
 // fetchChainID fetches and caches the chain ID from the gateway.
-// This is called once via sync.Once to ensure thread-safe lazy initialization.
-// Returns error if the fetch fails, which can be checked before critical operations.
+// This is called lazily with mutex-based synchronization to allow retry on transient failures.
+// Returns error if the fetch fails; the caller can retry on the next call.
 func (t *CRETransport) fetchChainID(ctx context.Context) error {

377-411: Use standard library strings package instead of custom implementations.

The strings package is available in wasip1 and works without issues. Replace toLower() with strings.ToLower(), contains() with strings.Contains(), and indexOfSubstring() with strings.Index(). These functions are only used for error message matching (lines where toLower(errMsg) and contains(lowerMsg, pattern) appear), making stdlib equivalents the more idiomatic choice.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between be948c2 and 8fe8cf9.

📒 Files selected for processing (2)
  • core/tnclient/transport_cre.go
  • core/tnclient/transport_cre_test.go
🧰 Additional context used
🧬 Code graph analysis (1)
core/tnclient/transport_cre.go (1)
core/tnclient/transport.go (1)
  • Transport (38-95)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test
🔇 Additional comments (12)
core/tnclient/transport_cre_test.go (5)

1-10: LGTM on build tag and imports.

The wasip1 build constraint correctly limits these tests to the CRE/WASM environment where the implementation is available.


16-28: LGTM on constructor existence test.

The test correctly verifies the function exists and acknowledges the CRE environment limitation.


30-39: LGTM on interface conformance test.

Good practice to include this compile-time check in tests for documentation, even though it's already verified in the implementation file.


41-64: LGTM on option function tests.

The nil parameters are acceptable for signature verification tests since the options aren't exercised beyond type checking.


68-157: Comprehensive test coverage for transient error detection.

The table-driven tests thoroughly cover JSON-RPC error codes, fallback patterns, and case insensitivity. The reasoning field provides good documentation of expected behavior.

core/tnclient/transport_cre.go (7)

1-21: LGTM on package structure and imports.

Imports are well-organized with standard library first, then internal dependencies, and finally external CRE SDK packages.


50-62: LGTM on struct design and interface compliance.

The struct correctly uses sync.RWMutex with a boolean flag for retry-capable lazy initialization, addressing the previous review concern about sync.Once permanently caching failures.


97-160: LGTM on JSON-RPC call implementation.

The method correctly handles request marshaling, CRE HTTP execution, response parsing, and error propagation. The HTTP 200 check is appropriate for JSON-RPC where errors are returned in the response body.


162-181: LGTM on read-only Call implementation.

The method correctly builds parameters matching the kwil-db user.call endpoint format and delegates to callJSONRPC.


226-251: Chain ID fetching addresses previous review concern.

The double-checked locking pattern correctly allows retry on transient failures by only setting chainIDInitialized = true on success. This resolves the previous sync.Once permanent failure issue.


437-469: Retry-capable lazy initialization with timeout.

The implementation correctly uses double-checked locking with a 10-second timeout, addressing the previous review concerns about permanent failure caching and indefinite hanging.


471-476: LGTM on Signer getter.

Simple accessor matching the Transport interface.

Copy link
Copy Markdown
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: 1

🧹 Nitpick comments (3)
core/tnclient/transport_cre.go (3)

83-91: Constructor never returns an error.

The NewCRETransport function signature includes an error return, but the implementation always returns nil. Consider simplifying the signature to func NewCRETransport(...) *CRETransport unless you anticipate validation or initialization errors in the future.

🔎 Simplified signature
-func NewCRETransport(runtime cre.NodeRuntime, endpoint string, signer auth.Signer) (*CRETransport, error) {
+func NewCRETransport(runtime cre.NodeRuntime, endpoint string, signer auth.Signer) *CRETransport {
 	return &CRETransport{
 		runtime:  runtime,
 		client:   &http.Client{},
 		endpoint: endpoint,
 		signer:   signer,
 		chainID:  "", // Will be fetched on first call if needed
-	}, nil
+	}
 }

Note: This would be a breaking change for existing callers.


336-385: Compile regex pattern once for better performance.

The regex pattern at line 347 is compiled on every call to isTransientTxError. Since WaitTx polls repeatedly and calls this function each time, the compilation overhead accumulates. Compile the pattern once as a package-level variable.

🔎 Proposed optimization
+// Package-level regex for extracting error codes from JSON-RPC error messages
+var jsonrpcCodePattern = regexp.MustCompile(`\(code:\s*(-?\d+)\)`)
+
 // isTransientTxError determines if an error from tx_query is transient (retry-able).
 //
 // Strategy:
 // 1. First, try to parse as JSON-RPC error and check error code
 // 2. Fall back to substring matching if not a structured JSON-RPC error
 //
 // Known transient error codes:
 //   - -202 (ErrorTxNotFound): Transaction not yet indexed
 //   - -32001 (ErrorTimeout): Temporary timeout
 //
 // Fragility warning: The substring fallback is brittle and may misclassify errors.
 // Consider adding structured error codes to the gateway API for better reliability.
 func isTransientTxError(err error) bool {
 	if err == nil {
 		return false
 	}
 
 	// Try to extract JSON-RPC error code from the error message
 	// The error from callJSONRPC is formatted as "JSON-RPC error: <message> (code: <code>)"
 	errMsg := err.Error()
 
-	// Use regex to extract error code from "(code: <number>)" pattern
-	// This handles multi-word error messages unlike fmt.Sscanf with %*s
-	codePattern := regexp.MustCompile(`\(code:\s*(-?\d+)\)`)
-	matches := codePattern.FindStringSubmatch(errMsg)
+	// Extract error code from "(code: <number>)" pattern
+	matches := jsonrpcCodePattern.FindStringSubmatch(errMsg)

387-389: Update outdated comment.

Line 388 mentions sync.Once, but the implementation now uses a mutex-based lazy initialization pattern (as seen in Execute and ChainID methods). Update the comment to reflect the current implementation.

🔎 Suggested comment update
 // fetchChainID fetches and caches the chain ID from the gateway.
-// This is called once via sync.Once to ensure thread-safe lazy initialization.
+// This is called lazily with mutex-based double-checked locking for thread-safe initialization.
 // Returns error if the fetch fails, which can be checked before critical operations.
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8fe8cf9 and 573ac4c.

📒 Files selected for processing (2)
  • core/tnclient/transport_cre.go
  • core/tnclient/transport_cre_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • core/tnclient/transport_cre_test.go
🧰 Additional context used
🧬 Code graph analysis (1)
core/tnclient/transport_cre.go (1)
core/tnclient/transport.go (1)
  • Transport (38-95)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test
🔇 Additional comments (2)
core/tnclient/transport_cre.go (2)

99-162: Well-structured JSON-RPC implementation.

The method provides comprehensive error handling, proper validation of HTTP status and JSON-RPC version, and clear error messages. The use of CRE's promise-based HTTP client is correctly implemented.


430-433: Good timeout protection added.

The 10-second timeout context prevents indefinite hanging when fetching chain metadata. This addresses the previous review concern about uncancellable contexts in ChainID().

Copy link
Copy Markdown
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

🧹 Nitpick comments (1)
core/tnclient/transport_cre.go (1)

66-91: Consider validating the endpoint parameter.

The constructor doesn't validate that endpoint is non-empty or a well-formed URL. While errors would surface during the first HTTP call, early validation provides clearer error messages.

🔎 Optional validation enhancement
 func NewCRETransport(runtime cre.NodeRuntime, endpoint string, signer auth.Signer) (*CRETransport, error) {
+	if endpoint == "" {
+		return nil, fmt.Errorf("endpoint cannot be empty")
+	}
+	// Note: URL parsing could be added but may be overly strict for CRE contexts
 	return &CRETransport{
 		runtime:  runtime,
 		client:   &http.Client{},
 		endpoint: endpoint,
 		signer:   signer,
 		chainID:  "", // Will be fetched on first call if needed
 	}, nil
 }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 573ac4c and 9add162.

📒 Files selected for processing (1)
  • core/tnclient/transport_cre.go
🧰 Additional context used
🧬 Code graph analysis (1)
core/tnclient/transport_cre.go (1)
core/tnclient/transport.go (1)
  • Transport (38-95)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test
🔇 Additional comments (11)
core/tnclient/transport_cre.go (11)

1-23: LGTM! Clean imports and appropriate build constraint.

The wasip1 build constraint correctly limits this transport to CRE/Wasm environments, and all imports are necessary for the CRE HTTP client integration.


25-64: Excellent documentation and thread-safe design!

The struct documentation provides clear usage examples for CRE workflows. The implementation uses proper concurrency primitives (sync.RWMutex for chain ID caching, atomic.Uint64 for request IDs) and includes compile-time interface verification.


93-97: LGTM! Thread-safe request ID generation.

The atomic operation ensures safe concurrent access without locks.


99-162: LGTM! Robust JSON-RPC implementation with comprehensive error handling.

The method properly:

  • Validates HTTP status (200 is correct for JSON-RPC)
  • Checks JSON-RPC version and error fields
  • Conditionally unmarshals results
  • Provides descriptive error messages at each stage

164-183: LGTM! Clean read-only action implementation.

The method correctly maps to kwil-db's user.call endpoint with appropriate parameter structure.


185-282: Excellent implementation with proper chain ID handling!

The Execute method correctly:

  • Validates signer presence upfront
  • Encodes inputs using kwil-db types
  • Uses double-checked locking for chain ID initialization
  • Only marks chain ID as initialized after successful fetch with validation (addresses past review feedback)
  • Signs and broadcasts the transaction

The chain ID validation fix from previous reviews has been properly implemented.


284-319: LGTM! Robust transaction polling with proper error classification.

The method correctly:

  • Uses ticker with deferred cleanup
  • Handles context cancellation
  • Distinguishes transient (retry-able) from permanent errors
  • Finalizes on Height > 0

321-382: Excellent fix for error code extraction!

The regex-based approach correctly addresses the previous Sscanf issue with multi-word error messages. The pattern \(code:\s*(-?\d+)\) reliably extracts error codes from the JSON-RPC error format, and the fallback substring matching is appropriately documented as fragile.

The fix properly handles:

  • Multi-word error messages (previous Sscanf with %*s failed here)
  • Optional whitespace
  • Negative error codes

384-405: Perfect! Chain ID validation happens before caching.

The validation at lines 398-400 ensures that empty chain IDs are rejected before caching, which prevents the initialized flag from being set on failures. This properly addresses the previous review feedback about validating before marking as initialized.


407-445: Excellent timeout protection added!

The method now creates a 10-second timeout context (lines 434-435) to prevent indefinite hangs, properly addressing the previous review feedback about using uncancellable context.Background(). The double-checked locking pattern and retry-on-failure behavior (not setting initialized flag on error) are both correct.


447-452: LGTM! Simple and well-documented accessor.

@MicBun MicBun merged commit 7335f30 into main Dec 23, 2025
5 checks passed
@MicBun MicBun deleted the CreSupportEnabled branch December 23, 2025 12:55
@coderabbitai coderabbitai bot mentioned this pull request Feb 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant