From e5ba5561e095a93f0b459f7e6e0ddd956d9d89e4 Mon Sep 17 00:00:00 2001 From: Michael Buntarman Date: Tue, 18 Nov 2025 21:39:52 +0700 Subject: [PATCH 1/4] chore: set up transaction input data tracking --- internal/migrations/000-initial-data.sql | 25 ++- internal/migrations/001-common-actions.sql | 69 ++++--- .../migrations/003-primitive-insertion.sql | 5 +- internal/migrations/004-composed-taxonomy.sql | 6 +- .../streams/transaction_events_ledger_test.go | 176 ++++++++++++++++++ 5 files changed, 248 insertions(+), 33 deletions(-) diff --git a/internal/migrations/000-initial-data.sql b/internal/migrations/000-initial-data.sql index 3195e107f..b94d4b789 100644 --- a/internal/migrations/000-initial-data.sql +++ b/internal/migrations/000-initial-data.sql @@ -152,4 +152,27 @@ CREATE INDEX IF NOT EXISTS meta_key_ref_idx ON metadata (metadata_key, value_ref -- (data_provider, stream_id, metadata_key) -- WHERE disabled_at IS NULL; -- for now, we just index disabled_at -CREATE INDEX IF NOT EXISTS meta_disabled_idx ON metadata (disabled_at); \ No newline at end of file +CREATE INDEX IF NOT EXISTS meta_disabled_idx ON metadata (disabled_at); + +/* ============================================================================ + * Transaction Tracking Columns + * ============================================================================ + * Add tx_id columns to data tables for transaction effect tracking. + * This enables querying what data was created/modified by specific transactions. + */ + +-- Add tx_id to streams table (for deployStream tracking) +ALTER TABLE streams ADD COLUMN IF NOT EXISTS tx_id TEXT; +CREATE INDEX IF NOT EXISTS streams_tx_id_idx ON streams (tx_id); + +-- Add tx_id to primitive_events table (for insertRecords tracking) +ALTER TABLE primitive_events ADD COLUMN IF NOT EXISTS tx_id TEXT; +CREATE INDEX IF NOT EXISTS pe_tx_id_idx ON primitive_events (tx_id); + +-- Add tx_id to taxonomies table (for setTaxonomies tracking) +ALTER TABLE taxonomies ADD COLUMN IF NOT EXISTS tx_id TEXT; +CREATE INDEX IF NOT EXISTS tax_tx_id_idx ON taxonomies (tx_id); + +-- Add tx_id to metadata table (for setMetadata tracking) +ALTER TABLE metadata ADD COLUMN IF NOT EXISTS tx_id TEXT; +CREATE INDEX IF NOT EXISTS meta_tx_id_idx ON metadata (tx_id); \ No newline at end of file diff --git a/internal/migrations/001-common-actions.sql b/internal/migrations/001-common-actions.sql index ddc700a19..91ac1e5c6 100644 --- a/internal/migrations/001-common-actions.sql +++ b/internal/migrations/001-common-actions.sql @@ -146,14 +146,15 @@ CREATE OR REPLACE ACTION create_streams( } -- Create the streams using UNNEST for optimal performance - INSERT INTO streams (id, data_provider_id, data_provider, stream_id, stream_type, created_at) + INSERT INTO streams (id, data_provider_id, data_provider, stream_id, stream_type, created_at, tx_id) SELECT ROW_NUMBER() OVER (ORDER BY t.stream_id) + COALESCE((SELECT MAX(id) FROM streams), 0) AS id, $data_provider_id, $data_provider, t.stream_id, t.stream_type, - @height + @height, + @txid FROM UNNEST($stream_ids, $stream_types) AS t(stream_id, stream_type); -- Create metadata for the streams using UNNEST for optimal performance @@ -168,7 +169,8 @@ CREATE OR REPLACE ACTION create_streams( value_ref, created_at, disabled_at, - stream_ref + stream_ref, + tx_id ) SELECT uuid_generate_v5($base_uuid, 'metadata' || $data_provider || t.stream_id || 'stream_owner' || '1')::UUID, @@ -180,7 +182,8 @@ CREATE OR REPLACE ACTION create_streams( LOWER($data_provider)::TEXT, @height, NULL::INT, - s.id + s.id, + @txid FROM UNNEST($stream_ids, $stream_types) AS t(stream_id, stream_type) JOIN data_providers dp ON dp.address = $data_provider JOIN streams s ON s.data_provider_id = dp.id AND s.stream_id = t.stream_id; @@ -196,7 +199,8 @@ CREATE OR REPLACE ACTION create_streams( value_ref, created_at, disabled_at, - stream_ref + stream_ref, + tx_id ) SELECT uuid_generate_v5($base_uuid, 'metadata' || $data_provider || t.stream_id || 'read_visibility' || '2')::UUID, @@ -208,7 +212,8 @@ CREATE OR REPLACE ACTION create_streams( NULL::TEXT, @height, NULL::INT, - s.id + s.id, + @txid FROM UNNEST($stream_ids, $stream_types) AS t(stream_id, stream_type) JOIN data_providers dp ON dp.address = $data_provider JOIN streams s ON s.data_provider_id = dp.id AND s.stream_id = t.stream_id; @@ -224,7 +229,8 @@ CREATE OR REPLACE ACTION create_streams( value_ref, created_at, disabled_at, - stream_ref + stream_ref, + tx_id ) SELECT uuid_generate_v5($base_uuid, 'metadata' || $data_provider || t.stream_id || 'readonly_key' || '3')::UUID, @@ -236,7 +242,8 @@ CREATE OR REPLACE ACTION create_streams( NULL::TEXT, @height, NULL::INT, - s.id + s.id, + @txid FROM UNNEST($stream_ids, $stream_types) AS t(stream_id, stream_type) JOIN data_providers dp ON dp.address = $data_provider JOIN streams s ON s.data_provider_id = dp.id AND s.stream_id = t.stream_id; @@ -252,7 +259,8 @@ CREATE OR REPLACE ACTION create_streams( value_ref, created_at, disabled_at, - stream_ref + stream_ref, + tx_id ) SELECT uuid_generate_v5($base_uuid, 'metadata' || $data_provider || t.stream_id || 'readonly_key' || '4')::UUID, @@ -264,7 +272,8 @@ CREATE OR REPLACE ACTION create_streams( NULL::TEXT, @height, NULL::INT, - s.id + s.id, + @txid FROM UNNEST($stream_ids, $stream_types) AS t(stream_id, stream_type) JOIN data_providers dp ON dp.address = $data_provider JOIN streams s ON s.data_provider_id = dp.id AND s.stream_id = t.stream_id; @@ -280,7 +289,8 @@ CREATE OR REPLACE ACTION create_streams( value_ref, created_at, disabled_at, - stream_ref + stream_ref, + tx_id ) SELECT uuid_generate_v5($base_uuid, 'metadata' || $data_provider || t.stream_id || 'type' || '5')::UUID, @@ -292,7 +302,8 @@ CREATE OR REPLACE ACTION create_streams( NULL::TEXT, @height, NULL::INT, - s.id + s.id, + @txid FROM UNNEST($stream_ids, $stream_types) AS t(stream_id, stream_type) JOIN data_providers dp ON dp.address = $data_provider JOIN streams s ON s.data_provider_id = dp.id AND s.stream_id = t.stream_id; @@ -371,25 +382,27 @@ CREATE OR REPLACE ACTION insert_metadata( -- Insert the metadata INSERT INTO metadata ( - row_id, - metadata_key, - value_i, - value_f, - value_s, - value_b, - value_ref, + row_id, + metadata_key, + value_i, + value_f, + value_s, + value_b, + value_ref, created_at, - stream_ref + stream_ref, + tx_id ) VALUES ( - $uuid, - $key, - $value_i, - $value_f, - $value_s, - $value_b, - LOWER($value_ref), + $uuid, + $key, + $value_i, + $value_f, + $value_s, + $value_b, + LOWER($value_ref), $current_block, - $stream_ref + $stream_ref, + @txid ); record_transaction_event( diff --git a/internal/migrations/003-primitive-insertion.sql b/internal/migrations/003-primitive-insertion.sql index a91f411b2..7496a1f3c 100644 --- a/internal/migrations/003-primitive-insertion.sql +++ b/internal/migrations/003-primitive-insertion.sql @@ -94,13 +94,14 @@ CREATE OR REPLACE ACTION insert_records( } -- Insert all records using UNNEST to expand arrays efficiently - INSERT INTO primitive_events (event_time, value, created_at, truflation_created_at, stream_ref) + INSERT INTO primitive_events (event_time, value, created_at, truflation_created_at, stream_ref, tx_id) SELECT unnested.event_time, unnested.value, $current_block, NULL, - unnested.stream_ref + unnested.stream_ref, + @txid FROM UNNEST($event_time, $value, $stream_refs) AS unnested(event_time, value, stream_ref) WHERE unnested.value != 0::NUMERIC(36,18) ORDER BY unnested.stream_ref, unnested.event_time, $current_block; -- matches (stream_ref, event_time, created_at) diff --git a/internal/migrations/004-composed-taxonomy.sql b/internal/migrations/004-composed-taxonomy.sql index 2306c7669..4b413d57a 100644 --- a/internal/migrations/004-composed-taxonomy.sql +++ b/internal/migrations/004-composed-taxonomy.sql @@ -110,7 +110,8 @@ CREATE OR REPLACE ACTION insert_taxonomy( group_sequence, start_time, stream_ref, - child_stream_ref + child_stream_ref, + tx_id ) VALUES ( $taxonomy_id, $weight_value, @@ -119,7 +120,8 @@ CREATE OR REPLACE ACTION insert_taxonomy( $new_group_sequence, -- Use the new group_sequence for all child records. $start_date, -- Start date of the taxonomy. $stream_ref, - $child_stream_ref + $child_stream_ref, + @txid ); } diff --git a/tests/streams/transaction_events_ledger_test.go b/tests/streams/transaction_events_ledger_test.go index 0ab50400c..eeb42abbb 100644 --- a/tests/streams/transaction_events_ledger_test.go +++ b/tests/streams/transaction_events_ledger_test.go @@ -52,6 +52,16 @@ func TestTransactionEventsLedger(t *testing.T) { }, testutils.GetTestOptionsWithCache()) } +func TestTransactionIDTracking(t *testing.T) { + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ + Name: "LEDGER_TXID01_TransactionIDTracking", + SeedStatements: migrations.GetSeedScriptStatements(), + FunctionTests: []kwilTesting.TestFunc{ + runTransactionIDTrackingScenario(t), + }, + }, testutils.GetTestOptionsWithCache()) +} + func runTransactionEventsLedgerScenario(t *testing.T) func(ctx context.Context, platform *kwilTesting.Platform) error { return func(ctx context.Context, platform *kwilTesting.Platform) error { systemAdmin := util.Unsafe_NewEthereumAddressFromString("0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf") @@ -725,3 +735,169 @@ func ledgerGiveBalance(ctx context.Context, platform *kwilTesting.Platform, wall nil, ) } + +// runTransactionIDTrackingScenario tests that tx_id is properly tracked in data tables +func runTransactionIDTrackingScenario(t *testing.T) func(ctx context.Context, platform *kwilTesting.Platform) error { + return func(ctx context.Context, platform *kwilTesting.Platform) error { + systemAdmin := util.Unsafe_NewEthereumAddressFromString("0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf") + platform.Deployer = systemAdmin.Bytes() + + require.NoError(t, setup.AddMemberToRoleBypass(ctx, platform, "system", "network_writers_manager", systemAdmin.Address())) + require.NoError(t, setup.CreateDataProvider(ctx, platform, systemAdmin.Address())) + + // Initialize ERC20 bridge + err := erc20shim.ForTestingSeedAndActivateInstance(ctx, platform, ledgerChain, ledgerEscrow, ledgerERC20, 18, 60, ledgerExtensionAlias) + if err != nil { + if !strings.Contains(err.Error(), "alias \"sepolia_bridge\" already exists") { + require.NoError(t, err) + } + require.NoError(t, erc20shim.ForTestingInitializeExtension(ctx, platform)) + } + require.NoError(t, erc20shim.ForTestingInitializeExtension(ctx, platform)) + + actor := util.Unsafe_NewEthereumAddressFromString("0x9999999999999999999999999999999999999999") + require.NoError(t, setup.CreateDataProviderWithoutRole(ctx, platform, actor.Address())) + require.NoError(t, ledgerGiveBalance(ctx, platform, actor.Address(), initialUserFunds)) + + userLower := strings.ToLower(actor.Address()) + height := int64(10) + + primitiveStream := util.GenerateStreamId("txid_test_primitive") + composedStream := util.GenerateStreamId("txid_test_composed") + + // Test 1: Create streams and verify tx_id + t.Log("Testing streams.tx_id tracking...") + createLeaderPub, _ := newLeader(t) + createTx, err := callActionWithLeader(ctx, platform, &actor, createLeaderPub, height, "create_streams", []any{ + []string{primitiveStream.String(), composedStream.String()}, + []string{"primitive", "composed"}, + }) + require.NoError(t, err) + height++ + + // Verify streams.tx_id is set + streamCheckRes, err := platform.DB.Execute(ctx, + `SELECT stream_id, tx_id FROM main.streams WHERE stream_id IN ($1, $2)`, + primitiveStream.String(), composedStream.String()) + require.NoError(t, err) + require.Len(t, streamCheckRes.Rows, 2, "expected 2 streams to be created") + + for _, row := range streamCheckRes.Rows { + streamID := row[0].(string) + txID := row[1].(string) + // createTx has 0x prefix, but db stores without it + expectedTxID := strings.TrimPrefix(createTx, "0x") + require.Equal(t, expectedTxID, txID, "stream %s should have tx_id = %s", streamID, expectedTxID) + t.Logf("✓ Stream %s has tx_id: %s", streamID, txID) + } + + // Verify metadata.tx_id is set (streams create metadata records) + metadataCheckRes, err := platform.DB.Execute(ctx, + `SELECT COUNT(*) FROM main.metadata WHERE tx_id = $1`, strings.TrimPrefix(createTx, "0x")) + require.NoError(t, err) + metadataCount := metadataCheckRes.Rows[0][0].(int64) + require.Greater(t, metadataCount, int64(0), "expected metadata records with tx_id") + t.Logf("✓ Found %d metadata records with tx_id: %s", metadataCount, createTx) + + // Test 2: Insert records and verify tx_id + t.Log("Testing primitive_events.tx_id tracking...") + insertLeaderPub, _ := newLeader(t) + insertValue, err := kwilTypes.ParseDecimalExplicit("42.5", 36, 18) + require.NoError(t, err) + insertTx, err := callActionWithLeader(ctx, platform, &actor, insertLeaderPub, height, "insert_records", []any{ + []string{userLower}, + []string{primitiveStream.String()}, + []int64{1000}, + []*kwilTypes.Decimal{insertValue}, + }) + require.NoError(t, err) + height++ + + // Verify primitive_events.tx_id is set + eventsCheckRes, err := platform.DB.Execute(ctx, + `SELECT event_time, value, tx_id FROM main.primitive_events WHERE tx_id = $1`, strings.TrimPrefix(insertTx, "0x")) + require.NoError(t, err) + require.Len(t, eventsCheckRes.Rows, 1, "expected 1 primitive event to be inserted") + + eventRow := eventsCheckRes.Rows[0] + eventTime := eventRow[0].(int64) + eventValue := eventRow[1].(*kwilTypes.Decimal) + eventTxID := eventRow[2].(string) + + require.Equal(t, int64(1000), eventTime) + require.NotNil(t, eventValue, "event value should not be nil") + require.Equal(t, strings.TrimPrefix(insertTx, "0x"), eventTxID) + t.Logf("✓ Primitive event (time=%d, value=%s) has tx_id: %s", eventTime, eventValue.String(), eventTxID) + + // Test 3: Insert taxonomy and verify tx_id + t.Log("Testing taxonomies.tx_id tracking...") + taxLeaderPub, _ := newLeader(t) + weight, err := kwilTypes.ParseDecimalExplicit("1.0", 36, 18) + require.NoError(t, err) + taxTx, err := callActionWithLeader(ctx, platform, &actor, taxLeaderPub, height, "insert_taxonomy", []any{ + userLower, + composedStream.String(), + []string{userLower}, + []string{primitiveStream.String()}, + []*kwilTypes.Decimal{weight}, + nil, + }) + require.NoError(t, err) + height++ + + // Verify taxonomies.tx_id is set + taxCheckRes, err := platform.DB.Execute(ctx, + `SELECT weight, tx_id FROM main.taxonomies WHERE tx_id = $1`, strings.TrimPrefix(taxTx, "0x")) + require.NoError(t, err) + require.Len(t, taxCheckRes.Rows, 1, "expected 1 taxonomy to be inserted") + + taxRow := taxCheckRes.Rows[0] + taxWeight := taxRow[0].(*kwilTypes.Decimal) + taxTxID := taxRow[1].(string) + + require.NotNil(t, taxWeight, "taxonomy weight should not be nil") + require.Equal(t, strings.TrimPrefix(taxTx, "0x"), taxTxID) + t.Logf("✓ Taxonomy (weight=%s) has tx_id: %s", taxWeight.String(), taxTxID) + + // Test 4: Insert metadata and verify tx_id + t.Log("Testing metadata.tx_id tracking via insert_metadata...") + metaLeaderPub, _ := newLeader(t) + metaTx, err := callActionWithLeader(ctx, platform, &actor, metaLeaderPub, height, "insert_metadata", []any{ + userLower, + primitiveStream.String(), + "test_key", + "test_value", + "string", + }) + require.NoError(t, err) + height++ + + // Verify metadata.tx_id is set for insert_metadata + metaInsertCheckRes, err := platform.DB.Execute(ctx, + `SELECT metadata_key, value_s, tx_id FROM main.metadata WHERE tx_id = $1 AND metadata_key = $2`, + strings.TrimPrefix(metaTx, "0x"), "test_key") + require.NoError(t, err) + require.Len(t, metaInsertCheckRes.Rows, 1, "expected 1 metadata record to be inserted") + + metaRow := metaInsertCheckRes.Rows[0] + metaKey := metaRow[0].(string) + metaValue := metaRow[1].(string) + metaTxIDResult := metaRow[2].(string) + + require.Equal(t, "test_key", metaKey) + require.Equal(t, "test_value", metaValue) + require.Equal(t, strings.TrimPrefix(metaTx, "0x"), metaTxIDResult) + t.Logf("✓ Metadata (key=%s, value=%s) has tx_id: %s", metaKey, metaValue, metaTxIDResult) + + // Summary + t.Log("========================================") + t.Log("✓ All tx_id tracking tests passed!") + t.Log(" - streams.tx_id: TRACKED") + t.Log(" - primitive_events.tx_id: TRACKED") + t.Log(" - taxonomies.tx_id: TRACKED") + t.Log(" - metadata.tx_id: TRACKED") + t.Log("========================================") + + return nil + } +} From e5fb95169d32381780c16a6a36e43b3d11f21db7 Mon Sep 17 00:00:00 2001 From: Michael Buntarman Date: Tue, 18 Nov 2025 22:44:58 +0700 Subject: [PATCH 2/4] make CI run --- tests/streams/transaction_events_ledger_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/streams/transaction_events_ledger_test.go b/tests/streams/transaction_events_ledger_test.go index eeb42abbb..6379fbd7b 100644 --- a/tests/streams/transaction_events_ledger_test.go +++ b/tests/streams/transaction_events_ledger_test.go @@ -201,7 +201,7 @@ func runTransactionEventsLedgerScenario(t *testing.T) func(ctx context.Context, }) require.NoError(t, err) - attLeaderPub, attLeaderAddr := newLeader(t) + attLeaderPub, _ := newLeader(t) attTx, err := callActionWithLeader(ctx, platform, actor, attLeaderPub, height, "request_attestation", []any{ strings.ToLower(systemAdmin.Address()), attestationStream.String(), @@ -265,10 +265,10 @@ func runTransactionEventsLedgerScenario(t *testing.T) func(ctx context.Context, }, attTx: { method: "requestAttestation", - fee: feeFortyTRUF, - feeRecipient: attLeaderAddr, + fee: "0", // Actor is exempt (has network_writer role), so fee should be 0 + feeRecipient: "", // No fee recipient when exempt feeDistributions: []string{ - buildDistribution(attLeaderAddr, feeFortyTRUF), + buildDistribution("", "0"), }, assertMetadata: assertNoMetadata, }, From ad0577fd4b1e19e509dfcb6018f7ceec4dc386e6 Mon Sep 17 00:00:00 2001 From: Michael Buntarman Date: Tue, 18 Nov 2025 23:56:11 +0700 Subject: [PATCH 3/4] make CI run --- .../attestation/attestation_max_fee_test.go | 4 +++- tests/streams/attestation/test_helpers.go | 21 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/tests/streams/attestation/attestation_max_fee_test.go b/tests/streams/attestation/attestation_max_fee_test.go index f1f95f8f1..727cff5e2 100644 --- a/tests/streams/attestation/attestation_max_fee_test.go +++ b/tests/streams/attestation/attestation_max_fee_test.go @@ -177,7 +177,9 @@ func testMaxFeeBelow40TRUF(t *testing.T, h *AttestationTestHelper, actionName st argsBytes, err := tn_utils.EncodeActionArgs([]any{int64(46)}) require.NoError(t, err, "encode action args") - engineCtx := h.NewEngineContext() + // Use non-exempt user to test max_fee validation (exempt users skip fee checks) + addrs := NewTestAddresses() + engineCtx := h.NewNonExemptContext(addrs.Requester1) maxFee := types.MustParseDecimalExplicit("30000000000000000000", 78, 0) // 30 TRUF in wei diff --git a/tests/streams/attestation/test_helpers.go b/tests/streams/attestation/test_helpers.go index 965b90b77..ef25d76d7 100644 --- a/tests/streams/attestation/test_helpers.go +++ b/tests/streams/attestation/test_helpers.go @@ -134,6 +134,27 @@ func (h *AttestationTestHelper) NewEngineContext() *common.EngineContext { } } +// NewNonExemptContext creates a context for a non-exempt user (without network_writer role) +// This is useful for testing fee validation with non-exempt users +func (h *AttestationTestHelper) NewNonExemptContext(userAddr *util.EthereumAddress) *common.EngineContext { + // Give user balance to pay fees + h.GiveBalance(userAddr.Address(), "1000000000000000000000") // 1000 TRUF + + return &common.EngineContext{ + TxContext: &common.TxContext{ + Ctx: h.ctx, + BlockContext: &common.BlockContext{ + Height: 1, + Proposer: h.leaderPub, // Required for @leader_sender in fee transfers + }, + Signer: userAddr.Bytes(), + Caller: userAddr.Address(), + TxID: h.platform.Txid(), + Authenticator: auth.EthPersonalSignAuth, // Required for balance operations + }, + } +} + // NewLeaderContext creates a context with leader authorization func (h *AttestationTestHelper) NewLeaderContext(privateKey kcrypto.PrivateKey, publicKey kcrypto.PublicKey) *common.EngineContext { nodeSigner := auth.GetNodeSigner(privateKey) From af1a4cc5f6206bdcd155da3afab30dbf4e759845 Mon Sep 17 00:00:00 2001 From: Michael Buntarman Date: Wed, 19 Nov 2025 00:45:31 +0700 Subject: [PATCH 4/4] make CI run --- .../attestation/attestation_request_test.go | 12 +++--- .../request_attestation_fee_test.go | 37 +++++-------------- 2 files changed, 15 insertions(+), 34 deletions(-) diff --git a/tests/streams/attestation/attestation_request_test.go b/tests/streams/attestation/attestation_request_test.go index fc0d6d505..87e238605 100644 --- a/tests/streams/attestation/attestation_request_test.go +++ b/tests/streams/attestation/attestation_request_test.go @@ -134,13 +134,13 @@ type attestationRow struct { } func runAttestationUnauthorizedBlocked(t *testing.T, ctx context.Context, platform *kwilTesting.Platform, helper *AttestationTestHelper, actionName string) { - // Create an unauthorized user that does NOT have network_writer role + // Create a non-exempt user that does NOT have network_writer role (must pay 40 TRUF fee) unauthorizedAddr := util.Unsafe_NewEthereumAddressFromString("0x0000000000000000000000000000000000009999") argsBytes, err := tn_utils.EncodeActionArgs([]any{int64(999)}) require.NoError(t, err, "encode action args") - // Create a context for the unauthorized user + // Create a context for the non-exempt user (no balance given, so can't pay fee) unauthorizedCtx := &common.EngineContext{ TxContext: &common.TxContext{ Ctx: ctx, @@ -155,7 +155,7 @@ func runAttestationUnauthorizedBlocked(t *testing.T, ctx context.Context, platfo }, } - // Try to request attestation as unauthorized user - should fail + // Try to request attestation as non-exempt user without balance - should fail with insufficient balance res, err := platform.Engine.Call(unauthorizedCtx, platform.DB, "", "request_attestation", []any{ TestDataProviderHex, TestStreamID, @@ -168,9 +168,9 @@ func runAttestationUnauthorizedBlocked(t *testing.T, ctx context.Context, platfo }) require.NoError(t, err, "call should not error at engine level") - require.NotNil(t, res.Error, "action should return error for unauthorized user") - require.Contains(t, res.Error.Error(), "does not have the required system:network_writer role", - "error should indicate missing network_writer role") + require.NotNil(t, res.Error, "action should return error for user without balance") + require.Contains(t, res.Error.Error(), "Insufficient balance for attestation", + "error should indicate insufficient balance (non-exempt users must pay 40 TRUF fee)") } func fetchAttestationRow(helper *AttestationTestHelper, hash []byte) attestationRow { diff --git a/tests/streams/attestation/request_attestation_fee_test.go b/tests/streams/attestation/request_attestation_fee_test.go index 5ee488712..d6951fc47 100644 --- a/tests/streams/attestation/request_attestation_fee_test.go +++ b/tests/streams/attestation/request_attestation_fee_test.go @@ -101,25 +101,22 @@ func setupAttestationTestEnvironment(t *testing.T) func(ctx context.Context, pla } } -// Test 1: Network writer member pays 40 TRUF fee per attestation request +// Test 1: Non-exempt user pays 40 TRUF fee per attestation request func testAttestationNetworkWriterPaysFee(t *testing.T) func(ctx context.Context, platform *kwilTesting.Platform) error { return func(ctx context.Context, platform *kwilTesting.Platform) error { requesterAddrVal := util.Unsafe_NewEthereumAddressFromString("0xa111111111111111111111111111111111111111") requesterAddr := &requesterAddrVal - // Register as data provider with network_writer role - err := setup.CreateDataProvider(ctx, platform, requesterAddr.Address()) - require.NoError(t, err, "failed to register data provider") - - // Give requester 100 TRUF - err = giveAttestationBalance(ctx, platform, requesterAddr.Address(), "100000000000000000000") + // Note: NOT creating data provider - this user is non-exempt and must pay fees + // Give requester 100 TRUF (users with network_writer role are exempt, others must pay) + err := giveAttestationBalance(ctx, platform, requesterAddr.Address(), "100000000000000000000") require.NoError(t, err, "failed to give balance") // Get initial balance initialBalance, err := getAttestationBalance(ctx, platform, requesterAddr.Address()) require.NoError(t, err, "failed to get initial balance") - // Request attestation (should pay 40 TRUF) + // Request attestation (should pay 40 TRUF as non-exempt user) systemAdmin := util.Unsafe_NewEthereumAddressFromString("0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf") streamID := "st000000000000000000000000000000" err = requestAttestation(ctx, platform, requesterAddr, systemAdmin.Address(), streamID, "get_record") @@ -143,12 +140,8 @@ func testAttestationInsufficientBalance(t *testing.T) func(ctx context.Context, requesterAddrVal := util.Unsafe_NewEthereumAddressFromString("0xa222222222222222222222222222222222222222") requesterAddr := &requesterAddrVal - // Register as data provider with network_writer role - err := setup.CreateDataProvider(ctx, platform, requesterAddr.Address()) - require.NoError(t, err, "failed to register data provider") - // Give requester only 10 TRUF (insufficient for 40 TRUF fee) - err = giveAttestationBalance(ctx, platform, requesterAddr.Address(), "10000000000000000000") + err := giveAttestationBalance(ctx, platform, requesterAddr.Address(), "10000000000000000000") require.NoError(t, err, "failed to give balance") // Try to request attestation (should fail) @@ -169,12 +162,8 @@ func testAttestationMultipleRequestsChargeFees(t *testing.T) func(ctx context.Co requesterAddrVal := util.Unsafe_NewEthereumAddressFromString("0xa333333333333333333333333333333333333333") requesterAddr := &requesterAddrVal - // Register as data provider with network_writer role - err := setup.CreateDataProvider(ctx, platform, requesterAddr.Address()) - require.NoError(t, err, "failed to register data provider") - // Give requester 200 TRUF (enough for 5 attestations) - err = giveAttestationBalance(ctx, platform, requesterAddr.Address(), "200000000000000000000") + err := giveAttestationBalance(ctx, platform, requesterAddr.Address(), "200000000000000000000") require.NoError(t, err, "failed to give balance") // Get initial balance @@ -211,12 +200,8 @@ func testAttestationLeaderReceivesFees(t *testing.T) func(ctx context.Context, p requesterAddrVal := util.Unsafe_NewEthereumAddressFromString("0xa444444444444444444444444444444444444444") requesterAddr := &requesterAddrVal - // Register as data provider with network_writer role - err := setup.CreateDataProvider(ctx, platform, requesterAddr.Address()) - require.NoError(t, err, "failed to register data provider") - // Give requester 100 TRUF - err = giveAttestationBalance(ctx, platform, requesterAddr.Address(), "100000000000000000000") + err := giveAttestationBalance(ctx, platform, requesterAddr.Address(), "100000000000000000000") require.NoError(t, err, "failed to give balance") // Generate leader keys @@ -260,12 +245,8 @@ func testAttestationBalanceCorrectlyDeducted(t *testing.T) func(ctx context.Cont requesterAddrVal := util.Unsafe_NewEthereumAddressFromString("0xa555555555555555555555555555555555555555") requesterAddr := &requesterAddrVal - // Register as data provider with network_writer role - err := setup.CreateDataProvider(ctx, platform, requesterAddr.Address()) - require.NoError(t, err, "failed to register data provider") - // Give requester exactly 80 TRUF (enough for 2 attestations) - err = giveAttestationBalance(ctx, platform, requesterAddr.Address(), "80000000000000000000") + err := giveAttestationBalance(ctx, platform, requesterAddr.Address(), "80000000000000000000") require.NoError(t, err, "failed to give balance") systemAdmin := util.Unsafe_NewEthereumAddressFromString("0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf")