SourceHub Documentation

repository·dev·Indexed 20 days ago

https://github.com/sourcenetwork/sourcehub

SourceHub provides a Dockerized environment for running nodes, supporting deployment modes such as validator recovery, RPC nodes, and standalone test networks. The documentation covers local development using Docker Compose, building from source with Go 1.23 and Ignite CLI, and configuring the chain via environment variables. It includes technical guides on testing JWS extension options with DID-based feegrants and details on the epochs module for implementing on-chain timers and hooks.

Tokens
22.2K
Snippets
86
Records
104
Agent score
69%

What's inside sourcehub

  1. Prevent Replay Attacks in ACP

    dev

    To protect against replay attacks when submitting MsgPolicyCmd payloads, the ACP module implements two primary mechanisms:

    1. Expiration Height: Every payload must include an expiration_height. If the current SourceHub block height exceeds this value, the payload is rejected.
    2. ID Caching: The module maintains a cache of payload ids. A payload is only accepted if its id is not already present in the cache. The id is cached until its expiration_height has passed.
  2. How the Epochs module works

    dev

    The epochs module defines on-chain timers that execute at fixed time intervals. An "epoch" is the period between two timer ticks.

    Key behaviors:

    • Timers: Each timer has a unique identifier and a fixed interval.
    • Tick Timing: A timer ticks at the first block whose blocktime is greater than the timer's calculated end time (start time + interval).
    • Catch-up Mechanism: If the chain is down for an extended period, the module will trigger one timer tick per block until the timer has caught up to the current time. This ensures no epochs are skipped, even if they occur in rapid succession during recovery.
    • State: The module maintains one EpochInfo per identifier, which tracks the current state of that specific timer.
  3. Use MsgPolicyCmd to execute commands without a SourceHub account

    dev

    The MsgPolicyCmd transaction allows users to issue commands to an Access Control Policy (ACP) without possessing a native SourceHub account. This is achieved by decoupling the transaction signer from the command issuer through a Signed Payload.

    Instead of signing a transaction with a SourceHub account, an Actor provides a payload containing a command, their Decentralized Identifier (DID), metadata, and a signature. The ACP module validates the signature using the public key found in the Actor's DID document. This allows any resolvable DID to act as an Actor under a Policy.

    /* Concept: The Actor uses a DID to sign a payload, which is then submitted via MsgPolicyCmd */
  4. Implementation Note: Validating Bearer JWS Timestamps

    dev

    When developing or extending SourceHub to validate Bearer JWS timestamps (iat and exp), do not use the local system time of the machine running the node.

    To ensure deterministic execution across the blockchain and handle clock skew, you must use the Block Time (available through the SDK context) as the reference time for all validation logic.

  5. Spin up a standalone SourceHub network with Docker

    dev

    To quickly start a single-node SourceHub network for testing, use the provided Dockerfile with the STANDALONE=1 environment variable. This mode starts a new network with zero fees and includes a pre-funded faucet account.

    Faucet Account Details: The funded account is static for all standalone deployments. You can import it into a keyring or a wallet (like Keplr) to broadcast transactions.

    Mnemonic: comic very pond victory suit tube ginger antique life then core warm loyal deliver iron fashion erupt husband weekend monster sunny artist empty uphold

    # Build the docker image
    docker image build -t sourcehub:latest .
    
    # Start the standalone network
    docker run -p 9090:9090 -p 26657:26656 -p 26656:26656 -p 1317:1317 -e STANDALONE=1 sourcehub:latest
  6. Run SourceHub locally for development

    dev

    There are two primary ways to run the chain locally for development:

    1. Using the dev entrypoint script: This runs ./scripts/genesis-setup.sh internally, initializes a new node with chain ID sourcehub-dev, creates validator and faucet keys, and configures development settings.
    2. Using Docker Compose: Run docker-compose up to start the environment via Docker.
    # Option 1: Run build/sourcehubd directly
    ./scripts/dev-entrypoint.sh start
    
    # Option 2: Using Docker Compose
    docker-compose up
  7. Use Validator Recovery mode

    dev

    To recover a validator's credentials and join a network using existing keys, use Validator Recovery mode by providing paths to your existing keys via environment variables. This mode allows you to restore the validator key, the CometBFT consensus key, and the CometBFT p2p key.

    Required/Optional variables:

    • MNEMONIC_PATH: Path to a file containing a cosmos key mnemonic to restore the validator key.
    • CONSENSUS_KEY_PATH: Path to a file containing the CometBFT consensus key (e.g., priv_validator_key.json).
    • COMET_NODE_KEY_PATH: Path to a file containing the CometBFT p2p key (e.g., node_key.json).
    • GENESIS_PATH: Path to the network genesis file.
  8. Authenticate Policy Commands using Bearer JWS

    dev

    In SourceHub, Policy Commands (which mutate relationships in a Policy) can be authenticated using the Bearer scheme. This scheme uses a self-signed JSON Web Signature (JWS) containing specific claims to identify the actor and authorize an account.

    To use Bearer authentication, you must provide a JWS that meets the following requirements:

    1. Claims: The JWS payload must include exactly these four claims:
      • iss: The DID of the Actor issuing the JWS. This DID must resolve to a DID Document containing a public key used to validate the signature.
      • authorized_account: A valid SourceHub address (bech32 encoded Cosmos Account with the source prefix). The Policy Command is only accepted if the transaction is signed by this specific account.
      • iat: Issued At (Unix timestamp in seconds).
      • exp: Expires (Unix timestamp in seconds).
    2. Signature: The JWS must be signed by a private key corresponding to the public key resolvable via the iss DID.
    3. Serialization: You must use the compact serialization format. JSON Serialization format will be rejected.
    4. Headers: All JOSE headers (e.g., jku, jwk, kid, x5u) are ignored; the key is derived solely from the iss DID.

    Security Warning: A Bearer JWS allows the authorized_account to issue any Policy Commands on behalf of the issuer for the token's lifespan. Use this only if you fully trust the account owner. It is recommended to use short-lived tokens (e.g., ~15 minutes).

     {
        "iss": "did:key:z6MkkHsQbp3tXECqmUJoCJwyuxSKn1BDF1RHzwDGg9tHbXKw",
        "authorized_account": "source12frvft2an4sjrdlvjhjunq9m7j0ygaev05crmh",
        "iat": 1718814750,
        "exp": 1718815650
    }
  9. Verify JWS payloads in the ACP module

    dev

    When using JSON Web Signatures (JWS) for the signed payload, follow these security constraints to prevent impersonation attacks:

    1. Ignore JOSE Headers: Do NOT trust any information in the JWS header (such as kid, x509 URLs, or embedded JWKs). An adversary may use these to trick the module into verifying the signature against a key they control instead of the intended Actor's key.
    2. Use the actor field: The verification MUST use the public key/VerificationMethod resolved from the DID specified in the actor field of the payload, not any information provided in the JWS header.
    3. Algorithm Restriction: Only accept web-encoded JWS and restrict verification to a pre-configured set of allowed algorithms.
    4. Single Use: Payloads are single-use entities to prevent replay attacks.
  10. Implement Epoch Hooks in your module

    dev

    To execute logic at specific intervals, your module can implement hooks provided by the epochs module.

    Available Hooks:

    • AfterEpochEnd(ctx sdk.Context, epochIdentifier string, epochNumber int64): Called when an epoch ends.
    • BeforeEpochStart(ctx sdk.Context, epochIdentifier string, epochNumber int64): Called at the start of a new epoch.

    Implementation Pattern: Because the epochs module can manage multiple identifiers, your hook implementation must filter by the epochIdentifier you are interested in. It is recommended to store the target identifier in your module's Params so it can be managed via governance.

    Panic Isolation: If a hook panics, the state update for that specific hook is reverted, but the module continues to execute subsequent hooks. This prevents a single faulty module from halting the entire state machine. However, you should design your logic to be resilient to the possibility that a prior module's epoch hook failed to execute.

    func (k MyModuleKeeper) AfterEpochEnd(ctx sdk.Context, epochIdentifier string, epochNumber int64) {
        params := k.GetParams(ctx)
        if epochIdentifier == params.DistrEpochIdentifier {
            // execute my specific logic here
        }
    }