Technical reference for the three core CompressNode contracts on Base.
Contract Overview
CompressNode deploys three interdependent smart contracts on Base. They handle all on-chain logic: node registration, job escrow, and verification challenges. All contracts are written in Solidity, compiled with Foundry, and use OpenZeppelin's Ownable for access control.
NodeRegistry
Manages node lifecycle - registration, deactivation, and reputation tracking.
- registerNode(endpoint, gpuTier) - register a new node (permissionless, free)
- deactivateSelf() - operator can voluntarily deactivate their node
- updateGpuTier(newTier) - update GPU tier after hardware upgrade
- getActiveNodes() - returns all currently active node addresses
- setLastChallengeAt(node) - called by VerificationEngine after challenge issuance
- Stores: operator address, endpoint, registeredAt, reputation (0-10000), gpuTier, active status, jobsCompleted, jobsFailed, totalEarned
JobRegistry
Handles job submission, assignment, completion, and ETH payments.
- submitJob(modelId, inputHash) payable - submit a job with ETH payment locked in escrow
- assignJob(jobId, nodeAddress) - backend assigns the job to the best available node
- completeJob(jobId, outputHash) - node completes the job, receives 95% ETH, 5% burned
- expireJob(jobId) - requester reclaims ETH if job is unassigned after 24 hours
- Follows CEI (Checks-Effects-Interactions) pattern to prevent reentrancy attacks
VerificationEngine
Issues challenges and validates node responses.
- issueChallenge(nodeAddress, inputHash, expectedOutputHash) - restricted to authorized issuers
- respondToChallenge(challengeId, responseHash) - called by the challenged node
- checkTimeout(challengeId) - deactivates nodes that miss the response window
- Uses block.prevrandao for additional entropy in challenge selection
Security Patterns
All contracts implement: zero-address checks on constructors and setters, CEI (Checks-Effects-Interactions) pattern for ETH transfers, access control via onlyOwner and onlyIssuer modifiers, bounded loops to prevent gas limit issues, and a receive() fallback on JobRegistry for direct ETH deposits.