Skip to content

chore: add leader signature to attestation#1212

Merged
MicBun merged 15 commits intomainfrom
chore/sign-ext
Oct 13, 2025
Merged

chore: add leader signature to attestation#1212
MicBun merged 15 commits intomainfrom
chore/sign-ext

Conversation

@outerlook
Copy link
Contributor

@outerlook outerlook commented Oct 10, 2025

Description

Complete leader-only attestation signing: canonical payload parsing → secp256k1 signing → broadcast → async status monitoring.

Signing Pipeline

  • Parse 8-field canonical payloads from SQL blobs
  • New SignDigest() for direct 32-byte hash signing (EVM-compatible)
  • Process queued hashes in EndBlock with periodic DB fallback scan

Async Monitoring

  • Background goroutine polls tx status (2s, 5s, 10s×10) without blocking consensus
  • 128-entry queue with drop-on-full, survives leader transitions via sync.Once

SQL Migration

  • sign_attestation() action enforces leader-only access, stores signatures with validator pubkey + height

Related Problem

How Has This Been Tested?

Summary by CodeRabbit

  • New Features

    • TN attestation extension: canonical payload parsing, SHA‑256 digest signing, leader-aware signing workflow, async transaction monitoring, integrated JSON‑RPC broadcasting with status querying, and a new on‑chain sign_attestation action.
  • Documentation

    • Added high‑level attestation workflow and component docs.
  • Tests

    • Extensive unit, integration, and harness tests covering parsing, hashing, signing, broadcasting, leader lifecycle, and status monitoring.
  • Chores

    • Minor migration comment update and added extension name constant.

- Simplified the comment in the attestation schema migration by removing the composite primary key reference for the attestations table.
- Removed the detailed comment block in the attestation actions migration, streamlining the file for better readability.

These changes enhance the clarity and maintainability of the migration files.
…ttestations

- Introduced the tn_attestation extension, which includes functionality for signing attestation payloads using a validator's key, processing attestation hashes, and managing the attestation queue.
- Added methods for preparing signing work, submitting signatures, and handling attestation lifecycle events.
- Implemented comprehensive tests to ensure the reliability of the signing workflow and attestation processing.
- Enhanced the integration with the existing system by ensuring proper logging and error handling throughout the attestation process.

These changes improve the overall functionality and maintainability of the attestation system, enabling efficient signing and processing of attestations in the Kwil framework.
@coderabbitai
Copy link

coderabbitai bot commented Oct 10, 2025

Walkthrough

Adds a tn_attestation extension that parses canonical payloads, prepares and signs attestations (leader-only), broadcasts sign_attestation transactions via a JSON‑RPC broadcaster, monitors tx status asynchronously, updates migrations/actions, and includes extensive unit and integration tests.

Changes

Cohort / File(s) Summary
Extension core & lifecycle
extensions/tn_attestation/extension.go, extensions/tn_attestation/tn_attestation.go, extensions/tn_attestation/doc.go, extensions/tn_attestation/constants.go
New singleton extension wiring, synchronized dependency accessors, config parsing (scan_interval_blocks, scan_batch_limit), leader acquire/lose hooks, EndBlock integration, queue lifecycle management, docs and ExtensionName.
Signing worker & queue processing
extensions/tn_attestation/worker.go, extensions/tn_attestation/processor.go
Implements processing pipeline: fetchUnsignedAttestations, prepareSigningWork, computeAttestationHash, submitSignature (nonce handling, tx creation/signing), processAttestationHashes, async status worker and enqueue/monitor logic; adds PreparedSignature.
Canonical payload + tests
extensions/tn_attestation/canonical.go, extensions/tn_attestation/canonical_test.go
Adds CanonicalPayload, strict binary parser ParseCanonicalPayload, SigningBytes/SigningDigest, length-prefixed decoding helper, and tests for success/truncation/extra-bytes.
Broadcaster & tx query interfaces/tests
extensions/tn_attestation/broadcast.go, extensions/tn_attestation/status_worker_test.go
Adds TxBroadcaster and TxQueryClient interfaces, listen-address normalization, makeBroadcasterFromURL factory (shared JSON‑RPC client/pool), sync-mode broadcast (wait accept/commit), optional TxQuery follow-up, and status worker tests.
Signer API & tests
extensions/tn_attestation/signer.go, extensions/tn_attestation/signer_test.go
Adds ValidatorSigner.SignDigest([]byte) with digest-length validation; SignKeccak256 delegates to it; tests updated to use SHA‑256 digest-based signing/verification.
Integration / harness tests
extensions/tn_attestation/integration_test.go, extensions/tn_attestation/harness_integration_test.go, extensions/tn_attestation/tn_attestation_test.go, extensions/tn_attestation/processor_test.go
Extensive unit and integration tests exercising queue vs fallback flows, capture broadcaster, DB/engine stubs, harness-driven sign_attestation execution, lifecycle behavior, and pending-hash retrieval.
Migrations (actions/schema)
internal/migrations/023-attestation-schema.sql, internal/migrations/024-attestation-actions.sql
Minor comment edit in 023; adds leader-only sign_attestation(attestation_hash BYTEA, requester BYTEA, created_height INT8, signature BYTEA) action and aligns attestation_hash construction in 024.
Streams test adjustments
tests/streams/attestation/attestation_request_test.go
Adds requester column handling and remaps scan indices to accommodate new column ordering.
Precompile formatting
extensions/tn_attestation/precompile.go
Whitespace/alignment only.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Node
  participant Ext as tn_attestation
  participant DB
  participant Signer
  participant Broadcaster
  participant StatusQ as Status Worker

  Note over Node,Ext: EndBlock (leader only)
  Node->>Ext: OnEndBlock(height)
  alt In-memory queue non-empty
    Ext->>Ext: dequeue hashes
  else Fallback scan window reached
    Ext->>DB: fetchPendingHashes(limit)
    DB-->>Ext: [attestation_hash...]
  end
  loop For each hash
    Ext->>DB: fetchUnsignedAttestations(hash)
    DB-->>Ext: records (payload, requester, created_height)
    Ext->>Signer: SignDigest(SHA256(payload.SigningBytes()))
    Signer-->>Ext: 65-byte signature
    Ext->>Broadcaster: BroadcastTx(sign_attestation(hash, requester, height, sig))
    Broadcaster-->>Ext: txHash (+optional TxResult)
    Ext->>StatusQ: enqueueStatusCheck(txHash, hash, requester)
  end

  par Async status monitoring
    StatusQ->>Broadcaster: TxQuery(txHash) (retries/backoff)
    Broadcaster-->>StatusQ: TxQueryResponse
    StatusQ-->>Ext: log outcome
  end
