// Copyright 2024 The Erigon Authors
// This file is part of Erigon.
//
// Erigon is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Erigon is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with Erigon. If not, see <http://www.gnu.org/licenses/>.

package cltypes

import (
	"bytes"
	"encoding/json"
	"fmt"

	"github.com/holiman/uint256"

	"github.com/erigontech/erigon/cl/clparams"
	"github.com/erigontech/erigon/cl/cltypes/solid"
	"github.com/erigontech/erigon/cl/merkle_tree"
	ssz2 "github.com/erigontech/erigon/cl/ssz"
	"github.com/erigontech/erigon/common"
	"github.com/erigontech/erigon/common/crypto"
	"github.com/erigontech/erigon/common/empty"
	"github.com/erigontech/erigon/common/log/v3"
	"github.com/erigontech/erigon/execution/protocol/rules/merge"
	"github.com/erigontech/erigon/execution/types"
)

// ETH1Block represents a block structure CL-side.
type Eth1Block struct {
	ParentHash    common.Hash      `json:"parent_hash"`
	FeeRecipient  common.Address   `json:"fee_recipient"`
	StateRoot     common.Hash      `json:"state_root"`
	ReceiptsRoot  common.Hash      `json:"receipts_root"`
	LogsBloom     types.Bloom      `json:"logs_bloom"`
	PrevRandao    common.Hash      `json:"prev_randao"`
	BlockNumber   uint64           `json:"block_number,string"`
	GasLimit      uint64           `json:"gas_limit,string"`
	GasUsed       uint64           `json:"gas_used,string"`
	Time          uint64           `json:"timestamp,string"`
	Extra         *solid.ExtraData `json:"extra_data"`
	BaseFeePerGas common.Hash      `json:"base_fee_per_gas"`
	// Extra fields
	BlockHash       common.Hash                 `json:"block_hash"`
	Transactions    *solid.TransactionsSSZ      `json:"transactions"`
	Withdrawals     *solid.ListSSZ[*Withdrawal] `json:"withdrawals,omitempty"`
	BlobGasUsed     uint64                      `json:"blob_gas_used,string"`
	ExcessBlobGas   uint64                      `json:"excess_blob_gas,string"`
	BlockAccessList *solid.ByteListSSZ          `json:"block_access_list,omitempty"` // [New in Gloas:EIP7928] ByteList[MAX_BYTES_PER_TRANSACTION]
	SlotNumber      uint64                      `json:"slot_number,string"`          // [New in Gloas:EIP7843]
	// internals
	version   clparams.StateVersion
	beaconCfg *clparams.BeaconChainConfig
}

// NewEth1Block creates a new Eth1Block.
func NewEth1Block(version clparams.StateVersion, beaconCfg *clparams.BeaconChainConfig) *Eth1Block {
	b := &Eth1Block{
		version:   version,
		beaconCfg: beaconCfg,
	}
	if version >= clparams.GloasVersion {
		b.BlockAccessList = solid.NewByteListSSZ(beaconCfg.MaxBytesPerTransaction)
	}
	return b
}

// NewEth1BlockFromExecutionHeader creates a genesis-style Eth1Block from an
// Eth1Header. This keeps the field mapping in sync across forks — any new field
// added to Eth1Header is automatically picked up here.
func NewEth1BlockFromExecutionHeader(header *Eth1Header, version clparams.StateVersion, beaconCfg *clparams.BeaconChainConfig) *Eth1Block {
	block := NewEth1Block(version, beaconCfg)
	block.ParentHash = header.ParentHash
	block.FeeRecipient = header.FeeRecipient
	block.StateRoot = header.StateRoot
	block.ReceiptsRoot = header.ReceiptsRoot
	block.LogsBloom = header.LogsBloom
	block.PrevRandao = header.PrevRandao
	block.BlockNumber = header.BlockNumber
	block.GasLimit = header.GasLimit
	block.GasUsed = header.GasUsed
	block.Time = header.Time
	block.BaseFeePerGas = header.BaseFeePerGas
	block.BlockHash = header.BlockHash
	if header.Extra != nil {
		block.Extra = solid.NewExtraData()
		block.Extra.SetBytes(header.Extra.Bytes())
	}
	block.Transactions = &solid.TransactionsSSZ{}
	if version >= clparams.CapellaVersion {
		block.Withdrawals = solid.NewStaticListSSZ[*Withdrawal](int(beaconCfg.MaxWithdrawalsPerPayload), 44)
	}
	if version >= clparams.DenebVersion {
		block.BlobGasUsed = header.BlobGasUsed
		block.ExcessBlobGas = header.ExcessBlobGas
	}
	if version >= clparams.GloasVersion {
		// BlockAccessList is initialized empty because Eth1Header only stores the
		// BlockAccessListRoot (hash), not the raw bytes. This constructor is used
		// for genesis block creation where the access list is genuinely empty.
		block.BlockAccessList = solid.NewByteListSSZ(beaconCfg.MaxBytesPerTransaction)
		block.SlotNumber = header.SlotNumber
	}
	return block
}

