Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Mintlify] Add Block SDK Docs #461

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/workflows/build-docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: 'build-docs'
on:
push:
branches: ['main']
pull_request:
branches: ['main']
workflow_dispatch:

defaults:
run:
shell: bash

jobs:
build-docs:
runs-on: ubuntu-latest
steps:
- name: Trigger mintlify workflow
if: github.event_name == 'push'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.DOCS_CHILDREN_ACTIONS_TRIGGER_TOKEN }}
script: |
await github.rest.actions.createWorkflowDispatch({
owner: 'mintlify-onboarding',
repo: 'skip',
workflow_id: 'mintlify-action.yml',
ref: 'main',
});
219 changes: 219 additions & 0 deletions docs/for-searchers.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
---
title: "For Searchers"
---
<Warning>
Searcher Simluation

Bundles must only pass basic `CheckTx` validation (e.g. nonce, account balance, etc.) in order to be accepted by the auction. This means that bundles that are submitted to the auction may not be entirely valid on-chain since `runMsgs` is not executed. Searchers are encouraged to simulate their bundles before submitting them to the auction.
</Warning>

### ➡️ How do searchers submit bundles to chains that use the Block SDK?[​](#️-how-do-searchers-submit-bundles-to-chains-that-use-the-block-sdk "Direct link to heading")
<Note>
Definitions

💡 `AuctionTx` (auction bid transaction) = `sdk.Tx` that includes a single `MsgAuctionBid`
</Note>
Searchers submit bundles by broadcasting a `AuctionTx` in the same way they broadcast any other transaction. A few important things to note:

* When a `MsgAuctionBid` message is included in a transaction, no other `sdk.Msg` can be present.
* Interfacing with the auction _may be different across_ `Block SDK` chains. Bidding may involve interacting with a dedicated `AuctionHouse` smart contract instead of including this special message type. In the future, we will link a chain directory here to check on which interface you need, when there are different implementations.

#### Default Auction Bid Message[​](#default-auction-bid-message "Direct link to heading")

```json


type MsgAuctionBid struct {


Bidder string `protobuf:"bytes,1,opt,name=bidder,proto3" json:"bidder,omitempty"`


Bid types.Coin `protobuf:"bytes,3,opt,name=bid,proto3" json:"bid"`


Transactions [][]byte `protobuf:"bytes,4,rep,name=transactions,proto3" json:"transactions,omitempty"`
}

```


There are three things searchers must specify to participate in an auction:

1. The **`Transactions`** they want executed at the top of block.
* Transactions will be executed in the order they are specified in the message.
* Each transactions included must be the raw bytes of the transaction.
2. The **`Bidder`** who is bidding for top of block execution.
* This must be the same account that signs the transaction.
3. The **`Bid`** they want to send alongside the bundle.
* This should be in the denom configured by the auction parameters (see below)
4. The **`Timeout`** height i.e. until what height the bid is valid for.
* This must be added to the transaction when it is being constructed i.e. `txBuilder.SetTimeoutHeight(timeoutHeight)`

#### Nonce Checking[​](#nonce-checking "Direct link to heading")

In general, all bundles must respect nonce ordering of accounts. If a bundle is submitted with an invalid nonce, it will be rejected. The execution of the bundle will always look like the following:

* Auction Transaction (which extracts the bid)
* All transactions in the bundle (in order)

For example, assume the following:

1. Searcher has account `A` with nonce `n`
2. Searcher wants to submit a bundle with 3 transactions from account `A`

The searcher must first sign the `AuctionTx` with nonce `n + 1`. Then, the searcher must sign the first transaction in the bundle with nonce `n + 2`, the second transaction with nonce `n + 3`, and the third transaction with nonce `n + 4`.

#### Skipper Bot[​](#skipper-bot "Direct link to heading")