Loading
sequenceDiagram
  autonumber
  participant Ext as tn_attestation
  participant RPC as JSON-RPC
  participant Pool as Conn Pool

  Note over Ext,RPC: Broadcaster initialization
  Ext->>Ext: normalizeListenAddressForClient(config.rpc_url || node RPC)
  Ext->>Pool: makeBroadcasterFromURL(url) (shared client/pool)
  Ext->>RPC: broadcast_tx(sync=BroadcastWaitAccept|BroadcastWaitCommit)
  alt sync=BroadcastWaitCommit (blocking)
    Ext->>RPC: tx_query(txHash)
    RPC-->>Ext: TxQueryResponse
  else sync=BroadcastWaitAccept (fast)
    RPC-->>Ext: txHash
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

type: chore

Suggested reviewers

  • MicBun

Poem

🐇 I nibble bytes and chase the hash,
I sign with hops and gentle dash.
Canonical crumbs I parse and bake,
I broadcast claims and then awake.
Hooray! Confirmed — the rabbit laughs.

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Title Check ✅ Passed The pull request title "chore: add leader signature to attestation" accurately reflects the primary change of adding leader-driven signature logic for attestations by the attestation extension, and it is concise and specific to the main feature implemented.
Linked Issues Check ✅ Passed The changes fully satisfy the acceptance criteria of issue #1209 by introducing the tn_attestation_signer extension, registering leaderwatch and EndBlock hooks, implementing an in-memory signing queue with periodic DB fallback scans, supporting the scan_interval_blocks configuration, handling leader acquire and loss events properly, processing attestations on EndBlock, and ensuring non-leaders are no-ops.
Out of Scope Changes Check ✅ Passed All modifications relate directly to the attestation signing extension, its SQL migration, payload handling, signing workflow, broadcasting and status monitoring, or necessary test and minor schema adjustments, with no unrelated or extraneous changes introduced.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
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 chore/sign-ext

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.

@outerlook outerlook self-assigned this Oct 10, 2025
…dations

- Removed redundant length checks for attestation_hash, requester, and signature in the SQL action to simplify validation logic.
- Enhanced comments in the integration test to clarify the workflow being tested, focusing on the complete production signing process.
- Adjusted the test setup to ensure the tn_attestation precompile is registered correctly for testing.

These changes improve the clarity and maintainability of the integration tests and SQL action validations, ensuring a more robust attestation signing workflow.
- Introduced a new doc.go file for the tn_attestation package, detailing the attestation signing workflow and key components.
- Added a comment to the constants.go file to clarify the purpose of the ExtensionName constant.

These changes improve the documentation and clarity of the tn_attestation extension, aiding developers in understanding its functionality and usage.
…and transaction querying

- Removed the txBroadcasterFunc and replaced it with a jsonRPCBroadcaster for better clarity and functionality.
- Updated the ensureBroadcaster method to set both the broadcaster and transaction query client.
- Introduced a new method to check transaction status asynchronously, improving logging and error handling.
- Enhanced the submitSignature method to use non-blocking transaction broadcasting and added logging for transaction status.

These changes improve the maintainability and functionality of the tn_attestation extension, ensuring efficient transaction handling and better integration with the signing workflow.
…ension

- Introduced a dedicated background worker for asynchronous transaction status checks, improving responsiveness and logging.
- Replaced blocking transaction status checks with a queuing mechanism to prevent interference with consensus processing.
- Updated methods to streamline transaction broadcasting and status querying, ensuring better integration with the signing workflow.

These changes enhance the efficiency and maintainability of the tn_attestation extension, providing improved operational visibility for transaction confirmations.
…extension

- Introduced a new test file for the status worker, implementing tests to verify successful transaction processing and retry logic on failure.
- Created a fake transaction query client to simulate responses and errors, ensuring robust testing of the status worker's behavior.
- Enhanced the testing framework to validate the asynchronous handling of transaction status checks.

These changes improve the test coverage and reliability of the tn_attestation extension, ensuring proper functionality of the transaction status monitoring system.
…orker_test.go

- Added comments to the newFakeTxQueryClient function to clarify its purpose and usage, including details on expected parameters and behavior.
- This enhancement improves code readability and aids future developers in understanding the testing framework for the transaction status worker.

These changes contribute to better documentation practices within the tn_attestation extension's test suite.
- Added detailed comments in processor.go to explain the handling of unsigned attestations and the importance of hash verification during signing.
- Improved comments in worker.go regarding nonce handling for signatures and the resilience of the status worker across leader transitions.

These changes enhance code readability and provide better context for future developers working on the tn_attestation extension.
@outerlook outerlook marked this pull request as ready for review October 11, 2025 23:05
Copy link

@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: 3

🧹 Nitpick comments (1)
extensions/tn_attestation/worker.go (1)

115-126: Handle account lookup “not found” with typed errors, not string matching

Comparing err.Error() text for "not found" / "no rows" is brittle and can silently regress if upstream error messages change. Please switch to errors.Is (e.g. against sql.ErrNoRows or a dedicated sentinel returned by Accounts.GetAccount) or expose an explicit helper on the accounts subsystem so the transport isn’t coupled to error strings.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d9abbe4 and 7118ae7.

📒 Files selected for processing (20)
  • extensions/tn_attestation/broadcast.go (1 hunks)
  • extensions/tn_attestation/canonical.go (1 hunks)
  • extensions/tn_attestation/canonical_test.go (1 hunks)
  • extensions/tn_attestation/constants.go (1 hunks)
  • extensions/tn_attestation/doc.go (1 hunks)
  • extensions/tn_attestation/extension.go (1 hunks)
  • extensions/tn_attestation/harness_integration_test.go (1 hunks)
  • extensions/tn_attestation/integration_test.go (1 hunks)
  • extensions/tn_attestation/precompile.go (1 hunks)
  • extensions/tn_attestation/processor.go (1 hunks)
  • extensions/tn_attestation/processor_test.go (1 hunks)
  • extensions/tn_attestation/signer.go (2 hunks)
  • extensions/tn_attestation/signer_test.go (10 hunks)
  • extensions/tn_attestation/status_worker_test.go (1 hunks)
  • extensions/tn_attestation/tn_attestation.go (7 hunks)
  • extensions/tn_attestation/tn_attestation_test.go (2 hunks)
  • extensions/tn_attestation/worker.go (1 hunks)
  • internal/migrations/023-attestation-schema.sql (1 hunks)
  • internal/migrations/024-attestation-actions.sql (1 hunks)
  • tests/streams/attestation/attestation_request_test.go (2 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-10-10T13:00:13.658Z
Learnt from: outerlook
PR: trufnetwork/node#1207
File: internal/migrations/023-attestation-schema.sql:20-21
Timestamp: 2025-10-10T13:00:13.658Z
Learning: In the attestations table (internal/migrations/023-attestation-schema.sql), the primary key is (requester, created_height, attestation_hash) because attestation_hash is computed deterministically from user input only (version|algo|data_provider|stream_id|action_id|args) and does not include created_height. This allows the same user to request the same attestation at different block heights.

Applied to files:

  • internal/migrations/023-attestation-schema.sql
📚 Learning: 2025-08-20T12:26:14.229Z
Learnt from: outerlook
PR: trufnetwork/node#1113
File: extensions/tn_digest/extension.go:64-65
Timestamp: 2025-08-20T12:26:14.229Z
Learning: In the tn_digest extension singleton pattern, SetExtension uses direct assignment (not sync.Once) to allow test dependency injection and mock overrides after GetExtension has been called.

Applied to files:

  • extensions/tn_attestation/extension.go
📚 Learning: 2025-10-10T13:00:14.170Z
Learnt from: outerlook
PR: trufnetwork/node#1207
File: internal/migrations/024-attestation-actions.sql:58-76
Timestamp: 2025-10-10T13:00:14.170Z
Learning: In the attestation system for internal/migrations/024-attestation-actions.sql, the attestation_hash is computed from (version|algo|data_provider|stream_id|action_id|args) and intentionally excludes created_height. This design ensures the hash is deterministic based only on user input, not network state like block height.

Applied to files:

  • internal/migrations/024-attestation-actions.sql
🧬 Code graph analysis (10)
extensions/tn_attestation/signer_test.go (1)
extensions/tn_attestation/signer.go (1)
  • NewValidatorSigner (26-43)
extensions/tn_attestation/processor.go (2)
extensions/tn_attestation/canonical.go (2)
  • CanonicalPayload (24-35)
  • ParseCanonicalPayload (40-82)
extensions/tn_attestation/signer.go (1)
  • GetValidatorSigner (122-124)
extensions/tn_attestation/tn_attestation_test.go (2)
extensions/tn_attestation/extension.go (1)
  • SetExtension (61-63)
extensions/tn_attestation/queue.go (1)
  • GetAttestationQueue (115-120)
extensions/tn_attestation/canonical_test.go (1)
extensions/tn_attestation/canonical.go (1)
  • ParseCanonicalPayload (40-82)
extensions/tn_attestation/worker.go (1)
extensions/tn_attestation/processor.go (1)
  • PreparedSignature (25-32)
extensions/tn_attestation/extension.go (1)
extensions/tn_attestation/broadcast.go (2)
  • TxBroadcaster (125-127)
  • TxQueryClient (130-132)
extensions/tn_attestation/processor_test.go (2)
extensions/tn_attestation/canonical.go (2)
  • CanonicalPayload (24-35)
  • ParseCanonicalPayload (40-82)
extensions/tn_attestation/signer.go (2)
  • ResetValidatorSignerForTesting (128-131)
  • InitializeValidatorSigner (112-118)
extensions/tn_attestation/integration_test.go (4)
extensions/tn_attestation/signer.go (2)
  • InitializeValidatorSigner (112-118)
  • ResetValidatorSignerForTesting (128-131)
extensions/tn_attestation/canonical.go (1)
  • ParseCanonicalPayload (40-82)
extensions/tn_attestation/queue.go (1)
  • GetAttestationQueue (115-120)
extensions/tn_attestation/extension.go (1)
  • SetExtension (61-63)
extensions/tn_attestation/tn_attestation.go (1)
extensions/tn_attestation/queue.go (1)
  • GetAttestationQueue (115-120)
extensions/tn_attestation/harness_integration_test.go (8)
extensions/tn_attestation/constants.go (1)
  • ExtensionName (4-4)
extensions/tn_cache/tn_cache.go (1)
  • InitializeCachePrecompile (102-229)
extensions/database-size/extension.go (1)
  • InitializeDatabaseSizePrecompile (19-68)
extensions/tn_attestation/tn_attestation.go (1)
  • InitializeExtension (20-39)
internal/migrations/migration.go (1)
  • GetSeedScriptPaths (15-48)
extensions/tn_utils/serialization.go (1)
  • EncodeActionArgs (45-76)
extensions/tn_attestation/canonical.go (1)
  • ParseCanonicalPayload (40-82)
extensions/tn_attestation/signer.go (3)
  • ResetValidatorSignerForTesting (128-131)
  • InitializeValidatorSigner (112-118)
  • GetValidatorSigner (122-124)
⏰ 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: acceptance-test
🔇 Additional comments (9)
internal/migrations/023-attestation-schema.sql (1)

5-5: LGTM! Documentation clarity improved.

The updated comment provides a clearer, more concise description of the attestations table's purpose.

internal/migrations/024-attestation-actions.sql (2)

90-93: LGTM! Formatting improved.

The updated formatting improves readability without changing functionality.


97-157: LGTM! Leader-only signing action correctly implemented.

The sign_attestation action properly enforces:

  • Leader-only access via @leader_sender and @signer validation
  • Comprehensive input validation for all required parameters
  • Attestation existence and duplicate signature prevention
  • Atomic signature recording with validator identity and height

The composite key constraint in the UPDATE statement correctly matches the primary key definition from migration 023.

extensions/tn_attestation/doc.go (1)

1-15: LGTM! Clear and comprehensive package documentation.

The documentation effectively describes the attestation signing workflow, key components, and initialization requirements. This will help developers understand the extension's purpose and architecture.

extensions/tn_attestation/constants.go (1)

3-4: LGTM! Clean constant definition.

The ExtensionName constant provides a clear, centralized identifier for the attestation extension.

tests/streams/attestation/attestation_request_test.go (1)

144-178: LGTM! Test correctly updated for requester field.

The test updates properly handle the new requester field:

  • Struct definition includes the new field
  • SELECT statement retrieves the requester column
  • Column mapping correctly aligns with the query result order

The index adjustments are accurate and maintain proper data extraction.

extensions/tn_attestation/signer.go (2)

45-66: LGTM! SignDigest correctly implements EVM-compatible signing.

The SignDigest method provides a clean API for signing pre-hashed 32-byte digests:

  • Thread-safe access via RLock/RUnlock
  • Validates digest length matches crypto.DigestLength (32 bytes)
  • Adjusts V byte to EVM standard (27/28) for compatibility with Solidity's ecrecover

This enables flexible signing workflows where the caller controls the hash algorithm.


68-83: LGTM! SignKeccak256 refactored for code reuse.

The refactoring delegates core signing logic to SignDigest, eliminating code duplication while maintaining the existing API surface. This is a clean separation of concerns between hash computation and signature generation.

extensions/tn_attestation/signer_test.go (1)

34-350: LGTM! Comprehensive test coverage for SignDigest.

The test suite properly covers the new SignDigest API:

  • Happy path: Valid digest signing with SHA-256 (lines 34-47)
  • Error handling: Invalid digest length rejection (lines 49-60)
  • Signature verification: Recovery and ECDSA verification using digests (lines 94-127)
  • Deterministic behavior: Same digest produces same signature (lines 157-176)
  • Thread safety: Concurrent signing correctness (lines 178-207)
  • EVM compatibility: V byte validation and ecrecover recovery (lines 281-350)

All tests correctly use SHA-256 digests and validate the 65-byte signature format.

@holdex
Copy link

holdex bot commented Oct 11, 2025

Time Submission Status

Member Status Time Action Last Update
outerlook ✅ Submitted 7h Update time Oct 13, 2025, 10:30 PM
MicBun ✅ Submitted 20min Update time Oct 13, 2025, 11:12 PM

…sion

- Updated the buildCanonical function to use Little Endian for length-prefixed data, improving consistency in payload generation.
- Simplified the computeAttestationHash function to directly utilize the raw payload when available, enhancing performance.
- Refactored the writeLengthPrefixed function for better clarity and reusability across the codebase.

These changes enhance the efficiency and maintainability of the tn_attestation extension, ensuring more reliable payload processing and hashing.
- Refactored the TestComputeAttestationHash function to improve clarity and structure.
- Added tests to verify behavior when the raw payload is present and when it is missing, ensuring accurate hash computation.
- Utilized a canonical payload builder for consistent test data generation.

These changes improve test coverage and reliability for the attestation hash computation in the tn_attestation extension.
Copy link

@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 (2)
extensions/tn_attestation/processor_test.go (2)

185-239: Refactor duplicated argument decoding into helper functions.

The four argument decoding blocks (hash, requester, created_height, signature) follow nearly identical patterns with type switching and nil checks, violating DRY principles. This makes the test harder to maintain and read.

Consider extracting into generic helpers:

func decodeBytesArg(t *testing.T, arg *ktypes.EncodedValue, fieldName string) []byte {
	val, err := arg.Decode()
	require.NoError(t, err)
	switch typed := val.(type) {
	case []byte:
		return typed
	case *[]byte:
		require.NotNil(t, typed, "%s argument pointer was nil", fieldName)
		return *typed
	default:
		t.Fatalf("unexpected %s argument type %T", fieldName, val)
		return nil
	}
}

func decodeInt64Arg(t *testing.T, arg *ktypes.EncodedValue, fieldName string) int64 {
	val, err := arg.Decode()
	require.NoError(t, err)
	switch typed := val.(type) {
	case int64:
		return typed
	case *int64:
		require.NotNil(t, typed, "%s argument pointer was nil", fieldName)
		return *typed
	default:
		t.Fatalf("unexpected %s argument type %T", fieldName, val)
		return 0
	}
}

Then simplify the test to:

-	hashVal, err := decoded.Arguments[0][0].Decode()
-	require.NoError(t, err)
-	var hashBytes []byte
-	switch typed := hashVal.(type) {
-	case []byte:
-		hashBytes = typed
-	case *[]byte:
-		require.NotNil(t, typed, "hash argument pointer was nil")
-		hashBytes = *typed
-	default:
-		t.Fatalf("unexpected hash argument type %T", hashVal)
-	}
+	hashBytes := decodeBytesArg(t, decoded.Arguments[0][0], "hash")
	assert.Equal(t, hash[:], hashBytes)

-	requesterVal, err := decoded.Arguments[0][1].Decode()
-	require.NoError(t, err)
-	var requesterBytes []byte
-	switch typed := requesterVal.(type) {
-	case []byte:
-		requesterBytes = typed
-	case *[]byte:
-		require.NotNil(t, typed, "requester argument pointer was nil")
-		requesterBytes = *typed
-	default:
-		t.Fatalf("unexpected requester argument type %T", requesterVal)
-	}
+	requesterBytes := decodeBytesArg(t, decoded.Arguments[0][1], "requester")
	assert.Equal(t, []byte("requester"), requesterBytes)

-	heightVal, err := decoded.Arguments[0][2].Decode()
-	require.NoError(t, err)
-	var createdHeight int64
-	switch typed := heightVal.(type) {
-	case int64:
-		createdHeight = typed
-	case *int64:
-		require.NotNil(t, typed, "created_height argument pointer was nil")
-		createdHeight = *typed
-	default:
-		t.Fatalf("unexpected created_height argument type %T", heightVal)
-	}
+	createdHeight := decodeInt64Arg(t, decoded.Arguments[0][2], "created_height")
	assert.Equal(t, int64(123), createdHeight)

-	sigVal, err := decoded.Arguments[0][3].Decode()
-	require.NoError(t, err)
-	var signatureBytes []byte
-	switch typed := sigVal.(type) {
-	case []byte:
-		signatureBytes = typed
-	case *[]byte:
-		require.NotNil(t, typed, "signature argument pointer was nil")
-		signatureBytes = *typed
-	default:
-		t.Fatalf("unexpected signature argument type %T", sigVal)
-	}
+	signatureBytes := decodeBytesArg(t, decoded.Arguments[0][3], "signature")
	assert.Len(t, signatureBytes, 65)

288-306: Consider more robust SQL detection.

The string matching on line 290 (strings.Contains(statement, "GROUP BY attestation_hash")) is brittle and could break if the production SQL is reformatted or refactored. While acceptable for test code, a more resilient approach would be to match on statement structure or use a query identifier parameter.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7118ae7 and 961c465.

📒 Files selected for processing (4)
  • extensions/tn_attestation/canonical_test.go (1 hunks)
  • extensions/tn_attestation/processor.go (1 hunks)
  • extensions/tn_attestation/processor_test.go (1 hunks)
  • extensions/tn_attestation/worker.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • extensions/tn_attestation/processor.go
  • extensions/tn_attestation/canonical_test.go
🧰 Additional context used
🧬 Code graph analysis (2)
extensions/tn_attestation/worker.go (2)
extensions/tn_attestation/processor.go (1)
  • PreparedSignature (25-32)
deployments/infra/config/node/values.go (1)
  • GenesisConfig (13-16)
extensions/tn_attestation/processor_test.go (2)
extensions/tn_attestation/canonical.go (1)
  • ParseCanonicalPayload (40-82)
extensions/tn_attestation/signer.go (2)
  • ResetValidatorSignerForTesting (128-131)
  • InitializeValidatorSigner (112-118)
⏰ 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). (2)
  • GitHub Check: acceptance-test
  • GitHub Check: lint
