diff --git a/Makefile b/Makefile index cd6c395b1..8955cb4ac 100644 --- a/Makefile +++ b/Makefile @@ -51,3 +51,6 @@ help: Makefile @echo " Choose a command run in go-ethereum:" @sed -n 's/^#?//p' $< | column -t -s ':' | sort | sed -e 's/^/ /' .PHONY: help + +docker-image: + DOCKER_BUILDKIT=1 docker build --build-arg VERSION=${VERSION} . -t prof-project/prof-builder \ No newline at end of file diff --git a/beacon/engine/types.go b/beacon/engine/types.go index 49c77af05..74d2c90d5 100644 --- a/beacon/engine/types.go +++ b/beacon/engine/types.go @@ -543,7 +543,7 @@ func ExecutionPayloadV3ToBlockProf(payload *deneb.ExecutionPayload, profTxs [][] Random: common.Hash(payload.PrevRandao), Number: payload.BlockNumber, GasLimit: payload.GasLimit, - GasUsed: payload.GasUsed, + GasUsed: 0, // This is overwritten in bundle-merger Timestamp: payload.Timestamp, ExtraData: payload.ExtraData, BaseFeePerGas: payload.BaseFeePerGas.ToBig(), diff --git a/cmd/geth/config.go b/cmd/geth/config.go index 9dd0f7cdf..6c4f7faac 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -35,6 +35,7 @@ import ( "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/eth" blockvalidationapi "github.com/ethereum/go-ethereum/eth/block-validation" "github.com/ethereum/go-ethereum/eth/catalyst" "github.com/ethereum/go-ethereum/eth/ethconfig" @@ -252,6 +253,21 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) { return stack, backend } +// makeFullNode loads geth configuration and creates the Ethereum backend. +func MakeFullNodeGethPROF(ctx *cli.Context) (ethapi.Backend, *eth.Ethereum) { + stack, cfg := makeConfigNode(ctx) + if ctx.IsSet(utils.OverrideCancun.Name) { + v := ctx.Uint64(utils.OverrideCancun.Name) + cfg.Eth.OverrideCancun = &v + } + if ctx.IsSet(utils.OverrideVerkle.Name) { + v := ctx.Uint64(utils.OverrideVerkle.Name) + cfg.Eth.OverrideVerkle = &v + } + backend, eth := utils.RegisterEthService(stack, &cfg.Eth, &cfg.Builder) + return backend, eth +} + // dumpConfig is the dumpconfig command. func dumpConfig(ctx *cli.Context) error { _, cfg := makeConfigNode(ctx) diff --git a/cmd/geth/config_test.go b/cmd/geth/config_test.go new file mode 100644 index 000000000..c343bcf87 --- /dev/null +++ b/cmd/geth/config_test.go @@ -0,0 +1,37 @@ +package main + +import ( + "flag" + "testing" + + "github.com/urfave/cli/v2" +) + +func TestMakeConfigNode(t *testing.T) { + // Create a CLI context with the required flags + app := cli.NewApp() + set := flag.NewFlagSet("test", 0) + set.String("gcmode", "full", "Blockchain garbage collection mode") + set.String("crypto.kzg", "gokzg", "KZG library implementation to use") + ctx := cli.NewContext(app, set, nil) + // Set the flags + err := set.Set("gcmode", "full") + if err != nil { + t.Fatalf("Failed to set gcmode flag: %v", err) + } + err = set.Set("crypto.kzg", "gokzg") + if err != nil { + t.Fatalf("Failed to set crypto.kzg flag: %v", err) + } + // Call the function you want to test + // node, cfg := makeConfigNode(ctx) + backend, eth := MakeFullNodeGethPROF(ctx) + // Test backend + if backend == nil { + t.Fatal("Backend is nil") + } + // Test eth + if eth == nil { + t.Fatal("Ethereum object is nil") + } +} diff --git a/core/blockchain.go b/core/blockchain.go index 7d1748140..dc8e327f0 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -2454,15 +2454,37 @@ func (bc *BlockChain) SetBlockValidatorAndProcessorForTesting(v Validator, p Pro func (bc *BlockChain) SimulateProfBlock(block *types.Block, feeRecipient common.Address, registeredGasLimit uint64, vmConfig vm.Config, useBalanceDiffProfit, excludeWithdrawals bool) (*uint256.Int, *types.Header, error) { // NOTE : PBS part of the block is already validated, so no need to verify header here or check for reorgs. parent exists + // Add validation for nil block + if block == nil { + log.Error("SimulateProfBlock called with nil block") + return nil, nil, errors.New("nil block") + } + + log.Info("Starting SimulateProfBlock", + "blockNumber", block.Number(), + "blockHash", block.Hash(), + "feeRecipient", feeRecipient, + "registeredGasLimit", registeredGasLimit) + parent := bc.GetHeader(block.ParentHash(), block.NumberU64()-1) if parent == nil { - return nil, nil, errors.New("parent not found") - } - - statedb, err := bc.StateAt(parent.Root) - if err != nil { - return nil, nil, err - } + log.Error("Parent header not found", + "parentHash", block.ParentHash(), + "parentNumber", block.NumberU64()-1) + return nil, nil, errors.New("parent not found") + } + + log.Info("Found parent block", + "parentNumber", parent.Number, + "parentHash", parent.Hash(), + "parentRoot", parent.Root) + + // Get state + statedb, err := bc.StateAt(parent.Root) + if err != nil { + log.Error("Failed to get state", "root", parent.Root, "err", err) + return nil, nil, err + } // The chain importer is starting and stopping trie prefetchers. If a bad // block or other error is hit however, an early return may not properly @@ -2472,40 +2494,53 @@ func (bc *BlockChain) SimulateProfBlock(block *types.Block, feeRecipient common. feeRecipientBalanceBefore := new(uint256.Int).Set(statedb.GetBalance(feeRecipient)) - log.Info("FeeRecipient", "Before", feeRecipientBalanceBefore) - - receipts, _, usedGas, err := bc.processor.Process(block, statedb, vmConfig) - if err != nil { - return nil, nil, err - } - - // TODO : check for registeredGasLimit hrere - - feeRecipientBalanceDelta := new(uint256.Int).Set(statedb.GetBalance(feeRecipient)) - log.Info("FeeRecipient", "After", feeRecipientBalanceDelta) - feeRecipientBalanceDelta.Sub(feeRecipientBalanceDelta, feeRecipientBalanceBefore) - log.Info("FeeRecipient", "Delta", feeRecipientBalanceDelta) - if excludeWithdrawals { - for _, w := range block.Withdrawals() { - if w.Address == feeRecipient { - amount := new(uint256.Int).Mul(new(uint256.Int).SetUint64(w.Amount), uint256.NewInt(params.GWei)) - feeRecipientBalanceDelta.Sub(feeRecipientBalanceDelta, amount) - } - } - } - // create the new header - header := block.Header() - header.GasUsed = usedGas - rbloom := types.CreateBloom(receipts) - header.Bloom = rbloom - header.ReceiptHash = types.DeriveSha(receipts, trie.NewStackTrie(nil)) - header.TxHash = types.DeriveSha(block.Transactions(), trie.NewStackTrie(nil)) - header.Root = statedb.IntermediateRoot(true /* TODO: assuming that EIP158 is enabled. TODO : get from the bc.validator's config which is private currently*/) - - // TODO : handle the case when usebalancediffprofit is false - - return feeRecipientBalanceDelta, header, nil - + // First process the block + receipts, _, usedGas, err := bc.processor.Process(block, statedb, vmConfig) + if err != nil { + return nil, nil, err + } + + log.Info("SimulateProfBlock - Used Gas", "usedGas", usedGas, "registeredGasLimit", registeredGasLimit) + + // Enforce the registeredGasLimit + if usedGas > registeredGasLimit { + log.Error("Used gas exceeds registered gas limit", "usedGas", usedGas, "registeredGasLimit", registeredGasLimit) + return nil, nil, fmt.Errorf("used gas %d exceeds registered gas limit %d", usedGas, registeredGasLimit) + } + + // Create new header and update ALL fields that depend on processing results + header := types.CopyHeader(block.Header()) + header.GasUsed = usedGas + header.Bloom = types.CreateBloom(receipts) + header.Root = statedb.IntermediateRoot(true) + header.ReceiptHash = types.DeriveSha(receipts, trie.NewStackTrie(nil)) + + // Create new block with the updated header + newBlock := types.NewBlockWithHeader(header).WithBody( + block.Transactions(), + block.Uncles(), + ).WithWithdrawals(block.Withdrawals()) + + // Now validate the state with all fields properly set + if err := bc.validator.ValidateState(newBlock, statedb, receipts, usedGas); err != nil { + log.Error("SimulateProfBlock - ValidateState failed", "err", err) + return nil, nil, err + } + + // Calculate fee recipient changes + feeRecipientBalanceDelta := new(uint256.Int).Set(statedb.GetBalance(feeRecipient)) + feeRecipientBalanceDelta.Sub(feeRecipientBalanceDelta, feeRecipientBalanceBefore) + + if excludeWithdrawals { + for _, w := range newBlock.Withdrawals() { + if w.Address == feeRecipient { + amount := new(uint256.Int).Mul(new(uint256.Int).SetUint64(w.Amount), uint256.NewInt(params.GWei)) + feeRecipientBalanceDelta.Sub(feeRecipientBalanceDelta, amount) + } + } + } + + return feeRecipientBalanceDelta, header, nil } // ValidatePayload validates the payload of the block. @@ -2527,7 +2562,7 @@ func (bc *BlockChain) ValidatePayload(block *types.Block, feeRecipient common.Ad parent := bc.GetHeader(block.ParentHash(), block.NumberU64()-1) if parent == nil { - return errors.New("parent not found") + return errors.New("Validate Payload:parent not found") } calculatedGasLimit := CalcGasLimit(parent.GasLimit, registeredGasLimit) diff --git a/eth/block-validation/api.go b/eth/block-validation/api.go index d9b1782e4..101b03621 100644 --- a/eth/block-validation/api.go +++ b/eth/block-validation/api.go @@ -1,6 +1,7 @@ package blockvalidation import ( + "bytes" "encoding/hex" "encoding/json" "errors" @@ -25,6 +26,7 @@ import ( "github.com/ethereum/go-ethereum/eth/tracers/logger" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" "github.com/holiman/uint256" ) @@ -248,6 +250,15 @@ type ProfSimResp struct { ExecutionPayload *builderApiDeneb.ExecutionPayloadAndBlobsBundle } +func serializeBlock(block *types.Block) (string, error) { + var buf bytes.Buffer + err := rlp.Encode(&buf, block) + if err != nil { + return "", err + } + return hex.EncodeToString(buf.Bytes()), nil +} + func (api *BlockValidationAPI) AppendProfBundle(params *ProfSimReq) (*ProfSimResp, error) { var err error log.Info("PROF simulation called!") @@ -269,7 +280,13 @@ func (api *BlockValidationAPI) AppendProfBundle(params *ProfSimReq) (*ProfSimRes return nil, err } - profValidationResp, err := api.validateProfBlock(block, params.ProposerFeeRecipient, params.RegisteredGasLimit) + // Serialize the block + blockData, err := serializeBlock(block) + if err != nil { + return nil, fmt.Errorf("failed to serialize block: %v", err) + } + + profValidationResp, err := api.ValidateProfBlock(blockData, params.ProposerFeeRecipient, params.RegisteredGasLimit) if err != nil { log.Error("invalid payload", "hash", block.Hash, "number", block.NumberU64(), "parentHash", block.ParentHash, "err", err) return nil, err @@ -318,14 +335,49 @@ func (api *BlockValidationAPI) ValidateBuilderSubmissionV3(params *BuilderBlockV } // TODO : invalid profTransactions are not being filtered out currently, change the validateProfBlock method to pluck out the invalid transactions, blockhash would also change in that case +func (api *BlockValidationAPI) ValidateProfBlock(blockData string, proposerFeeRecipient common.Address, registeredGasLimit uint64) (*ProfSimResp, error) { + log.Info("VaPrBl: ValidateProfBlock called!") -func (api *BlockValidationAPI) ValidateProfBlock(profBlock *types.Block, proposerFeeRecipient common.Address, registeredGasLimit uint64) (*ProfSimResp, error) { - log.Info("validateProfBlock method called!") + // Decode the hex-encoded block data + blockBytes, err := hex.DecodeString(blockData) + if err != nil { + return nil, err + } + + // Deserialize the block using RLP decoding + var profBlock *types.Block + err = rlp.DecodeBytes(blockBytes, &profBlock) + if err != nil { + return nil, err + } + + log.Info("> VaPrBl: profBlock details", "profBlock", fmt.Sprintf("%+v", profBlock)) + log.Info("> VaPrBl: proposerFeeRecipient", "proposerFeeRecipient", fmt.Sprintf("%+v", proposerFeeRecipient)) + log.Info("> VaPrBl: registeredGasLimit", "registeredGasLimit", fmt.Sprintf("%+v", registeredGasLimit)) + + // Now 'block' has all the data, including the header + if profBlock.Header() == nil { + return nil, errors.New("block header is nil after deserialization") + } + + // Now safe to call profBlock.Header() + header := profBlock.Header() + blockHash := profBlock.Hash() + blockNumber := header.Number.Uint64() feeRecipient := common.BytesToAddress(proposerFeeRecipient[:]) var vmconfig vm.Config + log.Info("> VaPrBl: SimulateProfBlock params", + "block", blockHash, + "blockNumber", blockNumber, + "feeRecipient", feeRecipient, + "registeredGasLimit", registeredGasLimit, + "vmconfig", vmconfig, + "useBalanceDiff", true, + "excludeWithdrawals", true) + value, header, err := api.eth.BlockChain().SimulateProfBlock(profBlock, feeRecipient, registeredGasLimit, vmconfig, true /* prof uses balance diff*/, true /* exclude withdrawals */) if err != nil { diff --git a/go.mod b/go.mod index 06e898c16..b315ccdfe 100644 --- a/go.mod +++ b/go.mod @@ -37,7 +37,7 @@ require ( github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 github.com/gofrs/flock v0.8.1 github.com/golang-jwt/jwt/v4 v4.5.0 - github.com/golang/protobuf v1.5.3 + github.com/golang/protobuf v1.5.4 github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb github.com/google/gofuzz v1.2.0 github.com/google/uuid v1.6.0 diff --git a/go.sum b/go.sum index 82812c3ac..308461e97 100644 --- a/go.sum +++ b/go.sum @@ -237,8 +237,8 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=