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

Fix OFA example #41

Closed
wants to merge 22 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
9c791f4
Add failing test for builder.
lthibault Feb 21, 2024
bb7062b
Deploy contract and call example method stub.
lthibault Feb 21, 2024
a3d4055
Pass confidential payload & decryption condition to builder contract.
lthibault Feb 21, 2024
a88007a
WIP. Port TestBlockBuildingContract from suave-geth.
lthibault Feb 22, 2024
96092c0
Use funded account.
lthibault Feb 22, 2024
c68ab60
Port framework calls to suapp-examples framework. Remove unneeded cal…
lthibault Feb 23, 2024
12809d1
Send bundle record to SUAVE builder.
lthibault Feb 23, 2024
85a8bfc
Fix insufficient gas per block.
lthibault Feb 24, 2024
b4944fa
Clean up.
lthibault Feb 24, 2024
73ce4ab
Remove unused MEVShare method.
lthibault Feb 27, 2024
329a2cf
Remove deprecated comment.
lthibault Feb 27, 2024
acf47fd
Remove unused contract addresses.
lthibault Feb 27, 2024
59d099e
Add L1Enabled field to config struct.
lthibault Feb 27, 2024
754f216
Merge branch 'main' into feat/builder
lthibault Feb 27, 2024
9f5896e
Pass framework.WithL1() option.
lthibault Feb 27, 2024
2a2ff79
Use latest version of suave-geth when testing.
lthibault Feb 27, 2024
593ef8c
fix private key config in example framework (#42)
zeroXbrock Feb 28, 2024
ad06863
Upgrade to suave-geth v0.1.3
lthibault Feb 29, 2024
a889dc8
Rename example to build-eth-block. Add README.md.
lthibault Feb 29, 2024
451eb00
Fix SUAVE-DNS whitelist and Docker reverse dial.
lthibault Feb 27, 2024
02ca930
Run example/app-ofa-private in integration tests. (fails)
lthibault Feb 29, 2024
c536530
Isolate fault by making the test pass. If you got here via git bisect,
lthibault Feb 29, 2024
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
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ lt: lint test

.PHONY: run-integration
run-integration:
go run examples/app-ofa-private/main.go
go run examples/build-eth-block/main.go
go run examples/mevm-confidential-store/main.go
go run examples/mevm-is-confidential/main.go
go run examples/onchain-callback/main.go
Expand Down
4 changes: 2 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ version: "3.8"

services:
suave-mevm:
image: flashbots/suave-geth:v0.1.2
image: flashbots/suave-geth:latest
command:
- --suave.dev
- --http.addr=0.0.0.0
- --suave.eth.remote_endpoint=http://suave-enabled-chain:8545
- --suave.eth.external-whitelist=172.17.0.1
- --suave.eth.external-whitelist=*
depends_on:
- suave-enabled-chain
ports:
Expand Down
18 changes: 9 additions & 9 deletions examples/app-ofa-private/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func main() {
if cfg.BuilderURL == "local" {
// '172.17.0.1' is the default docker host IP that a docker container can
// use to connect with a service running on the host machine.
cfg.BuilderURL = "http://172.17.0.1:1234"
cfg.BuilderURL = "http://host.docker.internal:1234"

go func() {
log.Fatal(http.ListenAndServe("0.0.0.0:1234", &relayHandlerExample{}))
Expand Down Expand Up @@ -124,16 +124,16 @@ func main() {

fmt.Println("Match event id", matchEvent.DataRecordId)

// Step 4. Emit the batch to the relayer and parse the output
fmt.Println("4. Emit batch")
// // Step 4. Emit the batch to the relayer and parse the output
// fmt.Println("4. Emit batch")

receipt = contract.SendTransaction("emitMatchDataRecordAndHint", []interface{}{cfg.BuilderURL, matchEvent.DataRecordId}, backRunBundleBytes)
bundleHash, err := decodeBundleEmittedOutput(receipt)
if err != nil {
log.Fatal(err)
}
// receipt = contract.SendTransaction("emitMatchDataRecordAndHint", []interface{}{cfg.BuilderURL, matchEvent.DataRecordId}, backRunBundleBytes)
// bundleHash, err := decodeBundleEmittedOutput(receipt)
// if err != nil {
// log.Fatal(err)
// }

fmt.Println("Bundle hash", bundleHash)
// fmt.Println("Bundle hash", bundleHash)
}

var (
Expand Down
30 changes: 30 additions & 0 deletions examples/build-eth-block/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Example Ethereum L1 Block Builder SUAPP

This example demonstrates a simple block building contract that receives bundles and returns an Ethereum L1 block.

## How to use

Start the `suave-geth` development environment

```
$ make devnet-up # from suave-geth root directory
```

Execute the deployment script:

```
$ go run main.go
```

Expected output:

```
2024/02/29 14:59:09 Test address 1: 0x675d92a306187fBC280f8Dd98465770FBAEFf8Ab
2024/02/29 14:59:09 funding account 0x675d92a306187fBC280f8Dd98465770FBAEFf8Ab with 100000000000000000
2024/02/29 14:59:09 funder 0xB5fEAfbDD752ad52Afb7e1bD2E40432A485bBB7F 115792089237316195423570985008687907853269984665640564039457584007913129639927
2024/02/29 14:59:09 transaction hash: 0x29e67f56dfd1a01ab210dcad889eba7a99028ec1bf2206b66d8054efc14e6fda
2024/02/29 14:59:09 deployed contract at 0xd594760B2A36467ec7F0267382564772D7b0b73c
2024/02/29 14:59:09 deployed contract at 0x8f21Fdd6B4f4CacD33151777A46c122797c8BF17
2024/02/29 14:59:09 transaction hash: 0x99a95bc20ea3e8c9d8a2ac21943c1c7a51599b57e4254a48f3773f923d881f2b
2024/02/29 14:59:09 transaction hash: 0xbf9ff92a229c76f59ed7d2be06297763b796c390d725fb1863e199cdb9cff1eb
```
201 changes: 201 additions & 0 deletions examples/build-eth-block/builder.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.8;

import "suave-std/suavelib/Suave.sol";

contract AnyBundleContract {
event DataRecordEvent(Suave.DataId dataId, uint64 decryptionCondition, address[] allowedPeekers);

function fetchConfidentialBundleData() public returns (bytes memory) {
require(Suave.isConfidential());

bytes memory confidentialInputs = Suave.confidentialInputs();
return abi.decode(confidentialInputs, (bytes));
}

function emitDataRecord(Suave.DataRecord calldata dataRecord) public {
emit DataRecordEvent(dataRecord.id, dataRecord.decryptionCondition, dataRecord.allowedPeekers);
}
}

contract BundleContract is AnyBundleContract {
function newBundle(
uint64 decryptionCondition,
address[] memory dataAllowedPeekers,
address[] memory dataAllowedStores
) external payable returns (bytes memory) {
require(Suave.isConfidential());

bytes memory bundleData = this.fetchConfidentialBundleData();

uint64 egp = Suave.simulateBundle(bundleData);

Suave.DataRecord memory dataRecord =
Suave.newDataRecord(decryptionCondition, dataAllowedPeekers, dataAllowedStores, "default:v0:ethBundles");

Suave.confidentialStore(dataRecord.id, "default:v0:ethBundles", bundleData);
Suave.confidentialStore(dataRecord.id, "default:v0:ethBundleSimResults", abi.encode(egp));

return emitAndReturn(dataRecord, bundleData);
}

function emitAndReturn(Suave.DataRecord memory dataRecord, bytes memory) internal virtual returns (bytes memory) {
emit DataRecordEvent(dataRecord.id, dataRecord.decryptionCondition, dataRecord.allowedPeekers);
return bytes.concat(this.emitDataRecord.selector, abi.encode(dataRecord));
}
}

contract EthBundleSenderContract is BundleContract {
string[] public builderUrls;

constructor(string[] memory builderUrls_) {
builderUrls = builderUrls_;
}

function emitAndReturn(Suave.DataRecord memory dataRecord, bytes memory bundleData)
internal
virtual
override
returns (bytes memory)
{
for (uint256 i = 0; i < builderUrls.length; i++) {
Suave.submitBundleJsonRPC(builderUrls[i], "eth_sendBundle", bundleData);
}

return BundleContract.emitAndReturn(dataRecord, bundleData);
}
}

struct EgpRecordPair {
uint64 egp; // in wei, beware overflow
Suave.DataId dataId;
}

contract EthBlockContract is AnyBundleContract {
event BuilderBoostBidEvent(Suave.DataId dataId, bytes builderBid);

function idsEqual(Suave.DataId _l, Suave.DataId _r) public pure returns (bool) {
bytes memory l = abi.encodePacked(_l);
bytes memory r = abi.encodePacked(_r);
for (uint256 i = 0; i < l.length; i++) {
if (bytes(l)[i] != r[i]) {
return false;
}
}

return true;
}

function buildFromPool(Suave.BuildBlockArgs memory blockArgs, uint64 blockHeight) public returns (bytes memory) {
require(Suave.isConfidential());

Suave.DataRecord[] memory allRecords = Suave.fetchDataRecords(blockHeight, "default:v0:ethBundles");
if (allRecords.length == 0) {
revert Suave.PeekerReverted(address(this), "no data records");
}

EgpRecordPair[] memory bidsByEGP = new EgpRecordPair[](allRecords.length);
for (uint256 i = 0; i < allRecords.length; i++) {
bytes memory simResults = Suave.confidentialRetrieve(allRecords[i].id, "default:v0:ethBundleSimResults");
uint64 egp = abi.decode(simResults, (uint64));
bidsByEGP[i] = EgpRecordPair(egp, allRecords[i].id);
}

// Bubble sort, cause why not
uint256 n = bidsByEGP.length;
for (uint256 i = 0; i < n - 1; i++) {
for (uint256 j = i + 1; j < n; j++) {
if (bidsByEGP[i].egp < bidsByEGP[j].egp) {
EgpRecordPair memory temp = bidsByEGP[i];
bidsByEGP[i] = bidsByEGP[j];
bidsByEGP[j] = temp;
}
}
}

Suave.DataId[] memory alldataIds = new Suave.DataId[](allRecords.length);
for (uint256 i = 0; i < bidsByEGP.length; i++) {
alldataIds[i] = bidsByEGP[i].dataId;
}

return buildAndEmit(blockArgs, blockHeight, alldataIds, "");
}

function buildAndEmit(
Suave.BuildBlockArgs memory blockArgs,
uint64 blockHeight,
Suave.DataId[] memory records,
string memory namespace
) public virtual returns (bytes memory) {
require(Suave.isConfidential());

(Suave.DataRecord memory blockBid, bytes memory builderBid) =
this.doBuild(blockArgs, blockHeight, records, namespace);

emit BuilderBoostBidEvent(blockBid.id, builderBid);
emit DataRecordEvent(blockBid.id, blockBid.decryptionCondition, blockBid.allowedPeekers);
return bytes.concat(this.emitBuilderBidAndBid.selector, abi.encode(blockBid, builderBid));
}

function doBuild(
Suave.BuildBlockArgs memory blockArgs,
uint64 blockHeight,
Suave.DataId[] memory records,
string memory namespace
) public returns (Suave.DataRecord memory, bytes memory) {
address[] memory allowedPeekers = new address[](2);
allowedPeekers[0] = address(this);
allowedPeekers[1] = Suave.BUILD_ETH_BLOCK;

Suave.DataRecord memory blockBid =
Suave.newDataRecord(blockHeight, allowedPeekers, allowedPeekers, "default:v0:mergedDataRecords");
Suave.confidentialStore(blockBid.id, "default:v0:mergedDataRecords", abi.encode(records));

(bytes memory builderBid, bytes memory payload) = Suave.buildEthBlock(blockArgs, blockBid.id, namespace);
Suave.confidentialStore(blockBid.id, "default:v0:builderPayload", payload); // only through this.unlock

return (blockBid, builderBid);
}

function emitBuilderBidAndBid(Suave.DataRecord memory dataRecord, bytes memory builderBid)
public
returns (Suave.DataRecord memory, bytes memory)
{
emit BuilderBoostBidEvent(dataRecord.id, builderBid);
emit DataRecordEvent(dataRecord.id, dataRecord.decryptionCondition, dataRecord.allowedPeekers);
return (dataRecord, builderBid);
}

function unlock(Suave.DataId dataId, bytes memory signedBlindedHeader) public returns (bytes memory) {
require(Suave.isConfidential());

// TODO: verify the header is correct
// TODO: incorporate protocol name
bytes memory payload = Suave.confidentialRetrieve(dataId, "default:v0:builderPayload");
return payload;
}
}

contract EthBlockBidSenderContract is EthBlockContract {
string boostRelayUrl;

constructor(string memory boostRelayUrl_) {
boostRelayUrl = boostRelayUrl_;
}

function buildAndEmit(
Suave.BuildBlockArgs memory blockArgs,
uint64 blockHeight,
Suave.DataId[] memory dataRecords,
string memory namespace
) public virtual override returns (bytes memory) {
require(Suave.isConfidential());

(Suave.DataRecord memory blockDataRecord, bytes memory builderBid) =
this.doBuild(blockArgs, blockHeight, dataRecords, namespace);
Suave.submitEthBlockToRelay(boostRelayUrl, builderBid);

emit DataRecordEvent(blockDataRecord.id, blockDataRecord.decryptionCondition, blockDataRecord.allowedPeekers);
return bytes.concat(this.emitDataRecord.selector, abi.encode(blockDataRecord));
}
}
89 changes: 89 additions & 0 deletions examples/build-eth-block/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package main

import (
"context"
"encoding/json"
"log"
"math/big"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"

"github.com/flashbots/suapp-examples/framework"
)

var buildEthBlockAddress = common.HexToAddress("0x42100001")

func main() {
fr := framework.New(framework.WithL1())

testAddr1 := framework.GeneratePrivKey()
log.Printf("Test address 1: %s", testAddr1.Address().Hex())

fundBalance := big.NewInt(100000000000000000)
maybe(fr.L1.FundAccount(testAddr1.Address(), fundBalance))

targeAddr := testAddr1.Address()
tx, err := fr.L1.SignTx(testAddr1, &types.LegacyTx{
To: &targeAddr,
Value: big.NewInt(1000),
Gas: 21000,
GasPrice: big.NewInt(6701898710),
})
maybe(err)

bundle := &types.SBundle{
Txs: types.Transactions{tx},
RevertingHashes: []common.Hash{},
}
bundleBytes, err := json.Marshal(bundle)
maybe(err)

bundleContract := fr.Suave.DeployContract("builder.sol/BundleContract.json")
ethBlockContract := fr.Suave.DeployContract("builder.sol/EthBlockContract.json")

targetBlock := currentBlock(fr).Time()

{ // Send a bundle to the builder
decryptionCondition := targetBlock + 1
allowedPeekers := []common.Address{
buildEthBlockAddress,
bundleContract.Address(),
ethBlockContract.Address()}
allowedStores := []common.Address{}
newBundleArgs := []any{
decryptionCondition,
allowedPeekers,
allowedStores}

confidentialDataBytes, err := bundleContract.Abi.Methods["fetchConfidentialBundleData"].Outputs.Pack(bundleBytes)
maybe(err)

_ = bundleContract.SendTransaction("newBundle", newBundleArgs, confidentialDataBytes)
}

{ // Signal to the builder that it's time to build a new block
payloadArgsTuple := types.BuildBlockArgs{
ProposerPubkey: []byte{0x42},
Timestamp: targetBlock + 12, // ethHead + uint64(12),
FeeRecipient: common.Address{0x42},
}

_ = ethBlockContract.SendTransaction("buildFromPool", []any{payloadArgsTuple, targetBlock + 1}, nil)
maybe(err)
}
}

func currentBlock(fr *framework.Framework) *types.Block {
n, err := fr.L1.RPC().BlockNumber(context.TODO())
maybe(err)
b, err := fr.L1.RPC().BlockByNumber(context.TODO(), new(big.Int).SetUint64(n))
maybe(err)
return b
}

func maybe(err error) {
if err != nil {
panic(err)
}
}
Loading