Blockchain Sync: Database & Contract Integrity

by Editorial Team 47 views
Iklan Headers

Hey guys! Ever run into a situation where your database and blockchain aren't quite seeing eye-to-eye? It's a total headache, especially when dealing with stuff like debate outcomes, stakes, or even just the general status of things. Let's dive into how to fix this!

Problem

The core issue here is the lack of synchronization between what's happening on the blockchain (like on Base) and what's reflected in our off-chain database (think Supabase). This disconnect can lead to some serious data integrity problems. Imagine showing the wrong winner for a debate, displaying incorrect stake amounts, or having outdated debate statuses. Not good, right?

Current Broken Flow

Let's break down the current flawed process step by step to see where things go off the rails.

Debate Creation

  1. User Initiates Debate: A user gets the ball rolling by creating a debate and sending a transaction to the Base blockchain.
  2. Transaction Pending: The transaction is in limbo, waiting for confirmation.
  3. Immediate Database Insertion: The application, without waiting for confirmation, immediately inserts the debate into the database. Critically, it marks the transactionHash as null.
  4. Problem 1: Blockchain Failure, Database Persistence: If, for any reason, the blockchain transaction fails (and trust me, things can go wrong), the database still holds a record of the debate as if it's valid. Talk about misleading!
  5. Problem 2: Database Failure, Blockchain Isolation: Conversely, if the database insert fails, the blockchain does have the debate recorded, but the application doesn't reflect it. It's like the debate exists in a parallel universe the user can't see.

Prize Claims

  1. Winner Claims Victory: The designated winner initiates a prize claim on the blockchain.
  2. USDC Transfer: The Base contract dutifully transfers the USDC prize.
  3. Database Neglect: Here's the kicker: the database never gets updated. It's stuck in the past.
  4. Repeat Claim Opportunity: The user, seeing the "Claim Prize" button still active, might think they can claim again.
  5. Multiple Claim Attempts: You guessed it – the user can potentially attempt to claim the prize multiple times, leading to chaos and potential exploits.

Vote Finalization

  1. Vote Submission: Votes are tallied and submitted to the database.
  2. No On-Chain Finalization: Crucially, there's no corresponding call to the blockchain to finalize the debate.
  3. Off-Chain Winner Determination: The debate winners are determined solely based on the data in the database.
  4. Smart Contract Inactivity: The smart contract on the blockchain remains oblivious, still thinking the debate is ongoing.
  5. Verification Impossibility: There's no way to independently verify the results by looking at the Base block explorer. Trust is broken!

What's Missing

Okay, so where do we even begin to fix this mess? Here's a breakdown of the essential components we need to implement.

1. Event Listeners for Base Contracts

The first thing we need is a way for our application to listen to what's happening on the Base blockchain. That means setting up event listeners that automatically react to specific contract events.

// src/lib/blockchain/eventListener.ts
import { createPublicClient, http } from 'viem'
import { base } from 'viem/chains'

const publicClient = createPublicClient({
 chain: base,
 transport: http()
})

// Listen for DebateCreated events
publicClient.watchContractEvent({
 address: DEBATE_FACTORY_ADDRESS,
 abi: DebateFactoryABI,
 eventName: 'DebateCreated',
 onLogs: async (logs) => {
 for (const log of logs) {
 const { debateId, creator, stake, poolAddress } = log.args
 
 // Update database with confirmed on-chain data
 await db.debates.update({
 where: { id: debateId },
 data: {
 contractAddress: poolAddress,
 transactionHash: log.transactionHash,
 blockNumber: log.blockNumber,
 status: 'confirmed'
 }
 })
 }
 }
})

// Listen for DebateJoined events
// Listen for PrizeClaimed events
// Listen for DebateFinalized events

This code snippet shows how to use viem to listen for DebateCreated events. When a new debate is created on-chain, the onLogs function is triggered. Inside this function, we extract the relevant data from the event log and update our database with the confirmed on-chain information. It's super important to have event listeners for all relevant contract events. That includes events like DebateJoined, PrizeClaimed, and DebateFinalized. Each event should trigger a corresponding update in the database, ensuring that our off-chain data is always in sync with the blockchain state.

2. Transaction Receipt Verification

Instead of blindly trusting that a transaction went through, we need to wait for confirmation from the Base blockchain. This means waiting for a certain number of blocks to be mined after the transaction is submitted. This gives us a higher degree of certainty that the transaction is valid and irreversible.

// src/lib/blockchain/transactions.ts
import { waitForTransactionReceipt } from 'wagmi/actions'

export async function createDebateWithConfirmation(params) {
 // Submit transaction to Base
 const hash = await writeContract({
 address: DEBATE_FACTORY_ADDRESS,
 abi: DebateFactoryABI,
 functionName: 'createDebate',
 args: [params.topic, params.stake]
 })
 
 // Wait for Base confirmation (don't trust immediately)
 const receipt = await waitForTransactionReceipt({
 hash,
 confirmations: 2 // Wait for 2 Base blocks
 })
 
 if (receipt.status === 'success') {
 // Now safe to update database
 const debateId = receipt.logs[0].args.debateId
 
 await fetch('/api/debates', {
 method: 'POST',
 body: JSON.stringify({
 id: debateId,
 transactionHash: hash,
 contractAddress: receipt.contractAddress,
 blockNumber: receipt.blockNumber
 })
 })
 } else {
 throw new Error('Transaction failed on Base')
 }
}

Here, the waitForTransactionReceipt function from wagmi/actions is key. It waits for the transaction to be confirmed on Base before proceeding. Only if the transaction is successful (i.e., receipt.status === 'success') do we update the database. If the transaction fails, we throw an error, preventing the database from being populated with incorrect data. We're waiting for 2 Base blocks here.

3. On-Chain Data as Source of Truth

The database should be treated as a cache of the blockchain state, not the source of truth. Before performing any critical operations, we should always query the Base blockchain to verify the data in our database. This helps us detect and correct any discrepancies that may have occurred.

// src/lib/blockchain/debateVerification.ts
export async function verifyDebateOnChain(debateId: string) {
 const debate = await db.debates.findUnique({ where: { id: debateId } })
 
 if (!debate.contractAddress) {
 return { verified: false, reason: 'No contract address' }
 }
 
 // Query Base blockchain
 const onChainData = await readContract({
 address: debate.contractAddress,
 abi: DebatePoolABI,
 functionName: 'getDebateState'
 })
 
 // Compare database vs Base chain
 const discrepancies = []
 
 if (debate.winner !== onChainData.winner) {
 discrepancies.push({
 field: 'winner',
 database: debate.winner,
 blockchain: onChainData.winner
 })
 }
 
 if (debate.status !== onChainData.status) {
 discrepancies.push({
 field: 'status',
 database: debate.status,
 blockchain: onChainData.status
 })
 }
 
 return {
 verified: discrepancies.length === 0,
 discrepancies
 }
}

This verifyDebateOnChain function is our safety net. It fetches the debate data from both the database and the Base blockchain. It compares the values of critical fields like winner and status. If any discrepancies are found, they are recorded, and the function returns verified: false. This allows us to take appropriate action, such as updating the database or alerting an administrator.

4. Base Block Explorer Integration

Transparency is key! We should provide users with easy access to verify transactions and contracts on the Base block explorer (Basescan). This allows them to independently verify the data and build trust in our platform.

// Show transaction on Base block explorer
<a href={`https://basescan.org/tx/${debate.transactionHash}`}>
 View on Basescan
</a>

// Show contract on Base block explorer
<a href={`https://basescan.org/address/${debate.contractAddress}`}>
 View Contract
</a>

These simple links can make a huge difference in user confidence. By providing direct links to Basescan, we empower users to see the data for themselves, fostering trust and transparency.

5. Reconciliation Service

Even with all the above measures in place, discrepancies can still occur. A background job, or reconciliation service, is crucial for periodically checking the database against the blockchain and correcting any mismatches.

// src/lib/blockchain/reconciliation.ts
export async function reconcileDebates() {
 const debates = await db.debates.findMany({
 where: {
 status: { in: ['pending', 'active'] }
 }
 })
 
 for (const debate of debates) {
 if (!debate.contractAddress) continue
 
 const verification = await verifyDebateOnChain(debate.id)
 
 if (!verification.verified) {
 console.error(`Debate ${debate.id} out of sync with Base`)
 
 // Update database to match Base blockchain
 for (const discrepancy of verification.discrepancies) {
 await db.debates.update({
 where: { id: debate.id },
 data: {
 [discrepancy.field]: discrepancy.blockchain
 }
 })
 }
 }
 }
}