🔇 Additional comments (7)
extensions/tn_attestation/processor_test.go (3)

23-51: LGTM! Previous issue resolved.

The test now correctly hashes the canonical bytes produced by buildCanonical (which includes block height, result, and length prefixes), addressing the prior review concern about truncated payloads. Both test cases (hashing with raw present and re-encoding when raw is missing) provide good coverage.


53-106: LGTM!

The test setup is comprehensive, with proper cleanup registration, realistic canonical payload construction, and thorough assertions covering all PreparedSignature fields.


242-269: LGTM!

The test provides good coverage of batch-limited and unlimited fetch scenarios with clear, minimal setup.

extensions/tn_attestation/worker.go (4)

32-69: LGTM: Clean batch processing with error isolation.

The per-item error handling ensures that a single malformed record doesn't block the entire batch, which is essential for making steady progress. The test override hook is a thoughtful addition for unit testing.


71-174: LGTM: Thorough validation and correct nonce handling.

The nonce logic correctly handles the first-ever signature case where the account doesn't exist yet (lines 112-124), and the comprehensive component validation prevents runtime panics. Broadcasting with mode 0 appropriately avoids blocking consensus, and the async status check decouples confirmation from the signing workflow.


176-213: LGTM: Well-designed worker lifecycle with defensive patterns.

