diff --git a/beacon/engine/errors.go b/beacon/engine/errors.go deleted file mode 100644 index 62773a0ea9..0000000000 --- a/beacon/engine/errors.go +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2022 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library 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. -// -// The go-ethereum library 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 the go-ethereum library. If not, see . - -package engine - -import ( - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/rpc" -) - -// EngineAPIError is a standardized error message between consensus and execution -// clients, also containing any custom error message Geth might include. -type EngineAPIError struct { - code int - msg string - err error -} - -func (e *EngineAPIError) ErrorCode() int { return e.code } -func (e *EngineAPIError) Error() string { return e.msg } -func (e *EngineAPIError) ErrorData() interface{} { - if e.err == nil { - return nil - } - return struct { - Error string `json:"err"` - }{e.err.Error()} -} - -// With returns a copy of the error with a new embedded custom data field. -func (e *EngineAPIError) With(err error) *EngineAPIError { - return &EngineAPIError{ - code: e.code, - msg: e.msg, - err: err, - } -} - -var ( - _ rpc.Error = new(EngineAPIError) - _ rpc.DataError = new(EngineAPIError) -) - -var ( - // VALID is returned by the engine API in the following calls: - // - newPayloadV1: if the payload was already known or was just validated and executed - // - forkchoiceUpdateV1: if the chain accepted the reorg (might ignore if it's stale) - VALID = "VALID" - - // INVALID is returned by the engine API in the following calls: - // - newPayloadV1: if the payload failed to execute on top of the local chain - // - forkchoiceUpdateV1: if the new head is unknown, pre-merge, or reorg to it fails - INVALID = "INVALID" - - // SYNCING is returned by the engine API in the following calls: - // - newPayloadV1: if the payload was accepted on top of an active sync - // - forkchoiceUpdateV1: if the new head was seen before, but not part of the chain - SYNCING = "SYNCING" - - // ACCEPTED is returned by the engine API in the following calls: - // - newPayloadV1: if the payload was accepted, but not processed (side chain) - ACCEPTED = "ACCEPTED" - - GenericServerError = &EngineAPIError{code: -32000, msg: "Server error"} - UnknownPayload = &EngineAPIError{code: -38001, msg: "Unknown payload"} - InvalidForkChoiceState = &EngineAPIError{code: -38002, msg: "Invalid forkchoice state"} - InvalidPayloadAttributes = &EngineAPIError{code: -38003, msg: "Invalid payload attributes"} - TooLargeRequest = &EngineAPIError{code: -38004, msg: "Too large request"} - InvalidParams = &EngineAPIError{code: -32602, msg: "Invalid parameters"} - UnsupportedFork = &EngineAPIError{code: -38005, msg: "Unsupported fork"} - - STATUS_INVALID = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: INVALID}, PayloadID: nil} - STATUS_SYNCING = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: SYNCING}, PayloadID: nil} - INVALID_TERMINAL_BLOCK = PayloadStatusV1{Status: INVALID, LatestValidHash: &common.Hash{}} -) diff --git a/beacon/engine/gen_blockparams.go b/beacon/engine/gen_blockparams.go deleted file mode 100644 index b1f01b50ff..0000000000 --- a/beacon/engine/gen_blockparams.go +++ /dev/null @@ -1,66 +0,0 @@ -// Code generated by github.com/fjl/gencodec. DO NOT EDIT. - -package engine - -import ( - "encoding/json" - "errors" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/core/types" -) - -var _ = (*payloadAttributesMarshaling)(nil) - -// MarshalJSON marshals as JSON. -func (p PayloadAttributes) MarshalJSON() ([]byte, error) { - type PayloadAttributes struct { - Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"` - Random common.Hash `json:"prevRandao" gencodec:"required"` - SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"` - Withdrawals []*types.Withdrawal `json:"withdrawals"` - BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"` - } - var enc PayloadAttributes - enc.Timestamp = hexutil.Uint64(p.Timestamp) - enc.Random = p.Random - enc.SuggestedFeeRecipient = p.SuggestedFeeRecipient - enc.Withdrawals = p.Withdrawals - enc.BeaconRoot = p.BeaconRoot - return json.Marshal(&enc) -} - -// UnmarshalJSON unmarshals from JSON. -func (p *PayloadAttributes) UnmarshalJSON(input []byte) error { - type PayloadAttributes struct { - Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"` - Random *common.Hash `json:"prevRandao" gencodec:"required"` - SuggestedFeeRecipient *common.Address `json:"suggestedFeeRecipient" gencodec:"required"` - Withdrawals []*types.Withdrawal `json:"withdrawals"` - BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"` - } - var dec PayloadAttributes - if err := json.Unmarshal(input, &dec); err != nil { - return err - } - if dec.Timestamp == nil { - return errors.New("missing required field 'timestamp' for PayloadAttributes") - } - p.Timestamp = uint64(*dec.Timestamp) - if dec.Random == nil { - return errors.New("missing required field 'prevRandao' for PayloadAttributes") - } - p.Random = *dec.Random - if dec.SuggestedFeeRecipient == nil { - return errors.New("missing required field 'suggestedFeeRecipient' for PayloadAttributes") - } - p.SuggestedFeeRecipient = *dec.SuggestedFeeRecipient - if dec.Withdrawals != nil { - p.Withdrawals = dec.Withdrawals - } - if dec.BeaconRoot != nil { - p.BeaconRoot = dec.BeaconRoot - } - return nil -} diff --git a/beacon/engine/gen_ed.go b/beacon/engine/gen_ed.go deleted file mode 100644 index 6893d64a16..0000000000 --- a/beacon/engine/gen_ed.go +++ /dev/null @@ -1,158 +0,0 @@ -// Code generated by github.com/fjl/gencodec. DO NOT EDIT. - -package engine - -import ( - "encoding/json" - "errors" - "math/big" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/core/types" -) - -var _ = (*executableDataMarshaling)(nil) - -// MarshalJSON marshals as JSON. -func (e ExecutableData) MarshalJSON() ([]byte, error) { - type ExecutableData struct { - ParentHash common.Hash `json:"parentHash" gencodec:"required"` - FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"` - StateRoot common.Hash `json:"stateRoot" gencodec:"required"` - ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"` - LogsBloom hexutil.Bytes `json:"logsBloom" gencodec:"required"` - Random common.Hash `json:"prevRandao" gencodec:"required"` - Number hexutil.Uint64 `json:"blockNumber" gencodec:"required"` - GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"` - GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"` - Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"` - ExtraData hexutil.Bytes `json:"extraData" gencodec:"required"` - BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"` - BlockHash common.Hash `json:"blockHash" gencodec:"required"` - Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"` - Withdrawals []*types.Withdrawal `json:"withdrawals"` - BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"` - ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"` - } - var enc ExecutableData - enc.ParentHash = e.ParentHash - enc.FeeRecipient = e.FeeRecipient - enc.StateRoot = e.StateRoot - enc.ReceiptsRoot = e.ReceiptsRoot - enc.LogsBloom = e.LogsBloom - enc.Random = e.Random - enc.Number = hexutil.Uint64(e.Number) - enc.GasLimit = hexutil.Uint64(e.GasLimit) - enc.GasUsed = hexutil.Uint64(e.GasUsed) - enc.Timestamp = hexutil.Uint64(e.Timestamp) - enc.ExtraData = e.ExtraData - enc.BaseFeePerGas = (*hexutil.Big)(e.BaseFeePerGas) - enc.BlockHash = e.BlockHash - if e.Transactions != nil { - enc.Transactions = make([]hexutil.Bytes, len(e.Transactions)) - for k, v := range e.Transactions { - enc.Transactions[k] = v - } - } - enc.Withdrawals = e.Withdrawals - enc.BlobGasUsed = (*hexutil.Uint64)(e.BlobGasUsed) - enc.ExcessBlobGas = (*hexutil.Uint64)(e.ExcessBlobGas) - return json.Marshal(&enc) -} - -// UnmarshalJSON unmarshals from JSON. -func (e *ExecutableData) UnmarshalJSON(input []byte) error { - type ExecutableData struct { - ParentHash *common.Hash `json:"parentHash" gencodec:"required"` - FeeRecipient *common.Address `json:"feeRecipient" gencodec:"required"` - StateRoot *common.Hash `json:"stateRoot" gencodec:"required"` - ReceiptsRoot *common.Hash `json:"receiptsRoot" gencodec:"required"` - LogsBloom *hexutil.Bytes `json:"logsBloom" gencodec:"required"` - Random *common.Hash `json:"prevRandao" gencodec:"required"` - Number *hexutil.Uint64 `json:"blockNumber" gencodec:"required"` - GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"` - GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"` - Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"` - ExtraData *hexutil.Bytes `json:"extraData" gencodec:"required"` - BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"` - BlockHash *common.Hash `json:"blockHash" gencodec:"required"` - Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"` - Withdrawals []*types.Withdrawal `json:"withdrawals"` - BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"` - ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"` - } - var dec ExecutableData - if err := json.Unmarshal(input, &dec); err != nil { - return err - } - if dec.ParentHash == nil { - return errors.New("missing required field 'parentHash' for ExecutableData") - } - e.ParentHash = *dec.ParentHash - if dec.FeeRecipient == nil { - return errors.New("missing required field 'feeRecipient' for ExecutableData") - } - e.FeeRecipient = *dec.FeeRecipient - if dec.StateRoot == nil { - return errors.New("missing required field 'stateRoot' for ExecutableData") - } - e.StateRoot = *dec.StateRoot - if dec.ReceiptsRoot == nil { - return errors.New("missing required field 'receiptsRoot' for ExecutableData") - } - e.ReceiptsRoot = *dec.ReceiptsRoot - if dec.LogsBloom == nil { - return errors.New("missing required field 'logsBloom' for ExecutableData") - } - e.LogsBloom = *dec.LogsBloom - if dec.Random == nil { - return errors.New("missing required field 'prevRandao' for ExecutableData") - } - e.Random = *dec.Random - if dec.Number == nil { - return errors.New("missing required field 'blockNumber' for ExecutableData") - } - e.Number = uint64(*dec.Number) - if dec.GasLimit == nil { - return errors.New("missing required field 'gasLimit' for ExecutableData") - } - e.GasLimit = uint64(*dec.GasLimit) - if dec.GasUsed == nil { - return errors.New("missing required field 'gasUsed' for ExecutableData") - } - e.GasUsed = uint64(*dec.GasUsed) - if dec.Timestamp == nil { - return errors.New("missing required field 'timestamp' for ExecutableData") - } - e.Timestamp = uint64(*dec.Timestamp) - if dec.ExtraData == nil { - return errors.New("missing required field 'extraData' for ExecutableData") - } - e.ExtraData = *dec.ExtraData - if dec.BaseFeePerGas == nil { - return errors.New("missing required field 'baseFeePerGas' for ExecutableData") - } - e.BaseFeePerGas = (*big.Int)(dec.BaseFeePerGas) - if dec.BlockHash == nil { - return errors.New("missing required field 'blockHash' for ExecutableData") - } - e.BlockHash = *dec.BlockHash - if dec.Transactions == nil { - return errors.New("missing required field 'transactions' for ExecutableData") - } - e.Transactions = make([][]byte, len(dec.Transactions)) - for k, v := range dec.Transactions { - e.Transactions[k] = v - } - if dec.Withdrawals != nil { - e.Withdrawals = dec.Withdrawals - } - if dec.BlobGasUsed != nil { - e.BlobGasUsed = (*uint64)(dec.BlobGasUsed) - } - if dec.ExcessBlobGas != nil { - e.ExcessBlobGas = (*uint64)(dec.ExcessBlobGas) - } - return nil -} diff --git a/beacon/engine/gen_epe.go b/beacon/engine/gen_epe.go deleted file mode 100644 index e69f9a5951..0000000000 --- a/beacon/engine/gen_epe.go +++ /dev/null @@ -1,58 +0,0 @@ -// Code generated by github.com/fjl/gencodec. DO NOT EDIT. - -package engine - -import ( - "encoding/json" - "errors" - "math/big" - - "github.com/ethereum/go-ethereum/common/hexutil" -) - -var _ = (*executionPayloadEnvelopeMarshaling)(nil) - -// MarshalJSON marshals as JSON. -func (e ExecutionPayloadEnvelope) MarshalJSON() ([]byte, error) { - type ExecutionPayloadEnvelope struct { - ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"` - BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"` - BlobsBundle *BlobsBundleV1 `json:"blobsBundle"` - Override bool `json:"shouldOverrideBuilder"` - } - var enc ExecutionPayloadEnvelope - enc.ExecutionPayload = e.ExecutionPayload - enc.BlockValue = (*hexutil.Big)(e.BlockValue) - enc.BlobsBundle = e.BlobsBundle - enc.Override = e.Override - return json.Marshal(&enc) -} - -// UnmarshalJSON unmarshals from JSON. -func (e *ExecutionPayloadEnvelope) UnmarshalJSON(input []byte) error { - type ExecutionPayloadEnvelope struct { - ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"` - BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"` - BlobsBundle *BlobsBundleV1 `json:"blobsBundle"` - Override *bool `json:"shouldOverrideBuilder"` - } - var dec ExecutionPayloadEnvelope - if err := json.Unmarshal(input, &dec); err != nil { - return err - } - if dec.ExecutionPayload == nil { - return errors.New("missing required field 'executionPayload' for ExecutionPayloadEnvelope") - } - e.ExecutionPayload = dec.ExecutionPayload - if dec.BlockValue == nil { - return errors.New("missing required field 'blockValue' for ExecutionPayloadEnvelope") - } - e.BlockValue = (*big.Int)(dec.BlockValue) - if dec.BlobsBundle != nil { - e.BlobsBundle = dec.BlobsBundle - } - if dec.Override != nil { - e.Override = *dec.Override - } - return nil -} diff --git a/beacon/engine/types.go b/beacon/engine/types.go deleted file mode 100644 index 67f30d4455..0000000000 --- a/beacon/engine/types.go +++ /dev/null @@ -1,280 +0,0 @@ -// Copyright 2022 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library 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. -// -// The go-ethereum library 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 the go-ethereum library. If not, see . - -package engine - -import ( - "fmt" - "math/big" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/trie" -) - -//go:generate go run github.com/fjl/gencodec -type PayloadAttributes -field-override payloadAttributesMarshaling -out gen_blockparams.go - -// PayloadAttributes describes the environment context in which a block should -// be built. -type PayloadAttributes struct { - Timestamp uint64 `json:"timestamp" gencodec:"required"` - Random common.Hash `json:"prevRandao" gencodec:"required"` - SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"` - Withdrawals []*types.Withdrawal `json:"withdrawals"` - BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"` -} - -// JSON type overrides for PayloadAttributes. -type payloadAttributesMarshaling struct { - Timestamp hexutil.Uint64 -} - -//go:generate go run github.com/fjl/gencodec -type ExecutableData -field-override executableDataMarshaling -out gen_ed.go - -// ExecutableData is the data necessary to execute an EL payload. -type ExecutableData struct { - ParentHash common.Hash `json:"parentHash" gencodec:"required"` - FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"` - StateRoot common.Hash `json:"stateRoot" gencodec:"required"` - ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"` - LogsBloom []byte `json:"logsBloom" gencodec:"required"` - Random common.Hash `json:"prevRandao" gencodec:"required"` - Number uint64 `json:"blockNumber" gencodec:"required"` - GasLimit uint64 `json:"gasLimit" gencodec:"required"` - GasUsed uint64 `json:"gasUsed" gencodec:"required"` - Timestamp uint64 `json:"timestamp" gencodec:"required"` - ExtraData []byte `json:"extraData" gencodec:"required"` - BaseFeePerGas *big.Int `json:"baseFeePerGas" gencodec:"required"` - BlockHash common.Hash `json:"blockHash" gencodec:"required"` - Transactions [][]byte `json:"transactions" gencodec:"required"` - Withdrawals []*types.Withdrawal `json:"withdrawals"` - BlobGasUsed *uint64 `json:"blobGasUsed"` - ExcessBlobGas *uint64 `json:"excessBlobGas"` -} - -// JSON type overrides for executableData. -type executableDataMarshaling struct { - Number hexutil.Uint64 - GasLimit hexutil.Uint64 - GasUsed hexutil.Uint64 - Timestamp hexutil.Uint64 - BaseFeePerGas *hexutil.Big - ExtraData hexutil.Bytes - LogsBloom hexutil.Bytes - Transactions []hexutil.Bytes - BlobGasUsed *hexutil.Uint64 - ExcessBlobGas *hexutil.Uint64 -} - -//go:generate go run github.com/fjl/gencodec -type ExecutionPayloadEnvelope -field-override executionPayloadEnvelopeMarshaling -out gen_epe.go - -type ExecutionPayloadEnvelope struct { - ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"` - BlockValue *big.Int `json:"blockValue" gencodec:"required"` - BlobsBundle *BlobsBundleV1 `json:"blobsBundle"` - Override bool `json:"shouldOverrideBuilder"` -} - -type BlobsBundleV1 struct { - Commitments []hexutil.Bytes `json:"commitments"` - Proofs []hexutil.Bytes `json:"proofs"` - Blobs []hexutil.Bytes `json:"blobs"` -} - -// JSON type overrides for ExecutionPayloadEnvelope. -type executionPayloadEnvelopeMarshaling struct { - BlockValue *hexutil.Big -} - -type PayloadStatusV1 struct { - Status string `json:"status"` - LatestValidHash *common.Hash `json:"latestValidHash"` - ValidationError *string `json:"validationError"` -} - -type TransitionConfigurationV1 struct { - TerminalTotalDifficulty *hexutil.Big `json:"terminalTotalDifficulty"` - TerminalBlockHash common.Hash `json:"terminalBlockHash"` - TerminalBlockNumber hexutil.Uint64 `json:"terminalBlockNumber"` -} - -// PayloadID is an identifier of the payload build process -type PayloadID [8]byte - -func (b PayloadID) String() string { - return hexutil.Encode(b[:]) -} - -func (b PayloadID) MarshalText() ([]byte, error) { - return hexutil.Bytes(b[:]).MarshalText() -} - -func (b *PayloadID) UnmarshalText(input []byte) error { - err := hexutil.UnmarshalFixedText("PayloadID", input, b[:]) - if err != nil { - return fmt.Errorf("invalid payload id %q: %w", input, err) - } - return nil -} - -type ForkChoiceResponse struct { - PayloadStatus PayloadStatusV1 `json:"payloadStatus"` - PayloadID *PayloadID `json:"payloadId"` -} - -type ForkchoiceStateV1 struct { - HeadBlockHash common.Hash `json:"headBlockHash"` - SafeBlockHash common.Hash `json:"safeBlockHash"` - FinalizedBlockHash common.Hash `json:"finalizedBlockHash"` -} - -func encodeTransactions(txs []*types.Transaction) [][]byte { - var enc = make([][]byte, len(txs)) - for i, tx := range txs { - enc[i], _ = tx.MarshalBinary() - } - return enc -} - -func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) { - var txs = make([]*types.Transaction, len(enc)) - for i, encTx := range enc { - var tx types.Transaction - if err := tx.UnmarshalBinary(encTx); err != nil { - return nil, fmt.Errorf("invalid transaction %d: %v", i, err) - } - txs[i] = &tx - } - return txs, nil -} - -// ExecutableDataToBlock constructs a block from executable data. -// It verifies that the following fields: -// -// len(extraData) <= 32 -// uncleHash = emptyUncleHash -// difficulty = 0 -// if versionedHashes != nil, versionedHashes match to blob transactions -// -// and that the blockhash of the constructed block matches the parameters. Nil -// Withdrawals value will propagate through the returned block. Empty -// Withdrawals value must be passed via non-nil, length 0 value in params. -func ExecutableDataToBlock(params ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (*types.Block, error) { - txs, err := decodeTransactions(params.Transactions) - if err != nil { - return nil, err - } - if len(params.ExtraData) > 32 { - return nil, fmt.Errorf("invalid extradata length: %v", len(params.ExtraData)) - } - if len(params.LogsBloom) != 256 { - return nil, fmt.Errorf("invalid logsBloom length: %v", len(params.LogsBloom)) - } - // Check that baseFeePerGas is not negative or too big - if params.BaseFeePerGas != nil && (params.BaseFeePerGas.Sign() == -1 || params.BaseFeePerGas.BitLen() > 256) { - return nil, fmt.Errorf("invalid baseFeePerGas: %v", params.BaseFeePerGas) - } - var blobHashes []common.Hash - for _, tx := range txs { - blobHashes = append(blobHashes, tx.BlobHashes()...) - } - if len(blobHashes) != len(versionedHashes) { - return nil, fmt.Errorf("invalid number of versionedHashes: %v blobHashes: %v", versionedHashes, blobHashes) - } - for i := 0; i < len(blobHashes); i++ { - if blobHashes[i] != versionedHashes[i] { - return nil, fmt.Errorf("invalid versionedHash at %v: %v blobHashes: %v", i, versionedHashes, blobHashes) - } - } - // Only set withdrawalsRoot if it is non-nil. This allows CLs to use - // ExecutableData before withdrawals are enabled by marshaling - // Withdrawals as the json null value. - var withdrawalsRoot *common.Hash - if params.Withdrawals != nil { - h := types.DeriveSha(types.Withdrawals(params.Withdrawals), trie.NewStackTrie(nil)) - withdrawalsRoot = &h - } - header := &types.Header{ - ParentHash: params.ParentHash, - UncleHash: types.EmptyUncleHash, - Coinbase: params.FeeRecipient, - Root: params.StateRoot, - TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)), - ReceiptHash: params.ReceiptsRoot, - Bloom: types.BytesToBloom(params.LogsBloom), - Difficulty: common.Big0, - Number: new(big.Int).SetUint64(params.Number), - GasLimit: params.GasLimit, - GasUsed: params.GasUsed, - Time: params.Timestamp, - BaseFee: params.BaseFeePerGas, - Extra: params.ExtraData, - MixDigest: params.Random, - WithdrawalsHash: withdrawalsRoot, - ExcessBlobGas: params.ExcessBlobGas, - BlobGasUsed: params.BlobGasUsed, - ParentBeaconRoot: beaconRoot, - } - block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */).WithWithdrawals(params.Withdrawals) - if block.Hash() != params.BlockHash { - return nil, fmt.Errorf("blockhash mismatch, want %x, got %x", params.BlockHash, block.Hash()) - } - return block, nil -} - -// BlockToExecutableData constructs the ExecutableData structure by filling the -// fields from the given block. It assumes the given block is post-merge block. -func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types.BlobTxSidecar) *ExecutionPayloadEnvelope { - data := &ExecutableData{ - BlockHash: block.Hash(), - ParentHash: block.ParentHash(), - FeeRecipient: block.Coinbase(), - StateRoot: block.Root(), - Number: block.NumberU64(), - GasLimit: block.GasLimit(), - GasUsed: block.GasUsed(), - BaseFeePerGas: block.BaseFee(), - Timestamp: block.Time(), - ReceiptsRoot: block.ReceiptHash(), - LogsBloom: block.Bloom().Bytes(), - Transactions: encodeTransactions(block.Transactions()), - Random: block.MixDigest(), - ExtraData: block.Extra(), - Withdrawals: block.Withdrawals(), - BlobGasUsed: block.BlobGasUsed(), - ExcessBlobGas: block.ExcessBlobGas(), - } - bundle := BlobsBundleV1{ - Commitments: make([]hexutil.Bytes, 0), - Blobs: make([]hexutil.Bytes, 0), - Proofs: make([]hexutil.Bytes, 0), - } - for _, sidecar := range sidecars { - for j := range sidecar.Blobs { - bundle.Blobs = append(bundle.Blobs, hexutil.Bytes(sidecar.Blobs[j][:])) - bundle.Commitments = append(bundle.Commitments, hexutil.Bytes(sidecar.Commitments[j][:])) - bundle.Proofs = append(bundle.Proofs, hexutil.Bytes(sidecar.Proofs[j][:])) - } - } - return &ExecutionPayloadEnvelope{ExecutionPayload: data, BlockValue: fees, BlobsBundle: &bundle, Override: false} -} - -// ExecutionPayloadBodyV1 is used in the response to GetPayloadBodiesByHashV1 and GetPayloadBodiesByRangeV1 -type ExecutionPayloadBodyV1 struct { - TransactionData []hexutil.Bytes `json:"transactions"` - Withdrawals []*types.Withdrawal `json:"withdrawals"` -} diff --git a/beacon/light/canonical.go b/beacon/light/canonical.go deleted file mode 100644 index b5371493b4..0000000000 --- a/beacon/light/canonical.go +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright 2023 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library 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. -// -// The go-ethereum library 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 the go-ethereum library. If not, see . - -package light - -import ( - "encoding/binary" - "fmt" - - "github.com/ethereum/go-ethereum/common/lru" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" -) - -// canonicalStore stores instances of the given type in a database and caches -// them in memory, associated with a continuous range of period numbers. -// Note: canonicalStore is not thread safe and it is the caller's responsibility -// to avoid concurrent access. -type canonicalStore[T any] struct { - keyPrefix []byte - periods periodRange - cache *lru.Cache[uint64, T] -} - -// newCanonicalStore creates a new canonicalStore and loads all keys associated -// with the keyPrefix in order to determine the ranges available in the database. -func newCanonicalStore[T any](db ethdb.Iteratee, keyPrefix []byte) (*canonicalStore[T], error) { - cs := &canonicalStore[T]{ - keyPrefix: keyPrefix, - cache: lru.NewCache[uint64, T](100), - } - var ( - iter = db.NewIterator(keyPrefix, nil) - kl = len(keyPrefix) - first = true - ) - defer iter.Release() - - for iter.Next() { - if len(iter.Key()) != kl+8 { - log.Warn("Invalid key length in the canonical chain database", "key", fmt.Sprintf("%#x", iter.Key())) - continue - } - period := binary.BigEndian.Uint64(iter.Key()[kl : kl+8]) - if first { - cs.periods.Start = period - } else if cs.periods.End != period { - return nil, fmt.Errorf("gap in the canonical chain database between periods %d and %d", cs.periods.End, period-1) - } - first = false - cs.periods.End = period + 1 - } - return cs, nil -} - -// databaseKey returns the database key belonging to the given period. -func (cs *canonicalStore[T]) databaseKey(period uint64) []byte { - return binary.BigEndian.AppendUint64(append([]byte{}, cs.keyPrefix...), period) -} - -// add adds the given item to the database. It also ensures that the range remains -// continuous. Can be used either with a batch or database backend. -func (cs *canonicalStore[T]) add(backend ethdb.KeyValueWriter, period uint64, value T) error { - if !cs.periods.canExpand(period) { - return fmt.Errorf("period expansion is not allowed, first: %d, next: %d, period: %d", cs.periods.Start, cs.periods.End, period) - } - enc, err := rlp.EncodeToBytes(value) - if err != nil { - return err - } - if err := backend.Put(cs.databaseKey(period), enc); err != nil { - return err - } - cs.cache.Add(period, value) - cs.periods.expand(period) - return nil -} - -// deleteFrom removes items starting from the given period. -func (cs *canonicalStore[T]) deleteFrom(db ethdb.KeyValueWriter, fromPeriod uint64) (deleted periodRange) { - keepRange, deleteRange := cs.periods.split(fromPeriod) - deleteRange.each(func(period uint64) { - db.Delete(cs.databaseKey(period)) - cs.cache.Remove(period) - }) - cs.periods = keepRange - return deleteRange -} - -// get returns the item at the given period or the null value of the given type -// if no item is present. -func (cs *canonicalStore[T]) get(backend ethdb.KeyValueReader, period uint64) (T, bool) { - var null, value T - if !cs.periods.contains(period) { - return null, false - } - if value, ok := cs.cache.Get(period); ok { - return value, true - } - enc, err := backend.Get(cs.databaseKey(period)) - if err != nil { - log.Error("Canonical store value not found", "period", period, "start", cs.periods.Start, "end", cs.periods.End) - return null, false - } - if err := rlp.DecodeBytes(enc, &value); err != nil { - log.Error("Error decoding canonical store value", "error", err) - return null, false - } - cs.cache.Add(period, value) - return value, true -} diff --git a/beacon/light/committee_chain.go b/beacon/light/committee_chain.go deleted file mode 100644 index d707f8cc34..0000000000 --- a/beacon/light/committee_chain.go +++ /dev/null @@ -1,514 +0,0 @@ -// Copyright 2023 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library 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. -// -// The go-ethereum library 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 the go-ethereum library. If not, see . - -package light - -import ( - "errors" - "fmt" - "math" - "sync" - "time" - - "github.com/ethereum/go-ethereum/beacon/params" - "github.com/ethereum/go-ethereum/beacon/types" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/lru" - "github.com/ethereum/go-ethereum/common/mclock" - "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/log" -) - -var ( - ErrNeedCommittee = errors.New("sync committee required") - ErrInvalidUpdate = errors.New("invalid committee update") - ErrInvalidPeriod = errors.New("invalid update period") - ErrWrongCommitteeRoot = errors.New("wrong committee root") - ErrCannotReorg = errors.New("can not reorg committee chain") -) - -// CommitteeChain is a passive data structure that can validate, hold and update -// a chain of beacon light sync committees and updates. It requires at least one -// externally set fixed committee root at the beginning of the chain which can -// be set either based on a BootstrapData or a trusted source (a local beacon -// full node). This makes the structure useful for both light client and light -// server setups. -// -// It always maintains the following consistency constraints: -// - a committee can only be present if its root hash matches an existing fixed -// root or if it is proven by an update at the previous period -// - an update can only be present if a committee is present at the same period -// and the update signature is valid and has enough participants. -// The committee at the next period (proven by the update) should also be -// present (note that this means they can only be added together if neither -// is present yet). If a fixed root is present at the next period then the -// update can only be present if it proves the same committee root. -// -// Once synced to the current sync period, CommitteeChain can also validate -// signed beacon headers. -type CommitteeChain struct { - // chainmu guards against concurrent access to the canonicalStore structures - // (updates, committees, fixedCommitteeRoots) and ensures that they stay consistent - // with each other and with committeeCache. - chainmu sync.RWMutex - db ethdb.KeyValueStore - updates *canonicalStore[*types.LightClientUpdate] - committees *canonicalStore[*types.SerializedSyncCommittee] - fixedCommitteeRoots *canonicalStore[common.Hash] - committeeCache *lru.Cache[uint64, syncCommittee] // cache deserialized committees - - clock mclock.Clock // monotonic clock (simulated clock in tests) - unixNano func() int64 // system clock (simulated clock in tests) - sigVerifier committeeSigVerifier // BLS sig verifier (dummy verifier in tests) - - config *types.ChainConfig - signerThreshold int - minimumUpdateScore types.UpdateScore - enforceTime bool // enforceTime specifies whether the age of a signed header should be checked -} - -// NewCommitteeChain creates a new CommitteeChain. -func NewCommitteeChain(db ethdb.KeyValueStore, config *types.ChainConfig, signerThreshold int, enforceTime bool) *CommitteeChain { - return newCommitteeChain(db, config, signerThreshold, enforceTime, blsVerifier{}, &mclock.System{}, func() int64 { return time.Now().UnixNano() }) -} - -// newCommitteeChain creates a new CommitteeChain with the option of replacing the -// clock source and signature verification for testing purposes. -func newCommitteeChain(db ethdb.KeyValueStore, config *types.ChainConfig, signerThreshold int, enforceTime bool, sigVerifier committeeSigVerifier, clock mclock.Clock, unixNano func() int64) *CommitteeChain { - s := &CommitteeChain{ - committeeCache: lru.NewCache[uint64, syncCommittee](10), - db: db, - sigVerifier: sigVerifier, - clock: clock, - unixNano: unixNano, - config: config, - signerThreshold: signerThreshold, - enforceTime: enforceTime, - minimumUpdateScore: types.UpdateScore{ - SignerCount: uint32(signerThreshold), - SubPeriodIndex: params.SyncPeriodLength / 16, - }, - } - - var err1, err2, err3 error - if s.fixedCommitteeRoots, err1 = newCanonicalStore[common.Hash](db, rawdb.FixedCommitteeRootKey); err1 != nil { - log.Error("Error creating fixed committee root store", "error", err1) - } - if s.committees, err2 = newCanonicalStore[*types.SerializedSyncCommittee](db, rawdb.SyncCommitteeKey); err2 != nil { - log.Error("Error creating committee store", "error", err2) - } - if s.updates, err3 = newCanonicalStore[*types.LightClientUpdate](db, rawdb.BestUpdateKey); err3 != nil { - log.Error("Error creating update store", "error", err3) - } - if err1 != nil || err2 != nil || err3 != nil || !s.checkConstraints() { - log.Info("Resetting invalid committee chain") - s.Reset() - } - // roll back invalid updates (might be necessary if forks have been changed since last time) - for !s.updates.periods.isEmpty() { - update, ok := s.updates.get(s.db, s.updates.periods.End-1) - if !ok { - log.Error("Sync committee update missing", "period", s.updates.periods.End-1) - s.Reset() - break - } - if valid, err := s.verifyUpdate(update); err != nil { - log.Error("Error validating update", "period", s.updates.periods.End-1, "error", err) - } else if valid { - break - } - if err := s.rollback(s.updates.periods.End); err != nil { - log.Error("Error writing batch into chain database", "error", err) - } - } - if !s.committees.periods.isEmpty() { - log.Trace("Sync committee chain loaded", "first period", s.committees.periods.Start, "last period", s.committees.periods.End-1) - } - return s -} - -// checkConstraints checks committee chain validity constraints -func (s *CommitteeChain) checkConstraints() bool { - isNotInFixedCommitteeRootRange := func(r periodRange) bool { - return s.fixedCommitteeRoots.periods.isEmpty() || - r.Start < s.fixedCommitteeRoots.periods.Start || - r.Start >= s.fixedCommitteeRoots.periods.End - } - - valid := true - if !s.updates.periods.isEmpty() { - if isNotInFixedCommitteeRootRange(s.updates.periods) { - log.Error("Start update is not in the fixed roots range") - valid = false - } - if s.committees.periods.Start > s.updates.periods.Start || s.committees.periods.End <= s.updates.periods.End { - log.Error("Missing committees in update range") - valid = false - } - } - if !s.committees.periods.isEmpty() { - if isNotInFixedCommitteeRootRange(s.committees.periods) { - log.Error("Start committee is not in the fixed roots range") - valid = false - } - if s.committees.periods.End > s.fixedCommitteeRoots.periods.End && s.committees.periods.End > s.updates.periods.End+1 { - log.Error("Last committee is neither in the fixed roots range nor proven by updates") - valid = false - } - } - return valid -} - -// Reset resets the committee chain. -func (s *CommitteeChain) Reset() { - s.chainmu.Lock() - defer s.chainmu.Unlock() - - if err := s.rollback(0); err != nil { - log.Error("Error writing batch into chain database", "error", err) - } -} - -// CheckpointInit initializes a CommitteeChain based on the checkpoint. -// Note: if the chain is already initialized and the committees proven by the -// checkpoint do match the existing chain then the chain is retained and the -// new checkpoint becomes fixed. -func (s *CommitteeChain) CheckpointInit(bootstrap *types.BootstrapData) error { - s.chainmu.Lock() - defer s.chainmu.Unlock() - - if err := bootstrap.Validate(); err != nil { - return err - } - - period := bootstrap.Header.SyncPeriod() - if err := s.deleteFixedCommitteeRootsFrom(period + 2); err != nil { - s.Reset() - return err - } - if s.addFixedCommitteeRoot(period, bootstrap.CommitteeRoot) != nil { - s.Reset() - if err := s.addFixedCommitteeRoot(period, bootstrap.CommitteeRoot); err != nil { - s.Reset() - return err - } - } - if err := s.addFixedCommitteeRoot(period+1, common.Hash(bootstrap.CommitteeBranch[0])); err != nil { - s.Reset() - return err - } - if err := s.addCommittee(period, bootstrap.Committee); err != nil { - s.Reset() - return err - } - return nil -} - -// addFixedCommitteeRoot sets a fixed committee root at the given period. -// Note that the period where the first committee is added has to have a fixed -// root which can either come from a BootstrapData or a trusted source. -func (s *CommitteeChain) addFixedCommitteeRoot(period uint64, root common.Hash) error { - if root == (common.Hash{}) { - return ErrWrongCommitteeRoot - } - - batch := s.db.NewBatch() - oldRoot := s.getCommitteeRoot(period) - if !s.fixedCommitteeRoots.periods.canExpand(period) { - // Note: the fixed committee root range should always be continuous and - // therefore the expected syncing method is to forward sync and optionally - // backward sync periods one by one, starting from a checkpoint. The only - // case when a root that is not adjacent to the already fixed ones can be - // fixed is when the same root has already been proven by an update chain. - // In this case the all roots in between can and should be fixed. - // This scenario makes sense when a new trusted checkpoint is added to an - // existing chain, ensuring that it will not be rolled back (might be - // important in case of low signer participation rate). - if root != oldRoot { - return ErrInvalidPeriod - } - // if the old root exists and matches the new one then it is guaranteed - // that the given period is after the existing fixed range and the roots - // in between can also be fixed. - for p := s.fixedCommitteeRoots.periods.End; p < period; p++ { - if err := s.fixedCommitteeRoots.add(batch, p, s.getCommitteeRoot(p)); err != nil { - return err - } - } - } - if oldRoot != (common.Hash{}) && (oldRoot != root) { - // existing old root was different, we have to reorg the chain - if err := s.rollback(period); err != nil { - return err - } - } - if err := s.fixedCommitteeRoots.add(batch, period, root); err != nil { - return err - } - if err := batch.Write(); err != nil { - log.Error("Error writing batch into chain database", "error", err) - return err - } - return nil -} - -// deleteFixedCommitteeRootsFrom deletes fixed roots starting from the given period. -// It also maintains chain consistency, meaning that it also deletes updates and -// committees if they are no longer supported by a valid update chain. -func (s *CommitteeChain) deleteFixedCommitteeRootsFrom(period uint64) error { - if period >= s.fixedCommitteeRoots.periods.End { - return nil - } - batch := s.db.NewBatch() - s.fixedCommitteeRoots.deleteFrom(batch, period) - if s.updates.periods.isEmpty() || period <= s.updates.periods.Start { - // Note: the first period of the update chain should always be fixed so if - // the fixed root at the first update is removed then the entire update chain - // and the proven committees have to be removed. Earlier committees in the - // remaining fixed root range can stay. - s.updates.deleteFrom(batch, period) - s.deleteCommitteesFrom(batch, period) - } else { - // The update chain stays intact, some previously fixed committee roots might - // get unfixed but are still proven by the update chain. If there were - // committees present after the range proven by updates, those should be - // removed if the belonging fixed roots are also removed. - fromPeriod := s.updates.periods.End + 1 // not proven by updates - if period > fromPeriod { - fromPeriod = period // also not justified by fixed roots - } - s.deleteCommitteesFrom(batch, fromPeriod) - } - if err := batch.Write(); err != nil { - log.Error("Error writing batch into chain database", "error", err) - return err - } - return nil -} - -// deleteCommitteesFrom deletes committees starting from the given period. -func (s *CommitteeChain) deleteCommitteesFrom(batch ethdb.Batch, period uint64) { - deleted := s.committees.deleteFrom(batch, period) - for period := deleted.Start; period < deleted.End; period++ { - s.committeeCache.Remove(period) - } -} - -// addCommittee adds a committee at the given period if possible. -func (s *CommitteeChain) addCommittee(period uint64, committee *types.SerializedSyncCommittee) error { - if !s.committees.periods.canExpand(period) { - return ErrInvalidPeriod - } - root := s.getCommitteeRoot(period) - if root == (common.Hash{}) { - return ErrInvalidPeriod - } - if root != committee.Root() { - return ErrWrongCommitteeRoot - } - if !s.committees.periods.contains(period) { - if err := s.committees.add(s.db, period, committee); err != nil { - return err - } - s.committeeCache.Remove(period) - } - return nil -} - -// InsertUpdate adds a new update if possible. -func (s *CommitteeChain) InsertUpdate(update *types.LightClientUpdate, nextCommittee *types.SerializedSyncCommittee) error { - s.chainmu.Lock() - defer s.chainmu.Unlock() - - period := update.AttestedHeader.Header.SyncPeriod() - if !s.updates.periods.canExpand(period) || !s.committees.periods.contains(period) { - return ErrInvalidPeriod - } - if s.minimumUpdateScore.BetterThan(update.Score()) { - return ErrInvalidUpdate - } - oldRoot := s.getCommitteeRoot(period + 1) - reorg := oldRoot != (common.Hash{}) && oldRoot != update.NextSyncCommitteeRoot - if oldUpdate, ok := s.updates.get(s.db, period); ok && !update.Score().BetterThan(oldUpdate.Score()) { - // a better or equal update already exists; no changes, only fail if new one tried to reorg - if reorg { - return ErrCannotReorg - } - return nil - } - if s.fixedCommitteeRoots.periods.contains(period+1) && reorg { - return ErrCannotReorg - } - if ok, err := s.verifyUpdate(update); err != nil { - return err - } else if !ok { - return ErrInvalidUpdate - } - addCommittee := !s.committees.periods.contains(period+1) || reorg - if addCommittee { - if nextCommittee == nil { - return ErrNeedCommittee - } - if nextCommittee.Root() != update.NextSyncCommitteeRoot { - return ErrWrongCommitteeRoot - } - } - if reorg { - if err := s.rollback(period + 1); err != nil { - return err - } - } - batch := s.db.NewBatch() - if addCommittee { - if err := s.committees.add(batch, period+1, nextCommittee); err != nil { - return err - } - s.committeeCache.Remove(period + 1) - } - if err := s.updates.add(batch, period, update); err != nil { - return err - } - if err := batch.Write(); err != nil { - log.Error("Error writing batch into chain database", "error", err) - return err - } - log.Info("Inserted new committee update", "period", period, "next committee root", update.NextSyncCommitteeRoot) - return nil -} - -// NextSyncPeriod returns the next period where an update can be added and also -// whether the chain is initialized at all. -func (s *CommitteeChain) NextSyncPeriod() (uint64, bool) { - s.chainmu.RLock() - defer s.chainmu.RUnlock() - - if s.committees.periods.isEmpty() { - return 0, false - } - if !s.updates.periods.isEmpty() { - return s.updates.periods.End, true - } - return s.committees.periods.End - 1, true -} - -// rollback removes all committees and fixed roots from the given period and updates -// starting from the previous period. -func (s *CommitteeChain) rollback(period uint64) error { - max := s.updates.periods.End + 1 - if s.committees.periods.End > max { - max = s.committees.periods.End - } - if s.fixedCommitteeRoots.periods.End > max { - max = s.fixedCommitteeRoots.periods.End - } - for max > period { - max-- - batch := s.db.NewBatch() - s.deleteCommitteesFrom(batch, max) - s.fixedCommitteeRoots.deleteFrom(batch, max) - if max > 0 { - s.updates.deleteFrom(batch, max-1) - } - if err := batch.Write(); err != nil { - log.Error("Error writing batch into chain database", "error", err) - return err - } - } - return nil -} - -// getCommitteeRoot returns the committee root at the given period, either fixed, -// proven by a previous update or both. It returns an empty hash if the committee -// root is unknown. -func (s *CommitteeChain) getCommitteeRoot(period uint64) common.Hash { - if root, ok := s.fixedCommitteeRoots.get(s.db, period); ok || period == 0 { - return root - } - if update, ok := s.updates.get(s.db, period-1); ok { - return update.NextSyncCommitteeRoot - } - return common.Hash{} -} - -// getSyncCommittee returns the deserialized sync committee at the given period. -func (s *CommitteeChain) getSyncCommittee(period uint64) (syncCommittee, error) { - if c, ok := s.committeeCache.Get(period); ok { - return c, nil - } - if sc, ok := s.committees.get(s.db, period); ok { - c, err := s.sigVerifier.deserializeSyncCommittee(sc) - if err != nil { - return nil, fmt.Errorf("Sync committee #%d deserialization error: %v", period, err) - } - s.committeeCache.Add(period, c) - return c, nil - } - return nil, fmt.Errorf("Missing serialized sync committee #%d", period) -} - -// VerifySignedHeader returns true if the given signed header has a valid signature -// according to the local committee chain. The caller should ensure that the -// committees advertised by the same source where the signed header came from are -// synced before verifying the signature. -// The age of the header is also returned (the time elapsed since the beginning -// of the given slot, according to the local system clock). If enforceTime is -// true then negative age (future) headers are rejected. -func (s *CommitteeChain) VerifySignedHeader(head types.SignedHeader) (bool, time.Duration, error) { - s.chainmu.RLock() - defer s.chainmu.RUnlock() - - return s.verifySignedHeader(head) -} - -func (s *CommitteeChain) verifySignedHeader(head types.SignedHeader) (bool, time.Duration, error) { - var age time.Duration - now := s.unixNano() - if head.Header.Slot < (uint64(now-math.MinInt64)/uint64(time.Second)-s.config.GenesisTime)/12 { - age = time.Duration(now - int64(time.Second)*int64(s.config.GenesisTime+head.Header.Slot*12)) - } else { - age = time.Duration(math.MinInt64) - } - if s.enforceTime && age < 0 { - return false, age, nil - } - committee, err := s.getSyncCommittee(types.SyncPeriod(head.SignatureSlot)) - if err != nil { - return false, 0, err - } - if committee == nil { - return false, age, nil - } - if signingRoot, err := s.config.Forks.SigningRoot(head.Header); err == nil { - return s.sigVerifier.verifySignature(committee, signingRoot, &head.Signature), age, nil - } - return false, age, nil -} - -// verifyUpdate checks whether the header signature is correct and the update -// fits into the specified constraints (assumes that the update has been -// successfully validated previously) -func (s *CommitteeChain) verifyUpdate(update *types.LightClientUpdate) (bool, error) { - // Note: SignatureSlot determines the sync period of the committee used for signature - // verification. Though in reality SignatureSlot is always bigger than update.Header.Slot, - // setting them as equal here enforces the rule that they have to be in the same sync - // period in order for the light client update proof to be meaningful. - ok, age, err := s.verifySignedHeader(update.AttestedHeader) - if age < 0 { - log.Warn("Future committee update received", "age", age) - } - return ok, err -} diff --git a/beacon/light/committee_chain_test.go b/beacon/light/committee_chain_test.go deleted file mode 100644 index 60ea2a0efd..0000000000 --- a/beacon/light/committee_chain_test.go +++ /dev/null @@ -1,356 +0,0 @@ -// Copyright 2022 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library 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. -// -// The go-ethereum library 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 the go-ethereum library. If not, see . - -package light - -import ( - "crypto/rand" - "testing" - "time" - - "github.com/ethereum/go-ethereum/beacon/params" - "github.com/ethereum/go-ethereum/beacon/types" - "github.com/ethereum/go-ethereum/common/mclock" - "github.com/ethereum/go-ethereum/ethdb/memorydb" -) - -var ( - testGenesis = newTestGenesis() - testGenesis2 = newTestGenesis() - - tfBase = newTestForks(testGenesis, types.Forks{ - &types.Fork{Epoch: 0, Version: []byte{0}}, - }) - tfAlternative = newTestForks(testGenesis, types.Forks{ - &types.Fork{Epoch: 0, Version: []byte{0}}, - &types.Fork{Epoch: 0x700, Version: []byte{1}}, - }) - tfAnotherGenesis = newTestForks(testGenesis2, types.Forks{ - &types.Fork{Epoch: 0, Version: []byte{0}}, - }) - - tcBase = newTestCommitteeChain(nil, tfBase, true, 0, 10, 400, false) - tcBaseWithInvalidUpdates = newTestCommitteeChain(tcBase, tfBase, false, 5, 10, 200, false) // signer count too low - tcBaseWithBetterUpdates = newTestCommitteeChain(tcBase, tfBase, false, 5, 10, 440, false) - tcReorgWithWorseUpdates = newTestCommitteeChain(tcBase, tfBase, true, 5, 10, 400, false) - tcReorgWithWorseUpdates2 = newTestCommitteeChain(tcBase, tfBase, true, 5, 10, 380, false) - tcReorgWithBetterUpdates = newTestCommitteeChain(tcBase, tfBase, true, 5, 10, 420, false) - tcReorgWithFinalizedUpdates = newTestCommitteeChain(tcBase, tfBase, true, 5, 10, 400, true) - tcFork = newTestCommitteeChain(tcBase, tfAlternative, true, 7, 10, 400, false) - tcAnotherGenesis = newTestCommitteeChain(nil, tfAnotherGenesis, true, 0, 10, 400, false) -) - -func TestCommitteeChainFixedCommitteeRoots(t *testing.T) { - for _, reload := range []bool{false, true} { - c := newCommitteeChainTest(t, tfBase, 300, true) - c.setClockPeriod(7) - c.addFixedCommitteeRoot(tcBase, 4, nil) - c.addFixedCommitteeRoot(tcBase, 5, nil) - c.addFixedCommitteeRoot(tcBase, 6, nil) - c.addFixedCommitteeRoot(tcBase, 8, ErrInvalidPeriod) // range has to be continuous - c.addFixedCommitteeRoot(tcBase, 3, nil) - c.addFixedCommitteeRoot(tcBase, 2, nil) - if reload { - c.reloadChain() - } - c.addCommittee(tcBase, 4, nil) - c.addCommittee(tcBase, 6, ErrInvalidPeriod) // range has to be continuous - c.addCommittee(tcBase, 5, nil) - c.addCommittee(tcBase, 6, nil) - c.addCommittee(tcAnotherGenesis, 3, ErrWrongCommitteeRoot) - c.addCommittee(tcBase, 3, nil) - if reload { - c.reloadChain() - } - c.verifyRange(tcBase, 3, 6) - } -} - -func TestCommitteeChainCheckpointSync(t *testing.T) { - for _, enforceTime := range []bool{false, true} { - for _, reload := range []bool{false, true} { - c := newCommitteeChainTest(t, tfBase, 300, enforceTime) - if enforceTime { - c.setClockPeriod(6) - } - c.insertUpdate(tcBase, 3, true, ErrInvalidPeriod) - c.addFixedCommitteeRoot(tcBase, 3, nil) - c.addFixedCommitteeRoot(tcBase, 4, nil) - c.insertUpdate(tcBase, 4, true, ErrInvalidPeriod) // still no committee - c.addCommittee(tcBase, 3, nil) - c.addCommittee(tcBase, 4, nil) - if reload { - c.reloadChain() - } - c.verifyRange(tcBase, 3, 4) - c.insertUpdate(tcBase, 3, false, nil) // update can be added without committee here - c.insertUpdate(tcBase, 4, false, ErrNeedCommittee) // but not here as committee 5 is not there yet - c.insertUpdate(tcBase, 4, true, nil) - c.verifyRange(tcBase, 3, 5) - c.insertUpdate(tcBaseWithInvalidUpdates, 5, true, ErrInvalidUpdate) // signer count too low - c.insertUpdate(tcBase, 5, true, nil) - if reload { - c.reloadChain() - } - if enforceTime { - c.insertUpdate(tcBase, 6, true, ErrInvalidUpdate) // future update rejected - c.setClockPeriod(7) - } - c.insertUpdate(tcBase, 6, true, nil) // when the time comes it's accepted - if reload { - c.reloadChain() - } - if enforceTime { - c.verifyRange(tcBase, 3, 6) // committee 7 is there but still in the future - c.setClockPeriod(8) - } - c.verifyRange(tcBase, 3, 7) // now period 7 can also be verified - // try reverse syncing an update - c.insertUpdate(tcBase, 2, false, ErrInvalidPeriod) // fixed committee is needed first - c.addFixedCommitteeRoot(tcBase, 2, nil) - c.addCommittee(tcBase, 2, nil) - c.insertUpdate(tcBase, 2, false, nil) - c.verifyRange(tcBase, 2, 7) - } - } -} - -func TestCommitteeChainReorg(t *testing.T) { - for _, reload := range []bool{false, true} { - for _, addBetterUpdates := range []bool{false, true} { - c := newCommitteeChainTest(t, tfBase, 300, true) - c.setClockPeriod(11) - c.addFixedCommitteeRoot(tcBase, 3, nil) - c.addFixedCommitteeRoot(tcBase, 4, nil) - c.addCommittee(tcBase, 3, nil) - for period := uint64(3); period < 10; period++ { - c.insertUpdate(tcBase, period, true, nil) - } - if reload { - c.reloadChain() - } - c.verifyRange(tcBase, 3, 10) - c.insertUpdate(tcReorgWithWorseUpdates, 5, true, ErrCannotReorg) - c.insertUpdate(tcReorgWithWorseUpdates2, 5, true, ErrCannotReorg) - if addBetterUpdates { - // add better updates for the base chain and expect first reorg to fail - // (only add updates as committees should be the same) - for period := uint64(5); period < 10; period++ { - c.insertUpdate(tcBaseWithBetterUpdates, period, false, nil) - } - if reload { - c.reloadChain() - } - c.verifyRange(tcBase, 3, 10) // still on the same chain - c.insertUpdate(tcReorgWithBetterUpdates, 5, true, ErrCannotReorg) - } else { - // reorg with better updates - c.insertUpdate(tcReorgWithBetterUpdates, 5, false, ErrNeedCommittee) - c.verifyRange(tcBase, 3, 10) // no success yet, still on the base chain - c.verifyRange(tcReorgWithBetterUpdates, 3, 5) - c.insertUpdate(tcReorgWithBetterUpdates, 5, true, nil) - // successful reorg, base chain should only match before the reorg period - if reload { - c.reloadChain() - } - c.verifyRange(tcBase, 3, 5) - c.verifyRange(tcReorgWithBetterUpdates, 3, 6) - for period := uint64(6); period < 10; period++ { - c.insertUpdate(tcReorgWithBetterUpdates, period, true, nil) - } - c.verifyRange(tcReorgWithBetterUpdates, 3, 10) - } - // reorg with finalized updates; should succeed even if base chain updates - // have been improved because a finalized update beats everything else - c.insertUpdate(tcReorgWithFinalizedUpdates, 5, false, ErrNeedCommittee) - c.insertUpdate(tcReorgWithFinalizedUpdates, 5, true, nil) - if reload { - c.reloadChain() - } - c.verifyRange(tcReorgWithFinalizedUpdates, 3, 6) - for period := uint64(6); period < 10; period++ { - c.insertUpdate(tcReorgWithFinalizedUpdates, period, true, nil) - } - c.verifyRange(tcReorgWithFinalizedUpdates, 3, 10) - } - } -} - -func TestCommitteeChainFork(t *testing.T) { - c := newCommitteeChainTest(t, tfAlternative, 300, true) - c.setClockPeriod(11) - // trying to sync a chain on an alternative fork with the base chain data - c.addFixedCommitteeRoot(tcBase, 0, nil) - c.addFixedCommitteeRoot(tcBase, 1, nil) - c.addCommittee(tcBase, 0, nil) - // shared section should sync without errors - for period := uint64(0); period < 7; period++ { - c.insertUpdate(tcBase, period, true, nil) - } - c.insertUpdate(tcBase, 7, true, ErrInvalidUpdate) // wrong fork - // committee root #7 is still the same but signatures are already signed with - // a different fork id so period 7 should only verify on the alternative fork - c.verifyRange(tcBase, 0, 6) - c.verifyRange(tcFork, 0, 7) - for period := uint64(7); period < 10; period++ { - c.insertUpdate(tcFork, period, true, nil) - } - c.verifyRange(tcFork, 0, 10) - // reload the chain while switching to the base fork - c.config = tfBase - c.reloadChain() - // updates 7..9 should be rolled back now - c.verifyRange(tcFork, 0, 6) // again, period 7 only verifies on the right fork - c.verifyRange(tcBase, 0, 7) - c.insertUpdate(tcFork, 7, true, ErrInvalidUpdate) // wrong fork - for period := uint64(7); period < 10; period++ { - c.insertUpdate(tcBase, period, true, nil) - } - c.verifyRange(tcBase, 0, 10) -} - -type committeeChainTest struct { - t *testing.T - db *memorydb.Database - clock *mclock.Simulated - config types.ChainConfig - signerThreshold int - enforceTime bool - chain *CommitteeChain -} - -func newCommitteeChainTest(t *testing.T, config types.ChainConfig, signerThreshold int, enforceTime bool) *committeeChainTest { - c := &committeeChainTest{ - t: t, - db: memorydb.New(), - clock: &mclock.Simulated{}, - config: config, - signerThreshold: signerThreshold, - enforceTime: enforceTime, - } - c.chain = newCommitteeChain(c.db, &config, signerThreshold, enforceTime, dummyVerifier{}, c.clock, func() int64 { return int64(c.clock.Now()) }) - return c -} - -func (c *committeeChainTest) reloadChain() { - c.chain = newCommitteeChain(c.db, &c.config, c.signerThreshold, c.enforceTime, dummyVerifier{}, c.clock, func() int64 { return int64(c.clock.Now()) }) -} - -func (c *committeeChainTest) setClockPeriod(period float64) { - target := mclock.AbsTime(period * float64(time.Second*12*params.SyncPeriodLength)) - wait := time.Duration(target - c.clock.Now()) - if wait < 0 { - c.t.Fatalf("Invalid setClockPeriod") - } - c.clock.Run(wait) -} - -func (c *committeeChainTest) addFixedCommitteeRoot(tc *testCommitteeChain, period uint64, expErr error) { - if err := c.chain.addFixedCommitteeRoot(period, tc.periods[period].committee.Root()); err != expErr { - c.t.Errorf("Incorrect error output from addFixedCommitteeRoot at period %d (expected %v, got %v)", period, expErr, err) - } -} - -func (c *committeeChainTest) addCommittee(tc *testCommitteeChain, period uint64, expErr error) { - if err := c.chain.addCommittee(period, tc.periods[period].committee); err != expErr { - c.t.Errorf("Incorrect error output from addCommittee at period %d (expected %v, got %v)", period, expErr, err) - } -} - -func (c *committeeChainTest) insertUpdate(tc *testCommitteeChain, period uint64, addCommittee bool, expErr error) { - var committee *types.SerializedSyncCommittee - if addCommittee { - committee = tc.periods[period+1].committee - } - if err := c.chain.InsertUpdate(tc.periods[period].update, committee); err != expErr { - c.t.Errorf("Incorrect error output from InsertUpdate at period %d (expected %v, got %v)", period, expErr, err) - } -} - -func (c *committeeChainTest) verifySignedHeader(tc *testCommitteeChain, period float64, expOk bool) { - slot := uint64(period * float64(params.SyncPeriodLength)) - signedHead := GenerateTestSignedHeader(types.Header{Slot: slot}, &tc.config, tc.periods[types.SyncPeriod(slot)].committee, slot+1, 400) - if ok, _, _ := c.chain.VerifySignedHeader(signedHead); ok != expOk { - c.t.Errorf("Incorrect output from VerifySignedHeader at period %f (expected %v, got %v)", period, expOk, ok) - } -} - -func (c *committeeChainTest) verifyRange(tc *testCommitteeChain, begin, end uint64) { - if begin > 0 { - c.verifySignedHeader(tc, float64(begin)-0.5, false) - } - for period := begin; period <= end; period++ { - c.verifySignedHeader(tc, float64(period)+0.5, true) - } - c.verifySignedHeader(tc, float64(end)+1.5, false) -} - -func newTestGenesis() types.ChainConfig { - var config types.ChainConfig - rand.Read(config.GenesisValidatorsRoot[:]) - return config -} - -func newTestForks(config types.ChainConfig, forks types.Forks) types.ChainConfig { - for _, fork := range forks { - config.AddFork(fork.Name, fork.Epoch, fork.Version) - } - return config -} - -func newTestCommitteeChain(parent *testCommitteeChain, config types.ChainConfig, newCommittees bool, begin, end int, signerCount int, finalizedHeader bool) *testCommitteeChain { - tc := &testCommitteeChain{ - config: config, - } - if parent != nil { - tc.periods = make([]testPeriod, len(parent.periods)) - copy(tc.periods, parent.periods) - } - if newCommittees { - if begin == 0 { - tc.fillCommittees(begin, end+1) - } else { - tc.fillCommittees(begin+1, end+1) - } - } - tc.fillUpdates(begin, end, signerCount, finalizedHeader) - return tc -} - -type testPeriod struct { - committee *types.SerializedSyncCommittee - update *types.LightClientUpdate -} - -type testCommitteeChain struct { - periods []testPeriod - config types.ChainConfig -} - -func (tc *testCommitteeChain) fillCommittees(begin, end int) { - if len(tc.periods) <= end { - tc.periods = append(tc.periods, make([]testPeriod, end+1-len(tc.periods))...) - } - for i := begin; i <= end; i++ { - tc.periods[i].committee = GenerateTestCommittee() - } -} - -func (tc *testCommitteeChain) fillUpdates(begin, end int, signerCount int, finalizedHeader bool) { - for i := begin; i <= end; i++ { - tc.periods[i].update = GenerateTestUpdate(&tc.config, uint64(i), tc.periods[i].committee, tc.periods[i+1].committee, signerCount, finalizedHeader) - } -} diff --git a/beacon/light/range.go b/beacon/light/range.go deleted file mode 100644 index 76ebe2381a..0000000000 --- a/beacon/light/range.go +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2023 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library 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. -// -// The go-ethereum library 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 the go-ethereum library. If not, see . - -package light - -// periodRange represents a (possibly zero-length) range of integers (sync periods). -type periodRange struct { - Start, End uint64 -} - -// isEmpty returns true if the length of the range is zero. -func (a periodRange) isEmpty() bool { - return a.End == a.Start -} - -// contains returns true if the range includes the given period. -func (a periodRange) contains(period uint64) bool { - return period >= a.Start && period < a.End -} - -// canExpand returns true if the range includes or can be expanded with the given -// period (either the range is empty or the given period is inside, right before or -// right after the range). -func (a periodRange) canExpand(period uint64) bool { - return a.isEmpty() || (period+1 >= a.Start && period <= a.End) -} - -// expand expands the range with the given period. -// This method assumes that canExpand returned true: otherwise this is a no-op. -func (a *periodRange) expand(period uint64) { - if a.isEmpty() { - a.Start, a.End = period, period+1 - return - } - if a.Start == period+1 { - a.Start-- - } - if a.End == period { - a.End++ - } -} - -// split splits the range into two ranges. The 'fromPeriod' will be the first -// element in the second range (if present). -// The original range is unchanged by this operation -func (a *periodRange) split(fromPeriod uint64) (periodRange, periodRange) { - if fromPeriod <= a.Start { - // First range empty, everything in second range, - return periodRange{}, *a - } - if fromPeriod >= a.End { - // Second range empty, everything in first range, - return *a, periodRange{} - } - x := periodRange{a.Start, fromPeriod} - y := periodRange{fromPeriod, a.End} - return x, y -} - -// each invokes the supplied function fn once per period in range -func (a *periodRange) each(fn func(uint64)) { - for p := a.Start; p < a.End; p++ { - fn(p) - } -} diff --git a/beacon/light/test_helpers.go b/beacon/light/test_helpers.go deleted file mode 100644 index f537d963a6..0000000000 --- a/beacon/light/test_helpers.go +++ /dev/null @@ -1,152 +0,0 @@ -// Copyright 2023 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library 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. -// -// The go-ethereum library 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 the go-ethereum library. If not, see . - -package light - -import ( - "crypto/rand" - "crypto/sha256" - mrand "math/rand" - - "github.com/ethereum/go-ethereum/beacon/merkle" - "github.com/ethereum/go-ethereum/beacon/params" - "github.com/ethereum/go-ethereum/beacon/types" - "github.com/ethereum/go-ethereum/common" -) - -func GenerateTestCommittee() *types.SerializedSyncCommittee { - s := new(types.SerializedSyncCommittee) - rand.Read(s[:32]) - return s -} - -func GenerateTestUpdate(config *types.ChainConfig, period uint64, committee, nextCommittee *types.SerializedSyncCommittee, signerCount int, finalizedHeader bool) *types.LightClientUpdate { - update := new(types.LightClientUpdate) - update.NextSyncCommitteeRoot = nextCommittee.Root() - var attestedHeader types.Header - if finalizedHeader { - update.FinalizedHeader = new(types.Header) - *update.FinalizedHeader, update.NextSyncCommitteeBranch = makeTestHeaderWithMerkleProof(types.SyncPeriodStart(period)+100, params.StateIndexNextSyncCommittee, merkle.Value(update.NextSyncCommitteeRoot)) - attestedHeader, update.FinalityBranch = makeTestHeaderWithMerkleProof(types.SyncPeriodStart(period)+200, params.StateIndexFinalBlock, merkle.Value(update.FinalizedHeader.Hash())) - } else { - attestedHeader, update.NextSyncCommitteeBranch = makeTestHeaderWithMerkleProof(types.SyncPeriodStart(period)+2000, params.StateIndexNextSyncCommittee, merkle.Value(update.NextSyncCommitteeRoot)) - } - update.AttestedHeader = GenerateTestSignedHeader(attestedHeader, config, committee, attestedHeader.Slot+1, signerCount) - return update -} - -func GenerateTestSignedHeader(header types.Header, config *types.ChainConfig, committee *types.SerializedSyncCommittee, signatureSlot uint64, signerCount int) types.SignedHeader { - bitmask := makeBitmask(signerCount) - signingRoot, _ := config.Forks.SigningRoot(header) - c, _ := dummyVerifier{}.deserializeSyncCommittee(committee) - return types.SignedHeader{ - Header: header, - Signature: types.SyncAggregate{ - Signers: bitmask, - Signature: makeDummySignature(c.(dummySyncCommittee), signingRoot, bitmask), - }, - SignatureSlot: signatureSlot, - } -} - -func GenerateTestCheckpoint(period uint64, committee *types.SerializedSyncCommittee) *types.BootstrapData { - header, branch := makeTestHeaderWithMerkleProof(types.SyncPeriodStart(period)+200, params.StateIndexSyncCommittee, merkle.Value(committee.Root())) - return &types.BootstrapData{ - Header: header, - Committee: committee, - CommitteeRoot: committee.Root(), - CommitteeBranch: branch, - } -} - -func makeBitmask(signerCount int) (bitmask [params.SyncCommitteeBitmaskSize]byte) { - for i := 0; i < params.SyncCommitteeSize; i++ { - if mrand.Intn(params.SyncCommitteeSize-i) < signerCount { - bitmask[i/8] += byte(1) << (i & 7) - signerCount-- - } - } - return -} - -func makeTestHeaderWithMerkleProof(slot, index uint64, value merkle.Value) (types.Header, merkle.Values) { - var branch merkle.Values - hasher := sha256.New() - for index > 1 { - var proofHash merkle.Value - rand.Read(proofHash[:]) - hasher.Reset() - if index&1 == 0 { - hasher.Write(value[:]) - hasher.Write(proofHash[:]) - } else { - hasher.Write(proofHash[:]) - hasher.Write(value[:]) - } - hasher.Sum(value[:0]) - index >>= 1 - branch = append(branch, proofHash) - } - return types.Header{Slot: slot, StateRoot: common.Hash(value)}, branch -} - -// syncCommittee holds either a blsSyncCommittee or a fake dummySyncCommittee used for testing -type syncCommittee interface{} - -// committeeSigVerifier verifies sync committee signatures (either proper BLS -// signatures or fake signatures used for testing) -type committeeSigVerifier interface { - deserializeSyncCommittee(s *types.SerializedSyncCommittee) (syncCommittee, error) - verifySignature(committee syncCommittee, signedRoot common.Hash, aggregate *types.SyncAggregate) bool -} - -// blsVerifier implements committeeSigVerifier -type blsVerifier struct{} - -// deserializeSyncCommittee implements committeeSigVerifier -func (blsVerifier) deserializeSyncCommittee(s *types.SerializedSyncCommittee) (syncCommittee, error) { - return s.Deserialize() -} - -// verifySignature implements committeeSigVerifier -func (blsVerifier) verifySignature(committee syncCommittee, signingRoot common.Hash, aggregate *types.SyncAggregate) bool { - return committee.(*types.SyncCommittee).VerifySignature(signingRoot, aggregate) -} - -type dummySyncCommittee [32]byte - -// dummyVerifier implements committeeSigVerifier -type dummyVerifier struct{} - -// deserializeSyncCommittee implements committeeSigVerifier -func (dummyVerifier) deserializeSyncCommittee(s *types.SerializedSyncCommittee) (syncCommittee, error) { - var sc dummySyncCommittee - copy(sc[:], s[:32]) - return sc, nil -} - -// verifySignature implements committeeSigVerifier -func (dummyVerifier) verifySignature(committee syncCommittee, signingRoot common.Hash, aggregate *types.SyncAggregate) bool { - return aggregate.Signature == makeDummySignature(committee.(dummySyncCommittee), signingRoot, aggregate.Signers) -} - -func makeDummySignature(committee dummySyncCommittee, signingRoot common.Hash, bitmask [params.SyncCommitteeBitmaskSize]byte) (sig [params.BLSSignatureSize]byte) { - for i, b := range committee[:] { - sig[i] = b ^ signingRoot[i] - } - copy(sig[32:], bitmask[:]) - return -} diff --git a/beacon/merkle/merkle.go b/beacon/merkle/merkle.go deleted file mode 100644 index 30896f9b01..0000000000 --- a/beacon/merkle/merkle.go +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2022 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library 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. -// -// The go-ethereum library 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 the go-ethereum library. If not, see . - -// Package merkle implements proof verifications in binary merkle trees. -package merkle - -import ( - "crypto/sha256" - "errors" - "reflect" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" -) - -// Value represents either a 32 byte leaf value or hash node in a binary merkle tree/partial proof. -type Value [32]byte - -// Values represent a series of merkle tree leaves/nodes. -type Values []Value - -var valueT = reflect.TypeOf(Value{}) - -// UnmarshalJSON parses a merkle value in hex syntax. -func (m *Value) UnmarshalJSON(input []byte) error { - return hexutil.UnmarshalFixedJSON(valueT, input, m[:]) -} - -// VerifyProof verifies a Merkle proof branch for a single value in a -// binary Merkle tree (index is a generalized tree index). -func VerifyProof(root common.Hash, index uint64, branch Values, value Value) error { - hasher := sha256.New() - for _, sibling := range branch { - hasher.Reset() - if index&1 == 0 { - hasher.Write(value[:]) - hasher.Write(sibling[:]) - } else { - hasher.Write(sibling[:]) - hasher.Write(value[:]) - } - hasher.Sum(value[:0]) - if index >>= 1; index == 0 { - return errors.New("branch has extra items") - } - } - if index != 1 { - return errors.New("branch is missing items") - } - if common.Hash(value) != root { - return errors.New("root mismatch") - } - return nil -} diff --git a/beacon/params/params.go b/beacon/params/params.go deleted file mode 100644 index ee9feb1acb..0000000000 --- a/beacon/params/params.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2022 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library 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. -// -// The go-ethereum library 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 the go-ethereum library. If not, see . - -package params - -const ( - EpochLength = 32 - SyncPeriodLength = 8192 - - BLSSignatureSize = 96 - BLSPubkeySize = 48 - - SyncCommitteeSize = 512 - SyncCommitteeBitmaskSize = SyncCommitteeSize / 8 - SyncCommitteeSupermajority = (SyncCommitteeSize*2 + 2) / 3 -) - -const ( - StateIndexGenesisTime = 32 - StateIndexGenesisValidators = 33 - StateIndexForkVersion = 141 - StateIndexLatestHeader = 36 - StateIndexBlockRoots = 37 - StateIndexStateRoots = 38 - StateIndexHistoricRoots = 39 - StateIndexFinalBlock = 105 - StateIndexSyncCommittee = 54 - StateIndexNextSyncCommittee = 55 - StateIndexExecPayload = 56 - StateIndexExecHead = 908 -) diff --git a/beacon/types/committee.go b/beacon/types/committee.go deleted file mode 100644 index 5f89c27554..0000000000 --- a/beacon/types/committee.go +++ /dev/null @@ -1,191 +0,0 @@ -// Copyright 2023 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library 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. -// -// The go-ethereum library 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 the go-ethereum library. If not, see . - -package types - -import ( - "crypto/sha256" - "encoding/json" - "fmt" - "math/bits" - - "github.com/ethereum/go-ethereum/beacon/params" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - bls "github.com/protolambda/bls12-381-util" -) - -// SerializedSyncCommitteeSize is the size of the sync committee plus the -// aggregate public key. -const SerializedSyncCommitteeSize = (params.SyncCommitteeSize + 1) * params.BLSPubkeySize - -// SerializedSyncCommittee is the serialized version of a sync committee -// plus the aggregate public key. -type SerializedSyncCommittee [SerializedSyncCommitteeSize]byte - -// jsonSyncCommittee is the JSON representation of a sync committee. -// -// See data structure definition here: -// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/beacon-chain.md#syncaggregate -type jsonSyncCommittee struct { - Pubkeys []hexutil.Bytes `json:"pubkeys"` - Aggregate hexutil.Bytes `json:"aggregate_pubkey"` -} - -// MarshalJSON implements json.Marshaler. -func (s *SerializedSyncCommittee) MarshalJSON() ([]byte, error) { - sc := jsonSyncCommittee{Pubkeys: make([]hexutil.Bytes, params.SyncCommitteeSize)} - for i := range sc.Pubkeys { - sc.Pubkeys[i] = make(hexutil.Bytes, params.BLSPubkeySize) - copy(sc.Pubkeys[i][:], s[i*params.BLSPubkeySize:(i+1)*params.BLSPubkeySize]) - } - sc.Aggregate = make(hexutil.Bytes, params.BLSPubkeySize) - copy(sc.Aggregate[:], s[params.SyncCommitteeSize*params.BLSPubkeySize:]) - return json.Marshal(&sc) -} - -// UnmarshalJSON implements json.Marshaler. -func (s *SerializedSyncCommittee) UnmarshalJSON(input []byte) error { - var sc jsonSyncCommittee - if err := json.Unmarshal(input, &sc); err != nil { - return err - } - if len(sc.Pubkeys) != params.SyncCommitteeSize { - return fmt.Errorf("invalid number of pubkeys %d", len(sc.Pubkeys)) - } - for i, key := range sc.Pubkeys { - if len(key) != params.BLSPubkeySize { - return fmt.Errorf("pubkey %d has invalid size %d", i, len(key)) - } - copy(s[i*params.BLSPubkeySize:], key[:]) - } - if len(sc.Aggregate) != params.BLSPubkeySize { - return fmt.Errorf("invalid aggregate pubkey size %d", len(sc.Aggregate)) - } - copy(s[params.SyncCommitteeSize*params.BLSPubkeySize:], sc.Aggregate[:]) - return nil -} - -// Root calculates the root hash of the binary tree representation of a sync -// committee provided in serialized format. -// -// TODO(zsfelfoldi): Get rid of this when SSZ encoding lands. -func (s *SerializedSyncCommittee) Root() common.Hash { - var ( - hasher = sha256.New() - padding [64 - params.BLSPubkeySize]byte - data [params.SyncCommitteeSize]common.Hash - l = params.SyncCommitteeSize - ) - for i := range data { - hasher.Reset() - hasher.Write(s[i*params.BLSPubkeySize : (i+1)*params.BLSPubkeySize]) - hasher.Write(padding[:]) - hasher.Sum(data[i][:0]) - } - for l > 1 { - for i := 0; i < l/2; i++ { - hasher.Reset() - hasher.Write(data[i*2][:]) - hasher.Write(data[i*2+1][:]) - hasher.Sum(data[i][:0]) - } - l /= 2 - } - hasher.Reset() - hasher.Write(s[SerializedSyncCommitteeSize-params.BLSPubkeySize : SerializedSyncCommitteeSize]) - hasher.Write(padding[:]) - hasher.Sum(data[1][:0]) - hasher.Reset() - hasher.Write(data[0][:]) - hasher.Write(data[1][:]) - hasher.Sum(data[0][:0]) - return data[0] -} - -// Deserialize splits open the pubkeys into proper BLS key types. -func (s *SerializedSyncCommittee) Deserialize() (*SyncCommittee, error) { - sc := new(SyncCommittee) - for i := 0; i <= params.SyncCommitteeSize; i++ { - key := new(bls.Pubkey) - - var bytes [params.BLSPubkeySize]byte - copy(bytes[:], s[i*params.BLSPubkeySize:(i+1)*params.BLSPubkeySize]) - - if err := key.Deserialize(&bytes); err != nil { - return nil, err - } - if i < params.SyncCommitteeSize { - sc.keys[i] = key - } else { - sc.aggregate = key - } - } - return sc, nil -} - -// SyncCommittee is a set of sync committee signer pubkeys and the aggregate key. -// -// See data structure definition here: -// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/beacon-chain.md#syncaggregate -type SyncCommittee struct { - keys [params.SyncCommitteeSize]*bls.Pubkey - aggregate *bls.Pubkey -} - -// VerifySignature returns true if the given sync aggregate is a valid signature -// or the given hash. -func (sc *SyncCommittee) VerifySignature(signingRoot common.Hash, signature *SyncAggregate) bool { - var ( - sig bls.Signature - keys = make([]*bls.Pubkey, 0, params.SyncCommitteeSize) - ) - if err := sig.Deserialize(&signature.Signature); err != nil { - return false - } - for i, key := range sc.keys { - if signature.Signers[i/8]&(byte(1)<<(i%8)) != 0 { - keys = append(keys, key) - } - } - return bls.FastAggregateVerify(keys, signingRoot[:], &sig) -} - -//go:generate go run github.com/fjl/gencodec -type SyncAggregate -field-override syncAggregateMarshaling -out gen_syncaggregate_json.go - -// SyncAggregate represents an aggregated BLS signature with Signers referring -// to a subset of the corresponding sync committee. -// -// See data structure definition here: -// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/beacon-chain.md#syncaggregate -type SyncAggregate struct { - Signers [params.SyncCommitteeBitmaskSize]byte `gencodec:"required" json:"sync_committee_bits"` - Signature [params.BLSSignatureSize]byte `gencodec:"required" json:"sync_committee_signature"` -} - -type syncAggregateMarshaling struct { - Signers hexutil.Bytes - Signature hexutil.Bytes -} - -// SignerCount returns the number of signers in the aggregate signature. -func (s *SyncAggregate) SignerCount() int { - var count int - for _, v := range s.Signers { - count += bits.OnesCount8(v) - } - return count -} diff --git a/beacon/types/config.go b/beacon/types/config.go deleted file mode 100644 index 8cb8808b6f..0000000000 --- a/beacon/types/config.go +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright 2022 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library 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. -// -// The go-ethereum library 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 the go-ethereum library. If not, see . - -package types - -import ( - "crypto/sha256" - "fmt" - "os" - "sort" - "strconv" - "strings" - - "github.com/ethereum/go-ethereum/beacon/merkle" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "gopkg.in/yaml.v3" -) - -// syncCommitteeDomain specifies the signatures specific use to avoid clashes -// across signing different data structures. -const syncCommitteeDomain = 7 - -// Fork describes a single beacon chain fork and also stores the calculated -// signature domain used after this fork. -type Fork struct { - // Name of the fork in the chain config (config.yaml) file{ - Name string - - // Epoch when given fork version is activated - Epoch uint64 - - // Fork version, see https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#custom-types - Version []byte - - // calculated by computeDomain, based on fork version and genesis validators root - domain merkle.Value -} - -// computeDomain returns the signature domain based on the given fork version -// and genesis validator set root. -func (f *Fork) computeDomain(genesisValidatorsRoot common.Hash) { - var ( - hasher = sha256.New() - forkVersion32 merkle.Value - forkDataRoot merkle.Value - ) - copy(forkVersion32[:], f.Version) - hasher.Write(forkVersion32[:]) - hasher.Write(genesisValidatorsRoot[:]) - hasher.Sum(forkDataRoot[:0]) - - f.domain[0] = syncCommitteeDomain - copy(f.domain[4:], forkDataRoot[:28]) -} - -// Forks is the list of all beacon chain forks in the chain configuration. -type Forks []*Fork - -// domain returns the signature domain for the given epoch (assumes that domains -// have already been calculated). -func (f Forks) domain(epoch uint64) (merkle.Value, error) { - for i := len(f) - 1; i >= 0; i-- { - if epoch >= f[i].Epoch { - return f[i].domain, nil - } - } - return merkle.Value{}, fmt.Errorf("unknown fork for epoch %d", epoch) -} - -// SigningRoot calculates the signing root of the given header. -func (f Forks) SigningRoot(header Header) (common.Hash, error) { - domain, err := f.domain(header.Epoch()) - if err != nil { - return common.Hash{}, err - } - var ( - signingRoot common.Hash - headerHash = header.Hash() - hasher = sha256.New() - ) - hasher.Write(headerHash[:]) - hasher.Write(domain[:]) - hasher.Sum(signingRoot[:0]) - - return signingRoot, nil -} - -func (f Forks) Len() int { return len(f) } -func (f Forks) Swap(i, j int) { f[i], f[j] = f[j], f[i] } -func (f Forks) Less(i, j int) bool { return f[i].Epoch < f[j].Epoch } - -// ChainConfig contains the beacon chain configuration. -type ChainConfig struct { - GenesisTime uint64 // Unix timestamp of slot 0 - GenesisValidatorsRoot common.Hash // Root hash of the genesis validator set, used for signature domain calculation - Forks Forks -} - -// AddFork adds a new item to the list of forks. -func (c *ChainConfig) AddFork(name string, epoch uint64, version []byte) *ChainConfig { - fork := &Fork{ - Name: name, - Epoch: epoch, - Version: version, - } - fork.computeDomain(c.GenesisValidatorsRoot) - - c.Forks = append(c.Forks, fork) - sort.Sort(c.Forks) - - return c -} - -// LoadForks parses the beacon chain configuration file (config.yaml) and extracts -// the list of forks. -func (c *ChainConfig) LoadForks(path string) error { - file, err := os.ReadFile(path) - if err != nil { - return fmt.Errorf("failed to read beacon chain config file: %v", err) - } - config := make(map[string]string) - if err := yaml.Unmarshal(file, &config); err != nil { - return fmt.Errorf("failed to parse beacon chain config file: %v", err) - } - var ( - versions = make(map[string][]byte) - epochs = make(map[string]uint64) - ) - epochs["GENESIS"] = 0 - - for key, value := range config { - if strings.HasSuffix(key, "_FORK_VERSION") { - name := key[:len(key)-len("_FORK_VERSION")] - if v, err := hexutil.Decode(value); err == nil { - versions[name] = v - } else { - return fmt.Errorf("failed to decode hex fork id %q in beacon chain config file: %v", value, err) - } - } - if strings.HasSuffix(key, "_FORK_EPOCH") { - name := key[:len(key)-len("_FORK_EPOCH")] - if v, err := strconv.ParseUint(value, 10, 64); err == nil { - epochs[name] = v - } else { - return fmt.Errorf("failed to parse epoch number %q in beacon chain config file: %v", value, err) - } - } - } - for name, epoch := range epochs { - if version, ok := versions[name]; ok { - delete(versions, name) - c.AddFork(name, epoch, version) - } else { - return fmt.Errorf("fork id missing for %q in beacon chain config file", name) - } - } - for name := range versions { - return fmt.Errorf("epoch number missing for fork %q in beacon chain config file", name) - } - sort.Sort(c.Forks) - return nil -} diff --git a/beacon/types/gen_header_json.go b/beacon/types/gen_header_json.go deleted file mode 100644 index 9b3ffea06f..0000000000 --- a/beacon/types/gen_header_json.go +++ /dev/null @@ -1,66 +0,0 @@ -// Code generated by github.com/fjl/gencodec. DO NOT EDIT. - -package types - -import ( - "encoding/json" - "errors" - - "github.com/ethereum/go-ethereum/common" -) - -var _ = (*headerMarshaling)(nil) - -// MarshalJSON marshals as JSON. -func (h Header) MarshalJSON() ([]byte, error) { - type Header struct { - Slot common.Decimal `gencodec:"required" json:"slot"` - ProposerIndex common.Decimal `gencodec:"required" json:"proposer_index"` - ParentRoot common.Hash `gencodec:"required" json:"parent_root"` - StateRoot common.Hash `gencodec:"required" json:"state_root"` - BodyRoot common.Hash `gencodec:"required" json:"body_root"` - } - var enc Header - enc.Slot = common.Decimal(h.Slot) - enc.ProposerIndex = common.Decimal(h.ProposerIndex) - enc.ParentRoot = h.ParentRoot - enc.StateRoot = h.StateRoot - enc.BodyRoot = h.BodyRoot - return json.Marshal(&enc) -} - -// UnmarshalJSON unmarshals from JSON. -func (h *Header) UnmarshalJSON(input []byte) error { - type Header struct { - Slot *common.Decimal `gencodec:"required" json:"slot"` - ProposerIndex *common.Decimal `gencodec:"required" json:"proposer_index"` - ParentRoot *common.Hash `gencodec:"required" json:"parent_root"` - StateRoot *common.Hash `gencodec:"required" json:"state_root"` - BodyRoot *common.Hash `gencodec:"required" json:"body_root"` - } - var dec Header - if err := json.Unmarshal(input, &dec); err != nil { - return err - } - if dec.Slot == nil { - return errors.New("missing required field 'slot' for Header") - } - h.Slot = uint64(*dec.Slot) - if dec.ProposerIndex == nil { - return errors.New("missing required field 'proposer_index' for Header") - } - h.ProposerIndex = uint64(*dec.ProposerIndex) - if dec.ParentRoot == nil { - return errors.New("missing required field 'parent_root' for Header") - } - h.ParentRoot = *dec.ParentRoot - if dec.StateRoot == nil { - return errors.New("missing required field 'state_root' for Header") - } - h.StateRoot = *dec.StateRoot - if dec.BodyRoot == nil { - return errors.New("missing required field 'body_root' for Header") - } - h.BodyRoot = *dec.BodyRoot - return nil -} diff --git a/beacon/types/gen_syncaggregate_json.go b/beacon/types/gen_syncaggregate_json.go deleted file mode 100644 index 1547ec5f01..0000000000 --- a/beacon/types/gen_syncaggregate_json.go +++ /dev/null @@ -1,51 +0,0 @@ -// Code generated by github.com/fjl/gencodec. DO NOT EDIT. - -package types - -import ( - "encoding/json" - "errors" - - "github.com/ethereum/go-ethereum/common/hexutil" -) - -var _ = (*syncAggregateMarshaling)(nil) - -// MarshalJSON marshals as JSON. -func (s SyncAggregate) MarshalJSON() ([]byte, error) { - type SyncAggregate struct { - Signers hexutil.Bytes `gencodec:"required" json:"sync_committee_bits"` - Signature hexutil.Bytes `gencodec:"required" json:"sync_committee_signature"` - } - var enc SyncAggregate - enc.Signers = s.Signers[:] - enc.Signature = s.Signature[:] - return json.Marshal(&enc) -} - -// UnmarshalJSON unmarshals from JSON. -func (s *SyncAggregate) UnmarshalJSON(input []byte) error { - type SyncAggregate struct { - Signers *hexutil.Bytes `gencodec:"required" json:"sync_committee_bits"` - Signature *hexutil.Bytes `gencodec:"required" json:"sync_committee_signature"` - } - var dec SyncAggregate - if err := json.Unmarshal(input, &dec); err != nil { - return err - } - if dec.Signers == nil { - return errors.New("missing required field 'sync_committee_bits' for SyncAggregate") - } - if len(*dec.Signers) != len(s.Signers) { - return errors.New("field 'sync_committee_bits' has wrong length, need 64 items") - } - copy(s.Signers[:], *dec.Signers) - if dec.Signature == nil { - return errors.New("missing required field 'sync_committee_signature' for SyncAggregate") - } - if len(*dec.Signature) != len(s.Signature) { - return errors.New("field 'sync_committee_signature' has wrong length, need 96 items") - } - copy(s.Signature[:], *dec.Signature) - return nil -} diff --git a/beacon/types/header.go b/beacon/types/header.go deleted file mode 100644 index 2ddc4575f1..0000000000 --- a/beacon/types/header.go +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright 2022 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library 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. -// -// The go-ethereum library 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 the go-ethereum library. If not, see . - -// Package types implements a few types of the beacon chain for light client usage. -package types - -import ( - "crypto/sha256" - "encoding/binary" - - "github.com/ethereum/go-ethereum/beacon/merkle" - "github.com/ethereum/go-ethereum/beacon/params" - "github.com/ethereum/go-ethereum/common" -) - -//go:generate go run github.com/fjl/gencodec -type Header -field-override headerMarshaling -out gen_header_json.go - -const ( - headerIndexSlot = 8 - headerIndexProposerIndex = 9 - headerIndexParentRoot = 10 - headerIndexStateRoot = 11 - headerIndexBodyRoot = 12 -) - -// Header defines a beacon header. -// -// See data structure definition here: -// https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#beaconblockheader -type Header struct { - // Monotonically increasing slot number for the beacon block (may be gapped) - Slot uint64 `gencodec:"required" json:"slot"` - - // Index into the validator table who created the beacon block - ProposerIndex uint64 `gencodec:"required" json:"proposer_index"` - - // SSZ hash of the parent beacon header - ParentRoot common.Hash `gencodec:"required" json:"parent_root"` - - // SSZ hash of the beacon state (https://github.com/ethereum/consensus-specs/blob/dev/specs/bellatrix/beacon-chain.md#beacon-state) - StateRoot common.Hash `gencodec:"required" json:"state_root"` - - // SSZ hash of the beacon block body (https://github.com/ethereum/consensus-specs/blob/dev/specs/bellatrix/beacon-chain.md#beaconblockbody) - BodyRoot common.Hash `gencodec:"required" json:"body_root"` -} - -// headerMarshaling is a field type overrides for gencodec. -type headerMarshaling struct { - Slot common.Decimal - ProposerIndex common.Decimal -} - -// Hash calculates the block root of the header. -// -// TODO(zsfelfoldi): Remove this when an SSZ encoder lands. -func (h *Header) Hash() common.Hash { - var values [16]merkle.Value // values corresponding to indices 8 to 15 of the beacon header tree - binary.LittleEndian.PutUint64(values[headerIndexSlot][:8], h.Slot) - binary.LittleEndian.PutUint64(values[headerIndexProposerIndex][:8], h.ProposerIndex) - values[headerIndexParentRoot] = merkle.Value(h.ParentRoot) - values[headerIndexStateRoot] = merkle.Value(h.StateRoot) - values[headerIndexBodyRoot] = merkle.Value(h.BodyRoot) - hasher := sha256.New() - for i := 7; i > 0; i-- { - hasher.Reset() - hasher.Write(values[i*2][:]) - hasher.Write(values[i*2+1][:]) - hasher.Sum(values[i][:0]) - } - return common.Hash(values[1]) -} - -// Epoch returns the epoch the header belongs to. -func (h *Header) Epoch() uint64 { - return h.Slot / params.EpochLength -} - -// SyncPeriod returns the sync period the header belongs to. -func (h *Header) SyncPeriod() uint64 { - return SyncPeriod(h.Slot) -} - -// SyncPeriodStart returns the first slot of the given period. -func SyncPeriodStart(period uint64) uint64 { - return period * params.SyncPeriodLength -} - -// SyncPeriod returns the sync period that the given slot belongs to. -func SyncPeriod(slot uint64) uint64 { - return slot / params.SyncPeriodLength -} - -// SignedHeader represents a beacon header signed by a sync committee. -// -// This structure is created from either an optimistic update or an instant update: -// - https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#lightclientoptimisticupdate -// - https://github.com/zsfelfoldi/beacon-APIs/blob/instant_update/apis/beacon/light_client/instant_update.yaml -type SignedHeader struct { - // Beacon header being signed - Header Header - - // Sync committee BLS signature aggregate - Signature SyncAggregate - - // Slot in which the signature has been created (newer than Header.Slot, - // determines the signing sync committee) - SignatureSlot uint64 -} diff --git a/beacon/types/light_sync.go b/beacon/types/light_sync.go deleted file mode 100644 index 3284081e4d..0000000000 --- a/beacon/types/light_sync.go +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright 2022 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library 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. -// -// The go-ethereum library 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 the go-ethereum library. If not, see . - -package types - -import ( - "errors" - "fmt" - - "github.com/ethereum/go-ethereum/beacon/merkle" - "github.com/ethereum/go-ethereum/beacon/params" - "github.com/ethereum/go-ethereum/common" -) - -// BootstrapData contains a sync committee where light sync can be started, -// together with a proof through a beacon header and corresponding state. -// Note: BootstrapData is fetched from a server based on a known checkpoint hash. -type BootstrapData struct { - Header Header - CommitteeRoot common.Hash - Committee *SerializedSyncCommittee `rlp:"-"` - CommitteeBranch merkle.Values -} - -// Validate verifies the proof included in BootstrapData. -func (c *BootstrapData) Validate() error { - if c.CommitteeRoot != c.Committee.Root() { - return errors.New("wrong committee root") - } - return merkle.VerifyProof(c.Header.StateRoot, params.StateIndexSyncCommittee, c.CommitteeBranch, merkle.Value(c.CommitteeRoot)) -} - -// LightClientUpdate is a proof of the next sync committee root based on a header -// signed by the sync committee of the given period. Optionally, the update can -// prove quasi-finality by the signed header referring to a previous, finalized -// header from the same period, and the finalized header referring to the next -// sync committee root. -// -// See data structure definition here: -// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#lightclientupdate -type LightClientUpdate struct { - AttestedHeader SignedHeader // Arbitrary header out of the period signed by the sync committee - NextSyncCommitteeRoot common.Hash // Sync committee of the next period advertised in the current one - NextSyncCommitteeBranch merkle.Values // Proof for the next period's sync committee - - FinalizedHeader *Header `rlp:"nil"` // Optional header to announce a point of finality - FinalityBranch merkle.Values // Proof for the announced finality - - score *UpdateScore // Weight of the update to compare between competing ones -} - -// Validate verifies the validity of the update. -func (update *LightClientUpdate) Validate() error { - period := update.AttestedHeader.Header.SyncPeriod() - if SyncPeriod(update.AttestedHeader.SignatureSlot) != period { - return errors.New("signature slot and signed header are from different periods") - } - if update.FinalizedHeader != nil { - if update.FinalizedHeader.SyncPeriod() != period { - return errors.New("finalized header is from different period") - } - if err := merkle.VerifyProof(update.AttestedHeader.Header.StateRoot, params.StateIndexFinalBlock, update.FinalityBranch, merkle.Value(update.FinalizedHeader.Hash())); err != nil { - return fmt.Errorf("invalid finalized header proof: %w", err) - } - } - if err := merkle.VerifyProof(update.AttestedHeader.Header.StateRoot, params.StateIndexNextSyncCommittee, update.NextSyncCommitteeBranch, merkle.Value(update.NextSyncCommitteeRoot)); err != nil { - return fmt.Errorf("invalid next sync committee proof: %w", err) - } - return nil -} - -// Score returns the UpdateScore describing the proof strength of the update -// Note: thread safety can be ensured by always calling Score on a newly received -// or decoded update before making it potentially available for other threads -func (update *LightClientUpdate) Score() UpdateScore { - if update.score == nil { - update.score = &UpdateScore{ - SignerCount: uint32(update.AttestedHeader.Signature.SignerCount()), - SubPeriodIndex: uint32(update.AttestedHeader.Header.Slot & 0x1fff), - FinalizedHeader: update.FinalizedHeader != nil, - } - } - return *update.score -} - -// UpdateScore allows the comparison between updates at the same period in order -// to find the best update chain that provides the strongest proof of being canonical. -// -// UpdateScores have a tightly packed binary encoding format for efficient p2p -// protocol transmission. Each UpdateScore is encoded in 3 bytes. -// When interpreted as a 24 bit little indian unsigned integer: -// - the lowest 10 bits contain the number of signers in the header signature aggregate -// - the next 13 bits contain the "sub-period index" which is he signed header's -// slot modulo params.SyncPeriodLength (which is correlated with the risk of the chain being -// re-orged before the previous period boundary in case of non-finalized updates) -// - the highest bit is set when the update is finalized (meaning that the finality -// header referenced by the signed header is in the same period as the signed -// header, making reorgs before the period boundary impossible -type UpdateScore struct { - SignerCount uint32 // number of signers in the header signature aggregate - SubPeriodIndex uint32 // signed header's slot modulo params.SyncPeriodLength - FinalizedHeader bool // update is considered finalized if has finalized header from the same period and 2/3 signatures -} - -// finalized returns true if the update has a header signed by at least 2/3 of -// the committee, referring to a finalized header that refers to the next sync -// committee. This condition is a close approximation of the actual finality -// condition that can only be verified by full beacon nodes. -func (u *UpdateScore) finalized() bool { - return u.FinalizedHeader && u.SignerCount >= params.SyncCommitteeSupermajority -} - -// BetterThan returns true if update u is considered better than w. -func (u UpdateScore) BetterThan(w UpdateScore) bool { - var ( - uFinalized = u.finalized() - wFinalized = w.finalized() - ) - if uFinalized != wFinalized { - return uFinalized - } - return u.SignerCount > w.SignerCount -}