// Run every 5 minutes
setInterval(reconcileDebates, 5 * 60 * 1000)

This reconcileDebates function iterates through all debates in the database and calls the verifyDebateOnChain function. If any discrepancies are found, it updates the database to match the blockchain state. This function should be run periodically (e.g., every 5 minutes) to ensure that the database remains consistent with the blockchain.

6. Base-Specific Optimizations

Let's leverage the unique advantages of the Base blockchain to make our synchronization even better:

  • Base's 2-second block time: This means faster confirmations and quicker updates.
  • Base's low fees: We can afford more on-chain operations, such as frequent data verification.
  • Base's RPC reliability: We can generally trust the data we retrieve from the Base RPC.
  • Base Scan API: We can use the Base Scan API to programmatically verify transactions.

Implementation Plan

Here's a phased approach to implementing these fixes.

Phase 1: Event Listening (Week 1)

  • Set up Base event listeners.
  • Create event handlers for all contract events.
  • Update the database upon event receipt.
  • Log all events for debugging purposes.

Phase 2: Transaction Verification (Week 1)

  • Wait for Base confirmations before updating the database.
  • Handle failed transactions gracefully.
  • Implement retry logic for Base RPC failures.
  • Show pending states in the UI to provide user feedback.

Phase 3: Data Verification (Week 2)

  • Implement on-chain verification functions.
  • Add Basescan links to all transactions.
  • Query Base before critical operations.
  • Show blockchain data in an admin panel for debugging and monitoring.

Phase 4: Reconciliation (Week 2)

  • Build the reconciliation service.
  • Schedule periodic syncs.
  • Implement alerting on discrepancies.
  • Automate fixes when it's safe to do so.

Files to Create

  • src/lib/blockchain/eventListener.ts - Base event listeners.
  • src/lib/blockchain/transactions.ts - Transaction confirmation.
  • src/lib/blockchain/debateVerification.ts - On-chain verification.
  • src/lib/blockchain/reconciliation.ts - Sync service.
  • src/lib/blockchain/basescan.ts - Basescan API integration.
  • src/app/api/blockchain/sync/route.ts - Manual sync endpoint.
  • src/components/blockchain/TransactionStatus.tsx - Base tx status UI.

Files to Modify

  • src/hooks/useCreateDebate.ts - Add transaction verification.
  • src/hooks/useJoinDebate.ts - Add confirmation waiting.
  • src/hooks/usePrizeClaim.ts - Verify on Base before claiming.
  • src/app/api/debates/[id]/finalize/route.ts - Check Base state.
  • src/lib/db/schema/debates.ts - Add blockchain metadata fields.

Acceptance Criteria

  • [ ] All contract events from Base are captured.
  • [ ] Database is updated only after Base confirmation.
  • [ ] Transaction receipts are stored with block numbers.
  • [ ] Basescan links are shown for all transactions.
  • [ ] Reconciliation service runs automatically.
  • [ ] Discrepancies are logged and alerted.
  • [ ] Failed transactions don't corrupt the database.
  • [ ] UI shows a "confirming on Base" status.
  • [ ] Can query Base directly to verify any debate.
  • [ ] Admin panel shows blockchain vs. database diff.
  • [ ] Prize claims are verified on-chain before allowing.
  • [ ] 2-block confirmations are required for finality.

Base Blockchain Benefits

  • Fast Finality: 2-second blocks on Base.
  • Cheap Verification: Low gas to read state.
  • Reliable RPC: Base RPC is highly available.
  • Block Explorer: Basescan for transparency.
  • Event Indexing: Base supports event queries.
  • L2 Benefits: Quick, cheap state checks.

Critical Scenarios This Fixes

  1. User creates debate → Base tx fails → Database remains clean
  2. Winner claims prize → Base confirms → Database updates → Button is disabled
  3. Votes are finalized → Base contract is updated → Winners match on-chain
  4. Network hiccup → Reconciliation job fixes mismatches
  5. Dispute arises → Point to Basescan as the source of truth