// NewEth1BlockFromHeaderAndBody with given header/body.
func NewEth1BlockFromHeaderAndBody(header *types.Header, body *types.RawBody, beaconCfg *clparams.BeaconChainConfig) *Eth1Block {
	baseFeeBytes := header.BaseFee.Bytes()
	for i, j := 0, len(baseFeeBytes)-1; i < j; i, j = i+1, j-1 {
		baseFeeBytes[i], baseFeeBytes[j] = baseFeeBytes[j], baseFeeBytes[i]
	}
	var baseFee32 [32]byte
	copy(baseFee32[:], baseFeeBytes)

	extra := solid.NewExtraData()
	extra.SetBytes(header.Extra)
	block := &Eth1Block{
		ParentHash:    header.ParentHash,
		FeeRecipient:  header.Coinbase,
		StateRoot:     header.Root,
		ReceiptsRoot:  header.ReceiptHash,
		LogsBloom:     header.Bloom,
		PrevRandao:    header.MixDigest,
		BlockNumber:   header.Number.Uint64(),
		GasLimit:      header.GasLimit,
		GasUsed:       header.GasUsed,
		Time:          header.Time,
		Extra:         extra,
		BaseFeePerGas: baseFee32,
		BlockHash:     header.Hash(),
		Transactions:  solid.NewTransactionsSSZFromTransactions(body.Transactions),
		Withdrawals:   solid.NewStaticListSSZFromList(convertExecutionWithdrawalsToConsensusWithdrawals(body.Withdrawals), int(beaconCfg.MaxWithdrawalsPerPayload), 44),
		beaconCfg:     beaconCfg,
	}

	if header.BlobGasUsed != nil && header.ExcessBlobGas != nil {
		block.BlobGasUsed = *header.BlobGasUsed
		block.ExcessBlobGas = *header.ExcessBlobGas
		block.version = clparams.DenebVersion
	} else if header.WithdrawalsHash != nil {
		block.version = clparams.CapellaVersion
	} else {
		block.version = clparams.BellatrixVersion
	}

	if header.SlotNumber != nil {
		// BlockAccessList is initialized empty here because types.RawBody does not
		// carry the block access list bytes (only types.RawBlock does). In production,
		// GLOAS execution payloads arrive via the Engine API and are populated through
		// SSZ decoding, not this constructor. This function is currently only called
		// from test code.
		block.BlockAccessList = solid.NewByteListSSZ(beaconCfg.MaxBytesPerTransaction)
		block.SlotNumber = *header.SlotNumber
		block.version = clparams.GloasVersion
	}
	return block
}

func (*Eth1Block) Static() bool {
	return false
}

