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

mev-boost with unconditional payments #109

Open
obadiaa opened this issue Apr 28, 2022 · 13 comments
Open

mev-boost with unconditional payments #109

obadiaa opened this issue Apr 28, 2022 · 13 comments
Labels
research 💡 Research topic

Comments

@obadiaa
Copy link

obadiaa commented Apr 28, 2022

TLDR: We'd like to consider whether it would make sense for builders to bid unconditionally for their header to be selected by the proposer. This would imply the proposer gets paid regardless of whether the body gets revealed. We care about this because it maps onto the current spec of in-protocol PBS more closely (further helping test PBS with MEV-Boost), because it is more favorable to proposers, and because it places less trust on relays.

Context
In the current version of MEV-Boost:

  • searchers submit bundles to builders
  • builders build blocks with these bundles and public mempool transactions
  • builders send their blocks and bids (body, header, bid) to relays
  • relays check the validity of and store builder blocks, get pinged by the proposer and share only the headers of all the valid blocks they have alongside their respective bids (header,bid)
  • the proposer selects the header with the highest bid and signs on it within the beacon block they propose
  • once the relay knows the proposer has signed on this header, the block body of the associated header is revealed by the relay. The payment is contained within the body.

Note:
This commit-reveal scheme was chosen in order to ensure there is no trust assumption placed on validators, making accessing MEV revenue via MEV-Boost completely permissionless. This is the 'trusted relay' model in this document: https://hackmd.io/8cUfu-HKQuyYjWk-f9DVuw. This Github issue doesn't discuss in detail why this solution was chosen. Please refer to a presentation given at EthStaker for more detail for why ensuring all validators have access to MEV revenue is especially important in PoS Ethereum: https://youtu.be/GJwS7VF40wk?t=23292.

Unconditional payments
In the current model, the proposer only gets paid if the block body whose header they've signed on is revealed by the relay. This means the proposer is at the mercy of the relay. If the relay doesn't reveal the body, then the proposer gets slashed from proposing an empty execution block loses protocol rewards they would've gotten from proposing a block as well as the MEV rewards (incl. transaction tips) that are in the block body. (edit: correcting previous statement that proposer gets slashed from proposing an empty block).

The relay is therefore trusted by 1) the builders who submit their blocks to it, 2) the validators who sign on one of the headers they receive from the relays.

We would like to consider an alternative system where builders pay unconditionally for their bid. In this system:

  • searchers submit bundles to builders
  • builders build blocks with these bundles and public mempool transactions
  • builders send their (header,bid) pairs to relays
  • relays store builder's (header,bid) pairs as well the money needed to fullfill the bid
  • relays get pinged by the proposer and release each (header,bids) pairs it has for the current slot.
  • the proposer selects the header with the highest bid and signs on it within the beacon block they propose
  • as soon as the relay sees the header, it releases the payment to the proposer, unconditionally of the body being revealed.
  • the block body of the associated header is revealed by the builder who is now incentivized to do so since they've already paid for it.

How does this differ from the current system?

  • The relay now essentially acts as a payment escrow.
  • The relay cannot see the block bodies and does not check for the validity of the bodies. This reduces the trust builders and proposers need to have in the relay.
  • The proposer now gets paid regardless of whether the body gets revealed.
  • The builders are now responsible to reveal their header's body, and they are incentivized to do so since they've already paid for it. Similarly, they are incentivized to make sure their body is valid.
  • This shifts the attack vector to builders, who can now be griefed by proposers who can pick a header late enough such that a builder doesn't have time to reveal the body before the appropriate time window but a relay has enough time to see it and release the payment.

A document outlining this alternative was written by @thegostep here: https://hackmd.io/@flashbots/Skc0vuyCt

Outstanding questions

  1. How does unconditional payment work with multiple relays? In other words, who does the builder send money to if it sends its header,bid pair to two relays? (h/t @lightclient for asking this question)
  2. Does this system put a prohibitively higher capital requirements on builders who now need to put up money upfront?
  3. Is a griefing vector from proposers acceptable?
  4. Which solution is better between the current design and unconditional payments? Do we fully understand the trade-off space?

We open this up for discussion and look forward to your questions and comments 🤗

@obadiaa obadiaa changed the title [wip] MEV-Boost with unconditional payments [wip] research: mev-boost with unconditional payments Apr 28, 2022
@metachris metachris added the research 💡 Research topic label Apr 29, 2022
@thegostep
Copy link
Contributor

thegostep commented Apr 29, 2022

I think the model can be refined slightly: builders would send to the relays the header + a signed payment. The relay sends the header to the validator and escrows the payment until it receives the signed header back. It then publishes the payment to the transaction pool.

This assumes that only the relay has the ability to censor a payment and it is possible for all parties to identify if they do. This can be achieved through each relay having an independent escrow contract.

The capital requirements is that builders must maintain an account balance with each relay they send payloads to.


contract Escrow {

    event Payment(address builder, address feeRecipient, uint256 amount, uint256 nonce, uint256 expiry, bytes32 blockhash);

    struct Account {
        uint256 timelock;
        uint256 balance;
        uint256 nonce;
    }

    mapping[address => Account] accounts;
    
    address immutable admin;
    uint256 immutable timelockDuration;

    constructor(address a, uint256 t) {
        admin = a;
        timelockDuration = t;
    }

    // builder deposits eth into their account
    receive() public payable {
        accounts[msg.sender].balance += msg.value;
    }

    // escrow executes payment
    function pay(address feeRecipient, uint256 amount, uint256 nonce, uint256 expiry, bytes32 blockhash, bytes memory permission) public {
        require(msg.sender == admin);
        require(block.number <= expiry);
        bytes memory msg = keccak256(abi.encodePacked(feeRecipient, amount, nonce, expiry, blockhash));
        address builder = ecrecover(msg, permission);
        require(nonce >= accounts[builder].nonce);
        accounts[builder].nonce = nonce + 1;
        accounts[builder].balance -= amount;
        payable(feeRecipient).transfer(amount);
        emit Payment(builder, feeRecipient, amount, nonce, expiry, blockhash);
    }

    // builder requests exit
    function requestExit() public {
        require(accounts[msg.sender].timelock == 0);
        accounts[msg.sender].timelock = block.number + timelockDuration;
    }

    // builder confirms exit
    function confirmExit() public {
        Account memory account = accounts[msg.sender];
        require(account.timelock != 0 && block.number >= account.timelock);
        delete accounts[msg.sender];
        payable(msg.sender).transfer(account.balance);
    }

}

@obadiaa obadiaa changed the title [wip] research: mev-boost with unconditional payments mev-boost with unconditional payments May 1, 2022
@obadiaa
Copy link
Author

obadiaa commented May 1, 2022

Ty @thegostep this is what I meant to describe above!

Q for you: what if a builder sends their header to multiple relays?

@obadiaa
Copy link
Author

obadiaa commented May 1, 2022

On Q3 Is a griefing vector from proposers acceptable?:

I'd like to understand the griefing attack better. In broad terms, the attack would be the proposer securing the money from the builder's bid but ensuring the body doesn't get revealed. The incentive for them to do so would be 'MEV-stealing'.

If relays are required to release the payment when they see the signed header on the p2p network, then the chances of relays but not the builder seeing the header in time could be small enough that the griefing risk is ok?

Could the risk also be mitigated by a time window the header has to be committed by that accounts for p2p network latency?

@fradamt
Copy link

fradamt commented May 3, 2022

Some comments:

If the relay doesn't reveal the body, then the proposer gets slashed from proposing an empty execution block

The proposer would lose income but they don't get slashed for not proposing.

This means the proposer is at the mercy of the relay.

The proposer is anyway at the mercy of the relay, even with "unconditional" payment. They still need to sign a header in order to obtain the payment (at which point the proposer can't do anything else without getting slashed for double signing), and the relay can withhold the payment just like they can withhold the block in the current scheme.

Not only that, but relay withholding is not a more attributable fault in this scheme than in the current one. Currently, if a relay withholds a block for a few seconds, chances are the block won't become canonical and the proposer doesn't get paid, but detecting the (probably) malicious behavior of the relay requires being online, and anyway it's indistinguishable from just temporarily degraded network conditions, or the relay getting DDoS'ed etc... The only way to tell if a relay is malicious is to observe their behavior for a long period of time and have some metrics, and even then it's hard to be sure. With this other scheme, a relay can withhold a payment just the same, but they are forced to withhold it forever (or anyway until the funds are not available anymore), which is a very clearly detectable behavior. Nonetheless, the fault is still not clearly attributable, because the relay is supposed to withhold the payment if the proposer sends them a signed header too late for the builder to release the block, and so one anyway one would have to monitor when messages are being received to try to determine who's at fault.

The shifts the attack vector to builders, who can now be griefed by proposers who can pick a header late enough such that a builder doesn't have time to reveal the body before the appropriate time window but a relay has enough time to see it and release the payment.

Imho, this is much worse than what a malicious relay can do to proposers in the current scheme (possible worse even for proposers themselves, because this attack vector existing and being hard to mitigate means builders might want to bid less, at least for untrusted proposers). A single relay can withhold a block, but a proposer can avoid ever finding themselves in a situation where they depend on a single relay, by only signing headers which have been suggested by multiple relays (which should be the case for most headers, since builders are incentivized to send them to all relay they deem trustworthy). To accept a header, a proposer would send it over p2p, so all relays which have suggested that header (even ones which the proposer doesn't trust but the builder trusted!) are able to release the body. It's basically a 1 of N trust model for the proposer, where N is all relays which have received the corresponding body, a subset of which the proposer explicitly trusts.

@thegostep
Copy link
Contributor

Nonetheless, the fault is still not clearly attributable, because the relay is supposed to withhold the payment if the proposer sends them a signed header too late for the builder to release the block, and so one anyway one would have to monitor when messages are being received to try to determine who's at fault.

I agree, this is the main concern with this approach and seems to be a show stopper.

It's basically a 1 of N trust model for the proposer, where N is all relays which have received the corresponding body, a subset of which the proposer explicitly trusts.

Something which has been brought up in the past is that relays should gossip builder payloads amongst eachother to improve data availability. This seems dangerous to me as it creates a set of colluding relays who all see the transaction content of a builder without the builders explicitly approving it.

@fradamt
Copy link

fradamt commented May 3, 2022

Something which has been brought up in the past is that relays should gossip builder payloads amongst eachother to improve data availability. This seems dangerous to me as it creates a set of colluding relays who all see the transaction content of a builder without the builders explicitly approving it.

Personally, I think they shouldn't, and it should be considered as a violation of the principles of their relationship with builders. Nonetheless, a builder can (and likely will) share payloads with multiple relays, and I think that this "natural" redundancy should be enough to give very good guarantees in most slots

@terencechain
Copy link
Collaborator

Assuming both relay and builder don't have access to the block gossip layer. One minor downside is this will require an extra hop before gossiping the full block. I'm not sure what that looks like latency-wise.

Status duo at the start of the slot:

  1. proposer --get_header--> relay
  2. relay --header--> proposer
  3. proposer signs header
  4. proposer --get_payload--> relay
  5. relay --payload--> proposer
  6. proposer validates payload
  7. proposer gossips the block

With unconditional payment:

  1. proposer --get_header--> relay
  2. relay --header--> proposer
  3. proposer signs header
  4. proposer --get_payload--> relay
  5. relay --get_payload--> builder
  6. builder --payload--> relay
  7. relay --payload--> proposer
  8. proposer validates payload
  9. proposer gossips the block

The additional 5 and 6 may introduce additional latencies to delay 9

@obadiaa
Copy link
Author

obadiaa commented May 11, 2022

Hey @fradamt @thegostep @terencechain, thanks for your replies! I'll address each individually.

@fradamt:

With this other scheme, a relay can withhold a payment just the same, but they are forced to withhold it forever (or anyway until the funds are not available anymore), which is a very clearly detectable behavior.

What's the intuition for why the relay needs to withhold the payment forever and that it's a detectable behavior? Is that an argument in favor of the 'unconditional payment' model?

Trying to summarize your point:

  1. the 'unconditional payment' model still requires a relay (otherwise proposers can seriously attack builders which would likely lead to builders only working with trusted proposers).
  2. because it does still require a relay, proposers are still subject to withholding attacks from relays but, as opposed to block-body-withholding this payment-withholding attack is more detectable.
  3. the current system has a 1/n trust model for proposers accepting bids from relays. more specifically, a proposer can select a bid it has seen from n relays, and then would just need 1 out of n relays to reveal the body. in the 'unconditional payment' model, this 1/n property stops existing.

For 3:

  • i understand the 1/n argument but question the assumption builders will share their payloads with lots of relays. are builders really incentivized to do so? the more relays they share their blocks with, the more they are subject to potential adverse behaviour and the harder it is for them to know who the malicious party is. I think this is outside the scope of this issue and have created a new issue for it here data availability & relay misbehavior #122 🤠

  • suppose we're in the unconditional payment model and a proposer selects a bid seen by multiple relays, then either a) all relays have the money for the payment which is capital inefficient but means we preserve the 1/n property, or b) there is some way for relays to split payments but then we lose the 1/n property. does that sound accurate to you?

Lastly: afaik in-protocol PBS has unconditional payments. does that mean its design should be re-thought?

@obadiaa
Copy link
Author

obadiaa commented May 11, 2022

@terencechain :
great point! however in my mind the builder had access to the block gossip layer, why do you assume they don't?

if they did, i imagine a participating builder would essentially monitor the network for a signed header from the proposer and verify if it matches their own. in case it does, they would then gossip the body. Going something like:

  1. proposer --get_header--> relay
  2. relay --header--> proposer
  3. proposer signs header
  4. proposer --get_payload--> (p2p network)
  5. builder --payload--> (p2p network)
  6. proposer validates payload
  7. proposer gossips the block

@obadiaa
Copy link
Author

obadiaa commented May 11, 2022

@thegostep :

Something which has been brought up in the past is that relays should gossip builder payloads amongst each other to improve data availability. This seems dangerous to me as it creates a set of colluding relays who all see the transaction content of a builder without the builders explicitly approving it.

I've opened a new issue for this #122. It's clear relays shouldn't violate the preferences of builders but I think even if builders explicitly approve it - I'm not sure they'd be fully aware of what it would take to distinguish misbehavior.

@fradamt
Copy link

fradamt commented May 12, 2022

What's the intuition for why the relay needs to withhold the payment forever and that it's a detectable behavior? Is that an argument in favor of the 'unconditional payment' model?
Not forever, but at least for a while, depending on how things are set up. If they don't, the proposer gets their payment, albeit maybe a bit later than they should have. Compare this to the current model, in which the block is the payment, and the proposer only gets paid if it is released in time for it to become canonical

As far as detectability, I think I was a bit unclear. If the relay withholds a payment, it is very easy to detect that they did, because they have to withhold it for a long time, rather than for just a few seconds in the current scheme, but this does not mean that we can easily detect why they withheld it, which is crucial to determining who's at fault, from an outside perspective. This is because the relay is entrusted by builders with not releasing payments for signed headers which are received late, so them withholding it can be malicious but can also just be prescribed behavior caused by a late proposer.
All of this to say that even with unconditional payments it does not become significantly easier (or at all, I think?) to monitor relays for malicious behavior

I guess the issue really comes from the fact that payments can't be (and aren't) unconditional. The condition is that the proposer sends a signed header in time, and someone has to enforce it. In PBS, that would be a committee, and here it has to be relays

Trying to summarize your point:

  1. the 'unconditional payment' model still requires a relay (otherwise proposers can seriously attack builders which would likely lead to builders only working with trusted proposers).
  2. because it does still require a relay, proposers are still subject to withholding attacks from relays but, as opposed to block-body-withholding this payment-withholding attack is more detectable.
  3. the current system has a 1/n trust model for proposers accepting bids from relays. more specifically, a proposer can select a bid it has seen from n relays, and then would just need 1 out of n relays to reveal the body. in the 'unconditional payment' model, this 1/n property stops existing.
  1. If you didn't have a relay, either builders act as their own relays (as in, the proposer still don't get to see the payload and instead sign a header), in which case proposers can only work with trusted builders, or proposers receive full payloads and we have the problem you mentioned. Basically one of the two sides has to be trusted, and we only get to choose which one, unless we put a bilaterally trusted entity in the middle.
  2. As mentioned above, unfortunately it doesn't really help that it is detectable, because it's not necessarily malicious behavior, and detecting whether it is is still hard.
  3. I guess that whether the 1/n property keeps existing or not would depend on the implementation, one could still do this in a way where the same payment is given to multiple relays (but can only be executed once), so proposers have the same kind of guarantee. Not really better ones though, which would be the point of "unconditional" payments! They still need to trust that at least one relay is honest, and the signed header still needs to reach relays quickly enough for them to be ok with releasing the payment. The only situation in which they are a bit more protected is if relays are very slow at releasing what they have, which would be ok if it's the payment but not ok if it's the payload. So we don't gain much for the proposers, but make it much less builder-friendly.

