From 972190796be5e08489a24c48ce37dc70b052f97c Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Fri, 22 Sep 2023 10:29:11 +0200 Subject: [PATCH 1/4] verkle proofs in blocks on top of capella --- eth2/beacon/verkle/block.go | 325 ++++++++++ eth2/beacon/verkle/bls_to_execution.go | 82 +++ eth2/beacon/verkle/execution.go | 400 ++++++++++++ eth2/beacon/verkle/execution_payload.go | 81 +++ eth2/beacon/verkle/execution_witness.go | 488 ++++++++++++++ eth2/beacon/verkle/execution_witness_test.go | 182 ++++++ eth2/beacon/verkle/fork.go | 172 +++++ eth2/beacon/verkle/history.go | 97 +++ eth2/beacon/verkle/state.go | 628 +++++++++++++++++++ eth2/beacon/verkle/transition.go | 348 ++++++++++ go.mod | 2 +- go.sum | 2 + 12 files changed, 2806 insertions(+), 1 deletion(-) create mode 100644 eth2/beacon/verkle/block.go create mode 100644 eth2/beacon/verkle/bls_to_execution.go create mode 100644 eth2/beacon/verkle/execution.go create mode 100644 eth2/beacon/verkle/execution_payload.go create mode 100644 eth2/beacon/verkle/execution_witness.go create mode 100644 eth2/beacon/verkle/execution_witness_test.go create mode 100644 eth2/beacon/verkle/fork.go create mode 100644 eth2/beacon/verkle/history.go create mode 100644 eth2/beacon/verkle/state.go create mode 100644 eth2/beacon/verkle/transition.go diff --git a/eth2/beacon/verkle/block.go b/eth2/beacon/verkle/block.go new file mode 100644 index 0000000..233d9aa --- /dev/null +++ b/eth2/beacon/verkle/block.go @@ -0,0 +1,325 @@ +package verkle + +import ( + "fmt" + + "github.com/protolambda/ztyp/codec" + "github.com/protolambda/ztyp/tree" + . "github.com/protolambda/ztyp/view" + + "github.com/protolambda/zrnt/eth2/beacon/altair" + "github.com/protolambda/zrnt/eth2/beacon/common" + "github.com/protolambda/zrnt/eth2/beacon/phase0" +) + +type SignedBeaconBlock struct { + Message BeaconBlock `json:"message" yaml:"message"` + Signature common.BLSSignature `json:"signature" yaml:"signature"` +} + +var _ common.EnvelopeBuilder = (*SignedBeaconBlock)(nil) + +func (b *SignedBeaconBlock) Envelope(spec *common.Spec, digest common.ForkDigest) *common.BeaconBlockEnvelope { + header := b.Message.Header(spec) + return &common.BeaconBlockEnvelope{ + ForkDigest: digest, + BeaconBlockHeader: *header, + Body: &b.Message.Body, + BlockRoot: header.HashTreeRoot(tree.GetHashFn()), + Signature: b.Signature, + } +} + +func (b *SignedBeaconBlock) Deserialize(spec *common.Spec, dr *codec.DecodingReader) error { + return dr.Container(spec.Wrap(&b.Message), &b.Signature) +} + +func (b *SignedBeaconBlock) Serialize(spec *common.Spec, w *codec.EncodingWriter) error { + return w.Container(spec.Wrap(&b.Message), &b.Signature) +} + +func (b *SignedBeaconBlock) ByteLength(spec *common.Spec) uint64 { + return codec.ContainerLength(spec.Wrap(&b.Message), &b.Signature) +} + +func (a *SignedBeaconBlock) FixedLength(*common.Spec) uint64 { + return 0 +} + +func (b *SignedBeaconBlock) HashTreeRoot(spec *common.Spec, hFn tree.HashFn) common.Root { + return hFn.HashTreeRoot(spec.Wrap(&b.Message), b.Signature) +} + +func (block *SignedBeaconBlock) SignedHeader(spec *common.Spec) *common.SignedBeaconBlockHeader { + return &common.SignedBeaconBlockHeader{ + Message: *block.Message.Header(spec), + Signature: block.Signature, + } +} + +type BeaconBlock struct { + Slot common.Slot `json:"slot" yaml:"slot"` + ProposerIndex common.ValidatorIndex `json:"proposer_index" yaml:"proposer_index"` + ParentRoot common.Root `json:"parent_root" yaml:"parent_root"` + StateRoot common.Root `json:"state_root" yaml:"state_root"` + Body BeaconBlockBody `json:"body" yaml:"body"` +} + +func (b *BeaconBlock) Deserialize(spec *common.Spec, dr *codec.DecodingReader) error { + return dr.Container(&b.Slot, &b.ProposerIndex, &b.ParentRoot, &b.StateRoot, spec.Wrap(&b.Body)) +} + +func (b *BeaconBlock) Serialize(spec *common.Spec, w *codec.EncodingWriter) error { + return w.Container(&b.Slot, &b.ProposerIndex, &b.ParentRoot, &b.StateRoot, spec.Wrap(&b.Body)) +} + +func (b *BeaconBlock) ByteLength(spec *common.Spec) uint64 { + return codec.ContainerLength(&b.Slot, &b.ProposerIndex, &b.ParentRoot, &b.StateRoot, spec.Wrap(&b.Body)) +} + +func (a *BeaconBlock) FixedLength(*common.Spec) uint64 { + return 0 +} + +func (b *BeaconBlock) HashTreeRoot(spec *common.Spec, hFn tree.HashFn) common.Root { + return hFn.HashTreeRoot(b.Slot, b.ProposerIndex, b.ParentRoot, b.StateRoot, spec.Wrap(&b.Body)) +} + +func BeaconBlockType(spec *common.Spec) *ContainerTypeDef { + return ContainerType("BeaconBlock", []FieldDef{ + {"slot", common.SlotType}, + {"proposer_index", common.ValidatorIndexType}, + {"parent_root", RootType}, + {"state_root", RootType}, + {"body", BeaconBlockBodyType(spec)}, + }) +} + +func SignedBeaconBlockType(spec *common.Spec) *ContainerTypeDef { + return ContainerType("SignedBeaconBlock", []FieldDef{ + {"message", BeaconBlockType(spec)}, + {"signature", common.BLSSignatureType}, + }) +} + +func (block *BeaconBlock) Header(spec *common.Spec) *common.BeaconBlockHeader { + return &common.BeaconBlockHeader{ + Slot: block.Slot, + ProposerIndex: block.ProposerIndex, + ParentRoot: block.ParentRoot, + StateRoot: block.StateRoot, + BodyRoot: block.Body.HashTreeRoot(spec, tree.GetHashFn()), + } +} + +type BeaconBlockBody struct { + RandaoReveal common.BLSSignature `json:"randao_reveal" yaml:"randao_reveal"` + Eth1Data common.Eth1Data `json:"eth1_data" yaml:"eth1_data"` + Graffiti common.Root `json:"graffiti" yaml:"graffiti"` + + ProposerSlashings phase0.ProposerSlashings `json:"proposer_slashings" yaml:"proposer_slashings"` + AttesterSlashings phase0.AttesterSlashings `json:"attester_slashings" yaml:"attester_slashings"` + Attestations phase0.Attestations `json:"attestations" yaml:"attestations"` + Deposits phase0.Deposits `json:"deposits" yaml:"deposits"` + VoluntaryExits phase0.VoluntaryExits `json:"voluntary_exits" yaml:"voluntary_exits"` + + SyncAggregate altair.SyncAggregate `json:"sync_aggregate" yaml:"sync_aggregate"` + + ExecutionPayload ExecutionPayload `json:"execution_payload" yaml:"execution_payload"` + + BLSToExecutionChanges common.SignedBLSToExecutionChanges `json:"bls_to_execution_changes" yaml:"bls_to_execution_changes"` +} + +func (b *BeaconBlockBody) Deserialize(spec *common.Spec, dr *codec.DecodingReader) error { + return dr.Container( + &b.RandaoReveal, &b.Eth1Data, + &b.Graffiti, spec.Wrap(&b.ProposerSlashings), + spec.Wrap(&b.AttesterSlashings), spec.Wrap(&b.Attestations), + spec.Wrap(&b.Deposits), spec.Wrap(&b.VoluntaryExits), + spec.Wrap(&b.SyncAggregate), spec.Wrap(&b.ExecutionPayload), + spec.Wrap(&b.BLSToExecutionChanges), + ) +} + +func (b *BeaconBlockBody) Serialize(spec *common.Spec, w *codec.EncodingWriter) error { + return w.Container( + &b.RandaoReveal, &b.Eth1Data, + &b.Graffiti, spec.Wrap(&b.ProposerSlashings), + spec.Wrap(&b.AttesterSlashings), spec.Wrap(&b.Attestations), + spec.Wrap(&b.Deposits), spec.Wrap(&b.VoluntaryExits), + spec.Wrap(&b.SyncAggregate), spec.Wrap(&b.ExecutionPayload), + spec.Wrap(&b.BLSToExecutionChanges), + ) +} + +func (b *BeaconBlockBody) ByteLength(spec *common.Spec) uint64 { + return codec.ContainerLength( + &b.RandaoReveal, &b.Eth1Data, + &b.Graffiti, spec.Wrap(&b.ProposerSlashings), + spec.Wrap(&b.AttesterSlashings), spec.Wrap(&b.Attestations), + spec.Wrap(&b.Deposits), spec.Wrap(&b.VoluntaryExits), + spec.Wrap(&b.SyncAggregate), spec.Wrap(&b.ExecutionPayload), + spec.Wrap(&b.BLSToExecutionChanges), + ) +} + +func (a *BeaconBlockBody) FixedLength(*common.Spec) uint64 { + return 0 +} + +func (b *BeaconBlockBody) HashTreeRoot(spec *common.Spec, hFn tree.HashFn) common.Root { + return hFn.HashTreeRoot( + b.RandaoReveal, &b.Eth1Data, + b.Graffiti, spec.Wrap(&b.ProposerSlashings), + spec.Wrap(&b.AttesterSlashings), spec.Wrap(&b.Attestations), + spec.Wrap(&b.Deposits), spec.Wrap(&b.VoluntaryExits), + spec.Wrap(&b.SyncAggregate), spec.Wrap(&b.ExecutionPayload), + spec.Wrap(&b.BLSToExecutionChanges), + ) +} + +func (b *BeaconBlockBody) CheckLimits(spec *common.Spec) error { + if x := uint64(len(b.ProposerSlashings)); x > uint64(spec.MAX_PROPOSER_SLASHINGS) { + return fmt.Errorf("too many proposer slashings: %d", x) + } + if x := uint64(len(b.AttesterSlashings)); x > uint64(spec.MAX_ATTESTER_SLASHINGS) { + return fmt.Errorf("too many attester slashings: %d", x) + } + if x := uint64(len(b.Attestations)); x > uint64(spec.MAX_ATTESTATIONS) { + return fmt.Errorf("too many attestations: %d", x) + } + if x := uint64(len(b.Deposits)); x > uint64(spec.MAX_DEPOSITS) { + return fmt.Errorf("too many deposits: %d", x) + } + if x := uint64(len(b.VoluntaryExits)); x > uint64(spec.MAX_VOLUNTARY_EXITS) { + return fmt.Errorf("too many voluntary exits: %d", x) + } + // TODO: also check sum of byte size, sanity check block size. + if x := uint64(len(b.ExecutionPayload.Transactions)); x > uint64(spec.MAX_TRANSACTIONS_PER_PAYLOAD) { + return fmt.Errorf("too many transactions: %d", x) + } + if x := uint64(len(b.BLSToExecutionChanges)); x > uint64(spec.MAX_BLS_TO_EXECUTION_CHANGES) { + return fmt.Errorf("too many bls-to-execution changes: %d", x) + } + return nil +} + +func (b *BeaconBlockBody) Shallow(spec *common.Spec) *BeaconBlockBodyShallow { + return &BeaconBlockBodyShallow{ + RandaoReveal: b.RandaoReveal, + Eth1Data: b.Eth1Data, + Graffiti: b.Graffiti, + ProposerSlashings: b.ProposerSlashings, + AttesterSlashings: b.AttesterSlashings, + Attestations: b.Attestations, + Deposits: b.Deposits, + VoluntaryExits: b.VoluntaryExits, + SyncAggregate: b.SyncAggregate, + ExecutionPayloadRoot: b.ExecutionPayload.HashTreeRoot(spec, tree.GetHashFn()), + BLSToExecutionChanges: b.BLSToExecutionChanges, + } +} + +func BeaconBlockBodyType(spec *common.Spec) *ContainerTypeDef { + return ContainerType("BeaconBlockBody", []FieldDef{ + {"randao_reveal", common.BLSSignatureType}, + {"eth1_data", common.Eth1DataType}, // Eth1 data vote + {"graffiti", common.Bytes32Type}, // Arbitrary data + // Operations + {"proposer_slashings", phase0.BlockProposerSlashingsType(spec)}, + {"attester_slashings", phase0.BlockAttesterSlashingsType(spec)}, + {"attestations", phase0.BlockAttestationsType(spec)}, + {"deposits", phase0.BlockDepositsType(spec)}, + {"voluntary_exits", phase0.BlockVoluntaryExitsType(spec)}, + {"sync_aggregate", altair.SyncAggregateType(spec)}, + // Capella + {"execution_payload", ExecutionPayloadType(spec)}, + {"bls_to_execution_changes", common.BlockSignedBLSToExecutionChangesType(spec)}, + }) +} + +type BeaconBlockBodyShallow struct { + RandaoReveal common.BLSSignature `json:"randao_reveal" yaml:"randao_reveal"` + Eth1Data common.Eth1Data `json:"eth1_data" yaml:"eth1_data"` + Graffiti common.Root `json:"graffiti" yaml:"graffiti"` + + ProposerSlashings phase0.ProposerSlashings `json:"proposer_slashings" yaml:"proposer_slashings"` + AttesterSlashings phase0.AttesterSlashings `json:"attester_slashings" yaml:"attester_slashings"` + Attestations phase0.Attestations `json:"attestations" yaml:"attestations"` + Deposits phase0.Deposits `json:"deposits" yaml:"deposits"` + VoluntaryExits phase0.VoluntaryExits `json:"voluntary_exits" yaml:"voluntary_exits"` + + SyncAggregate altair.SyncAggregate `json:"sync_aggregate" yaml:"sync_aggregate"` + + ExecutionPayloadRoot common.Root `json:"execution_payload_root" yaml:"execution_payload_root"` + + BLSToExecutionChanges common.SignedBLSToExecutionChanges `json:"bls_to_execution_changes" yaml:"bls_to_execution_changes"` +} + +func (b *BeaconBlockBodyShallow) Deserialize(spec *common.Spec, dr *codec.DecodingReader) error { + return dr.Container( + &b.RandaoReveal, &b.Eth1Data, + &b.Graffiti, spec.Wrap(&b.ProposerSlashings), + spec.Wrap(&b.AttesterSlashings), spec.Wrap(&b.Attestations), + spec.Wrap(&b.Deposits), spec.Wrap(&b.VoluntaryExits), + spec.Wrap(&b.SyncAggregate), &b.ExecutionPayloadRoot, + spec.Wrap(&b.BLSToExecutionChanges), + ) +} + +func (b *BeaconBlockBodyShallow) Serialize(spec *common.Spec, w *codec.EncodingWriter) error { + return w.Container( + &b.RandaoReveal, &b.Eth1Data, + &b.Graffiti, spec.Wrap(&b.ProposerSlashings), + spec.Wrap(&b.AttesterSlashings), spec.Wrap(&b.Attestations), + spec.Wrap(&b.Deposits), spec.Wrap(&b.VoluntaryExits), + spec.Wrap(&b.SyncAggregate), &b.ExecutionPayloadRoot, + spec.Wrap(&b.BLSToExecutionChanges), + ) +} + +func (b *BeaconBlockBodyShallow) ByteLength(spec *common.Spec) uint64 { + return codec.ContainerLength( + &b.RandaoReveal, &b.Eth1Data, + &b.Graffiti, spec.Wrap(&b.ProposerSlashings), + spec.Wrap(&b.AttesterSlashings), spec.Wrap(&b.Attestations), + spec.Wrap(&b.Deposits), spec.Wrap(&b.VoluntaryExits), + spec.Wrap(&b.SyncAggregate), &b.ExecutionPayloadRoot, + spec.Wrap(&b.BLSToExecutionChanges), + ) +} + +func (a *BeaconBlockBodyShallow) FixedLength(*common.Spec) uint64 { + return 0 +} + +func (b *BeaconBlockBodyShallow) HashTreeRoot(spec *common.Spec, hFn tree.HashFn) common.Root { + return hFn.HashTreeRoot( + b.RandaoReveal, &b.Eth1Data, + b.Graffiti, spec.Wrap(&b.ProposerSlashings), + spec.Wrap(&b.AttesterSlashings), spec.Wrap(&b.Attestations), + spec.Wrap(&b.Deposits), spec.Wrap(&b.VoluntaryExits), + spec.Wrap(&b.SyncAggregate), &b.ExecutionPayloadRoot, + spec.Wrap(&b.BLSToExecutionChanges), + ) +} + +func (b *BeaconBlockBodyShallow) WithExecutionPayload(spec *common.Spec, payload ExecutionPayload) (*BeaconBlockBody, error) { + payloadRoot := payload.HashTreeRoot(spec, tree.GetHashFn()) + if b.ExecutionPayloadRoot != payloadRoot { + return nil, fmt.Errorf("payload does not match expected root: %s <> %s", b.ExecutionPayloadRoot, payloadRoot) + } + return &BeaconBlockBody{ + RandaoReveal: b.RandaoReveal, + Eth1Data: b.Eth1Data, + Graffiti: b.Graffiti, + ProposerSlashings: b.ProposerSlashings, + AttesterSlashings: b.AttesterSlashings, + Attestations: b.Attestations, + Deposits: b.Deposits, + VoluntaryExits: b.VoluntaryExits, + SyncAggregate: b.SyncAggregate, + ExecutionPayload: payload, + BLSToExecutionChanges: b.BLSToExecutionChanges, + }, nil +} diff --git a/eth2/beacon/verkle/bls_to_execution.go b/eth2/beacon/verkle/bls_to_execution.go new file mode 100644 index 0000000..90945bc --- /dev/null +++ b/eth2/beacon/verkle/bls_to_execution.go @@ -0,0 +1,82 @@ +package verkle + +import ( + "bytes" + "context" + "fmt" + + blsu "github.com/protolambda/bls12-381-util" + "github.com/protolambda/ztyp/tree" + + "github.com/protolambda/zrnt/eth2/beacon/common" + "github.com/protolambda/zrnt/eth2/util/hashing" +) + +func ProcessBLSToExecutionChanges(ctx context.Context, spec *common.Spec, epc *common.EpochsContext, state common.BeaconState, ops common.SignedBLSToExecutionChanges) error { + for i := range ops { + if err := ctx.Err(); err != nil { + return err + } + if err := ProcessBLSToExecutionChange(ctx, spec, epc, state, &ops[i]); err != nil { + return err + } + } + return nil +} + +func ProcessBLSToExecutionChange(ctx context.Context, spec *common.Spec, epc *common.EpochsContext, state common.BeaconState, op *common.SignedBLSToExecutionChange) error { + validators, err := state.Validators() + if err != nil { + return err + } + validatorCount, err := validators.ValidatorCount() + if err != nil { + return err + } + + addressChange := op.BLSToExecutionChange + if uint64(addressChange.ValidatorIndex) >= validatorCount { + return fmt.Errorf("invalid validator index for bls to execution change") + } + + validator, err := validators.Validator(addressChange.ValidatorIndex) + if err != nil { + return err + } + + validatorWithdrawalCredentials, err := validator.WithdrawalCredentials() + if err != nil { + return err + } + if !bytes.Equal(validatorWithdrawalCredentials[:1], []byte{common.BLS_WITHDRAWAL_PREFIX}) { + return fmt.Errorf("invalid bls to execution change, validator not bls: %v", validatorWithdrawalCredentials) + } + sigHash := hashing.Hash(addressChange.FromBLSPubKey[:]) + if !bytes.Equal(validatorWithdrawalCredentials[1:], sigHash[1:]) { + return fmt.Errorf("invalid bls to execution change, incorrect public key: got %v, want %v", addressChange.FromBLSPubKey, validatorWithdrawalCredentials) + } + genesisValidatorsRoot, err := state.GenesisValidatorsRoot() + if err != nil { + return err + } + domain := common.ComputeDomain(common.DOMAIN_BLS_TO_EXECUTION_CHANGE, spec.GENESIS_FORK_VERSION, genesisValidatorsRoot) + + sigRoot := common.ComputeSigningRoot(addressChange.HashTreeRoot(tree.GetHashFn()), domain) + pubKey, err := addressChange.FromBLSPubKey.Pubkey() + if err != nil { + return err + } + + signature, err := op.Signature.Signature() + if err != nil { + return err + } + + if !blsu.Verify(pubKey, sigRoot[:], signature) { + return fmt.Errorf("invalid bls to execution change signature") + } + var newWithdrawalCredentials tree.Root + copy(newWithdrawalCredentials[0:1], []byte{common.ETH1_ADDRESS_WITHDRAWAL_PREFIX}) + copy(newWithdrawalCredentials[12:], addressChange.ToExecutionAddress[:]) + return validator.SetWithdrawalCredentials(newWithdrawalCredentials) +} diff --git a/eth2/beacon/verkle/execution.go b/eth2/beacon/verkle/execution.go new file mode 100644 index 0000000..630fcdc --- /dev/null +++ b/eth2/beacon/verkle/execution.go @@ -0,0 +1,400 @@ +package verkle + +import ( + "context" + "fmt" + + "github.com/protolambda/ztyp/codec" + "github.com/protolambda/ztyp/tree" + . "github.com/protolambda/ztyp/view" + + "github.com/protolambda/zrnt/eth2/beacon/common" +) + +const ( + __parentHash = iota + __feeRecipient + __stateRoot + __receiptsRoot + __logsBloom + __prevRandao + __blockNumber + __gasLimit + __gasUsed + __timestamp + __extraData + __baseFeePerGas + __blockHash + __transactionsRoot + __withdrawalsRoot + __verkleproofRoot + __end + + MAX_STEMS = 1 << 16 + MAX_COMMITMENTS_PER_STEM = 33 // = 31 for inner nodes + 2 (C1/C2) + VERKLE_WIDTH = 256 + IPA_PROOF_DEPTH = 8 // = log2(VERKLE_WIDTH) +) + +var StemType RootMeta = 31 + +var SuffixStateDiffType = ContainerType("SuffixStateDiff", []FieldDef{ + {"suffix", ByteType}, + {"current_value", UnionType([]TypeDef{nil, common.Bytes32Type})}, + {"new_value", UnionType([]TypeDef{nil, common.Bytes32Type})}, +}) + +var StemStateDiffType = ContainerType("StemStateDiff", []FieldDef{ + {"stem", StemType}, + {"suffix_diffs", ListType(SuffixStateDiffType, VERKLE_WIDTH)}, +}) + +var IpaProofType = ContainerType("IpaProof", []FieldDef{ + {"C_L", VectorType(common.Bytes32Type, IPA_PROOF_DEPTH)}, + {"C_R", VectorType(common.Bytes32Type, IPA_PROOF_DEPTH)}, + {"final_evaluation", common.Bytes32Type}, +}) + +var VerkleProofFields = []FieldDef{ + {"other_stems", ListType(StemType, MAX_STEMS)}, + {"depth_extension_present", ListType(Uint8Type, MAX_STEMS)}, + {"commitments_by_path", ListType(common.Bytes32Type, MAX_STEMS*MAX_COMMITMENTS_PER_STEM)}, + {"D", common.Bytes32Type}, + {"ipa_poof", IpaProofType}, +} + +var VerkleProofType = ContainerType("VerkleProof", VerkleProofFields) + +var ExecutionWitnessFields = []FieldDef{ + {"state_diff", ListType(StemStateDiffType, MAX_STEMS)}, + {"verkle_proof", VerkleProofType}, +} + +var ExecutionWitnessType = ContainerType("ExecutionWitness", ExecutionWitnessFields) + +var ExecutionPayloadHeaderType = ContainerType("ExecutionPayloadHeader", []FieldDef{ + {"parent_hash", common.Hash32Type}, + {"fee_recipient", common.Eth1AddressType}, + {"state_root", common.Bytes32Type}, + {"receipts_root", common.Bytes32Type}, + {"logs_bloom", common.LogsBloomType}, + {"prev_randao", common.Bytes32Type}, + {"block_number", Uint64Type}, + {"gas_limit", Uint64Type}, + {"gas_used", Uint64Type}, + {"timestamp", common.TimestampType}, + {"extra_data", common.ExtraDataType}, + {"base_fee_per_gas", Uint256Type}, + {"block_hash", common.Hash32Type}, + {"transactions_root", RootType}, + {"withdrawals_root", RootType}, +}) + +type ExecutionPayloadHeaderView struct { + *ContainerView +} + +func (v *ExecutionPayloadHeaderView) Raw() (*ExecutionPayloadHeader, error) { + values, err := v.FieldValues() + if err != nil { + return nil, err + } + if len(values) != __end { + return nil, fmt.Errorf("unexpected number of execution payload header fields: %d", len(values)) + } + parentHash, err := AsRoot(values[__parentHash], err) + feeRecipient, err := common.AsEth1Address(values[__feeRecipient], err) + stateRoot, err := AsRoot(values[__stateRoot], err) + receiptsRoot, err := AsRoot(values[__receiptsRoot], err) + logsBloomView, err := common.AsLogsBloom(values[__logsBloom], err) + prevRandao, err := AsRoot(values[__prevRandao], err) + blockNumber, err := AsUint64(values[__blockNumber], err) + gasLimit, err := AsUint64(values[__gasLimit], err) + gasUsed, err := AsUint64(values[__gasUsed], err) + timestamp, err := common.AsTimestamp(values[__timestamp], err) + extraDataView, err := common.AsExtraData(values[__extraData], err) + baseFeePerGas, err := AsUint256(values[__baseFeePerGas], err) + blockHash, err := AsRoot(values[__blockHash], err) + transactionsRoot, err := AsRoot(values[__transactionsRoot], err) + withdrawalsRoot, err := AsRoot(values[__withdrawalsRoot], err) + executionWitnessRoot, err := AsRoot(values[__verkleproofRoot], err) + if err != nil { + return nil, err + } + logsBloom, err := logsBloomView.Raw() + if err != nil { + return nil, err + } + extraData, err := extraDataView.Raw() + if err != nil { + return nil, err + } + return &ExecutionPayloadHeader{ + ParentHash: parentHash, + FeeRecipient: feeRecipient, + StateRoot: stateRoot, + ReceiptsRoot: receiptsRoot, + LogsBloom: *logsBloom, + PrevRandao: prevRandao, + BlockNumber: blockNumber, + GasLimit: gasLimit, + GasUsed: gasUsed, + Timestamp: timestamp, + ExtraData: extraData, + BaseFeePerGas: baseFeePerGas, + BlockHash: blockHash, + TransactionsRoot: transactionsRoot, + WithdrawalsRoot: withdrawalsRoot, + ExecutionWitnessRoot: executionWitnessRoot, + }, nil +} + +func (v *ExecutionPayloadHeaderView) ParentHash() (common.Hash32, error) { + return AsRoot(v.Get(__parentHash)) +} + +func (v *ExecutionPayloadHeaderView) FeeRecipient() (common.Eth1Address, error) { + return common.AsEth1Address(v.Get(__feeRecipient)) +} + +func (v *ExecutionPayloadHeaderView) StateRoot() (common.Bytes32, error) { + return AsRoot(v.Get(__stateRoot)) +} + +func (v *ExecutionPayloadHeaderView) ReceiptRoot() (common.Bytes32, error) { + return AsRoot(v.Get(__receiptsRoot)) +} + +func (v *ExecutionPayloadHeaderView) LogsBloom() (*common.LogsBloom, error) { + logV, err := common.AsLogsBloom(v.Get(__logsBloom)) + if err != nil { + return nil, err + } + return logV.Raw() +} + +func (v *ExecutionPayloadHeaderView) Random() (common.Bytes32, error) { + return AsRoot(v.Get(__prevRandao)) +} + +func (v *ExecutionPayloadHeaderView) BlockNumber() (Uint64View, error) { + return AsUint64(v.Get(__blockNumber)) +} + +func (v *ExecutionPayloadHeaderView) GasLimit() (Uint64View, error) { + return AsUint64(v.Get(__gasLimit)) +} + +func (v *ExecutionPayloadHeaderView) GasUsed() (Uint64View, error) { + return AsUint64(v.Get(__gasUsed)) +} + +func (v *ExecutionPayloadHeaderView) Timestamp() (common.Timestamp, error) { + return common.AsTimestamp(v.Get(__timestamp)) +} + +func (v *ExecutionPayloadHeaderView) BaseFeePerGas() (Uint256View, error) { + return AsUint256(v.Get(__baseFeePerGas)) +} + +func (v *ExecutionPayloadHeaderView) BlockHash() (common.Hash32, error) { + return AsRoot(v.Get(__blockHash)) +} + +func (v *ExecutionPayloadHeaderView) TransactionsRoot() (common.Root, error) { + return AsRoot(v.Get(__transactionsRoot)) +} + +func AsExecutionPayloadHeader(v View, err error) (*ExecutionPayloadHeaderView, error) { + c, err := AsContainer(v, err) + return &ExecutionPayloadHeaderView{c}, err +} + +type ExecutionPayloadHeader struct { + ParentHash common.Hash32 `json:"parent_hash" yaml:"parent_hash"` + FeeRecipient common.Eth1Address `json:"fee_recipient" yaml:"fee_recipient"` + StateRoot common.Bytes32 `json:"state_root" yaml:"state_root"` + ReceiptsRoot common.Bytes32 `json:"receipts_root" yaml:"receipts_root"` + LogsBloom common.LogsBloom `json:"logs_bloom" yaml:"logs_bloom"` + PrevRandao common.Bytes32 `json:"prev_randao" yaml:"prev_randao"` + BlockNumber Uint64View `json:"block_number" yaml:"block_number"` + GasLimit Uint64View `json:"gas_limit" yaml:"gas_limit"` + GasUsed Uint64View `json:"gas_used" yaml:"gas_used"` + Timestamp common.Timestamp `json:"timestamp" yaml:"timestamp"` + ExtraData common.ExtraData `json:"extra_data" yaml:"extra_data"` + BaseFeePerGas Uint256View `json:"base_fee_per_gas" yaml:"base_fee_per_gas"` + BlockHash common.Hash32 `json:"block_hash" yaml:"block_hash"` + TransactionsRoot common.Root `json:"transactions_root" yaml:"transactions_root"` + WithdrawalsRoot common.Root `json:"withdrawals_root" yaml:"withdrawals_root"` + ExecutionWitnessRoot common.Root `json:"execution_witness_root" yaml:"execution_witness_root"` +} + +func (s *ExecutionPayloadHeader) View() *ExecutionPayloadHeaderView { + ed, err := s.ExtraData.View() + if err != nil { + panic(err) + } + pr, cb, sr, rr := (*RootView)(&s.ParentHash), s.FeeRecipient.View(), (*RootView)(&s.StateRoot), (*RootView)(&s.ReceiptsRoot) + lb, rng, nr, gl, gu := s.LogsBloom.View(), (*RootView)(&s.PrevRandao), s.BlockNumber, s.GasLimit, s.GasUsed + ts, bf, bh, tr := Uint64View(s.Timestamp), &s.BaseFeePerGas, (*RootView)(&s.BlockHash), (*RootView)(&s.TransactionsRoot) + wr := (*RootView)(&s.WithdrawalsRoot) + ew := (*RootView)(&s.ExecutionWitnessRoot) + + v, err := AsExecutionPayloadHeader(ExecutionPayloadHeaderType.FromFields(pr, cb, sr, rr, lb, rng, nr, gl, gu, ts, ed, bf, bh, tr, wr, ew)) + if err != nil { + panic(err) + } + return v +} + +func (s *ExecutionPayloadHeader) Deserialize(dr *codec.DecodingReader) error { + return dr.Container(&s.ParentHash, &s.FeeRecipient, &s.StateRoot, + &s.ReceiptsRoot, &s.LogsBloom, &s.PrevRandao, &s.BlockNumber, &s.GasLimit, + &s.GasUsed, &s.Timestamp, &s.ExtraData, &s.BaseFeePerGas, &s.BlockHash, &s.TransactionsRoot, + &s.WithdrawalsRoot, &s.ExecutionWitnessRoot, + ) +} + +func (s *ExecutionPayloadHeader) Serialize(w *codec.EncodingWriter) error { + return w.Container(&s.ParentHash, &s.FeeRecipient, &s.StateRoot, + &s.ReceiptsRoot, &s.LogsBloom, &s.PrevRandao, &s.BlockNumber, &s.GasLimit, + &s.GasUsed, &s.Timestamp, &s.ExtraData, &s.BaseFeePerGas, &s.BlockHash, &s.TransactionsRoot, + &s.WithdrawalsRoot, &s.ExecutionWitnessRoot, + ) +} + +func (s *ExecutionPayloadHeader) ByteLength() uint64 { + return codec.ContainerLength(&s.ParentHash, &s.FeeRecipient, &s.StateRoot, + &s.ReceiptsRoot, &s.LogsBloom, &s.PrevRandao, &s.BlockNumber, &s.GasLimit, + &s.GasUsed, &s.Timestamp, &s.ExtraData, &s.BaseFeePerGas, &s.BlockHash, &s.TransactionsRoot, + &s.WithdrawalsRoot, &s.ExecutionWitnessRoot, + ) +} + +func (b *ExecutionPayloadHeader) FixedLength() uint64 { + return 0 +} + +func (s *ExecutionPayloadHeader) HashTreeRoot(hFn tree.HashFn) common.Root { + return hFn.HashTreeRoot(&s.ParentHash, &s.FeeRecipient, &s.StateRoot, + &s.ReceiptsRoot, &s.LogsBloom, &s.PrevRandao, &s.BlockNumber, &s.GasLimit, + &s.GasUsed, &s.Timestamp, &s.ExtraData, &s.BaseFeePerGas, &s.BlockHash, &s.TransactionsRoot, + &s.WithdrawalsRoot, s.ExecutionWitnessRoot, + ) +} + +func ExecutionPayloadType(spec *common.Spec) *ContainerTypeDef { + return ContainerType("ExecutionPayload", []FieldDef{ + {"parent_hash", common.Hash32Type}, + {"fee_recipient", common.Eth1AddressType}, + {"state_root", common.Bytes32Type}, + {"receipts_root", common.Bytes32Type}, + {"logs_bloom", common.LogsBloomType}, + {"prev_randao", common.Bytes32Type}, + {"block_number", Uint64Type}, + {"gas_limit", Uint64Type}, + {"gas_used", Uint64Type}, + {"timestamp", common.TimestampType}, + {"extra_data", common.ExtraDataType}, + {"base_fee_per_gas", Uint256Type}, + {"block_hash", common.Hash32Type}, + {"transactions", common.PayloadTransactionsType(spec)}, + {"withdrawals", common.WithdrawalsType(spec)}, + {"execution_witness", ExecutionWitnessType}, + }) +} + +type ExecutionPayloadView struct { + *ContainerView +} + +func AsExecutionPayload(v View, err error) (*ExecutionPayloadView, error) { + c, err := AsContainer(v, err) + return &ExecutionPayloadView{c}, err +} + +type ExecutionPayload struct { + ParentHash common.Hash32 `json:"parent_hash" yaml:"parent_hash"` + FeeRecipient common.Eth1Address `json:"fee_recipient" yaml:"fee_recipient"` + StateRoot common.Bytes32 `json:"state_root" yaml:"state_root"` + ReceiptsRoot common.Bytes32 `json:"receipts_root" yaml:"receipts_root"` + LogsBloom common.LogsBloom `json:"logs_bloom" yaml:"logs_bloom"` + PrevRandao common.Bytes32 `json:"prev_randao" yaml:"prev_randao"` + BlockNumber Uint64View `json:"block_number" yaml:"block_number"` + GasLimit Uint64View `json:"gas_limit" yaml:"gas_limit"` + GasUsed Uint64View `json:"gas_used" yaml:"gas_used"` + Timestamp common.Timestamp `json:"timestamp" yaml:"timestamp"` + ExtraData common.ExtraData `json:"extra_data" yaml:"extra_data"` + BaseFeePerGas Uint256View `json:"base_fee_per_gas" yaml:"base_fee_per_gas"` + BlockHash common.Hash32 `json:"block_hash" yaml:"block_hash"` + Transactions common.PayloadTransactions `json:"transactions" yaml:"transactions"` + Withdrawals common.Withdrawals `json:"withdrawals" yaml:"withdrawals"` + ExecutionWitness ExecutionWitness `json:"execution_witness", yaml:"execution_witness"` +} + +func (s *ExecutionPayload) Deserialize(spec *common.Spec, dr *codec.DecodingReader) error { + return dr.Container(&s.ParentHash, &s.FeeRecipient, &s.StateRoot, + &s.ReceiptsRoot, &s.LogsBloom, &s.PrevRandao, &s.BlockNumber, &s.GasLimit, + &s.GasUsed, &s.Timestamp, &s.ExtraData, &s.BaseFeePerGas, &s.BlockHash, spec.Wrap(&s.Transactions), + spec.Wrap(&s.Withdrawals), &s.ExecutionWitness, + ) +} + +func (s *ExecutionPayload) Serialize(spec *common.Spec, w *codec.EncodingWriter) error { + return w.Container(&s.ParentHash, &s.FeeRecipient, &s.StateRoot, + &s.ReceiptsRoot, &s.LogsBloom, &s.PrevRandao, &s.BlockNumber, &s.GasLimit, + &s.GasUsed, &s.Timestamp, &s.ExtraData, &s.BaseFeePerGas, &s.BlockHash, spec.Wrap(&s.Transactions), + spec.Wrap(&s.Withdrawals), &s.ExecutionWitness, + ) +} + +func (s *ExecutionPayload) ByteLength(spec *common.Spec) uint64 { + return codec.ContainerLength(&s.ParentHash, &s.FeeRecipient, &s.StateRoot, + &s.ReceiptsRoot, &s.LogsBloom, &s.PrevRandao, &s.BlockNumber, &s.GasLimit, + &s.GasUsed, &s.Timestamp, &s.ExtraData, &s.BaseFeePerGas, &s.BlockHash, spec.Wrap(&s.Transactions), + spec.Wrap(&s.Withdrawals), &s.ExecutionWitness, + ) +} + +func (a *ExecutionPayload) FixedLength(*common.Spec) uint64 { + // transactions list is not fixed length, so the whole thing is not fixed length. + return 0 +} + +func (s *ExecutionPayload) HashTreeRoot(spec *common.Spec, hFn tree.HashFn) common.Root { + return hFn.HashTreeRoot(&s.ParentHash, &s.FeeRecipient, &s.StateRoot, + &s.ReceiptsRoot, &s.LogsBloom, &s.PrevRandao, &s.BlockNumber, &s.GasLimit, + &s.GasUsed, &s.Timestamp, &s.ExtraData, &s.BaseFeePerGas, &s.BlockHash, spec.Wrap(&s.Transactions), + spec.Wrap(&s.Withdrawals), &s.ExecutionWitness, + ) +} + +func (ep *ExecutionPayload) Header(spec *common.Spec) *ExecutionPayloadHeader { + return &ExecutionPayloadHeader{ + ParentHash: ep.ParentHash, + FeeRecipient: ep.FeeRecipient, + StateRoot: ep.StateRoot, + ReceiptsRoot: ep.ReceiptsRoot, + LogsBloom: ep.LogsBloom, + PrevRandao: ep.PrevRandao, + BlockNumber: ep.BlockNumber, + GasLimit: ep.GasLimit, + GasUsed: ep.GasUsed, + Timestamp: ep.Timestamp, + ExtraData: ep.ExtraData, + BaseFeePerGas: ep.BaseFeePerGas, + BlockHash: ep.BlockHash, + TransactionsRoot: ep.Transactions.HashTreeRoot(spec, tree.GetHashFn()), + WithdrawalsRoot: ep.Withdrawals.HashTreeRoot(spec, tree.GetHashFn()), + ExecutionWitnessRoot: ep.ExecutionWitness.HashTreeRoot(tree.GetHashFn()), + } +} + +func (ep *ExecutionPayload) GetWitdrawals() []common.Withdrawal { + return ep.Withdrawals +} + +type ExecutionEngine interface { + ExecutePayload(ctx context.Context, executionPayload *ExecutionPayload) (valid bool, err error) + // TODO: remaining interface parts +} diff --git a/eth2/beacon/verkle/execution_payload.go b/eth2/beacon/verkle/execution_payload.go new file mode 100644 index 0000000..5c92f9b --- /dev/null +++ b/eth2/beacon/verkle/execution_payload.go @@ -0,0 +1,81 @@ +package verkle + +import ( + "context" + "errors" + "fmt" + + "github.com/protolambda/zrnt/eth2/beacon/common" +) + +func ProcessExecutionPayload(ctx context.Context, spec *common.Spec, state ExecutionTrackingBeaconState, executionPayload *ExecutionPayload, engine common.ExecutionEngine) error { + if err := ctx.Err(); err != nil { + return err + } + if engine == nil { + return errors.New("nil execution engine") + } + + slot, err := state.Slot() + if err != nil { + return err + } + + completed := true + if s, ok := state.(ExecutionUpgradeBeaconState); ok { + var err error + completed, err = s.IsTransitionCompleted() + if err != nil { + return err + } + } + if completed { + latestExecHeader, err := state.LatestExecutionPayloadHeader() + if err != nil { + return err + } + parent, err := latestExecHeader.Raw() + if err != nil { + return fmt.Errorf("failed to read previous header: %v", err) + } + if executionPayload.ParentHash != parent.BlockHash { + return fmt.Errorf("expected parent hash %s in execution payload, but got %s", + parent.BlockHash, executionPayload.ParentHash) + } + } + + // verify random + mixes, err := state.RandaoMixes() + if err != nil { + return err + } + expectedMix, err := mixes.GetRandomMix(spec.SlotToEpoch(slot)) + if err != nil { + return err + } + if executionPayload.PrevRandao != expectedMix { + return fmt.Errorf("invalid random data %s, expected %s", executionPayload.PrevRandao, expectedMix) + } + + // verify timestamp + genesisTime, err := state.GenesisTime() + if err != nil { + return err + } + if expectedTime, err := spec.TimeAtSlot(slot, genesisTime); err != nil { + return fmt.Errorf("slot or genesis time in state is corrupt, cannot compute time: %v", err) + } else if executionPayload.Timestamp != expectedTime { + return fmt.Errorf("state at slot %d, genesis time %d, expected execution payload time %d, but got %d", + slot, genesisTime, expectedTime, executionPayload.Timestamp) + } + + if valid, err := engine.ExecutePayload(ctx, executionPayload); err != nil { + return fmt.Errorf("unexpected problem in execution engine when inserting block %s (height %d), err: %v", + executionPayload.BlockHash, executionPayload.BlockNumber, err) + } else if !valid { + return fmt.Errorf("execution engine says payload is invalid: %s (height %d)", + executionPayload.BlockHash, executionPayload.BlockNumber) + } + + return state.SetLatestExecutionPayloadHeader(executionPayload.Header(spec)) +} diff --git a/eth2/beacon/verkle/execution_witness.go b/eth2/beacon/verkle/execution_witness.go new file mode 100644 index 0000000..83b9179 --- /dev/null +++ b/eth2/beacon/verkle/execution_witness.go @@ -0,0 +1,488 @@ +package verkle + +import ( + "fmt" + + "github.com/protolambda/zrnt/eth2/beacon/common" + "github.com/protolambda/ztyp/codec" + "github.com/protolambda/ztyp/tree" +) + +type Stem [31]byte +type Value []byte +type Suffix byte + +func (s *Stem) Deserialize(dr *codec.DecodingReader) error { + _, err := dr.Read(s[:]) + return err +} + +func (*Stem) FixedLength() uint64 { + return 31 +} + +func (s *Stem) HashTreeRoot(h tree.HashFn) tree.Root { + return h.ByteVectorHTR(s[:]) +} + +func (s *Stem) Serialize(w *codec.EncodingWriter) error { + return w.Write(s[:]) +} + +func (s *Stem) ByteLength() uint64 { + return 31 +} + +func (s *Suffix) Deserialize(dr *codec.DecodingReader) error { + b, err := dr.ReadByte() + *s = Suffix(b) + return err +} + +func (s *Suffix) HashTreeRoot(h tree.HashFn) tree.Root { + return tree.Root{0: byte(*s)} +} + +func (*Suffix) FixedLength() uint64 { + return 1 +} + +func (s *Suffix) Serialize(w *codec.EncodingWriter) error { + return w.WriteByte(byte(*s)) +} + +func (s *Suffix) ByteLength() uint64 { + return 1 +} + +func (ov *Value) Deserialize(dr *codec.DecodingReader) error { + selector, err := dr.ReadByte() + if err != nil { + return err + } + + if selector == 0 { + return nil + } + + *ov = Value(make([]byte, 32)) + n, err := dr.Read((*ov)[:]) + if err != nil { + return err + } + if n != 32 { + return fmt.Errorf("invalid read length: 32 != %d", n) + } + return nil +} + +func (ov *Value) ByteLength() uint64 { + if ov == nil { + return 1 + } + + return 33 +} + +func (ov *Value) FixedLength() uint64 { + return 0 +} + +func (ov *Value) Serialize(w *codec.EncodingWriter) error { + if ov == nil || len(*ov) == 0 { + return w.Union(0, nil) + } + + err := w.WriteByte(1) + if err != nil { + return err + } + + return w.Write((*ov)[:]) +} + +func (ov *Value) HashTreeRoot(h tree.HashFn) tree.Root { + if ov == nil { + return h.Union(0, nil) + } + + return h.Union(1, ov) +} + +// tree.import[32]byte{0:31} +type SuffixStateDiff struct { + Suffix Suffix + CurrentValue Value + // Uncomment in post-Kaustinen testnets + NewValue Value +} + +func (ssd *SuffixStateDiff) ByteLength() (out uint64) { + out = 1 + if ssd.CurrentValue != nil { + out += 32 + } + if len(ssd.NewValue) != 0 { + out += 32 + } + return +} + +func (ssd *SuffixStateDiff) Serialize(w *codec.EncodingWriter) error { + return w.Container(&ssd.Suffix, &ssd.CurrentValue, &ssd.NewValue) +} + +func (ssd *SuffixStateDiff) Deserialize(dr *codec.DecodingReader) error { + return dr.Container(&ssd.Suffix, &ssd.CurrentValue, &ssd.NewValue) +} + +func (ssd *SuffixStateDiff) FixedLength() uint64 { + return 0 +} + +func (ssd *SuffixStateDiff) HashTreeRoot(h tree.HashFn) tree.Root { + return h.HashTreeRoot(&ssd.Suffix, &ssd.CurrentValue) +} + +type SuffixStateDiffs []SuffixStateDiff + +func (ssds SuffixStateDiffs) ByteLength() (out uint64) { + for _, v := range ssds { + out += v.ByteLength() + codec.OFFSET_SIZE + } + return +} + +func (ssds SuffixStateDiffs) Serialize(w *codec.EncodingWriter) error { + return w.List(func(i uint64) codec.Serializable { + return &(ssds[i]) + }, 0, uint64(len(ssds))) +} + +func (ssds SuffixStateDiffs) FixedLength() uint64 { + return 0 +} + +func (ssds *SuffixStateDiffs) Deserialize(dr *codec.DecodingReader) (err error) { + return dr.List(func() codec.Deserializable { + i := len(*ssds) + *ssds = append(*ssds, SuffixStateDiff{}) + return &(*ssds)[i] + }, 0, VERKLE_WIDTH) +} + +func (ssds *SuffixStateDiffs) HashTreeRoot(h tree.HashFn) tree.Root { + length := uint64(len(*ssds)) + return h.ComplexListHTR(func(i uint64) tree.HTR { + if i < length { + return &(*ssds)[i] + } + return nil + }, length, VERKLE_WIDTH) +} + +type StemStateDiff struct { + Stem Stem + SuffixDiffs SuffixStateDiffs +} + +func (ssd *StemStateDiff) ByteLength() (out uint64) { + out = 31 + for _, v := range ssd.SuffixDiffs { + out += v.ByteLength() + } + return +} + +func (ssd *StemStateDiff) Serialize(w *codec.EncodingWriter) error { + return w.Container(&ssd.Stem, &ssd.SuffixDiffs) +} + +func (*StemStateDiff) FixedLength() uint64 { + return 0 +} + +func (ssd *StemStateDiff) Deserialize(dr *codec.DecodingReader) error { + return dr.Container(&ssd.Stem, &ssd.SuffixDiffs) +} + +func (sd *StemStateDiff) HashTreeRoot(h tree.HashFn) tree.Root { + return h.HashTreeRoot(&sd.Stem, &sd.SuffixDiffs) +} + +type StateDiff []StemStateDiff + +func (sd StateDiff) ByteLength() (out uint64) { + for _, v := range sd { + out += v.ByteLength() + codec.OFFSET_SIZE + } + return +} + +func (sd StateDiff) Serialize(w *codec.EncodingWriter) error { + return w.List(func(i uint64) codec.Serializable { + return &sd[i] + }, 0, uint64(len(sd))) +} + +func (sd *StateDiff) Deserialize(dr *codec.DecodingReader) error { + return dr.List(func() codec.Deserializable { + i := len(*sd) + *sd = append(*sd, StemStateDiff{}) + return &(*sd)[i] + }, 0, uint64(MAX_STEMS)) +} + +func (*StateDiff) FixedLength() uint64 { + return 0 +} + +func (sd *StateDiff) HashTreeRoot(h tree.HashFn) tree.Root { + length := uint64(len(*sd)) + return h.ComplexListHTR(func(i uint64) tree.HTR { + if i < length { + return &(*sd)[i] + } + return nil + }, length, uint64(MAX_STEMS)) +} + +type Stems []Stem + +func (s *Stems) Deserialize(dr *codec.DecodingReader) error { + return dr.List(func() codec.Deserializable { + i := len(*s) + *s = append(*s, Stem{}) + return &(*s)[i] + }, 31, MAX_STEMS) +} + +func (Stems) FixedLength() uint64 { + return 0 +} + +func (s Stems) HashTreeRoot(h tree.HashFn) tree.Root { + return h.ComplexListHTR(func(i uint64) tree.HTR { + return &s[i] + }, 0, uint64(len(s))) +} + +func (s Stems) Serialize(w *codec.EncodingWriter) error { + return w.List(func(i uint64) codec.Serializable { + return &s[i] + }, 31, uint64(len(s))) +} + +func (s Stems) ByteLength() uint64 { + return uint64(len(s)) * (31 /*+ codec.OFFSET_SIZE*/) +} + +type BanderwagonGroupElement [32]byte + +func (bge *BanderwagonGroupElement) Deserialize(dr *codec.DecodingReader) error { + dst := make([]byte, 32) + err := dr.ByteVector(&dst, 32) + if err != nil { + return err + } + copy(bge[:], dst) + return nil +} + +func (bge *BanderwagonGroupElement) FixedLength() uint64 { + return 32 +} + +func (bge *BanderwagonGroupElement) HashTreeRoot(h tree.HashFn) tree.Root { + return h.ByteVectorHTR(bge[:]) +} + +func (bge *BanderwagonGroupElement) Serialize(w *codec.EncodingWriter) error { + return w.Write(bge[:]) +} + +func (bge *BanderwagonGroupElement) ByteLength() uint64 { + return 32 +} + +type BanderwagonGroupElements []BanderwagonGroupElement + +func (bge *BanderwagonGroupElements) Deserialize(dr *codec.DecodingReader) error { + return dr.List(func() codec.Deserializable { + i := len(*bge) + *bge = append(*bge, BanderwagonGroupElement{}) + return &(*bge)[i] + }, 32, MAX_STEMS) +} + +func (bge *BanderwagonGroupElements) FixedLength() uint64 { + return 0 +} + +func (bge *BanderwagonGroupElements) HashTreeRoot(h tree.HashFn) tree.Root { + return h.ComplexVectorHTR(func(i uint64) tree.HTR { + return &((*bge)[i]) + }, uint64(len(*bge))) +} + +func (bge *BanderwagonGroupElements) Serialize(w *codec.EncodingWriter) error { + return w.List(func(i uint64) codec.Serializable { return &(*bge)[i] }, 32, uint64(len(*bge))) +} + +func (bge *BanderwagonGroupElements) ByteLength() uint64 { + if len(*bge) == 0 { + return 0 + } + return uint64(len(*bge)) * ((*bge)[0].ByteLength() + codec.OFFSET_SIZE) +} + +type BanderwagonFieldElement [32]byte + +func (bfe *BanderwagonFieldElement) Deserialize(dr *codec.DecodingReader) error { + b := bfe[:] + return dr.ByteVector(&b, 32) +} + +func (bfe *BanderwagonFieldElement) FixedLength() uint64 { + return 32 +} + +func (bfe *BanderwagonFieldElement) HashTreeRoot(h tree.HashFn) tree.Root { + return h.ByteVectorHTR(bfe[:]) +} + +func (bge *BanderwagonFieldElement) Serialize(w *codec.EncodingWriter) error { + return w.Write(bge[:]) +} + +func (bge *BanderwagonFieldElement) ByteLength() uint64 { + return 32 +} + +type IPAProofVectors [IPA_PROOF_DEPTH]BanderwagonGroupElement + +func (ipv *IPAProofVectors) Deserialize(dr *codec.DecodingReader) error { + return dr.Vector(func(i uint64) codec.Deserializable { + return &ipv[i] + }, 32, uint64(IPA_PROOF_DEPTH)) +} + +func (ipv *IPAProofVectors) FixedLength() uint64 { + return IPA_PROOF_DEPTH * 32 +} + +func (ipv *IPAProofVectors) HashTreeRoot(h tree.HashFn) tree.Root { + return h.ComplexVectorHTR(func(i uint64) tree.HTR { + return &ipv[i] + }, uint64(IPA_PROOF_DEPTH)) +} + +func (ipv *IPAProofVectors) Serialize(w *codec.EncodingWriter) error { + return w.Vector(func(i uint64) codec.Serializable { + return &ipv[i] + }, uint64(ipv[0].FixedLength()), uint64(len(*ipv))) +} + +func (ipv *IPAProofVectors) ByteLength() uint64 { + return uint64(len(ipv)) * ipv[0].ByteLength() +} + +type IPAProof struct { + CL, CR IPAProofVectors + FinalEvaluation BanderwagonFieldElement +} + +func (ip *IPAProof) Deserialize(dr *codec.DecodingReader) error { + return dr.Container(&ip.CL, &ip.CR, &ip.FinalEvaluation) +} + +func (ip *IPAProof) FixedLength() uint64 { + return 544 +} + +func (ip *IPAProof) HashTreeRoot(h tree.HashFn) tree.Root { + return h.HashTreeRoot(&ip.CR, &ip.CL, &ip.FinalEvaluation) +} + +func (ip *IPAProof) Serialize(w *codec.EncodingWriter) error { + return w.Container(&ip.CL, &ip.CR, &ip.FinalEvaluation) +} + +func (ip *IPAProof) ByteLength() uint64 { + return 544 +} + +type DepthExtensionPresent []byte + +func (dep *DepthExtensionPresent) Deserialize(dr *codec.DecodingReader) error { + return dr.ByteList((*[]byte)(dep), MAX_STEMS) +} + +func (DepthExtensionPresent) FixedLength() uint64 { + return 0 +} + +func (dep DepthExtensionPresent) HashTreeRoot(h tree.HashFn) tree.Root { + return h.ByteListHTR(dep[:], MAX_STEMS) +} + +func (dep DepthExtensionPresent) Serialize(w *codec.EncodingWriter) error { + return w.Write(dep) +} + +func (dep DepthExtensionPresent) ByteLength() uint64 { + return uint64(len(dep)) +} + +type VerkleProof struct { + OtherStems Stems + DepthExtensionPresent DepthExtensionPresent + CommitmentsByPath BanderwagonGroupElements + D BanderwagonGroupElement + IPAProof IPAProof +} + +func (vp *VerkleProof) FixedLength() uint64 { + return 0 +} + +func (vp *VerkleProof) ByteLength() uint64 { + return vp.OtherStems.ByteLength() + vp.DepthExtensionPresent.ByteLength() + vp.CommitmentsByPath.ByteLength() + vp.D.ByteLength() + vp.IPAProof.ByteLength() +} + +func (vp *VerkleProof) Serialize(w *codec.EncodingWriter) error { + return w.Container(&vp.OtherStems, &vp.DepthExtensionPresent, &vp.CommitmentsByPath, &vp.D, &vp.IPAProof) +} + +func (vp *VerkleProof) Deserialize(dr *codec.DecodingReader) error { + return dr.Container(&vp.OtherStems, &vp.DepthExtensionPresent, &vp.CommitmentsByPath, &vp.D, &vp.IPAProof) +} + +func (vp *VerkleProof) HashTreeRoot(h tree.HashFn) tree.Root { + return h.HashTreeRoot(&vp.OtherStems, vp.DepthExtensionPresent, &vp.CommitmentsByPath, &vp.D, &vp.IPAProof) +} + +type ExecutionWitness struct { + StateDiff StateDiff + VerkleProof VerkleProof +} + +func (ew *ExecutionWitness) Deserialize(dr *codec.DecodingReader) error { + return dr.Container(&ew.StateDiff, &ew.VerkleProof) +} + +func (ew *ExecutionWitness) FixedLength() uint64 { + return 0 +} + +func (ew *ExecutionWitness) Serialize(w *codec.EncodingWriter) error { + return w.Container(&ew.StateDiff, &ew.VerkleProof) +} + +func (ew *ExecutionWitness) ByteLength() uint64 { + return ew.StateDiff.ByteLength() + ew.VerkleProof.ByteLength() +} + +func (ew *ExecutionWitness) HashTreeRoot(fn tree.HashFn) common.Root { + return fn.HashTreeRoot(&ew.StateDiff, &ew.VerkleProof) +} diff --git a/eth2/beacon/verkle/execution_witness_test.go b/eth2/beacon/verkle/execution_witness_test.go new file mode 100644 index 0000000..ea68c51 --- /dev/null +++ b/eth2/beacon/verkle/execution_witness_test.go @@ -0,0 +1,182 @@ +package verkle + +import ( + "bytes" + "testing" + + "github.com/protolambda/ztyp/codec" +) + +var testStem = Stem([31]byte{0: 1, 2: 3, 4: 5, 30: 0xff}) +var testValueBytes = [32]byte{0: 0xaa, 7: 8, 23: 0xde} +var testValue = Value(testValueBytes[:]) +var testValueNewBytes = [32]byte{0: 0xaa, 7: 8, 23: 0xde, 30: 42} +var testValueNew = Value(testValueNewBytes[:]) + +func TestStemSerialization(t *testing.T) { + var buf bytes.Buffer + if err := testStem.Serialize(codec.NewEncodingWriter(&buf)); err != nil { + t.Fatal(err) + } + + if !bytes.Equal(buf.Bytes()[:31], testStem[:]) { + t.Fatalf("invalid output %x != %x", buf.Bytes(), testStem[:]) + } +} + +func TestStemSerde(t *testing.T) { + var buf bytes.Buffer + if err := testStem.Serialize(codec.NewEncodingWriter(&buf)); err != nil { + t.Fatal(err) + } + + var newStem Stem + if err := newStem.Deserialize(codec.NewDecodingReader(&buf, uint64(buf.Len()))); err != nil { + t.Fatal(err) + } + + if !bytes.Equal(newStem[:], testStem[:]) { + t.Fatalf("invalid deserialized stem %x != %x", newStem, testStem) + } +} + +func TestValueSerde(t *testing.T) { + var buf bytes.Buffer + if err := testValue.Serialize(codec.NewEncodingWriter(&buf)); err != nil { + t.Fatal(err) + } + t.Logf("serialized=%x", buf.Bytes()) + + var newValue Value + if err := newValue.Deserialize(codec.NewDecodingReader(&buf, uint64(buf.Len()))); err != nil { + t.Fatal(err) + } + + t.Logf("%v", newValue) + if !bytes.Equal(newValue[:], testValue[:]) { + t.Fatalf("invalid deserialized stem %x != %x", newValue, testValue) + } +} + +func TestSuffixStateDiffSerde(t *testing.T) { + var buf bytes.Buffer + sd := &SuffixStateDiff{ + Suffix: 0xde, + CurrentValue: testValue, + } + if err := sd.Serialize(codec.NewEncodingWriter(&buf)); err != nil { + t.Fatal(err) + } + var newssd SuffixStateDiff + if err := newssd.Deserialize(codec.NewDecodingReader(&buf, uint64(buf.Len()))); err != nil { + t.Fatal(err) + } + + if !bytes.Equal(newssd.CurrentValue[:], sd.CurrentValue[:]) { + t.Fatalf("invalid deserialized current value %x != %x", newssd.CurrentValue, sd.CurrentValue) + } + + if newssd.Suffix != sd.Suffix { + t.Fatalf("Differing suffixes %x != %x", newssd.Suffix, sd.Suffix) + } + + // Same thing with a non-nil NewValue + sd = &SuffixStateDiff{ + Suffix: 0xde, + CurrentValue: testValue, + NewValue: testValueNew, + } + if err := sd.Serialize(codec.NewEncodingWriter(&buf)); err != nil { + t.Fatal(err) + } + t.Logf("serialized=%x", buf.Bytes()) + if err := newssd.Deserialize(codec.NewDecodingReader(&buf, uint64(buf.Len()))); err != nil { + t.Fatal(err) + } + + if !bytes.Equal(newssd.CurrentValue[:], sd.CurrentValue[:]) { + t.Fatalf("invalid deserialized current value %x != %x", newssd.CurrentValue, sd.CurrentValue) + } + + if newssd.Suffix != sd.Suffix { + t.Fatalf("Differing suffixes %x != %x", newssd.Suffix, sd.Suffix) + } +} + +func TestStemStateDiffSerde(t *testing.T) { + var buf bytes.Buffer + sd := &StemStateDiff{ + Stem: testStem, + SuffixDiffs: []SuffixStateDiff{{ + Suffix: 0xde, + CurrentValue: testValue, + }}, + } + if err := sd.Serialize(codec.NewEncodingWriter(&buf)); err != nil { + t.Fatal(err) + } + var newssd StemStateDiff + if err := newssd.Deserialize(codec.NewDecodingReader(&buf, uint64(buf.Len()))); err != nil { + t.Fatal(err) + } + + if !bytes.Equal(sd.Stem[:], newssd.Stem[:]) { + t.Fatalf("invalid deserialized stem %x != %x", sd.Stem, newssd.Stem) + } + + if len(newssd.SuffixDiffs) != 1 { + t.Fatalf("invalid length for suffix diffs %d != 1", len(newssd.SuffixDiffs)) + } + + if !bytes.Equal(newssd.SuffixDiffs[0].CurrentValue[:], sd.SuffixDiffs[0].CurrentValue[:]) { + t.Fatalf("invalid deserialized current value %x != %x", newssd.SuffixDiffs[0].CurrentValue, sd.SuffixDiffs[0].CurrentValue) + } + + if newssd.SuffixDiffs[0].Suffix != sd.SuffixDiffs[0].Suffix { + t.Fatalf("Differing suffixes %x != %x", newssd.SuffixDiffs[0].Suffix, sd.SuffixDiffs[0].Suffix) + } +} +func TestIPAProofSerde(t *testing.T) { + var buf bytes.Buffer + ipp := &IPAProof{} + if err := ipp.Serialize(codec.NewEncodingWriter(&buf)); err != nil { + t.Fatal(err) + } + var newipp IPAProof + if err := newipp.Deserialize(codec.NewDecodingReader(&buf, uint64(buf.Len()))); err != nil { + t.Fatal(err) + } +} + +func TestVerkleProofSerde(t *testing.T) { + var buf bytes.Buffer + vp := &VerkleProof{ + OtherStems: Stems{{0: 0x33, 30: 0x44}, {0: 0x55, 30: 0x66}}, + DepthExtensionPresent: []byte{1, 2, 3}, + CommitmentsByPath: []BanderwagonGroupElement{ + [32]byte{0: 0x77, 31: 0x88}, + [32]byte{0: 0x99, 31: 0xaa}}, + D: BanderwagonGroupElement{0: 0x12, 31: 0x34}, + IPAProof: IPAProof{ + CL: [IPA_PROOF_DEPTH]BanderwagonGroupElement{0: {0: 0xee, 31: 0xff}, 7: {31: 0xbb}}, + CR: [IPA_PROOF_DEPTH]BanderwagonGroupElement{0: {0: 0xcc, 31: 0xdd}, 7: {31: 0x11}}, + FinalEvaluation: BanderwagonFieldElement{31: 0x22}, + }, + } + if err := vp.Serialize(codec.NewEncodingWriter(&buf)); err != nil { + t.Fatal(err) + } + var newvp VerkleProof + if err := newvp.Deserialize(codec.NewDecodingReader(&buf, uint64(buf.Len()))); err != nil { + t.Fatalf("deserializing proof: %v", err) + } + if newvp.DepthExtensionPresent[2] != vp.DepthExtensionPresent[2] { + t.Fatalf("could not deserialize depth + extension presence indicator: %d != %d", newvp.DepthExtensionPresent[2], vp.DepthExtensionPresent[2]) + } + if !bytes.Equal(newvp.CommitmentsByPath[1][:], vp.CommitmentsByPath[1][:]) { + t.Fatalf("differing second commitment %x != %x", newvp.CommitmentsByPath[1][:], vp.CommitmentsByPath[1][:]) + } + if !bytes.Equal(newvp.IPAProof.CL[0][:], vp.IPAProof.CL[0][:]) { + t.Fatalf("differing CL proof element %x != %x", newvp.IPAProof.CL[0][:], vp.IPAProof.CL[0][:]) + } +} diff --git a/eth2/beacon/verkle/fork.go b/eth2/beacon/verkle/fork.go new file mode 100644 index 0000000..9393ecd --- /dev/null +++ b/eth2/beacon/verkle/fork.go @@ -0,0 +1,172 @@ +package verkle + +import ( + "github.com/protolambda/ztyp/view" + + "github.com/protolambda/zrnt/eth2/beacon/bellatrix" + "github.com/protolambda/zrnt/eth2/beacon/common" +) + +func UpgradeToCapella(spec *common.Spec, epc *common.EpochsContext, pre *bellatrix.BeaconStateView) (*BeaconStateView, error) { + // yes, super ugly code, but it does transfer compatible subtrees without duplicating data or breaking caches + slot, err := pre.Slot() + if err != nil { + return nil, err + } + epoch := spec.SlotToEpoch(slot) + genesisTime, err := pre.GenesisTime() + if err != nil { + return nil, err + } + genesisValidatorsRoot, err := pre.GenesisValidatorsRoot() + if err != nil { + return nil, err + } + preFork, err := pre.Fork() + if err != nil { + return nil, err + } + fork := common.Fork{ + PreviousVersion: preFork.CurrentVersion, + CurrentVersion: spec.CAPELLA_FORK_VERSION, + Epoch: epoch, + } + latestBlockHeader, err := pre.LatestBlockHeader() + if err != nil { + return nil, err + } + blockRoots, err := pre.BlockRoots() + if err != nil { + return nil, err + } + stateRoots, err := pre.StateRoots() + if err != nil { + return nil, err + } + historicalRoots, err := pre.HistoricalRoots() + if err != nil { + return nil, err + } + eth1Data, err := pre.Eth1Data() + if err != nil { + return nil, err + } + eth1DataVotes, err := pre.Eth1DataVotes() + if err != nil { + return nil, err + } + eth1DepositIndex, err := pre.Eth1DepositIndex() + if err != nil { + return nil, err + } + validators, err := pre.Validators() + if err != nil { + return nil, err + } + balances, err := pre.Balances() + if err != nil { + return nil, err + } + randaoMixes, err := pre.RandaoMixes() + if err != nil { + return nil, err + } + slashings, err := pre.Slashings() + if err != nil { + return nil, err + } + previousEpochParticipation, err := pre.PreviousEpochParticipation() + if err != nil { + return nil, err + } + currentEpochParticipation, err := pre.CurrentEpochParticipation() + if err != nil { + return nil, err + } + justBits, err := pre.JustificationBits() + if err != nil { + return nil, err + } + prevJustCh, err := pre.PreviousJustifiedCheckpoint() + if err != nil { + return nil, err + } + currJustCh, err := pre.CurrentJustifiedCheckpoint() + if err != nil { + return nil, err + } + finCh, err := pre.FinalizedCheckpoint() + if err != nil { + return nil, err + } + inactivityScores, err := pre.InactivityScores() + if err != nil { + return nil, err + } + currentSyncCommitteeView, err := pre.CurrentSyncCommittee() + if err != nil { + return nil, err + } + nextSyncCommitteeView, err := pre.NextSyncCommittee() + if err != nil { + return nil, err + } + latestExecutionPayloadHeader, err := pre.LatestExecutionPayloadHeader() + if err != nil { + return nil, err + } + oldExecutionHeader, err := latestExecutionPayloadHeader.Raw() + if err != nil { + return nil, err + } + updatedExecutionPayloadHeader := &ExecutionPayloadHeader{ + ParentHash: oldExecutionHeader.ParentHash, + FeeRecipient: oldExecutionHeader.FeeRecipient, + StateRoot: oldExecutionHeader.StateRoot, + ReceiptsRoot: oldExecutionHeader.ReceiptsRoot, + LogsBloom: oldExecutionHeader.LogsBloom, + PrevRandao: oldExecutionHeader.PrevRandao, + BlockNumber: oldExecutionHeader.BlockNumber, + GasLimit: oldExecutionHeader.GasLimit, + GasUsed: oldExecutionHeader.GasUsed, + Timestamp: oldExecutionHeader.Timestamp, + ExtraData: oldExecutionHeader.ExtraData, + BaseFeePerGas: oldExecutionHeader.BaseFeePerGas, + BlockHash: oldExecutionHeader.BlockHash, + TransactionsRoot: oldExecutionHeader.TransactionsRoot, + WithdrawalsRoot: common.Root{}, // New in Capella + } + nextWithdrawalIndex := view.Uint64View(0) + nextWithdrawalValidatorIndex := view.Uint64View(0) + + return AsBeaconStateView(BeaconStateType(spec).FromFields( + (*view.Uint64View)(&genesisTime), + (*view.RootView)(&genesisValidatorsRoot), + (*view.Uint64View)(&slot), + fork.View(), + latestBlockHeader.View(), + blockRoots.(view.View), + stateRoots.(view.View), + historicalRoots.(view.View), + eth1Data.View(), + eth1DataVotes.(view.View), + (*view.Uint64View)(ð1DepositIndex), + validators.(view.View), + balances.(view.View), + randaoMixes.(view.View), + slashings.(view.View), + previousEpochParticipation, + currentEpochParticipation, + justBits.View(), + prevJustCh.View(), + currJustCh.View(), + finCh.View(), + inactivityScores, + currentSyncCommitteeView, + nextSyncCommitteeView, + updatedExecutionPayloadHeader.View(), + nextWithdrawalIndex, + nextWithdrawalValidatorIndex, + HistoricalSummariesType(spec).Default(nil), + )) +} diff --git a/eth2/beacon/verkle/history.go b/eth2/beacon/verkle/history.go new file mode 100644 index 0000000..61d1ac1 --- /dev/null +++ b/eth2/beacon/verkle/history.go @@ -0,0 +1,97 @@ +package verkle + +import ( + "github.com/protolambda/ztyp/codec" + "github.com/protolambda/ztyp/tree" + . "github.com/protolambda/ztyp/view" + + "github.com/protolambda/zrnt/eth2/beacon/common" +) + +// HistoricalSummary is a summary of HistoricalBatch and was introduced in Capella +type HistoricalSummary struct { + BlockSummaryRoot common.Root + StateSummaryRoot common.Root +} + +func (hs *HistoricalSummary) View() *ContainerView { + a, b := RootView(hs.BlockSummaryRoot), RootView(hs.StateSummaryRoot) + c, _ := HistoricalSummaryType.FromFields(&a, &b) + return c +} + +func (hs *HistoricalSummary) Deserialize(dr *codec.DecodingReader) error { + return dr.FixedLenContainer(&hs.BlockSummaryRoot, &hs.StateSummaryRoot) +} + +func (hs *HistoricalSummary) Serialize(w *codec.EncodingWriter) error { + return w.FixedLenContainer(&hs.BlockSummaryRoot, &hs.StateSummaryRoot) +} + +func (*HistoricalSummary) ByteLength() uint64 { + return 32 * 2 +} + +func (*HistoricalSummary) FixedLength() uint64 { + return 32 * 2 +} + +func (hs *HistoricalSummary) HashTreeRoot(hFn tree.HashFn) common.Root { + return hFn.HashTreeRoot(&hs.BlockSummaryRoot, &hs.StateSummaryRoot) +} + +var HistoricalSummaryType = ContainerType("HistoricalSummary", []FieldDef{ + {"block_summary_root", RootType}, + {"state_summary_root", RootType}, +}) + +// HistoricalSummaries are the summaries of historical batches +type HistoricalSummaries []HistoricalSummary + +func (a *HistoricalSummaries) Deserialize(spec *common.Spec, dr *codec.DecodingReader) error { + return dr.List(func() codec.Deserializable { + i := len(*a) + *a = append(*a, HistoricalSummary{}) + return &((*a)[i]) + }, HistoricalSummaryType.TypeByteLength(), uint64(spec.HISTORICAL_ROOTS_LIMIT)) +} + +func (a HistoricalSummaries) Serialize(_ *common.Spec, w *codec.EncodingWriter) error { + return w.List(func(i uint64) codec.Serializable { + return &a[i] + }, HistoricalSummaryType.TypeByteLength(), uint64(len(a))) +} + +func (a HistoricalSummaries) ByteLength(_ *common.Spec) (out uint64) { + return HistoricalSummaryType.TypeByteLength() * uint64(len(a)) +} + +func (*HistoricalSummaries) FixedLength(*common.Spec) uint64 { + return 0 +} + +func (li HistoricalSummaries) HashTreeRoot(spec *common.Spec, hFn tree.HashFn) common.Root { + length := uint64(len(li)) + return hFn.ComplexListHTR(func(i uint64) tree.HTR { + if i < length { + return &li[i] + } + return nil + }, length, uint64(spec.HISTORICAL_ROOTS_LIMIT)) +} + +func HistoricalSummariesType(spec *common.Spec) ListTypeDef { + return ListType(HistoricalSummaryType, uint64(spec.HISTORICAL_ROOTS_LIMIT)) +} + +type HistoricalSummariesView struct{ *ComplexListView } + +func AsHistoricalSummaries(v View, err error) (*HistoricalSummariesView, error) { + c, err := AsComplexList(v, err) + return &HistoricalSummariesView{c}, err +} + +func (h *HistoricalSummariesView) Append(summary HistoricalSummary) error { + v := summary.View() + return h.ComplexListView.Append(v) +} diff --git a/eth2/beacon/verkle/state.go b/eth2/beacon/verkle/state.go new file mode 100644 index 0000000..2e68a59 --- /dev/null +++ b/eth2/beacon/verkle/state.go @@ -0,0 +1,628 @@ +package verkle + +import ( + "bytes" + + "github.com/protolambda/ztyp/codec" + "github.com/protolambda/ztyp/tree" + . "github.com/protolambda/ztyp/view" + + "github.com/protolambda/zrnt/eth2/beacon/altair" + "github.com/protolambda/zrnt/eth2/beacon/common" + "github.com/protolambda/zrnt/eth2/beacon/phase0" +) + +type BeaconState struct { + // Versioning + GenesisTime common.Timestamp `json:"genesis_time" yaml:"genesis_time"` + GenesisValidatorsRoot common.Root `json:"genesis_validators_root" yaml:"genesis_validators_root"` + Slot common.Slot `json:"slot" yaml:"slot"` + Fork common.Fork `json:"fork" yaml:"fork"` + // History + LatestBlockHeader common.BeaconBlockHeader `json:"latest_block_header" yaml:"latest_block_header"` + BlockRoots phase0.HistoricalBatchRoots `json:"block_roots" yaml:"block_roots"` + StateRoots phase0.HistoricalBatchRoots `json:"state_roots" yaml:"state_roots"` + HistoricalRoots phase0.HistoricalRoots `json:"historical_roots" yaml:"historical_roots"` // Frozen in Capella, replaced by historical_summaries + // Eth1 + Eth1Data common.Eth1Data `json:"eth1_data" yaml:"eth1_data"` + Eth1DataVotes phase0.Eth1DataVotes `json:"eth1_data_votes" yaml:"eth1_data_votes"` + Eth1DepositIndex common.DepositIndex `json:"eth1_deposit_index" yaml:"eth1_deposit_index"` + // Registry + Validators phase0.ValidatorRegistry `json:"validators" yaml:"validators"` + Balances phase0.Balances `json:"balances" yaml:"balances"` + RandaoMixes phase0.RandaoMixes `json:"randao_mixes" yaml:"randao_mixes"` + Slashings phase0.SlashingsHistory `json:"slashings" yaml:"slashings"` + // Participation + PreviousEpochParticipation altair.ParticipationRegistry `json:"previous_epoch_participation" yaml:"previous_epoch_participation"` + CurrentEpochParticipation altair.ParticipationRegistry `json:"current_epoch_participation" yaml:"current_epoch_participation"` + // Finality + JustificationBits common.JustificationBits `json:"justification_bits" yaml:"justification_bits"` + PreviousJustifiedCheckpoint common.Checkpoint `json:"previous_justified_checkpoint" yaml:"previous_justified_checkpoint"` + CurrentJustifiedCheckpoint common.Checkpoint `json:"current_justified_checkpoint" yaml:"current_justified_checkpoint"` + FinalizedCheckpoint common.Checkpoint `json:"finalized_checkpoint" yaml:"finalized_checkpoint"` + // Inactivity + InactivityScores altair.InactivityScores `json:"inactivity_scores" yaml:"inactivity_scores"` + // Light client sync committees + CurrentSyncCommittee common.SyncCommittee `json:"current_sync_committee" yaml:"current_sync_committee"` + NextSyncCommittee common.SyncCommittee `json:"next_sync_committee" yaml:"next_sync_committee"` + // Execution-layer + LatestExecutionPayloadHeader ExecutionPayloadHeader `json:"latest_execution_payload_header" yaml:"latest_execution_payload_header"` + // Withdrawals + NextWithdrawalIndex common.WithdrawalIndex `json:"next_withdrawal_index" yaml:"next_withdrawal_index"` + NextWithdrawalValidatorIndex common.ValidatorIndex `json:"next_withdrawal_validator_index" yaml:"next_withdrawal_validator_index"` + // Deep history valid from Capella onwards + HistoricalSummaries HistoricalSummaries `json:"historical_summaries"` +} + +func (v *BeaconState) Deserialize(spec *common.Spec, dr *codec.DecodingReader) error { + return dr.Container(&v.GenesisTime, &v.GenesisValidatorsRoot, + &v.Slot, &v.Fork, &v.LatestBlockHeader, + spec.Wrap(&v.BlockRoots), spec.Wrap(&v.StateRoots), spec.Wrap(&v.HistoricalRoots), + &v.Eth1Data, spec.Wrap(&v.Eth1DataVotes), &v.Eth1DepositIndex, + spec.Wrap(&v.Validators), spec.Wrap(&v.Balances), + spec.Wrap(&v.RandaoMixes), spec.Wrap(&v.Slashings), + spec.Wrap(&v.PreviousEpochParticipation), spec.Wrap(&v.CurrentEpochParticipation), + &v.JustificationBits, + &v.PreviousJustifiedCheckpoint, &v.CurrentJustifiedCheckpoint, + &v.FinalizedCheckpoint, + spec.Wrap(&v.InactivityScores), + spec.Wrap(&v.CurrentSyncCommittee), spec.Wrap(&v.NextSyncCommittee), + &v.LatestExecutionPayloadHeader, + &v.NextWithdrawalIndex, &v.NextWithdrawalValidatorIndex, + spec.Wrap(&v.HistoricalSummaries), + ) +} + +func (v *BeaconState) Serialize(spec *common.Spec, w *codec.EncodingWriter) error { + return w.Container(&v.GenesisTime, &v.GenesisValidatorsRoot, + &v.Slot, &v.Fork, &v.LatestBlockHeader, + spec.Wrap(&v.BlockRoots), spec.Wrap(&v.StateRoots), spec.Wrap(&v.HistoricalRoots), + &v.Eth1Data, spec.Wrap(&v.Eth1DataVotes), &v.Eth1DepositIndex, + spec.Wrap(&v.Validators), spec.Wrap(&v.Balances), + spec.Wrap(&v.RandaoMixes), spec.Wrap(&v.Slashings), + spec.Wrap(&v.PreviousEpochParticipation), spec.Wrap(&v.CurrentEpochParticipation), + &v.JustificationBits, + &v.PreviousJustifiedCheckpoint, &v.CurrentJustifiedCheckpoint, + &v.FinalizedCheckpoint, + spec.Wrap(&v.InactivityScores), + spec.Wrap(&v.CurrentSyncCommittee), spec.Wrap(&v.NextSyncCommittee), + &v.LatestExecutionPayloadHeader, + &v.NextWithdrawalIndex, &v.NextWithdrawalValidatorIndex, + spec.Wrap(&v.HistoricalSummaries), + ) +} + +func (v *BeaconState) ByteLength(spec *common.Spec) uint64 { + return codec.ContainerLength(&v.GenesisTime, &v.GenesisValidatorsRoot, + &v.Slot, &v.Fork, &v.LatestBlockHeader, + spec.Wrap(&v.BlockRoots), spec.Wrap(&v.StateRoots), spec.Wrap(&v.HistoricalRoots), + &v.Eth1Data, spec.Wrap(&v.Eth1DataVotes), &v.Eth1DepositIndex, + spec.Wrap(&v.Validators), spec.Wrap(&v.Balances), + spec.Wrap(&v.RandaoMixes), spec.Wrap(&v.Slashings), + spec.Wrap(&v.PreviousEpochParticipation), spec.Wrap(&v.CurrentEpochParticipation), + &v.JustificationBits, + &v.PreviousJustifiedCheckpoint, &v.CurrentJustifiedCheckpoint, + &v.FinalizedCheckpoint, + spec.Wrap(&v.InactivityScores), + spec.Wrap(&v.CurrentSyncCommittee), spec.Wrap(&v.NextSyncCommittee), + &v.LatestExecutionPayloadHeader, + &v.NextWithdrawalIndex, &v.NextWithdrawalValidatorIndex, + spec.Wrap(&v.HistoricalSummaries), + ) +} + +func (*BeaconState) FixedLength(*common.Spec) uint64 { + return 0 // dynamic size +} + +func (v *BeaconState) HashTreeRoot(spec *common.Spec, hFn tree.HashFn) common.Root { + return hFn.HashTreeRoot(&v.GenesisTime, &v.GenesisValidatorsRoot, + &v.Slot, &v.Fork, &v.LatestBlockHeader, + spec.Wrap(&v.BlockRoots), spec.Wrap(&v.StateRoots), spec.Wrap(&v.HistoricalRoots), + &v.Eth1Data, spec.Wrap(&v.Eth1DataVotes), &v.Eth1DepositIndex, + spec.Wrap(&v.Validators), spec.Wrap(&v.Balances), + spec.Wrap(&v.RandaoMixes), spec.Wrap(&v.Slashings), + spec.Wrap(&v.PreviousEpochParticipation), spec.Wrap(&v.CurrentEpochParticipation), + &v.JustificationBits, + &v.PreviousJustifiedCheckpoint, &v.CurrentJustifiedCheckpoint, + &v.FinalizedCheckpoint, + spec.Wrap(&v.InactivityScores), + spec.Wrap(&v.CurrentSyncCommittee), spec.Wrap(&v.NextSyncCommittee), + &v.LatestExecutionPayloadHeader, + &v.NextWithdrawalIndex, &v.NextWithdrawalValidatorIndex, + spec.Wrap(&v.HistoricalSummaries), + ) +} + +// Hack to make state fields consistent and verifiable without using many hardcoded indices +// A trade-off to interpret the state as tree, without generics, and access fields by index very fast. +const ( + _stateGenesisTime = iota + _stateGenesisValidatorsRoot + _stateSlot + _stateFork + _stateLatestBlockHeader + _stateBlockRoots + _stateStateRoots + _stateHistoricalRoots + _stateEth1Data + _stateEth1DataVotes + _stateEth1DepositIndex + _stateValidators + _stateBalances + _stateRandaoMixes + _stateSlashings + _statePreviousEpochParticipation + _stateCurrentEpochParticipation + _stateJustificationBits + _statePreviousJustifiedCheckpoint + _stateCurrentJustifiedCheckpoint + _stateFinalizedCheckpoint + _inactivityScores + _currentSyncCommittee + _nextSyncCommittee + _latestExecutionPayloadHeader + _nextWithdrawalIndex + _nextWithdrawalValidatorIndex + _historicalSummaries +) + +func BeaconStateType(spec *common.Spec) *ContainerTypeDef { + return ContainerType("BeaconState", []FieldDef{ + // Versioning + {"genesis_time", Uint64Type}, + {"genesis_validators_root", RootType}, + {"slot", common.SlotType}, + {"fork", common.ForkType}, + // History + {"latest_block_header", common.BeaconBlockHeaderType}, + {"block_roots", phase0.BatchRootsType(spec)}, + {"state_roots", phase0.BatchRootsType(spec)}, + {"historical_roots", phase0.HistoricalRootsType(spec)}, + // Eth1 + {"eth1_data", common.Eth1DataType}, + {"eth1_data_votes", phase0.Eth1DataVotesType(spec)}, + {"eth1_deposit_index", Uint64Type}, + // Registry + {"validators", phase0.ValidatorsRegistryType(spec)}, + {"balances", phase0.RegistryBalancesType(spec)}, + // Randomness + {"randao_mixes", phase0.RandaoMixesType(spec)}, + // Slashings + {"slashings", phase0.SlashingsType(spec)}, + // Participation + {"previous_epoch_participation", altair.ParticipationRegistryType(spec)}, + {"current_epoch_participation", altair.ParticipationRegistryType(spec)}, + // Finality + {"justification_bits", common.JustificationBitsType}, + {"previous_justified_checkpoint", common.CheckpointType}, + {"current_justified_checkpoint", common.CheckpointType}, + {"finalized_checkpoint", common.CheckpointType}, + // Inactivity + {"inactivity_scores", altair.InactivityScoresType(spec)}, + // Sync + {"current_sync_committee", common.SyncCommitteeType(spec)}, + {"next_sync_committee", common.SyncCommitteeType(spec)}, + // Execution-layer + {"latest_execution_payload_header", ExecutionPayloadHeaderType}, + // Withdrawals + {"next_withdrawal_index", common.WithdrawalIndexType}, + {"next_withdrawal_validator_index", common.ValidatorIndexType}, + // Deep history valid from Capella onwards + {"historical_summaries", HistoricalSummariesType(spec)}, + }) +} + +// To load a state: +// +// state, err := beacon.AsBeaconStateView(beacon.BeaconStateType.Deserialize(codec.NewDecodingReader(reader, size))) +func AsBeaconStateView(v View, err error) (*BeaconStateView, error) { + c, err := AsContainer(v, err) + return &BeaconStateView{c}, err +} + +type BeaconStateView struct { + *ContainerView +} + +var _ common.BeaconState = (*phase0.BeaconStateView)(nil) + +func NewBeaconStateView(spec *common.Spec) *BeaconStateView { + return &BeaconStateView{ContainerView: BeaconStateType(spec).New()} +} + +func (state *BeaconStateView) GenesisTime() (common.Timestamp, error) { + return common.AsTimestamp(state.Get(_stateGenesisTime)) +} + +func (state *BeaconStateView) SetGenesisTime(t common.Timestamp) error { + return state.Set(_stateGenesisTime, Uint64View(t)) +} + +func (state *BeaconStateView) GenesisValidatorsRoot() (common.Root, error) { + return AsRoot(state.Get(_stateGenesisValidatorsRoot)) +} + +func (state *BeaconStateView) SetGenesisValidatorsRoot(r common.Root) error { + rv := RootView(r) + return state.Set(_stateGenesisValidatorsRoot, &rv) +} + +func (state *BeaconStateView) Slot() (common.Slot, error) { + return common.AsSlot(state.Get(_stateSlot)) +} + +func (state *BeaconStateView) SetSlot(slot common.Slot) error { + return state.Set(_stateSlot, Uint64View(slot)) +} + +func (state *BeaconStateView) Fork() (common.Fork, error) { + fv, err := common.AsFork(state.Get(_stateFork)) + if err != nil { + return common.Fork{}, err + } + return fv.Raw() +} + +func (state *BeaconStateView) SetFork(f common.Fork) error { + return state.Set(_stateFork, f.View()) +} + +func (state *BeaconStateView) LatestBlockHeader() (*common.BeaconBlockHeader, error) { + h, err := common.AsBeaconBlockHeader(state.Get(_stateLatestBlockHeader)) + if err != nil { + return nil, err + } + return h.Raw() +} + +func (state *BeaconStateView) SetLatestBlockHeader(v *common.BeaconBlockHeader) error { + return state.Set(_stateLatestBlockHeader, v.View()) +} + +func (state *BeaconStateView) BlockRoots() (common.BatchRoots, error) { + return phase0.AsBatchRoots(state.Get(_stateBlockRoots)) +} + +func (state *BeaconStateView) StateRoots() (common.BatchRoots, error) { + return phase0.AsBatchRoots(state.Get(_stateStateRoots)) +} + +func (state *BeaconStateView) HistoricalRoots() (common.HistoricalRoots, error) { + return phase0.AsHistoricalRoots(state.Get(_stateHistoricalRoots)) +} + +func (state *BeaconStateView) Eth1Data() (common.Eth1Data, error) { + dat, err := common.AsEth1Data(state.Get(_stateEth1Data)) + if err != nil { + return common.Eth1Data{}, err + } + return dat.Raw() +} + +func (state *BeaconStateView) SetEth1Data(v common.Eth1Data) error { + return state.Set(_stateEth1Data, v.View()) +} + +func (state *BeaconStateView) Eth1DataVotes() (common.Eth1DataVotes, error) { + return phase0.AsEth1DataVotes(state.Get(_stateEth1DataVotes)) +} + +func (state *BeaconStateView) Eth1DepositIndex() (common.DepositIndex, error) { + return common.AsDepositIndex(state.Get(_stateEth1DepositIndex)) +} + +func (state *BeaconStateView) IncrementDepositIndex() error { + depIndex, err := state.Eth1DepositIndex() + if err != nil { + return err + } + return state.Set(_stateEth1DepositIndex, Uint64View(depIndex+1)) +} + +func (state *BeaconStateView) Validators() (common.ValidatorRegistry, error) { + return phase0.AsValidatorsRegistry(state.Get(_stateValidators)) +} + +func (state *BeaconStateView) Balances() (common.BalancesRegistry, error) { + return phase0.AsRegistryBalances(state.Get(_stateBalances)) +} + +func (state *BeaconStateView) SetBalances(balances []common.Gwei) error { + typ := state.Fields[_stateBalances].Type.(*BasicListTypeDef) + balancesView, err := phase0.Balances(balances).View(typ.ListLimit) + if err != nil { + return err + } + return state.Set(_stateBalances, balancesView) +} + +func (state *BeaconStateView) AddValidator(spec *common.Spec, pub common.BLSPubkey, withdrawalCreds common.Root, balance common.Gwei) error { + effBalance := balance - (balance % spec.EFFECTIVE_BALANCE_INCREMENT) + if effBalance > spec.MAX_EFFECTIVE_BALANCE { + effBalance = spec.MAX_EFFECTIVE_BALANCE + } + validatorRaw := phase0.Validator{ + Pubkey: pub, + WithdrawalCredentials: withdrawalCreds, + ActivationEligibilityEpoch: common.FAR_FUTURE_EPOCH, + ActivationEpoch: common.FAR_FUTURE_EPOCH, + ExitEpoch: common.FAR_FUTURE_EPOCH, + WithdrawableEpoch: common.FAR_FUTURE_EPOCH, + EffectiveBalance: effBalance, + } + validators, err := phase0.AsValidatorsRegistry(state.Get(_stateValidators)) + if err != nil { + return err + } + if err := validators.Append(validatorRaw.View()); err != nil { + return err + } + bals, err := state.Balances() + if err != nil { + return err + } + if err := bals.AppendBalance(balance); err != nil { + return err + } + // New in Altair: init participation + prevPart, err := state.PreviousEpochParticipation() + if err != nil { + return err + } + if err := prevPart.Append(Uint8View(altair.ParticipationFlags(0))); err != nil { + return err + } + currPart, err := state.CurrentEpochParticipation() + if err != nil { + return err + } + if err := currPart.Append(Uint8View(altair.ParticipationFlags(0))); err != nil { + return err + } + inActivityScores, err := state.InactivityScores() + if err != nil { + return err + } + if err := inActivityScores.Append(Uint8View(0)); err != nil { + return err + } + // New in Altair: init inactivity score + return nil +} + +func (state *BeaconStateView) RandaoMixes() (common.RandaoMixes, error) { + return phase0.AsRandaoMixes(state.Get(_stateRandaoMixes)) +} + +func (state *BeaconStateView) SeedRandao(spec *common.Spec, seed common.Root) error { + v, err := phase0.SeedRandao(spec, seed) + if err != nil { + return err + } + return state.Set(_stateRandaoMixes, v) +} + +func (state *BeaconStateView) Slashings() (common.Slashings, error) { + return phase0.AsSlashings(state.Get(_stateSlashings)) +} + +func (state *BeaconStateView) PreviousEpochParticipation() (*altair.ParticipationRegistryView, error) { + return altair.AsParticipationRegistry(state.Get(_statePreviousEpochParticipation)) +} + +func (state *BeaconStateView) CurrentEpochParticipation() (*altair.ParticipationRegistryView, error) { + return altair.AsParticipationRegistry(state.Get(_stateCurrentEpochParticipation)) +} + +func (state *BeaconStateView) JustificationBits() (common.JustificationBits, error) { + b, err := common.AsJustificationBits(state.Get(_stateJustificationBits)) + if err != nil { + return common.JustificationBits{}, err + } + return b.Raw() +} + +func (state *BeaconStateView) SetJustificationBits(bits common.JustificationBits) error { + b, err := common.AsJustificationBits(state.Get(_stateJustificationBits)) + if err != nil { + return err + } + return b.Set(bits) +} + +func (state *BeaconStateView) PreviousJustifiedCheckpoint() (common.Checkpoint, error) { + c, err := common.AsCheckPoint(state.Get(_statePreviousJustifiedCheckpoint)) + if err != nil { + return common.Checkpoint{}, err + } + return c.Raw() +} + +func (state *BeaconStateView) SetPreviousJustifiedCheckpoint(c common.Checkpoint) error { + v, err := common.AsCheckPoint(state.Get(_statePreviousJustifiedCheckpoint)) + if err != nil { + return err + } + return v.Set(&c) +} + +func (state *BeaconStateView) CurrentJustifiedCheckpoint() (common.Checkpoint, error) { + c, err := common.AsCheckPoint(state.Get(_stateCurrentJustifiedCheckpoint)) + if err != nil { + return common.Checkpoint{}, err + } + return c.Raw() +} + +func (state *BeaconStateView) SetCurrentJustifiedCheckpoint(c common.Checkpoint) error { + v, err := common.AsCheckPoint(state.Get(_stateCurrentJustifiedCheckpoint)) + if err != nil { + return err + } + return v.Set(&c) +} + +func (state *BeaconStateView) FinalizedCheckpoint() (common.Checkpoint, error) { + c, err := common.AsCheckPoint(state.Get(_stateFinalizedCheckpoint)) + if err != nil { + return common.Checkpoint{}, err + } + return c.Raw() +} + +func (state *BeaconStateView) SetFinalizedCheckpoint(c common.Checkpoint) error { + v, err := common.AsCheckPoint(state.Get(_stateFinalizedCheckpoint)) + if err != nil { + return err + } + return v.Set(&c) +} + +func (state *BeaconStateView) InactivityScores() (*altair.InactivityScoresView, error) { + return altair.AsInactivityScores(state.Get(_inactivityScores)) +} + +func (state *BeaconStateView) CurrentSyncCommittee() (*common.SyncCommitteeView, error) { + return common.AsSyncCommittee(state.Get(_currentSyncCommittee)) +} + +func (state *BeaconStateView) SetCurrentSyncCommittee(v *common.SyncCommitteeView) error { + return state.Set(_currentSyncCommittee, v) +} + +func (state *BeaconStateView) NextSyncCommittee() (*common.SyncCommitteeView, error) { + return common.AsSyncCommittee(state.Get(_nextSyncCommittee)) +} + +func (state *BeaconStateView) SetNextSyncCommittee(v *common.SyncCommitteeView) error { + return state.Set(_nextSyncCommittee, v) +} + +func (state *BeaconStateView) RotateSyncCommittee(next *common.SyncCommitteeView) error { + v, err := state.Get(_nextSyncCommittee) + if err != nil { + return err + } + if err := state.Set(_currentSyncCommittee, v); err != nil { + return err + } + return state.Set(_nextSyncCommittee, next) +} + +func (state *BeaconStateView) LatestExecutionPayloadHeader() (*ExecutionPayloadHeaderView, error) { + return AsExecutionPayloadHeader(state.Get(_latestExecutionPayloadHeader)) +} + +func (state *BeaconStateView) SetLatestExecutionPayloadHeader(h *ExecutionPayloadHeader) error { + return state.Set(_latestExecutionPayloadHeader, h.View()) +} + +func (state *BeaconStateView) NextWithdrawalIndex() (common.WithdrawalIndex, error) { + v, err := state.Get(_nextWithdrawalIndex) + return common.AsWithdrawalIndex(v, err) +} + +func (state *BeaconStateView) IncrementNextWithdrawalIndex() error { + nextIndex, err := state.NextWithdrawalIndex() + if err != nil { + return err + } + return state.Set(_nextWithdrawalIndex, Uint64View(nextIndex+1)) +} + +func (state *BeaconStateView) SetNextWithdrawalIndex(nextIndex common.WithdrawalIndex) error { + return state.Set(_nextWithdrawalIndex, Uint64View(nextIndex)) +} + +func (state *BeaconStateView) NextWithdrawalValidatorIndex() (common.ValidatorIndex, error) { + v, err := state.Get(_nextWithdrawalValidatorIndex) + return common.AsValidatorIndex(v, err) +} + +func (state *BeaconStateView) SetNextWithdrawalValidatorIndex(nextValidator common.ValidatorIndex) error { + return state.Set(_nextWithdrawalValidatorIndex, Uint64View(nextValidator)) +} + +type HistoricalSummariesList interface { + Append(summary HistoricalSummary) error +} + +func (state *BeaconStateView) HistoricalSummaries() (HistoricalSummariesList, error) { + v, err := state.Get(_historicalSummaries) + return AsHistoricalSummaries(v, err) +} + +func (state *BeaconStateView) ForkSettings(spec *common.Spec) *common.ForkSettings { + return &common.ForkSettings{ + MinSlashingPenaltyQuotient: uint64(spec.MIN_SLASHING_PENALTY_QUOTIENT_BELLATRIX), + ProportionalSlashingMultiplier: uint64(spec.PROPORTIONAL_SLASHING_MULTIPLIER_BELLATRIX), + InactivityPenaltyQuotient: uint64(spec.INACTIVITY_PENALTY_QUOTIENT_BELLATRIX), + CalcProposerShare: func(whistleblowerReward common.Gwei) common.Gwei { + return whistleblowerReward * altair.PROPOSER_WEIGHT / altair.WEIGHT_DENOMINATOR + }, + } +} + +// Raw converts the tree-structured state into a flattened native Go structure. +func (state *BeaconStateView) Raw(spec *common.Spec) (*BeaconState, error) { + var buf bytes.Buffer + if err := state.Serialize(codec.NewEncodingWriter(&buf)); err != nil { + return nil, err + } + var raw BeaconState + err := raw.Deserialize(spec, codec.NewDecodingReader(bytes.NewReader(buf.Bytes()), uint64(len(buf.Bytes())))) + if err != nil { + return nil, err + } + return &raw, nil +} + +func (state *BeaconStateView) CopyState() (common.BeaconState, error) { + return AsBeaconStateView(state.ContainerView.Copy()) +} + +type ExecutionUpgradeBeaconState interface { + IsExecutionEnabled(spec *common.Spec, block *BeaconBlock) (bool, error) + IsTransitionCompleted() (bool, error) + IsTransitionBlock(spec *common.Spec, block *BeaconBlock) (bool, error) +} + +type ExecutionTrackingBeaconState interface { + common.BeaconState + + LatestExecutionPayloadHeader() (*ExecutionPayloadHeaderView, error) + SetLatestExecutionPayloadHeader(h *ExecutionPayloadHeader) error +} + +func (state *BeaconStateView) IsExecutionEnabled(spec *common.Spec, block *BeaconBlock) (bool, error) { + isTransitionCompleted, err := state.IsTransitionCompleted() + if err != nil { + return false, err + } + if isTransitionCompleted { + return true, nil + } + return state.IsTransitionBlock(spec, block) +} + +func (state *BeaconStateView) IsTransitionCompleted() (bool, error) { + execHeader, err := state.LatestExecutionPayloadHeader() + if err != nil { + return false, err + } + empty := ExecutionPayloadHeaderType.DefaultNode().MerkleRoot(tree.GetHashFn()) + return execHeader.HashTreeRoot(tree.GetHashFn()) != empty, nil +} + +func (state *BeaconStateView) IsTransitionBlock(spec *common.Spec, block *BeaconBlock) (bool, error) { + isTransitionCompleted, err := state.IsTransitionCompleted() + if err != nil { + return false, err + } + if isTransitionCompleted { + return false, nil + } + empty := ExecutionPayloadType(spec).DefaultNode().MerkleRoot(tree.GetHashFn()) + return block.Body.ExecutionPayload.HashTreeRoot(spec, tree.GetHashFn()) != empty, nil +} diff --git a/eth2/beacon/verkle/transition.go b/eth2/beacon/verkle/transition.go new file mode 100644 index 0000000..de2e842 --- /dev/null +++ b/eth2/beacon/verkle/transition.go @@ -0,0 +1,348 @@ +package verkle + +import ( + "bytes" + "context" + "fmt" + + "github.com/protolambda/ztyp/tree" + + "github.com/protolambda/zrnt/eth2/beacon/altair" + "github.com/protolambda/zrnt/eth2/beacon/common" + "github.com/protolambda/zrnt/eth2/beacon/phase0" +) + +func (state *BeaconStateView) ProcessEpoch(ctx context.Context, spec *common.Spec, epc *common.EpochsContext) error { + vals, err := state.Validators() + if err != nil { + return err + } + flats, err := common.FlattenValidators(vals) + if err != nil { + return err + } + attesterData, err := altair.ComputeEpochAttesterData(ctx, spec, epc, flats, state) + if err != nil { + return err + } + just := phase0.JustificationStakeData{ + CurrentEpoch: epc.CurrentEpoch.Epoch, + TotalActiveStake: epc.TotalActiveStake, + PrevEpochUnslashedTargetStake: attesterData.PrevEpochUnslashedStake.TargetStake, + CurrEpochUnslashedTargetStake: attesterData.CurrEpochUnslashedTargetStake, + } + if err := phase0.ProcessEpochJustification(ctx, spec, &just, state); err != nil { + return err + } + if err := altair.ProcessInactivityUpdates(ctx, spec, attesterData, state); err != nil { + return err + } + if err := altair.ProcessEpochRewardsAndPenalties(ctx, spec, epc, attesterData, state); err != nil { + return err + } + if err := phase0.ProcessEpochRegistryUpdates(ctx, spec, epc, flats, state); err != nil { + return err + } + // phase0 implementation, but with fork-logic, will account for changed slashing multiplier + if err := phase0.ProcessEpochSlashings(ctx, spec, epc, flats, state); err != nil { + return err + } + if err := phase0.ProcessEth1DataReset(ctx, spec, epc, state); err != nil { + return err + } + if err := phase0.ProcessEffectiveBalanceUpdates(ctx, spec, epc, flats, state); err != nil { + return err + } + if err := phase0.ProcessSlashingsReset(ctx, spec, epc, state); err != nil { + return err + } + if err := phase0.ProcessRandaoMixesReset(ctx, spec, epc, state); err != nil { + return err + } + if err := ProcessHistoricalSummariesUpdate(ctx, spec, epc, state); err != nil { + return err + } + if err := altair.ProcessParticipationFlagUpdates(ctx, spec, state); err != nil { + return err + } + if err := altair.ProcessSyncCommitteeUpdates(ctx, spec, epc, state); err != nil { + return err + } + return nil +} + +func (state *BeaconStateView) ProcessBlock(ctx context.Context, spec *common.Spec, epc *common.EpochsContext, benv *common.BeaconBlockEnvelope) error { + body, ok := benv.Body.(*BeaconBlockBody) + if !ok { + return fmt.Errorf("unexpected block type %T in Bellatrix ProcessBlock", benv.Body) + } + expectedProposer, err := epc.GetBeaconProposer(benv.Slot) + if err != nil { + return err + } + if err := common.ProcessHeader(ctx, spec, state, &benv.BeaconBlockHeader, expectedProposer); err != nil { + return err + } + block := &BeaconBlock{ + Slot: benv.Slot, + ProposerIndex: benv.ProposerIndex, + ParentRoot: benv.ParentRoot, + StateRoot: benv.StateRoot, + Body: *body, + } + if enabled, err := state.IsExecutionEnabled(spec, block); err != nil { + return err + } else if enabled { + if err := ProcessWithdrawals(ctx, spec, state, &body.ExecutionPayload); err != nil { + return err + } + if err := ProcessExecutionPayload(ctx, spec, state, &body.ExecutionPayload, spec.ExecutionEngine); err != nil { + return err + } + } + if err := phase0.ProcessRandaoReveal(ctx, spec, epc, state, body.RandaoReveal); err != nil { + return err + } + if err := phase0.ProcessEth1Vote(ctx, spec, epc, state, body.Eth1Data); err != nil { + return err + } + // Safety checks, in case the user of the function provided too many operations + if err := body.CheckLimits(spec); err != nil { + return err + } + + if err := phase0.ProcessProposerSlashings(ctx, spec, epc, state, body.ProposerSlashings); err != nil { + return err + } + if err := phase0.ProcessAttesterSlashings(ctx, spec, epc, state, body.AttesterSlashings); err != nil { + return err + } + if err := altair.ProcessAttestations(ctx, spec, epc, state, body.Attestations); err != nil { + return err + } + // Note: state.AddValidator changed in Altair, but the deposit processing itself stayed the same. + if err := phase0.ProcessDeposits(ctx, spec, epc, state, body.Deposits); err != nil { + return err + } + if err := phase0.ProcessVoluntaryExits(ctx, spec, epc, state, body.VoluntaryExits); err != nil { + return err + } + if err := ProcessBLSToExecutionChanges(ctx, spec, epc, state, body.BLSToExecutionChanges); err != nil { + return err + } + if err := altair.ProcessSyncAggregate(ctx, spec, epc, state, &body.SyncAggregate); err != nil { + return err + } + return nil +} + +func HasEth1WithdrawalCredential(validator common.Validator) bool { + withdrawalCredentials, err := validator.WithdrawalCredentials() + if err != nil { + panic(err) + } + return bytes.Equal(withdrawalCredentials[:1], []byte{common.ETH1_ADDRESS_WITHDRAWAL_PREFIX}) +} + +func Eth1WithdrawalCredential(validator common.Validator) common.Eth1Address { + withdrawalCredentials, err := validator.WithdrawalCredentials() + if err != nil { + panic(err) + } + var address common.Eth1Address + copy(address[:], withdrawalCredentials[12:]) + return address +} + +func IsFullyWithdrawableValidator(validator common.Validator, balance common.Gwei, epoch common.Epoch) bool { + withdrawableEpoch, err := validator.WithdrawableEpoch() + if err != nil { + panic(err) + } + return HasEth1WithdrawalCredential(validator) && withdrawableEpoch <= epoch && balance > 0 +} + +func IsPartiallyWithdrawableValidator(spec *common.Spec, validator common.Validator, balance common.Gwei, epoch common.Epoch) bool { + effectiveBalance, err := validator.EffectiveBalance() + if err != nil { + panic(err) + } + hasMaxEffectiveBalance := effectiveBalance == spec.MAX_EFFECTIVE_BALANCE + hasExcessBalance := balance > spec.MAX_EFFECTIVE_BALANCE + return HasEth1WithdrawalCredential(validator) && hasMaxEffectiveBalance && hasExcessBalance +} + +type BeaconStateWithWithdrawals interface { + common.BeaconState + NextWithdrawalValidatorIndex() (common.ValidatorIndex, error) + SetNextWithdrawalIndex(nextIndex common.WithdrawalIndex) error + SetNextWithdrawalValidatorIndex(nextValidator common.ValidatorIndex) error + NextWithdrawalIndex() (common.WithdrawalIndex, error) +} + +func GetExpectedWithdrawals(state BeaconStateWithWithdrawals, spec *common.Spec) ([]common.Withdrawal, error) { + slot, err := state.Slot() + if err != nil { + return nil, err + } + epoch := spec.SlotToEpoch(slot) + withdrawalIndex, err := state.NextWithdrawalIndex() + if err != nil { + return nil, err + } + validatorIndex, err := state.NextWithdrawalValidatorIndex() + if err != nil { + return nil, err + } + validators, err := state.Validators() + if err != nil { + return nil, err + } + validatorCount, err := validators.ValidatorCount() + if err != nil { + return nil, err + } + balances, err := state.Balances() + if err != nil { + return nil, err + } + withdrawals := make(common.Withdrawals, 0) + var i uint64 = 0 + for { + validator, err := validators.Validator(validatorIndex) + if err != nil { + return nil, err + } + balance, err := balances.GetBalance(validatorIndex) + if err != nil { + return nil, err + } + if i >= validatorCount || i >= uint64(spec.MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP) { + break + } + if IsFullyWithdrawableValidator(validator, balance, epoch) { + withdrawals = append(withdrawals, common.Withdrawal{ + Index: withdrawalIndex, + ValidatorIndex: validatorIndex, + Address: Eth1WithdrawalCredential(validator), + Amount: balance, + }) + withdrawalIndex += 1 + } else if IsPartiallyWithdrawableValidator(spec, validator, balance, epoch) { + withdrawals = append(withdrawals, common.Withdrawal{ + Index: withdrawalIndex, + ValidatorIndex: validatorIndex, + Address: Eth1WithdrawalCredential(validator), + Amount: balance - spec.MAX_EFFECTIVE_BALANCE, + }) + withdrawalIndex += 1 + } + if len(withdrawals) == int(spec.MAX_WITHDRAWALS_PER_PAYLOAD) { + break + } + validatorIndex = common.ValidatorIndex(uint64(validatorIndex+1) % validatorCount) + i += 1 + } + return withdrawals, nil +} + +type ExecutionPayloadWithWithdrawals interface { + GetWitdrawals() []common.Withdrawal +} + +func ProcessWithdrawals(ctx context.Context, spec *common.Spec, state BeaconStateWithWithdrawals, executionPayload ExecutionPayloadWithWithdrawals) error { + expectedWithdrawals, err := GetExpectedWithdrawals(state, spec) + if err != nil { + return err + } + withdrawals := executionPayload.GetWitdrawals() + if len(expectedWithdrawals) != len(withdrawals) { + return fmt.Errorf("unexpected number of withdrawals in Capella ProcessWithdrawals: want=%d, got=%d", len(expectedWithdrawals), len(withdrawals)) + } + bals, err := state.Balances() + if err != nil { + return err + } + for w := 0; w < len(expectedWithdrawals); w++ { + withdrawal := withdrawals[w] + expectedWithdrawal := expectedWithdrawals[w] + if withdrawal.Index != expectedWithdrawal.Index || + withdrawal.ValidatorIndex != expectedWithdrawal.ValidatorIndex || + !bytes.Equal(withdrawal.Address[:], expectedWithdrawal.Address[:]) || + withdrawal.Amount != expectedWithdrawal.Amount { + return fmt.Errorf("unexpected withdrawal in Capella ProcessWithdrawals: want=%s, got=%s", expectedWithdrawal, withdrawal) + } + if err := common.DecreaseBalance(bals, expectedWithdrawal.ValidatorIndex, expectedWithdrawal.Amount); err != nil { + return fmt.Errorf("failed to decrease balance: %w", err) + } + } + if len(expectedWithdrawals) > 0 { + latestWithdrawal := expectedWithdrawals[len(expectedWithdrawals)-1] + if err := state.SetNextWithdrawalIndex(latestWithdrawal.Index + 1); err != nil { + return fmt.Errorf("failed to set withdrawal index: %w", err) + } + } + validators, err := state.Validators() + if err != nil { + return err + } + validatorCount, err := validators.ValidatorCount() + if err != nil { + return err + } + if len(expectedWithdrawals) == int(spec.MAX_WITHDRAWALS_PER_PAYLOAD) { + latestWithdrawal := expectedWithdrawals[len(expectedWithdrawals)-1] + nextValidatorIndex := common.ValidatorIndex(uint64(latestWithdrawal.ValidatorIndex+1) % validatorCount) + if err = state.SetNextWithdrawalValidatorIndex(nextValidatorIndex); err != nil { + return err + } + } else { + nextValidatorIndex, err := state.NextWithdrawalValidatorIndex() + if err != nil { + return err + } + nextValidatorIndex = common.ValidatorIndex((uint64(nextValidatorIndex) + uint64(spec.MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP)) % validatorCount) + if err = state.SetNextWithdrawalValidatorIndex(nextValidatorIndex); err != nil { + return err + } + } + return nil +} + +type HistoricalSummariesBeaconState interface { + common.BeaconState + HistoricalSummaries() (HistoricalSummariesList, error) +} + +func ProcessHistoricalSummariesUpdate(ctx context.Context, spec *common.Spec, epc *common.EpochsContext, state HistoricalSummariesBeaconState) error { + if err := ctx.Err(); err != nil { + return err + } + // Set historical summaries accumulator + if epc.NextEpoch.Epoch%spec.SlotToEpoch(spec.SLOTS_PER_HISTORICAL_ROOT) == 0 { + if err := UpdateHistoricalSummaries(state); err != nil { + return err + } + } + return nil +} + +func UpdateHistoricalSummaries(state HistoricalSummariesBeaconState) error { + histSummaries, err := state.HistoricalSummaries() + if err != nil { + return err + } + blockRoots, err := state.BlockRoots() + if err != nil { + return err + } + stateRoots, err := state.StateRoots() + if err != nil { + return err + } + hFn := tree.GetHashFn() + return histSummaries.Append(HistoricalSummary{ + BlockSummaryRoot: blockRoots.HashTreeRoot(hFn), + StateSummaryRoot: stateRoots.HashTreeRoot(hFn), + }) +} diff --git a/go.mod b/go.mod index c8f37c4..5289c4c 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,6 @@ require ( github.com/minio/sha256-simd v0.1.0 github.com/protolambda/bls12-381-util v0.0.0-20210720105258-a772f2aac13e github.com/protolambda/messagediff v1.4.0 - github.com/protolambda/ztyp v0.2.2 + github.com/protolambda/ztyp v0.2.3-0.20230210160528-a0236251c773 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b ) diff --git a/go.sum b/go.sum index 79f73cb..5bc149c 100644 --- a/go.sum +++ b/go.sum @@ -12,6 +12,8 @@ github.com/protolambda/messagediff v1.4.0 h1:fk6gxK7WybJCaeOFK1yuh2Ldplx7qYMLibi github.com/protolambda/messagediff v1.4.0/go.mod h1:LboJp0EwIbJsePYpzh5Op/9G1/4mIztMRYzzwR0dR2M= github.com/protolambda/ztyp v0.2.2 h1:rVcL3vBu9W/aV646zF6caLS/dyn9BN8NYiuJzicLNyY= github.com/protolambda/ztyp v0.2.2/go.mod h1:9bYgKGqg3wJqT9ac1gI2hnVb0STQq7p/1lapqrqY1dU= +github.com/protolambda/ztyp v0.2.3-0.20230210160528-a0236251c773 h1:EvWc9oYKLUV5tM77CbRj15Z/yBdEaLI+zbu+mpIpBXo= +github.com/protolambda/ztyp v0.2.3-0.20230210160528-a0236251c773/go.mod h1:9bYgKGqg3wJqT9ac1gI2hnVb0STQq7p/1lapqrqY1dU= golang.org/x/sys v0.0.0-20201101102859-da207088b7d1 h1:a/mKvvZr9Jcc8oKfcmgzyp7OwF73JPWsQLvH1z2Kxck= golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= From c89df85f6245341878d65ec025ccbf8bec2ffcdb Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Thu, 5 Oct 2023 15:31:06 +0200 Subject: [PATCH 2/4] fix: missing descriptor in field --- eth2/beacon/verkle/execution.go | 1 + 1 file changed, 1 insertion(+) diff --git a/eth2/beacon/verkle/execution.go b/eth2/beacon/verkle/execution.go index 630fcdc..33ffb7d 100644 --- a/eth2/beacon/verkle/execution.go +++ b/eth2/beacon/verkle/execution.go @@ -88,6 +88,7 @@ var ExecutionPayloadHeaderType = ContainerType("ExecutionPayloadHeader", []Field {"block_hash", common.Hash32Type}, {"transactions_root", RootType}, {"withdrawals_root", RootType}, + {"execution_witness_root", RootType}, }) type ExecutionPayloadHeaderView struct { From a610beb530dd0eeb506881b24d426277aeec6a47 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Fri, 6 Oct 2023 09:44:44 +0200 Subject: [PATCH 3/4] fix: add ELECTRA_FORK_* --- eth2/beacon/common/spec.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/eth2/beacon/common/spec.go b/eth2/beacon/common/spec.go index ccf32bf..a70cc5c 100644 --- a/eth2/beacon/common/spec.go +++ b/eth2/beacon/common/spec.go @@ -147,6 +147,9 @@ type Config struct { DENEB_FORK_VERSION Version `yaml:"DENEB_FORK_VERSION" json:"DENEB_FORK_VERSION"` DENEB_FORK_EPOCH Epoch `yaml:"DENEB_FORK_EPOCH" json:"DENEB_FORK_EPOCH"` + ELECTRA_FORK_VERSION Version `yaml:"ELECTRA_FORK_VERSION" json:"ELECTRA_FORK_VERSION"` + ELECTRA_FORK_EPOCH Epoch `yaml:"ELECTRA_FORK_EPOCH" json:"ELECTRA_FORK_EPOCH"` + // Merge transition TERMINAL_TOTAL_DIFFICULTY Uint256View `yaml:"TERMINAL_TOTAL_DIFFICULTY" json:"TERMINAL_TOTAL_DIFFICULTY"` TERMINAL_BLOCK_HASH Bytes32 `yaml:"TERMINAL_BLOCK_HASH" json:"TERMINAL_BLOCK_HASH"` From 5c77207d5f11b09187f6554746e7d95a032cf84e Mon Sep 17 00:00:00 2001 From: g11tech Date: Fri, 27 Sep 2024 15:55:45 +0530 Subject: [PATCH 4/4] add parentstateroot to execution witness (#1) --- eth2/beacon/verkle/execution.go | 1 + eth2/beacon/verkle/execution_witness.go | 9 +++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/eth2/beacon/verkle/execution.go b/eth2/beacon/verkle/execution.go index 33ffb7d..49a0d89 100644 --- a/eth2/beacon/verkle/execution.go +++ b/eth2/beacon/verkle/execution.go @@ -68,6 +68,7 @@ var VerkleProofType = ContainerType("VerkleProof", VerkleProofFields) var ExecutionWitnessFields = []FieldDef{ {"state_diff", ListType(StemStateDiffType, MAX_STEMS)}, {"verkle_proof", VerkleProofType}, + {"parent_state_root", common.Bytes32Type}, } var ExecutionWitnessType = ContainerType("ExecutionWitness", ExecutionWitnessFields) diff --git a/eth2/beacon/verkle/execution_witness.go b/eth2/beacon/verkle/execution_witness.go index 83b9179..d8d551d 100644 --- a/eth2/beacon/verkle/execution_witness.go +++ b/eth2/beacon/verkle/execution_witness.go @@ -465,10 +465,11 @@ func (vp *VerkleProof) HashTreeRoot(h tree.HashFn) tree.Root { type ExecutionWitness struct { StateDiff StateDiff VerkleProof VerkleProof + ParentStateRoot common.Bytes32 } func (ew *ExecutionWitness) Deserialize(dr *codec.DecodingReader) error { - return dr.Container(&ew.StateDiff, &ew.VerkleProof) + return dr.Container(&ew.StateDiff, &ew.VerkleProof, &ew.ParentStateRoot) } func (ew *ExecutionWitness) FixedLength() uint64 { @@ -476,13 +477,13 @@ func (ew *ExecutionWitness) FixedLength() uint64 { } func (ew *ExecutionWitness) Serialize(w *codec.EncodingWriter) error { - return w.Container(&ew.StateDiff, &ew.VerkleProof) + return w.Container(&ew.StateDiff, &ew.VerkleProof, &ew.ParentStateRoot) } func (ew *ExecutionWitness) ByteLength() uint64 { - return ew.StateDiff.ByteLength() + ew.VerkleProof.ByteLength() + return ew.StateDiff.ByteLength() + ew.VerkleProof.ByteLength() + ew.ParentStateRoot.ByteLength() } func (ew *ExecutionWitness) HashTreeRoot(fn tree.HashFn) common.Root { - return fn.HashTreeRoot(&ew.StateDiff, &ew.VerkleProof) + return fn.HashTreeRoot(&ew.StateDiff, &ew.VerkleProof, &ew.ParentStateRoot) }