func (b *Eth1Block) MarshalJSON() ([]byte, error) {
	baseFeePerGas := uint256.NewInt(0).SetBytes32(b.BaseFeePerGas[:])
	baseFeePerGas.ReverseBytes(baseFeePerGas)

	// Ensure withdrawals is never nil for Capella+ (spec requires empty array, not absent)
	withdrawals := b.Withdrawals
	if withdrawals == nil && b.version >= clparams.CapellaVersion {
		withdrawals = solid.NewStaticListSSZ[*Withdrawal](0, 44)
	}

	type bellatrixPayload struct {
		ParentHash    common.Hash            `json:"parent_hash"`
		FeeRecipient  common.Address         `json:"fee_recipient"`
		StateRoot     common.Hash            `json:"state_root"`
		ReceiptsRoot  common.Hash            `json:"receipts_root"`
		LogsBloom     types.Bloom            `json:"logs_bloom"`
		PrevRandao    common.Hash            `json:"prev_randao"`
		BlockNumber   uint64                 `json:"block_number,string"`
		GasLimit      uint64                 `json:"gas_limit,string"`
		GasUsed       uint64                 `json:"gas_used,string"`
		Time          uint64                 `json:"timestamp,string"`
		Extra         *solid.ExtraData       `json:"extra_data"`
		BaseFeePerGas string                 `json:"base_fee_per_gas"`
		BlockHash     common.Hash            `json:"block_hash"`
		Transactions  *solid.TransactionsSSZ `json:"transactions"`
	}
	base := bellatrixPayload{
		ParentHash:    b.ParentHash,
		FeeRecipient:  b.FeeRecipient,
		StateRoot:     b.StateRoot,
		ReceiptsRoot:  b.ReceiptsRoot,
		LogsBloom:     b.LogsBloom,
		PrevRandao:    b.PrevRandao,
		BlockNumber:   b.BlockNumber,
		GasLimit:      b.GasLimit,
		GasUsed:       b.GasUsed,
		Time:          b.Time,
		Extra:         b.Extra,
		BaseFeePerGas: baseFeePerGas.Dec(),
		BlockHash:     b.BlockHash,
		Transactions:  b.Transactions,
	}

	if b.version >= clparams.GloasVersion {
		return json.Marshal(struct {
			bellatrixPayload
			Withdrawals     *solid.ListSSZ[*Withdrawal] `json:"withdrawals"`
			BlobGasUsed     uint64                      `json:"blob_gas_used,string"`
			ExcessBlobGas   uint64                      `json:"excess_blob_gas,string"`
			BlockAccessList *solid.ByteListSSZ          `json:"block_access_list"`
			SlotNumber      uint64                      `json:"slot_number,string"`
		}{
			bellatrixPayload: base,
			Withdrawals:      withdrawals,
			BlobGasUsed:      b.BlobGasUsed,
			ExcessBlobGas:    b.ExcessBlobGas,
			BlockAccessList:  b.BlockAccessList,
			SlotNumber:       b.SlotNumber,
		})
	}
	if b.version >= clparams.DenebVersion {
		return json.Marshal(struct {
			bellatrixPayload
			Withdrawals   *solid.ListSSZ[*Withdrawal] `json:"withdrawals"`
			BlobGasUsed   uint64                      `json:"blob_gas_used,string"`
			ExcessBlobGas uint64                      `json:"excess_blob_gas,string"`
		}{
			bellatrixPayload: base,
			Withdrawals:      withdrawals,
			BlobGasUsed:      b.BlobGasUsed,
			ExcessBlobGas:    b.ExcessBlobGas,
		})
	}
	if b.version >= clparams.CapellaVersion {
		return json.Marshal(struct {
			bellatrixPayload
			Withdrawals *solid.ListSSZ[*Withdrawal] `json:"withdrawals"`
		}{
			bellatrixPayload: base,
			Withdrawals:      withdrawals,
		})
	}
	return json.Marshal(base)
}

