- Powerloom Protocol Contracts
- Table of Contents
- Overview: Smart Contract Architecture
- State transitions
- State modification: Data market contracts
- State view: Data market contracts
- State view: Snapshotter identity state contract
- Events
The smart contracts that maintain the protocol state are arranged as depicted in the diagram that follows. The system is designed to be modular and extensible, with the ability to add new features as the protocol evolves.
Main components and features are explained below:
All the interactions with underlying data markets, their state changes and event emissions are accessible through the main ProtocolState Contract.
- The Protocol State is a separate contract, managed by an UUPS upgradeable proxy
- Snapshotter State contract is separate from the protocol state. It is used to maintain the state of snapshotters and their slots.
The upgradable proxy pattern allows for the core state contract to be upgraded without the need to redeploy the proxy and all the dependent contracts.
The upgradable proxy pattern also allows for the state contract to be upgraded in a controlled manner, with the ability to maintain previous versions of the contract in case a regression is introduced in the new version
Data market contracts are separate from the protocol state. Their creation is initiated from the protocol state core contract and intermediated by a data market factory contract.
The following features of the protocol state are maintained in the data market contract since they are specific to their operations and functions:
- Epochs
- Epoch release and epoch size
- Epochs in a "day"
- Allowed sequencer identities
- Finalized snapshot CIDs against project IDs
- Submission counts against snapshotter slots
- Submission batches and their attestation consensus by validators
Read more about different components in our docs:
Snapshotters are assigned slots on the protocol, and their identities are maintained on a separate SnapshotterState
contract. The interface to interact with this contract is maintained in the protocol state contract as well as the data market contracts.
/**
* @title ISnapshotterState
* @dev Interface for the SnapshotterState contract
*/
interface ISnapshotterState {
/**
* @dev Returns the snapshotter address for a given slot
* @param slotId The ID of the slot
* @return address of the snapshotter
*/
function slotSnapshotterMapping(uint256 slotId) external view returns (address);
/**
* @dev Returns the total number of slots
* @return uint256 representing the slot counter
*/
function slotCounter() external view returns (uint256);
/**
* @dev Checks if an address is a registered snapshotter
* @param snapshotter Address to check
* @return bool indicating if the address is a snapshotter
*/
function allSnapshotters(address snapshotter) external view returns (bool);
}
enum SnapshotStatus {
PENDING,
FINALIZED
}
The state of a snapshot CID against a project ID is PENDING
when reported by the sequencer and FINALIZED
when the same is attested to by the validator.
The PENDING
state can be considered to be an intermediate, trusted state since it is reported by the sequencer which has no incentive to be a bad actor unless its security is compromised.
function updateDaySize(uint256 _daySize) public onlyOwnerOrigin {
DAY_SIZE = _daySize;
epochsInADay = DAY_SIZE / (SOURCE_CHAIN_BLOCK_TIME * EPOCH_SIZE);
}
A 'day' for a data market is defined by the DAY_SIZE
in seconds. The epochsInADay
is the number of epochs that fit into a day.
function toggleRewards() public onlyOwnerOrigin {
rewardsEnabled = !rewardsEnabled;
}
This quota is the number of snapshots that have to be submitted by a snapshotter in a day to be eligible for rewards.
function updateDailySnapshotQuota(
uint256 _dailySnapshotQuota
) public onlyOwnerOrigin {
dailySnapshotQuota = _dailySnapshotQuota;
}
The sequencer commits the submission counts for a day against the slot IDs of the snapshotters. It is planned to be decentralized in the future by combining the election of sequencers and reports on submitted counts peers called 'watchers'.
function updateRewards(
uint256[] memory slotIds,
uint256[] memory submissionsList,
uint256 day
) public onlySequencer returns (bool) {
Refer: day size for a data market
function releaseEpoch(
uint256 begin,
uint256 end
) public onlyEpochManager isActive returns (bool, bool) {
Refer: Epoch manager
function forceSkipEpoch(
uint256 begin,
uint256 end
) public onlyOwnerOrigin isActive {
This is a fallback mechanism to skip epochs in case the epoch release service fails.
Snapshot submission in batches by sequencer
function submitSubmissionBatch(
string memory batchCid,
uint256 epochId,
string[] memory projectIds,
string[] memory snapshotCids,
bytes32 finalizedCidsRootHash
) public onlySequencer
An epoch as identified by epochId
can contain multiple batches of snapshot submissions from the sequencer, as referenced by the batchCid
.
The entire contents of this batch are made available on IPFS on the CID batchCid
.
The elements of the arrays projectIds
and snapshotCids
are present as a 1:1 mapping that the sequencer reports as finalized CID against each of the project IDs.
NOTE:
- The
projectIds
andsnapshotCids
arrays are expected to be of the same length. - In the next upgrade, the
projectIds
andsnapshotCids
arrays will be removed. ThefinalizedCidsRootHash
, that is the root hash of the merkle tree built from the CIDs of the projects, holds appropriate information to be used in the consensus rule for attestation as well as verification of the batch CID uploaded to IPFS and anchored to the protocol state by this function call.
function endBatchSubmissions(uint256 epochId) external onlySequencer
Attestation against submission batches by validator
function submitBatchAttestation(
string memory batchCid,
uint256 epochId,
bytes32 finalizedCidsRootHash
) public onlyValidator
Validators submit their attestations against batches of snapshot submissions in an epochId
by refererring to their batchCid
.
The attestation is the finalizedCidsRootHash
which is the hash of the merkle tree root constructed from the finalized CIDs across the projects contained in a batch.
shouldFinalizeBatchAttestation()
is used as the state check whether the consensus rule around attestations submitted by the network of validators is satisfied, followed by a call to finalizeSnapshotBatch()
that finalizes the snapshot CIDs against the project IDs contained in a batchCid
for an epochId
.
function shouldFinalizeBatchAttestation(
string memory batchCid,
uint256 currentAttestationCount
) private view returns (bool)
function finalizeSnapshotBatch(string memory batchCid, uint256 epochId) private
function forceCompleteConsensusAttestations(
PowerloomDataMarket dataMarket,
string memory batchCid,
uint256 epochId
) public
uint8 public EPOCH_SIZE; // Number of Blocks in each Epoch
Refer: Epoch
These properties are specific to the chain on which the actual data sources i.e. smart contracts and applications are running.
uint256 public SOURCE_CHAIN_ID;
uint256 public SOURCE_CHAIN_BLOCK_TIME; // Block time in seconds * 1e4 (to allow decimals)
uint256 public batchSubmissionWindow // Number of blocks to wait before finalizing batch
uint256 public attestationSubmissionWindow // Number of blocks to wait for attestation acceptance
uint256 public minAttestationsForConsensus // Minimum number of attestations for consensus
- The snapshot CID reported to have reached consensus against a
projectId
for anepochId
. TheConsensusStatus
wraps theSnapshotStatus
enum.
mapping(string projectId => mapping(
uint256 epochId => ConsensusStatus
)) public snapshotStatus;
function maxSnapshotsCid(
string memory projectId,
uint256 epochId
) public view returns (string memory)
- Snapshot CID finalized for a project ID against an epoch ID, as reported by the sequencer.
mapping(string projectId => uint256 epochId) public lastSequencerFinalizedSnapshot;
- Snapshot CID finalized against an epoch ID for each project ID, once validators attest to the finalization from sequencer as shown above.
mapping(string projectId => uint256 epochId) public lastFinalizedSnapshot;
- The very first epoch ID against which a finalization was achieved for a project ID.
mapping(string projectId => uint256 epochId) public projectFirstEpochId;
mapping(string batchCid => string[] projectids) public batchCidToProjects;
Project IDs contained within a Batch CID.
mapping(uint256 epochId => string[] batchCids) public epochIdToBatchCids;
Batch CIDs of submissions sent out for an epoch by the sequencer.
mapping(string batchCid => mapping(address => bool)) public attestationsReceived;
mapping(string batchCid => mapping(bytes32 finalizedCidsRootHash=> uint256 count)) public attestationsReceivedCount;
mapping(string batchCid => uint256 count) public maxAttestationsCount;
mapping(string batchCid => bytes32 finalizedCidsRootHash) public maxAttestationFinalizedRootHash;
mapping(string batchCid => bool) public batchCidAttestationStatus;
Storing attestations received from validator identities and their counts of attestations against finalized root hashes of merkle trees built from CIDs.
mapping(string batchCid => bytes32 finalizedCidsRootHash) public batchCidSequencerAttestation;
mapping(string batchCid => address[] validators) public batchCidDivergentValidators;
State of the initial attestation as reported by the sequencer as finalized CIDs against the project IDs and the state of them if they diverge from the consensus on attestations as reached by validators.
Mapping to check for existence of snapshotter identity on protocol state.
Mapping from slot ID to registered snapshotter node's signing wallet address.
Number of registered slots on the protocol state.
Event emissions specific to data market operations are emitted from the data market contracts as well as the protocol state core contract, which has an additional topic that identifies the data market against which the operation is being performed.
This allows for state and event observers on the protocol to filter events by the data market contract of interest.
For example, the SnapshotBatchSubmitted
event has the following signatures when emitted from:
event SnapshotBatchSubmitted(
address indexed dataMarketAddress,
string batchCid,
uint256 indexed epochId,
uint256 timestamp
);
event SnapshotBatchSubmitted(
string batchCid,
uint256 indexed epochId,
uint256 timestamp
);
DailyTaskCompletedEvent
: Emitted when a snapshotter reaches their daily quota of snapshot submission count.
event DailyTaskCompletedEvent(
address indexed dataMarketAddress,
address snapshotterAddress,
uint256 slotId,
uint256 dayId,
uint256 timestamp
);
Read more: Daily snapshot quota
DayStartedEvent
: Emitted when a new day starts.
event DayStartedEvent(
address indexed dataMarketAddress,
uint256 dayId,
uint256 timestamp
);
Read more: Day size for a data market
EpochReleased
: Emitted when an epoch is released.
event EpochReleased(
address indexed dataMarketAddress,
uint256 indexed epochId,
uint256 begin,
uint256 end,
uint256 timestamp
);
Read more: Epoch release
- SnapshotBatchSubmitted: Emitted upon the sequencer submitting a batch of snapshot submissions along with their claimed finalizations for an
epochId
event SnapshotBatchSubmitted(
address indexed dataMarketAddress,
string batchCid,
uint256 indexed epochId,
uint256 timestamp
);
- DelayedBatchSubmitted: Emitted when the sequencer submits a batch past the submission deadline for an epoch
event DelayedBatchSubmitted(
address indexed dataMarketAddress,
string batchCid,
uint256 indexed epochId,
uint256 timestamp
);
- SnapshotBatchFinalized: Emitted when a majority of the validators have submitted their attestations on a
batchCid
submitted by the sequencer.
event SnapshotBatchFinalized(
uint256 indexed epochId,
string batchCid,
uint256 timestamp
);
- SnapshotBatchAttestationSubmitted: Emitted when a validator
validatorAddr
submits their attestation for abatchCid
batch.
event SnapshotBatchAttestationSubmitted(
string batchCid,
uint256 indexed epochId,
uint256 timestamp,
address indexed validatorAddr
);
- DelayedAttestationSubmitted: Emitted when a validator
validatorAddr
submits their attestation for abatchCid
batch past the submission deadline
event DelayedAttestationSubmitted(
string batchCid,
uint256 indexed epochId,
uint256 timestamp,
address indexed validatorAddr
);
Each of the components has a detailed documentation in the docs
folder: