Skip to content

Commit

Permalink
New payment tx (ethereum#40)
Browse files Browse the repository at this point in the history
* Rework payment tx

* move env access from worker

* add builder.dry-run

* Move proposer tx from fillTransactions

* Use one flag for validation blocklist
  • Loading branch information
dvush authored and avalonche committed Feb 6, 2023
1 parent c2582cc commit 540fb04
Show file tree
Hide file tree
Showing 14 changed files with 305 additions and 122 deletions.
25 changes: 18 additions & 7 deletions builder/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package builder
import (
"context"
"errors"
blockvalidation "github.com/ethereum/go-ethereum/eth/block-validation"
"golang.org/x/time/rate"
"math/big"
_ "os"
Expand Down Expand Up @@ -49,6 +50,8 @@ type Builder struct {
ds flashbotsextra.IDatabaseService
relay IRelay
eth IEthereumService
dryRun bool
validator *blockvalidation.BlockValidationAPI
builderSecretKey *bls.SecretKey
builderPublicKey boostTypes.PublicKey
builderSigningDomain boostTypes.Domain
Expand All @@ -62,7 +65,7 @@ type Builder struct {
slotCtxCancel context.CancelFunc
}

func NewBuilder(sk *bls.SecretKey, ds flashbotsextra.IDatabaseService, relay IRelay, builderSigningDomain boostTypes.Domain, eth IEthereumService) *Builder {
func NewBuilder(sk *bls.SecretKey, ds flashbotsextra.IDatabaseService, relay IRelay, builderSigningDomain boostTypes.Domain, eth IEthereumService, dryRun bool, validator *blockvalidation.BlockValidationAPI) *Builder {
pkBytes := bls.PublicKeyFromSecretKey(sk).Compress()
pk := boostTypes.PublicKey{}
pk.FromSlice(pkBytes)
Expand All @@ -72,6 +75,8 @@ func NewBuilder(sk *bls.SecretKey, ds flashbotsextra.IDatabaseService, relay IRe
ds: ds,
relay: relay,
eth: eth,
dryRun: dryRun,
validator: validator,
builderSecretKey: sk,
builderPublicKey: pk,
builderSigningDomain: builderSigningDomain,
Expand Down Expand Up @@ -118,8 +123,6 @@ func (b *Builder) onSealedBlock(block *types.Block, bundles []types.SimulatedBun
Value: *value,
}

go b.ds.ConsumeBuiltBlock(block, bundles, &blockBidMsg)

signature, err := boostTypes.SignMessage(&blockBidMsg, b.builderSigningDomain, b.builderSecretKey)
if err != nil {
log.Error("could not sign builder bid", "err", err)
Expand All @@ -132,10 +135,18 @@ func (b *Builder) onSealedBlock(block *types.Block, bundles []types.SimulatedBun
ExecutionPayload: payload,
}

err = b.relay.SubmitBlock(&blockSubmitReq)
if err != nil {
log.Error("could not submit block", "err", err, "bundles", len(bundles))
return err
if b.dryRun {
err = b.validator.ValidateBuilderSubmissionV1(&blockSubmitReq)
if err != nil {
log.Error("could not validate block", "err", err)
}
} else {
go b.ds.ConsumeBuiltBlock(block, bundles, &blockBidMsg)
err = b.relay.SubmitBlock(&blockSubmitReq)
if err != nil {
log.Error("could not submit block", "err", err, "bundles", len(bundles))
return err
}
}

log.Info("submitted block", "slot", blockBidMsg.Slot, "value", blockBidMsg.Value.String(), "parent", blockBidMsg.ParentHash, "hash", block.Hash(), "bundles", len(bundles))
Expand Down
2 changes: 1 addition & 1 deletion builder/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ func TestOnPayloadAttributes(t *testing.T) {

testEthService := &testEthereumService{synced: true, testExecutableData: testExecutableData, testBlock: testBlock}

builder := NewBuilder(sk, flashbotsextra.NilDbService{}, &testRelay, bDomain, testEthService)
builder := NewBuilder(sk, flashbotsextra.NilDbService{}, &testRelay, bDomain, testEthService, false, nil)
builder.Start()
defer builder.Stop()

Expand Down
2 changes: 1 addition & 1 deletion builder/local_relay_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func newTestBackend(t *testing.T, forkchoiceData *beacon.ExecutableDataV1, block
beaconClient := &testBeaconClient{validator: validator}
localRelay := NewLocalRelay(sk, beaconClient, bDomain, cDomain, ForkData{}, true)
ethService := &testEthereumService{synced: true, testExecutableData: forkchoiceData, testBlock: block}
backend := NewBuilder(sk, flashbotsextra.NilDbService{}, localRelay, bDomain, ethService)
backend := NewBuilder(sk, flashbotsextra.NilDbService{}, localRelay, bDomain, ethService, false, nil)
// service := NewService("127.0.0.1:31545", backend)

backend.limiter = rate.NewLimiter(rate.Inf, 0)
Expand Down
17 changes: 16 additions & 1 deletion builder/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package builder
import (
"errors"
"fmt"
blockvalidation "github.com/ethereum/go-ethereum/eth/block-validation"
"net/http"
"os"

Expand Down Expand Up @@ -106,6 +107,7 @@ type BuilderConfig struct {
Enabled bool
EnableValidatorChecks bool
EnableLocalRelay bool
DryRun bool
BuilderSecretKey string
RelaySecretKey string
ListenAddr string
Expand All @@ -114,6 +116,7 @@ type BuilderConfig struct {
GenesisValidatorsRoot string
BeaconEndpoint string
RemoteRelayEndpoint string
ValidationBlocklist string
}

func Register(stack *node.Node, backend *eth.Ethereum, cfg *BuilderConfig) error {
Expand Down Expand Up @@ -172,6 +175,18 @@ func Register(stack *node.Node, backend *eth.Ethereum, cfg *BuilderConfig) error
return errors.New("neither local nor remote relay specified")
}

var validator *blockvalidation.BlockValidationAPI
if cfg.DryRun {
var accessVerifier *blockvalidation.AccessVerifier
if cfg.ValidationBlocklist != "" {
accessVerifier, err = blockvalidation.NewAccessVerifierFromFile(cfg.ValidationBlocklist)
if err != nil {
return fmt.Errorf("failed to load validation blocklist %w", err)
}
}
validator = blockvalidation.NewBlockValidationAPI(backend, accessVerifier)
}

// TODO: move to proper flags
var ds flashbotsextra.IDatabaseService
dbDSN := os.Getenv("FLASHBOTS_POSTGRES_DSN")
Expand All @@ -193,7 +208,7 @@ func Register(stack *node.Node, backend *eth.Ethereum, cfg *BuilderConfig) error
go bundleFetcher.Run()

ethereumService := NewEthereumService(backend)
builderBackend := NewBuilder(builderSk, ds, relay, builderSigningDomain, ethereumService)
builderBackend := NewBuilder(builderSk, ds, relay, builderSigningDomain, ethereumService, cfg.DryRun, validator)
builderService := NewService(cfg.ListenAddr, localRelay, builderBackend)

stack.RegisterAPIs([]rpc.API{
Expand Down
2 changes: 1 addition & 1 deletion cmd/geth/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) {
// Configure log filter RPC API.
filterSystem := utils.RegisterFilterAPI(stack, backend, &cfg.Eth)

if err := blockvalidationapi.Register(stack, eth, ctx); err != nil {
if err := blockvalidationapi.Register(stack, eth, ctx.String(utils.BuilderBlockValidationBlacklistSourceFilePath.Name)); err != nil {
utils.Fatalf("Failed to register the Block Validation API: %v", err)
}

Expand Down
1 change: 1 addition & 0 deletions cmd/geth/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ var (
utils.BuilderEnabled,
utils.BuilderEnableValidatorChecks,
utils.BuilderEnableLocalRelay,
utils.BuilderDryRun,
utils.BuilderSecretKey,
utils.BuilderRelaySecretKey,
utils.BuilderListenAddr,
Expand Down
8 changes: 6 additions & 2 deletions cmd/utils/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,10 @@ var (
Name: "builder.local_relay",
Usage: "Enable the local relay",
}
BuilderDryRun = &cli.BoolFlag{
Name: "builder.dry-run",
Usage: "Builder only validates blocks without submission to the relay",
}
BuilderSecretKey = &cli.StringFlag{
Name: "builder.secret_key",
Usage: "Builder key used for signing blocks",
Expand Down Expand Up @@ -1076,8 +1080,8 @@ Please note that --` + MetricsHTTPFlag.Name + ` must be set to start the server.
// Builder API flags
BuilderBlockValidationBlacklistSourceFilePath = &cli.StringFlag{
Name: "builder.validation_blacklist",
Usage: "Path to file containing blacklisted addresses, json-encoded list of strings. Default assumes CWD is repo's root",
Value: "ofac_blacklist.json",
Usage: "Path to file containing blacklisted addresses, json-encoded list of strings. Default assumes no blacklist",
Value: "",
Category: flags.EthCategory,
}
)
Expand Down
9 changes: 3 additions & 6 deletions eth/block-validation/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"math/big"
"os"

"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/beacon"
"github.com/ethereum/go-ethereum/core/types"
Expand All @@ -17,8 +16,6 @@ import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/rpc"
"github.com/urfave/cli/v2"

boostTypes "github.com/flashbots/go-boost-utils/types"
)

Expand Down Expand Up @@ -81,11 +78,11 @@ func NewAccessVerifierFromFile(path string) (*AccessVerifier, error) {
}

// Register adds catalyst APIs to the full node.
func Register(stack *node.Node, backend *eth.Ethereum, ctx *cli.Context) error {
func Register(stack *node.Node, backend *eth.Ethereum, blockValidationBlocklistFile string) error {
var accessVerifier *AccessVerifier
if ctx.IsSet(utils.BuilderBlockValidationBlacklistSourceFilePath.Name) {
if blockValidationBlocklistFile != "" {
var err error
accessVerifier, err = NewAccessVerifierFromFile(ctx.String(utils.BuilderBlockValidationBlacklistSourceFilePath.Name))
accessVerifier, err = NewAccessVerifierFromFile(blockValidationBlocklistFile)
if err != nil {
return err
}
Expand Down
4 changes: 2 additions & 2 deletions eth/block-validation/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ func TestValidateBuilderSubmissionV1(t *testing.T) {
}
blockRequest.Message.Value = boostTypes.IntToU256(190526394825529)
require.ErrorContains(t, api.ValidateBuilderSubmissionV1(blockRequest), "inaccurate payment")
blockRequest.Message.Value = boostTypes.IntToU256(190215802060530)
blockRequest.Message.Value = boostTypes.IntToU256(190526394825530)
require.NoError(t, api.ValidateBuilderSubmissionV1(blockRequest))

// TODO: test with contract calling blacklisted address
Expand Down Expand Up @@ -142,7 +142,7 @@ func TestValidateBuilderSubmissionV1(t *testing.T) {
invalidPayload.LogsBloom = boostTypes.Bloom{}
copy(invalidPayload.ReceiptsRoot[:], hexutil.MustDecode("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")[:32])
blockRequest.ExecutionPayload = invalidPayload
copy(blockRequest.Message.BlockHash[:], hexutil.MustDecode("0x65cded68b85277f489f22497731d8cece9e42f0429a250a5022a9417408f3998")[:32])
copy(blockRequest.Message.BlockHash[:], hexutil.MustDecode("0x272872d14b2a8a0454e747ed472d82d8d5ce342cfafd65fa7b77aa6de1c061d4")[:32])
require.ErrorContains(t, api.ValidateBuilderSubmissionV1(blockRequest), "could not apply tx 3", "insufficient funds for gas * price + value")
}

Expand Down
112 changes: 112 additions & 0 deletions miner/algo_common.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package miner

import (
"crypto/ecdsa"
"errors"
"fmt"
"math/big"
"sync/atomic"

Expand All @@ -20,6 +22,8 @@ const (
popTx = 2
)

var emptyCodeHash = common.HexToHash("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470")

var errInterrupt = errors.New("miner worker interrupted")

type environmentDiff struct {
Expand Down Expand Up @@ -263,3 +267,111 @@ func (envDiff *environmentDiff) commitBundle(bundle *types.SimulatedBundle, chDa
*envDiff = *tmpEnvDiff
return nil
}

func estimatePayoutTxGas(env *environment, sender, receiver common.Address, prv *ecdsa.PrivateKey, chData chainData) (uint64, bool, error) {
if codeHash := env.state.GetCodeHash(receiver); codeHash == (common.Hash{}) || codeHash == emptyCodeHash {
return params.TxGas, true, nil
}
gasLimit := env.gasPool.Gas()

balance := new(big.Int).SetBytes([]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF})
value := new(big.Int).SetBytes([]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF})

diff := newEnvironmentDiff(env)
diff.state.SetBalance(sender, balance)
receipt, err := diff.commitPayoutTx(value, sender, receiver, gasLimit, prv, chData)
if err != nil {
return 0, false, err
}
return receipt.GasUsed, false, nil
}

func insertPayoutTx(env *environment, sender, receiver common.Address, gas uint64, isEOA bool, availableFunds *big.Int, prv *ecdsa.PrivateKey, chData chainData) (*types.Receipt, error) {
diff := newEnvironmentDiff(env)
applyTx := func(gas uint64) (*types.Receipt, error) {
fee := new(big.Int).Mul(env.header.BaseFee, new(big.Int).SetUint64(gas))
amount := new(big.Int).Sub(availableFunds, fee)
if amount.Sign() < 0 {
return nil, errors.New("not enough funds available")
}
rec, err := diff.commitPayoutTx(amount, sender, receiver, gas, prv, chData)
if err != nil {
return nil, fmt.Errorf("failed to commit payment tx: %w", err)
}
if rec.Status != types.ReceiptStatusSuccessful {
return nil, fmt.Errorf("payment tx failed")
}
return rec, nil
}

if isEOA {
rec, err := applyTx(gas)
if err != nil {
return nil, err
}
diff.applyToBaseEnv()
return rec, nil
}

var (
rec *types.Receipt
err error
)
for i := 0; i < 6; i++ {
rec, err = applyTx(gas)
if err != nil {
gas += 1000
} else {
break
}
}

if err != nil {
return nil, err
}
diff.applyToBaseEnv()
return rec, nil
}

func (envDiff *environmentDiff) commitPayoutTx(amount *big.Int, sender, receiver common.Address, gas uint64, prv *ecdsa.PrivateKey, chData chainData) (*types.Receipt, error) {
senderBalance := envDiff.state.GetBalance(sender)

if gas < params.TxGas {
return nil, errors.New("not enough gas for intrinsic gas cost")
}

requiredBalance := new(big.Int).Mul(envDiff.header.BaseFee, new(big.Int).SetUint64(gas))
requiredBalance = requiredBalance.Add(requiredBalance, amount)
if requiredBalance.Cmp(senderBalance) > 0 {
return nil, errors.New("not enough balance")
}

signer := envDiff.baseEnvironment.signer
tx, err := types.SignNewTx(prv, signer, &types.DynamicFeeTx{
ChainID: chData.chainConfig.ChainID,
Nonce: envDiff.state.GetNonce(sender),
GasTipCap: new(big.Int),
GasFeeCap: envDiff.header.BaseFee,
Gas: gas,
To: &receiver,
Value: amount,
})
if err != nil {
return nil, err
}

txSender, err := signer.Sender(tx)
if err != nil {
return nil, err
}
if txSender != sender {
return nil, errors.New("incorrect sender private key")
}

receipt, _, err := envDiff.commitTx(tx, chData)
if err != nil {
return nil, err
}

return receipt, nil
}
Loading

0 comments on commit 540fb04

Please sign in to comment.