func (b *Eth1Block) UnmarshalJSON(data []byte) error {
	var aux struct {
		ParentHash      common.Hash                 `json:"parent_hash"`
		FeeRecipient    common.Address              `json:"fee_recipient"`
		StateRoot       common.Hash                 `json:"state_root"`
		ReceiptsRoot    common.Hash                 `json:"receipts_root"`
		LogsBloom       types.Bloom                 `json:"logs_bloom"`
		PrevRandao      common.Hash                 `json:"prev_randao"`
		BlockNumber     uint64                      `json:"block_number,string"`
		GasLimit        uint64                      `json:"gas_limit,string"`
		GasUsed         uint64                      `json:"gas_used,string"`
		Time            uint64                      `json:"timestamp,string"`
		Extra           *solid.ExtraData            `json:"extra_data"`
		BaseFeePerGas   string                      `json:"base_fee_per_gas"`
		BlockHash       common.Hash                 `json:"block_hash"`
		Transactions    *solid.TransactionsSSZ      `json:"transactions"`
		Withdrawals     *solid.ListSSZ[*Withdrawal] `json:"withdrawals"`
		BlobGasUsed     uint64                      `json:"blob_gas_used,string"`
		ExcessBlobGas   uint64                      `json:"excess_blob_gas,string"`
		BlockAccessList *solid.ByteListSSZ          `json:"block_access_list"`
		SlotNumber      uint64                      `json:"slot_number,string"`
	}
	aux.Withdrawals = solid.NewStaticListSSZ[*Withdrawal](int(b.beaconCfg.MaxWithdrawalsPerPayload), 44)
	if b.version >= clparams.GloasVersion {
		aux.BlockAccessList = solid.NewByteListSSZ(b.beaconCfg.MaxBytesPerTransaction)
	}
	if err := json.Unmarshal(data, &aux); err != nil {
		return err
	}
	b.ParentHash = aux.ParentHash
	b.FeeRecipient = aux.FeeRecipient
	b.StateRoot = aux.StateRoot
	b.ReceiptsRoot = aux.ReceiptsRoot
	b.LogsBloom = aux.LogsBloom
	b.PrevRandao = aux.PrevRandao
	b.BlockNumber = aux.BlockNumber
	b.GasLimit = aux.GasLimit
	b.GasUsed = aux.GasUsed
	b.Time = aux.Time
	b.Extra = aux.Extra
	tmp := uint256.NewInt(0)
	if err := tmp.SetFromDecimal(aux.BaseFeePerGas); err != nil {
		return err
	}
	tmp.ReverseBytes(tmp)
	tmp.WriteToArray32((*[32]byte)(&b.BaseFeePerGas))
	b.BlockHash = aux.BlockHash
	b.Transactions = aux.Transactions
	b.Withdrawals = aux.Withdrawals
	b.BlobGasUsed = aux.BlobGasUsed
	b.ExcessBlobGas = aux.ExcessBlobGas
	if aux.BlockAccessList != nil {
		b.BlockAccessList = aux.BlockAccessList
	}
	b.SlotNumber = aux.SlotNumber
	return nil
}

// PayloadHeader returns the equivalent ExecutionPayloadHeader object.
func (b *Eth1Block) PayloadHeader() (*Eth1Header, error) {
	var err error
	var transactionsRoot, withdrawalsRoot common.Hash
	// Corner case: before TTD this is 0, since all fields are 0, a 0 hash check will suffice.
	if b.BlockHash != (common.Hash{}) {
		if transactionsRoot, err = b.Transactions.HashSSZ(); err != nil {
			return nil, err
		}
	}
	if b.version >= clparams.CapellaVersion {
		withdrawalsRoot, err = b.Withdrawals.HashSSZ()
		if err != nil {
			return nil, err
		}
	}

	var blobGasUsed, excessBlobGas uint64
	if b.version >= clparams.DenebVersion {
		blobGasUsed = b.BlobGasUsed
		excessBlobGas = b.ExcessBlobGas
	}

	var blockAccessListRoot common.Hash
	var slotNumber uint64
	if b.version >= clparams.GloasVersion {
		if b.BlockAccessList != nil {
			blockAccessListRoot, err = b.BlockAccessList.HashSSZ()
			if err != nil {
				return nil, err
			}
		}
		slotNumber = b.SlotNumber
	}

	return &Eth1Header{
		ParentHash:          b.ParentHash,
		FeeRecipient:        b.FeeRecipient,
		StateRoot:           b.StateRoot,
		ReceiptsRoot:        b.ReceiptsRoot,
		LogsBloom:           b.LogsBloom,
		PrevRandao:          b.PrevRandao,
		BlockNumber:         b.BlockNumber,
		GasLimit:            b.GasLimit,
		GasUsed:             b.GasUsed,
		Time:                b.Time,
		Extra:               b.Extra,
		BaseFeePerGas:       b.BaseFeePerGas,
		BlockHash:           b.BlockHash,
		TransactionsRoot:    transactionsRoot,
		WithdrawalsRoot:     withdrawalsRoot,
		BlobGasUsed:         blobGasUsed,
		ExcessBlobGas:       excessBlobGas,
		BlockAccessListRoot: blockAccessListRoot,
		SlotNumber:          slotNumber,
		version:             b.version,
	}, nil
}

