Decentralized Module Update Service Rev0
Motivation
Since https://github.com/filecoin-station/core/pull/316, Station Core is keeping its Zinnia module sources up to date by fetching most current versions on a fixed loop. It does this by asking the GitHub API for the latest tag of the module, and then fetching the release tarball if its newer than the last seen latest tag.
Operators have since complained in #station on Filecoin slack, that GitHub is inaccessible in certain locations, and they would need an update solution that doesn’t rely on GitHub.
Centralized suggestion
The first idea was to create a new service, that sits in front of the GitHub API, refreshes its state periodically, and serves the tarballs. In order not to get rate limiting problems, Cloudflare sits in front of the service and caches responses indefinitely.
This has drawbacks:
- The update service becomes a single point of failure
- The team needs to pay for hosting, cloudflare
- The team needs to perform DevOps
Decentralized approach
Instead, use a smart contract for tracking references (CIDs) to the last version of a module, and store module sources on IPFS.
Publish
sequenceDiagram
participant a as GitHub Action
participant w as web3.storage
participant c as Smart Contract
a ->> w: upload module CAR
w ->> a: return CID
a ->> c: set(module, CID)Update / Get Latest Version
sequenceDiagram
participant s as Station Core
participant c as Smart Contract
participant w as web3.storage
s ->> c: get(module)
c ->> s: return CID
s ->> w: download given CID
w ->> s: CAR fileComponents
Smart Contract
// SPDX-License-Identifier: (MIT or Apache-2.0)
import "../lib/openzeppelin-contracts/contracts/access/AccessControl.sol";
pragma solidity ^0.8.19;
contract ZinniaModules is AccessControl {
bytes32 public constant UPDATER_ROLE = keccak256("UPDATER_ROLE");
mapping (string => string) public modules;
constructor(address updater) {
_grantRole(UPDATER_ROLE, updater);
}
function setLatest (string memory module, string memory cid) public {
require(hasRole(UPDATER_ROLE, msg.sender), "Not an updater");
modules[module] = cid;
}
function getLatest(
string memory module
) public view returns (string) {
require(modules[module] !== "", "Module not found");
return modules[module];
}
}GitHub Action
- has a FIL/ETH account
- configured secrets:
WALLET_SEED
GLIF_AUTH
WEB3_STORAGE_*
- activates when a new git tag is created
- downloads release tarball
- uploads release tarball to
web3.storageusing their JS API
- remembers the returned CID
- sets up a smart contract API using
ethers@6and glif
- reads repository name as
module
- calls
contract.setLatest(module, CID)(costs gas)
- account balance will be monitored in Grafana
Station Core
- Uses
ethers@6/ GLIF to callcontract.getLatest(module)for every module registered
- If the returned CID is different from the last seen one, update using the
web3.storagepublic gateway (with validation)