The sync.Once guard prevents goroutine leaks across leader transitions, and the non-blocking enqueue (lines 206-212) prevents EndBlock stalls if the queue is full. The defensive copy of the requester slice on line 204 is excellent practice to prevent aliasing bugs.


225-304: LGTM: Past review comment addressed, robust retry strategy.

The terminal warning on lines 299-303 correctly addresses the past review comment by logging when transaction status remains unresolved after all retry attempts. The exponential backoff strategy (~115s total) is appropriate for handling block production variance, and the context cancellation support prevents runaway polling.

- Simplified the TestSubmitSignature function by introducing helper functions to decode byte and int64 arguments, enhancing readability and reducing code duplication.
- Updated assertions to directly compare decoded values, ensuring clearer test expectations and error messages.
- Improved error handling in the decoding process to provide more informative feedback on unexpected argument types.

These changes enhance the maintainability and clarity of the tests in the tn_attestation extension, ensuring more reliable validation of signature submissions.
- Refactored the computeAttestationHash function to utilize a hasher for better performance and clarity.
- Introduced a new helper function, buildHashMaterial, to streamline the construction of hash input data.
- Updated tests in processor_test.go to reflect changes in hash computation logic, ensuring accurate validation of expected outputs.

These changes enhance the efficiency and maintainability of the tn_attestation extension, improving the reliability of attestation hash computations.
Copy link

@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 (2)
extensions/tn_attestation/processor.go (2)

57-62: Harden row value type handling to avoid panics

Un-guarded type assertions on requester and created_height can panic if driver types vary (e.g., TEXT string for requester). Use a type switch.

-            rec := attestationRecord{
-                hash:          bytesClone(row.Values[0].([]byte)),
-                requester:     bytesCloneOrNil(row.Values[1]),
-                canonical:     bytesClone(row.Values[2].([]byte)),
-                createdHeight: row.Values[3].(int64),
-            }
+            // hash
+            hb, ok := row.Values[0].([]byte)
+            if !ok {
+                return fmt.Errorf("unexpected attestation_hash type %T", row.Values[0])
+            }
+            // requester (nullable, may be []byte or string)
+            rb := bytesCloneOrNil(row.Values[1])
+            // canonical
+            cb, ok := row.Values[2].([]byte)
+            if !ok {
+                return fmt.Errorf("unexpected result_canonical type %T", row.Values[2])
+            }
+            // created_height
+            ch, ok := row.Values[3].(int64)
+            if !ok {
+                return fmt.Errorf("unexpected created_height type %T", row.Values[3])
+            }
+            rec := attestationRecord{
+                hash:          bytesClone(hb),
+                requester:     rb,
+                canonical:     bytesClone(cb),
+                createdHeight: ch,
+            }
@@
-func bytesCloneOrNil(v any) []byte {
-    if v == nil {
-        return nil
-    }
-    return bytes.Clone(v.([]byte))
-}
+func bytesCloneOrNil(v any) []byte {
+    if v == nil {
+        return nil
+    }
+    switch t := v.(type) {
+    case []byte:
+        return bytes.Clone(t)
+    case string:
+        return []byte(t)
+    default:
+        return nil
+    }
+}

