From cd12ec4473c0469d0ad28c0d435f41fe7085086c Mon Sep 17 00:00:00 2001 From: Devon Bear Date: Fri, 3 Nov 2023 14:46:45 -0400 Subject: [PATCH] feat(abci): add a custom process proposal (#1286) --- .mockery.yaml | 2 +- cosmos/go.mod | 1 - cosmos/go.sum | 1 - cosmos/miner/msgs.go | 78 ------------ cosmos/runtime/ante/ante.go | 2 +- cosmos/runtime/chain/abci.go | 115 ++++++++++++++++++ cosmos/runtime/chain/chain.go | 69 +++++++++++ cosmos/runtime/chain/interfaces.go | 33 +++++ cosmos/runtime/miner/abci.go | 82 +++++++++++++ cosmos/runtime/miner/interfaces.go | 53 ++++++++ cosmos/{ => runtime}/miner/miner.go | 76 +----------- cosmos/runtime/runtime.go | 23 +++- cosmos/{ => runtime}/txpool/ante.go | 0 cosmos/{ => runtime}/txpool/handler.go | 0 cosmos/{ => runtime}/txpool/handler_test.go | 4 +- cosmos/{ => runtime}/txpool/mempool.go | 0 cosmos/{ => runtime}/txpool/mempool_test.go | 2 +- .../txpool/mocks/geth_tx_pool.go | 0 .../{ => runtime}/txpool/mocks/lifecycle.go | 0 cosmos/{ => runtime}/txpool/mocks/sdk_tx.go | 0 .../txpool/mocks/subscription.go | 0 .../txpool/mocks/tx_broadcaster.go | 0 .../txpool/mocks/tx_serializer.go | 0 .../txpool/mocks/tx_sub_provider.go | 0 cosmos/x/evm/genesis_test.go | 3 +- cosmos/x/evm/keeper/abci.go | 2 +- cosmos/x/evm/keeper/genesis.go | 5 +- cosmos/x/evm/keeper/keeper.go | 13 +- cosmos/x/evm/keeper/processor.go | 4 +- cosmos/x/evm/plugins/state/plugin.go | 7 ++ eth/core/chain_writer.go | 33 ++++- 31 files changed, 426 insertions(+), 182 deletions(-) delete mode 100644 cosmos/miner/msgs.go create mode 100644 cosmos/runtime/chain/abci.go create mode 100644 cosmos/runtime/chain/chain.go create mode 100644 cosmos/runtime/chain/interfaces.go create mode 100644 cosmos/runtime/miner/abci.go create mode 100644 cosmos/runtime/miner/interfaces.go rename cosmos/{ => runtime}/miner/miner.go (57%) rename cosmos/{ => runtime}/txpool/ante.go (100%) rename cosmos/{ => runtime}/txpool/handler.go (100%) rename cosmos/{ => runtime}/txpool/handler_test.go (97%) rename cosmos/{ => runtime}/txpool/mempool.go (100%) rename cosmos/{ => runtime}/txpool/mempool_test.go (98%) rename cosmos/{ => runtime}/txpool/mocks/geth_tx_pool.go (100%) rename cosmos/{ => runtime}/txpool/mocks/lifecycle.go (100%) rename cosmos/{ => runtime}/txpool/mocks/sdk_tx.go (100%) rename cosmos/{ => runtime}/txpool/mocks/subscription.go (100%) rename cosmos/{ => runtime}/txpool/mocks/tx_broadcaster.go (100%) rename cosmos/{ => runtime}/txpool/mocks/tx_serializer.go (100%) rename cosmos/{ => runtime}/txpool/mocks/tx_sub_provider.go (100%) diff --git a/.mockery.yaml b/.mockery.yaml index cb836e05d..1bccd82cd 100644 --- a/.mockery.yaml +++ b/.mockery.yaml @@ -8,7 +8,7 @@ packages: recursive: True with-expecter: true all: True - pkg.berachain.dev/polaris/cosmos/txpool: + pkg.berachain.dev/polaris/cosmos/runtime/txpool: config: recursive: True with-expecter: true diff --git a/cosmos/go.mod b/cosmos/go.mod index 71c74ba1a..0df1cd382 100644 --- a/cosmos/go.mod +++ b/cosmos/go.mod @@ -16,7 +16,6 @@ require ( cosmossdk.io/log v1.2.1 cosmossdk.io/math v1.1.3-rc.1 cosmossdk.io/store v1.0.0-rc.0 - cosmossdk.io/x/evidence v0.0.0-20230818115413-c402c51a1508 cosmossdk.io/x/tx v0.11.0 github.com/btcsuite/btcd v0.23.2 github.com/btcsuite/btcd/btcutil v1.1.3 diff --git a/cosmos/go.sum b/cosmos/go.sum index 8b47e6e91..01aa4eddc 100644 --- a/cosmos/go.sum +++ b/cosmos/go.sum @@ -51,7 +51,6 @@ cosmossdk.io/math v1.1.3-rc.1 h1:NebCNWDqb1MJRNfvxr4YY7d8FSYgkuB3L75K6xvM+Zo= cosmossdk.io/math v1.1.3-rc.1/go.mod h1:l2Gnda87F0su8a/7FEKJfFdJrM0JZRXQaohlgJeyQh0= cosmossdk.io/store v1.0.0-rc.0 h1:9DwOjuUYxDtYxn/REkTxGQAmxlIGfRroB35MQ8TrxF4= cosmossdk.io/store v1.0.0-rc.0/go.mod h1:FtBDOJmwtOZfmKKF65bKZbTYgS3bDNjjo3nP76dAegk= -cosmossdk.io/x/evidence v0.0.0-20230818115413-c402c51a1508 h1:R9H1lDpcPSkrLOnt6IDE38o0Wp8xE/+BAxocb0oyX4I= cosmossdk.io/x/tx v0.11.0 h1:Ak2LIC06bXqPbpMIEorkQbwVddRvRys1sL3Cjm+KPfs= cosmossdk.io/x/tx v0.11.0/go.mod h1:tzuC7JlfGivYuIO32JbvvY3Ft9s6FK1+r0/nGHiHwtM= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= diff --git a/cosmos/miner/msgs.go b/cosmos/miner/msgs.go deleted file mode 100644 index c0d3b1dd2..000000000 --- a/cosmos/miner/msgs.go +++ /dev/null @@ -1,78 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -// -// Copyright (C) 2023, Berachain Foundation. All rights reserved. -// Use of this software is govered by the Business Source License included -// in the LICENSE file of this repository and at www.mariadb.com/bsl11. -// -// ANY USE OF THE LICENSED WORK IN VIOLATION OF THIS LICENSE WILL AUTOMATICALLY -// TERMINATE YOUR RIGHTS UNDER THIS LICENSE FOR THE CURRENT AND ALL OTHER -// VERSIONS OF THE LICENSED WORK. -// -// THIS LICENSE DOES NOT GRANT YOU ANY RIGHT IN ANY TRADEMARK OR LOGO OF -// LICENSOR OR ITS AFFILIATES (PROVIDED THAT YOU MAY USE A TRADEMARK OR LOGO OF -// LICENSOR AS EXPRESSLY REQUIRED BY THIS LICENSE). -// -// TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON -// AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, -// EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND -// TITLE. - -package miner - -import ( - evidence "cosmossdk.io/x/evidence/types" - - sdk "github.com/cosmos/cosmos-sdk/types" - crisis "github.com/cosmos/cosmos-sdk/x/crisis/types" - gov "github.com/cosmos/cosmos-sdk/x/gov/types/v1" - govbeta "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" - slashing "github.com/cosmos/cosmos-sdk/x/slashing/types" - staking "github.com/cosmos/cosmos-sdk/x/staking/types" -) - -var ( - // DefaultAllowedMsgs are messages that can be submitted by external users. - DefaultAllowedMsgs = map[string]sdk.Msg{ - // crisis - "cosmos.crisis.v1beta1.MsgVerifyInvariant": &crisis.MsgVerifyInvariant{}, - "cosmos.crisis.v1beta1.MsgVerifyInvariantResponse": nil, - - // evidence - "cosmos.evidence.v1beta1.Equivocation": nil, - "cosmos.evidence.v1beta1.MsgSubmitEvidence": &evidence.MsgSubmitEvidence{}, - "cosmos.evidence.v1beta1.MsgSubmitEvidenceResponse": nil, - - // gov - "cosmos.gov.v1.MsgDeposit": &gov.MsgDeposit{}, - "cosmos.gov.v1.MsgDepositResponse": nil, - "cosmos.gov.v1.MsgVote": &gov.MsgVote{}, - "cosmos.gov.v1.MsgVoteResponse": nil, - "cosmos.gov.v1.MsgVoteWeighted": &gov.MsgVoteWeighted{}, - "cosmos.gov.v1.MsgVoteWeightedResponse": nil, - "cosmos.gov.v1beta1.MsgDeposit": &govbeta.MsgDeposit{}, - "cosmos.gov.v1beta1.MsgDepositResponse": nil, - "cosmos.gov.v1beta1.MsgVote": &govbeta.MsgVote{}, - "cosmos.gov.v1beta1.MsgVoteResponse": nil, - "cosmos.gov.v1beta1.MsgVoteWeighted": &govbeta.MsgVoteWeighted{}, - "cosmos.gov.v1beta1.MsgVoteWeightedResponse": nil, - "cosmos.gov.v1beta1.TextProposal": nil, - - // slashing - "cosmos.slashing.v1beta1.MsgUnjail": &slashing.MsgUnjail{}, - "cosmos.slashing.v1beta1.MsgUnjailResponse": nil, - - // staking - "cosmos.staking.v1beta1.MsgCreateValidator": &staking.MsgCreateValidator{}, - "cosmos.staking.v1beta1.MsgCreateValidatorResponse": nil, - "cosmos.staking.v1beta1.MsgEditValidator": &staking.MsgEditValidator{}, - "cosmos.staking.v1beta1.MsgEditValidatorResponse": nil, - - // tx - "cosmos.tx.v1beta1.Tx": nil, - - // upgrade - "cosmos.upgrade.v1beta1.CancelSoftwareUpgradeProposal": nil, - "cosmos.upgrade.v1beta1.SoftwareUpgradeProposal": nil, - } -) diff --git a/cosmos/runtime/ante/ante.go b/cosmos/runtime/ante/ante.go index 367127ab9..a2832a039 100644 --- a/cosmos/runtime/ante/ante.go +++ b/cosmos/runtime/ante/ante.go @@ -24,7 +24,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth/ante" - "pkg.berachain.dev/polaris/cosmos/txpool" + "pkg.berachain.dev/polaris/cosmos/runtime/txpool" ) // NewAnteHandler creates a new instance of AnteHandler with EjectOnRecheckTxDecorator. diff --git a/cosmos/runtime/chain/abci.go b/cosmos/runtime/chain/abci.go new file mode 100644 index 000000000..2ebc6e1c4 --- /dev/null +++ b/cosmos/runtime/chain/abci.go @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: BUSL-1.1 +// +// Copyright (C) 2023, Berachain Foundation. All rights reserved. +// Use of this software is govered by the Business Source License included +// in the LICENSE file of this repository and at www.mariadb.com/bsl11. +// +// ANY USE OF THE LICENSED WORK IN VIOLATION OF THIS LICENSE WILL AUTOMATICALLY +// TERMINATE YOUR RIGHTS UNDER THIS LICENSE FOR THE CURRENT AND ALL OTHER +// VERSIONS OF THE LICENSED WORK. +// +// THIS LICENSE DOES NOT GRANT YOU ANY RIGHT IN ANY TRADEMARK OR LOGO OF +// LICENSOR OR ITS AFFILIATES (PROVIDED THAT YOU MAY USE A TRADEMARK OR LOGO OF +// LICENSOR AS EXPRESSLY REQUIRED BY THIS LICENSE). +// +// TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON +// AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, +// EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND +// TITLE. + +package chain + +import ( + "fmt" + + storetypes "cosmossdk.io/store/types" + + abci "github.com/cometbft/cometbft/abci/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/ethereum/go-ethereum/beacon/engine" + + evmtypes "pkg.berachain.dev/polaris/cosmos/x/evm/types" + "pkg.berachain.dev/polaris/eth/core/types" +) + +func (wbc *WrappedBlockchain) ProcessProposal( + ctx sdk.Context, req *abci.RequestProcessProposal, +) (*abci.ResponseProcessProposal, error) { + var ( + err error + ) + + // We have to run the PreBlocker && BeginBlocker to get the chain into the state + // it'll be in when the EVM transaction actually runs. + if _, err = wbc.app.PreBlocker(ctx, &abci.RequestFinalizeBlock{ + Txs: req.Txs, + Time: req.Time, + Misbehavior: req.Misbehavior, + Height: req.Height, + NextValidatorsHash: req.NextValidatorsHash, + ProposerAddress: req.ProposerAddress, + }); err != nil { + return &abci.ResponseProcessProposal{ + Status: abci.ResponseProcessProposal_REJECT, + }, err + } else if _, err = wbc.app.BeginBlocker(ctx); err != nil { + return &abci.ResponseProcessProposal{ + Status: abci.ResponseProcessProposal_REJECT, + }, err + } + + ctx.GasMeter().RefundGas(ctx.GasMeter().GasConsumed(), "process proposal") + ctx.BlockGasMeter().RefundGas(ctx.BlockGasMeter().GasConsumed(), "process proposal") + ctx = ctx.WithKVGasConfig(storetypes.GasConfig{}). + WithTransientKVGasConfig(storetypes.GasConfig{}). + WithGasMeter(storetypes.NewInfiniteGasMeter()) + + // Pull an execution payload out of the proposal. + var envelope *engine.ExecutionPayloadEnvelope + for _, tx := range req.Txs { + var sdkTx sdk.Tx + sdkTx, err = wbc.app.TxDecode(tx) + if err != nil { + // should have been verified in prepare proposal, we + // ignore it for now (i.e VE extensions will fail decode). + continue + } + + protoEnvelope := sdkTx.GetMsgs()[0] + if env, ok := protoEnvelope.(*evmtypes.WrappedPayloadEnvelope); ok { + envelope = env.UnwrapPayload() + break + } + } + + // If the proposal doesn't contain an ethereum envelope, we should reject it. + if envelope == nil { + return &abci.ResponseProcessProposal{ + Status: abci.ResponseProcessProposal_REJECT, + }, fmt.Errorf("failed to find envelope in proposal") + } + + // Convert it to a block. + var block *types.Block + if block, err = engine.ExecutableDataToBlock(*envelope.ExecutionPayload, nil, nil); err != nil { + ctx.Logger().Error("failed to build evm block", "err", err) + return &abci.ResponseProcessProposal{ + Status: abci.ResponseProcessProposal_REJECT, + }, err + } + + // Insert the block into the chain. + if err = wbc.InsertBlockWithoutSetHead(ctx, block); err != nil { + ctx.Logger().Error("failed to insert block", "err", err) + return &abci.ResponseProcessProposal{ + Status: abci.ResponseProcessProposal_REJECT, + }, err + } + + return &abci.ResponseProcessProposal{ + Status: abci.ResponseProcessProposal_ACCEPT, + }, nil +} diff --git a/cosmos/runtime/chain/chain.go b/cosmos/runtime/chain/chain.go new file mode 100644 index 000000000..6f38f0dae --- /dev/null +++ b/cosmos/runtime/chain/chain.go @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: BUSL-1.1 +// +// Copyright (C) 2023, Berachain Foundation. All rights reserved. +// Use of this software is govered by the Business Source License included +// in the LICENSE file of this repository and at www.mariadb.com/bsl11. +// +// ANY USE OF THE LICENSED WORK IN VIOLATION OF THIS LICENSE WILL AUTOMATICALLY +// TERMINATE YOUR RIGHTS UNDER THIS LICENSE FOR THE CURRENT AND ALL OTHER +// VERSIONS OF THE LICENSED WORK. +// +// THIS LICENSE DOES NOT GRANT YOU ANY RIGHT IN ANY TRADEMARK OR LOGO OF +// LICENSOR OR ITS AFFILIATES (PROVIDED THAT YOU MAY USE A TRADEMARK OR LOGO OF +// LICENSOR AS EXPRESSLY REQUIRED BY THIS LICENSE). +// +// TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON +// AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, +// EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND +// TITLE. + +package chain + +import ( + "context" + + "pkg.berachain.dev/polaris/eth/core" + "pkg.berachain.dev/polaris/eth/core/types" +) + +// WrappedBlockchain is a struct that wraps the core blockchain with additional +// application context. +type WrappedBlockchain struct { + core.Blockchain // chain is the core blockchain. + app App // App is the application context. + +} + +// New creates a new instance of WrappedBlockchain with the provided core blockchain +// and application context. +func New(chain core.Blockchain, app App) *WrappedBlockchain { + return &WrappedBlockchain{Blockchain: chain, app: app} +} + +// WriteGenesisState writes the genesis state to the blockchain. +// It uses the provided context as the application context. +func (wbc *WrappedBlockchain) WriteGenesisState( + ctx context.Context, genState *core.Genesis, +) error { + wbc.PreparePlugins(ctx) + return wbc.WriteGenesisBlock(genState.ToBlock()) +} + +// InsertBlockWithoutSetHead inserts a block into the blockchain and sets +// it as the head. It uses the provided context as the application context. +func (wbc *WrappedBlockchain) InsertBlockAndSetHead( + ctx context.Context, block *types.Block, +) error { + wbc.PreparePlugins(ctx) + return wbc.Blockchain.InsertBlockAndSetHead(block) +} + +// InsertBlockWithoutSetHead inserts a block into the blockchain without setting it +// as the head. It uses the provided context as the application context. +func (wbc *WrappedBlockchain) InsertBlockWithoutSetHead( + ctx context.Context, block *types.Block, +) error { + wbc.PreparePlugins(ctx) + return wbc.Blockchain.InsertBlockWithoutSetHead(block) +} diff --git a/cosmos/runtime/chain/interfaces.go b/cosmos/runtime/chain/interfaces.go new file mode 100644 index 000000000..a60d5d6d1 --- /dev/null +++ b/cosmos/runtime/chain/interfaces.go @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: BUSL-1.1 +// +// Copyright (C) 2023, Berachain Foundation. All rights reserved. +// Use of this software is govered by the Business Source License included +// in the LICENSE file of this repository and at www.mariadb.com/bsl11. +// +// ANY USE OF THE LICENSED WORK IN VIOLATION OF THIS LICENSE WILL AUTOMATICALLY +// TERMINATE YOUR RIGHTS UNDER THIS LICENSE FOR THE CURRENT AND ALL OTHER +// VERSIONS OF THE LICENSED WORK. +// +// THIS LICENSE DOES NOT GRANT YOU ANY RIGHT IN ANY TRADEMARK OR LOGO OF +// LICENSOR OR ITS AFFILIATES (PROVIDED THAT YOU MAY USE A TRADEMARK OR LOGO OF +// LICENSOR AS EXPRESSLY REQUIRED BY THIS LICENSE). +// +// TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON +// AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, +// EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND +// TITLE. + +package chain + +import ( + abci "github.com/cometbft/cometbft/abci/types" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +type App interface { + BeginBlocker(sdk.Context) (sdk.BeginBlock, error) + PreBlocker(sdk.Context, *abci.RequestFinalizeBlock) (*sdk.ResponsePreBlock, error) + TxDecode(txBz []byte) (sdk.Tx, error) +} diff --git a/cosmos/runtime/miner/abci.go b/cosmos/runtime/miner/abci.go new file mode 100644 index 000000000..c0f979faf --- /dev/null +++ b/cosmos/runtime/miner/abci.go @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: BUSL-1.1 +// +// Copyright (C) 2023, Berachain Foundation. All rights reserved. +// Use of this software is govered by the Business Source License included +// in the LICENSE file of this repository and at www.mariadb.com/bsl11. +// +// ANY USE OF THE LICENSED WORK IN VIOLATION OF THIS LICENSE WILL AUTOMATICALLY +// TERMINATE YOUR RIGHTS UNDER THIS LICENSE FOR THE CURRENT AND ALL OTHER +// VERSIONS OF THE LICENSED WORK. +// +// THIS LICENSE DOES NOT GRANT YOU ANY RIGHT IN ANY TRADEMARK OR LOGO OF +// LICENSOR OR ITS AFFILIATES (PROVIDED THAT YOU MAY USE A TRADEMARK OR LOGO OF +// LICENSOR AS EXPRESSLY REQUIRED BY THIS LICENSE). +// +// TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON +// AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, +// EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND +// TITLE. + +// Package miner implements the Ethereum miner. +package miner + +import ( + storetypes "cosmossdk.io/store/types" + + abci "github.com/cometbft/cometbft/abci/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/ethereum/go-ethereum/common" +) + +// emptyHash is a common.Hash initialized to all zeros. +var emptyHash = common.Hash{} + +// PrepareProposal implements baseapp.PrepareProposal. +func (m *Miner) PrepareProposal( + ctx sdk.Context, req *abci.RequestPrepareProposal, +) (*abci.ResponsePrepareProposal, error) { + var ( + payloadEnvelopeBz []byte + err error + ) + + // We have to run the PreBlocker && BeginBlocker to get the chain into the state + // it'll be in when the EVM transaction actually runs. + if _, err = m.app.PreBlocker(ctx, &abci.RequestFinalizeBlock{ + Txs: req.Txs, + Time: req.Time, + Misbehavior: req.Misbehavior, + Height: req.Height, + NextValidatorsHash: req.NextValidatorsHash, + ProposerAddress: req.ProposerAddress, + }); err != nil { + return nil, err + } else if _, err = m.app.BeginBlocker(ctx); err != nil { + return nil, err + } + + ctx.GasMeter().RefundGas(ctx.GasMeter().GasConsumed(), "prepare proposal") + ctx.BlockGasMeter().RefundGas(ctx.BlockGasMeter().GasConsumed(), "prepare proposal") + ctx = ctx.WithKVGasConfig(storetypes.GasConfig{}). + WithTransientKVGasConfig(storetypes.GasConfig{}). + WithGasMeter(storetypes.NewInfiniteGasMeter()) + + // We have to prime the state plugin. + // NOTE: if buildBlock below fails, we will have a bad context set on the state plugin. + // In practice this is not a big deal, but this is something we need to address as part + // of better context management practices across Polaris. + if err = m.keeper.SetLatestQueryContext(ctx); err != nil { + return nil, err + } + + // Trigger the geth miner to build a block. + if payloadEnvelopeBz, err = m.buildBlock(ctx); err != nil { + return nil, err + } + + // Return the payload as a transaction in the proposal. + return &abci.ResponsePrepareProposal{Txs: [][]byte{payloadEnvelopeBz}}, err +} diff --git a/cosmos/runtime/miner/interfaces.go b/cosmos/runtime/miner/interfaces.go new file mode 100644 index 000000000..46c46a7ba --- /dev/null +++ b/cosmos/runtime/miner/interfaces.go @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: BUSL-1.1 +// +// Copyright (C) 2023, Berachain Foundation. All rights reserved. +// Use of this software is govered by the Business Source License included +// in the LICENSE file of this repository and at www.mariadb.com/bsl11. +// +// ANY USE OF THE LICENSED WORK IN VIOLATION OF THIS LICENSE WILL AUTOMATICALLY +// TERMINATE YOUR RIGHTS UNDER THIS LICENSE FOR THE CURRENT AND ALL OTHER +// VERSIONS OF THE LICENSED WORK. +// +// THIS LICENSE DOES NOT GRANT YOU ANY RIGHT IN ANY TRADEMARK OR LOGO OF +// LICENSOR OR ITS AFFILIATES (PROVIDED THAT YOU MAY USE A TRADEMARK OR LOGO OF +// LICENSOR AS EXPRESSLY REQUIRED BY THIS LICENSE). +// +// TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON +// AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, +// EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND +// TITLE. + +package miner + +import ( + "context" + + abci "github.com/cometbft/cometbft/abci/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/ethereum/go-ethereum/beacon/engine" + + evmkeeper "pkg.berachain.dev/polaris/cosmos/x/evm/keeper" +) + +// EnvelopeSerializer is used to convert an envelope into a byte slice that represents +// a cosmos sdk.Tx. +type ( + EnvelopeSerializer interface { + ToSdkTxBytes(*engine.ExecutionPayloadEnvelope, uint64) ([]byte, error) + } + + App interface { + BeginBlocker(sdk.Context) (sdk.BeginBlock, error) + PreBlocker(sdk.Context, *abci.RequestFinalizeBlock) (*sdk.ResponsePreBlock, error) + } + + // EVMKeeper is an interface that defines the methods needed for the EVM setup. + EVMKeeper interface { + // Setup initializes the EVM keeper. + Setup(evmkeeper.WrappedBlockchain) error + SetLatestQueryContext(context.Context) error + } +) diff --git a/cosmos/miner/miner.go b/cosmos/runtime/miner/miner.go similarity index 57% rename from cosmos/miner/miner.go rename to cosmos/runtime/miner/miner.go index 21346b4de..43d7875a0 100644 --- a/cosmos/miner/miner.go +++ b/cosmos/runtime/miner/miner.go @@ -24,57 +24,33 @@ package miner import ( "context" - storetypes "cosmossdk.io/store/types" - - abci "github.com/cometbft/cometbft/abci/types" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/ethereum/go-ethereum/beacon/engine" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/miner" - evmkeeper "pkg.berachain.dev/polaris/cosmos/x/evm/keeper" "pkg.berachain.dev/polaris/eth" + "pkg.berachain.dev/polaris/eth/core" "pkg.berachain.dev/polaris/eth/core/types" ) -// emptyHash is a common.Hash initialized to all zeros. -var emptyHash = common.Hash{} - -// EnvelopeSerializer is used to convert an envelope into a byte slice that represents -// a cosmos sdk.Tx. -type EnvelopeSerializer interface { - ToSdkTxBytes(*engine.ExecutionPayloadEnvelope, uint64) ([]byte, error) -} - -type App interface { - BeginBlocker(sdk.Context) (sdk.BeginBlock, error) - PreBlocker(sdk.Context, *abci.RequestFinalizeBlock) (*sdk.ResponsePreBlock, error) -} - -// EVMKeeper is an interface that defines the methods needed for the EVM setup. -type EVMKeeper interface { - // Setup initializes the EVM keeper. - Setup(evmkeeper.Blockchain) error - SetLatestQueryContext(context.Context) error -} - // Miner implements the baseapp.TxSelector interface. type Miner struct { eth.Miner app App + chain core.Blockchain keeper EVMKeeper serializer EnvelopeSerializer currentPayload *miner.Payload } // New produces a cosmos miner from a geth miner. -func New(gm eth.Miner, app App, keeper EVMKeeper) *Miner { +func New(gm eth.Miner, app App, keeper EVMKeeper, chain core.Blockchain) *Miner { return &Miner{ Miner: gm, keeper: keeper, app: app, + chain: chain, } } @@ -83,50 +59,6 @@ func (m *Miner) Init(serializer EnvelopeSerializer) { m.serializer = serializer } -// PrepareProposal implements baseapp.PrepareProposal. -func (m *Miner) PrepareProposal( - ctx sdk.Context, req *abci.RequestPrepareProposal, -) (*abci.ResponsePrepareProposal, error) { - var ( - payloadEnvelopeBz []byte - err error - ) - - // We have to run the PreBlocker && BeginBlocker to get the chain into the state - // it'll be in when the EVM transaction actually runs. - if _, err = m.app.PreBlocker(ctx, &abci.RequestFinalizeBlock{ - Txs: req.Txs, - Time: req.Time, - Misbehavior: req.Misbehavior, - Height: req.Height, - NextValidatorsHash: req.NextValidatorsHash, - ProposerAddress: req.ProposerAddress, - }); err != nil { - return nil, err - } else if _, err = m.app.BeginBlocker(ctx); err != nil { - return nil, err - } - - ctx.GasMeter().RefundGas(ctx.GasMeter().GasConsumed(), "prepare proposal") - ctx.BlockGasMeter().RefundGas(ctx.BlockGasMeter().GasConsumed(), "prepare proposal") - ctx = ctx.WithKVGasConfig(storetypes.GasConfig{}). - WithTransientKVGasConfig(storetypes.GasConfig{}). - WithGasMeter(storetypes.NewInfiniteGasMeter()) - - // We have to prime the state plugin. - if err = m.keeper.SetLatestQueryContext(ctx); err != nil { - return nil, err - } - - // Trigger the geth miner to build a block. - if payloadEnvelopeBz, err = m.buildBlock(ctx); err != nil { - return nil, err - } - - // Return the payload as a transaction in the proposal. - return &abci.ResponsePrepareProposal{Txs: [][]byte{payloadEnvelopeBz}}, err -} - // buildBlock builds and submits a payload, it also waits for the txs // to resolve from the underying worker. func (m *Miner) buildBlock(ctx sdk.Context) ([]byte, error) { diff --git a/cosmos/runtime/runtime.go b/cosmos/runtime/runtime.go index d37058df2..7f092aac6 100644 --- a/cosmos/runtime/runtime.go +++ b/cosmos/runtime/runtime.go @@ -27,6 +27,8 @@ import ( "cosmossdk.io/log" storetypes "cosmossdk.io/store/types" + abci "github.com/cometbft/cometbft/abci/types" + "github.com/cosmos/cosmos-sdk/client" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/mempool" @@ -34,10 +36,11 @@ import ( "github.com/ethereum/go-ethereum/beacon/engine" libtx "pkg.berachain.dev/polaris/cosmos/lib/tx" - "pkg.berachain.dev/polaris/cosmos/miner" antelib "pkg.berachain.dev/polaris/cosmos/runtime/ante" + "pkg.berachain.dev/polaris/cosmos/runtime/chain" "pkg.berachain.dev/polaris/cosmos/runtime/comet" - "pkg.berachain.dev/polaris/cosmos/txpool" + "pkg.berachain.dev/polaris/cosmos/runtime/miner" + "pkg.berachain.dev/polaris/cosmos/runtime/txpool" evmkeeper "pkg.berachain.dev/polaris/cosmos/x/evm/keeper" evmtypes "pkg.berachain.dev/polaris/cosmos/x/evm/types" "pkg.berachain.dev/polaris/eth" @@ -50,16 +53,19 @@ import ( // EVMKeeper is an interface that defines the methods needed for the EVM setup. type EVMKeeper interface { // Setup initializes the EVM keeper. - Setup(evmkeeper.Blockchain) error + Setup(evmkeeper.WrappedBlockchain) error SetLatestQueryContext(context.Context) error } // CosmosApp is an interface that defines the methods needed for the Cosmos setup. type CosmosApp interface { SetPrepareProposal(sdk.PrepareProposalHandler) + SetProcessProposal(sdk.ProcessProposalHandler) SetMempool(mempool.Mempool) SetAnteHandler(sdk.AnteHandler) - miner.App + TxDecode(txBz []byte) (sdk.Tx, error) + BeginBlocker(sdk.Context) (sdk.BeginBlock, error) + PreBlocker(sdk.Context, *abci.RequestFinalizeBlock) (*sdk.ResponsePreBlock, error) } // Polaris is a struct that wraps the Polaris struct from the polar package. @@ -71,6 +77,9 @@ type Polaris struct { WrappedMiner *miner.Miner // WrappedTxPool is a wrapped version of the Mempool component. WrappedTxPool *txpool.Mempool + // WrappedBlockchain is a wrapped version of the Blockchain component. + WrappedBlockchain *chain.WrappedBlockchain + // logger is the underlying logger supplied by the sdk. logger log.Logger } @@ -104,12 +113,14 @@ func New( func (p *Polaris) Build(app CosmosApp, ek EVMKeeper) error { // Wrap the geth miner and txpool with the cosmos miner and txpool. p.WrappedTxPool = txpool.New(p.Blockchain(), p.TxPool()) - p.WrappedMiner = miner.New(p.Miner(), app, ek) + p.WrappedMiner = miner.New(p.Miner(), app, ek, p.Blockchain()) + p.WrappedBlockchain = chain.New(p.Blockchain(), app) app.SetMempool(p.WrappedTxPool) app.SetPrepareProposal(p.WrappedMiner.PrepareProposal) + app.SetProcessProposal(p.WrappedBlockchain.ProcessProposal) - if err := ek.Setup(p.Blockchain()); err != nil { + if err := ek.Setup(p.WrappedBlockchain); err != nil { return err } diff --git a/cosmos/txpool/ante.go b/cosmos/runtime/txpool/ante.go similarity index 100% rename from cosmos/txpool/ante.go rename to cosmos/runtime/txpool/ante.go diff --git a/cosmos/txpool/handler.go b/cosmos/runtime/txpool/handler.go similarity index 100% rename from cosmos/txpool/handler.go rename to cosmos/runtime/txpool/handler.go diff --git a/cosmos/txpool/handler_test.go b/cosmos/runtime/txpool/handler_test.go similarity index 97% rename from cosmos/txpool/handler_test.go rename to cosmos/runtime/txpool/handler_test.go index 6e31d9585..7c9073ca4 100644 --- a/cosmos/txpool/handler_test.go +++ b/cosmos/runtime/txpool/handler_test.go @@ -28,7 +28,7 @@ import ( "cosmossdk.io/log" - "pkg.berachain.dev/polaris/cosmos/txpool/mocks" + "pkg.berachain.dev/polaris/cosmos/runtime/txpool/mocks" "pkg.berachain.dev/polaris/eth/core" coretypes "pkg.berachain.dev/polaris/eth/core/types" @@ -38,7 +38,7 @@ import ( func TestTxpool(t *testing.T) { RegisterFailHandler(Fail) - RunSpecs(t, "cosmos/txpool") + RunSpecs(t, "cosmos/runtime/txpool") } var _ = Describe("", func() { diff --git a/cosmos/txpool/mempool.go b/cosmos/runtime/txpool/mempool.go similarity index 100% rename from cosmos/txpool/mempool.go rename to cosmos/runtime/txpool/mempool.go diff --git a/cosmos/txpool/mempool_test.go b/cosmos/runtime/txpool/mempool_test.go similarity index 98% rename from cosmos/txpool/mempool_test.go rename to cosmos/runtime/txpool/mempool_test.go index 0cdf156b1..ff1d30a67 100644 --- a/cosmos/txpool/mempool_test.go +++ b/cosmos/runtime/txpool/mempool_test.go @@ -27,7 +27,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" - "pkg.berachain.dev/polaris/cosmos/txpool/mocks" + "pkg.berachain.dev/polaris/cosmos/runtime/txpool/mocks" evmtypes "pkg.berachain.dev/polaris/cosmos/x/evm/types" coretypes "pkg.berachain.dev/polaris/eth/core/types" diff --git a/cosmos/txpool/mocks/geth_tx_pool.go b/cosmos/runtime/txpool/mocks/geth_tx_pool.go similarity index 100% rename from cosmos/txpool/mocks/geth_tx_pool.go rename to cosmos/runtime/txpool/mocks/geth_tx_pool.go diff --git a/cosmos/txpool/mocks/lifecycle.go b/cosmos/runtime/txpool/mocks/lifecycle.go similarity index 100% rename from cosmos/txpool/mocks/lifecycle.go rename to cosmos/runtime/txpool/mocks/lifecycle.go diff --git a/cosmos/txpool/mocks/sdk_tx.go b/cosmos/runtime/txpool/mocks/sdk_tx.go similarity index 100% rename from cosmos/txpool/mocks/sdk_tx.go rename to cosmos/runtime/txpool/mocks/sdk_tx.go diff --git a/cosmos/txpool/mocks/subscription.go b/cosmos/runtime/txpool/mocks/subscription.go similarity index 100% rename from cosmos/txpool/mocks/subscription.go rename to cosmos/runtime/txpool/mocks/subscription.go diff --git a/cosmos/txpool/mocks/tx_broadcaster.go b/cosmos/runtime/txpool/mocks/tx_broadcaster.go similarity index 100% rename from cosmos/txpool/mocks/tx_broadcaster.go rename to cosmos/runtime/txpool/mocks/tx_broadcaster.go diff --git a/cosmos/txpool/mocks/tx_serializer.go b/cosmos/runtime/txpool/mocks/tx_serializer.go similarity index 100% rename from cosmos/txpool/mocks/tx_serializer.go rename to cosmos/runtime/txpool/mocks/tx_serializer.go diff --git a/cosmos/txpool/mocks/tx_sub_provider.go b/cosmos/runtime/txpool/mocks/tx_sub_provider.go similarity index 100% rename from cosmos/txpool/mocks/tx_sub_provider.go rename to cosmos/runtime/txpool/mocks/tx_sub_provider.go diff --git a/cosmos/x/evm/genesis_test.go b/cosmos/x/evm/genesis_test.go index 702672fc9..ab654aa37 100644 --- a/cosmos/x/evm/genesis_test.go +++ b/cosmos/x/evm/genesis_test.go @@ -32,6 +32,7 @@ import ( "github.com/ethereum/go-ethereum/consensus/beacon" "pkg.berachain.dev/polaris/cosmos/config" + "pkg.berachain.dev/polaris/cosmos/runtime/chain" testutil "pkg.berachain.dev/polaris/cosmos/testutil" "pkg.berachain.dev/polaris/cosmos/x/evm" "pkg.berachain.dev/polaris/cosmos/x/evm/keeper" @@ -83,7 +84,7 @@ var _ = Describe("Genesis", func() { cfg, ) err = k.Setup( - core.NewChain(k.Host, params.DefaultChainConfig, beacon.NewFaker()), + chain.New(core.NewChain(k.Host, params.DefaultChainConfig, beacon.NewFaker()), nil), ) Expect(err).ToNot(HaveOccurred()) diff --git a/cosmos/x/evm/keeper/abci.go b/cosmos/x/evm/keeper/abci.go index ea1a2b513..66fe83dae 100644 --- a/cosmos/x/evm/keeper/abci.go +++ b/cosmos/x/evm/keeper/abci.go @@ -32,7 +32,7 @@ func (k *Keeper) EndBlock(ctx context.Context) error { // Verify that the EVM block was written. // TODO: Set/GetHead to set and get the canonical head. blockNum := uint64(sdk.UnwrapSDKContext(ctx).BlockHeight()) - block := k.chain.GetBlockByNumber(blockNum) + block := k.wrappedChain.GetBlockByNumber(blockNum) if block == nil { return fmt.Errorf( "evm block %d failed to process", blockNum, diff --git a/cosmos/x/evm/keeper/genesis.go b/cosmos/x/evm/keeper/genesis.go index 22816847e..f8df65efd 100644 --- a/cosmos/x/evm/keeper/genesis.go +++ b/cosmos/x/evm/keeper/genesis.go @@ -31,7 +31,7 @@ import ( // InitGenesis is called during the InitGenesis. func (k *Keeper) InitGenesis(ctx sdk.Context, genState *core.Genesis) error { // TODO: Feels jank as fuck lol, but it works. - genState.Config = k.chain.Config() + genState.Config = k.wrappedChain.Config() // Initialize all the plugins. for _, plugin := range k.Host.GetAllPlugins() { @@ -44,8 +44,7 @@ func (k *Keeper) InitGenesis(ctx sdk.Context, genState *core.Genesis) error { } // Insert to chain. - k.chain.PreparePlugins(ctx.WithEventManager(sdk.NewEventManager())) - return k.chain.WriteGenesisBlock(genState.ToBlock()) + return k.wrappedChain.WriteGenesisState(ctx, genState) } // ExportGenesis returns the exported genesis state. diff --git a/cosmos/x/evm/keeper/keeper.go b/cosmos/x/evm/keeper/keeper.go index f1bd832d3..8fb036d9f 100644 --- a/cosmos/x/evm/keeper/keeper.go +++ b/cosmos/x/evm/keeper/keeper.go @@ -31,16 +31,17 @@ import ( "pkg.berachain.dev/polaris/cosmos/config" "pkg.berachain.dev/polaris/cosmos/x/evm/plugins/state" "pkg.berachain.dev/polaris/cosmos/x/evm/types" + "pkg.berachain.dev/polaris/eth/core" ethprecompile "pkg.berachain.dev/polaris/eth/core/precompile" coretypes "pkg.berachain.dev/polaris/eth/core/types" "pkg.berachain.dev/polaris/eth/params" ) -type Blockchain interface { +type WrappedBlockchain interface { PreparePlugins(context.Context) Config() *params.ChainConfig - WriteGenesisBlock(*coretypes.Block) error - InsertBlockAndSetHead(*coretypes.Block) error + WriteGenesisState(context.Context, *core.Genesis) error + InsertBlockAndSetHead(context.Context, *coretypes.Block) error GetBlockByNumber(uint64) *coretypes.Block } @@ -49,7 +50,7 @@ type Keeper struct { *Host // provider is the struct that houses the Polaris EVM. - chain Blockchain + wrappedChain WrappedBlockchain } // NewKeeper creates new instances of the polaris Keeper. @@ -72,8 +73,8 @@ func NewKeeper( } } -func (k *Keeper) Setup(chain Blockchain) error { - k.chain = chain +func (k *Keeper) Setup(wrappedChain WrappedBlockchain) error { + k.wrappedChain = wrappedChain return k.SetupPrecompiles() } diff --git a/cosmos/x/evm/keeper/processor.go b/cosmos/x/evm/keeper/processor.go index 960809d7a..19823ea93 100644 --- a/cosmos/x/evm/keeper/processor.go +++ b/cosmos/x/evm/keeper/processor.go @@ -65,8 +65,8 @@ func (k *Keeper) ProcessPayloadEnvelope( // Prepare should be moved to the blockchain? THIS IS VERY HOOD YES NEEDS TO BE MOVED. ctx = sCtx.WithKVGasConfig(storetypes.GasConfig{}). WithTransientKVGasConfig(storetypes.GasConfig{}) - k.chain.PreparePlugins(ctx) - if err = k.chain.InsertBlockAndSetHead(block); err != nil { + + if err = k.wrappedChain.InsertBlockAndSetHead(ctx, block); err != nil { return nil, err } diff --git a/cosmos/x/evm/plugins/state/plugin.go b/cosmos/x/evm/plugins/state/plugin.go index ab4c9b470..d3102279c 100644 --- a/cosmos/x/evm/plugins/state/plugin.go +++ b/cosmos/x/evm/plugins/state/plugin.go @@ -528,6 +528,13 @@ func (p *plugin) StateAtBlockNumber(number uint64) (core.StatePlugin, error) { return nil, errors.New("no query context function set in host chain") } + // NOTE: the PreBlock and BeginBlock state changes will not have been applied to the state + // at this point. + // This is kind of bad since queries from JSON-RPC (i.e eth_call estimateGas etc.) + // won't be able to do this + // ontop of a state that has these updates for the block. + // TODO: Fix this. + int64Number := int64(number) // TODO: the GTE may be hiding a larger issue with the timing of the NewHead channel stuff. // Investigate and hopefully remove this GTE. diff --git a/eth/core/chain_writer.go b/eth/core/chain_writer.go index fd2e196ef..78230fa32 100644 --- a/eth/core/chain_writer.go +++ b/eth/core/chain_writer.go @@ -36,6 +36,7 @@ type ChainWriter interface { LoadLastState(context.Context, uint64) error WriteGenesisBlock(block *types.Block) error InsertBlockAndSetHead(block *types.Block) error + InsertBlockWithoutSetHead(block *types.Block) error WriteBlockAndSetHead(block *types.Block, receipts []*types.Receipt, logs []*types.Log, state state.StateDB, emitHeadEvent bool) (status core.WriteStatus, err error) } @@ -50,13 +51,25 @@ func (bc *blockchain) WriteGenesisBlock(block *types.Block) error { return err } -// InsertBlockAndSetHead inserts a block into the blockchain and sets the head. -func (bc *blockchain) InsertBlockAndSetHead(block *types.Block) error { +// InsertBlockWithoutSetHead inserts a block into the blockchain without setting it as the head. +func (bc *blockchain) InsertBlockWithoutSetHead(block *types.Block) error { + // Call the private method to insert the block without setting it as the head. + _, _, err := bc.insertBlockWithoutSetHead(block) + // Return any error that might have occurred. + return err +} + +// insertBlockWithoutSetHead inserts a block into the blockchain without setting it as the head. +func (bc *blockchain) insertBlockWithoutSetHead( + block *types.Block, +) ([]*types.Receipt, []*types.Log, error) { // Validate that we are about to insert a valid block. + // If the block number is greater than 1, + // it means it's not the genesis block and needs to be validated. TODO kinda hood. if block.NumberU64() > 1 { // TODO DIAGNOSE if err := bc.validator.ValidateBody(block); err != nil { log.Error("invalid block body", "err", err) - return err + return nil, nil, err } } @@ -64,22 +77,30 @@ func (bc *blockchain) InsertBlockAndSetHead(block *types.Block) error { receipts, logs, usedGas, err := bc.processor.Process(block, bc.statedb, *bc.vmConfig) if err != nil { log.Error("failed to process block", "num", block.NumberU64(), "err", err) - return err + return nil, nil, err } // ValidateState validates the statedb post block processing. if err = bc.validator.ValidateState(block, bc.statedb, receipts, usedGas); err != nil { log.Error("invalid state after processing block", "num", block.NumberU64(), "err", err) - return err + return nil, nil, err } + return receipts, logs, nil +} + +// InsertBlockAndSetHead inserts a block into the blockchain and sets the head. +func (bc *blockchain) InsertBlockAndSetHead(block *types.Block) error { + receipts, logs, err := bc.insertBlockWithoutSetHead(block) + if err != nil { + return err + } // We can just immediately finalize the block. It's okay in this context. if _, err = bc.WriteBlockAndSetHead( block, receipts, logs, nil, true); err != nil { log.Error("failed to write block", "num", block.NumberU64(), "err", err) return err } - return err }