@terencechain
Copy link
Collaborator

@terencechain : great point! however in my mind the builder had access to the block gossip layer, why do you assume they don't?

if they did, i imagine a participating builder would essentially monitor the network for a signed header from the proposer and verify if it matches their own. in case it does, they would then gossip the body. Going something like:

  1. proposer --get_header--> relay
  2. relay --header--> proposer
  3. proposer signs header
  4. proposer --get_payload--> (p2p network)
  5. builder --payload--> (p2p network)
  6. proposer validates payload
  7. proposer gossips the block

Apology, I meant builder or relay don't have gossip implemented today, but they do plan to have it one day? The above interaction will work if they have gossip layers. There are also a few options.

The proposer and builder need to implement a payload header and payload gossip protocol in this option. We don't have this today. One pro is proposer gets to validate the payload with local EE before gossiping out the block although this endures extra latency for everyone else to receive the block

proposer --get_payload--> (p2p network)
builder --payload--> (p2p network)
proposer validates payload
proposer gossips the block

In this option, the proposer will gossip out the signed blinded block, and when the builder sees it, the builder will gossip the signed full block. It's faster and doesn't require additional gossip implementations for payload. The trade-off is proposer will not be able to validate the block using local EE before gossip

proposer --signed_blinded_block--> (p2p network)
builder --signed_block--> (p2p network)

@obadiaa
Copy link
Author

obadiaa commented Jun 27, 2022

Hey! Thank you all for your comments and the several conversations on and offline on this topic.

My conclusion on this issue is the following: 'unconditional payments' is unlikely to be a good alternative because:

  • it has higher capital requirements on builders, not only from the need to escrow payments but also in order to preserve the same neat 1/n security property we have in the current system for proposers to mitigate against body-withdrawal issues (malicious or not). In particular, a builder would have to send payments to each relay it sends it body to. (thanks @fradamt !)
  • it likely has additional communication overhead or potential trouble linking up communication pipes appropriately. such problems could rear their head in p2p network delays, generally overhead from additional message passing between different entities or induced latency in other parts of the pbs workflow. (thanks @terencechain !)

on the plus side, such a design would

  • provide better privacy guarantees for builders since they wouldn't have to reveal block bodies to relays.
  • make byzantine behavior of relays more easily detectable because payments have to be withheld for longer, although it would still be difficult to determine whether it is a malicious relay, a relay performance issue, or a malicious proposer/proposer performance issue.

It's also worth noting:

  • the proposers would not directly benefit from this system. they could potentially indirectly benefit from it if it incentivizes builders to submit bodies they wouldn't have submitted otherwise, or if it helps mitigate against builder-relay integrations for privacy reasons.
  • the griefing attack vector on the builders still exists.
  • the payments in in-protocol PBS aren't unconditional, they still rely on a committee which replaces the relay. this is a mistake I made in the initial statement of the problem.

One potential avenue to explore is that this system could still exist in the proposer-builder abstraction, and it could be a service offered by relays to attract builders who are privacy-sensitive although it would add technical complexity to the system that would need to be understood better (payments, additional message passing, impact on the pbs workflow).

I consider this issue closed for now but will keep it open for any additional comments from y'all, thanks again!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
research 💡 Research topic
Projects
None yet
Development

No branches or pull requests

5 participants