diff --git a/Makefile b/Makefile index 849fc211..6df0f378 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ clean: create-test-dir: mkdir -p $(TEST_OUT_DIR) -SPEC_VERSION ?= v1.2.0-rc.1 +SPEC_VERSION ?= v1.3.0-alpha.2 clear-tests: rm -rf tests/spec/eth2.0-spec-tests diff --git a/eth2/beacon/altair/lightclient.go b/eth2/beacon/altair/lightclient.go index c4efede8..26a51005 100644 --- a/eth2/beacon/altair/lightclient.go +++ b/eth2/beacon/altair/lightclient.go @@ -142,7 +142,7 @@ func LightClientUpdateType(spec *common.Spec) *ContainerTypeDef { {"finalized_header", common.BeaconBlockHeaderType}, {"finality_branch", FinalizedRootProofBranchType}, {"sync_aggregate", SyncAggregateType(spec)}, - {"fork_version", common.VersionType}, + {"signature_slot", common.SlotType}, }) } @@ -157,8 +157,8 @@ type LightClientUpdate struct { FinalityBranch FinalizedRootProofBranch `yaml:"finality_branch" json:"finality_branch"` // Sync committee aggregate signature SyncAggregate SyncAggregate `yaml:"sync_aggregate" json:"sync_aggregate"` - // Fork version for the aggregate signature - ForkVersion common.Version `yaml:"fork_version" json:"fork_version"` + // Slot at which the aggregate signature was created (untrusted) + SignatureSlot common.Slot `yaml:"signature_slot" json:"signature_slot"` } func (lcu *LightClientUpdate) Deserialize(spec *common.Spec, dr *codec.DecodingReader) error { @@ -169,7 +169,7 @@ func (lcu *LightClientUpdate) Deserialize(spec *common.Spec, dr *codec.DecodingR &lcu.FinalizedHeader, &lcu.FinalityBranch, spec.Wrap(&lcu.SyncAggregate), - &lcu.ForkVersion, + &lcu.SignatureSlot, ) } @@ -181,7 +181,7 @@ func (lcu *LightClientUpdate) Serialize(spec *common.Spec, w *codec.EncodingWrit &lcu.FinalizedHeader, &lcu.FinalityBranch, spec.Wrap(&lcu.SyncAggregate), - &lcu.ForkVersion, + &lcu.SignatureSlot, ) } @@ -193,7 +193,7 @@ func (lcu *LightClientUpdate) ByteLength(spec *common.Spec) uint64 { &lcu.FinalizedHeader, &lcu.FinalityBranch, spec.Wrap(&lcu.SyncAggregate), - &lcu.ForkVersion, + &lcu.SignatureSlot, ) } @@ -205,7 +205,7 @@ func (lcu *LightClientUpdate) FixedLength(spec *common.Spec) uint64 { &lcu.FinalizedHeader, &lcu.FinalityBranch, spec.Wrap(&lcu.SyncAggregate), - &lcu.ForkVersion, + &lcu.SignatureSlot, ) } @@ -217,6 +217,6 @@ func (lcu *LightClientUpdate) HashTreeRoot(spec *common.Spec, hFn tree.HashFn) c &lcu.FinalizedHeader, &lcu.FinalityBranch, spec.Wrap(&lcu.SyncAggregate), - &lcu.ForkVersion, + &lcu.SignatureSlot, ) } diff --git a/eth2/beacon/capella/block.go b/eth2/beacon/capella/block.go new file mode 100644 index 00000000..10acf760 --- /dev/null +++ b/eth2/beacon/capella/block.go @@ -0,0 +1,324 @@ +package capella + +import ( + "fmt" + + "github.com/protolambda/zrnt/eth2/beacon/altair" + "github.com/protolambda/zrnt/eth2/beacon/common" + "github.com/protolambda/zrnt/eth2/beacon/phase0" + "github.com/protolambda/ztyp/codec" + "github.com/protolambda/ztyp/tree" + . "github.com/protolambda/ztyp/view" +) + +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/capella/bls_to_execution.go b/eth2/beacon/capella/bls_to_execution.go new file mode 100644 index 00000000..776ed5f1 --- /dev/null +++ b/eth2/beacon/capella/bls_to_execution.go @@ -0,0 +1,85 @@ +package capella + +import ( + "bytes" + "context" + "fmt" + + blsu "github.com/protolambda/bls12-381-util" + "github.com/protolambda/zrnt/eth2/beacon/common" + "github.com/protolambda/zrnt/eth2/util/hashing" + "github.com/protolambda/ztyp/tree" +) + +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) + } + currentSlot, err := state.Slot() + if err != nil { + return err + } + prevSlot := currentSlot.Previous() + domain, err := common.GetDomain(state, common.DOMAIN_BLS_TO_EXECUTION_CHANGE, spec.SlotToEpoch(prevSlot)) + if err != nil { + return err + } + + 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/capella/execution.go b/eth2/beacon/capella/execution.go new file mode 100644 index 00000000..d78f98c3 --- /dev/null +++ b/eth2/beacon/capella/execution.go @@ -0,0 +1,331 @@ +package capella + +import ( + "context" + "fmt" + + "github.com/protolambda/zrnt/eth2/beacon/common" + "github.com/protolambda/ztyp/codec" + "github.com/protolambda/ztyp/tree" + . "github.com/protolambda/ztyp/view" +) + +type Hash32 = common.Root + +const Hash32Type = RootType + +var ExecutionPayloadHeaderType = ContainerType("ExecutionPayloadHeader", []FieldDef{ + {"parent_hash", 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", 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) != 15 { + return nil, fmt.Errorf("unexpected number of execution payload header fields: %d", len(values)) + } + parentHash, err := AsRoot(values[0], err) + feeRecipient, err := common.AsEth1Address(values[1], err) + stateRoot, err := AsRoot(values[2], err) + receiptsRoot, err := AsRoot(values[3], err) + logsBloomView, err := common.AsLogsBloom(values[4], err) + prevRandao, err := AsRoot(values[5], err) + blockNumber, err := AsUint64(values[6], err) + gasLimit, err := AsUint64(values[7], err) + gasUsed, err := AsUint64(values[8], err) + timestamp, err := common.AsTimestamp(values[9], err) + extraDataView, err := common.AsExtraData(values[10], err) + baseFeePerGas, err := AsUint256(values[11], err) + blockHash, err := AsRoot(values[12], err) + transactionsRoot, err := AsRoot(values[13], err) + withdrawalsRoot, err := AsRoot(values[14], 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, + }, nil +} + +func (v *ExecutionPayloadHeaderView) ParentHash() (Hash32, error) { + return AsRoot(v.Get(0)) +} + +func (v *ExecutionPayloadHeaderView) FeeRecipient() (common.Eth1Address, error) { + return common.AsEth1Address(v.Get(1)) +} + +func (v *ExecutionPayloadHeaderView) StateRoot() (common.Bytes32, error) { + return AsRoot(v.Get(2)) +} + +func (v *ExecutionPayloadHeaderView) ReceiptRoot() (common.Bytes32, error) { + return AsRoot(v.Get(3)) +} + +func (v *ExecutionPayloadHeaderView) LogsBloom() (*common.LogsBloom, error) { + logV, err := common.AsLogsBloom(v.Get(4)) + if err != nil { + return nil, err + } + return logV.Raw() +} + +func (v *ExecutionPayloadHeaderView) Random() (common.Bytes32, error) { + return AsRoot(v.Get(5)) +} + +func (v *ExecutionPayloadHeaderView) BlockNumber() (Uint64View, error) { + return AsUint64(v.Get(6)) +} + +func (v *ExecutionPayloadHeaderView) GasLimit() (Uint64View, error) { + return AsUint64(v.Get(7)) +} + +func (v *ExecutionPayloadHeaderView) GasUsed() (Uint64View, error) { + return AsUint64(v.Get(8)) +} + +func (v *ExecutionPayloadHeaderView) Timestamp() (common.Timestamp, error) { + return common.AsTimestamp(v.Get(9)) +} + +func (v *ExecutionPayloadHeaderView) BaseFeePerGas() (Uint256View, error) { + return AsUint256(v.Get(10)) +} + +func (v *ExecutionPayloadHeaderView) BlockHash() (Hash32, error) { + return AsRoot(v.Get(11)) +} + +func (v *ExecutionPayloadHeaderView) TransactionsRoot() (common.Root, error) { + return AsRoot(v.Get(12)) +} + +func AsExecutionPayloadHeader(v View, err error) (*ExecutionPayloadHeaderView, error) { + c, err := AsContainer(v, err) + return &ExecutionPayloadHeaderView{c}, err +} + +type ExecutionPayloadHeader struct { + ParentHash 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 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"` +} + +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) + + v, err := AsExecutionPayloadHeader(ExecutionPayloadHeaderType.FromFields(pr, cb, sr, rr, lb, rng, nr, gl, gu, ts, ed, bf, bh, tr, wr)) + 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, + ) +} + +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, + ) +} + +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, + ) +} + +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, + ) +} + +func ExecutionPayloadType(spec *common.Spec) *ContainerTypeDef { + return ContainerType("ExecutionPayload", []FieldDef{ + {"parent_hash", 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", Hash32Type}, + {"transactions", common.PayloadTransactionsType(spec)}, + {"withdrawals", common.WithdrawalsType(spec)}, + }) +} + +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 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 Hash32 `json:"block_hash" yaml:"block_hash"` + Transactions common.PayloadTransactions `json:"transactions" yaml:"transactions"` + Withdrawals common.Withdrawals `json:"withdrawals" yaml:"withdrawals"` +} + +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), + ) +} + +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), + ) +} + +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), + ) +} + +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), + ) +} + +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()), + } +} + +type ExecutionEngine interface { + ExecutePayload(ctx context.Context, executionPayload *ExecutionPayload) (valid bool, err error) + // TODO: remaining interface parts +} diff --git a/eth2/beacon/capella/execution_payload.go b/eth2/beacon/capella/execution_payload.go new file mode 100644 index 00000000..42bf50fe --- /dev/null +++ b/eth2/beacon/capella/execution_payload.go @@ -0,0 +1,81 @@ +package capella + +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/capella/fork.go b/eth2/beacon/capella/fork.go new file mode 100644 index 00000000..910ba403 --- /dev/null +++ b/eth2/beacon/capella/fork.go @@ -0,0 +1,170 @@ +package capella + +import ( + "github.com/protolambda/zrnt/eth2/beacon/bellatrix" + "github.com/protolambda/zrnt/eth2/beacon/common" + "github.com/protolambda/ztyp/view" +) + +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, + )) +} diff --git a/eth2/beacon/capella/state.go b/eth2/beacon/capella/state.go new file mode 100644 index 00000000..df2050cf --- /dev/null +++ b/eth2/beacon/capella/state.go @@ -0,0 +1,564 @@ +package capella + +import ( + "bytes" + + "github.com/protolambda/zrnt/eth2/beacon/altair" + "github.com/protolambda/zrnt/eth2/beacon/common" + "github.com/protolambda/zrnt/eth2/beacon/phase0" + "github.com/protolambda/ztyp/codec" + "github.com/protolambda/ztyp/tree" + . "github.com/protolambda/ztyp/view" +) + +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"` + // 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"` +} + +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, + ) +} + +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, + ) +} + +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, + ) +} + +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, + ) +} + +// 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 +) + +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}, + }) +} + +// 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)) +} + +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()) +} diff --git a/eth2/beacon/capella/transition.go b/eth2/beacon/capella/transition.go new file mode 100644 index 00000000..7bb4ef68 --- /dev/null +++ b/eth2/beacon/capella/transition.go @@ -0,0 +1,341 @@ +package capella + +import ( + "bytes" + "context" + "fmt" + + "github.com/protolambda/zrnt/eth2/beacon/altair" + "github.com/protolambda/zrnt/eth2/beacon/common" + "github.com/protolambda/zrnt/eth2/beacon/phase0" + "github.com/protolambda/ztyp/tree" +) + +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 := phase0.ProcessHistoricalRootsUpdate(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 +} + +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 +} + +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 +} + +func (state *BeaconStateView) GetExpectedWithdrawals(spec *common.Spec) (common.Withdrawals, 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 +} + +func ProcessWithdrawals(ctx context.Context, spec *common.Spec, state *BeaconStateView, executionPayload *ExecutionPayload) error { + expectedWithdrawals, err := state.GetExpectedWithdrawals(spec) + if err != nil { + return err + } + if len(expectedWithdrawals) != len(executionPayload.Withdrawals) { + return fmt.Errorf("unexpected number of withdrawals in Capella ProcessWithdrawals: want=%d, got=%d", len(expectedWithdrawals), len(executionPayload.Withdrawals)) + } + bals, err := state.Balances() + if err != nil { + return err + } + for w := 0; w < len(expectedWithdrawals); w++ { + withdrawal := executionPayload.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 +} diff --git a/eth2/beacon/common/execution.go b/eth2/beacon/common/execution.go index 40a571ec..ef3cd117 100644 --- a/eth2/beacon/common/execution.go +++ b/eth2/beacon/common/execution.go @@ -368,6 +368,6 @@ func (ep *ExecutionPayload) Header(spec *Spec) *ExecutionPayloadHeader { } type ExecutionEngine interface { - ExecutePayload(ctx context.Context, executionPayload *ExecutionPayload) (valid bool, err error) + ExecutePayload(ctx context.Context, executionPayload interface{}) (valid bool, err error) // TODO: remaining interface parts } diff --git a/eth2/beacon/common/logs_bloom.go b/eth2/beacon/common/logs_bloom.go index 597ded19..2621d630 100644 --- a/eth2/beacon/common/logs_bloom.go +++ b/eth2/beacon/common/logs_bloom.go @@ -4,8 +4,6 @@ import ( "bytes" "encoding/hex" "errors" - "fmt" - "github.com/protolambda/ztyp/codec" "github.com/protolambda/ztyp/conv" "github.com/protolambda/ztyp/tree" @@ -19,14 +17,12 @@ type LogsBloomView struct { } func (v *LogsBloomView) Raw() (*LogsBloom, error) { - var out LogsBloom - buf := codec.NewEncodingWriter(bytes.NewBuffer(out[:])) - if err := v.Serialize(buf); err != nil { + var buf bytes.Buffer + if err := v.Serialize(codec.NewEncodingWriter(&buf)); err != nil { return nil, err } - if x := buf.Written(); x != BYTES_PER_LOGS_BLOOM { - return nil, fmt.Errorf("unexpected logs bloom tree view, got %d bytes", x) - } + var out LogsBloom + copy(out[:], buf.Bytes()) return &out, nil } diff --git a/eth2/beacon/common/spec.go b/eth2/beacon/common/spec.go index 72fbb341..8cfdb0c4 100644 --- a/eth2/beacon/common/spec.go +++ b/eth2/beacon/common/spec.go @@ -32,6 +32,9 @@ var DOMAIN_SYNC_COMMITTEE = BLSDomainType{0x07, 0x00, 0x00, 0x00} var DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF = BLSDomainType{0x08, 0x00, 0x00, 0x00} var DOMAIN_CONTRIBUTION_AND_PROOF = BLSDomainType{0x09, 0x00, 0x00, 0x00} +// Capella +var DOMAIN_BLS_TO_EXECUTION_CHANGE = BLSDomainType{0x0A, 0x00, 0x00, 0x00} + // Sharding var DOMAIN_SHARD_BLOB = BLSDomainType{0x80, 0x00, 0x00, 0x00} @@ -110,6 +113,12 @@ type BellatrixPreset struct { MAX_EXTRA_DATA_BYTES Uint64View `yaml:"MAX_EXTRA_DATA_BYTES" json:"MAX_EXTRA_DATA_BYTES"` } +type CapellaPreset struct { + MAX_BLS_TO_EXECUTION_CHANGES Uint64View `yaml:"MAX_BLS_TO_EXECUTION_CHANGES" json:"MAX_BLS_TO_EXECUTION_CHANGES"` + MAX_WITHDRAWALS_PER_PAYLOAD Uint64View `yaml:"MAX_WITHDRAWALS_PER_PAYLOAD" json:"MAX_WITHDRAWALS_PER_PAYLOAD"` + MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP Uint64View `yaml:"MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP" json:"MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP"` +} + type ShardingPreset struct { // Misc. MAX_SHARDS Uint64View `yaml:"MAX_SHARDS" json:"MAX_SHARDS"` @@ -146,6 +155,10 @@ type Config struct { BELLATRIX_FORK_VERSION Version `yaml:"BELLATRIX_FORK_VERSION" json:"BELLATRIX_FORK_VERSION"` BELLATRIX_FORK_EPOCH Epoch `yaml:"BELLATRIX_FORK_EPOCH" json:"BELLATRIX_FORK_EPOCH"` + // Capella + CAPELLA_FORK_VERSION Version `yaml:"CAPELLA_FORK_VERSION" json:"CAPELLA_FORK_VERSION"` + CAPELLA_FORK_EPOCH Epoch `yaml:"CAPELLA_FORK_EPOCH" json:"CAPELLA_FORK_EPOCH"` + // Sharding SHARDING_FORK_VERSION Version `yaml:"SHARDING_FORK_VERSION" json:"SHARDING_FORK_VERSION"` SHARDING_FORK_EPOCH Epoch `yaml:"SHARDING_FORK_EPOCH" json:"SHARDING_FORK_EPOCH"` @@ -253,6 +266,7 @@ type Spec struct { Phase0Preset `json:",inline" yaml:",inline"` AltairPreset `json:",inline" yaml:",inline"` BellatrixPreset `json:",inline" yaml:",inline"` + CapellaPreset `json:",inline" yaml:",inline"` ShardingPreset `json:",inline" yaml:",inline"` Config `json:",inline" yaml:",inline"` Setup `json:",inline" yaml:",inline"` @@ -289,10 +303,10 @@ func (spec *Spec) ForkVersion(slot Slot) Version { return spec.GENESIS_FORK_VERSION } else if epoch < spec.BELLATRIX_FORK_EPOCH { return spec.ALTAIR_FORK_VERSION - } else if epoch < spec.SHARDING_FORK_EPOCH { + } else if epoch < spec.CAPELLA_FORK_EPOCH { return spec.BELLATRIX_FORK_VERSION } else { - return spec.SHARDING_FORK_VERSION + return spec.CAPELLA_FORK_VERSION } } diff --git a/eth2/beacon/common/state.go b/eth2/beacon/common/state.go index c407d5f4..10b91fc5 100644 --- a/eth2/beacon/common/state.go +++ b/eth2/beacon/common/state.go @@ -27,6 +27,7 @@ type Eth1DataVotes interface { type Validator interface { Pubkey() (BLSPubkey, error) WithdrawalCredentials() (out Root, err error) + SetWithdrawalCredentials(out Root) (err error) EffectiveBalance() (Gwei, error) SetEffectiveBalance(b Gwei) error Slashed() (bool, error) diff --git a/eth2/beacon/common/withdrawals.go b/eth2/beacon/common/withdrawals.go index f654edd9..24c77402 100644 --- a/eth2/beacon/common/withdrawals.go +++ b/eth2/beacon/common/withdrawals.go @@ -4,6 +4,10 @@ import ( "encoding/hex" "errors" "fmt" + + "github.com/protolambda/ztyp/codec" + "github.com/protolambda/ztyp/tree" + . "github.com/protolambda/ztyp/view" ) type WithdrawalPrefix [1]byte @@ -29,3 +33,372 @@ func (p *WithdrawalPrefix) UnmarshalText(text []byte) error { _, err := hex.Decode(p[:], text) return err } + +const WithdrawalIndexType = Uint64Type + +type WithdrawalIndex Uint64View + +func AsWithdrawalIndex(v View, err error) (WithdrawalIndex, error) { + i, err := AsUint64(v, err) + return WithdrawalIndex(i), err +} + +func (a *WithdrawalIndex) Deserialize(dr *codec.DecodingReader) error { + return (*Uint64View)(a).Deserialize(dr) +} + +func (i WithdrawalIndex) Serialize(w *codec.EncodingWriter) error { + return w.WriteUint64(uint64(i)) +} + +func (WithdrawalIndex) ByteLength() uint64 { + return 8 +} + +func (WithdrawalIndex) FixedLength() uint64 { + return 8 +} + +func (t WithdrawalIndex) HashTreeRoot(hFn tree.HashFn) Root { + return Uint64View(t).HashTreeRoot(hFn) +} + +func (e WithdrawalIndex) MarshalJSON() ([]byte, error) { + return Uint64View(e).MarshalJSON() +} + +func (e *WithdrawalIndex) UnmarshalJSON(b []byte) error { + return ((*Uint64View)(e)).UnmarshalJSON(b) +} + +func (e WithdrawalIndex) String() string { + return Uint64View(e).String() +} + +var WithdrawalType = ContainerType("Withdrawal", []FieldDef{ + {"index", WithdrawalIndexType}, + {"validator_index", ValidatorIndexType}, + {"address", Eth1AddressType}, + {"amount", GweiType}, +}) + +type WithdrawalView struct { + *ContainerView +} + +func (v *WithdrawalView) Raw() (*Withdrawal, error) { + values, err := v.FieldValues() + if err != nil { + return nil, err + } + if len(values) != 4 { + return nil, fmt.Errorf("unexpected number of withdrawal fields: %d", len(values)) + } + index, err := AsWithdrawalIndex(values[0], err) + validatorIndex, err := AsValidatorIndex(values[1], err) + address, err := AsEth1Address(values[2], err) + amount, err := AsGwei(values[3], err) + if err != nil { + return nil, err + } + return &Withdrawal{ + Index: index, + ValidatorIndex: validatorIndex, + Address: address, + Amount: amount, + }, nil +} + +func (v *WithdrawalView) Index() (WithdrawalIndex, error) { + return AsWithdrawalIndex(v.Get(0)) +} + +func (v *WithdrawalView) ValidatorIndex() (ValidatorIndex, error) { + return AsValidatorIndex(v.Get(1)) +} + +func (v *WithdrawalView) Address() (Eth1Address, error) { + return AsEth1Address(v.Get(2)) +} + +func (v *WithdrawalView) Amount() (Gwei, error) { + return AsGwei(v.Get(3)) +} + +func AsWithdrawal(v View, err error) (*WithdrawalView, error) { + c, err := AsContainer(v, err) + return &WithdrawalView{c}, err +} + +type Withdrawal struct { + Index WithdrawalIndex `json:"index" yaml:"index"` + ValidatorIndex ValidatorIndex `json:"validator_index" yaml:"validator_index"` + Address Eth1Address `json:"address" yaml:"address"` + Amount Gwei `json:"amount" yaml:"amount"` +} + +func (s *Withdrawal) View() *WithdrawalView { + i, vi, ad, am := s.Index, s.ValidatorIndex, s.Address, s.Amount + v, err := AsWithdrawal(WithdrawalType.FromFields(Uint64View(i), Uint64View(vi), ad.View(), Uint64View(am))) + if err != nil { + panic(err) + } + return v +} + +func (s *Withdrawal) Deserialize(dr *codec.DecodingReader) error { + return dr.FixedLenContainer(&s.Index, &s.ValidatorIndex, &s.Address, &s.Amount) +} + +func (s *Withdrawal) Serialize(w *codec.EncodingWriter) error { + return w.FixedLenContainer(&s.Index, &s.ValidatorIndex, &s.Address, &s.Amount) +} + +func (s *Withdrawal) ByteLength() uint64 { + return Uint64Type.TypeByteLength()*3 + Eth1AddressType.TypeByteLength() +} + +func (s *Withdrawal) FixedLength() uint64 { + return Uint64Type.TypeByteLength()*3 + Eth1AddressType.TypeByteLength() +} + +func (s *Withdrawal) HashTreeRoot(hFn tree.HashFn) Root { + return hFn.HashTreeRoot(&s.Index, &s.ValidatorIndex, &s.Address, &s.Amount) +} + +func WithdrawalsType(spec *Spec) ListTypeDef { + return ListType(WithdrawalType, uint64(spec.MAX_WITHDRAWALS_PER_PAYLOAD)) +} + +type Withdrawals []Withdrawal + +func (ws *Withdrawals) Deserialize(spec *Spec, dr *codec.DecodingReader) error { + return dr.List(func() codec.Deserializable { + i := len(*ws) + *ws = append(*ws, Withdrawal{}) + return &((*ws)[i]) + }, WithdrawalType.TypeByteLength(), uint64(spec.MAX_WITHDRAWALS_PER_PAYLOAD)) +} + +func (ws Withdrawals) Serialize(spec *Spec, w *codec.EncodingWriter) error { + return w.List(func(i uint64) codec.Serializable { + return &ws[i] + }, WithdrawalType.TypeByteLength(), uint64(len(ws))) +} + +func (ws Withdrawals) ByteLength(spec *Spec) (out uint64) { + return WithdrawalType.TypeByteLength() * uint64(len(ws)) +} + +func (ws *Withdrawals) FixedLength(*Spec) uint64 { + return 0 +} + +func (ws Withdrawals) HashTreeRoot(spec *Spec, hFn tree.HashFn) Root { + length := uint64(len(ws)) + return hFn.ComplexListHTR(func(i uint64) tree.HTR { + if i < length { + return &ws[i] + } + return nil + }, length, uint64(spec.MAX_WITHDRAWALS_PER_PAYLOAD)) +} + +var BLSToExecutionChangeType = ContainerType("BLSToExecutionChange", []FieldDef{ + {"validator_index", ValidatorIndexType}, + {"from_bls_pubkey", BLSPubkeyType}, + {"to_execution_address", Eth1AddressType}, +}) + +type BLSToExecutionChangeView struct { + *ContainerView +} + +func (v *BLSToExecutionChangeView) Raw() (*BLSToExecutionChange, error) { + values, err := v.FieldValues() + if err != nil { + return nil, err + } + if len(values) != 3 { + return nil, fmt.Errorf("unexpected number of bls to execution change fields: %d", len(values)) + } + validatorIndex, err := AsValidatorIndex(values[0], err) + fromBLSPubKey, err := AsBLSPubkey(values[1], err) + toExecAddress, err := AsEth1Address(values[2], err) + if err != nil { + return nil, err + } + return &BLSToExecutionChange{ + ValidatorIndex: validatorIndex, + FromBLSPubKey: fromBLSPubKey, + ToExecutionAddress: toExecAddress, + }, nil +} + +func (v *BLSToExecutionChangeView) ValidatorIndex() (ValidatorIndex, error) { + return AsValidatorIndex(v.Get(0)) +} + +func (v *BLSToExecutionChangeView) FromBLSPubKey() (BLSPubkey, error) { + return AsBLSPubkey(v.Get(1)) +} + +func (v *BLSToExecutionChangeView) ToExecutionAddress() (Eth1Address, error) { + return AsEth1Address(v.Get(2)) +} + +func AsBLSToExecutionChange(v View, err error) (*BLSToExecutionChangeView, error) { + c, err := AsContainer(v, err) + return &BLSToExecutionChangeView{c}, err +} + +type BLSToExecutionChange struct { + ValidatorIndex ValidatorIndex `json:"validator_index" yaml:"validator_index"` + FromBLSPubKey BLSPubkey `json:"from_bls_pubkey" yaml:"from_bls_pubkey"` + ToExecutionAddress Eth1Address `json:"to_execution_address" yaml:"to_execution_address"` +} + +func (s *BLSToExecutionChange) View() *BLSToExecutionChangeView { + vi, pk, ea := s.ValidatorIndex, s.FromBLSPubKey, s.ToExecutionAddress + v, err := AsBLSToExecutionChange(ExecutionPayloadHeaderType.FromFields(Uint64View(vi), ViewPubkey(&pk), ea.View())) + if err != nil { + panic(err) + } + return v +} + +func (s *BLSToExecutionChange) Deserialize(dr *codec.DecodingReader) error { + return dr.FixedLenContainer(&s.ValidatorIndex, &s.FromBLSPubKey, &s.ToExecutionAddress) +} + +func (s *BLSToExecutionChange) Serialize(w *codec.EncodingWriter) error { + return w.FixedLenContainer(&s.ValidatorIndex, &s.FromBLSPubKey, &s.ToExecutionAddress) +} + +func (s *BLSToExecutionChange) ByteLength() uint64 { + return 8 + 48 + 20 +} + +func (s *BLSToExecutionChange) FixedLength() uint64 { + return 8 + 48 + 20 +} + +func (s *BLSToExecutionChange) HashTreeRoot(hFn tree.HashFn) Root { + return hFn.HashTreeRoot(&s.ValidatorIndex, &s.FromBLSPubKey, &s.ToExecutionAddress) +} + +var SignedBLSToExecutionChangeType = ContainerType("SignedBLSToExecutionChange", []FieldDef{ + {"message", BLSToExecutionChangeType}, + {"signature", BLSSignatureType}, +}) + +type SignedBLSToExecutionChangeView struct { + *ContainerView +} + +func (v *SignedBLSToExecutionChangeView) Raw() (*SignedBLSToExecutionChange, error) { + values, err := v.FieldValues() + if err != nil { + return nil, err + } + if len(values) != 2 { + return nil, fmt.Errorf("unexpected number of signed bls to execution change fields: %d", len(values)) + } + blsToExecView, err := AsBLSToExecutionChange(values[0], err) + signature, err := AsBLSSignature(values[1], err) + if err != nil { + return nil, err + } + blsToExec, err := blsToExecView.Raw() + if err != nil { + return nil, err + } + return &SignedBLSToExecutionChange{ + BLSToExecutionChange: *blsToExec, + Signature: signature, + }, nil +} + +func (v *SignedBLSToExecutionChangeView) BLSToExecutionChange() (*BLSToExecutionChangeView, error) { + return AsBLSToExecutionChange(v.Get(0)) +} + +func (v *SignedBLSToExecutionChangeView) Signature() (BLSSignature, error) { + return AsBLSSignature(v.Get(1)) +} + +func AsSignedBLSToExecutionChange(v View, err error) (*SignedBLSToExecutionChangeView, error) { + c, err := AsContainer(v, err) + return &SignedBLSToExecutionChangeView{c}, err +} + +type SignedBLSToExecutionChange struct { + BLSToExecutionChange BLSToExecutionChange `json:"message" yaml:"message"` + Signature BLSSignature `json:"signature" yaml:"signature"` +} + +func (s *SignedBLSToExecutionChange) View() *SignedBLSToExecutionChangeView { + v, err := AsSignedBLSToExecutionChange(SignedBLSToExecutionChangeType.FromFields(s.BLSToExecutionChange.View(), ViewSignature(&s.Signature))) + if err != nil { + panic(err) + } + return v +} + +func (s *SignedBLSToExecutionChange) Deserialize(dr *codec.DecodingReader) error { + return dr.FixedLenContainer(&s.BLSToExecutionChange, &s.Signature) +} + +func (s *SignedBLSToExecutionChange) Serialize(w *codec.EncodingWriter) error { + return w.FixedLenContainer(&s.BLSToExecutionChange, &s.Signature) +} + +func (s *SignedBLSToExecutionChange) ByteLength() uint64 { + return codec.ContainerLength(&s.BLSToExecutionChange, &s.Signature) +} + +func (s *SignedBLSToExecutionChange) FixedLength() uint64 { + return codec.ContainerLength(&s.BLSToExecutionChange, &s.Signature) +} + +func (s *SignedBLSToExecutionChange) HashTreeRoot(hFn tree.HashFn) Root { + return hFn.HashTreeRoot(&s.BLSToExecutionChange, &s.Signature) +} + +func BlockSignedBLSToExecutionChangesType(spec *Spec) ListTypeDef { + return ListType(SignedBLSToExecutionChangeType, uint64(spec.MAX_BLS_TO_EXECUTION_CHANGES)) +} + +type SignedBLSToExecutionChanges []SignedBLSToExecutionChange + +func (li *SignedBLSToExecutionChanges) Deserialize(spec *Spec, dr *codec.DecodingReader) error { + return dr.List(func() codec.Deserializable { + i := len(*li) + *li = append(*li, SignedBLSToExecutionChange{}) + return &((*li)[i]) + }, SignedBLSToExecutionChangeType.TypeByteLength(), uint64(spec.MAX_BLS_TO_EXECUTION_CHANGES)) +} + +func (li SignedBLSToExecutionChanges) Serialize(_ *Spec, w *codec.EncodingWriter) error { + return w.List(func(i uint64) codec.Serializable { + return &li[i] + }, SignedBLSToExecutionChangeType.TypeByteLength(), uint64(len(li))) +} + +func (li SignedBLSToExecutionChanges) ByteLength(_ *Spec) (out uint64) { + return SignedBLSToExecutionChangeType.TypeByteLength() * uint64(len(li)) +} + +func (*SignedBLSToExecutionChanges) FixedLength(*Spec) uint64 { + return 0 +} + +func (li SignedBLSToExecutionChanges) HashTreeRoot(spec *Spec, hFn tree.HashFn) Root { + length := uint64(len(li)) + return hFn.ComplexListHTR(func(i uint64) tree.HTR { + if i < length { + return &li[i] + } + return nil + }, length, uint64(spec.MAX_BLS_TO_EXECUTION_CHANGES)) +} diff --git a/eth2/beacon/fork.go b/eth2/beacon/fork.go index 915b74d8..fdfa22c4 100644 --- a/eth2/beacon/fork.go +++ b/eth2/beacon/fork.go @@ -6,6 +6,7 @@ import ( "github.com/protolambda/zrnt/eth2/beacon/altair" "github.com/protolambda/zrnt/eth2/beacon/bellatrix" + "github.com/protolambda/zrnt/eth2/beacon/capella" "github.com/protolambda/zrnt/eth2/beacon/common" "github.com/protolambda/zrnt/eth2/beacon/phase0" ) @@ -15,6 +16,7 @@ type ForkDecoder struct { Genesis common.ForkDigest Altair common.ForkDigest Bellatrix common.ForkDigest + Capella common.ForkDigest Sharding common.ForkDigest // TODO more forks } @@ -25,6 +27,7 @@ func NewForkDecoder(spec *common.Spec, genesisValRoot common.Root) *ForkDecoder Genesis: common.ComputeForkDigest(spec.GENESIS_FORK_VERSION, genesisValRoot), Altair: common.ComputeForkDigest(spec.ALTAIR_FORK_VERSION, genesisValRoot), Bellatrix: common.ComputeForkDigest(spec.BELLATRIX_FORK_VERSION, genesisValRoot), + Capella: common.ComputeForkDigest(spec.CAPELLA_FORK_VERSION, genesisValRoot), Sharding: common.ComputeForkDigest(spec.SHARDING_FORK_VERSION, genesisValRoot), } } @@ -42,6 +45,8 @@ func (d *ForkDecoder) BlockAllocator(digest common.ForkDigest) (func() OpaqueBlo return func() OpaqueBlock { return new(altair.SignedBeaconBlock) }, nil case d.Bellatrix: return func() OpaqueBlock { return new(bellatrix.SignedBeaconBlock) }, nil + case d.Capella: + return func() OpaqueBlock { return new(capella.SignedBeaconBlock) }, nil //case d.Sharding: // return new(sharding.SignedBeaconBlock), nil default: @@ -54,10 +59,10 @@ func (d *ForkDecoder) ForkDigest(epoch common.Epoch) common.ForkDigest { return d.Genesis } else if epoch < d.Spec.BELLATRIX_FORK_EPOCH { return d.Altair - } else if epoch < d.Spec.SHARDING_FORK_EPOCH { + } else if epoch < d.Spec.CAPELLA_FORK_EPOCH { return d.Bellatrix } else { - return d.Sharding + return d.Capella } } @@ -87,6 +92,13 @@ func (s *StandardUpgradeableBeaconState) UpgradeMaybe(ctx context.Context, spec } s.BeaconState = post } + if tpre, ok := s.BeaconState.(*bellatrix.BeaconStateView); ok && slot == common.Slot(spec.CAPELLA_FORK_EPOCH)*spec.SLOTS_PER_EPOCH { + post, err := capella.UpgradeToCapella(spec, epc, tpre) + if err != nil { + return fmt.Errorf("failed to upgrade bellatrix to capella state: %v", err) + } + s.BeaconState = post + } //if slot == common.Slot(spec.SHARDING_FORK_EPOCH)*spec.SLOTS_PER_EPOCH { // TODO: upgrade //} @@ -130,6 +142,17 @@ func EnvelopeToSignedBeaconBlock(benv *common.BeaconBlockEnvelope) (common.SpecO }, Signature: benv.Signature, }, nil + case *capella.BeaconBlockBody: + return &capella.SignedBeaconBlock{ + Message: capella.BeaconBlock{ + Slot: benv.Slot, + ProposerIndex: benv.ProposerIndex, + ParentRoot: benv.ParentRoot, + StateRoot: benv.StateRoot, + Body: *x, + }, + Signature: benv.Signature, + }, nil default: return nil, fmt.Errorf("cannot convert beacon block envelope to full signed block, unrecognized body type: %T", x) } diff --git a/eth2/beacon/phase0/validator.go b/eth2/beacon/phase0/validator.go index 663fc060..409dfc17 100644 --- a/eth2/beacon/phase0/validator.go +++ b/eth2/beacon/phase0/validator.go @@ -104,6 +104,10 @@ func (v *ValidatorView) Pubkey() (common.BLSPubkey, error) { func (v *ValidatorView) WithdrawalCredentials() (out common.Root, err error) { return AsRoot(v.Get(_validatorWithdrawalCredentials)) } +func (v *ValidatorView) SetWithdrawalCredentials(b common.Root) (err error) { + wCred := RootView(b) + return v.Set(_validatorWithdrawalCredentials, &wCred) +} func (v *ValidatorView) EffectiveBalance() (common.Gwei, error) { return common.AsGwei(v.Get(_validatorEffectiveBalance)) } diff --git a/eth2/configs/cli.go b/eth2/configs/cli.go index bd67dbb0..2d95e222 100644 --- a/eth2/configs/cli.go +++ b/eth2/configs/cli.go @@ -16,6 +16,7 @@ type SpecOptions struct { Phase0Preset string `ask:"--preset-phase0" help:"Eth2 phase0 spec preset, name or path to YAML"` AltairPreset string `ask:"--preset-altair" help:"Eth2 altair spec preset, name or path to YAML"` BellatrixPreset string `ask:"--preset-bellatrix" help:"Eth2 bellatrix spec preset, name or path to YAML"` + CapellaPreset string `ask:"--preset-capella" help:"Eth2 capella spec preset, name or path to YAML"` ShardingPreset string `ask:"--preset-sharding" help:"Eth2 sharding spec preset, name or path to YAML"` // TODO: execution engine config for Bellatrix @@ -27,6 +28,7 @@ type LegacyConfig struct { common.Phase0Preset `yaml:",inline"` common.AltairPreset `yaml:",inline"` common.BellatrixPreset `yaml:",inline"` + common.CapellaPreset `yaml:",inline"` common.ShardingPreset `yaml:",inline"` common.Config `yaml:",inline"` } @@ -54,6 +56,7 @@ func (c *SpecOptions) Spec() (*common.Spec, error) { spec.Phase0Preset = legacy.Phase0Preset spec.AltairPreset = legacy.AltairPreset spec.BellatrixPreset = legacy.BellatrixPreset + spec.CapellaPreset = legacy.CapellaPreset spec.ShardingPreset = legacy.ShardingPreset spec.Config = legacy.Config } @@ -123,6 +126,22 @@ func (c *SpecOptions) Spec() (*common.Spec, error) { } } + switch c.CapellaPreset { + case "mainnet": + spec.CapellaPreset = Mainnet.CapellaPreset + case "minimal": + spec.CapellaPreset = Minimal.CapellaPreset + default: + f, err := os.Open(c.CapellaPreset) + if err != nil { + return nil, fmt.Errorf("failed to open capella preset file: %v", err) + } + dec := yaml.NewDecoder(f) + if err := dec.Decode(&spec.CapellaPreset); err != nil { + return nil, fmt.Errorf("failed to decode capella preset: %v", err) + } + } + switch c.ShardingPreset { case "mainnet": spec.ShardingPreset = Mainnet.ShardingPreset @@ -147,5 +166,6 @@ func (c *SpecOptions) Default() { c.Phase0Preset = "mainnet" c.AltairPreset = "mainnet" c.BellatrixPreset = "mainnet" + c.CapellaPreset = "mainnet" c.ShardingPreset = "mainnet" } diff --git a/eth2/configs/mainnet.go b/eth2/configs/mainnet.go index fdb9a24f..00a579d0 100644 --- a/eth2/configs/mainnet.go +++ b/eth2/configs/mainnet.go @@ -58,6 +58,11 @@ var Mainnet = &common.Spec{ BYTES_PER_LOGS_BLOOM: 256, MAX_EXTRA_DATA_BYTES: 32, }, + CapellaPreset: common.CapellaPreset{ + MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP: 16384, + MAX_BLS_TO_EXECUTION_CHANGES: 16, + MAX_WITHDRAWALS_PER_PAYLOAD: 16, + }, ShardingPreset: common.ShardingPreset{ MAX_SHARDS: 1024, INITIAL_ACTIVE_SHARDS: 64, @@ -81,7 +86,9 @@ var Mainnet = &common.Spec{ ALTAIR_FORK_EPOCH: common.Epoch(74240), BELLATRIX_FORK_VERSION: common.Version{0x02, 0x00, 0x00, 0x00}, BELLATRIX_FORK_EPOCH: ^common.Epoch(0), - SHARDING_FORK_VERSION: common.Version{0x03, 0x00, 0x00, 0x00}, + CAPELLA_FORK_VERSION: common.Version{0x03, 0x00, 0x00, 0x00}, + CAPELLA_FORK_EPOCH: ^common.Epoch(0), + SHARDING_FORK_VERSION: common.Version{0x04, 0x00, 0x00, 0x00}, SHARDING_FORK_EPOCH: ^common.Epoch(0), TERMINAL_TOTAL_DIFFICULTY: view.MustUint256("115792089237316195423570985008687907853269984665640564039457584007913129638912"), TERMINAL_BLOCK_HASH: common.Bytes32{}, diff --git a/eth2/configs/minimal.go b/eth2/configs/minimal.go index f82391f6..db1c5ad6 100644 --- a/eth2/configs/minimal.go +++ b/eth2/configs/minimal.go @@ -58,6 +58,11 @@ var Minimal = &common.Spec{ BYTES_PER_LOGS_BLOOM: 256, MAX_EXTRA_DATA_BYTES: 32, }, + CapellaPreset: common.CapellaPreset{ + MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP: 16, + MAX_BLS_TO_EXECUTION_CHANGES: 16, + MAX_WITHDRAWALS_PER_PAYLOAD: 4, + }, ShardingPreset: common.ShardingPreset{ MAX_SHARDS: 8, INITIAL_ACTIVE_SHARDS: 2, @@ -81,7 +86,9 @@ var Minimal = &common.Spec{ ALTAIR_FORK_EPOCH: ^common.Epoch(0), BELLATRIX_FORK_VERSION: common.Version{0x02, 0x00, 0x00, 0x01}, BELLATRIX_FORK_EPOCH: ^common.Epoch(0), - SHARDING_FORK_VERSION: common.Version{0x03, 0x00, 0x00, 0x01}, + CAPELLA_FORK_VERSION: common.Version{0x03, 0x00, 0x00, 0x01}, + CAPELLA_FORK_EPOCH: ^common.Epoch(0), + SHARDING_FORK_VERSION: common.Version{0x04, 0x00, 0x00, 0x01}, SHARDING_FORK_EPOCH: ^common.Epoch(0), TERMINAL_TOTAL_DIFFICULTY: view.MustUint256("115792089237316195423570985008687907853269984665640564039457584007913129638912"), TERMINAL_BLOCK_HASH: common.Bytes32{}, diff --git a/eth2/configs/yaml_test.go b/eth2/configs/yaml_test.go index de43c106..3d6883c8 100644 --- a/eth2/configs/yaml_test.go +++ b/eth2/configs/yaml_test.go @@ -68,6 +68,16 @@ func TestYamlDecodingMainnetBellatrix(t *testing.T) { } } +func TestYamlDecodingMainnetCapella(t *testing.T) { + var conf common.CapellaPreset + if err := yaml.Unmarshal(mustLoad("presets", "mainnet", "capella"), &conf); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(conf, Mainnet.CapellaPreset) { + t.Fatal("Failed to load mainnet capella preset") + } +} + func TestYamlDecodingMainnetSharding(t *testing.T) { var conf common.ShardingPreset if err := yaml.Unmarshal(mustLoad("presets", "mainnet", "sharding"), &conf); err != nil { @@ -108,6 +118,16 @@ func TestYamlDecodingMinimalBellatrix(t *testing.T) { } } +func TestYamlDecodingMinimalCapella(t *testing.T) { + var conf common.CapellaPreset + if err := yaml.Unmarshal(mustLoad("presets", "minimal", "capella"), &conf); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(conf, Minimal.CapellaPreset) { + t.Fatal("Failed to load minimal capella preset") + } +} + func TestYamlDecodingMinimalSharding(t *testing.T) { var conf common.ShardingPreset if err := yaml.Unmarshal(mustLoad("presets", "minimal", "sharding"), &conf); err != nil { diff --git a/eth2/configs/yamls/configs/mainnet.yaml b/eth2/configs/yamls/configs/mainnet.yaml index 6c6af628..2aedcbc4 100644 --- a/eth2/configs/yamls/configs/mainnet.yaml +++ b/eth2/configs/yamls/configs/mainnet.yaml @@ -37,8 +37,11 @@ ALTAIR_FORK_EPOCH: 74240 # Oct 27, 2021, 10:56:23am UTC # Bellatrix BELLATRIX_FORK_VERSION: 0x02000000 BELLATRIX_FORK_EPOCH: 18446744073709551615 +# Capella +CAPELLA_FORK_VERSION: 0x03000000 +CAPELLA_FORK_EPOCH: 18446744073709551615 # Sharding -SHARDING_FORK_VERSION: 0x03000000 +SHARDING_FORK_VERSION: 0x04000000 SHARDING_FORK_EPOCH: 18446744073709551615 diff --git a/eth2/configs/yamls/configs/minimal.yaml b/eth2/configs/yamls/configs/minimal.yaml index 4e48c470..35ac1a7a 100644 --- a/eth2/configs/yamls/configs/minimal.yaml +++ b/eth2/configs/yamls/configs/minimal.yaml @@ -36,8 +36,11 @@ ALTAIR_FORK_EPOCH: 18446744073709551615 # Bellatrix BELLATRIX_FORK_VERSION: 0x02000001 BELLATRIX_FORK_EPOCH: 18446744073709551615 +# Capella +CAPELLA_FORK_VERSION: 0x03000001 +CAPELLA_FORK_EPOCH: 18446744073709551615 # Sharding -SHARDING_FORK_VERSION: 0x03000001 +SHARDING_FORK_VERSION: 0x04000001 SHARDING_FORK_EPOCH: 18446744073709551615 diff --git a/eth2/configs/yamls/presets/mainnet/capella.yaml b/eth2/configs/yamls/presets/mainnet/capella.yaml new file mode 100644 index 00000000..4b087ccb --- /dev/null +++ b/eth2/configs/yamls/presets/mainnet/capella.yaml @@ -0,0 +1,19 @@ +# Mainnet preset - Capella + + +# Withdrawals processing +# --------------------------------------------------------------- +# 2**14 (= 16,384) withdrawals +MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP: 16384 + + +# Max operations per block +# --------------------------------------------------------------- +# 2**4 (= 16) +MAX_BLS_TO_EXECUTION_CHANGES: 16 + + +# Execution +# --------------------------------------------------------------- +# 2**4 (= 16) withdrawals +MAX_WITHDRAWALS_PER_PAYLOAD: 16 diff --git a/eth2/configs/yamls/presets/minimal/capella.yaml b/eth2/configs/yamls/presets/minimal/capella.yaml new file mode 100644 index 00000000..d27253de --- /dev/null +++ b/eth2/configs/yamls/presets/minimal/capella.yaml @@ -0,0 +1,17 @@ +# Minimal preset - Capella + +# Max operations per block +# --------------------------------------------------------------- +# 2**4 (= 16) +MAX_BLS_TO_EXECUTION_CHANGES: 16 + + +# Execution +# --------------------------------------------------------------- +# [customized] 2**2 (= 4) +MAX_WITHDRAWALS_PER_PAYLOAD: 4 + +# Withdrawals processing +# --------------------------------------------------------------- +# [customized] 2**4 (= 16) validators +MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP: 16 diff --git a/tests/spec/test_runners/epoch_processing/epoch_test.go b/tests/spec/test_runners/epoch_processing/epoch_test.go index 83317023..860468e6 100644 --- a/tests/spec/test_runners/epoch_processing/epoch_test.go +++ b/tests/spec/test_runners/epoch_processing/epoch_test.go @@ -108,7 +108,7 @@ func TestParticipationRecordUpdates(t *testing.T) { } func TestParticipationFlagUpdates(t *testing.T) { - test_util.RunTransitionTest(t, []test_util.ForkName{"altair", "bellatrix"}, "epoch_processing", "participation_flag_updates", + test_util.RunTransitionTest(t, []test_util.ForkName{"altair", "bellatrix", "capella"}, "epoch_processing", "participation_flag_updates", NewEpochTest(func(spec *common.Spec, state common.BeaconState, epc *common.EpochsContext, flats []common.FlatValidator) error { if s, ok := state.(altair.AltairLikeBeaconState); ok { return altair.ProcessParticipationFlagUpdates(context.Background(), spec, s) @@ -168,7 +168,7 @@ func TestSlashingsReset(t *testing.T) { } func TestSyncCommitteeUpdates(t *testing.T) { - test_util.RunTransitionTest(t, []test_util.ForkName{"altair", "bellatrix"}, "epoch_processing", "sync_committee_updates", + test_util.RunTransitionTest(t, []test_util.ForkName{"altair", "bellatrix", "capella"}, "epoch_processing", "sync_committee_updates", NewEpochTest(func(spec *common.Spec, state common.BeaconState, epc *common.EpochsContext, flats []common.FlatValidator) error { if s, ok := state.(common.SyncCommitteeBeaconState); ok { return altair.ProcessSyncCommitteeUpdates(context.Background(), spec, epc, s) diff --git a/tests/spec/test_runners/finality/finality_test.go b/tests/spec/test_runners/finality/finality_test.go index 385f26ee..956b4cd6 100644 --- a/tests/spec/test_runners/finality/finality_test.go +++ b/tests/spec/test_runners/finality/finality_test.go @@ -3,6 +3,7 @@ package finality import ( "context" "fmt" + "github.com/protolambda/zrnt/eth2/beacon/capella" "testing" "github.com/protolambda/zrnt/eth2/beacon" @@ -50,6 +51,11 @@ func (c *FinalityTestCase) Load(t *testing.T, forkName test_util.ForkName, readP test_util.LoadSpecObj(t, fmt.Sprintf("blocks_%d", i), dst, readPart) digest := common.ComputeForkDigest(c.Spec.BELLATRIX_FORK_VERSION, valRoot) return dst.Envelope(c.Spec, digest) + case "capella": + dst := new(capella.SignedBeaconBlock) + test_util.LoadSpecObj(t, fmt.Sprintf("blocks_%d", i), dst, readPart) + digest := common.ComputeForkDigest(c.Spec.CAPELLA_FORK_VERSION, valRoot) + return dst.Envelope(c.Spec, digest) default: t.Fatal(fmt.Errorf("unrecognized fork name: %s", forkName)) return nil diff --git a/tests/spec/test_runners/fork/fork_test.go b/tests/spec/test_runners/fork/fork_test.go index 696907ca..a50d1033 100644 --- a/tests/spec/test_runners/fork/fork_test.go +++ b/tests/spec/test_runners/fork/fork_test.go @@ -2,6 +2,7 @@ package finality import ( "fmt" + "github.com/protolambda/zrnt/eth2/beacon/capella" "testing" "github.com/protolambda/zrnt/eth2/beacon/altair" @@ -39,6 +40,8 @@ func (c *ForkTestCase) Load(t *testing.T, forkName test_util.ForkName, readPart preFork = "phase0" case "bellatrix": preFork = "altair" + case "capella": + preFork = "bellatrix" default: t.Fatalf("unrecognized fork: %s", c.PostFork) return @@ -73,6 +76,12 @@ func (c *ForkTestCase) Run() error { return err } c.Pre = out + case "capella": + out, err := capella.UpgradeToCapella(c.Spec, epc, c.Pre.(*bellatrix.BeaconStateView)) + if err != nil { + return err + } + c.Pre = out default: return fmt.Errorf("unrecognized fork: %s", c.PostFork) } @@ -94,6 +103,6 @@ func (c *ForkTestCase) Check(t *testing.T) { } func TestFork(t *testing.T) { - test_util.RunTransitionTest(t, []test_util.ForkName{"altair", "bellatrix"}, "fork", "fork", + test_util.RunTransitionTest(t, []test_util.ForkName{"altair", "bellatrix", "capella"}, "fork", "fork", func() test_util.TransitionTest { return new(ForkTestCase) }) } diff --git a/tests/spec/test_runners/genesis/validity_test.go b/tests/spec/test_runners/genesis/validity_test.go index 50f4d172..4a6805b7 100644 --- a/tests/spec/test_runners/genesis/validity_test.go +++ b/tests/spec/test_runners/genesis/validity_test.go @@ -2,6 +2,7 @@ package sanity import ( "bytes" + "github.com/protolambda/zrnt/eth2/beacon/capella" "io/ioutil" "testing" @@ -33,6 +34,8 @@ func runCase(t *testing.T, forkName test_util.ForkName, readPart test_util.TestP genesisState, err = altair.AsBeaconStateView(altair.BeaconStateType(spec).Deserialize(decodingReader)) case "bellatrix": genesisState, err = bellatrix.AsBeaconStateView(bellatrix.BeaconStateType(spec).Deserialize(decodingReader)) + case "capella": + genesisState, err = capella.AsBeaconStateView(capella.BeaconStateType(spec).Deserialize(decodingReader)) default: t.Fatalf("unrecognized fork name: %s", forkName) } diff --git a/tests/spec/test_runners/operations/block_header_test.go b/tests/spec/test_runners/operations/block_header_test.go index 031d179e..358987ed 100644 --- a/tests/spec/test_runners/operations/block_header_test.go +++ b/tests/spec/test_runners/operations/block_header_test.go @@ -2,6 +2,7 @@ package operations import ( "context" + "github.com/protolambda/zrnt/eth2/beacon/capella" "testing" "github.com/protolambda/zrnt/eth2/beacon/altair" @@ -31,6 +32,10 @@ func (c *BlockHeaderTestCase) Load(t *testing.T, forkName test_util.ForkName, re var block bellatrix.BeaconBlock test_util.LoadSpecObj(t, "block", &block, readPart) c.Header = block.Header(c.Spec) + case "capella": + var block capella.BeaconBlock + test_util.LoadSpecObj(t, "block", &block, readPart) + c.Header = block.Header(c.Spec) default: t.Fatalf("unrecognized fork: %s", forkName) } diff --git a/tests/spec/test_runners/operations/bls_to_execution_change_test.go b/tests/spec/test_runners/operations/bls_to_execution_change_test.go new file mode 100644 index 00000000..7be21d08 --- /dev/null +++ b/tests/spec/test_runners/operations/bls_to_execution_change_test.go @@ -0,0 +1,33 @@ +package operations + +import ( + "context" + "github.com/protolambda/zrnt/eth2/beacon/capella" + "testing" + + "github.com/protolambda/zrnt/eth2/beacon/common" + "github.com/protolambda/zrnt/tests/spec/test_util" +) + +type BlsToExecutionChangeTestCase struct { + test_util.BaseTransitionTest + BlsToExecutionChange common.SignedBLSToExecutionChange +} + +func (c *BlsToExecutionChangeTestCase) Load(t *testing.T, forkName test_util.ForkName, readPart test_util.TestPartReader) { + c.BaseTransitionTest.Load(t, forkName, readPart) + test_util.LoadSSZ(t, "address_change", &c.BlsToExecutionChange, readPart) +} + +func (c *BlsToExecutionChangeTestCase) Run() error { + epc, err := common.NewEpochsContext(c.Spec, c.Pre) + if err != nil { + return err + } + return capella.ProcessBLSToExecutionChange(context.Background(), c.Spec, epc, c.Pre, &c.BlsToExecutionChange) +} + +func TestBlsToExecutionChange(t *testing.T) { + test_util.RunTransitionTest(t, []test_util.ForkName{"capella"}, "operations", "bls_to_execution_change", + func() test_util.TransitionTest { return new(BlsToExecutionChangeTestCase) }) +} diff --git a/tests/spec/test_runners/operations/execution_payload.go b/tests/spec/test_runners/operations/execution_payload.go index 2010d261..1a3a5d7b 100644 --- a/tests/spec/test_runners/operations/execution_payload.go +++ b/tests/spec/test_runners/operations/execution_payload.go @@ -15,7 +15,7 @@ type MockExecEngine struct { Valid bool `yaml:"execution_valid"` } -func (m *MockExecEngine) ExecutePayload(ctx context.Context, executionPayload *common.ExecutionPayload) (valid bool, err error) { +func (m *MockExecEngine) ExecutePayload(ctx context.Context, executionPayload interface{}) (valid bool, err error) { return m.Valid, nil } @@ -29,7 +29,7 @@ type ExecutionPayloadTestCase struct { func (c *ExecutionPayloadTestCase) Load(t *testing.T, forkName test_util.ForkName, readPart test_util.TestPartReader) { c.BaseTransitionTest.Load(t, forkName, readPart) - test_util.LoadSSZ(t, "sync_aggregate", c.Spec.Wrap(&c.ExecutionPayload), readPart) + test_util.LoadSSZ(t, "execution_payload", c.Spec.Wrap(&c.ExecutionPayload), readPart) part := readPart.Part("execution.yml") dec := yaml.NewDecoder(part) dec.KnownFields(true) @@ -37,7 +37,7 @@ func (c *ExecutionPayloadTestCase) Load(t *testing.T, forkName test_util.ForkNam } func (c *ExecutionPayloadTestCase) Run() error { - s, ok := c.Pre.(*bellatrix.BeaconStateView) + s, ok := c.Pre.(bellatrix.ExecutionTrackingBeaconState) if !ok { return fmt.Errorf("unrecognized state type: %T", c.Pre) } @@ -45,6 +45,6 @@ func (c *ExecutionPayloadTestCase) Run() error { } func TestExecutionPayload(t *testing.T) { - test_util.RunTransitionTest(t, []test_util.ForkName{"bellatrix"}, "operations", "execution_payload", + test_util.RunTransitionTest(t, []test_util.ForkName{"bellatrix", "capella"}, "operations", "execution_payload", func() test_util.TransitionTest { return new(ExecutionPayloadTestCase) }) } diff --git a/tests/spec/test_runners/operations/sync_aggregate.go b/tests/spec/test_runners/operations/sync_aggregate.go index 7d18e78e..eb1af338 100644 --- a/tests/spec/test_runners/operations/sync_aggregate.go +++ b/tests/spec/test_runners/operations/sync_aggregate.go @@ -33,6 +33,6 @@ func (c *SyncAggregateTestCase) Run() error { } func TestSyncAggregate(t *testing.T) { - test_util.RunTransitionTest(t, []test_util.ForkName{"altair"}, "operations", "sync_aggregate", + test_util.RunTransitionTest(t, []test_util.ForkName{"altair", "bellatrix", "capella"}, "operations", "sync_aggregate", func() test_util.TransitionTest { return new(SyncAggregateTestCase) }) } diff --git a/tests/spec/test_runners/operations/withdrawals_test.go b/tests/spec/test_runners/operations/withdrawals_test.go new file mode 100644 index 00000000..574dc9b6 --- /dev/null +++ b/tests/spec/test_runners/operations/withdrawals_test.go @@ -0,0 +1,33 @@ +package operations + +import ( + "context" + "fmt" + "github.com/protolambda/zrnt/eth2/beacon/capella" + "testing" + + "github.com/protolambda/zrnt/tests/spec/test_util" +) + +type WithdrawalsTestCase struct { + test_util.BaseTransitionTest + ExecutionPayload capella.ExecutionPayload +} + +func (c *WithdrawalsTestCase) Load(t *testing.T, forkName test_util.ForkName, readPart test_util.TestPartReader) { + c.BaseTransitionTest.Load(t, forkName, readPart) + test_util.LoadSSZ(t, "execution_payload", c.Spec.Wrap(&c.ExecutionPayload), readPart) +} + +func (c *WithdrawalsTestCase) Run() error { + s, ok := c.Pre.(*capella.BeaconStateView) + if !ok { + return fmt.Errorf("unrecognized state type: %T", c.Pre) + } + return capella.ProcessWithdrawals(context.Background(), c.Spec, s, &c.ExecutionPayload) +} + +func TestWithdrawals(t *testing.T) { + test_util.RunTransitionTest(t, []test_util.ForkName{"capella"}, "operations", "withdrawals", + func() test_util.TransitionTest { return new(WithdrawalsTestCase) }) +} diff --git a/tests/spec/test_runners/ssz_static/ssz_static_test.go b/tests/spec/test_runners/ssz_static/ssz_static_test.go index 6f6561ae..2940f514 100644 --- a/tests/spec/test_runners/ssz_static/ssz_static_test.go +++ b/tests/spec/test_runners/ssz_static/ssz_static_test.go @@ -3,6 +3,7 @@ package ssz_static import ( "bytes" "encoding/hex" + "github.com/protolambda/zrnt/eth2/beacon/capella" "io/ioutil" "testing" @@ -98,6 +99,7 @@ var objs = map[test_util.ForkName]map[string]ObjAllocator{ "phase0": {}, "altair": {}, "bellatrix": {}, + "capella": {}, } func init() { @@ -129,6 +131,7 @@ func init() { objs["phase0"][k] = v objs["altair"][k] = v objs["bellatrix"][k] = v + objs["capella"][k] = v } objs["phase0"]["BeaconBlockBody"] = func() interface{} { return new(phase0.BeaconBlockBody) } objs["phase0"]["BeaconBlock"] = func() interface{} { return new(phase0.BeaconBlock) } @@ -158,6 +161,15 @@ func init() { objs["bellatrix"]["ExecutionPayloadHeader"] = func() interface{} { return new(common.ExecutionPayloadHeader) } //objs["bellatrix"]["PowBlock"] = func() interface{} { return new(bellatrix.PowBlock) } + objs["capella"]["BeaconBlockBody"] = func() interface{} { return new(capella.BeaconBlockBody) } + objs["capella"]["BeaconBlock"] = func() interface{} { return new(capella.BeaconBlock) } + objs["capella"]["BeaconState"] = func() interface{} { return new(capella.BeaconState) } + objs["capella"]["SignedBeaconBlock"] = func() interface{} { return new(capella.SignedBeaconBlock) } + objs["capella"]["ExecutionPayload"] = func() interface{} { return new(capella.ExecutionPayload) } + objs["capella"]["ExecutionPayloadHeader"] = func() interface{} { return new(capella.ExecutionPayloadHeader) } + objs["capella"]["Withdrawal"] = func() interface{} { return new(common.Withdrawal) } + objs["capella"]["BLSToExecutionChange"] = func() interface{} { return new(common.BLSToExecutionChange) } + objs["capella"]["SignedBLSToExecutionChange"] = func() interface{} { return new(common.SignedBLSToExecutionChange) } } type RootsYAML struct { diff --git a/tests/spec/test_runners/transition/transition_test.go b/tests/spec/test_runners/transition/transition_test.go index f109c99d..7cee59fa 100644 --- a/tests/spec/test_runners/transition/transition_test.go +++ b/tests/spec/test_runners/transition/transition_test.go @@ -3,6 +3,7 @@ package transition import ( "context" "fmt" + "github.com/protolambda/zrnt/eth2/beacon/capella" "testing" "github.com/protolambda/zrnt/eth2/beacon" @@ -47,6 +48,9 @@ func (c *TransitionTestCase) Load(t *testing.T, testFork test_util.ForkName, rea case "bellatrix": preForkName = "altair" c.Spec.BELLATRIX_FORK_EPOCH = common.Epoch(m.ForkEpoch) + case "capella": + preForkName = "bellatrix" + c.Spec.CAPELLA_FORK_EPOCH = common.Epoch(m.ForkEpoch) default: t.Fatalf("unsupported fork %s", testFork) } @@ -90,6 +94,11 @@ func (c *TransitionTestCase) Load(t *testing.T, testFork test_util.ForkName, rea test_util.LoadSpecObj(t, fmt.Sprintf("blocks_%d", i), dst, readPart) digest := common.ComputeForkDigest(c.Spec.BELLATRIX_FORK_VERSION, valRoot) return dst.Envelope(c.Spec, digest) + case "capella": + dst := new(capella.SignedBeaconBlock) + test_util.LoadSpecObj(t, fmt.Sprintf("blocks_%d", i), dst, readPart) + digest := common.ComputeForkDigest(c.Spec.CAPELLA_FORK_VERSION, valRoot) + return dst.Envelope(c.Spec, digest) default: t.Fatalf("unrecognized fork name: %s", forkName) return nil @@ -118,6 +127,6 @@ func (c *TransitionTestCase) Run() error { } func TestTransition(t *testing.T) { - test_util.RunTransitionTest(t, []test_util.ForkName{"altair"}, "transition", "core", + test_util.RunTransitionTest(t, []test_util.ForkName{"altair", "bellatrix", "capella"}, "transition", "core", func() test_util.TransitionTest { return new(TransitionTestCase) }) } diff --git a/tests/spec/test_util/transition_util.go b/tests/spec/test_util/transition_util.go index 9ed059f5..1ec9c46e 100644 --- a/tests/spec/test_util/transition_util.go +++ b/tests/spec/test_util/transition_util.go @@ -12,6 +12,7 @@ import ( "github.com/protolambda/zrnt/eth2/beacon" "github.com/protolambda/zrnt/eth2/beacon/altair" "github.com/protolambda/zrnt/eth2/beacon/bellatrix" + "github.com/protolambda/zrnt/eth2/beacon/capella" "github.com/protolambda/zrnt/eth2/beacon/common" "github.com/protolambda/zrnt/eth2/beacon/phase0" "github.com/protolambda/zrnt/eth2/configs" @@ -23,7 +24,7 @@ import ( // Fork where the test is organized, and thus the state/block/etc. types default to. type ForkName string -var AllForks = []ForkName{"phase0", "altair", "bellatrix"} +var AllForks = []ForkName{"phase0", "altair", "bellatrix", "capella"} type BaseTransitionTest struct { Spec *common.Spec @@ -54,6 +55,8 @@ func LoadState(t *testing.T, fork ForkName, name string, readPart TestPartReader state, err = altair.AsBeaconStateView(altair.BeaconStateType(spec).Deserialize(decodingReader)) case "bellatrix": state, err = bellatrix.AsBeaconStateView(bellatrix.BeaconStateType(spec).Deserialize(decodingReader)) + case "capella": + state, err = capella.AsBeaconStateView(capella.BeaconStateType(spec).Deserialize(decodingReader)) default: t.Fatalf("unrecognized fork name: %s", fork) return nil @@ -130,6 +133,11 @@ func (c *BlocksTestCase) Load(t *testing.T, forkName ForkName, readPart TestPart LoadSpecObj(t, fmt.Sprintf("blocks_%d", i), dst, readPart) digest := common.ComputeForkDigest(c.Spec.BELLATRIX_FORK_VERSION, valRoot) return dst.Envelope(c.Spec, digest) + case "capella": + dst := new(capella.SignedBeaconBlock) + LoadSpecObj(t, fmt.Sprintf("blocks_%d", i), dst, readPart) + digest := common.ComputeForkDigest(c.Spec.CAPELLA_FORK_VERSION, valRoot) + return dst.Envelope(c.Spec, digest) default: t.Fatalf("unrecognized fork name: %s", forkName) return nil @@ -165,6 +173,8 @@ func encodeStateForDiff(spec *common.Spec, state common.BeaconState) (interface{ return s.Raw(spec) case *bellatrix.BeaconStateView: return s.Raw(spec) + case *capella.BeaconStateView: + return s.Raw(spec) default: return nil, fmt.Errorf("unrecognized beacon state type: %T", s) } @@ -234,7 +244,7 @@ func RunTransitionTest(t *testing.T, forks []ForkName, runnerName string, handle type NoOpExecutionEngine struct{} -func (m *NoOpExecutionEngine) ExecutePayload(ctx context.Context, executionPayload *common.ExecutionPayload) (valid bool, err error) { +func (m *NoOpExecutionEngine) ExecutePayload(ctx context.Context, executionPayload interface{}) (valid bool, err error) { return true, nil }