Conversation
WalkthroughAdds an on-chain order book: database schema (queries, participants, positions), vault helper actions for collateral management, public market actions (create, query, list, exists) including fee handling, tests for market creation flows, and a Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Engine as Market Engine
participant Vault as Collateral Vault
participant DB as Order Book DB
User->>Engine: create_market(hash, settle_time, max_spread, min_order_size)
Engine->>Vault: check balance for 2 TRUF
Vault-->>Engine: balance OK / insufficient
alt balance OK
Engine->>Vault: lock 2 TRUF fee
Vault-->>Engine: fee locked
Engine->>DB: insert into ob_queries (generate id)
DB-->>Engine: query_id
Engine-->>User: return query_id
else insufficient
Engine-->>User: error (insufficient funds)
end
sequenceDiagram
participant User
participant Engine as Market Engine
participant Participants as ob_participants
participant Positions as ob_positions
participant Vault as Collateral Vault
User->>Engine: ob_lock_collateral(amount)
Engine->>Vault: escrow via bridge
Vault-->>Engine: confirmed
User->>Engine: trade/order
Engine->>Participants: ob_get_or_create_participant()
Participants-->>Engine: participant_id
Engine->>Positions: insert/update position (query_id, participant_id, outcome, price, amount)
Positions-->>Engine: confirmed
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches🧪 Generate unit tests (beta)
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. Comment |
Time Submission Status
|
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (4)
internal/migrations/031-order-book-vault.sql (2)
48-49: Consider stricter address validation.The current validation checks length and
0xprefix but doesn't verify that the remaining 40 characters are valid hex. Whiledecode()will fail on invalid hex later, explicit validation here would provide clearer error messages.Apply this diff to add hex validation:
- if $user_address IS NULL OR length($user_address) != 42 OR substring(LOWER($user_address), 1, 2) != '0x' { - ERROR('Invalid user address format (expected 0x-prefixed hex, 42 chars)'); + if $user_address IS NULL OR length($user_address) != 42 OR substring(LOWER($user_address), 1, 2) != '0x' { + ERROR('Invalid user address format (expected 0x-prefixed hex, 42 chars)'); + } + + -- Validate hex characters (will throw if invalid) + BEGIN + $test_decode BYTEA := decode(substring(LOWER($user_address), 3, 40), 'hex'); + EXCEPTION WHEN OTHERS THEN + ERROR('Invalid hex characters in address'); + END; + + if false { -- Remove the old check + ERROR('Invalid user address format (expected 0x-prefixed hex, 42 chars)'); }Note: If Kuneiform doesn't support exception handling, the current approach is acceptable since
decode()will fail with an error anyway.
83-94: Consider using RETURNING clause for efficiency.The current implementation performs a separate SELECT after INSERT to retrieve the generated ID. If Kuneiform supports the RETURNING clause, it would be more efficient to combine these operations.
If RETURNING is supported, apply this diff:
- -- Create new participant with MAX(id) + 1 pattern - INSERT INTO ob_participants (id, wallet_address) - SELECT COALESCE(MAX(id), 0) + 1, $caller_bytes - FROM ob_participants; - - -- Get the ID we just inserted - for $row in SELECT id FROM ob_participants WHERE wallet_address = $caller_bytes { - RETURN $row.id; - } + -- Create new participant with MAX(id) + 1 pattern + for $row in + INSERT INTO ob_participants (id, wallet_address) + SELECT COALESCE(MAX(id), 0) + 1, $caller_bytes + FROM ob_participants + RETURNING id + { + RETURN $row.id; + }If RETURNING is not supported in Kuneiform, the current approach is acceptable but slightly less efficient.
internal/migrations/032-order-book-actions.sql (2)
116-120: Consider using RETURNING clause for efficiency.Similar to the participant creation in 031-order-book-vault.sql, this pattern performs a separate SELECT after INSERT. If Kuneiform supports RETURNING, it would be more efficient.
If supported, apply this diff:
- -- Get the ID we just inserted $query_id INT; - for $row in SELECT id FROM ob_queries WHERE hash = $query_hash { + for $row in + INSERT INTO ob_queries (...) + SELECT ... + FROM ob_queries + RETURNING id + { $query_id := $row.id; }
256-272: Consider reducing query duplication.The two query branches differ only by the WHERE clause. While the current implementation is clear, a more maintainable approach would reduce duplication.
If Kuneiform supports dynamic WHERE clauses, consider:
-- Build query dynamically $where_clause TEXT := ''; if $settled_filter IS NOT NULL { $where_clause := 'WHERE settled = ' || $settled_filter::TEXT; } -- Execute single query RETURN SELECT id, hash, settle_time, settled, winning_outcome, max_spread, min_order_size, created_at FROM ob_queries || $where_clause || ORDER BY created_at DESC LIMIT $effective_limit OFFSET $effective_offset;However, the current explicit approach is acceptable and may be clearer for maintenance.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
.gitignore(1 hunks)internal/migrations/030-order-book-schema.sql(1 hunks)internal/migrations/031-order-book-vault.sql(1 hunks)internal/migrations/032-order-book-actions.sql(1 hunks)tests/streams/order_book/market_creation_test.go(1 hunks)
⏰ 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 (16)
.gitignore (1)
34-35: LGTM!The addition of CLAUDE.md to the ignore list is appropriate for auxiliary documentation files.
internal/migrations/031-order-book-vault.sql (2)
24-34: LGTM!The amount validation is correct, and the ethereum_bridge placeholder is appropriately documented with a TODO for future USDC bridge migration.
101-115: LGTM!The read-only participant lookup is correctly implemented with appropriate NULL handling for both invalid addresses and missing records.
internal/migrations/030-order-book-schema.sql (2)
56-69: LGTM!The positions table design appropriately models both holdings (price=0) and orders (price≠0) with proper constraints and cascading deletes for referential integrity.
100-102: No issues identified—method_id=8 is unique and available.Verification confirms that method_id=8 is not already in use in any migration, and 'createMarket' is a new unique method name. The INSERT with ON CONFLICT (method_id) DO NOTHING is safe and idiomatic.
tests/streams/order_book/market_creation_test.go (7)
25-44: LGTM!Test constants correctly match the market creation fee, and the helper function provides clear error messages for invalid inputs.
47-61: LGTM!The test suite provides comprehensive coverage of market creation flows with good organization and proper test isolation.
64-118: LGTM!The happy path test comprehensively validates market creation, fee collection, and data integrity with appropriate assertions.
132-134: Note: BYTEA length validation limitation.The comment correctly identifies that Kuneiform's
length()function doesn't support BYTEA, which is why hash length validation must rely on table constraints or external validation. This aligns with the concerns raised in the schema review.
159-317: LGTM!The test suite provides excellent coverage of validation, duplicate handling, balance checks, and all retrieval operations with appropriate assertions and error handling.
351-388: LGTM!The helper function properly constructs the transaction context with all required fields including the leader key for fee transfers.
390-472: LGTM!The helper functions consistently wrap engine calls with proper context construction and error handling.
internal/migrations/032-order-book-actions.sql (4)
146-174: LGTM!The action correctly retrieves market information with proper validation and error handling.
188-216: LGTM!The hash-based lookup is correctly implemented with appropriate error handling.
285-296: LGTM!The existence check is efficiently implemented with appropriate NULL handling and LIMIT optimization.
77-85: The @leader_sender NULL check is intentional and correct.Verification shows this is not a defensive precaution but a consistent architectural pattern across the entire codebase. Every PUBLIC state-modifying action that requires fees (in migrations 001, 003, 004, 024, 032, erc20 001, erc20 002, etc.) checks if
@leader_sender IS NULLbefore use. By contrast, PRIVATE actions do not check—they assume@leader_senderexists. The tests confirm@leader_senderis populated fromBlockContext.Proposerduring normal block execution, but the codebase design intentionally allows it to be NULL in certain contexts. The current validation (check + ERROR) is the appropriate pattern: if the action cannot execute without a leader address for fee transfer, failing fast is correct behavior.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (3)
internal/migrations/030-order-book-schema.sql (2)
20-34: Hash length constraint addressed in actions layer.The past review comment about adding a hash length constraint has been addressed in
032-order-book-actions.sql(lines 45-47) which validateslength(encode($query_hash, 'hex')) != 64before insertion. This is the appropriate approach given Kuneiform's limitations with BYTEA length constraints.
44-47: Wallet address length constraint should be validated at insertion.Past review noted that Ethereum addresses are 20 bytes, but the column doesn't enforce this. Verify that
ob_get_or_create_participant(in 031-order-book-vault.sql) validates wallet address length before insertion, similar to how hash length is validated increate_market.#!/bin/bash # Check if wallet address length validation exists in vault actions rg -n "wallet" internal/migrations/031-order-book-vault.sql | head -30 rg -n "length|octet" internal/migrations/031-order-book-vault.sqlinternal/migrations/032-order-book-actions.sql (1)
39-47: Hash length validation now implemented.The past review comment about adding hash length validation has been addressed. Using
length(encode($query_hash, 'hex')) != 64is a creative workaround for Kuneiform's lack ofoctet_length()support on BYTEA.
🧹 Nitpick comments (5)
tests/streams/order_book/market_creation_test.go (3)
33-36: GlobalpointCountermay cause race conditions in parallel test execution.While tests in this suite run sequentially within the schema test framework, the global
pointCountervariable could cause data races if other test files in the package access it concurrently. Consider making this a local counter passed through test setup, or protect with a mutex if parallelism is expected.
225-253: Commented-out fields intestGetMarketInforeduce clarity.Lines 233-235 contain commented-out field assignments. If these fields are intentionally unused, consider removing the comments or documenting why they're skipped.
err = callGetMarketInfo(ctx, platform, &userAddr, int(queryID), func(row *common.Row) error { hash = row.Values[0].([]byte) storedSettleTime = row.Values[1].(int64) - // settled = row.Values[2].(bool) - // winningOutcome = row.Values[3] - // settledAt = row.Values[4] + // Indices 2-4 (settled, winningOutcome, settledAt) intentionally skipped for this test maxSpread = row.Values[5].(int64) minOrderSize = row.Values[6].(int64) return nil })
354-391: Generated key incallCreateMarketis unused beyondProposer.A new key is generated for each call but only the public key is used for
BlockContext.Proposer. This works but may cause confusion about its purpose.Consider documenting the purpose or reusing a constant test key if the specific key doesn't matter:
func callCreateMarket(ctx context.Context, platform *kwilTesting.Platform, signer *util.EthereumAddress, queryHash []byte, settleTime int64, maxSpread int64, minOrderSize int64, resultFn func(*common.Row) error) error { - // Generate leader key for fee transfers + // Generate a block proposer key for the test context (key value doesn't affect test outcomes) _, pubGeneric, err := crypto.GenerateSecp256k1Key(nil)internal/migrations/032-order-book-actions.sql (2)
120-124: ID retrieval after INSERT could be simplified.The pattern of inserting then querying by hash to get the ID works but adds an extra query. If Kuneiform supports
RETURNING, that would be more efficient. Otherwise, this approach is acceptable given the sequential transaction processing.
235-277: Consider consolidating duplicate SQL inlist_markets.The two branches (lines 263-267 and 270-275) duplicate most of the SQL. If Kuneiform supports conditional WHERE clauses or dynamic SQL, this could be simplified. However, the current approach is explicit and may be necessary given language constraints.
If Kuneiform supports
WHERE ($settled_filter IS NULL OR settled = $settled_filter):RETURN SELECT id, hash, settle_time, settled, winning_outcome, max_spread, min_order_size, created_at FROM ob_queries WHERE $settled_filter IS NULL OR settled = $settled_filter ORDER BY created_at DESC LIMIT $effective_limit OFFSET $effective_offset;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
internal/migrations/030-order-book-schema.sql(1 hunks)internal/migrations/032-order-book-actions.sql(1 hunks)tests/streams/order_book/market_creation_test.go(1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-10-10T13:00:14.189Z
Learnt from: outerlook
Repo: trufnetwork/node PR: 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/030-order-book-schema.sql
📚 Learning: 2025-10-10T13:00:13.731Z
Learnt from: outerlook
Repo: trufnetwork/node PR: 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/030-order-book-schema.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 (10)
tests/streams/order_book/market_creation_test.go (4)
46-61: Well-structured test suite with comprehensive coverage.The test suite covers key scenarios: happy path, input validation, duplicate rejection, insufficient balance, and CRUD operations. The use of
SchemaTestwith seed migrations ensures proper database state.
63-118: Solid happy path test with proper balance verification.The test correctly validates:
- Market creation returns a positive query ID
- Balance decreases by the expected 2 TRUF fee
- Market metadata is retrievable and matches input
120-159: Good validation test coverage.The test covers multiple validation scenarios: invalid hash length, past settlement time, max_spread boundaries, and min_order_size validation. Error message assertions help ensure meaningful error feedback.
393-419: Consistent helper function patterns.The
callGetMarketInfo,callListMarkets, andcallMarketExistshelpers follow a clean, consistent pattern for setting up transaction context and handling results. Good use of the common error handling pattern.Also applies to: 421-447, 449-475
internal/migrations/030-order-book-schema.sql (3)
56-69: Well-designed unified positions table.The design combining orders and holdings with price semantics (0=holding, positive=sell, negative=buy) is elegant. The CHECK constraints appropriately bound price (-99 to 99) and require positive amounts.
77-91: Appropriate indexes for query patterns.The indexes align well with expected query patterns:
idx_ob_pos_order_matchfor order book matching (FIFO via last_updated)idx_ob_positions_participantfor portfolio queriesidx_ob_queries_settle_timefor settlement processingidx_ob_queries_settledfor filtering active/settled marketsNote: Past comment about redundant
idx_ob_participants_walletappears to have been addressed as it's not present in this version.
97-100: Transaction method registration looks correct.Using
ON CONFLICT DO NOTHINGensures idempotency for migration reruns.internal/migrations/032-order-book-actions.sql (3)
150-178:get_market_infoaction is clean and correct.Proper validation, clear error messages, and efficient single-row lookup by primary key.
289-300: Lightweightmarket_existsis well-implemented.Efficient pattern using
SELECT 1 ... LIMIT 1and returning boolean. Returnsfalsefor NULL input rather than erroring, which is a sensible default for an existence check.
70-89: Balance validation already provides explicit error handling; clarify bridge error assumptions.The code at lines 74-79 includes explicit balance validation with
ERROR()thrown if insufficient funds. This satisfies the request for explicit error handling on the primary failure mode. However, the comment's assumption thatethereum_bridge.transfer()throwsERRORon all failure types is undocumented in this repository. Since Kwil'sERROR()function halts and rolls back the transaction, the balance check does prevent market creation without fee collection for insufficient balance. For other potential bridge failures beyond balance checks, either verify that the bridge extension reliably throws errors, or add inline documentation confirming this is the intended error semantics from the external bridge extension.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (3)
internal/migrations/032-order-book-actions.sql (3)
88-88: Verify @leader_sender type and encode() compatibility.Line 88 encodes
@leader_senderas hex, which assumes it's BYTEA or a BYTEA-like type. Confirm that:
@leader_senderis always provided and in the expected format (BYTEA or compatible).- If
@leader_sendercan be NULL or TEXT, the encode() call will fail or produce unexpected results.Consider adding a format validation check similar to the
@callervalidation on lines 95–100.
129-131: Optimize ID retrieval after INSERT.After inserting a market record, the code queries back to retrieve the generated ID. This requires an additional SELECT query. Since the ID is computed as
COALESCE(MAX(id), 0) + 1, you already know the value; consider returning it directly or using a returning clause (if Kuneiform supports it) to avoid the second query.-- Current pattern: INSERT INTO ob_queries (...) SELECT COALESCE(MAX(id), 0) + 1, ... FROM ob_queries; for $row in SELECT id FROM ob_queries WHERE hash = $query_hash { ... } -- Suggested: compute and return the ID upfront $query_id INT := (SELECT COALESCE(MAX(id), 0) + 1 FROM ob_queries); INSERT INTO ob_queries (id, ...) VALUES ($query_id, ...); RETURN $query_id;
136-141: Document the transaction event type constant.Line 137 passes a hardcoded action type
8torecord_transaction_event(). Clarify:
- What does action type 8 represent?
- Should this be a named constant defined in a shared location?
- Is there validation that this value is correct?
Add a comment above the call explaining the meaning, or extract it to a named constant.
-- Record market creation event (type: CREATE_MARKET or similar) $transaction_type INT := 8; -- Document the meaning of 8 record_transaction_event($transaction_type, $market_creation_fee, '0x' || $leader_hex, NULL);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
internal/migrations/032-order-book-actions.sql(1 hunks)
⏰ 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 (4)
internal/migrations/032-order-book-actions.sql (4)
157-185: LGTM: get_market_info.Clear input validation and straightforward query logic. Error handling is defensive.
199-227: LGTM: get_market_by_hash.Mirrors get_market_info with consistent validation and error handling. Useful for checking market existence before creation.
242-284: LGTM: list_markets.Sensible pagination defaults (limit 100, offset 0) and clear conditional logic for the settled filter. Query is efficient.
296-307: LGTM: market_exists.Lightweight existence check with clear semantics. Returning false for NULL input is reasonable defensive behavior.
resolves: https://github.com/truflation/website/issues/2915
Summary by CodeRabbit
New Features
Tests
Chores
✏️ Tip: You can customize this high-level summary in your review settings.