ArbiterOperator Contract¶
The ArbiterOperator is a specialized Chainlink operator contract that enforces access control for Verdikta's oracle network. It extends the standard Chainlink operator with reputation-based allowlists and multi-word response capabilities.
Overview¶
The ArbiterOperator serves as the gateway between the Verdikta network and Chainlink infrastructure by:
- Access Control: Only approved contracts can request oracle services
- Multi-word Responses: Supports complex data structures in responses
- Reputation Integration: Works with ReputationKeeper allowlists
- Standard Compliance: Maintains full Chainlink operator compatibility
Key Features¶
Enhanced Access Control¶
- Reputation Keeper Integration: Validates requests against approved contract lists
- Flexible Allowlists: Multiple ReputationKeeper contracts can be registered
- Gateway Mode: Can operate with or without allowlist enforcement
Advanced Response Handling¶
- Multi-word Responses: Supports complex data structures beyond single values
- Gas Management: Ensures sufficient gas for complex callback operations
- Detailed Event Logging: Comprehensive callback tracing for debugging
Contract Interface¶
Core Functionality¶
// Multi-word response fulfillment
function fulfillOracleRequestV(
bytes32 requestId,
uint256 payment,
address callbackAddress,
bytes4 callbackFunctionId,
uint256 expiration,
bytes calldata data
) external returns (bool success)
Parameters¶
Parameter | Type | Description |
---|---|---|
requestId | bytes32 | Unique identifier for the oracle request |
payment | uint256 | Payment amount in LINK tokens |
callbackAddress | address | Contract to call with the response |
callbackFunctionId | bytes4 | Function selector to call |
expiration | uint256 | Request expiration timestamp |
data | bytes | Encoded response data |
Reputation Keeper Management¶
// Add a ReputationKeeper to the allowlist
function addReputationKeeper(address rkAddr) external onlyOwner
// Remove a ReputationKeeper from the allowlist
function removeReputationKeeper(address rkAddr) external onlyOwner
// Check if address is a registered ReputationKeeper
function isReputationKeeper(address rkAddr) external view returns (bool)
// Check if the allowlist is empty (gate disabled)
function isReputationKeeperListEmpty() external view returns (bool)
Interface Detection¶
// ERC-165 interface detection
function supportsInterface(bytes4 interfaceId)
external view returns (bool)
The ArbiterOperator implements the IArbiterOperator
interface with ID 0xd9f812f9
.
Events¶
// Reputation Keeper management
event ReputationKeeperAdded(address indexed rk);
event ReputationKeeperRemoved(address indexed rk);
// Callback execution tracing
event OracleCallbackAttempt(
bytes32 indexed requestId,
address callback,
bytes4 selector,
uint256 gasBefore
);
event OracleCallbackResult(
bytes32 indexed requestId,
bool success,
bytes returnData,
uint256 gasAfter
);
// Inherited from standard Chainlink operator
event OracleRequest(/* ... */);
event OracleResponse(bytes32 indexed requestId);
Usage Examples¶
Oracle Deployment¶
pragma solidity ^0.8.19;
import "./ArbiterOperator.sol";
contract OracleDeployment {
ArbiterOperator public operator;
address public owner;
constructor(address linkToken) {
operator = new ArbiterOperator(linkToken);
owner = msg.sender;
}
function setupReputationKeepers(address[] memory keepers) external {
require(msg.sender == owner, "Only owner");
for (uint256 i = 0; i < keepers.length; i++) {
operator.addReputationKeeper(keepers[i]);
}
}
function getOperatorInfo() external view returns (
address operatorAddress,
bool hasReputationKeepers,
bool isArbiterOperator
) {
return (
address(operator),
!operator.isReputationKeeperListEmpty(),
operator.supportsInterface(type(IArbiterOperator).interfaceId)
);
}
}
Integration with ReputationKeeper¶
pragma solidity ^0.8.19;
contract OracleRegistration {
ArbiterOperator public operator;
ReputationKeeper public reputationKeeper;
function registerWithSystem(
bytes32 jobId,
uint256 fee,
uint64[] memory classes
) external {
// First, register operator with reputation system
reputationKeeper.registerOracle(
address(operator),
jobId,
fee,
classes
);
// Ensure operator recognizes the reputation keeper
require(
operator.isReputationKeeper(address(reputationKeeper)) ||
operator.isReputationKeeperListEmpty(),
"Operator doesn't recognize ReputationKeeper"
);
}
function checkCompatibility() external view returns (bool) {
// Verify the operator is compatible with Verdikta system
return operator.supportsInterface(type(IArbiterOperator).interfaceId);
}
}
Client Contract Example¶
pragma solidity ^0.8.19;
import "@chainlink/contracts/src/v0.8/ChainlinkClient.sol";
contract VerdiktaClient is ChainlinkClient {
using Chainlink for Chainlink.Request;
ArbiterOperator public arbiterOperator;
bytes32 public jobId;
uint256 public fee;
constructor(
address _operator,
address _link,
bytes32 _jobId,
uint256 _fee
) {
_setChainlinkToken(_link);
arbiterOperator = ArbiterOperator(_operator);
jobId = _jobId;
fee = _fee;
}
function requestEvaluation(string memory cid) external returns (bytes32) {
// Verify this contract is approved
require(
arbiterOperator.isReputationKeeperListEmpty() ||
_isApprovedByReputationKeepers(),
"Contract not approved for oracle requests"
);
Chainlink.Request memory req = _buildOperatorRequest(
jobId,
this.fulfill.selector
);
req._add("cid", cid);
return _sendOperatorRequestTo(address(arbiterOperator), req, fee);
}
function fulfill(
bytes32 requestId,
uint256[] memory likelihoods,
string memory justificationCID
) external recordChainlinkFulfillment(requestId) {
// Handle the multi-word response
// Process likelihoods array and justification
}
function _isApprovedByReputationKeepers() internal view returns (bool) {
// Implementation would check with known ReputationKeeper contracts
// This is simplified for the example
return true;
}
}
Node.js Oracle Implementation¶
const { Requester, Validator } = require('@chainlink/external-adapter');
// Example external adapter for Verdikta AI evaluation
const inputParams = {
cid: ['cid', 'hash'],
mode: false // Optional mode parameter
};
const customParams = (data) => {
const validator = new Validator(data, inputParams);
if (validator.error) throw validator.error;
return {
cid: validator.validated.data.cid,
mode: validator.validated.data.mode || '0'
};
};
const createRequest = async (input, callback) => {
try {
const { cid, mode } = customParams(input);
// Determine if this is commit or reveal phase
const isCommitPhase = mode.startsWith('1:');
const isRevealPhase = mode.startsWith('2:');
let responseData;
if (isCommitPhase) {
// Commit phase: return hash of actual evaluation
const actualEvaluation = await performAIEvaluation(cid);
const salt = generateRandomSalt();
const commitment = computeCommitment(actualEvaluation, salt);
responseData = {
data: [commitment], // Single value for commit
result: commitment
};
// Store evaluation and salt for reveal phase
storeForReveal(input.id, actualEvaluation, salt);
} else if (isRevealPhase) {
// Reveal phase: return actual evaluation and salt
const { evaluation, salt } = retrieveStored(input.id);
const cleanCid = extractJustificationCID(evaluation);
responseData = {
data: evaluation.likelihoods,
result: evaluation.likelihoods,
cid: `${cleanCid}:${salt.toString(16)}`
};
} else {
// Legacy mode: direct evaluation
const evaluation = await performAIEvaluation(cid);
responseData = {
data: evaluation.likelihoods,
result: evaluation.likelihoods,
cid: evaluation.justificationCID
};
}
callback(200, Requester.success(input.id, responseData));
} catch (error) {
console.error('Oracle request failed:', error);
callback(500, Requester.errored(input.id, error));
}
};
// AI evaluation function (placeholder)
async function performAIEvaluation(cid) {
// Implementation would:
// 1. Fetch data from IPFS using the CID
// 2. Send to AI service for evaluation
// 3. Return structured response
return {
likelihoods: [750, 250], // Example: 75% likelihood for first option
justificationCID: 'QmNewJustificationHash'
};
}
function computeCommitment(evaluation, salt) {
const crypto = require('crypto');
const encoded = ethers.utils.defaultAbiCoder.encode(
['uint256[]', 'uint256'],
[evaluation.likelihoods, salt]
);
const hash = crypto.createHash('sha256').update(encoded).digest();
// Return first 128 bits as integer
return BigInt('0x' + hash.toString('hex').substring(0, 32));
}
module.exports.createRequest = createRequest;
Access Control Mechanism¶
Allowlist Enforcement¶
The ArbiterOperator enforces access control through ReputationKeeper contracts:
- Request Validation: Before emitting
OracleRequest
, checks if requester is approved - Flexible Gatekeeping: Multiple ReputationKeeper contracts can be registered
- Gate Disable: If no ReputationKeepers are registered, all requests are allowed
- Double Validation: Additional check during fulfillment for security
Configuration States¶
State | ReputationKeepers | Behavior |
---|---|---|
Open | None registered | All requests allowed |
Gated | ≥1 registered | Only approved contracts allowed |
Multi-Keeper | Multiple registered | Approved by ANY keeper |
Interface Specification¶
IArbiterOperator Interface¶
interface IArbiterOperator is IERC165 {
// Multi-word fulfillment
function fulfillOracleRequestV(
bytes32 requestId,
uint256 payment,
address callbackAddress,
bytes4 callbackFunctionId,
uint256 expiration,
bytes calldata data
) external returns (bool success);
// Allowlist queries
function isReputationKeeper(address rkAddr) external view returns (bool);
function isReputationKeeperListEmpty() external view returns (bool);
}
Interface ID Calculation¶
The interface ID 0xd9f812f9
is computed as:
bytes4(keccak256("fulfillOracleRequestV(bytes32,uint256,address,bytes4,uint256,bytes)"))
^ bytes4(keccak256("isReputationKeeper(address)"))
^ bytes4(keccak256("isReputationKeeperListEmpty()"))
Security Considerations¶
Access Control Security¶
- Pre-emission Validation: Prevents unauthorized requests from reaching the oracle node
- Dual Validation: Additional check during fulfillment prevents bypass attempts
- Owner Controls: Only operator owner can modify ReputationKeeper list
Callback Security¶
- Gas Limits: Enforces minimum gas requirements for callback execution
- Execution Monitoring: Detailed event logging for callback success/failure
- Error Isolation: Callback failures don't affect operator state
Integration Security¶
- Interface Verification: Operators must implement IArbiterOperator interface
- Version Compatibility: Maintains compatibility with standard Chainlink infrastructure
- Upgrade Path: Reputation Keeper list can be updated without operator redeployment
Best Practices¶
For Oracle Operators¶
- Register Multiple Keepers: Add redundant ReputationKeeper contracts for resilience
- Monitor Events: Track callback success rates and investigate failures
- Gas Management: Ensure adequate gas limits for complex callbacks
- Regular Updates: Keep ReputationKeeper list current with network changes
For Contract Developers¶
- Interface Detection: Always verify operator supports IArbiterOperator interface
- Approval Verification: Check contract approval status before making requests
- Error Handling: Implement robust handling for callback failures
- Gas Estimation: Provide sufficient gas for multi-word response processing
For System Administrators¶
- Operator Monitoring: Track operator performance and availability
- Access Control Audits: Regularly review approved contract lists
- Network Health: Monitor overall system connectivity and response times
- Upgrade Coordination: Plan operator updates to minimize service disruption
Troubleshooting¶
Common Issues¶
"Operator: requester not approved"
// Check if contract is approved by any ReputationKeeper
const keepers = await getRegisteredReputationKeepers(operatorAddress);
for (const keeper of keepers) {
const approved = await keeper.isContractApproved(contractAddress);
if (approved) {
console.log(`Approved by keeper: ${keeper}`);
break;
}
}
"Oracle not ArbiterOperator type"
// Verify operator implements correct interface
const operator = new ethers.Contract(address, OPERATOR_ABI, provider);
const isArbiter = await operator.supportsInterface('0xd9f812f9');
console.log('Is ArbiterOperator:', isArbiter);
"Callback execution failed" - Check gas limits in oracle job specification - Verify callback function signature matches request - Review event logs for detailed error information
Diagnostic Queries¶
// Check operator configuration
function diagnoseOperator(address operatorAddr) external view returns (
bool isArbiterOperator,
bool hasReputationKeepers,
address[] memory registeredKeepers
) {
ArbiterOperator op = ArbiterOperator(operatorAddr);
isArbiterOperator = op.supportsInterface(type(IArbiterOperator).interfaceId);
hasReputationKeepers = !op.isReputationKeeperListEmpty();
// Note: registeredKeepers would need additional enumeration logic
// This is simplified for the example
}