Zenith

api.mekatek.xyz

Zenith is in an early stage and may exhibit instability.

Overview

Zenith creates a market for block space in supported Cosmos networks . Supply is provided by participating validators , who delegate block production to Zenith, and receive a majority share of profits from winning bids. Demand is created by participating searchers , who submit bids, or bundles of transactions, that are prioritized in the block if they win at auction.

Zenith is currently implemented as an API at api.mekatek.xyz .

Supported Networks

Validators

Validators on supported networks can integrate with Zenith by compiling their full node with a fork of Tendermint. Click here for instructions .

By default, integration with Zenith is disabled, and blocks are produced normally. To enable the integration, set the ZENITH_CHAIN_ID environment variable to the chain ID your validator is configured with, e.g. osmosis-1. To enable a “dry run” mode, where Zenith is called and results are logged but blocks are built via the normal method, set ZENITH_DRY_RUN to true.

Participating validators receive payments from successful searcher bids to their staking address.

Further information:

Searchers

Searchers can participate in auctions on supported networks by submitting valid bids to the bid endpoint .

A bid is valid if it targets an auction (chain ID and height) which Zenith expects to produce, and if it includes transactions (messages) that send payments to the correct addresses, with the correct allocations, for that auction.

Further information:

Payments

Each auction defines payment requirements, which are expressed as a set of addresses and corresponding allocations, or weights.

Bids for an auction must include transactions (messages) that send payments to those addresses, at the correct ratio. (The ratio tolerance is 1%.) Bids that don’t satisfy the payment requirements are rejected.

When building a block, bids are prioritized based on overall payment: more payment, higher priority. Payments are calculated per bid, and so can be split up among the transactions in the bid.

Payments are different than fees and gas. Each transaction in a bid still needs to include a fee that satisfies the gas requirements for the network. However, unlike fees, payments are only collected from winning bids that are included in a block.

Payments from auctions are split between Mekatek and the proposer. The payment requirements are returned via the auctions endpoint. Currently the split is 97% to the proposer and 3% to Mekatek.

As an example, consider an auction with the following payment requirements.

{
  "payments": [
    {
      "address":    "validatoraddr", 
      "allocation": 0.97,
      "denom":      "coin"
    },
    {
      "address":    "builderaddr", 
      "allocation": 0.03,
      "denom":      "coin"
    }
  ]
}

Bids for this auction must contain transactions (messages) that send payments of coins to both validatoraddr and builderaddr. 97% of the coins paid to both of these addresses must go to validatoraddr, and 3% of the coins must go to builderaddr.

Alice places a bid containing payments of 972 coins to validatoraddr and 29 coins to builderaddr. This represents a ratio of of 972 / (971+29) = 0.9710 to the validator, and 29 / (972+29) = 0.02897 to the builder. 0.9710 is within 1% of 0.97, and 0.02897 is within 1% of 0.03, so the ratio requirement is satisfied. The bid is accepted, with a total payment of 1001 coins.

Bertrand places a bid with a payment of 100 coins to validatoraddr and 100 coins to builderaddr. This represents a ratio of 0.5 to the validator, and 0.5 to the builder, which doesn’t satisfy the ratio requirement. The bid is rejected.

Block Production

Blocks are produced from the mempool transactions given to the build endpoint by the proposer, and valid bids placed for the corresponding auction by searchers. Bids are processed before mempool transactions.

Bids are atomic: if a bid is accepted, all of its transactions will be included in the block, in the order set by the bid. A given transaction is included in a block at most once. If a winning bid contains a transaction, all other bids that contain that transaction will be rejected.

Blocks produced by Zenith satisfy the same transaction size (bytes) and gas limits used during normal block production.

Validator Integration

Validators integrate with Zenith by compiling their full node with Mekatek’s fork of the Tendermint repository. See the the Validator section for more information on setup.

There are a few environment variables that control behavior.

  • ZENITH_CHAIN_ID — Enables the integration with Zenith for the given chain ID.
  • ZENITH_DRY_RUN — If set to true, the validator node will make requests to Zenith and log the results, but will produce blocks via the normal method instead.
  • ZENITH_TIMEOUT — Overrides the default HTTP request timeout, e.g. 3s

When a participating validator becomes a proposer, it makes a build request to Zenith. That request contains, among other things, the complete content of the validator’s mempool, and a signature that verifies the identity of the caller. Zenith responds with an ordered list of transactions that should be used as the proposed block, which includes transactions from winning bids made by searchers for the corresponding auction.

If for any reason Zenith fails to propose a block within a strict time limit, then the code falls back to the normal block production behavior.

Signing

Zenith authenticates validators by verifying a signature included in each request. Validators produce those signatures with their private key, which requires extensions to the relevant interfaces in Tendermint, specifically privval.

If the private key is local to the validator node, the Tendermint patch handles everything automatically. If the private key is managed by a remote signing system, operators will need to use a patched version to support the new signable types.

API Reference

POST /v0/register

Deprecated. Registration is implicit in /v1/build .

Registers a validator for a supported chain, allowing them to make build requests.

Registration is a two-step process. First, a validator makes an apply request, specifying a chain ID, their unique address on that network, and a payment address which will receive shared profits from block production. The response will contain a challenge, which the validator must sign to prove their identity.

Request

curl -Ss --data '
{
  "chain_id":          "CHAIN_ID",
  "validator_address": "VALIDATOR_ADDRESS",
  "payment_address":   "PAYMENT_ADDRESS"
}
' -XPOST api.mekatek.xyz/v0/register

Response

{
  "challenge_id": "UUID",
  "challenge":    "BASE64_BYTES"
}

The validator signs the challenge with their private key, and then makes a register request, containing the challenge ID and the signed challenge bytes. If the signature is valid, the validator is registered (or re-registered) with the payment address specified in the apply stage.

Request

curl -Ss --data '
{
  "challenge_id": "UUID",
  "signature":    "BASE64_SIGNATURE"
}
' -XPOST api.mekatek.xyz/v0/register

Response

{
  "result": "registered"
}

POST /v0/build

Deprecated. Use /v1/build instead, which has an identical API.

POST /v1/build

Builds a block, yielding a share of payments from winning bids to the calling proposer.

A build request includes, among other things, the validator’s mempool transactions. Those transactions are combined with any bids (bundles) received by Zenith for the corresponding chain ID and height, and an ordered set of transactions — a block — is computed and returned. See the Block production section for more information on how blocks are produced.

A majority share of payments collected from accepted bids is given to the proposer.

Build requests must include a signature, which is produced by concatenating the values in the request into a specific sequence of bytes, and signing those bytes with the key corresponding to the address of the proposing validator.

Request

curl -Ss --data '
{
  "chain_id":          "CHAIN_ID", 
  "height":            INTEGER,
  "validator_address": "VALIDATOR_ADDRESS",
  "max_bytes":         INTEGER,
  "max_gas":           INTEGER,
  "txs":               ["BASE64_TX", "BASE64_TX", ...]
  "signature":         "BASE64_SIGNATURE"
}
' -XPOST api.mekatek.xyz/v1/build

Response

{
  "txs":               ["BASE64_TX", "BASE64_TX", ...],
  "validator_payment": "100coin"
}

GET /v0/auction

Returns information about valid auctions, including payment requirements. See the Payments section for more information about payments.

A valid auction is a specific chain ID and height, typically in the future, where the corresponding block is predicted to be produced by Zenith. The auction endpoint supports auctions up to 10 heights in the future.

If an auction is in the past, or has already started, the API will respond with 410 Gone. If the auction is too far in the future, the API will respond with 425 Too Early. If the auction is otherwise valid but not predicted to be run by the builder, the API will respond with 417 Expectation Failed.

Request

curl -L -Ss --data '
{
  "chain_id": "CHAIN_ID",
  "height":   INTEGER
}
' -XGET api.mekatek.xyz/v0/auction

Response

{
  "chain_id": "CHAIN_ID",
  "height":   INTEGER,
  "payments": [
    {
      "address":    "PAYMENT_ADDRESS",
      "allocation": FLOAT,
      "denom":      "DENOM"
    },
    {
      "address":    "PAYMENT_ADDRESS",
      "allocation": FLOAT,
      "denom":      "DENOM"
    }
  ]
}

POST /v0/bid

Accepts a bid (a bundle of transactions) for a valid auction (a specific chain ID and height).

Bids for an auction are prioritized by payment, and winning bids are included, in priority order, at the top of the auction block. See the Payments section for more information on payment requirements.

The default kind of bid is a “top” bid, which means the transactions in the bid will either be the first transactions in the auction block, or the bid will fail. It’s also possible to make a “block” bid, which means the transactions in the bid may be included in the auction block at any position. See BID_KIND for more information.

Blocks are produced by evaluating bids first, and mempool transactions second: bids have first priority. A given transaction will be included at most once in a given block: if multiple bids include the same transaction, the winning bid will “claim” the transaction, and any other bids that include that transaction will fail.

Bids are atomic, and bid transactions are ordered. If a bid succeeds, all of its transactions will be included in the block, in the order defined by the bid.

The bid endpoint returns success if the bid is valid and will be considered when producing the corresponding auction block. Note that this does not necessarily mean the bid will be included in the auction block.

Like the auction endpoint, the bid endpoint responds with 410 Gone for expired auctions, 425 Too Early for auctions too far in the future, and 417 Expectation Failed for auctions not predicted to be run by Zenith.

Request

curl -Ss --data '
{
  "chain_id": "CHAIN_ID",
  "height":   INTEGER,
  "kind":     "BID_KIND",
  "txs":      ["BASE64_TX", "BASE64_TX", ...]
}
' -XPOST api.mekatek.xyz/v0/bid

Response

{
  "chain_id":  "CHAIN_ID",
  "height":    INTEGER,
  "kind":      "BID_KIND",
  "tx_hashes": ["TX_HASH", "TX_HASH", ...]
}

API Types

CHAIN_ID

The chain ID of the network. For example, the chain ID for Osmosis might be osmosis-1 .

VALIDATOR_ADDRESS

The uppercase hex encoded address of a validator on the network. Should be equal to one of the addresses returned by the /validators RPC endpoint.

PAYMENT_ADDRESS

A valid Bech32-encoded address that can receive payments on the network.

BID_KIND

One of the following string values.

  • top — the bid will only be accepted if it is first in the block
  • block — the bid may be accepted at any position in the block

BASE64_TX

The base64 representation of the bytes of a single transaction.

BASE64_SIGNATURE

The base64 representation of the bytes returned from a signing operation.

TX_HASH

A string representation of the hash of a transaction, typically uppercase hex digits.