cowprotocol / services

Off-chain services for CoW Protocol
https://cow.fi/
Other
157 stars 65 forks source link

CowSwap Complete Auction Status #187

Open nlordell opened 2 years ago

nlordell commented 2 years ago

This issue captures the work for adding an API for exposing solver competition and driver status at an API level so that the CowSwap front-end can better display status of an order. Also, this is a good place to have some discussion on the "shape" of the API.

My current proposal is:

GET /api/v1/solver-competitions/current
GET /api/v1/solver-competitions/{auctionId}
GET /api/v1/transactions/{transactionHash}/solver-competition
 => SolverCompetition

GET /api/v1/orders/{orderUid}/solver-competitions
 => SolverCompetition[]

--- Types ---

type Instant = {
  timestamp: datetime;
  block: number;
}

type SolverCompetition = {
  id: string;          // the ID of the auction.

  start: Instant;      // the instant the auction started at

  status:
    | "collecting"     // collecting order information and on-chain liquidity
    | "solving"        // solving for all orders and liquidity
    | "mining"         // executing transactions on-chain
    | "finalized"      // auction is final: transaction mined, or no settlements proposed
    | "timeout"        // auction timed-out while waiting for a settlement to mine
    ;

  settlements: {   // all the settlements ordered by objective value (so
                   // `auction.settlements[0]` is always the currently winning solution;
                   // note that we don't show all the data when still "solving" to protect
                   // sensitive information from the solution of the solvers

    solver: {
      id: string;                     // solver descriptive ID
      address: string;                // solver address that will mine the transaction
    };

    proposal: Instant;                // the instant that the settlement was proposed at,
                                      // the settlement must be able to successfully
                                      // simulate at this instant (i.e. as the first
                                      // transaction on top of the block number included in
                                      // the `Instant` data).

    calldata: {
      bytes?: bytes;                  // settlement calldata - only revealed after "solving"
      hash: digest;                   // settlement calldata hash
    };

    trades?: {                        // Trades included in the settlement. This information
                                      // can technically be derived from the `calldata`, but
                                      // is included here as a convenience. Only revealed
                                      // after "solving" is complete.

      orderUid: orderuid,             // The UID of the order being traded
      executedAmount: bigint,         // The amount of the order that was executed
    }[];

    clearingPrices?: Record<address, bigint>;
                                      // Clearing prices for the settlement - only revealed
                                      // after "solving"

    objective?: {                     // objective is only revealed after "solving"; this way 
                                      // solvers are incentivized to always send solutions
                                      // (makes it so we collect more alternatives).

      total: bignumber;               // the total value of the objective function

      // objective value components...
      surplus: bignumber;
      fees: bignumber;
      cost: bignumber;
      gas: bigint;
    };

    transaction?: {                   // the transaction data of the settlement; only
                                      // available for the winner

      hash?: digest                   // The transaction hash; not available while "mining"
                                      // a transaction on a private network.

      private: bool                   // transaction is being mined via private network
                                      // submission (FlashBots/Eden)

      submission: Instant             // the initial instant that the winner was chosen
                                      // and the transaction submitted

      latestSubmission: Instant       // the latest instant that the transaction was
                                      // submitted; equal to `submission` for the very
                                      // first transaction submission

      success?: bool                  // whether the transaction was executed successful
                                      // or reverted; only known in when "finalized"

      gasPrice: {                     // the gas price used in the latest submission
        maxFeePerGas: bigint;
        maxPriorityFeePerGas: bigint;
      };
  }[];
}

State transition diagram:

stateDiagram-v2
  [*] --> collecting
  collecting --> solving: collected orders and liquidity (~1-10 secs)
  solving --> mining: a winning settlement was proposed (~1-15 secs)
  solving --> finalized: no settlements were proposed (~1-15 secs)
  mining --> finalized: the settlement transaction was mined (~30-120 secs)
  mining --> timeout: the settlement transaction did not get mined in time (>120 secs)
  timeout --> finalized: transaction eventually mined, reverted, or nonce invalid
  finalized --> [*]

cc @anxolin

edit 1: Applied suggestions from https://github.com/cowprotocol/services/issues/187#issuecomment-1115924837 edit 2: Applied suggestions from https://github.com/cowprotocol/services/issues/187#issuecomment-1115950533 edit 3: Addressed some concerns from https://github.com/cowprotocol/services/issues/187#issuecomment-1116984993 edit 4: Hiding trade order UIDs until solving is complete. edit 5: Adjustments on timeout and finalized states to account for more errors.

anxolin commented 2 years ago

Awesome initiative. This will boost the progress bar!

Some questions:

  1. What is exactly solver name/id? where are the values for those?
  2. What is exactly "auctionId"? how is this id generated?
  3. Given the 3rd of the endpoints, does it mean we will be able to return the competition for any past batch (at least for the ones after you start persisting them?)
  4. I like the states! I think this is already an amazing good start. I think it would be amazing if we can add a state diagram showing all possible transition of states. i.e. can we go from collecting to timeout? and things like that. We can focus also in seeing the edge cases.
  5. settlements is an array of the objects you describe, right? is not an object.
  6. calldata: I understand the privacy thing, but instead of using the same field for the digest and calldata, can we make that an object with a mandatory digest, and optional calldata?.
  7. Regarding the transaction, maybe could be also an object with two fields: txHash (string), private: boolean

The I have some nitpicks about the API name: Given the concept of "auctionstatus" I like more the name "solving-competition(s)". Also i would use plural for the 2 first endpoints.

GET /api/v1/solving-competitions/current
GET /api/v1/solving-competitions/{id}
GET /api/v1/transactions/{transactionHash}/solving-competition

GET /api/v1/auctionstatus/current GET /api/v1/auctionstatus/{auctionId} GET /api/v1/transactions/{transactionHash}/auctionstatus

anxolin commented 2 years ago

Some other question. Can we know also the gasPrice being used (even if the tx is private), also i would miss maybe a submission date, and resubmission date in case it was resubmitted.

Do you think this is possible?

nlordell commented 2 years ago

What is exactly solver name/id? where are the values for those?

This is something we still need to determine. Ideally we would come up with some strings so that you can attach icons to them.

What is exactly "auctionId"? how is this id generated?

This is the ID of the auction (i.e. "batch") that is passed as input to the solvers. The ID is an opaque string and generated in some unspecified way (so the FE should not rely on any specific behaviour). It can be assumed that it is unique per deployment (so, prod and barn may have colliding IDs). It will likely not get included on-chain in any way.

does it mean we will be able to return the competition for any past batch

Yes, after we start including auctions in the database, you can return historic competition information. Either by transaction ID or opaque auction ID.

settlements is an array of the objects you describe, right?

Yes. This is what I tried to indicate with the closing [].

can we make that an object with a mandatory digest, and optional calldata?

Sure! Whatever is easier for you to consume. I had imagined

{ calldata: "0x..." } // bytes
{ calldata: "keccak256:0x..." } // digest

But the object is a good suggestion fine.

Regarding the transaction, maybe could be also an object with two fields: txHash (string), private: boolean

Sure. But we can't reveal the hash until after it gets mined otherwise MEV bots could query private network APIs to potentially extract the calldata and try to extract value from the settlement with that knowledge.

The I have some nitpicks about the API name:

"solving competition" sounds weird to me. What about "solver competition"?

Can we know also the gasPrice being used (even if the tx is private), also i would miss maybe a submission date, and resubmission date in case it was resubmitted.

Yes.

alfetopito commented 2 years ago

Would it be too much to ask and endpoint to be able to check whether an order is included in any settlement?

Otherwise the alternative I see is to query the current endpoint and check whether it's included in the first settlement in the list (if any), which I it's also ok.

Another point is that I'm not sold on is the closed state. I feels that something else can still happen. What about finished?

nlordell commented 2 years ago

What about finished?

Not a fan of finished, but I do agree with your point. finalized maybe (since the technical term used for the fact that it happened and can't change is "finality" - like "block finality").

nlordell commented 2 years ago

Updated the original post with suggestions.

nlordell commented 2 years ago

Would it be too much to ask and endpoint to be able to check whether an order is included in any settlement?

No, added this as well.

MartinquaXD commented 2 years ago
orders: orderuid[];               // UIDs of the orders included in the settlement

Should this only contain user orders or liquidity orders as well? How should partially fillable orders be treated here? (e.g. include, don't include, only include if completely filled) Shouldn't this field also be hidden until "finalization" to not give competing solvers any hints?

settlements: { // all the settlements ordered by objective value (so auction.settlements[0] is always the currently winning solution;

Should those include only solutions which seem reasonable (i.e. can be successfully simulated)?

nlordell commented 2 years ago

Should this only contain user orders or liquidity orders as well?

The purpose of this is for the FE to be able to display if the currently winning solution has included their order or not. With that in mind we probably want to include all orders so that this information can show up in the batch explorer for partially fillable orders as well.

Shouldn't this field also be hidden until "finalization" to not give competing solvers any hints?

Since solution gathering is fairly quick, it might be OK to hide this until "solving" is over to, as you mentioned, leak lest data. I don't think we need to hide this until the settlement is "finalized" however (since the batch competition is over anyway - so we already know the winner and have broadcast its calldata from which this information can be extracted anyway). @anxolin, do you think this is OK or do you think that knowing which orders are included in which settlement 15 seconds early (i.e. only knowing once the winner is chosen)?

How should partially fillable orders be treated here?

Good point. I will do something like @vkgnosis did in https://github.com/cowprotocol/services/pull/185 and include execution information as well.

Should those include only solutions which seem reasonable (i.e. can be successfully simulated)?

Yes - the way I imagined it is that settlements get proposed and simulated before adding them to the database. The timestamp and block number of when the settlement was proposed and successfully simulated is stored in proposal: Instant field. I added a note about this to be more clear.

nlordell commented 2 years ago

@anxolin I added a state transition diagram 😄

anxolin commented 2 years ago

I love the new changes ❤️

Next round of comments and bikeshedding, since I'm very happy with the proposal already.

  1. How much can we show the user about the objective criteria? Can we show a formula, or some comprehensive way so they understand how this number came to be, and more importantly, why this number matter to them and protect their interests?

  2. The diagram is super clear, thanks for that. I understand from it, that for example, in an hypothetical case of "slow solvers", the competition will start&timeout, start&timeout, start&timeout, until the slow solvers delivers one solution. I understand how each of them will be new instances of a competition, each with their own unique ID. The user will see how they are all expiring.

  3. This is the ID of the auction (i.e. "batch") that is passed as input to the solvers. The ID is an opaque string and generated in some unspecified way (so the FE should not rely on any specific behaviour). It can be assumed that it is unique per deployment (so, prod and barn may have colliding IDs). It will likely not get included on-chain in any way.

But I would think theres a single solver competition running for the protocol right? Why here the barn/production instances might be having different IDs? Which of them is running the competition? Which is the canonical competition for a given moment in time we should show in the explorer?

  1. What is exactly solver name/id? where are the values for those? This is something we still need to determine. Ideally we would come up with some strings so that you can attach icons to them.

Should we make it an object with an address, and this be the ID? This way, we can link it to Etherscan or past solutions. Also, probably nice to have an endpoint to return the solver details: /solvers/<address> which return the name, description, icon svg, and maybe even bonding pool address, etc

This way, we could give further information for the solvers. We could hardcode in the web, but problem then would be that we need to replicate in each UI

  1. Good point. I will do something like @vkgnosis did in #185 and include execution information as well.

👍

  1. @anxolin, do you think this is OK or do you think that knowing which orders are included in which settlement 15 seconds early (i.e. only knowing once the winner is chosen)?

👍

7. Lastly, I'll include some links to some epics on potencial things we could work. Still not fully proposed yet. We will start with the progress bar, but then I see a lot of value on adding the other ones:

nlordell commented 2 years ago

How much can we show the user about the objective criteria? Can we show a formula, or some comprehensive way so they understand how this number came to be, and more importantly, why this number matter to them and protect their interests?

I think this is more of a FE thing since the equation is fixed and not "per-solver" so it doesn't have to be included in the solver competition response.

That being said, its super useful and when writing the Open API spec, I will try to include as much context as possible to explain each of these values and why they are included in the objective function.

in an hypothetical case of "slow solvers", the competition will start&timeout, start&timeout, start&timeout, until the slow solvers delivers one solution.

Only mining (i.e. executing the settlement transaction on the block-chain) can time out, not solving. In this case, it would be a "no solution" transaction from "solving" to "finalized".

But I would think theres a single solver competition running for the protocol right?

There will be one competition on barn and one in prod - so 2 at a time.

Why here the barn/production instances might be having different IDs? Which of them is running the competition? Which is the canonical competition for a given moment in time we should show in the explorer?

There will be two. The canonical competition is the production one. You are right that this is tricky though, since how do you show the prod vs barn solver competition in the explorer without confusing users 🤷.

Should we make it an object with an address, and this be the ID?

I would prefer for this not to be the address as, in the future, we want to split submission from solution finding in which case addresses may not work out. Also, at a glance, having "english-ish IDs" makes it easier to make sense of the response JSON.

We could hardcode in the web, but problem then would be that we need to replicate in each UI

Can't we just have a JSON document in GitHub (like we do for token lists):

{
  solvers: [
    {
      id: "1inch",
      name: "1Inch Solver (operated by Gnosis)",
      icon: "...",
    },
    // ...
  ],
}
github-actions[bot] commented 2 months ago

This issue has been marked as stale because it has been inactive a while. Please update this issue or it will be automatically closed.

github-actions[bot] commented 2 weeks ago

This issue has been marked as stale because it has been inactive a while. Please update this issue or it will be automatically closed.