Please confirm the actual driver types for requester/created_height so we can tighten further. Based on learnings.

Also applies to: 195-200


146-152: Optional: reduce race on multi-leader transitions with SKIP LOCKED

If there's any chance of concurrent leaders/processes, consider selecting hashes with row locking to avoid duplicate work.

  • Option: SELECT ... FROM attestations WHERE signature IS NULL GROUP BY attestation_hash ORDER BY MIN(created_height) ASC LIMIT $limit FOR UPDATE SKIP LOCKED (adjust per engine support).
  • Alternatively, insert hashes into a dedicated work queue table and claim with SKIP LOCKED.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c0a4f80 and 0ce7e98.

📒 Files selected for processing (2)
  • extensions/tn_attestation/processor.go (1 hunks)
  • extensions/tn_attestation/processor_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • extensions/tn_attestation/processor_test.go
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-10T13:00:14.189Z
Learnt from: outerlook
PR: trufnetwork/node#1207
File: internal/migrations/024-attestation-actions.sql:58-76
Timestamp: 2025-10-10T13:00:14.189Z
Learning: In the attestation system for internal/migrations/024-attestation-actions.sql, the attestation_hash is computed from (version|algo|data_provider|stream_id|action_id|args) and intentionally excludes created_height. This design ensures the hash is deterministic based only on user input, not network state like block height.

Applied to files:

  • extensions/tn_attestation/processor.go
🧬 Code graph analysis (1)
extensions/tn_attestation/processor.go (2)
extensions/tn_attestation/canonical.go (2)
  • CanonicalPayload (24-35)
  • ParseCanonicalPayload (40-82)
extensions/tn_attestation/signer.go (1)
  • GetValidatorSigner (122-124)
⏰ 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: acceptance-test

- Refactored the TestComputeAttestationHash function to eliminate unnecessary sub-tests, enhancing readability and reducing complexity.
- Consolidated assertions for hash computation when the raw payload is present and when it is missing, ensuring consistent validation of expected outputs.
- Updated error messages in the prepareSigningWork function to provide clearer context on attestation hash mismatches.

These changes improve the maintainability and clarity of the tests in the tn_attestation extension, ensuring more reliable validation of attestation hash computations.
Copy link

@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

♻️ Duplicate comments (1)
extensions/tn_attestation/processor_test.go (1)

25-48: Verify that the hash covers all canonical fields.

The test compares computeAttestationHash output against a hash built from a truncated subset (line 41 via buildHashMaterial at lines 227-240) that excludes BlockHeight, Result, and length prefixes. A past review comment flagged this exact issue, noting that production should hash the full canonical encoding for integrity.

If computeAttestationHash is meant to hash the complete canonical payload, update line 41 to hash payload.raw (or the full canonical bytes) instead of the truncated subset. If only a subset should be hashed, document why certain fields are excluded and ensure this matches the specification.

Run the following script to verify what computeAttestationHash actually hashes:

#!/bin/bash
# Description: Check the implementation of computeAttestationHash to determine which fields are hashed.

# Search for the function definition
ast-grep --pattern 'func computeAttestationHash($$$) $_ {
  $$$
}'
🧹 Nitpick comments (2)
extensions/tn_attestation/processor_test.go (2)

227-240: Document why buildHashMaterial omits height and result.

buildHashMaterial constructs a payload subset that excludes BlockHeight and Result. If this is intentional (e.g., for backward compatibility or a specific hashing spec), add a comment explaining why these fields are omitted. Otherwise, consider whether the hash should cover the full canonical encoding for integrity.


288-307: Reduce coupling to SQL implementation details.

Lines 297-300 check for "GROUP BY attestation_hash" in the SQL statement string as a fallback. This couples the test stub to the exact query format, making it fragile if the implementation changes the query structure (e.g., formatting, aliases, subqueries).

Consider either:

  1. Using distinct method signatures for different query types, or
  2. Configuring the stub with expected query types via a field rather than parsing SQL strings.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0ce7e98 and 4497ef2.