// Return minimum required buffer length to be an acceptable SSZ encoding.
func (b *Eth1Block) EncodingSizeSSZ() (size int) {
	size = 508
	if b.Extra == nil {
		b.Extra = solid.NewExtraData()
	}
	// Field (10) 'ExtraData'
	size += b.Extra.EncodingSizeSSZ()
	// Field (13) 'Transactions'
	size += b.Transactions.EncodingSizeSSZ()

	if b.version >= clparams.CapellaVersion {
		if b.Withdrawals == nil {
			b.Withdrawals = solid.NewStaticListSSZ[*Withdrawal](int(b.beaconCfg.MaxWithdrawalsPerPayload), 44)
		}
		size += b.Withdrawals.EncodingSizeSSZ() + 4
	}

	if b.version >= clparams.DenebVersion {
		size += 8 * 2 // BlobGasUsed + ExcessBlobGas
	}

	if b.version >= clparams.GloasVersion {
		if b.BlockAccessList == nil {
			b.BlockAccessList = solid.NewByteListSSZ(b.beaconCfg.MaxBytesPerTransaction)
		}
		size += b.BlockAccessList.EncodingSizeSSZ() + 4 // BlockAccessList (variable-length, +4 for offset)
		size += 8                                       // SlotNumber
	}

	return
}

// DecodeSSZ decodes the block in SSZ format.
func (b *Eth1Block) DecodeSSZ(buf []byte, version int) error {
	b.Extra = solid.NewExtraData()
	b.Transactions = solid.NewTransactionsSSZWithLimits(b.beaconCfg.MaxTransactionsPerPayload, b.beaconCfg.MaxBytesPerTransaction)
	b.Withdrawals = solid.NewStaticListSSZ[*Withdrawal](int(b.beaconCfg.MaxWithdrawalsPerPayload), 44)
	b.version = clparams.StateVersion(version)
	if b.version >= clparams.GloasVersion {
		b.BlockAccessList = solid.NewByteListSSZ(b.beaconCfg.MaxBytesPerTransaction)
	}
	return ssz2.UnmarshalSSZ(buf, version, b.getSchema()...)
}

// EncodeSSZ encodes the block in SSZ format.
func (b *Eth1Block) EncodeSSZ(dst []byte) ([]byte, error) {
	b.ensureSSZFields()
	return ssz2.MarshalSSZ(dst, b.getSchema()...)
}

// HashSSZ calculates the SSZ hash of the Eth1Block's payload header.
func (b *Eth1Block) HashSSZ() ([32]byte, error) {
	b.ensureSSZFields()
	return merkle_tree.HashTreeRoot(b.getSchema()...)
}

// ensureSSZFields lazily initializes nil slice/list fields that getSchema()
// references, so that HashSSZ/EncodeSSZ never panic on a zero-value Eth1Block.
func (b *Eth1Block) ensureSSZFields() {
	if b.Extra == nil {
		b.Extra = solid.NewExtraData()
	}
	if b.Transactions == nil {
		b.Transactions = &solid.TransactionsSSZ{}
	}
	if b.version >= clparams.CapellaVersion && b.Withdrawals == nil && b.beaconCfg != nil {
		b.Withdrawals = solid.NewStaticListSSZ[*Withdrawal](int(b.beaconCfg.MaxWithdrawalsPerPayload), 44)
	}
}

func (b *Eth1Block) getSchema() []any {
	s := []any{b.ParentHash[:], b.FeeRecipient[:], b.StateRoot[:], b.ReceiptsRoot[:], b.LogsBloom[:],
		b.PrevRandao[:], &b.BlockNumber, &b.GasLimit, &b.GasUsed, &b.Time, b.Extra, b.BaseFeePerGas[:], b.BlockHash[:], b.Transactions}
	if b.version >= clparams.CapellaVersion {
		s = append(s, b.Withdrawals)
	}
	if b.version >= clparams.DenebVersion {
		s = append(s, &b.BlobGasUsed, &b.ExcessBlobGas)
	}
	if b.version >= clparams.GloasVersion {
		s = append(s, b.BlockAccessList, &b.SlotNumber)
	}
	return s
}

