-
Notifications
You must be signed in to change notification settings - Fork 233
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
Comments
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);
}
} |
Ty @thegostep this is what I meant to describe above! Q for you: what if a builder sends their header to multiple relays? |
On Q3 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? |
Some comments:
The proposer would lose income but they don't get slashed for not proposing.
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.
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. |
I agree, this is the main concern with this approach and seems to be a show stopper.
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 |
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:
With unconditional payment:
The additional 5 and 6 may introduce additional latencies to delay 9 |
Hey @fradamt @thegostep @terencechain, thanks for your replies! I'll address each individually.
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:
For 3:
Lastly: afaik in-protocol PBS has unconditional payments. does that mean its design should be re-thought? |
@terencechain : 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:
|
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. |
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. 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
|
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
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
|
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:
on the plus side, such a design would
It's also worth noting:
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! |
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:
(body, header, bid)
to relays(header,bid)
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 blockloses 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:
(header,bid)
pairs to relays(header,bid)
pairs as well the money needed to fullfill the bid(header,bids)
pairs it has for the current slot.How does this differ from the current system?
A document outlining this alternative was written by @thegostep here: https://hackmd.io/@flashbots/Skc0vuyCt
Outstanding questions
header,bid
pair to two relays? (h/t @lightclient for asking this question)We open this up for discussion and look forward to your questions and comments 🤗
The text was updated successfully, but these errors were encountered: