FlureeLabs

Commit Receipts and tx-id

Every successful transaction returns a commit receipt containing metadata about the transaction. This receipt provides important information for tracking, auditing, and referencing transactions.

Commit Receipt Structure

Basic commit receipt:

{
  "t": 42,
  "timestamp": "2024-01-22T10:30:00.000Z",
  "commit_id": "bafybeig...commitT42",
  "flakes_added": 15,
  "flakes_retracted": 3,
  "previous_commit_id": "bafybeig...commitT41"
}

Receipt Fields

Transaction Time (t)

The transaction time is a monotonically increasing integer uniquely identifying this transaction:

{
  "t": 42
}

Properties:

  • Unique across all ledgers in the Fluree instance
  • Monotonically increasing (never decreases)
  • Used for time travel queries
  • Basis for temporal ordering

Usage:

# Query at specific transaction
curl -X POST http://localhost:8090/v1/fluree/query \
  -d '{"from": "mydb:main@t:42", ...}'

Read-after-write consistency: The t value is the key to ensuring queries see freshly committed data. Pass it as min_t to refresh() to gate queries on a minimum transaction time. See Time Travel — Consistency and Read-After-Write for details.

Timestamp

ISO 8601 formatted timestamp of when the transaction was committed:

{
  "timestamp": "2024-01-22T10:30:00.000Z"
}

Properties:

  • UTC timezone
  • Millisecond precision
  • Server-assigned (not client-provided)
  • Monotonic (within same transaction time ordering)

Usage:

# Query at specific time
curl -X POST http://localhost:8090/v1/fluree/query \
  -d '{"from": "mydb:main@iso:2024-01-22T10:30:00Z", ...}'

Commit ID

Content-addressed identifier for the commit:

{
  "commit_id": "bafybeig...commitT42"
}

Properties:

  • CIDv1 value (base32-lower multibase string)
  • Derived from the commit's canonical bytes via SHA-256
  • Storage-agnostic -- does not depend on where the commit is stored
  • Can be used to fetch the commit from any content store

Usage:

# Query at specific commit
curl -X POST http://localhost:8090/v1/fluree/query \
  -d '{"from": "mydb:main@commit:bafybeig...commitT42", ...}'

Flake Counts

Number of triples added and retracted:

{
  "flakes_added": 15,
  "flakes_retracted": 3
}

flakes_added: Number of new triples asserted flakes_retracted: Number of existing triples removed

Net change: flakes_added - flakes_retracted

Previous Commit

ContentId of the previous commit (forms a chain):

{
  "previous_commit_id": "bafybeig...commitT41"
}

Properties:

  • Links to parent commit by ContentId
  • Forms immutable commit chain
  • Enables commit history traversal
  • null for first transaction (t=1)

Extended Receipt Fields

Author (Signed Transactions)

For signed transactions, includes author DID:

{
  "t": 42,
  "author": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
  "signature": "z58DAdFfa9SkqZMVP...",
  ...
}

Message

Optional commit message (if provided):

{
  "t": 42,
  "message": "Add new customer records for Q1 2024",
  ...
}

Ledger

Ledger ID:

{
  "t": 42,
  "ledger": "mydb:main",
  ...
}

Duration

Transaction processing time in milliseconds:

{
  "t": 42,
  "duration_ms": 45,
  ...
}

Using Transaction IDs

Referencing Transactions

Store transaction ID for later reference:

const receipt = await transact({
  "@graph": [{ "@id": "ex:alice", "schema:name": "Alice" }]
});

// Store for audit trail
await logTransaction({
  entity: "ex:alice",
  operation: "create",
  transactionId: receipt.t,
  timestamp: receipt.timestamp
});

Historical Queries

Query data at specific transaction:

// Get data as it was at transaction 42
const historicalData = await query({
  from: `mydb:main@t:${receipt.t}`,
  select: ["?name"],
  where: [{ "@id": "ex:alice", "schema:name": "?name" }]
});

Commit Verification

Verify commit integrity by re-deriving the ContentId from fetched bytes:

async function verifyCommit(receipt) {
  const bytes = await contentStore.get(receipt.commit_id);
  const derivedCid = computeContentId("Commit", bytes);

  if (derivedCid !== receipt.commit_id) {
    throw new Error('Commit integrity violation!');
  }
}

Commit Chain

Commits form an immutable chain:

t=1 (cid:aaa) ← t=2 (cid:bbb) ← t=3 (cid:ccc) ← t=4 (cid:ddd)
  ↑                ↑                ↑                ↑
  |                |                |                |
previous=null   previous=aaa    previous=bbb    previous=ccc

Traversing History

Walk the commit chain:

async function getCommitHistory(ledger, fromT, toT) {
  const history = [];
  let currentT = fromT;
  
  while (currentT >= toT) {
    const commit = await getCommit(ledger, currentT);
    history.push(commit);
    currentT = commit.previous_t;
  }
  
  return history;
}

Querying Commit Metadata

SPARQL Query for Commits

PREFIX f: <https://ns.flur.ee/db#>

SELECT ?t ?timestamp ?commitId ?author
WHERE {
  ?commit a f:Commit ;
          f:t ?t ;
          f:timestamp ?timestamp ;
          f:commitId ?commitId .
  OPTIONAL { ?commit f:author ?author }
}
ORDER BY DESC(?t)
LIMIT 10

JSON-LD Query for Recent Commits

{
  "@context": {
    "f": "https://ns.flur.ee/db#"
  },
  "select": ["?t", "?timestamp", "?commitId"],
  "where": [
    { "@id": "?commit", "@type": "f:Commit" },
    { "@id": "?commit", "f:t": "?t" },
    { "@id": "?commit", "f:timestamp": "?timestamp" },
    { "@id": "?commit", "f:commitId": "?commitId" }
  ],
  "orderBy": ["-?t"],
  "limit": 10
}

Receipt Storage

Application Database

Store receipts in your application database:

CREATE TABLE transaction_receipts (
  id SERIAL PRIMARY KEY,
  ledger VARCHAR(255),
  transaction_t INTEGER,
  commit_id TEXT,
  timestamp TIMESTAMP,
  flakes_added INTEGER,
  flakes_retracted INTEGER,
  author VARCHAR(255),
  created_at TIMESTAMP DEFAULT NOW()
);

Document Store

Store as JSON documents:

await mongodb.collection('receipts').insertOne({
  ledger: receipt.ledger,
  t: receipt.t,
  commit_id: receipt.commit_id,
  timestamp: receipt.timestamp,
  flakes: {
    added: receipt.flakes_added,
    retracted: receipt.flakes_retracted
  },
  metadata: {
    author: receipt.author,
    duration_ms: receipt.duration_ms
  }
});

Time-Series Database

For analytics:

await influxdb.writePoint({
  measurement: 'transactions',
  tags: { ledger: receipt.ledger },
  fields: {
    t: receipt.t,
    flakes_added: receipt.flakes_added,
    flakes_retracted: receipt.flakes_retracted,
    duration_ms: receipt.duration_ms
  },
  timestamp: new Date(receipt.timestamp)
});

Audit Trail

Transaction Log

Build complete audit log from receipts:

async function buildAuditLog(ledger, startDate, endDate) {
  const receipts = await fetchReceipts(ledger, startDate, endDate);
  
  return receipts.map(r => ({
    time: r.timestamp,
    transactionId: r.t,
    author: r.author || 'anonymous',
    changes: {
      added: r.flakes_added,
      removed: r.flakes_retracted
    },
    commit: r.commit_id,
    verifiable: true
  }));
}

Compliance Reports

Generate compliance reports:

async function generateComplianceReport(ledger, period) {
  const receipts = await fetchReceipts(ledger, period.start, period.end);
  
  return {
    period: period,
    totalTransactions: receipts.length,
    totalChanges: receipts.reduce((sum, r) => sum + r.flakes_added, 0),
    authors: [...new Set(receipts.map(r => r.author))],
    verifiedChain: verifyCommitChain(receipts)
  };
}

Performance Monitoring

Transaction Metrics

Track transaction performance:

function analyzeReceipts(receipts) {
  const durations = receipts.map(r => r.duration_ms);
  const sizes = receipts.map(r => r.flakes_added + r.flakes_retracted);
  
  return {
    avgDuration: average(durations),
    maxDuration: Math.max(...durations),
    avgSize: average(sizes),
    maxSize: Math.max(...sizes),
    throughput: receipts.length / (period.hours)
  };
}

Alert on Anomalies

function checkForAnomalies(receipt) {
  if (receipt.duration_ms > 1000) {
    alert(`Slow transaction: ${receipt.t} took ${receipt.duration_ms}ms`);
  }
  
  if (receipt.flakes_added > 10000) {
    alert(`Large transaction: ${receipt.t} added ${receipt.flakes_added} flakes`);
  }
}

Best Practices

1. Always Store Receipts

Store transaction receipts for audit trail:

const receipt = await transact(transaction);
await storeReceipt(receipt);

2. Verify Commit Chain

Periodically verify commit chain integrity:

async function verifyChainIntegrity(ledger) {
  const receipts = await fetchAllReceipts(ledger);
  
  for (let i = 1; i < receipts.length; i++) {
    if (receipts[i].previous_commit_id !== receipts[i-1].commit_id) {
      throw new Error(`Chain broken at t=${receipts[i].t}`);
    }
  }
}

3. Use Transaction IDs for References

Store transaction IDs rather than timestamps:

Good:

{ entity: "ex:alice", createdAt_t: 42 }

Less reliable:

{ entity: "ex:alice", createdAt: "2024-01-22T10:30:00Z" }

4. Monitor Performance

Track receipt metadata for performance insights:

const avgDuration = receipts.reduce((sum, r) => sum + r.duration_ms, 0) / receipts.length;

5. Include in Error Handling

Log receipt info on errors:

try {
  const receipt = await transact(transaction);
  logger.info(`Transaction successful: t=${receipt.t}`);
} catch (err) {
  logger.error(`Transaction failed`, {
    error: err.message,
    transaction: transaction
  });
}

Related Documentation