User’s can bootstrap their searching bots by utilizing Skip’s own open source [Skipper Bot](https://github.com/skip-mev/skipper).

#### Creating an `AuctionTx`[​](#creating-an-auctiontx "Direct link to heading")

```json

func createBidTx(
privateKey *secp256k1.PrivKey,
bidder string,
bid sdk.Coin,
bundle [][]byte,
height uint64,
sequenceOffset uint64,
) (sdk.Tx, error) {

msgs := []sdk.Msg{
&buildertypes.MsgAuctionBid{
Bidder: bidder,
Bid: bid,
Transactions: bundle,
},
}


sequenceNum, accountNum := getAccountInfo(privateKey)

txConfig := authtx.NewTxConfig(
codec.NewProtoCodec(codectypes.NewInterfaceRegistry()),
authTx.DefaultSignModes,
)
txBuilder := txConfig.NewTxBuilder()


txBuilder.SetMsgs(msgs...)
...
txBuilder.SetGasLimit(5000000)




txBuilder.SetTimeoutHeight(height)




offsetNonce := sequenceNum + sequenceOffset
signerData := auth.SignerData{
ChainID: CHAIN_ID,
AccountNumber: accountNumber,
Sequence: offsetNonce,
}

sigV2, err = clientTx.SignWithPrivKey(
txConfig.SignModeHandler().DefaultMode(),
signerData,
txBuilder,
privateKey,
txConfig,
offsetNonce,
)
if err != nil {
return nil, err
}

if err = txBuilder.SetSignatures(sigV2); err != nil {
return nil, error
}

return txBuilder.GetTx(), nil
}

```


### ⚙️ Auction fees[​](#️-auction-fees "Direct link to heading")
<Note>
Auction Configuration

All auction parameters are accessible though the `/block-sdk/x/auction/v1/params` HTTP path on a standard node or gRPC service defined by `x/auction`.
</Note>

In order to participate in an auction, searchers must pay a fee. This fee is paid in the native token of the chain. The fee is determined by the auction parameters, which are set by the chain. The auction parameters are:

1. **`MaxBundleSize`**: specifies the maximum number of transactions that can be included in a bundle (bundle = an ordered list of transactions). Bundles must be ≤ this number.
2. **`ReserveFee`**: specifies the bid floor to participate in the auction. Bids that are lower than the reserve fee are ignored.
3. **`MinBidIncrement`**: specifies how much greater each subsequent bid must be (as seen by an individual node) in order to be considered. If the bid is lower than the `highest current bid + min bid increment`, the bid is ignored.
4. **`FrontRunningProtection`**: determines whether front-running and sandwich protection is enabled.

<Note>
Front-running and sandwich protection

**If this is set to true, your bundle must follow these guidelines:**

* You must put your signed transactions **after** transactions you didn’t sign
* You can only have **at most two** unique signers in the bundle
</Note>

Bundle Examples:

1. **Valid**: \[tx1, tx2, tx3\] where tx1 is signed by the signer 1 and tx2 and tx3 are signed by the bidder.
2. **Valid**: \[tx1, tx2, tx3, tx4\] where tx1 - tx4 are signed by the bidder.
3. **Invalid**: \[tx1, tx2, tx3\] where tx1 and tx3 are signed by the bidder and tx2 is signed by some other signer. (possible sandwich attack)
4. **Invalid**: \[tx1, tx2, tx3\] where tx1 is signed by the bidder, and tx2, tx3 are signed by some other signer. (possible front-running attack)

#### Querying auction parameters[​](#querying-auction-parameters "Direct link to heading")

```json
func getAuctionParams() (*auctiontypes.Params, error) {
# Replace this URL with the gRPC url of a node
url := "localhost:9090"

# Establish a gRPC connection to query auction parameters
grpcConn, err := grpc.Dial(
url,
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
return nil, err
}

client := auctiontypes.NewQueryClient(grpcConn)

req := &auctiontypes.QueryParamsRequest{}
resp, err := client.Params(context.Background(), req)

return resp.Params
}

```


### 🚨 Chains currently using the MEV-Lane[​](#-chains-currently-using-the-mev-lane "Direct link to heading")

#### Mainnets[​](#mainnets "Direct link to heading")


|Chain Name |Chain-ID |Block-SDK Version|
|-----------|-------------|-----------------|
|Juno |juno-1 |v1.0.2 |
|Persistence|persistence-1|v1.0.2 |
|Initia |NA |v1.0.2 |
|Prism |NA |v1.0.2 |
|Terra |phoenix-1 |v1.0.2 |


#### Testnets[​](#testnets "Direct link to heading")


|Chain Name|Chain-ID|Block-SDK Version|
|----------|--------|-----------------|
|Juno |uni-6 |v1.0.2 |
57 changes: 57 additions & 0 deletions docs/how-it-works.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
title: "How it Works"
---
### Summary[​](#summary "Direct link to heading")

With the Block SDK, blocks are broken up into smaller partial blocks called `lanes`.

* Each `lane` has its own custom block building logic and stores distinct types of transactions.
* Each lane can only consume a portion of the block as defined on the `lane`'s configuration (`MaxBlockSpace`).
* When a block proposal is requested, a block will **fill** with transactions from each `lane`, iteratively, in the order in which the `lanes` are defined in the application.
* When a block proposal is processed, each `lane` will **verify** its portion of the block, iteratively, in the order in which the `lanes` are defined in the application.
* **Transactions in blocks MUST respect the ordering of lanes.**

### 🔁 Background: Transaction Lifecycle[​](#-background-transaction-lifecycle "Direct link to heading")

Knowledge of the general transaction lifecycle is important to understand how `lanes` work.

* A transaction begins when it is signed and broadcasted to a node on a chain.
* It will be then be verified by the application on the node.
* If it is valid, it will be inserted into the node's `mempool`, which is a storage area for transactions before inclusion in a block.
* If the node happens to be a `validator`, and is proposing a block, the application will call `PrepareProposal` to create a new block proposal.
* The proposer will look at what transactions they have in their mempool, iteratively select transactions until the block is full, and share the proposal with other validators.
* When a different validator receives a proposal, the validator will verify its contents via `ProcessProposal` before signing it.
* If the proposal is valid, the validator will sign the proposal and broadcast their vote to the network.
* If the block is invalid, the validator will reject the proposal.
* Once a proposal is accepted by the network, it is committed as a block and the transactions that were included are removed from every validator's mempool.

### 🛣️ Lane Lifecycle[​](#️-lane-lifecycle "Direct link to heading")

`Lanes` introduce new steps in the transaction lifecycle outlined above.

A `LanedMempool` is composed of several distinct `lanes` that store their own transactions. The `LanedMempool` will insert the transaction into all `lanes` that accept it

* After the base application accepts a transaction, the transaction will be checked to see if it can go into any `lanes`, as defined by the lane's `MatchHandler`.
* `Lane`'s can be configured to only accept transactions that match a certain criteria. For example, a `lane` could be configured to only accept transactions that are staking related (such as a free-transaction lane).
* When a new block is proposed, the `PrepareProposalHandler` of the application will iteratively call `PrepareLane` on each `lane` (in the order in which they are defined in the application). The `PrepareLane` method is similar to `PrepareProposal`.
* Calling `PrepareLane` on a `lane` will trigger the lane to reap transactions from its mempool and add them to the proposal (if they respect the verification rules of the `lane`).
* When proposals are verified in `ProcessProposal` by other validators, the `ProcessProposalHandler` defined in `abci/abci.go` will call `ProcessLane` on each `lane` in the same order as they were called in the `PrepareProposalHandler`.
* Each subsequent call to `ProcessLane` will filter out transactions that belong to previous lanes. **A given lane's ProcessLane will only verify transactions that belong to that lane.**

<Note>
Scenario

Let's say we have a `LanedMempool` composed of two lanes: `LaneA` and `LaneB`.

`LaneA` is defined first in the `LanedMempool` and `LaneB` is defined second.

`LaneA` contains transactions Tx1 and Tx2 and `LaneB` contains transactions Tx3 and Tx4.
</Note>

When a new block needs to be proposed, the `PrepareProposalHandler` will call `PrepareLane` on `LaneA` first and `LaneB` second.

When `PrepareLane` is called on `LaneA`, `LaneA` will reap transactions from its mempool and add them to the proposal. The same applies for `LaneB`. Say `LaneA` reaps transactions Tx1 and Tx2 and `LaneB` reaps transactions Tx3 and Tx4. This gives us a proposal composed of the following:

* `Tx1`, `Tx2`, `Tx3`, `Tx4`

When the `ProcessProposalHandler` is called, it will call `ProcessLane` on `LaneA` with the proposal composed of Tx1, Tx2, Tx3, and Tx4. `LaneA` will then verify Tx1 and Tx2 and return the remaining transactions - Tx3 and Tx4. The `ProcessProposalHandler` will then call `ProcessLane` on `LaneB` with the remaining transactions - Tx3 and Tx4. `LaneB` will then verify Tx3 and Tx4 and return no remaining transactions.
Loading
Loading