📒 Files selected for processing (3)
  • extensions/tn_attestation/processor.go (1 hunks)
  • extensions/tn_attestation/processor_test.go (1 hunks)
  • internal/migrations/024-attestation-actions.sql (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • extensions/tn_attestation/processor.go
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-10-10T13:00:14.189Z
Learnt from: outerlook
PR: trufnetwork/node#1207
File: internal/migrations/024-attestation-actions.sql:58-76
Timestamp: 2025-10-10T13:00:14.189Z
Learning: In the attestation system for internal/migrations/024-attestation-actions.sql, the attestation_hash is computed from (version|algo|data_provider|stream_id|action_id|args) and intentionally excludes created_height. This design ensures the hash is deterministic based only on user input, not network state like block height.

Applied to files:

  • internal/migrations/024-attestation-actions.sql
📚 Learning: 2025-10-10T13:00:13.731Z
Learnt from: outerlook
PR: trufnetwork/node#1207
File: internal/migrations/023-attestation-schema.sql:20-21
Timestamp: 2025-10-10T13:00:13.731Z
Learning: In the attestations table (internal/migrations/023-attestation-schema.sql), the primary key is (requester, created_height, attestation_hash) because attestation_hash is computed deterministically from user input only (version|algo|data_provider|stream_id|action_id|args) and does not include created_height. This allows the same user to request the same attestation at different block heights.

Applied to files:

  • internal/migrations/024-attestation-actions.sql
🧬 Code graph analysis (1)
extensions/tn_attestation/processor_test.go (2)
extensions/tn_attestation/canonical.go (1)
  • ParseCanonicalPayload (40-82)
extensions/tn_attestation/signer.go (2)
  • ResetValidatorSignerForTesting (128-131)
  • InitializeValidatorSigner (112-118)
⏰ 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: acceptance-test
🔇 Additional comments (6)
extensions/tn_attestation/processor_test.go (4)

50-103: LGTM!

The test correctly verifies the signing workflow: generates a key, initializes the signer, builds canonical payload, sets up stub engine with appropriate row data, and asserts all critical fields of the prepared signature (hash, payload, created height, 65-byte signature).


105-193: LGTM!

The test provides comprehensive end-to-end coverage of the signing and broadcasting flow. It properly verifies the transaction structure, namespace, action name, and all four arguments (hash, requester, created height, signature), ensuring the integration between signing and broadcasting works correctly.


242-269: LGTM!

The test correctly verifies batch retrieval of pending hashes with configurable batch sizes, including the fallback behavior when batch size is zero (returns all rows).


365-374: LGTM!

The recordingBroadcaster test double is simple and effective for capturing broadcast calls and inspecting transactions in tests.

internal/migrations/024-attestation-actions.sql (2)

89-93: Confirm queue input format and idempotency.

tn_attestation.queue_for_signing(encode(...,'hex')) passes lowercase hex without 0x. Please confirm the precompile expects this format and that re‑queuing the same hash is idempotent or safely de‑duplicated under drop‑on‑full semantics.


103-114: Leader gate: verify env semantics.

The check @leader_sender == @signer is strict. Confirm this holds with your broadcaster/relayer path; if a relayer signs the tx on behalf of the leader, this would reject. If intended, LGTM.

- Modified the sign_attestation action to include a condition that checks for a null signature, enhancing data integrity during attestation processing.
- This change ensures that only valid attestations with a signature are processed, improving the reliability of the attestation workflow in the tn_attestation extension.
Copy link

@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)
internal/migrations/024-attestation-actions.sql (1)

148-157: Handle rare race between pre-check and UPDATE (concurrent sign).

You guard with AND signature IS NULL (good), but if another leader signs between the SELECT and UPDATE, this UPDATE affects 0 rows and the action still “succeeds.” Add a post-UPDATE verification (or row-count check) to fail fast on races.

Apply this follow-up after the UPDATE:

 UPDATE attestations
    SET signature = $signature,
        validator_pubkey = @signer,
        signed_height = @height
  WHERE attestation_hash = $attestation_hash
    AND requester = $requester
    AND created_height = $created_height
    AND signature IS NULL;
+
+-- Verify the row was actually updated (detect concurrent sign)
+$ok BOOL := FALSE;
+FOR $r IN
+    SELECT signature FROM attestations
+     WHERE attestation_hash = $attestation_hash
+       AND requester = $requester
+       AND created_height = $created_height
+     LIMIT 1
+{
+    IF $r.signature IS NOT NULL { $ok := TRUE; }
+}
+IF NOT $ok {
+    ERROR('Concurrent sign detected; attestation already signed at height ' || @height::TEXT);
+}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4497ef2 and 9dcf571.

📒 Files selected for processing (1)
  • internal/migrations/024-attestation-actions.sql (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-10T13:00:14.189Z
Learnt from: outerlook
PR: trufnetwork/node#1207
File: internal/migrations/024-attestation-actions.sql:58-76
Timestamp: 2025-10-10T13:00:14.189Z
Learning: In the attestation system for internal/migrations/024-attestation-actions.sql, the attestation_hash is computed from (version|algo|data_provider|stream_id|action_id|args) and intentionally excludes created_height. This design ensures the hash is deterministic based only on user input, not network state like block height.

Applied to files:

  • internal/migrations/024-attestation-actions.sql
⏰ 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: acceptance-test
🔇 Additional comments (2)
internal/migrations/024-attestation-actions.sql (2)

68-79: Attestation hash excludes height/result — design-aligned and matches Go helpers.
Existing tests in tn_attestation/processor_test.go verify that buildHashMaterial and computeAttestationHash use the same fields and byte order.


89-93: queue_for_signing expects hex‐encoded string
The precompile’s attestation_hash parameter is declared as text, so using encode($attestation_hash, 'hex') is correct.

@outerlook outerlook requested a review from MicBun October 13, 2025 17:36
Copy link
Member

@MicBun MicBun left a comment

Choose a reason for hiding this comment

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

Lgtm

@MicBun MicBun merged commit 3f76e92 into main Oct 13, 2025
8 checks passed
@MicBun MicBun deleted the chore/sign-ext branch October 13, 2025 22:30
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.

Problem: Leader cannot sign attestations automatically Problem: Leader signatures not stored in consensus

2 participants