// RlpHeader returns the equivalent types.Header struct with RLP-based fields.
func (b *Eth1Block) RlpHeader(parentRoot *common.Hash, executionReqHash common.Hash) (*types.Header, error) {
	// Reverse the order of the bytes in the BaseFeePerGas array and convert it to a big integer.
	reversedBaseFeePerGas := bytes.Clone(b.BaseFeePerGas[:])
	for i, j := 0, len(reversedBaseFeePerGas)-1; i < j; i, j = i+1, j-1 {
		reversedBaseFeePerGas[i], reversedBaseFeePerGas[j] = reversedBaseFeePerGas[j], reversedBaseFeePerGas[i]
	}
	baseFee := new(uint256.Int).SetBytes(reversedBaseFeePerGas)
	// If the block version is Capella or later, calculate the withdrawals hash.
	var withdrawalsHash *common.Hash
	if b.version >= clparams.CapellaVersion {
		withdrawalsHash = new(common.Hash)
		// extract all withdrawals from itearable list
		withdrawals := make([]*types.Withdrawal, b.Withdrawals.Len())
		b.Withdrawals.Range(func(idx int, w *Withdrawal, _ int) bool {
			withdrawals[idx] = convertConsensusWithdrawalToExecutionWithdrawal(w)
			return true
		})
		*withdrawalsHash = types.DeriveSha(types.Withdrawals(withdrawals))
	}
	if b.version < clparams.DenebVersion {
		log.Debug("ParentRoot is nil", "parentRoot", parentRoot, "version", b.version)
		parentRoot = nil
	}

	header := &types.Header{
		ParentHash:            b.ParentHash,
		UncleHash:             empty.UncleHash,
		Coinbase:              b.FeeRecipient,
		Root:                  b.StateRoot,
		TxHash:                types.DeriveSha(types.BinaryTransactions(b.Transactions.UnderlyngReference())),
		ReceiptHash:           b.ReceiptsRoot,
		Bloom:                 b.LogsBloom,
		Difficulty:            *merge.ProofOfStakeDifficulty,
		GasLimit:              b.GasLimit,
		GasUsed:               b.GasUsed,
		Time:                  b.Time,
		Extra:                 b.Extra.Bytes(),
		MixDigest:             b.PrevRandao,
		Nonce:                 merge.ProofOfStakeNonce,
		BaseFee:               baseFee,
		WithdrawalsHash:       withdrawalsHash,
		ParentBeaconBlockRoot: parentRoot,
	}
	header.Number.SetUint64(b.BlockNumber)

	if b.version >= clparams.DenebVersion {
		blobGasUsed := b.BlobGasUsed
		header.BlobGasUsed = &blobGasUsed
		excessBlobGas := b.ExcessBlobGas
		header.ExcessBlobGas = &excessBlobGas
	}

	if b.version >= clparams.ElectraVersion {
		header.RequestsHash = &executionReqHash
	}

	// [New in Gloas:EIP7928/EIP7843] Populate block access list hash and slot number.
	// Matches the engine API pattern in engine_server.go: keccak256(raw RLP bytes).
	if b.version >= clparams.GloasVersion {
		blockAccessListHash := new(common.Hash)
		if b.BlockAccessList == nil || b.BlockAccessList.EncodingSizeSSZ() == 0 {
			*blockAccessListHash = empty.BlockAccessListHash
		} else {
			*blockAccessListHash = crypto.Keccak256Hash(b.BlockAccessList.Bytes())
		}
		header.BlockAccessListHash = blockAccessListHash
		slotNumber := b.SlotNumber
		header.SlotNumber = &slotNumber
	}

	// If the header hash does not match the block hash, return an error.
	if header.Hash() != b.BlockHash {
		return nil, fmt.Errorf("cannot derive rlp header: mismatching hash: %s != %s, %d", header.Hash(), b.BlockHash, header.Number)
	}

	return header, nil
}

func (b *Eth1Block) Version() clparams.StateVersion {
	return b.version
}

// Body returns the equivalent raw body (only eth1 body section).
func (b *Eth1Block) Body() *types.RawBody {
	withdrawals := make([]*types.Withdrawal, b.Withdrawals.Len())
	b.Withdrawals.Range(func(idx int, w *Withdrawal, _ int) bool {
		withdrawals[idx] = convertConsensusWithdrawalToExecutionWithdrawal(w)
		return true
	})
	return &types.RawBody{
		Transactions: b.Transactions.UnderlyngReference(),
		Withdrawals:  withdrawals,
	}
}
