From 963acefe128c1b0c2d183fcf5c9de4d4b94efe33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Kapka?= Date: Thu, 13 Jan 2022 12:23:53 +0100 Subject: [PATCH] Split `state` package into `state-proto` and `state-native` (#10069) Co-authored-by: Raul Jordan Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com> Co-authored-by: terence tsao Co-authored-by: Kasey Kirkham Co-authored-by: Preston Van Loon Co-authored-by: kasey <489222+kasey@users.noreply.github.com> Co-authored-by: Dan Loewenherz Co-authored-by: prestonvanloon Co-authored-by: Fredrik Svantes Co-authored-by: Leo Lara --- beacon-chain/rpc/eth/beacon/pool_test.go | 72 +-- .../rpc/eth/validator/validator_test.go | 12 +- beacon-chain/rpc/statefetcher/fetcher_test.go | 40 +- beacon-chain/state/BUILD.bazel | 3 + beacon-chain/state/phase0.go | 1 + beacon-chain/state/prometheus.go | 13 + .../state/state-native/fieldtrie/BUILD.bazel | 40 ++ .../state-native/fieldtrie/field_trie.go | 200 ++++++++ .../fieldtrie/field_trie_helpers.go | 295 ++++++++++++ .../state-native/fieldtrie/field_trie_test.go | 79 ++++ .../state-native/fieldtrie/helpers_test.go | 85 ++++ .../state/state-native/v1/BUILD.bazel | 105 +++++ beacon-chain/state/state-native/v1/doc.go | 40 ++ .../state/state-native/v1/field_roots.go | 18 + .../state-native/v1/getters_attestation.go | 55 +++ .../v1/getters_attestation_test.go | 46 ++ .../state/state-native/v1/getters_block.go | 99 ++++ .../state-native/v1/getters_block_test.go | 59 +++ .../state-native/v1/getters_checkpoint.go | 160 +++++++ .../state/state-native/v1/getters_eth1.go | 91 ++++ .../state/state-native/v1/getters_misc.go | 163 +++++++ .../state/state-native/v1/getters_randao.go | 85 ++++ .../state/state-native/v1/getters_state.go | 123 +++++ .../state/state-native/v1/getters_test.go | 216 +++++++++ .../state-native/v1/getters_validator.go | 298 ++++++++++++ .../state-native/v1/getters_validator_test.go | 20 + beacon-chain/state/state-native/v1/proofs.go | 54 +++ .../state/state-native/v1/proofs_test.go | 64 +++ .../state-native/v1/readonly_validator.go | 89 ++++ .../v1/readonly_validator_test.go | 74 +++ .../state/state-native/v1/references_test.go | 354 ++++++++++++++ .../state-native/v1/setters_attestation.go | 98 ++++ .../v1/setters_attestation_test.go | 72 +++ .../state/state-native/v1/setters_block.go | 68 +++ .../state-native/v1/setters_checkpoint.go | 58 +++ .../state/state-native/v1/setters_eth1.go | 74 +++ .../state/state-native/v1/setters_misc.go | 187 ++++++++ .../state/state-native/v1/setters_randao.go | 53 +++ .../state/state-native/v1/setters_state.go | 59 +++ .../state-native/v1/setters_validator.go | 228 +++++++++ .../state/state-native/v1/state_test.go | 193 ++++++++ .../state/state-native/v1/state_trie.go | 434 +++++++++++++++++ .../state/state-native/v1/state_trie_test.go | 260 ++++++++++ beacon-chain/state/state-native/v1/types.go | 78 +++ .../state/state-native/v1/types_test.go | 232 +++++++++ .../state-native/v1/unsupported_getters.go | 36 ++ .../state-native/v1/unsupported_setters.go | 51 ++ .../state/state-native/v2/BUILD.bazel | 91 ++++ .../state-native/v2/deprecated_getters.go | 21 + .../v2/deprecated_getters_test.go | 19 + .../state-native/v2/deprecated_setters.go | 36 ++ .../v2/deprecated_setters_test.go | 27 ++ .../state/state-native/v2/field_roots.go | 18 + .../state/state-native/v2/getters_block.go | 99 ++++ .../state-native/v2/getters_block_test.go | 60 +++ .../state-native/v2/getters_checkpoint.go | 160 +++++++ .../state/state-native/v2/getters_eth1.go | 91 ++++ .../state/state-native/v2/getters_misc.go | 212 +++++++++ .../state-native/v2/getters_participation.go | 53 +++ .../state/state-native/v2/getters_randao.go | 85 ++++ .../state/state-native/v2/getters_state.go | 126 +++++ .../state-native/v2/getters_sync_committee.go | 69 +++ .../state/state-native/v2/getters_test.go | 193 ++++++++ .../state-native/v2/getters_validator.go | 329 +++++++++++++ .../state-native/v2/getters_validator_test.go | 20 + beacon-chain/state/state-native/v2/proofs.go | 83 ++++ .../state/state-native/v2/proofs_test.go | 105 +++++ .../state/state-native/v2/setters_block.go | 68 +++ .../state-native/v2/setters_checkpoint.go | 58 +++ .../state/state-native/v2/setters_eth1.go | 74 +++ .../state/state-native/v2/setters_misc.go | 186 ++++++++ .../state-native/v2/setters_participation.go | 89 ++++ .../state/state-native/v2/setters_randao.go | 53 +++ .../state/state-native/v2/setters_state.go | 59 +++ .../state-native/v2/setters_sync_committee.go | 31 ++ .../state/state-native/v2/setters_test.go | 161 +++++++ .../state-native/v2/setters_validator.go | 265 +++++++++++ .../state/state-native/v2/state_trie.go | 444 ++++++++++++++++++ .../state/state-native/v2/state_trie_test.go | 169 +++++++ beacon-chain/state/state-native/v2/types.go | 78 +++ .../state/state-native/v3/BUILD.bazel | 92 ++++ .../state-native/v3/deprecated_getters.go | 16 + .../v3/deprecated_getters_test.go | 19 + .../state-native/v3/deprecated_setters.go | 31 ++ .../v3/deprecated_setters_test.go | 27 ++ .../state/state-native/v3/field_roots.go | 19 + .../state/state-native/v3/getters_block.go | 99 ++++ .../state-native/v3/getters_block_test.go | 59 +++ .../state-native/v3/getters_checkpoint.go | 160 +++++++ .../state/state-native/v3/getters_eth1.go | 91 ++++ .../state/state-native/v3/getters_misc.go | 211 +++++++++ .../state-native/v3/getters_participation.go | 53 +++ .../state-native/v3/getters_payload_header.go | 30 ++ .../state/state-native/v3/getters_randao.go | 85 ++++ .../state/state-native/v3/getters_state.go | 127 +++++ .../state-native/v3/getters_sync_committee.go | 69 +++ .../state/state-native/v3/getters_test.go | 193 ++++++++ .../state-native/v3/getters_validator.go | 329 +++++++++++++ .../state-native/v3/getters_validator_test.go | 20 + beacon-chain/state/state-native/v3/proofs.go | 83 ++++ .../state/state-native/v3/proofs_test.go | 105 +++++ .../state/state-native/v3/setters_block.go | 68 +++ .../state-native/v3/setters_checkpoint.go | 58 +++ .../state/state-native/v3/setters_eth1.go | 74 +++ .../state/state-native/v3/setters_misc.go | 186 ++++++++ .../state-native/v3/setters_participation.go | 89 ++++ .../state-native/v3/setters_payload_header.go | 16 + .../state/state-native/v3/setters_randao.go | 53 +++ .../state/state-native/v3/setters_state.go | 59 +++ .../state-native/v3/setters_sync_committee.go | 31 ++ .../state/state-native/v3/setters_test.go | 185 ++++++++ .../state-native/v3/setters_validator.go | 265 +++++++++++ .../state/state-native/v3/state_trie.go | 406 ++++++++++++++++ .../state/state-native/v3/state_trie_test.go | 168 +++++++ beacon-chain/state/state-native/v3/types.go | 77 +++ beacon-chain/state/v1/BUILD.bazel | 2 - beacon-chain/state/v1/setters_state.go | 18 + beacon-chain/state/v1/state_trie.go | 15 +- beacon-chain/state/v2/BUILD.bazel | 2 - beacon-chain/state/v2/setters_state.go | 18 + beacon-chain/state/v2/state_trie.go | 15 +- beacon-chain/state/v3/BUILD.bazel | 2 - beacon-chain/state/v3/setters_state.go | 18 + beacon-chain/state/v3/state_trie.go | 15 +- deps.bzl | 12 +- go.mod | 2 +- go.sum | 9 +- 127 files changed, 12534 insertions(+), 113 deletions(-) create mode 100644 beacon-chain/state/prometheus.go create mode 100644 beacon-chain/state/state-native/fieldtrie/BUILD.bazel create mode 100644 beacon-chain/state/state-native/fieldtrie/field_trie.go create mode 100644 beacon-chain/state/state-native/fieldtrie/field_trie_helpers.go create mode 100644 beacon-chain/state/state-native/fieldtrie/field_trie_test.go create mode 100644 beacon-chain/state/state-native/fieldtrie/helpers_test.go create mode 100644 beacon-chain/state/state-native/v1/BUILD.bazel create mode 100644 beacon-chain/state/state-native/v1/doc.go create mode 100644 beacon-chain/state/state-native/v1/field_roots.go create mode 100644 beacon-chain/state/state-native/v1/getters_attestation.go create mode 100644 beacon-chain/state/state-native/v1/getters_attestation_test.go create mode 100644 beacon-chain/state/state-native/v1/getters_block.go create mode 100644 beacon-chain/state/state-native/v1/getters_block_test.go create mode 100644 beacon-chain/state/state-native/v1/getters_checkpoint.go create mode 100644 beacon-chain/state/state-native/v1/getters_eth1.go create mode 100644 beacon-chain/state/state-native/v1/getters_misc.go create mode 100644 beacon-chain/state/state-native/v1/getters_randao.go create mode 100644 beacon-chain/state/state-native/v1/getters_state.go create mode 100644 beacon-chain/state/state-native/v1/getters_test.go create mode 100644 beacon-chain/state/state-native/v1/getters_validator.go create mode 100644 beacon-chain/state/state-native/v1/getters_validator_test.go create mode 100644 beacon-chain/state/state-native/v1/proofs.go create mode 100644 beacon-chain/state/state-native/v1/proofs_test.go create mode 100644 beacon-chain/state/state-native/v1/readonly_validator.go create mode 100644 beacon-chain/state/state-native/v1/readonly_validator_test.go create mode 100644 beacon-chain/state/state-native/v1/references_test.go create mode 100644 beacon-chain/state/state-native/v1/setters_attestation.go create mode 100644 beacon-chain/state/state-native/v1/setters_attestation_test.go create mode 100644 beacon-chain/state/state-native/v1/setters_block.go create mode 100644 beacon-chain/state/state-native/v1/setters_checkpoint.go create mode 100644 beacon-chain/state/state-native/v1/setters_eth1.go create mode 100644 beacon-chain/state/state-native/v1/setters_misc.go create mode 100644 beacon-chain/state/state-native/v1/setters_randao.go create mode 100644 beacon-chain/state/state-native/v1/setters_state.go create mode 100644 beacon-chain/state/state-native/v1/setters_validator.go create mode 100644 beacon-chain/state/state-native/v1/state_test.go create mode 100644 beacon-chain/state/state-native/v1/state_trie.go create mode 100644 beacon-chain/state/state-native/v1/state_trie_test.go create mode 100644 beacon-chain/state/state-native/v1/types.go create mode 100644 beacon-chain/state/state-native/v1/types_test.go create mode 100644 beacon-chain/state/state-native/v1/unsupported_getters.go create mode 100644 beacon-chain/state/state-native/v1/unsupported_setters.go create mode 100644 beacon-chain/state/state-native/v2/BUILD.bazel create mode 100644 beacon-chain/state/state-native/v2/deprecated_getters.go create mode 100644 beacon-chain/state/state-native/v2/deprecated_getters_test.go create mode 100644 beacon-chain/state/state-native/v2/deprecated_setters.go create mode 100644 beacon-chain/state/state-native/v2/deprecated_setters_test.go create mode 100644 beacon-chain/state/state-native/v2/field_roots.go create mode 100644 beacon-chain/state/state-native/v2/getters_block.go create mode 100644 beacon-chain/state/state-native/v2/getters_block_test.go create mode 100644 beacon-chain/state/state-native/v2/getters_checkpoint.go create mode 100644 beacon-chain/state/state-native/v2/getters_eth1.go create mode 100644 beacon-chain/state/state-native/v2/getters_misc.go create mode 100644 beacon-chain/state/state-native/v2/getters_participation.go create mode 100644 beacon-chain/state/state-native/v2/getters_randao.go create mode 100644 beacon-chain/state/state-native/v2/getters_state.go create mode 100644 beacon-chain/state/state-native/v2/getters_sync_committee.go create mode 100644 beacon-chain/state/state-native/v2/getters_test.go create mode 100644 beacon-chain/state/state-native/v2/getters_validator.go create mode 100644 beacon-chain/state/state-native/v2/getters_validator_test.go create mode 100644 beacon-chain/state/state-native/v2/proofs.go create mode 100644 beacon-chain/state/state-native/v2/proofs_test.go create mode 100644 beacon-chain/state/state-native/v2/setters_block.go create mode 100644 beacon-chain/state/state-native/v2/setters_checkpoint.go create mode 100644 beacon-chain/state/state-native/v2/setters_eth1.go create mode 100644 beacon-chain/state/state-native/v2/setters_misc.go create mode 100644 beacon-chain/state/state-native/v2/setters_participation.go create mode 100644 beacon-chain/state/state-native/v2/setters_randao.go create mode 100644 beacon-chain/state/state-native/v2/setters_state.go create mode 100644 beacon-chain/state/state-native/v2/setters_sync_committee.go create mode 100644 beacon-chain/state/state-native/v2/setters_test.go create mode 100644 beacon-chain/state/state-native/v2/setters_validator.go create mode 100644 beacon-chain/state/state-native/v2/state_trie.go create mode 100644 beacon-chain/state/state-native/v2/state_trie_test.go create mode 100644 beacon-chain/state/state-native/v2/types.go create mode 100644 beacon-chain/state/state-native/v3/BUILD.bazel create mode 100644 beacon-chain/state/state-native/v3/deprecated_getters.go create mode 100644 beacon-chain/state/state-native/v3/deprecated_getters_test.go create mode 100644 beacon-chain/state/state-native/v3/deprecated_setters.go create mode 100644 beacon-chain/state/state-native/v3/deprecated_setters_test.go create mode 100644 beacon-chain/state/state-native/v3/field_roots.go create mode 100644 beacon-chain/state/state-native/v3/getters_block.go create mode 100644 beacon-chain/state/state-native/v3/getters_block_test.go create mode 100644 beacon-chain/state/state-native/v3/getters_checkpoint.go create mode 100644 beacon-chain/state/state-native/v3/getters_eth1.go create mode 100644 beacon-chain/state/state-native/v3/getters_misc.go create mode 100644 beacon-chain/state/state-native/v3/getters_participation.go create mode 100644 beacon-chain/state/state-native/v3/getters_payload_header.go create mode 100644 beacon-chain/state/state-native/v3/getters_randao.go create mode 100644 beacon-chain/state/state-native/v3/getters_state.go create mode 100644 beacon-chain/state/state-native/v3/getters_sync_committee.go create mode 100644 beacon-chain/state/state-native/v3/getters_test.go create mode 100644 beacon-chain/state/state-native/v3/getters_validator.go create mode 100644 beacon-chain/state/state-native/v3/getters_validator_test.go create mode 100644 beacon-chain/state/state-native/v3/proofs.go create mode 100644 beacon-chain/state/state-native/v3/proofs_test.go create mode 100644 beacon-chain/state/state-native/v3/setters_block.go create mode 100644 beacon-chain/state/state-native/v3/setters_checkpoint.go create mode 100644 beacon-chain/state/state-native/v3/setters_eth1.go create mode 100644 beacon-chain/state/state-native/v3/setters_misc.go create mode 100644 beacon-chain/state/state-native/v3/setters_participation.go create mode 100644 beacon-chain/state/state-native/v3/setters_payload_header.go create mode 100644 beacon-chain/state/state-native/v3/setters_randao.go create mode 100644 beacon-chain/state/state-native/v3/setters_state.go create mode 100644 beacon-chain/state/state-native/v3/setters_sync_committee.go create mode 100644 beacon-chain/state/state-native/v3/setters_test.go create mode 100644 beacon-chain/state/state-native/v3/setters_validator.go create mode 100644 beacon-chain/state/state-native/v3/state_trie.go create mode 100644 beacon-chain/state/state-native/v3/state_trie_test.go create mode 100644 beacon-chain/state/state-native/v3/types.go diff --git a/beacon-chain/rpc/eth/beacon/pool_test.go b/beacon-chain/rpc/eth/beacon/pool_test.go index 5d21744b36..bb5d6514fd 100644 --- a/beacon-chain/rpc/eth/beacon/pool_test.go +++ b/beacon-chain/rpc/eth/beacon/pool_test.go @@ -31,7 +31,7 @@ import ( ) func TestListPoolAttestations(t *testing.T) { - state, err := util.NewBeaconState() + bs, err := util.NewBeaconState() require.NoError(t, err) att1 := ðpbv1alpha1.Attestation{ AggregationBits: []byte{1, 10}, @@ -136,7 +136,7 @@ func TestListPoolAttestations(t *testing.T) { Signature: bytesutil.PadTo([]byte("signature2"), 96), } s := &Server{ - ChainInfoFetcher: &blockchainmock.ChainService{State: state}, + ChainInfoFetcher: &blockchainmock.ChainService{State: bs}, AttestationsPool: attestations.NewPool(), } require.NoError(t, s.AttestationsPool.SaveAggregatedAttestations([]*ethpbv1alpha1.Attestation{att1, att2, att3})) @@ -193,7 +193,7 @@ func TestListPoolAttestations(t *testing.T) { } func TestListPoolAttesterSlashings(t *testing.T) { - state, err := util.NewBeaconState() + bs, err := util.NewBeaconState() require.NoError(t, err) slashing1 := ðpbv1alpha1.AttesterSlashing{ Attestation_1: ðpbv1alpha1.IndexedAttestation{ @@ -269,7 +269,7 @@ func TestListPoolAttesterSlashings(t *testing.T) { } s := &Server{ - ChainInfoFetcher: &blockchainmock.ChainService{State: state}, + ChainInfoFetcher: &blockchainmock.ChainService{State: bs}, SlashingsPool: &slashings.PoolMock{PendingAttSlashings: []*ethpbv1alpha1.AttesterSlashing{slashing1, slashing2}}, } @@ -281,7 +281,7 @@ func TestListPoolAttesterSlashings(t *testing.T) { } func TestListPoolProposerSlashings(t *testing.T) { - state, err := util.NewBeaconState() + bs, err := util.NewBeaconState() require.NoError(t, err) slashing1 := ðpbv1alpha1.ProposerSlashing{ Header_1: ðpbv1alpha1.SignedBeaconBlockHeader{ @@ -329,7 +329,7 @@ func TestListPoolProposerSlashings(t *testing.T) { } s := &Server{ - ChainInfoFetcher: &blockchainmock.ChainService{State: state}, + ChainInfoFetcher: &blockchainmock.ChainService{State: bs}, SlashingsPool: &slashings.PoolMock{PendingPropSlashings: []*ethpbv1alpha1.ProposerSlashing{slashing1, slashing2}}, } @@ -341,7 +341,7 @@ func TestListPoolProposerSlashings(t *testing.T) { } func TestListPoolVoluntaryExits(t *testing.T) { - state, err := util.NewBeaconState() + bs, err := util.NewBeaconState() require.NoError(t, err) exit1 := ðpbv1alpha1.SignedVoluntaryExit{ Exit: ðpbv1alpha1.VoluntaryExit{ @@ -359,7 +359,7 @@ func TestListPoolVoluntaryExits(t *testing.T) { } s := &Server{ - ChainInfoFetcher: &blockchainmock.ChainService{State: state}, + ChainInfoFetcher: &blockchainmock.ChainService{State: bs}, VoluntaryExitsPool: &voluntaryexits.PoolMock{Exits: []*ethpbv1alpha1.SignedVoluntaryExit{exit1, exit2}}, } @@ -378,7 +378,7 @@ func TestSubmitAttesterSlashing_Ok(t *testing.T) { validator := ðpbv1alpha1.Validator{ PublicKey: keys[0].PublicKey().Marshal(), } - state, err := util.NewBeaconState(func(state *ethpbv1alpha1.BeaconState) error { + bs, err := util.NewBeaconState(func(state *ethpbv1alpha1.BeaconState) error { state.Validators = []*ethpbv1alpha1.Validator{validator} return nil }) @@ -422,7 +422,7 @@ func TestSubmitAttesterSlashing_Ok(t *testing.T) { } for _, att := range []*ethpbv1.IndexedAttestation{slashing.Attestation_1, slashing.Attestation_2} { - sb, err := signing.ComputeDomainAndSign(state, att.Data.Target.Epoch, att.Data, params.BeaconConfig().DomainBeaconAttester, keys[0]) + sb, err := signing.ComputeDomainAndSign(bs, att.Data.Target.Epoch, att.Data, params.BeaconConfig().DomainBeaconAttester, keys[0]) require.NoError(t, err) sig, err := bls.SignatureFromBytes(sb) require.NoError(t, err) @@ -431,14 +431,14 @@ func TestSubmitAttesterSlashing_Ok(t *testing.T) { broadcaster := &p2pMock.MockBroadcaster{} s := &Server{ - ChainInfoFetcher: &blockchainmock.ChainService{State: state}, + ChainInfoFetcher: &blockchainmock.ChainService{State: bs}, SlashingsPool: &slashings.PoolMock{}, Broadcaster: broadcaster, } _, err = s.SubmitAttesterSlashing(ctx, slashing) require.NoError(t, err) - pendingSlashings := s.SlashingsPool.PendingAttesterSlashings(ctx, state, true) + pendingSlashings := s.SlashingsPool.PendingAttesterSlashings(ctx, bs, true) require.Equal(t, 1, len(pendingSlashings)) assert.DeepEqual(t, migration.V1AttSlashingToV1Alpha1(slashing), pendingSlashings[0]) assert.Equal(t, true, broadcaster.BroadcastCalled) @@ -446,7 +446,7 @@ func TestSubmitAttesterSlashing_Ok(t *testing.T) { func TestSubmitAttesterSlashing_InvalidSlashing(t *testing.T) { ctx := context.Background() - state, err := util.NewBeaconState() + bs, err := util.NewBeaconState() require.NoError(t, err) attestation := ðpbv1.IndexedAttestation{ @@ -474,7 +474,7 @@ func TestSubmitAttesterSlashing_InvalidSlashing(t *testing.T) { broadcaster := &p2pMock.MockBroadcaster{} s := &Server{ - ChainInfoFetcher: &blockchainmock.ChainService{State: state}, + ChainInfoFetcher: &blockchainmock.ChainService{State: bs}, SlashingsPool: &slashings.PoolMock{}, Broadcaster: broadcaster, } @@ -493,7 +493,7 @@ func TestSubmitProposerSlashing_Ok(t *testing.T) { PublicKey: keys[0].PublicKey().Marshal(), WithdrawableEpoch: eth2types.Epoch(1), } - state, err := util.NewBeaconState(func(state *ethpbv1alpha1.BeaconState) error { + bs, err := util.NewBeaconState(func(state *ethpbv1alpha1.BeaconState) error { state.Validators = []*ethpbv1alpha1.Validator{validator} return nil }) @@ -524,7 +524,7 @@ func TestSubmitProposerSlashing_Ok(t *testing.T) { for _, h := range []*ethpbv1.SignedBeaconBlockHeader{slashing.SignedHeader_1, slashing.SignedHeader_2} { sb, err := signing.ComputeDomainAndSign( - state, + bs, slots.ToEpoch(h.Message.Slot), h.Message, params.BeaconConfig().DomainBeaconProposer, @@ -538,14 +538,14 @@ func TestSubmitProposerSlashing_Ok(t *testing.T) { broadcaster := &p2pMock.MockBroadcaster{} s := &Server{ - ChainInfoFetcher: &blockchainmock.ChainService{State: state}, + ChainInfoFetcher: &blockchainmock.ChainService{State: bs}, SlashingsPool: &slashings.PoolMock{}, Broadcaster: broadcaster, } _, err = s.SubmitProposerSlashing(ctx, slashing) require.NoError(t, err) - pendingSlashings := s.SlashingsPool.PendingProposerSlashings(ctx, state, true) + pendingSlashings := s.SlashingsPool.PendingProposerSlashings(ctx, bs, true) require.Equal(t, 1, len(pendingSlashings)) assert.DeepEqual(t, migration.V1ProposerSlashingToV1Alpha1(slashing), pendingSlashings[0]) assert.Equal(t, true, broadcaster.BroadcastCalled) @@ -553,7 +553,7 @@ func TestSubmitProposerSlashing_Ok(t *testing.T) { func TestSubmitProposerSlashing_InvalidSlashing(t *testing.T) { ctx := context.Background() - state, err := util.NewBeaconState() + bs, err := util.NewBeaconState() require.NoError(t, err) header := ðpbv1.SignedBeaconBlockHeader{ @@ -574,7 +574,7 @@ func TestSubmitProposerSlashing_InvalidSlashing(t *testing.T) { broadcaster := &p2pMock.MockBroadcaster{} s := &Server{ - ChainInfoFetcher: &blockchainmock.ChainService{State: state}, + ChainInfoFetcher: &blockchainmock.ChainService{State: bs}, SlashingsPool: &slashings.PoolMock{}, Broadcaster: broadcaster, } @@ -593,7 +593,7 @@ func TestSubmitVoluntaryExit_Ok(t *testing.T) { ExitEpoch: params.BeaconConfig().FarFutureEpoch, PublicKey: keys[0].PublicKey().Marshal(), } - state, err := util.NewBeaconState(func(state *ethpbv1alpha1.BeaconState) error { + bs, err := util.NewBeaconState(func(state *ethpbv1alpha1.BeaconState) error { state.Validators = []*ethpbv1alpha1.Validator{validator} // Satisfy activity time required before exiting. state.Slot = params.BeaconConfig().SlotsPerEpoch.Mul(uint64(params.BeaconConfig().ShardCommitteePeriod)) @@ -609,7 +609,7 @@ func TestSubmitVoluntaryExit_Ok(t *testing.T) { Signature: make([]byte, 96), } - sb, err := signing.ComputeDomainAndSign(state, exit.Message.Epoch, exit.Message, params.BeaconConfig().DomainVoluntaryExit, keys[0]) + sb, err := signing.ComputeDomainAndSign(bs, exit.Message.Epoch, exit.Message, params.BeaconConfig().DomainVoluntaryExit, keys[0]) require.NoError(t, err) sig, err := bls.SignatureFromBytes(sb) require.NoError(t, err) @@ -617,14 +617,14 @@ func TestSubmitVoluntaryExit_Ok(t *testing.T) { broadcaster := &p2pMock.MockBroadcaster{} s := &Server{ - ChainInfoFetcher: &blockchainmock.ChainService{State: state}, + ChainInfoFetcher: &blockchainmock.ChainService{State: bs}, VoluntaryExitsPool: &voluntaryexits.PoolMock{}, Broadcaster: broadcaster, } _, err = s.SubmitVoluntaryExit(ctx, exit) require.NoError(t, err) - pendingExits := s.VoluntaryExitsPool.PendingExits(state, state.Slot(), true) + pendingExits := s.VoluntaryExitsPool.PendingExits(bs, bs.Slot(), true) require.Equal(t, 1, len(pendingExits)) assert.DeepEqual(t, migration.V1ExitToV1Alpha1(exit), pendingExits[0]) assert.Equal(t, true, broadcaster.BroadcastCalled) @@ -639,7 +639,7 @@ func TestSubmitVoluntaryExit_InvalidValidatorIndex(t *testing.T) { ExitEpoch: params.BeaconConfig().FarFutureEpoch, PublicKey: keys[0].PublicKey().Marshal(), } - state, err := util.NewBeaconState(func(state *ethpbv1alpha1.BeaconState) error { + bs, err := util.NewBeaconState(func(state *ethpbv1alpha1.BeaconState) error { state.Validators = []*ethpbv1alpha1.Validator{validator} return nil }) @@ -655,7 +655,7 @@ func TestSubmitVoluntaryExit_InvalidValidatorIndex(t *testing.T) { broadcaster := &p2pMock.MockBroadcaster{} s := &Server{ - ChainInfoFetcher: &blockchainmock.ChainService{State: state}, + ChainInfoFetcher: &blockchainmock.ChainService{State: bs}, VoluntaryExitsPool: &voluntaryexits.PoolMock{}, Broadcaster: broadcaster, } @@ -674,7 +674,7 @@ func TestSubmitVoluntaryExit_InvalidExit(t *testing.T) { ExitEpoch: params.BeaconConfig().FarFutureEpoch, PublicKey: keys[0].PublicKey().Marshal(), } - state, err := util.NewBeaconState(func(state *ethpbv1alpha1.BeaconState) error { + bs, err := util.NewBeaconState(func(state *ethpbv1alpha1.BeaconState) error { state.Validators = []*ethpbv1alpha1.Validator{validator} return nil }) @@ -690,7 +690,7 @@ func TestSubmitVoluntaryExit_InvalidExit(t *testing.T) { broadcaster := &p2pMock.MockBroadcaster{} s := &Server{ - ChainInfoFetcher: &blockchainmock.ChainService{State: state}, + ChainInfoFetcher: &blockchainmock.ChainService{State: bs}, VoluntaryExitsPool: &voluntaryexits.PoolMock{}, Broadcaster: broadcaster, } @@ -716,7 +716,7 @@ func TestServer_SubmitAttestations_Ok(t *testing.T) { ExitEpoch: params.BeaconConfig().FarFutureEpoch, }, } - state, err := util.NewBeaconState(func(state *ethpbv1alpha1.BeaconState) error { + bs, err := util.NewBeaconState(func(state *ethpbv1alpha1.BeaconState) error { state.Validators = validators state.Slot = 1 state.PreviousJustifiedCheckpoint = ðpbv1alpha1.Checkpoint{ @@ -764,7 +764,7 @@ func TestServer_SubmitAttestations_Ok(t *testing.T) { for _, att := range []*ethpbv1.Attestation{att1, att2} { sb, err := signing.ComputeDomainAndSign( - state, + bs, slots.ToEpoch(att.Data.Slot), att.Data, params.BeaconConfig().DomainBeaconAttester, @@ -777,7 +777,7 @@ func TestServer_SubmitAttestations_Ok(t *testing.T) { } broadcaster := &p2pMock.MockBroadcaster{} - chainService := &blockchainmock.ChainService{State: state} + chainService := &blockchainmock.ChainService{State: bs} s := &Server{ HeadFetcher: chainService, ChainInfoFetcher: chainService, @@ -822,7 +822,7 @@ func TestServer_SubmitAttestations_ValidAttestationSubmitted(t *testing.T) { ExitEpoch: params.BeaconConfig().FarFutureEpoch, }, } - state, err := util.NewBeaconState(func(state *ethpbv1alpha1.BeaconState) error { + bs, err := util.NewBeaconState(func(state *ethpbv1alpha1.BeaconState) error { state.Validators = validators state.Slot = 1 state.PreviousJustifiedCheckpoint = ðpbv1alpha1.Checkpoint{ @@ -871,7 +871,7 @@ func TestServer_SubmitAttestations_ValidAttestationSubmitted(t *testing.T) { // Don't sign attInvalidSignature. sb, err := signing.ComputeDomainAndSign( - state, + bs, slots.ToEpoch(attValid.Data.Slot), attValid.Data, params.BeaconConfig().DomainBeaconAttester, @@ -883,7 +883,7 @@ func TestServer_SubmitAttestations_ValidAttestationSubmitted(t *testing.T) { attValid.Signature = sig.Marshal() broadcaster := &p2pMock.MockBroadcaster{} - chainService := &blockchainmock.ChainService{State: state} + chainService := &blockchainmock.ChainService{State: bs} s := &Server{ HeadFetcher: chainService, ChainInfoFetcher: chainService, @@ -922,7 +922,7 @@ func TestServer_SubmitAttestations_InvalidAttestationGRPCHeader(t *testing.T) { ExitEpoch: params.BeaconConfig().FarFutureEpoch, }, } - state, err := util.NewBeaconState(func(state *ethpbv1alpha1.BeaconState) error { + bs, err := util.NewBeaconState(func(state *ethpbv1alpha1.BeaconState) error { state.Validators = validators state.Slot = 1 state.PreviousJustifiedCheckpoint = ðpbv1alpha1.Checkpoint{ @@ -954,7 +954,7 @@ func TestServer_SubmitAttestations_InvalidAttestationGRPCHeader(t *testing.T) { Signature: nil, } - chain := &blockchainmock.ChainService{State: state} + chain := &blockchainmock.ChainService{State: bs} broadcaster := &p2pMock.MockBroadcaster{} s := &Server{ ChainInfoFetcher: chain, diff --git a/beacon-chain/rpc/eth/validator/validator_test.go b/beacon-chain/rpc/eth/validator/validator_test.go index 484e8e3803..7ee054527d 100644 --- a/beacon-chain/rpc/eth/validator/validator_test.go +++ b/beacon-chain/rpc/eth/validator/validator_test.go @@ -59,7 +59,7 @@ func TestGetAttesterDuties(t *testing.T) { require.NoError(t, bs.SetSlot(5)) genesisRoot, err := genesis.Block.HashTreeRoot() require.NoError(t, err, "Could not get signing root") - roots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot) + roots := make([][]byte, fieldparams.BlockRootsLength) roots[0] = genesisRoot[:] require.NoError(t, bs.SetBlockRoots(roots)) @@ -141,7 +141,7 @@ func TestGetAttesterDuties(t *testing.T) { require.NoError(t, bs.SetSlot(5)) genesisRoot, err := genesis.Block.HashTreeRoot() require.NoError(t, err, "Could not get signing root") - roots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot) + roots := make([][]byte, fieldparams.BlockRootsLength) roots[0] = genesisRoot[:] require.NoError(t, bs.SetBlockRoots(roots)) @@ -236,7 +236,7 @@ func TestGetProposerDuties(t *testing.T) { require.NoError(t, bs.SetSlot(5)) genesisRoot, err := genesis.Block.HashTreeRoot() require.NoError(t, err, "Could not get signing root") - roots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot) + roots := make([][]byte, fieldparams.BlockRootsLength) roots[0] = genesisRoot[:] require.NoError(t, bs.SetBlockRoots(roots)) @@ -285,7 +285,7 @@ func TestGetProposerDuties(t *testing.T) { require.NoError(t, bs.SetSlot(5)) genesisRoot, err := genesis.Block.HashTreeRoot() require.NoError(t, err, "Could not get signing root") - roots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot) + roots := make([][]byte, fieldparams.BlockRootsLength) roots[0] = genesisRoot[:] require.NoError(t, bs.SetBlockRoots(roots)) @@ -1117,7 +1117,7 @@ func TestSubmitBeaconCommitteeSubscription(t *testing.T) { require.NoError(t, bs.SetSlot(5)) genesisRoot, err := genesis.Block.HashTreeRoot() require.NoError(t, err, "Could not get signing root") - roots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot) + roots := make([][]byte, fieldparams.BlockRootsLength) roots[0] = genesisRoot[:] require.NoError(t, bs.SetBlockRoots(roots)) @@ -1258,7 +1258,7 @@ func TestSubmitSyncCommitteeSubscription(t *testing.T) { require.NoError(t, err, "Could not set up genesis state") genesisRoot, err := genesis.Block.HashTreeRoot() require.NoError(t, err, "Could not get signing root") - roots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot) + roots := make([][]byte, fieldparams.BlockRootsLength) roots[0] = genesisRoot[:] require.NoError(t, bs.SetBlockRoots(roots)) diff --git a/beacon-chain/rpc/statefetcher/fetcher_test.go b/beacon-chain/rpc/statefetcher/fetcher_test.go index f61aa5b85e..98558495c1 100644 --- a/beacon-chain/rpc/statefetcher/fetcher_test.go +++ b/beacon-chain/rpc/statefetcher/fetcher_test.go @@ -29,14 +29,14 @@ func TestGetState(t *testing.T) { state.Slot = headSlot return nil } - state, err := util.NewBeaconState(util.FillRootsNaturalOpt, fillSlot) + newBeaconState, err := util.NewBeaconState(util.FillRootsNaturalOpt, fillSlot) require.NoError(t, err) - stateRoot, err := state.HashTreeRoot(ctx) + stateRoot, err := newBeaconState.HashTreeRoot(ctx) require.NoError(t, err) t.Run("head", func(t *testing.T) { p := StateProvider{ - ChainInfoFetcher: &chainMock.ChainService{State: state}, + ChainInfoFetcher: &chainMock.ChainService{State: newBeaconState}, } s, err := p.State(ctx, []byte("head")) @@ -59,17 +59,17 @@ func TestGetState(t *testing.T) { r, err := b.Block.HashTreeRoot() require.NoError(t, err) - state, err := util.NewBeaconState(func(state *ethpb.BeaconState) error { + bs, err := util.NewBeaconState(func(state *ethpb.BeaconState) error { state.BlockRoots[0] = r[:] return nil }) require.NoError(t, err) - stateRoot, err := state.HashTreeRoot(ctx) + newStateRoot, err := bs.HashTreeRoot(ctx) require.NoError(t, err) require.NoError(t, db.SaveStateSummary(ctx, ðpb.StateSummary{Root: r[:]})) require.NoError(t, db.SaveGenesisBlockRoot(ctx, r)) - require.NoError(t, db.SaveState(ctx, state, r)) + require.NoError(t, db.SaveState(ctx, bs, r)) p := StateProvider{ BeaconDB: db, @@ -79,12 +79,12 @@ func TestGetState(t *testing.T) { require.NoError(t, err) sRoot, err := s.HashTreeRoot(ctx) require.NoError(t, err) - assert.DeepEqual(t, stateRoot, sRoot) + assert.DeepEqual(t, newStateRoot, sRoot) }) t.Run("finalized", func(t *testing.T) { stateGen := stategen.NewMockService() - stateGen.StatesByRoot[stateRoot] = state + stateGen.StatesByRoot[stateRoot] = newBeaconState p := StateProvider{ ChainInfoFetcher: &chainMock.ChainService{ @@ -104,7 +104,7 @@ func TestGetState(t *testing.T) { t.Run("justified", func(t *testing.T) { stateGen := stategen.NewMockService() - stateGen.StatesByRoot[stateRoot] = state + stateGen.StatesByRoot[stateRoot] = newBeaconState p := StateProvider{ ChainInfoFetcher: &chainMock.ChainService{ @@ -126,10 +126,10 @@ func TestGetState(t *testing.T) { stateId, err := hexutil.Decode("0x" + strings.Repeat("0", 63) + "1") require.NoError(t, err) stateGen := stategen.NewMockService() - stateGen.StatesByRoot[bytesutil.ToBytes32(stateId)] = state + stateGen.StatesByRoot[bytesutil.ToBytes32(stateId)] = newBeaconState p := StateProvider{ - ChainInfoFetcher: &chainMock.ChainService{State: state}, + ChainInfoFetcher: &chainMock.ChainService{State: newBeaconState}, StateGenService: stateGen, } @@ -142,7 +142,7 @@ func TestGetState(t *testing.T) { t.Run("hex_root_not_found", func(t *testing.T) { p := StateProvider{ - ChainInfoFetcher: &chainMock.ChainService{State: state}, + ChainInfoFetcher: &chainMock.ChainService{State: newBeaconState}, } stateId, err := hexutil.Decode("0x" + strings.Repeat("f", 64)) require.NoError(t, err) @@ -152,7 +152,7 @@ func TestGetState(t *testing.T) { t.Run("slot", func(t *testing.T) { stateGen := stategen.NewMockService() - stateGen.StatesBySlot[headSlot] = state + stateGen.StatesBySlot[headSlot] = newBeaconState p := StateProvider{ GenesisTimeFetcher: &chainMock.ChainService{Slot: &headSlot}, @@ -191,9 +191,9 @@ func TestGetStateRoot(t *testing.T) { state.Slot = headSlot return nil } - state, err := util.NewBeaconState(util.FillRootsNaturalOpt, fillSlot) + newBeaconState, err := util.NewBeaconState(util.FillRootsNaturalOpt, fillSlot) require.NoError(t, err) - stateRoot, err := state.HashTreeRoot(ctx) + stateRoot, err := newBeaconState.HashTreeRoot(ctx) require.NoError(t, err) t.Run("head", func(t *testing.T) { @@ -201,7 +201,7 @@ func TestGetStateRoot(t *testing.T) { b.Block.StateRoot = stateRoot[:] p := StateProvider{ ChainInfoFetcher: &chainMock.ChainService{ - State: state, + State: newBeaconState, Block: wrapper.WrappedPhase0SignedBeaconBlock(b), }, } @@ -218,7 +218,7 @@ func TestGetStateRoot(t *testing.T) { r, err := b.Block.HashTreeRoot() require.NoError(t, err) - state, err := util.NewBeaconState(func(state *ethpb.BeaconState) error { + bs, err := util.NewBeaconState(func(state *ethpb.BeaconState) error { state.BlockRoots[0] = r[:] return nil }) @@ -226,7 +226,7 @@ func TestGetStateRoot(t *testing.T) { require.NoError(t, db.SaveStateSummary(ctx, ðpb.StateSummary{Root: r[:]})) require.NoError(t, db.SaveGenesisBlockRoot(ctx, r)) - require.NoError(t, db.SaveState(ctx, state, r)) + require.NoError(t, db.SaveState(ctx, bs, r)) p := StateProvider{ BeaconDB: db, @@ -306,7 +306,7 @@ func TestGetStateRoot(t *testing.T) { require.NoError(t, err) p := StateProvider{ - ChainInfoFetcher: &chainMock.ChainService{State: state}, + ChainInfoFetcher: &chainMock.ChainService{State: newBeaconState}, } s, err := p.StateRoot(ctx, stateId) @@ -316,7 +316,7 @@ func TestGetStateRoot(t *testing.T) { t.Run("hex_root_not_found", func(t *testing.T) { p := StateProvider{ - ChainInfoFetcher: &chainMock.ChainService{State: state}, + ChainInfoFetcher: &chainMock.ChainService{State: newBeaconState}, } stateId, err := hexutil.Decode("0x" + strings.Repeat("f", 64)) require.NoError(t, err) diff --git a/beacon-chain/state/BUILD.bazel b/beacon-chain/state/BUILD.bazel index f67952199c..e7b711b156 100644 --- a/beacon-chain/state/BUILD.bazel +++ b/beacon-chain/state/BUILD.bazel @@ -5,6 +5,7 @@ go_library( srcs = [ "altair.go", "phase0.go", + "prometheus.go", ], importpath = "github.com/prysmaticlabs/prysm/beacon-chain/state", visibility = [ @@ -25,6 +26,8 @@ go_library( deps = [ "//config/fieldparams:go_default_library", "//proto/prysm/v1alpha1:go_default_library", + "@com_github_prometheus_client_golang//prometheus:go_default_library", + "@com_github_prometheus_client_golang//prometheus/promauto:go_default_library", "@com_github_prysmaticlabs_eth2_types//:go_default_library", "@com_github_prysmaticlabs_go_bitfield//:go_default_library", ], diff --git a/beacon-chain/state/phase0.go b/beacon-chain/state/phase0.go index fbdffcd122..30ef98003e 100644 --- a/beacon-chain/state/phase0.go +++ b/beacon-chain/state/phase0.go @@ -159,6 +159,7 @@ type WriteOnlyBlockRoots interface { // WriteOnlyStateRoots defines a struct which only has write access to state roots methods. type WriteOnlyStateRoots interface { + SetStateRoots(val [][]byte) error UpdateStateRootAtIndex(idx uint64, stateRoot [32]byte) error } diff --git a/beacon-chain/state/prometheus.go b/beacon-chain/state/prometheus.go new file mode 100644 index 0000000000..a9f7920388 --- /dev/null +++ b/beacon-chain/state/prometheus.go @@ -0,0 +1,13 @@ +package state + +import ( + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" +) + +var ( + StateCount = promauto.NewGauge(prometheus.GaugeOpts{ + Name: "beacon_state_count", + Help: "Count the number of active beacon state objects.", + }) +) diff --git a/beacon-chain/state/state-native/fieldtrie/BUILD.bazel b/beacon-chain/state/state-native/fieldtrie/BUILD.bazel new file mode 100644 index 0000000000..6e53eb6dc7 --- /dev/null +++ b/beacon-chain/state/state-native/fieldtrie/BUILD.bazel @@ -0,0 +1,40 @@ +load("@prysm//tools/go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = [ + "field_trie.go", + "field_trie_helpers.go", + ], + importpath = "github.com/prysmaticlabs/prysm/beacon-chain/state/state-native/fieldtrie", + visibility = ["//beacon-chain:__subpackages__"], + deps = [ + "//beacon-chain/state/stateutil:go_default_library", + "//beacon-chain/state/types:go_default_library", + "//crypto/hash:go_default_library", + "//encoding/bytesutil:go_default_library", + "//encoding/ssz:go_default_library", + "//proto/prysm/v1alpha1:go_default_library", + "//runtime/version:go_default_library", + "@com_github_pkg_errors//:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = [ + "field_trie_test.go", + "helpers_test.go", + ], + embed = [":go_default_library"], + deps = [ + "//beacon-chain/state/stateutil:go_default_library", + "//beacon-chain/state/types:go_default_library", + "//config/params:go_default_library", + "//proto/prysm/v1alpha1:go_default_library", + "//testing/assert:go_default_library", + "//testing/require:go_default_library", + "//testing/util:go_default_library", + "@com_github_prysmaticlabs_eth2_types//:go_default_library", + ], +) diff --git a/beacon-chain/state/state-native/fieldtrie/field_trie.go b/beacon-chain/state/state-native/fieldtrie/field_trie.go new file mode 100644 index 0000000000..4f441668b7 --- /dev/null +++ b/beacon-chain/state/state-native/fieldtrie/field_trie.go @@ -0,0 +1,200 @@ +package fieldtrie + +import ( + "reflect" + "sync" + + "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" + "github.com/prysmaticlabs/prysm/beacon-chain/state/types" +) + +// FieldTrie is the representation of the representative +// trie of the particular field. +type FieldTrie struct { + *sync.RWMutex + reference *stateutil.Reference + fieldLayers [][]*[32]byte + field types.FieldIndex + dataType types.DataType + length uint64 + numOfElems int +} + +// NewFieldTrie is the constructor for the field trie data structure. It creates the corresponding +// trie according to the given parameters. Depending on whether the field is a basic/composite array +// which is either fixed/variable length, it will appropriately determine the trie. +func NewFieldTrie(field types.FieldIndex, dataType types.DataType, elements interface{}, length uint64) (*FieldTrie, error) { + if elements == nil { + return &FieldTrie{ + field: field, + dataType: dataType, + reference: stateutil.NewRef(1), + RWMutex: new(sync.RWMutex), + length: length, + numOfElems: 0, + }, nil + } + fieldRoots, err := fieldConverters(field, []uint64{}, elements, true) + if err != nil { + return nil, err + } + if err := validateElements(field, dataType, elements, length); err != nil { + return nil, err + } + switch dataType { + case types.BasicArray: + fl, err := stateutil.ReturnTrieLayer(fieldRoots, length) + if err != nil { + return nil, err + } + return &FieldTrie{ + fieldLayers: fl, + field: field, + dataType: dataType, + reference: stateutil.NewRef(1), + RWMutex: new(sync.RWMutex), + length: length, + numOfElems: reflect.ValueOf(elements).Len(), + }, nil + case types.CompositeArray, types.CompressedArray: + return &FieldTrie{ + fieldLayers: stateutil.ReturnTrieLayerVariable(fieldRoots, length), + field: field, + dataType: dataType, + reference: stateutil.NewRef(1), + RWMutex: new(sync.RWMutex), + length: length, + numOfElems: reflect.ValueOf(elements).Len(), + }, nil + default: + return nil, errors.Errorf("unrecognized data type in field map: %v", reflect.TypeOf(dataType).Name()) + } + +} + +// RecomputeTrie rebuilds the affected branches in the trie according to the provided +// changed indices and elements. This recomputes the trie according to the particular +// field the trie is based on. +func (f *FieldTrie) RecomputeTrie(indices []uint64, elements interface{}) ([32]byte, error) { + f.Lock() + defer f.Unlock() + var fieldRoot [32]byte + if len(indices) == 0 { + return f.TrieRoot() + } + fieldRoots, err := fieldConverters(f.field, indices, elements, false) + if err != nil { + return [32]byte{}, err + } + if err := f.validateIndices(indices); err != nil { + return [32]byte{}, err + } + switch f.dataType { + case types.BasicArray: + fieldRoot, f.fieldLayers, err = stateutil.RecomputeFromLayer(fieldRoots, indices, f.fieldLayers) + if err != nil { + return [32]byte{}, err + } + f.numOfElems = reflect.ValueOf(elements).Len() + return fieldRoot, nil + case types.CompositeArray: + fieldRoot, f.fieldLayers, err = stateutil.RecomputeFromLayerVariable(fieldRoots, indices, f.fieldLayers) + if err != nil { + return [32]byte{}, err + } + f.numOfElems = reflect.ValueOf(elements).Len() + return stateutil.AddInMixin(fieldRoot, uint64(len(f.fieldLayers[0]))) + case types.CompressedArray: + numOfElems, err := f.field.ElemsInChunk() + if err != nil { + return [32]byte{}, err + } + // We remove the duplicates here in order to prevent + // duplicated insertions into the trie. + newIndices := []uint64{} + indexExists := make(map[uint64]bool) + newRoots := make([][32]byte, 0, len(fieldRoots)/int(numOfElems)) + for i, idx := range indices { + startIdx := idx / numOfElems + if indexExists[startIdx] { + continue + } + newIndices = append(newIndices, startIdx) + indexExists[startIdx] = true + newRoots = append(newRoots, fieldRoots[i]) + } + fieldRoot, f.fieldLayers, err = stateutil.RecomputeFromLayerVariable(newRoots, newIndices, f.fieldLayers) + if err != nil { + return [32]byte{}, err + } + f.numOfElems = reflect.ValueOf(elements).Len() + return stateutil.AddInMixin(fieldRoot, uint64(f.numOfElems)) + default: + return [32]byte{}, errors.Errorf("unrecognized data type in field map: %v", reflect.TypeOf(f.dataType).Name()) + } +} + +// CopyTrie copies the references to the elements the trie +// is built on. +func (f *FieldTrie) CopyTrie() *FieldTrie { + if f.fieldLayers == nil { + return &FieldTrie{ + field: f.field, + dataType: f.dataType, + reference: stateutil.NewRef(1), + RWMutex: new(sync.RWMutex), + length: f.length, + numOfElems: f.numOfElems, + } + } + dstFieldTrie := make([][]*[32]byte, len(f.fieldLayers)) + for i, layer := range f.fieldLayers { + dstFieldTrie[i] = make([]*[32]byte, len(layer)) + copy(dstFieldTrie[i], layer) + } + return &FieldTrie{ + fieldLayers: dstFieldTrie, + field: f.field, + dataType: f.dataType, + reference: stateutil.NewRef(1), + RWMutex: new(sync.RWMutex), + length: f.length, + numOfElems: f.numOfElems, + } +} + +// TrieRoot returns the corresponding root of the trie. +func (f *FieldTrie) TrieRoot() ([32]byte, error) { + switch f.dataType { + case types.BasicArray: + return *f.fieldLayers[len(f.fieldLayers)-1][0], nil + case types.CompositeArray: + trieRoot := *f.fieldLayers[len(f.fieldLayers)-1][0] + return stateutil.AddInMixin(trieRoot, uint64(len(f.fieldLayers[0]))) + case types.CompressedArray: + trieRoot := *f.fieldLayers[len(f.fieldLayers)-1][0] + return stateutil.AddInMixin(trieRoot, uint64(f.numOfElems)) + default: + return [32]byte{}, errors.Errorf("unrecognized data type in field map: %v", reflect.TypeOf(f.dataType).Name()) + } +} + +// FieldReference returns the underlying field reference +// object for the trie. +func (f *FieldTrie) FieldReference() *stateutil.Reference { + return f.reference +} + +// Empty checks whether the underlying field trie is +// empty or not. +func (f *FieldTrie) Empty() bool { + return f == nil || len(f.fieldLayers) == 0 +} + +// InsertFieldLayer manually inserts a field layer. This method +// bypasses the normal method of field computation, it is only +// meant to be used in tests. +func (f *FieldTrie) InsertFieldLayer(layer [][]*[32]byte) { + f.fieldLayers = layer +} diff --git a/beacon-chain/state/state-native/fieldtrie/field_trie_helpers.go b/beacon-chain/state/state-native/fieldtrie/field_trie_helpers.go new file mode 100644 index 0000000000..c12097f9ca --- /dev/null +++ b/beacon-chain/state/state-native/fieldtrie/field_trie_helpers.go @@ -0,0 +1,295 @@ +package fieldtrie + +import ( + "encoding/binary" + "fmt" + "reflect" + + "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" + "github.com/prysmaticlabs/prysm/beacon-chain/state/types" + "github.com/prysmaticlabs/prysm/crypto/hash" + "github.com/prysmaticlabs/prysm/encoding/bytesutil" + "github.com/prysmaticlabs/prysm/encoding/ssz" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/runtime/version" +) + +// ProofFromMerkleLayers creates a proof starting at the leaf index of the state Merkle layers. +func ProofFromMerkleLayers(layers [][][]byte, startingLeafIndex types.FieldIndex) [][]byte { + // The merkle tree structure looks as follows: + // [[r1, r2, r3, r4], [parent1, parent2], [root]] + proof := make([][]byte, 0) + currentIndex := startingLeafIndex + for i := 0; i < len(layers)-1; i++ { + neighborIdx := currentIndex ^ 1 + neighbor := layers[i][neighborIdx] + proof = append(proof, neighbor) + currentIndex = currentIndex / 2 + } + return proof +} + +func (f *FieldTrie) validateIndices(idxs []uint64) error { + length := f.length + if f.dataType == types.CompressedArray { + comLength, err := f.field.ElemsInChunk() + if err != nil { + return err + } + length *= comLength + } + for _, idx := range idxs { + if idx >= length { + return errors.Errorf("invalid index for field %s: %d >= length %d", f.field.String(version.Phase0), idx, length) + } + } + return nil +} + +func validateElements(field types.FieldIndex, dataType types.DataType, elements interface{}, length uint64) error { + if dataType == types.CompressedArray { + comLength, err := field.ElemsInChunk() + if err != nil { + return err + } + length *= comLength + } + val := reflect.ValueOf(elements) + if val.Len() > int(length) { + return errors.Errorf("elements length is larger than expected for field %s: %d > %d", field.String(version.Phase0), val.Len(), length) + } + return nil +} + +// fieldConverters converts the corresponding field and the provided elements to the appropriate roots. +func fieldConverters(field types.FieldIndex, indices []uint64, elements interface{}, convertAll bool) ([][32]byte, error) { + switch field { + case types.BlockRoots, types.StateRoots, types.RandaoMixes: + val, ok := elements.([][]byte) + if !ok { + return nil, errors.Errorf("Wanted type of %v but got %v", + reflect.TypeOf([][]byte{}).Name(), reflect.TypeOf(elements).Name()) + } + return handleByteArrays(val, indices, convertAll) + case types.Eth1DataVotes: + val, ok := elements.([]*ethpb.Eth1Data) + if !ok { + return nil, errors.Errorf("Wanted type of %v but got %v", + reflect.TypeOf([]*ethpb.Eth1Data{}).Name(), reflect.TypeOf(elements).Name()) + } + return handleEth1DataSlice(val, indices, convertAll) + case types.Validators: + val, ok := elements.([]*ethpb.Validator) + if !ok { + return nil, errors.Errorf("Wanted type of %v but got %v", + reflect.TypeOf([]*ethpb.Validator{}).Name(), reflect.TypeOf(elements).Name()) + } + return handleValidatorSlice(val, indices, convertAll) + case types.PreviousEpochAttestations, types.CurrentEpochAttestations: + val, ok := elements.([]*ethpb.PendingAttestation) + if !ok { + return nil, errors.Errorf("Wanted type of %v but got %v", + reflect.TypeOf([]*ethpb.PendingAttestation{}).Name(), reflect.TypeOf(elements).Name()) + } + return handlePendingAttestation(val, indices, convertAll) + case types.Balances: + val, ok := elements.([]uint64) + if !ok { + return nil, errors.Errorf("Wanted type of %v but got %v", + reflect.TypeOf([]uint64{}).Name(), reflect.TypeOf(elements).Name()) + } + return handleBalanceSlice(val, indices, convertAll) + default: + return [][32]byte{}, errors.Errorf("got unsupported type of %v", reflect.TypeOf(elements).Name()) + } +} + +// handleByteArrays computes and returns byte arrays in a slice of root format. +func handleByteArrays(val [][]byte, indices []uint64, convertAll bool) ([][32]byte, error) { + length := len(indices) + if convertAll { + length = len(val) + } + roots := make([][32]byte, 0, length) + rootCreator := func(input []byte) { + newRoot := bytesutil.ToBytes32(input) + roots = append(roots, newRoot) + } + if convertAll { + for i := range val { + rootCreator(val[i]) + } + return roots, nil + } + if len(val) > 0 { + for _, idx := range indices { + if idx > uint64(len(val))-1 { + return nil, fmt.Errorf("index %d greater than number of byte arrays %d", idx, len(val)) + } + rootCreator(val[idx]) + } + } + return roots, nil +} + +// handleValidatorSlice returns the validator indices in a slice of root format. +func handleValidatorSlice(val []*ethpb.Validator, indices []uint64, convertAll bool) ([][32]byte, error) { + length := len(indices) + if convertAll { + length = len(val) + } + roots := make([][32]byte, 0, length) + hasher := hash.CustomSHA256Hasher() + rootCreator := func(input *ethpb.Validator) error { + newRoot, err := stateutil.ValidatorRootWithHasher(hasher, input) + if err != nil { + return err + } + roots = append(roots, newRoot) + return nil + } + if convertAll { + for i := range val { + err := rootCreator(val[i]) + if err != nil { + return nil, err + } + } + return roots, nil + } + if len(val) > 0 { + for _, idx := range indices { + if idx > uint64(len(val))-1 { + return nil, fmt.Errorf("index %d greater than number of validators %d", idx, len(val)) + } + err := rootCreator(val[idx]) + if err != nil { + return nil, err + } + } + } + return roots, nil +} + +// handleEth1DataSlice processes a list of eth1data and indices into the appropriate roots. +func handleEth1DataSlice(val []*ethpb.Eth1Data, indices []uint64, convertAll bool) ([][32]byte, error) { + length := len(indices) + if convertAll { + length = len(val) + } + roots := make([][32]byte, 0, length) + hasher := hash.CustomSHA256Hasher() + rootCreator := func(input *ethpb.Eth1Data) error { + newRoot, err := stateutil.Eth1DataRootWithHasher(hasher, input) + if err != nil { + return err + } + roots = append(roots, newRoot) + return nil + } + if convertAll { + for i := range val { + err := rootCreator(val[i]) + if err != nil { + return nil, err + } + } + return roots, nil + } + if len(val) > 0 { + for _, idx := range indices { + if idx > uint64(len(val))-1 { + return nil, fmt.Errorf("index %d greater than number of items in eth1 data slice %d", idx, len(val)) + } + err := rootCreator(val[idx]) + if err != nil { + return nil, err + } + } + } + return roots, nil +} + +func handlePendingAttestation(val []*ethpb.PendingAttestation, indices []uint64, convertAll bool) ([][32]byte, error) { + length := len(indices) + if convertAll { + length = len(val) + } + roots := make([][32]byte, 0, length) + hasher := hash.CustomSHA256Hasher() + rootCreator := func(input *ethpb.PendingAttestation) error { + newRoot, err := stateutil.PendingAttRootWithHasher(hasher, input) + if err != nil { + return err + } + roots = append(roots, newRoot) + return nil + } + if convertAll { + for i := range val { + err := rootCreator(val[i]) + if err != nil { + return nil, err + } + } + return roots, nil + } + if len(val) > 0 { + for _, idx := range indices { + if idx > uint64(len(val))-1 { + return nil, fmt.Errorf("index %d greater than number of pending attestations %d", idx, len(val)) + } + err := rootCreator(val[idx]) + if err != nil { + return nil, err + } + } + } + return roots, nil +} + +func handleBalanceSlice(val, indices []uint64, convertAll bool) ([][32]byte, error) { + if convertAll { + balancesMarshaling := make([][]byte, 0) + for _, b := range val { + balanceBuf := make([]byte, 8) + binary.LittleEndian.PutUint64(balanceBuf, b) + balancesMarshaling = append(balancesMarshaling, balanceBuf) + } + balancesChunks, err := ssz.PackByChunk(balancesMarshaling) + if err != nil { + return [][32]byte{}, errors.Wrap(err, "could not pack balances into chunks") + } + return balancesChunks, nil + } + if len(val) > 0 { + numOfElems, err := types.Balances.ElemsInChunk() + if err != nil { + return nil, err + } + roots := [][32]byte{} + for _, idx := range indices { + // We split the indexes into their relevant groups. Balances + // are compressed according to 4 values -> 1 chunk. + startIdx := idx / numOfElems + startGroup := startIdx * numOfElems + chunk := [32]byte{} + sizeOfElem := len(chunk) / int(numOfElems) + for i, j := 0, startGroup; j < startGroup+numOfElems; i, j = i+sizeOfElem, j+1 { + wantedVal := uint64(0) + // We are adding chunks in sets of 4, if the set is at the edge of the array + // then you will need to zero out the rest of the chunk. Ex : 41 indexes, + // so 41 % 4 = 1 . There are 3 indexes, which do not exist yet but we + // have to add in as a root. These 3 indexes are then given a 'zero' value. + if int(j) < len(val) { + wantedVal = val[j] + } + binary.LittleEndian.PutUint64(chunk[i:i+sizeOfElem], wantedVal) + } + roots = append(roots, chunk) + } + return roots, nil + } + return [][32]byte{}, nil +} diff --git a/beacon-chain/state/state-native/fieldtrie/field_trie_test.go b/beacon-chain/state/state-native/fieldtrie/field_trie_test.go new file mode 100644 index 0000000000..5b0bd992e3 --- /dev/null +++ b/beacon-chain/state/state-native/fieldtrie/field_trie_test.go @@ -0,0 +1,79 @@ +package fieldtrie_test + +import ( + "testing" + + types "github.com/prysmaticlabs/eth2-types" + "github.com/prysmaticlabs/prysm/beacon-chain/state/state-native/fieldtrie" + "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" + stateTypes "github.com/prysmaticlabs/prysm/beacon-chain/state/types" + "github.com/prysmaticlabs/prysm/config/params" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/testing/assert" + "github.com/prysmaticlabs/prysm/testing/require" + "github.com/prysmaticlabs/prysm/testing/util" +) + +func TestFieldTrie_NewTrie(t *testing.T) { + newState, _ := util.DeterministicGenesisState(t, 40) + + // 5 represents the enum value of state roots + trie, err := fieldtrie.NewFieldTrie(5, stateTypes.BasicArray, newState.StateRoots(), uint64(params.BeaconConfig().SlotsPerHistoricalRoot)) + require.NoError(t, err) + root, err := stateutil.RootsArrayHashTreeRoot(newState.StateRoots(), uint64(params.BeaconConfig().SlotsPerHistoricalRoot), "StateRoots") + require.NoError(t, err) + newRoot, err := trie.TrieRoot() + require.NoError(t, err) + assert.Equal(t, root, newRoot) +} + +func TestFieldTrie_RecomputeTrie(t *testing.T) { + newState, _ := util.DeterministicGenesisState(t, 32) + // 10 represents the enum value of validators + trie, err := fieldtrie.NewFieldTrie(11, stateTypes.CompositeArray, newState.Validators(), params.BeaconConfig().ValidatorRegistryLimit) + require.NoError(t, err) + + changedIdx := []uint64{2, 29} + val1, err := newState.ValidatorAtIndex(10) + require.NoError(t, err) + val2, err := newState.ValidatorAtIndex(11) + require.NoError(t, err) + val1.Slashed = true + val1.ExitEpoch = 20 + + val2.Slashed = true + val2.ExitEpoch = 40 + + changedVals := []*ethpb.Validator{val1, val2} + require.NoError(t, newState.UpdateValidatorAtIndex(types.ValidatorIndex(changedIdx[0]), changedVals[0])) + require.NoError(t, newState.UpdateValidatorAtIndex(types.ValidatorIndex(changedIdx[1]), changedVals[1])) + + expectedRoot, err := stateutil.ValidatorRegistryRoot(newState.Validators()) + require.NoError(t, err) + root, err := trie.RecomputeTrie(changedIdx, newState.Validators()) + require.NoError(t, err) + assert.Equal(t, expectedRoot, root) +} + +func TestFieldTrie_CopyTrieImmutable(t *testing.T) { + newState, _ := util.DeterministicGenesisState(t, 32) + // 12 represents the enum value of randao mixes. + trie, err := fieldtrie.NewFieldTrie(13, stateTypes.BasicArray, newState.RandaoMixes(), uint64(params.BeaconConfig().EpochsPerHistoricalVector)) + require.NoError(t, err) + + newTrie := trie.CopyTrie() + + changedIdx := []uint64{2, 29} + + changedVals := [][32]byte{{'A', 'B'}, {'C', 'D'}} + require.NoError(t, newState.UpdateRandaoMixesAtIndex(changedIdx[0], changedVals[0][:])) + require.NoError(t, newState.UpdateRandaoMixesAtIndex(changedIdx[1], changedVals[1][:])) + + root, err := trie.RecomputeTrie(changedIdx, newState.RandaoMixes()) + require.NoError(t, err) + newRoot, err := newTrie.TrieRoot() + require.NoError(t, err) + if root == newRoot { + t.Errorf("Wanted roots to be different, but they are the same: %#x", root) + } +} diff --git a/beacon-chain/state/state-native/fieldtrie/helpers_test.go b/beacon-chain/state/state-native/fieldtrie/helpers_test.go new file mode 100644 index 0000000000..54666b872a --- /dev/null +++ b/beacon-chain/state/state-native/fieldtrie/helpers_test.go @@ -0,0 +1,85 @@ +package fieldtrie + +import ( + "encoding/binary" + "sync" + "testing" + + "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" + "github.com/prysmaticlabs/prysm/beacon-chain/state/types" + "github.com/prysmaticlabs/prysm/config/params" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/testing/assert" +) + +func Test_handlePendingAttestation_OutOfRange(t *testing.T) { + items := make([]*ethpb.PendingAttestation, 1) + indices := []uint64{3} + _, err := handlePendingAttestation(items, indices, false) + assert.ErrorContains(t, "index 3 greater than number of pending attestations 1", err) +} + +func Test_handleEth1DataSlice_OutOfRange(t *testing.T) { + items := make([]*ethpb.Eth1Data, 1) + indices := []uint64{3} + _, err := handleEth1DataSlice(items, indices, false) + assert.ErrorContains(t, "index 3 greater than number of items in eth1 data slice 1", err) + +} + +func Test_handleValidatorSlice_OutOfRange(t *testing.T) { + vals := make([]*ethpb.Validator, 1) + indices := []uint64{3} + _, err := handleValidatorSlice(vals, indices, false) + assert.ErrorContains(t, "index 3 greater than number of validators 1", err) +} + +func TestBalancesSlice_CorrectRoots_All(t *testing.T) { + balances := []uint64{5, 2929, 34, 1291, 354305} + roots, err := handleBalanceSlice(balances, []uint64{}, true) + assert.NoError(t, err) + + root1 := [32]byte{} + binary.LittleEndian.PutUint64(root1[:8], balances[0]) + binary.LittleEndian.PutUint64(root1[8:16], balances[1]) + binary.LittleEndian.PutUint64(root1[16:24], balances[2]) + binary.LittleEndian.PutUint64(root1[24:32], balances[3]) + + root2 := [32]byte{} + binary.LittleEndian.PutUint64(root2[:8], balances[4]) + + assert.DeepEqual(t, roots, [][32]byte{root1, root2}) +} + +func TestBalancesSlice_CorrectRoots_Some(t *testing.T) { + balances := []uint64{5, 2929, 34, 1291, 354305} + roots, err := handleBalanceSlice(balances, []uint64{2, 3}, false) + assert.NoError(t, err) + + root1 := [32]byte{} + binary.LittleEndian.PutUint64(root1[:8], balances[0]) + binary.LittleEndian.PutUint64(root1[8:16], balances[1]) + binary.LittleEndian.PutUint64(root1[16:24], balances[2]) + binary.LittleEndian.PutUint64(root1[24:32], balances[3]) + + // Returns root for each indice(even if duplicated) + assert.DeepEqual(t, roots, [][32]byte{root1, root1}) +} + +func TestValidateIndices_CompressedField(t *testing.T) { + fakeTrie := &FieldTrie{ + RWMutex: new(sync.RWMutex), + reference: stateutil.NewRef(0), + fieldLayers: nil, + field: types.Balances, + dataType: types.CompressedArray, + length: params.BeaconConfig().ValidatorRegistryLimit / 4, + numOfElems: 0, + } + goodIdx := params.BeaconConfig().ValidatorRegistryLimit - 1 + assert.NoError(t, fakeTrie.validateIndices([]uint64{goodIdx})) + + badIdx := goodIdx + 1 + assert.ErrorContains(t, "invalid index for field balances", fakeTrie.validateIndices([]uint64{badIdx})) + +} diff --git a/beacon-chain/state/state-native/v1/BUILD.bazel b/beacon-chain/state/state-native/v1/BUILD.bazel new file mode 100644 index 0000000000..5a0cd4ed5d --- /dev/null +++ b/beacon-chain/state/state-native/v1/BUILD.bazel @@ -0,0 +1,105 @@ +load("@prysm//tools/go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "field_roots.go", + "getters_attestation.go", + "getters_block.go", + "getters_checkpoint.go", + "getters_eth1.go", + "getters_misc.go", + "getters_randao.go", + "getters_state.go", + "getters_validator.go", + "proofs.go", + "readonly_validator.go", + "setters_attestation.go", + "setters_block.go", + "setters_checkpoint.go", + "setters_eth1.go", + "setters_misc.go", + "setters_randao.go", + "setters_state.go", + "setters_validator.go", + "state_trie.go", + "types.go", + "unsupported_getters.go", + "unsupported_setters.go", + ], + importpath = "github.com/prysmaticlabs/prysm/beacon-chain/state/state-native/v1", + visibility = [ + "//beacon-chain:__subpackages__", + "//contracts/deposit:__subpackages__", + "//proto/migration:__subpackages__", + "//proto/prysm/v1alpha1:__subpackages__", + "//proto/testing:__subpackages__", + "//runtime/interop:__subpackages__", + "//slasher/rpc:__subpackages__", + "//testing/benchmark:__pkg__", + "//testing/fuzz:__pkg__", + "//testing/spectest:__subpackages__", + "//testing/util:__pkg__", + "//tools/benchmark-files-gen:__pkg__", + "//tools/pcli:__pkg__", + ], + deps = [ + "//beacon-chain/state:go_default_library", + "//beacon-chain/state/state-native/fieldtrie:go_default_library", + "//beacon-chain/state/stateutil:go_default_library", + "//beacon-chain/state/types:go_default_library", + "//config/features:go_default_library", + "//config/fieldparams:go_default_library", + "//config/params:go_default_library", + "//container/slice:go_default_library", + "//crypto/hash:go_default_library", + "//encoding/bytesutil:go_default_library", + "//encoding/ssz:go_default_library", + "//proto/prysm/v1alpha1:go_default_library", + "//runtime/version:go_default_library", + "@com_github_pkg_errors//:go_default_library", + "@com_github_prysmaticlabs_eth2_types//:go_default_library", + "@com_github_prysmaticlabs_go_bitfield//:go_default_library", + "@io_opencensus_go//trace:go_default_library", + "@org_golang_google_protobuf//proto:go_default_library", + ], +) + +# gazelle:exclude types_bench_test.go +go_test( + name = "go_default_test", + srcs = [ + "getters_attestation_test.go", + "getters_block_test.go", + "getters_test.go", + "getters_validator_test.go", + "proofs_test.go", + "readonly_validator_test.go", + "references_test.go", + "setters_attestation_test.go", + "state_test.go", + "state_trie_test.go", + "types_test.go", + ], + embed = [":go_default_library"], + deps = [ + "//beacon-chain/state:go_default_library", + "//beacon-chain/state/stateutil:go_default_library", + "//beacon-chain/state/types:go_default_library", + "//config/features:go_default_library", + "//config/fieldparams:go_default_library", + "//config/params:go_default_library", + "//container/trie:go_default_library", + "//encoding/bytesutil:go_default_library", + "//proto/prysm/v1alpha1:go_default_library", + "//runtime/interop:go_default_library", + "//testing/assert:go_default_library", + "//testing/require:go_default_library", + "//testing/util:go_default_library", + "@com_github_prysmaticlabs_eth2_types//:go_default_library", + "@com_github_prysmaticlabs_go_bitfield//:go_default_library", + "@com_github_sirupsen_logrus//:go_default_library", + "@org_golang_google_protobuf//proto:go_default_library", + ], +) diff --git a/beacon-chain/state/state-native/v1/doc.go b/beacon-chain/state/state-native/v1/doc.go new file mode 100644 index 0000000000..d47c3fb247 --- /dev/null +++ b/beacon-chain/state/state-native/v1/doc.go @@ -0,0 +1,40 @@ +// Package v1 defines how the beacon chain state for Ethereum +// functions in the running beacon node, using an advanced, +// immutable implementation of the state data structure. +// +// BeaconState getters may be accessed from inside or outside the package. To +// avoid duplicating locks, we have internal and external versions of the +// getter The external function carries out the short-circuit conditions, +// obtains a read lock, then calls the internal function. The internal function +// carries out the short-circuit conditions and returns the required data +// without further locking, allowing it to be used by other package-level +// functions that already hold a lock. Hence the functions look something +// like this: +// +// func (b *BeaconState) Foo() uint64 { +// // Short-circuit conditions. +// if !b.hasInnerState() { +// return 0 +// } +// +// // Read lock. +// b.lock.RLock() +// defer b.lock.RUnlock() +// +// // Internal getter. +// return b.foo() +// } +// +// func (b *BeaconState) foo() uint64 { +// // Short-circuit conditions. +// if !b.hasInnerState() { +// return 0 +// } +// +// return b.state.foo +// } +// +// Although it is technically possible to remove the short-circuit conditions +// from the external function, that would require every read to obtain a lock +// even if the data was not present, leading to potential slowdowns. +package v1 diff --git a/beacon-chain/state/state-native/v1/field_roots.go b/beacon-chain/state/state-native/v1/field_roots.go new file mode 100644 index 0000000000..0b9983b52c --- /dev/null +++ b/beacon-chain/state/state-native/v1/field_roots.go @@ -0,0 +1,18 @@ +package v1 + +import ( + "context" + + "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" + "github.com/prysmaticlabs/prysm/config/features" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" +) + +// computeFieldRoots returns the hash tree root computations of every field in +// the beacon state as a list of 32 byte roots. +func computeFieldRoots(ctx context.Context, state *ethpb.BeaconState) ([][]byte, error) { + if features.Get().EnableSSZCache { + return stateutil.CachedHasher.ComputeFieldRootsWithHasherPhase0(ctx, state) + } + return stateutil.NocachedHasher.ComputeFieldRootsWithHasherPhase0(ctx, state) +} diff --git a/beacon-chain/state/state-native/v1/getters_attestation.go b/beacon-chain/state/state-native/v1/getters_attestation.go new file mode 100644 index 0000000000..e7e3bd6901 --- /dev/null +++ b/beacon-chain/state/state-native/v1/getters_attestation.go @@ -0,0 +1,55 @@ +package v1 + +import ( + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" +) + +// PreviousEpochAttestations corresponding to blocks on the beacon chain. +func (b *BeaconState) PreviousEpochAttestations() ([]*ethpb.PendingAttestation, error) { + if !b.hasInnerState() { + return nil, nil + } + if b.state.PreviousEpochAttestations == nil { + return nil, nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.previousEpochAttestations(), nil +} + +// previousEpochAttestations corresponding to blocks on the beacon chain. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) previousEpochAttestations() []*ethpb.PendingAttestation { + if !b.hasInnerState() { + return nil + } + + return ethpb.CopyPendingAttestationSlice(b.state.PreviousEpochAttestations) +} + +// CurrentEpochAttestations corresponding to blocks on the beacon chain. +func (b *BeaconState) CurrentEpochAttestations() ([]*ethpb.PendingAttestation, error) { + if !b.hasInnerState() { + return nil, nil + } + if b.state.CurrentEpochAttestations == nil { + return nil, nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.currentEpochAttestations(), nil +} + +// currentEpochAttestations corresponding to blocks on the beacon chain. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) currentEpochAttestations() []*ethpb.PendingAttestation { + if !b.hasInnerState() { + return nil + } + + return ethpb.CopyPendingAttestationSlice(b.state.CurrentEpochAttestations) +} diff --git a/beacon-chain/state/state-native/v1/getters_attestation_test.go b/beacon-chain/state/state-native/v1/getters_attestation_test.go new file mode 100644 index 0000000000..af85b2661a --- /dev/null +++ b/beacon-chain/state/state-native/v1/getters_attestation_test.go @@ -0,0 +1,46 @@ +package v1 + +import ( + "testing" + + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/testing/require" +) + +func TestBeaconState_PreviousEpochAttestations(t *testing.T) { + s, err := InitializeFromProto(ðpb.BeaconState{}) + require.NoError(t, err) + atts, err := s.PreviousEpochAttestations() + require.NoError(t, err) + require.DeepEqual(t, []*ethpb.PendingAttestation(nil), atts) + + want := []*ethpb.PendingAttestation{{ProposerIndex: 100}} + s, err = InitializeFromProto(ðpb.BeaconState{PreviousEpochAttestations: want}) + require.NoError(t, err) + got, err := s.PreviousEpochAttestations() + require.NoError(t, err) + require.DeepEqual(t, want, got) + + // Test copy does not mutate. + got[0].ProposerIndex = 101 + require.DeepNotEqual(t, want, got) +} + +func TestBeaconState_CurrentEpochAttestations(t *testing.T) { + s, err := InitializeFromProto(ðpb.BeaconState{}) + require.NoError(t, err) + atts, err := s.CurrentEpochAttestations() + require.NoError(t, err) + require.DeepEqual(t, []*ethpb.PendingAttestation(nil), atts) + + want := []*ethpb.PendingAttestation{{ProposerIndex: 101}} + s, err = InitializeFromProto(ðpb.BeaconState{CurrentEpochAttestations: want}) + require.NoError(t, err) + got, err := s.CurrentEpochAttestations() + require.NoError(t, err) + require.DeepEqual(t, want, got) + + // Test copy does not mutate. + got[0].ProposerIndex = 102 + require.DeepNotEqual(t, want, got) +} diff --git a/beacon-chain/state/state-native/v1/getters_block.go b/beacon-chain/state/state-native/v1/getters_block.go new file mode 100644 index 0000000000..64baa623cb --- /dev/null +++ b/beacon-chain/state/state-native/v1/getters_block.go @@ -0,0 +1,99 @@ +package v1 + +import ( + "github.com/prysmaticlabs/prysm/encoding/bytesutil" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" +) + +// LatestBlockHeader stored within the beacon state. +func (b *BeaconState) LatestBlockHeader() *ethpb.BeaconBlockHeader { + if !b.hasInnerState() { + return nil + } + if b.state.LatestBlockHeader == nil { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.latestBlockHeader() +} + +// latestBlockHeader stored within the beacon state. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) latestBlockHeader() *ethpb.BeaconBlockHeader { + if !b.hasInnerState() { + return nil + } + if b.state.LatestBlockHeader == nil { + return nil + } + + hdr := ðpb.BeaconBlockHeader{ + Slot: b.state.LatestBlockHeader.Slot, + ProposerIndex: b.state.LatestBlockHeader.ProposerIndex, + } + + parentRoot := make([]byte, len(b.state.LatestBlockHeader.ParentRoot)) + bodyRoot := make([]byte, len(b.state.LatestBlockHeader.BodyRoot)) + stateRoot := make([]byte, len(b.state.LatestBlockHeader.StateRoot)) + + copy(parentRoot, b.state.LatestBlockHeader.ParentRoot) + copy(bodyRoot, b.state.LatestBlockHeader.BodyRoot) + copy(stateRoot, b.state.LatestBlockHeader.StateRoot) + hdr.ParentRoot = parentRoot + hdr.BodyRoot = bodyRoot + hdr.StateRoot = stateRoot + return hdr +} + +// BlockRoots kept track of in the beacon state. +func (b *BeaconState) BlockRoots() [][]byte { + if !b.hasInnerState() { + return nil + } + if b.state.BlockRoots == nil { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.blockRoots() +} + +// blockRoots kept track of in the beacon state. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) blockRoots() [][]byte { + if !b.hasInnerState() { + return nil + } + return bytesutil.SafeCopy2dBytes(b.state.BlockRoots) +} + +// BlockRootAtIndex retrieves a specific block root based on an +// input index value. +func (b *BeaconState) BlockRootAtIndex(idx uint64) ([]byte, error) { + if !b.hasInnerState() { + return nil, ErrNilInnerState + } + if b.state.BlockRoots == nil { + return nil, nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.blockRootAtIndex(idx) +} + +// blockRootAtIndex retrieves a specific block root based on an +// input index value. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) blockRootAtIndex(idx uint64) ([]byte, error) { + if !b.hasInnerState() { + return nil, ErrNilInnerState + } + return bytesutil.SafeCopyRootAtIndex(b.state.BlockRoots, idx) +} diff --git a/beacon-chain/state/state-native/v1/getters_block_test.go b/beacon-chain/state/state-native/v1/getters_block_test.go new file mode 100644 index 0000000000..aee36ec9b0 --- /dev/null +++ b/beacon-chain/state/state-native/v1/getters_block_test.go @@ -0,0 +1,59 @@ +package v1 + +import ( + "testing" + + "github.com/prysmaticlabs/prysm/encoding/bytesutil" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/testing/require" +) + +func TestBeaconState_LatestBlockHeader(t *testing.T) { + s, err := InitializeFromProto(ðpb.BeaconState{}) + require.NoError(t, err) + got := s.LatestBlockHeader() + require.DeepEqual(t, (*ethpb.BeaconBlockHeader)(nil), got) + + want := ðpb.BeaconBlockHeader{Slot: 100} + s, err = InitializeFromProto(ðpb.BeaconState{LatestBlockHeader: want}) + require.NoError(t, err) + got = s.LatestBlockHeader() + require.DeepEqual(t, want, got) + + // Test copy does not mutate. + got.Slot = 101 + require.DeepNotEqual(t, want, got) +} + +func TestBeaconState_BlockRoots(t *testing.T) { + s, err := InitializeFromProto(ðpb.BeaconState{}) + require.NoError(t, err) + got := s.BlockRoots() + require.DeepEqual(t, ([][]byte)(nil), got) + + want := [][]byte{{'a'}} + s, err = InitializeFromProto(ðpb.BeaconState{BlockRoots: want}) + require.NoError(t, err) + got = s.BlockRoots() + require.DeepEqual(t, want, got) + + // Test copy does not mutate. + got[0][0] = 'b' + require.DeepNotEqual(t, want, got) +} + +func TestBeaconState_BlockRootAtIndex(t *testing.T) { + s, err := InitializeFromProto(ðpb.BeaconState{}) + require.NoError(t, err) + got, err := s.BlockRootAtIndex(0) + require.NoError(t, err) + require.DeepEqual(t, ([]byte)(nil), got) + + r := [][]byte{{'a'}} + s, err = InitializeFromProto(ðpb.BeaconState{BlockRoots: r}) + require.NoError(t, err) + got, err = s.BlockRootAtIndex(0) + require.NoError(t, err) + want := bytesutil.PadTo([]byte{'a'}, 32) + require.DeepSSZEqual(t, want, got) +} diff --git a/beacon-chain/state/state-native/v1/getters_checkpoint.go b/beacon-chain/state/state-native/v1/getters_checkpoint.go new file mode 100644 index 0000000000..70f6237303 --- /dev/null +++ b/beacon-chain/state/state-native/v1/getters_checkpoint.go @@ -0,0 +1,160 @@ +package v1 + +import ( + "bytes" + + types "github.com/prysmaticlabs/eth2-types" + "github.com/prysmaticlabs/go-bitfield" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" +) + +// JustificationBits marking which epochs have been justified in the beacon chain. +func (b *BeaconState) JustificationBits() bitfield.Bitvector4 { + if !b.hasInnerState() { + return nil + } + if b.state.JustificationBits == nil { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.justificationBits() +} + +// justificationBits marking which epochs have been justified in the beacon chain. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) justificationBits() bitfield.Bitvector4 { + if !b.hasInnerState() { + return nil + } + if b.state.JustificationBits == nil { + return nil + } + + res := make([]byte, len(b.state.JustificationBits.Bytes())) + copy(res, b.state.JustificationBits.Bytes()) + return res +} + +// PreviousJustifiedCheckpoint denoting an epoch and block root. +func (b *BeaconState) PreviousJustifiedCheckpoint() *ethpb.Checkpoint { + if !b.hasInnerState() { + return nil + } + if b.state.PreviousJustifiedCheckpoint == nil { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.previousJustifiedCheckpoint() +} + +// previousJustifiedCheckpoint denoting an epoch and block root. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) previousJustifiedCheckpoint() *ethpb.Checkpoint { + if !b.hasInnerState() { + return nil + } + + return ethpb.CopyCheckpoint(b.state.PreviousJustifiedCheckpoint) +} + +// CurrentJustifiedCheckpoint denoting an epoch and block root. +func (b *BeaconState) CurrentJustifiedCheckpoint() *ethpb.Checkpoint { + if !b.hasInnerState() { + return nil + } + if b.state.CurrentJustifiedCheckpoint == nil { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.currentJustifiedCheckpoint() +} + +// currentJustifiedCheckpoint denoting an epoch and block root. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) currentJustifiedCheckpoint() *ethpb.Checkpoint { + if !b.hasInnerState() { + return nil + } + + return ethpb.CopyCheckpoint(b.state.CurrentJustifiedCheckpoint) +} + +// MatchCurrentJustifiedCheckpoint returns true if input justified checkpoint matches +// the current justified checkpoint in state. +func (b *BeaconState) MatchCurrentJustifiedCheckpoint(c *ethpb.Checkpoint) bool { + if !b.hasInnerState() { + return false + } + if b.state.CurrentJustifiedCheckpoint == nil { + return false + } + + if c.Epoch != b.state.CurrentJustifiedCheckpoint.Epoch { + return false + } + return bytes.Equal(c.Root, b.state.CurrentJustifiedCheckpoint.Root) +} + +// MatchPreviousJustifiedCheckpoint returns true if the input justified checkpoint matches +// the previous justified checkpoint in state. +func (b *BeaconState) MatchPreviousJustifiedCheckpoint(c *ethpb.Checkpoint) bool { + if !b.hasInnerState() { + return false + } + if b.state.PreviousJustifiedCheckpoint == nil { + return false + } + + if c.Epoch != b.state.PreviousJustifiedCheckpoint.Epoch { + return false + } + return bytes.Equal(c.Root, b.state.PreviousJustifiedCheckpoint.Root) +} + +// FinalizedCheckpoint denoting an epoch and block root. +func (b *BeaconState) FinalizedCheckpoint() *ethpb.Checkpoint { + if !b.hasInnerState() { + return nil + } + if b.state.FinalizedCheckpoint == nil { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.finalizedCheckpoint() +} + +// finalizedCheckpoint denoting an epoch and block root. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) finalizedCheckpoint() *ethpb.Checkpoint { + if !b.hasInnerState() { + return nil + } + + return ethpb.CopyCheckpoint(b.state.FinalizedCheckpoint) +} + +// FinalizedCheckpointEpoch returns the epoch value of the finalized checkpoint. +func (b *BeaconState) FinalizedCheckpointEpoch() types.Epoch { + if !b.hasInnerState() { + return 0 + } + if b.state.FinalizedCheckpoint == nil { + return 0 + } + b.lock.RLock() + defer b.lock.RUnlock() + + return b.state.FinalizedCheckpoint.Epoch +} diff --git a/beacon-chain/state/state-native/v1/getters_eth1.go b/beacon-chain/state/state-native/v1/getters_eth1.go new file mode 100644 index 0000000000..4f1ad84d12 --- /dev/null +++ b/beacon-chain/state/state-native/v1/getters_eth1.go @@ -0,0 +1,91 @@ +package v1 + +import ( + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" +) + +// Eth1Data corresponding to the proof-of-work chain information stored in the beacon state. +func (b *BeaconState) Eth1Data() *ethpb.Eth1Data { + if !b.hasInnerState() { + return nil + } + if b.state.Eth1Data == nil { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.eth1Data() +} + +// eth1Data corresponding to the proof-of-work chain information stored in the beacon state. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) eth1Data() *ethpb.Eth1Data { + if !b.hasInnerState() { + return nil + } + if b.state.Eth1Data == nil { + return nil + } + + return ethpb.CopyETH1Data(b.state.Eth1Data) +} + +// Eth1DataVotes corresponds to votes from Ethereum on the canonical proof-of-work chain +// data retrieved from eth1. +func (b *BeaconState) Eth1DataVotes() []*ethpb.Eth1Data { + if !b.hasInnerState() { + return nil + } + if b.state.Eth1DataVotes == nil { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.eth1DataVotes() +} + +// eth1DataVotes corresponds to votes from Ethereum on the canonical proof-of-work chain +// data retrieved from eth1. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) eth1DataVotes() []*ethpb.Eth1Data { + if !b.hasInnerState() { + return nil + } + if b.state.Eth1DataVotes == nil { + return nil + } + + res := make([]*ethpb.Eth1Data, len(b.state.Eth1DataVotes)) + for i := 0; i < len(res); i++ { + res[i] = ethpb.CopyETH1Data(b.state.Eth1DataVotes[i]) + } + return res +} + +// Eth1DepositIndex corresponds to the index of the deposit made to the +// validator deposit contract at the time of this state's eth1 data. +func (b *BeaconState) Eth1DepositIndex() uint64 { + if !b.hasInnerState() { + return 0 + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.eth1DepositIndex() +} + +// eth1DepositIndex corresponds to the index of the deposit made to the +// validator deposit contract at the time of this state's eth1 data. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) eth1DepositIndex() uint64 { + if !b.hasInnerState() { + return 0 + } + + return b.state.Eth1DepositIndex +} diff --git a/beacon-chain/state/state-native/v1/getters_misc.go b/beacon-chain/state/state-native/v1/getters_misc.go new file mode 100644 index 0000000000..b7c9cec335 --- /dev/null +++ b/beacon-chain/state/state-native/v1/getters_misc.go @@ -0,0 +1,163 @@ +package v1 + +import ( + types "github.com/prysmaticlabs/eth2-types" + "github.com/prysmaticlabs/prysm/config/params" + "github.com/prysmaticlabs/prysm/encoding/bytesutil" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/runtime/version" +) + +// GenesisTime of the beacon state as a uint64. +func (b *BeaconState) GenesisTime() uint64 { + if !b.hasInnerState() { + return 0 + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.genesisTime() +} + +// genesisTime of the beacon state as a uint64. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) genesisTime() uint64 { + if !b.hasInnerState() { + return 0 + } + + return b.state.GenesisTime +} + +// GenesisValidatorRoot of the beacon state. +func (b *BeaconState) GenesisValidatorRoot() []byte { + if !b.hasInnerState() { + return nil + } + if b.state.GenesisValidatorsRoot == nil { + return params.BeaconConfig().ZeroHash[:] + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.genesisValidatorRoot() +} + +// genesisValidatorRoot of the beacon state. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) genesisValidatorRoot() []byte { + if !b.hasInnerState() { + return nil + } + if b.state.GenesisValidatorsRoot == nil { + return params.BeaconConfig().ZeroHash[:] + } + + root := make([]byte, 32) + copy(root, b.state.GenesisValidatorsRoot) + return root +} + +// Version of the beacon state. This method +// is strictly meant to be used without a lock +// internally. +func (_ *BeaconState) Version() int { + return version.Phase0 +} + +// Slot of the current beacon chain state. +func (b *BeaconState) Slot() types.Slot { + if !b.hasInnerState() { + return 0 + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.slot() +} + +// slot of the current beacon chain state. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) slot() types.Slot { + if !b.hasInnerState() { + return 0 + } + + return b.state.Slot +} + +// Fork version of the beacon chain. +func (b *BeaconState) Fork() *ethpb.Fork { + if !b.hasInnerState() { + return nil + } + if b.state.Fork == nil { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.fork() +} + +// fork version of the beacon chain. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) fork() *ethpb.Fork { + if !b.hasInnerState() { + return nil + } + if b.state.Fork == nil { + return nil + } + + prevVersion := make([]byte, len(b.state.Fork.PreviousVersion)) + copy(prevVersion, b.state.Fork.PreviousVersion) + currVersion := make([]byte, len(b.state.Fork.CurrentVersion)) + copy(currVersion, b.state.Fork.CurrentVersion) + return ðpb.Fork{ + PreviousVersion: prevVersion, + CurrentVersion: currVersion, + Epoch: b.state.Fork.Epoch, + } +} + +// HistoricalRoots based on epochs stored in the beacon state. +func (b *BeaconState) HistoricalRoots() [][]byte { + if !b.hasInnerState() { + return nil + } + if b.state.HistoricalRoots == nil { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.historicalRoots() +} + +// historicalRoots based on epochs stored in the beacon state. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) historicalRoots() [][]byte { + if !b.hasInnerState() { + return nil + } + return bytesutil.SafeCopy2dBytes(b.state.HistoricalRoots) +} + +// balancesLength returns the length of the balances slice. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) balancesLength() int { + if !b.hasInnerState() { + return 0 + } + if b.state.Balances == nil { + return 0 + } + + return len(b.state.Balances) +} diff --git a/beacon-chain/state/state-native/v1/getters_randao.go b/beacon-chain/state/state-native/v1/getters_randao.go new file mode 100644 index 0000000000..b222ceb36b --- /dev/null +++ b/beacon-chain/state/state-native/v1/getters_randao.go @@ -0,0 +1,85 @@ +package v1 + +import ( + "github.com/prysmaticlabs/prysm/encoding/bytesutil" +) + +// RandaoMixes of block proposers on the beacon chain. +func (b *BeaconState) RandaoMixes() [][]byte { + if !b.hasInnerState() { + return nil + } + if b.state.RandaoMixes == nil { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.randaoMixes() +} + +// randaoMixes of block proposers on the beacon chain. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) randaoMixes() [][]byte { + if !b.hasInnerState() { + return nil + } + + return bytesutil.SafeCopy2dBytes(b.state.RandaoMixes) +} + +// RandaoMixAtIndex retrieves a specific block root based on an +// input index value. +func (b *BeaconState) RandaoMixAtIndex(idx uint64) ([]byte, error) { + if !b.hasInnerState() { + return nil, ErrNilInnerState + } + if b.state.RandaoMixes == nil { + return nil, nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.randaoMixAtIndex(idx) +} + +// randaoMixAtIndex retrieves a specific block root based on an +// input index value. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) randaoMixAtIndex(idx uint64) ([]byte, error) { + if !b.hasInnerState() { + return nil, ErrNilInnerState + } + + return bytesutil.SafeCopyRootAtIndex(b.state.RandaoMixes, idx) +} + +// RandaoMixesLength returns the length of the randao mixes slice. +func (b *BeaconState) RandaoMixesLength() int { + if !b.hasInnerState() { + return 0 + } + if b.state.RandaoMixes == nil { + return 0 + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.randaoMixesLength() +} + +// randaoMixesLength returns the length of the randao mixes slice. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) randaoMixesLength() int { + if !b.hasInnerState() { + return 0 + } + if b.state.RandaoMixes == nil { + return 0 + } + + return len(b.state.RandaoMixes) +} diff --git a/beacon-chain/state/state-native/v1/getters_state.go b/beacon-chain/state/state-native/v1/getters_state.go new file mode 100644 index 0000000000..20c435480a --- /dev/null +++ b/beacon-chain/state/state-native/v1/getters_state.go @@ -0,0 +1,123 @@ +package v1 + +import ( + "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/encoding/bytesutil" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" +) + +// InnerStateUnsafe returns the pointer value of the underlying +// beacon state proto object, bypassing immutability. Use with care. +func (b *BeaconState) InnerStateUnsafe() interface{} { + if b == nil { + return nil + } + return b.state +} + +// CloneInnerState the beacon state into a protobuf for usage. +func (b *BeaconState) CloneInnerState() interface{} { + if b == nil || b.state == nil { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + return ðpb.BeaconState{ + GenesisTime: b.genesisTime(), + GenesisValidatorsRoot: b.genesisValidatorRoot(), + Slot: b.slot(), + Fork: b.fork(), + LatestBlockHeader: b.latestBlockHeader(), + BlockRoots: b.blockRoots(), + StateRoots: b.stateRoots(), + HistoricalRoots: b.historicalRoots(), + Eth1Data: b.eth1Data(), + Eth1DataVotes: b.eth1DataVotes(), + Eth1DepositIndex: b.eth1DepositIndex(), + Validators: b.validators(), + Balances: b.balances(), + RandaoMixes: b.randaoMixes(), + Slashings: b.slashings(), + PreviousEpochAttestations: b.previousEpochAttestations(), + CurrentEpochAttestations: b.currentEpochAttestations(), + JustificationBits: b.justificationBits(), + PreviousJustifiedCheckpoint: b.previousJustifiedCheckpoint(), + CurrentJustifiedCheckpoint: b.currentJustifiedCheckpoint(), + FinalizedCheckpoint: b.finalizedCheckpoint(), + } +} + +// hasInnerState detects if the internal reference to the state data structure +// is populated correctly. Returns false if nil. +func (b *BeaconState) hasInnerState() bool { + return b != nil && b.state != nil +} + +// StateRoots kept track of in the beacon state. +func (b *BeaconState) StateRoots() [][]byte { + if !b.hasInnerState() { + return nil + } + if b.state.StateRoots == nil { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.stateRoots() +} + +// StateRoots kept track of in the beacon state. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) stateRoots() [][]byte { + if !b.hasInnerState() { + return nil + } + return bytesutil.SafeCopy2dBytes(b.state.StateRoots) +} + +// StateRootAtIndex retrieves a specific state root based on an +// input index value. +func (b *BeaconState) StateRootAtIndex(idx uint64) ([]byte, error) { + if !b.hasInnerState() { + return nil, ErrNilInnerState + } + if b.state.StateRoots == nil { + return nil, nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.stateRootAtIndex(idx) +} + +// stateRootAtIndex retrieves a specific state root based on an +// input index value. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) stateRootAtIndex(idx uint64) ([]byte, error) { + if !b.hasInnerState() { + return nil, ErrNilInnerState + } + return bytesutil.SafeCopyRootAtIndex(b.state.StateRoots, idx) +} + +// MarshalSSZ marshals the underlying beacon state to bytes. +func (b *BeaconState) MarshalSSZ() ([]byte, error) { + if !b.hasInnerState() { + return nil, errors.New("nil beacon state") + } + return b.state.MarshalSSZ() +} + +// ProtobufBeaconState transforms an input into beacon state in the form of protobuf. +// Error is returned if the input is not type protobuf beacon state. +func ProtobufBeaconState(s interface{}) (*ethpb.BeaconState, error) { + pbState, ok := s.(*ethpb.BeaconState) + if !ok { + return nil, errors.New("input is not type ethpb.BeaconState") + } + return pbState, nil +} diff --git a/beacon-chain/state/state-native/v1/getters_test.go b/beacon-chain/state/state-native/v1/getters_test.go new file mode 100644 index 0000000000..2de87fc41a --- /dev/null +++ b/beacon-chain/state/state-native/v1/getters_test.go @@ -0,0 +1,216 @@ +package v1 + +import ( + "runtime/debug" + "sync" + "testing" + + types "github.com/prysmaticlabs/eth2-types" + fieldparams "github.com/prysmaticlabs/prysm/config/fieldparams" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/testing/assert" + "github.com/prysmaticlabs/prysm/testing/require" +) + +func TestBeaconState_SlotDataRace(t *testing.T) { + headState, err := InitializeFromProto(ðpb.BeaconState{Slot: 1}) + require.NoError(t, err) + + wg := sync.WaitGroup{} + wg.Add(2) + go func() { + require.NoError(t, headState.SetSlot(0)) + wg.Done() + }() + go func() { + headState.Slot() + wg.Done() + }() + + wg.Wait() +} + +func TestNilState_NoPanic(t *testing.T) { + var st *BeaconState + defer func() { + if r := recover(); r != nil { + t.Errorf("Method panicked when it was not supposed to: %v\n%v\n", r, string(debug.Stack())) + } + }() + // retrieve elements from nil state + _ = st.GenesisTime() + _ = st.GenesisValidatorRoot() + _ = st.GenesisValidatorRoot() + _ = st.Slot() + _ = st.Fork() + _ = st.LatestBlockHeader() + _ = st.BlockRoots() + _, err := st.BlockRootAtIndex(0) + _ = err + _ = st.StateRoots() + _ = st.HistoricalRoots() + _ = st.Eth1Data() + _ = st.Eth1DataVotes() + _ = st.Eth1DepositIndex() + _, err = st.ValidatorAtIndex(0) + _ = err + _, err = st.ValidatorAtIndexReadOnly(0) + _ = err + _, _ = st.ValidatorIndexByPubkey([fieldparams.BLSPubkeyLength]byte{}) + _ = st.PubkeyAtIndex(0) + _ = st.NumValidators() + _ = st.Balances() + _, err = st.BalanceAtIndex(0) + _ = err + _ = st.BalancesLength() + _ = st.RandaoMixes() + _, err = st.RandaoMixAtIndex(0) + _ = err + _ = st.RandaoMixesLength() + _ = st.Slashings() + _, err = st.PreviousEpochAttestations() + require.NoError(t, err) + _, err = st.CurrentEpochAttestations() + require.NoError(t, err) + _ = st.JustificationBits() + _ = st.PreviousJustifiedCheckpoint() + _ = st.CurrentJustifiedCheckpoint() + _ = st.FinalizedCheckpoint() +} + +func TestBeaconState_MatchCurrentJustifiedCheckpt(t *testing.T) { + c1 := ðpb.Checkpoint{Epoch: 1} + c2 := ðpb.Checkpoint{Epoch: 2} + beaconState, err := InitializeFromProto(ðpb.BeaconState{CurrentJustifiedCheckpoint: c1}) + require.NoError(t, err) + require.Equal(t, true, beaconState.MatchCurrentJustifiedCheckpoint(c1)) + require.Equal(t, false, beaconState.MatchCurrentJustifiedCheckpoint(c2)) + require.Equal(t, false, beaconState.MatchPreviousJustifiedCheckpoint(c1)) + require.Equal(t, false, beaconState.MatchPreviousJustifiedCheckpoint(c2)) + beaconState.state = nil + require.Equal(t, false, beaconState.MatchCurrentJustifiedCheckpoint(c1)) +} + +func TestBeaconState_MatchPreviousJustifiedCheckpt(t *testing.T) { + c1 := ðpb.Checkpoint{Epoch: 1} + c2 := ðpb.Checkpoint{Epoch: 2} + beaconState, err := InitializeFromProto(ðpb.BeaconState{PreviousJustifiedCheckpoint: c1}) + require.NoError(t, err) + require.NoError(t, err) + require.Equal(t, false, beaconState.MatchCurrentJustifiedCheckpoint(c1)) + require.Equal(t, false, beaconState.MatchCurrentJustifiedCheckpoint(c2)) + require.Equal(t, true, beaconState.MatchPreviousJustifiedCheckpoint(c1)) + require.Equal(t, false, beaconState.MatchPreviousJustifiedCheckpoint(c2)) + beaconState.state = nil + require.Equal(t, false, beaconState.MatchPreviousJustifiedCheckpoint(c1)) +} + +func TestBeaconState_MarshalSSZ_NilState(t *testing.T) { + s, err := InitializeFromProto(ðpb.BeaconState{}) + require.NoError(t, err) + s.state = nil + _, err = s.MarshalSSZ() + require.ErrorContains(t, "nil beacon state", err) +} + +func TestBeaconState_ValidatorByPubkey(t *testing.T) { + keyCreator := func(input []byte) [fieldparams.BLSPubkeyLength]byte { + nKey := [fieldparams.BLSPubkeyLength]byte{} + copy(nKey[:1], input) + return nKey + } + + tests := []struct { + name string + modifyFunc func(b *BeaconState, k [fieldparams.BLSPubkeyLength]byte) + exists bool + expectedIdx types.ValidatorIndex + largestIdxInSet types.ValidatorIndex + }{ + { + name: "retrieve validator", + modifyFunc: func(b *BeaconState, key [fieldparams.BLSPubkeyLength]byte) { + assert.NoError(t, b.AppendValidator(ðpb.Validator{PublicKey: key[:]})) + }, + exists: true, + expectedIdx: 0, + }, + { + name: "retrieve validator with multiple validators from the start", + modifyFunc: func(b *BeaconState, key [fieldparams.BLSPubkeyLength]byte) { + key1 := keyCreator([]byte{'C'}) + key2 := keyCreator([]byte{'D'}) + assert.NoError(t, b.AppendValidator(ðpb.Validator{PublicKey: key[:]})) + assert.NoError(t, b.AppendValidator(ðpb.Validator{PublicKey: key1[:]})) + assert.NoError(t, b.AppendValidator(ðpb.Validator{PublicKey: key2[:]})) + }, + exists: true, + expectedIdx: 0, + }, + { + name: "retrieve validator with multiple validators", + modifyFunc: func(b *BeaconState, key [fieldparams.BLSPubkeyLength]byte) { + key1 := keyCreator([]byte{'C'}) + key2 := keyCreator([]byte{'D'}) + assert.NoError(t, b.AppendValidator(ðpb.Validator{PublicKey: key1[:]})) + assert.NoError(t, b.AppendValidator(ðpb.Validator{PublicKey: key2[:]})) + assert.NoError(t, b.AppendValidator(ðpb.Validator{PublicKey: key[:]})) + }, + exists: true, + expectedIdx: 2, + }, + { + name: "retrieve validator with multiple validators from the start with shared state", + modifyFunc: func(b *BeaconState, key [fieldparams.BLSPubkeyLength]byte) { + key1 := keyCreator([]byte{'C'}) + key2 := keyCreator([]byte{'D'}) + assert.NoError(t, b.AppendValidator(ðpb.Validator{PublicKey: key[:]})) + _ = b.Copy() + assert.NoError(t, b.AppendValidator(ðpb.Validator{PublicKey: key1[:]})) + assert.NoError(t, b.AppendValidator(ðpb.Validator{PublicKey: key2[:]})) + }, + exists: true, + expectedIdx: 0, + }, + { + name: "retrieve validator with multiple validators with shared state", + modifyFunc: func(b *BeaconState, key [fieldparams.BLSPubkeyLength]byte) { + key1 := keyCreator([]byte{'C'}) + key2 := keyCreator([]byte{'D'}) + assert.NoError(t, b.AppendValidator(ðpb.Validator{PublicKey: key1[:]})) + assert.NoError(t, b.AppendValidator(ðpb.Validator{PublicKey: key2[:]})) + n := b.Copy() + // Append to another state + assert.NoError(t, n.AppendValidator(ðpb.Validator{PublicKey: key[:]})) + + }, + exists: false, + expectedIdx: 0, + }, + { + name: "retrieve validator with multiple validators with shared state at boundary", + modifyFunc: func(b *BeaconState, key [fieldparams.BLSPubkeyLength]byte) { + key1 := keyCreator([]byte{'C'}) + assert.NoError(t, b.AppendValidator(ðpb.Validator{PublicKey: key1[:]})) + n := b.Copy() + // Append to another state + assert.NoError(t, n.AppendValidator(ðpb.Validator{PublicKey: key[:]})) + + }, + exists: false, + expectedIdx: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s, err := InitializeFromProto(ðpb.BeaconState{}) + require.NoError(t, err) + nKey := keyCreator([]byte{'A'}) + tt.modifyFunc(s, nKey) + idx, ok := s.ValidatorIndexByPubkey(nKey) + assert.Equal(t, tt.exists, ok) + assert.Equal(t, tt.expectedIdx, idx) + }) + } +} diff --git a/beacon-chain/state/state-native/v1/getters_validator.go b/beacon-chain/state/state-native/v1/getters_validator.go new file mode 100644 index 0000000000..604e112195 --- /dev/null +++ b/beacon-chain/state/state-native/v1/getters_validator.go @@ -0,0 +1,298 @@ +package v1 + +import ( + "fmt" + + "github.com/pkg/errors" + types "github.com/prysmaticlabs/eth2-types" + "github.com/prysmaticlabs/prysm/beacon-chain/state" + fieldparams "github.com/prysmaticlabs/prysm/config/fieldparams" + "github.com/prysmaticlabs/prysm/encoding/bytesutil" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" +) + +// ValidatorIndexOutOfRangeError represents an error scenario where a validator does not exist +// at a given index in the validator's array. +type ValidatorIndexOutOfRangeError struct { + message string +} + +var ( + // ErrNilValidatorsInState returns when accessing validators in the state while the state has a + // nil slice for the validators field. + ErrNilValidatorsInState = errors.New("state has nil validator slice") +) + +// NewValidatorIndexOutOfRangeError creates a new error instance. +func NewValidatorIndexOutOfRangeError(index types.ValidatorIndex) ValidatorIndexOutOfRangeError { + return ValidatorIndexOutOfRangeError{ + message: fmt.Sprintf("index %d out of range", index), + } +} + +// Error returns the underlying error message. +func (e *ValidatorIndexOutOfRangeError) Error() string { + return e.message +} + +// Validators participating in consensus on the beacon chain. +func (b *BeaconState) Validators() []*ethpb.Validator { + if !b.hasInnerState() { + return nil + } + if b.state.Validators == nil { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.validators() +} + +// validators participating in consensus on the beacon chain. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) validators() []*ethpb.Validator { + if !b.hasInnerState() { + return nil + } + if b.state.Validators == nil { + return nil + } + + res := make([]*ethpb.Validator, len(b.state.Validators)) + for i := 0; i < len(res); i++ { + val := b.state.Validators[i] + if val == nil { + continue + } + res[i] = ethpb.CopyValidator(val) + } + return res +} + +// references of validators participating in consensus on the beacon chain. +// This assumes that a lock is already held on BeaconState. This does not +// copy fully and instead just copies the reference. +func (b *BeaconState) validatorsReferences() []*ethpb.Validator { + if !b.hasInnerState() { + return nil + } + if b.state.Validators == nil { + return nil + } + + res := make([]*ethpb.Validator, len(b.state.Validators)) + for i := 0; i < len(res); i++ { + validator := b.state.Validators[i] + if validator == nil { + continue + } + // copy validator reference instead. + res[i] = validator + } + return res +} + +// ValidatorAtIndex is the validator at the provided index. +func (b *BeaconState) ValidatorAtIndex(idx types.ValidatorIndex) (*ethpb.Validator, error) { + if !b.hasInnerState() { + return nil, ErrNilInnerState + } + if b.state.Validators == nil { + return ðpb.Validator{}, nil + } + if uint64(len(b.state.Validators)) <= uint64(idx) { + e := NewValidatorIndexOutOfRangeError(idx) + return nil, &e + } + + b.lock.RLock() + defer b.lock.RUnlock() + + val := b.state.Validators[idx] + return ethpb.CopyValidator(val), nil +} + +// ValidatorAtIndexReadOnly is the validator at the provided index. This method +// doesn't clone the validator. +func (b *BeaconState) ValidatorAtIndexReadOnly(idx types.ValidatorIndex) (state.ReadOnlyValidator, error) { + if !b.hasInnerState() { + return nil, ErrNilInnerState + } + if b.state.Validators == nil { + return nil, ErrNilValidatorsInState + } + if uint64(len(b.state.Validators)) <= uint64(idx) { + e := NewValidatorIndexOutOfRangeError(idx) + return nil, &e + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return NewValidator(b.state.Validators[idx]) +} + +// ValidatorIndexByPubkey returns a given validator by its 48-byte public key. +func (b *BeaconState) ValidatorIndexByPubkey(key [fieldparams.BLSPubkeyLength]byte) (types.ValidatorIndex, bool) { + if b == nil || b.valMapHandler == nil || b.valMapHandler.IsNil() { + return 0, false + } + b.lock.RLock() + defer b.lock.RUnlock() + numOfVals := len(b.state.Validators) + + idx, ok := b.valMapHandler.Get(key) + if ok && numOfVals <= int(idx) { + return types.ValidatorIndex(0), false + } + return idx, ok +} + +// PubkeyAtIndex returns the pubkey at the given +// validator index. +func (b *BeaconState) PubkeyAtIndex(idx types.ValidatorIndex) [fieldparams.BLSPubkeyLength]byte { + if !b.hasInnerState() { + return [fieldparams.BLSPubkeyLength]byte{} + } + if uint64(idx) >= uint64(len(b.state.Validators)) { + return [fieldparams.BLSPubkeyLength]byte{} + } + b.lock.RLock() + defer b.lock.RUnlock() + + if b.state.Validators[idx] == nil { + return [fieldparams.BLSPubkeyLength]byte{} + } + return bytesutil.ToBytes48(b.state.Validators[idx].PublicKey) +} + +// NumValidators returns the size of the validator registry. +func (b *BeaconState) NumValidators() int { + if !b.hasInnerState() { + return 0 + } + b.lock.RLock() + defer b.lock.RUnlock() + + return len(b.state.Validators) +} + +// ReadFromEveryValidator reads values from every validator and applies it to the provided function. +// Warning: This method is potentially unsafe, as it exposes the actual validator registry. +func (b *BeaconState) ReadFromEveryValidator(f func(idx int, val state.ReadOnlyValidator) error) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + if b.state.Validators == nil { + return errors.New("nil validators in state") + } + b.lock.RLock() + validators := b.state.Validators + b.lock.RUnlock() + + for i, v := range validators { + v, err := NewValidator(v) + if err != nil { + return err + } + if err := f(i, v); err != nil { + return err + } + } + return nil +} + +// Balances of validators participating in consensus on the beacon chain. +func (b *BeaconState) Balances() []uint64 { + if !b.hasInnerState() { + return nil + } + if b.state.Balances == nil { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.balances() +} + +// balances of validators participating in consensus on the beacon chain. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) balances() []uint64 { + if !b.hasInnerState() { + return nil + } + if b.state.Balances == nil { + return nil + } + + res := make([]uint64, len(b.state.Balances)) + copy(res, b.state.Balances) + return res +} + +// BalanceAtIndex of validator with the provided index. +func (b *BeaconState) BalanceAtIndex(idx types.ValidatorIndex) (uint64, error) { + if !b.hasInnerState() { + return 0, ErrNilInnerState + } + if b.state.Balances == nil { + return 0, nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + if uint64(len(b.state.Balances)) <= uint64(idx) { + return 0, fmt.Errorf("index of %d does not exist", idx) + } + return b.state.Balances[idx], nil +} + +// BalancesLength returns the length of the balances slice. +func (b *BeaconState) BalancesLength() int { + if !b.hasInnerState() { + return 0 + } + if b.state.Balances == nil { + return 0 + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.balancesLength() +} + +// Slashings of validators on the beacon chain. +func (b *BeaconState) Slashings() []uint64 { + if !b.hasInnerState() { + return nil + } + if b.state.Slashings == nil { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.slashings() +} + +// slashings of validators on the beacon chain. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) slashings() []uint64 { + if !b.hasInnerState() { + return nil + } + if b.state.Slashings == nil { + return nil + } + + res := make([]uint64, len(b.state.Slashings)) + copy(res, b.state.Slashings) + return res +} diff --git a/beacon-chain/state/state-native/v1/getters_validator_test.go b/beacon-chain/state/state-native/v1/getters_validator_test.go new file mode 100644 index 0000000000..6e97f78746 --- /dev/null +++ b/beacon-chain/state/state-native/v1/getters_validator_test.go @@ -0,0 +1,20 @@ +package v1_test + +import ( + "testing" + + v1 "github.com/prysmaticlabs/prysm/beacon-chain/state/state-native/v1" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/testing/assert" + "github.com/prysmaticlabs/prysm/testing/require" +) + +func TestBeaconState_ValidatorAtIndexReadOnly_HandlesNilSlice(t *testing.T) { + st, err := v1.InitializeFromProtoUnsafe(ðpb.BeaconState{ + Validators: nil, + }) + require.NoError(t, err) + + _, err = st.ValidatorAtIndexReadOnly(0) + assert.Equal(t, v1.ErrNilValidatorsInState, err) +} diff --git a/beacon-chain/state/state-native/v1/proofs.go b/beacon-chain/state/state-native/v1/proofs.go new file mode 100644 index 0000000000..5a69f8bcd5 --- /dev/null +++ b/beacon-chain/state/state-native/v1/proofs.go @@ -0,0 +1,54 @@ +package v1 + +import ( + "context" + "encoding/binary" + + "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/beacon-chain/state/state-native/fieldtrie" + "github.com/prysmaticlabs/prysm/encoding/bytesutil" +) + +const ( + finalizedRootIndex = uint64(105) // Precomputed value. +) + +// FinalizedRootGeneralizedIndex for the beacon state. +func FinalizedRootGeneralizedIndex() uint64 { + return finalizedRootIndex +} + +// CurrentSyncCommitteeProof from the state's Merkle trie representation. +func (*BeaconState) CurrentSyncCommitteeProof(_ context.Context) ([][]byte, error) { + return nil, errors.New("CurrentSyncCommitteeProof() unsupported for v1 beacon state") +} + +// NextSyncCommitteeProof from the state's Merkle trie representation. +func (*BeaconState) NextSyncCommitteeProof(_ context.Context) ([][]byte, error) { + return nil, errors.New("NextSyncCommitteeProof() unsupported for v1 beacon state") +} + +// FinalizedRootProof crafts a Merkle proof for the finalized root +// contained within the finalized checkpoint of a beacon state. +func (b *BeaconState) FinalizedRootProof(ctx context.Context) ([][]byte, error) { + b.lock.Lock() + defer b.lock.Unlock() + if err := b.initializeMerkleLayers(ctx); err != nil { + return nil, err + } + if err := b.recomputeDirtyFields(ctx); err != nil { + return nil, err + } + cpt := b.state.FinalizedCheckpoint + // The epoch field of a finalized checkpoint is the neighbor + // index of the finalized root field in its Merkle tree representation + // of the checkpoint. This neighbor is the first element added to the proof. + epochBuf := make([]byte, 8) + binary.LittleEndian.PutUint64(epochBuf, uint64(cpt.Epoch)) + epochRoot := bytesutil.ToBytes32(epochBuf) + proof := make([][]byte, 0) + proof = append(proof, epochRoot[:]) + branch := fieldtrie.ProofFromMerkleLayers(b.merkleLayers, finalizedCheckpoint) + proof = append(proof, branch...) + return proof, nil +} diff --git a/beacon-chain/state/state-native/v1/proofs_test.go b/beacon-chain/state/state-native/v1/proofs_test.go new file mode 100644 index 0000000000..f7778c44f6 --- /dev/null +++ b/beacon-chain/state/state-native/v1/proofs_test.go @@ -0,0 +1,64 @@ +package v1_test + +import ( + "context" + "testing" + + v1 "github.com/prysmaticlabs/prysm/beacon-chain/state/state-native/v1" + "github.com/prysmaticlabs/prysm/container/trie" + "github.com/prysmaticlabs/prysm/testing/require" + "github.com/prysmaticlabs/prysm/testing/util" +) + +func TestBeaconStateMerkleProofs(t *testing.T) { + ctx := context.Background() + st, _ := util.DeterministicGenesisState(t, 256) + htr, err := st.HashTreeRoot(ctx) + require.NoError(t, err) + t.Run("current sync committee", func(t *testing.T) { + _, err := st.CurrentSyncCommitteeProof(ctx) + require.ErrorContains(t, "unsupported", err) + }) + t.Run("next sync committee", func(t *testing.T) { + _, err := st.NextSyncCommitteeProof(ctx) + require.ErrorContains(t, "unsupported", err) + }) + t.Run("finalized root", func(t *testing.T) { + finalizedRoot := st.FinalizedCheckpoint().Root + proof, err := st.FinalizedRootProof(ctx) + require.NoError(t, err) + gIndex := v1.FinalizedRootGeneralizedIndex() + valid := trie.VerifyMerkleProof(htr[:], finalizedRoot, gIndex, proof) + require.Equal(t, true, valid) + }) + t.Run("recomputes root on dirty fields", func(t *testing.T) { + currentRoot, err := st.HashTreeRoot(ctx) + require.NoError(t, err) + cpt := st.FinalizedCheckpoint() + require.NoError(t, err) + + // Edit the checkpoint. + cpt.Epoch = 100 + require.NoError(t, st.SetFinalizedCheckpoint(cpt)) + + // Produce a proof for the finalized root. + proof, err := st.FinalizedRootProof(ctx) + require.NoError(t, err) + + // We expect the previous step to have triggered + // a recomputation of dirty fields in the beacon state, resulting + // in a new hash tree root as the finalized checkpoint had previously + // changed and should have been marked as a dirty state field. + // The proof validity should be false for the old root, but true for the new. + finalizedRoot := st.FinalizedCheckpoint().Root + gIndex := v1.FinalizedRootGeneralizedIndex() + valid := trie.VerifyMerkleProof(currentRoot[:], finalizedRoot, gIndex, proof) + require.Equal(t, false, valid) + + newRoot, err := st.HashTreeRoot(ctx) + require.NoError(t, err) + + valid = trie.VerifyMerkleProof(newRoot[:], finalizedRoot, gIndex, proof) + require.Equal(t, true, valid) + }) +} diff --git a/beacon-chain/state/state-native/v1/readonly_validator.go b/beacon-chain/state/state-native/v1/readonly_validator.go new file mode 100644 index 0000000000..ce82153968 --- /dev/null +++ b/beacon-chain/state/state-native/v1/readonly_validator.go @@ -0,0 +1,89 @@ +package v1 + +import ( + "github.com/pkg/errors" + types "github.com/prysmaticlabs/eth2-types" + "github.com/prysmaticlabs/prysm/beacon-chain/state" + fieldparams "github.com/prysmaticlabs/prysm/config/fieldparams" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" +) + +var ( + // ErrNilWrappedValidator returns when caller attempts to wrap a nil pointer validator. + ErrNilWrappedValidator = errors.New("nil validator cannot be wrapped as readonly") +) + +// readOnlyValidator returns a wrapper that only allows fields from a validator +// to be read, and prevents any modification of internal validator fields. +type readOnlyValidator struct { + validator *ethpb.Validator +} + +var _ = state.ReadOnlyValidator(readOnlyValidator{}) + +// NewValidator initializes the read only wrapper for validator. +func NewValidator(v *ethpb.Validator) (state.ReadOnlyValidator, error) { + rov := readOnlyValidator{ + validator: v, + } + if rov.IsNil() { + return nil, ErrNilWrappedValidator + } + return rov, nil +} + +// EffectiveBalance returns the effective balance of the +// read only validator. +func (v readOnlyValidator) EffectiveBalance() uint64 { + return v.validator.EffectiveBalance +} + +// ActivationEligibilityEpoch returns the activation eligibility epoch of the +// read only validator. +func (v readOnlyValidator) ActivationEligibilityEpoch() types.Epoch { + return v.validator.ActivationEligibilityEpoch +} + +// ActivationEpoch returns the activation epoch of the +// read only validator. +func (v readOnlyValidator) ActivationEpoch() types.Epoch { + return v.validator.ActivationEpoch +} + +// WithdrawableEpoch returns the withdrawable epoch of the +// read only validator. +func (v readOnlyValidator) WithdrawableEpoch() types.Epoch { + return v.validator.WithdrawableEpoch +} + +// ExitEpoch returns the exit epoch of the +// read only validator. +func (v readOnlyValidator) ExitEpoch() types.Epoch { + return v.validator.ExitEpoch +} + +// PublicKey returns the public key of the +// read only validator. +func (v readOnlyValidator) PublicKey() [fieldparams.BLSPubkeyLength]byte { + var pubkey [fieldparams.BLSPubkeyLength]byte + copy(pubkey[:], v.validator.PublicKey) + return pubkey +} + +// WithdrawalCredentials returns the withdrawal credentials of the +// read only validator. +func (v readOnlyValidator) WithdrawalCredentials() []byte { + creds := make([]byte, len(v.validator.WithdrawalCredentials)) + copy(creds, v.validator.WithdrawalCredentials) + return creds +} + +// Slashed returns the read only validator is slashed. +func (v readOnlyValidator) Slashed() bool { + return v.validator.Slashed +} + +// IsNil returns true if the validator is nil. +func (v readOnlyValidator) IsNil() bool { + return v.validator == nil +} diff --git a/beacon-chain/state/state-native/v1/readonly_validator_test.go b/beacon-chain/state/state-native/v1/readonly_validator_test.go new file mode 100644 index 0000000000..1bb79410ac --- /dev/null +++ b/beacon-chain/state/state-native/v1/readonly_validator_test.go @@ -0,0 +1,74 @@ +package v1_test + +import ( + "testing" + + types "github.com/prysmaticlabs/eth2-types" + v1 "github.com/prysmaticlabs/prysm/beacon-chain/state/state-native/v1" + fieldparams "github.com/prysmaticlabs/prysm/config/fieldparams" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/testing/assert" + "github.com/prysmaticlabs/prysm/testing/require" +) + +func TestReadOnlyValidator_ReturnsErrorOnNil(t *testing.T) { + if _, err := v1.NewValidator(nil); err != v1.ErrNilWrappedValidator { + t.Errorf("Wrong error returned. Got %v, wanted %v", err, v1.ErrNilWrappedValidator) + } +} + +func TestReadOnlyValidator_EffectiveBalance(t *testing.T) { + bal := uint64(234) + v, err := v1.NewValidator(ðpb.Validator{EffectiveBalance: bal}) + require.NoError(t, err) + assert.Equal(t, bal, v.EffectiveBalance()) +} + +func TestReadOnlyValidator_ActivationEligibilityEpoch(t *testing.T) { + epoch := types.Epoch(234) + v, err := v1.NewValidator(ðpb.Validator{ActivationEligibilityEpoch: epoch}) + require.NoError(t, err) + assert.Equal(t, epoch, v.ActivationEligibilityEpoch()) +} + +func TestReadOnlyValidator_ActivationEpoch(t *testing.T) { + epoch := types.Epoch(234) + v, err := v1.NewValidator(ðpb.Validator{ActivationEpoch: epoch}) + require.NoError(t, err) + assert.Equal(t, epoch, v.ActivationEpoch()) +} + +func TestReadOnlyValidator_WithdrawableEpoch(t *testing.T) { + epoch := types.Epoch(234) + v, err := v1.NewValidator(ðpb.Validator{WithdrawableEpoch: epoch}) + require.NoError(t, err) + assert.Equal(t, epoch, v.WithdrawableEpoch()) +} + +func TestReadOnlyValidator_ExitEpoch(t *testing.T) { + epoch := types.Epoch(234) + v, err := v1.NewValidator(ðpb.Validator{ExitEpoch: epoch}) + require.NoError(t, err) + assert.Equal(t, epoch, v.ExitEpoch()) +} + +func TestReadOnlyValidator_PublicKey(t *testing.T) { + key := [fieldparams.BLSPubkeyLength]byte{0xFA, 0xCC} + v, err := v1.NewValidator(ðpb.Validator{PublicKey: key[:]}) + require.NoError(t, err) + assert.Equal(t, key, v.PublicKey()) +} + +func TestReadOnlyValidator_WithdrawalCredentials(t *testing.T) { + creds := []byte{0xFA, 0xCC} + v, err := v1.NewValidator(ðpb.Validator{WithdrawalCredentials: creds}) + require.NoError(t, err) + assert.DeepEqual(t, creds, v.WithdrawalCredentials()) +} + +func TestReadOnlyValidator_Slashed(t *testing.T) { + slashed := true + v, err := v1.NewValidator(ðpb.Validator{Slashed: slashed}) + require.NoError(t, err) + assert.Equal(t, slashed, v.Slashed()) +} diff --git a/beacon-chain/state/state-native/v1/references_test.go b/beacon-chain/state/state-native/v1/references_test.go new file mode 100644 index 0000000000..6feb76de3d --- /dev/null +++ b/beacon-chain/state/state-native/v1/references_test.go @@ -0,0 +1,354 @@ +package v1 + +import ( + "reflect" + "runtime" + "runtime/debug" + "testing" + + "github.com/prysmaticlabs/go-bitfield" + "github.com/prysmaticlabs/prysm/beacon-chain/state" + "github.com/prysmaticlabs/prysm/beacon-chain/state/types" + "github.com/prysmaticlabs/prysm/encoding/bytesutil" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/testing/assert" + "github.com/prysmaticlabs/prysm/testing/require" +) + +func TestStateReferenceSharing_Finalizer(t *testing.T) { + // This test showcases the logic on a the RandaoMixes field with the GC finalizer. + + a, err := InitializeFromProtoUnsafe(ðpb.BeaconState{RandaoMixes: [][]byte{[]byte("foo")}}) + require.NoError(t, err) + assert.Equal(t, uint(1), a.sharedFieldReferences[randaoMixes].Refs(), "Expected a single reference for RANDAO mixes") + + func() { + // Create object in a different scope for GC + b := a.Copy() + assert.Equal(t, uint(2), a.sharedFieldReferences[randaoMixes].Refs(), "Expected 2 references to RANDAO mixes") + _ = b + }() + + runtime.GC() // Should run finalizer on object b + assert.Equal(t, uint(1), a.sharedFieldReferences[randaoMixes].Refs(), "Expected 1 shared reference to RANDAO mixes!") + + copied := a.Copy() + b, ok := copied.(*BeaconState) + require.Equal(t, true, ok) + assert.Equal(t, uint(2), b.sharedFieldReferences[randaoMixes].Refs(), "Expected 2 shared references to RANDAO mixes") + require.NoError(t, b.UpdateRandaoMixesAtIndex(0, []byte("bar"))) + if b.sharedFieldReferences[randaoMixes].Refs() != 1 || a.sharedFieldReferences[randaoMixes].Refs() != 1 { + t.Error("Expected 1 shared reference to RANDAO mix for both a and b") + } +} + +func TestStateReferenceCopy_NoUnexpectedRootsMutation(t *testing.T) { + root1, root2 := bytesutil.ToBytes32([]byte("foo")), bytesutil.ToBytes32([]byte("bar")) + a, err := InitializeFromProtoUnsafe(ðpb.BeaconState{ + BlockRoots: [][]byte{ + root1[:], + }, + StateRoots: [][]byte{ + root1[:], + }, + }) + require.NoError(t, err) + assertRefCount(t, a, blockRoots, 1) + assertRefCount(t, a, stateRoots, 1) + + // Copy, increases reference count. + copied := a.Copy() + b, ok := copied.(*BeaconState) + require.Equal(t, true, ok) + assertRefCount(t, a, blockRoots, 2) + assertRefCount(t, a, stateRoots, 2) + assertRefCount(t, b, blockRoots, 2) + assertRefCount(t, b, stateRoots, 2) + assert.Equal(t, 1, len(b.state.GetBlockRoots()), "No block roots found") + assert.Equal(t, 1, len(b.state.GetStateRoots()), "No state roots found") + + // Assert shared state. + blockRootsA := a.state.GetBlockRoots() + stateRootsA := a.state.GetStateRoots() + blockRootsB := b.state.GetBlockRoots() + stateRootsB := b.state.GetStateRoots() + if len(blockRootsA) != len(blockRootsB) || len(blockRootsA) < 1 { + t.Errorf("Unexpected number of block roots, want: %v", 1) + } + if len(stateRootsA) != len(stateRootsB) || len(stateRootsA) < 1 { + t.Errorf("Unexpected number of state roots, want: %v", 1) + } + assertValFound(t, blockRootsA, root1[:]) + assertValFound(t, blockRootsB, root1[:]) + assertValFound(t, stateRootsA, root1[:]) + assertValFound(t, stateRootsB, root1[:]) + + // Mutator should only affect calling state: a. + require.NoError(t, a.UpdateBlockRootAtIndex(0, root2)) + require.NoError(t, a.UpdateStateRootAtIndex(0, root2)) + + // Assert no shared state mutation occurred only on state a (copy on write). + assertValNotFound(t, a.state.GetBlockRoots(), root1[:]) + assertValNotFound(t, a.state.GetStateRoots(), root1[:]) + assertValFound(t, a.state.GetBlockRoots(), root2[:]) + assertValFound(t, a.state.GetStateRoots(), root2[:]) + assertValFound(t, b.state.GetBlockRoots(), root1[:]) + assertValFound(t, b.state.GetStateRoots(), root1[:]) + if len(blockRootsA) != len(blockRootsB) || len(blockRootsA) < 1 { + t.Errorf("Unexpected number of block roots, want: %v", 1) + } + if len(stateRootsA) != len(stateRootsB) || len(stateRootsA) < 1 { + t.Errorf("Unexpected number of state roots, want: %v", 1) + } + assert.DeepEqual(t, root2[:], a.state.GetBlockRoots()[0], "Expected mutation not found") + assert.DeepEqual(t, root2[:], a.state.GetStateRoots()[0], "Expected mutation not found") + assert.DeepEqual(t, root1[:], blockRootsB[0], "Unexpected mutation found") + assert.DeepEqual(t, root1[:], stateRootsB[0], "Unexpected mutation found") + + // Copy on write happened, reference counters are reset. + assertRefCount(t, a, blockRoots, 1) + assertRefCount(t, a, stateRoots, 1) + assertRefCount(t, b, blockRoots, 1) + assertRefCount(t, b, stateRoots, 1) +} + +func TestStateReferenceCopy_NoUnexpectedRandaoMutation(t *testing.T) { + + val1, val2 := []byte("foo"), []byte("bar") + a, err := InitializeFromProtoUnsafe(ðpb.BeaconState{ + RandaoMixes: [][]byte{ + val1, + }, + }) + require.NoError(t, err) + assertRefCount(t, a, randaoMixes, 1) + + // Copy, increases reference count. + copied := a.Copy() + b, ok := copied.(*BeaconState) + require.Equal(t, true, ok) + assertRefCount(t, a, randaoMixes, 2) + assertRefCount(t, b, randaoMixes, 2) + assert.Equal(t, 1, len(b.state.GetRandaoMixes()), "No randao mixes found") + + // Assert shared state. + mixesA := a.state.GetRandaoMixes() + mixesB := b.state.GetRandaoMixes() + if len(mixesA) != len(mixesB) || len(mixesA) < 1 { + t.Errorf("Unexpected number of mix values, want: %v", 1) + } + assertValFound(t, mixesA, val1) + assertValFound(t, mixesB, val1) + + // Mutator should only affect calling state: a. + require.NoError(t, a.UpdateRandaoMixesAtIndex(0, val2)) + + // Assert no shared state mutation occurred only on state a (copy on write). + if len(mixesA) != len(mixesB) || len(mixesA) < 1 { + t.Errorf("Unexpected number of mix values, want: %v", 1) + } + assertValFound(t, a.state.GetRandaoMixes(), val2) + assertValNotFound(t, a.state.GetRandaoMixes(), val1) + assertValFound(t, b.state.GetRandaoMixes(), val1) + assertValNotFound(t, b.state.GetRandaoMixes(), val2) + assertValFound(t, mixesB, val1) + assertValNotFound(t, mixesB, val2) + assert.DeepEqual(t, val2, a.state.GetRandaoMixes()[0], "Expected mutation not found") + assert.DeepEqual(t, val1, mixesB[0], "Unexpected mutation found") + + // Copy on write happened, reference counters are reset. + assertRefCount(t, a, randaoMixes, 1) + assertRefCount(t, b, randaoMixes, 1) +} + +func TestStateReferenceCopy_NoUnexpectedAttestationsMutation(t *testing.T) { + assertAttFound := func(vals []*ethpb.PendingAttestation, val uint64) { + for i := range vals { + if reflect.DeepEqual(vals[i].AggregationBits, bitfield.NewBitlist(val)) { + return + } + } + t.Log(string(debug.Stack())) + t.Fatalf("Expected attestation not found (%v), want: %v", vals, val) + } + assertAttNotFound := func(vals []*ethpb.PendingAttestation, val uint64) { + for i := range vals { + if reflect.DeepEqual(vals[i].AggregationBits, bitfield.NewBitlist(val)) { + t.Log(string(debug.Stack())) + t.Fatalf("Unexpected attestation found (%v): %v", vals, val) + return + } + } + } + + a, err := InitializeFromProtoUnsafe(ðpb.BeaconState{}) + require.NoError(t, err) + assertRefCount(t, a, previousEpochAttestations, 1) + assertRefCount(t, a, currentEpochAttestations, 1) + + // Update initial state. + atts := []*ethpb.PendingAttestation{ + {AggregationBits: bitfield.NewBitlist(1)}, + {AggregationBits: bitfield.NewBitlist(2)}, + } + a.setPreviousEpochAttestations(atts[:1]) + a.setCurrentEpochAttestations(atts[:1]) + curAtt, err := a.CurrentEpochAttestations() + require.NoError(t, err) + assert.Equal(t, 1, len(curAtt), "Unexpected number of attestations") + preAtt, err := a.PreviousEpochAttestations() + require.NoError(t, err) + assert.Equal(t, 1, len(preAtt), "Unexpected number of attestations") + + // Copy, increases reference count. + copied := a.Copy() + b, ok := copied.(*BeaconState) + require.Equal(t, true, ok) + assertRefCount(t, a, previousEpochAttestations, 2) + assertRefCount(t, a, currentEpochAttestations, 2) + assertRefCount(t, b, previousEpochAttestations, 2) + assertRefCount(t, b, currentEpochAttestations, 2) + assert.Equal(t, 1, len(b.state.GetPreviousEpochAttestations()), "Unexpected number of attestations") + assert.Equal(t, 1, len(b.state.GetCurrentEpochAttestations()), "Unexpected number of attestations") + + // Assert shared state. + curAttsA := a.state.GetCurrentEpochAttestations() + prevAttsA := a.state.GetPreviousEpochAttestations() + curAttsB := b.state.GetCurrentEpochAttestations() + prevAttsB := b.state.GetPreviousEpochAttestations() + if len(curAttsA) != len(curAttsB) || len(curAttsA) < 1 { + t.Errorf("Unexpected number of attestations, want: %v", 1) + } + if len(prevAttsA) != len(prevAttsB) || len(prevAttsA) < 1 { + t.Errorf("Unexpected number of attestations, want: %v", 1) + } + assertAttFound(curAttsA, 1) + assertAttFound(prevAttsA, 1) + assertAttFound(curAttsB, 1) + assertAttFound(prevAttsB, 1) + + // Extends state a attestations. + require.NoError(t, a.AppendCurrentEpochAttestations(atts[1])) + require.NoError(t, a.AppendPreviousEpochAttestations(atts[1])) + curAtt, err = a.CurrentEpochAttestations() + require.NoError(t, err) + assert.Equal(t, 2, len(curAtt), "Unexpected number of attestations") + preAtt, err = a.PreviousEpochAttestations() + require.NoError(t, err) + assert.Equal(t, 2, len(preAtt), "Unexpected number of attestations") + assertAttFound(a.state.GetCurrentEpochAttestations(), 1) + assertAttFound(a.state.GetPreviousEpochAttestations(), 1) + assertAttFound(a.state.GetCurrentEpochAttestations(), 2) + assertAttFound(a.state.GetPreviousEpochAttestations(), 2) + assertAttFound(b.state.GetCurrentEpochAttestations(), 1) + assertAttFound(b.state.GetPreviousEpochAttestations(), 1) + assertAttNotFound(b.state.GetCurrentEpochAttestations(), 2) + assertAttNotFound(b.state.GetPreviousEpochAttestations(), 2) + + // Mutator should only affect calling state: a. + applyToEveryAttestation := func(state *ethpb.BeaconState) { + // One MUST copy on write. + atts = make([]*ethpb.PendingAttestation, len(state.CurrentEpochAttestations)) + copy(atts, state.CurrentEpochAttestations) + state.CurrentEpochAttestations = atts + for i := range state.GetCurrentEpochAttestations() { + att := ethpb.CopyPendingAttestation(state.CurrentEpochAttestations[i]) + att.AggregationBits = bitfield.NewBitlist(3) + state.CurrentEpochAttestations[i] = att + } + + atts = make([]*ethpb.PendingAttestation, len(state.PreviousEpochAttestations)) + copy(atts, state.PreviousEpochAttestations) + state.PreviousEpochAttestations = atts + for i := range state.GetPreviousEpochAttestations() { + att := ethpb.CopyPendingAttestation(state.PreviousEpochAttestations[i]) + att.AggregationBits = bitfield.NewBitlist(3) + state.PreviousEpochAttestations[i] = att + } + } + applyToEveryAttestation(a.state) + + // Assert no shared state mutation occurred only on state a (copy on write). + assertAttFound(a.state.GetCurrentEpochAttestations(), 3) + assertAttFound(a.state.GetPreviousEpochAttestations(), 3) + assertAttNotFound(a.state.GetCurrentEpochAttestations(), 1) + assertAttNotFound(a.state.GetPreviousEpochAttestations(), 1) + assertAttNotFound(a.state.GetCurrentEpochAttestations(), 2) + assertAttNotFound(a.state.GetPreviousEpochAttestations(), 2) + // State b must be unaffected. + assertAttNotFound(b.state.GetCurrentEpochAttestations(), 3) + assertAttNotFound(b.state.GetPreviousEpochAttestations(), 3) + assertAttFound(b.state.GetCurrentEpochAttestations(), 1) + assertAttFound(b.state.GetPreviousEpochAttestations(), 1) + assertAttNotFound(b.state.GetCurrentEpochAttestations(), 2) + assertAttNotFound(b.state.GetPreviousEpochAttestations(), 2) + + // Copy on write happened, reference counters are reset. + assertRefCount(t, a, currentEpochAttestations, 1) + assertRefCount(t, b, currentEpochAttestations, 1) + assertRefCount(t, a, previousEpochAttestations, 1) + assertRefCount(t, b, previousEpochAttestations, 1) +} + +func TestValidatorReferences_RemainsConsistent(t *testing.T) { + a, err := InitializeFromProtoUnsafe(ðpb.BeaconState{ + Validators: []*ethpb.Validator{ + {PublicKey: []byte{'A'}}, + {PublicKey: []byte{'B'}}, + {PublicKey: []byte{'C'}}, + {PublicKey: []byte{'D'}}, + {PublicKey: []byte{'E'}}, + }, + }) + require.NoError(t, err) + + // Create a second state. + copied := a.Copy() + b, ok := copied.(*BeaconState) + require.Equal(t, true, ok) + + // Update First Validator. + assert.NoError(t, a.UpdateValidatorAtIndex(0, ðpb.Validator{PublicKey: []byte{'Z'}})) + + assert.DeepNotEqual(t, a.state.Validators[0], b.state.Validators[0], "validators are equal when they are supposed to be different") + // Modify all validators from copied state. + assert.NoError(t, b.ApplyToEveryValidator(func(idx int, val *ethpb.Validator) (bool, *ethpb.Validator, error) { + return true, ðpb.Validator{PublicKey: []byte{'V'}}, nil + })) + + // Ensure reference is properly accounted for. + assert.NoError(t, a.ReadFromEveryValidator(func(idx int, val state.ReadOnlyValidator) error { + assert.NotEqual(t, bytesutil.ToBytes48([]byte{'V'}), val.PublicKey()) + return nil + })) +} + +// assertRefCount checks whether reference count for a given state +// at a given index is equal to expected amount. +func assertRefCount(t *testing.T, b *BeaconState, idx types.FieldIndex, want uint) { + if cnt := b.sharedFieldReferences[idx].Refs(); cnt != want { + t.Errorf("Unexpected count of references for index %d, want: %v, got: %v", idx, want, cnt) + } +} + +// assertValFound checks whether item with a given value exists in list. +func assertValFound(t *testing.T, vals [][]byte, val []byte) { + for i := range vals { + if reflect.DeepEqual(vals[i], val) { + return + } + } + t.Log(string(debug.Stack())) + t.Fatalf("Expected value not found (%v), want: %v", vals, val) +} + +// assertValNotFound checks whether item with a given value doesn't exist in list. +func assertValNotFound(t *testing.T, vals [][]byte, val []byte) { + for i := range vals { + if reflect.DeepEqual(vals[i], val) { + t.Log(string(debug.Stack())) + t.Errorf("Unexpected value found (%v),: %v", vals, val) + return + } + } +} diff --git a/beacon-chain/state/state-native/v1/setters_attestation.go b/beacon-chain/state/state-native/v1/setters_attestation.go new file mode 100644 index 0000000000..7b3fd41e53 --- /dev/null +++ b/beacon-chain/state/state-native/v1/setters_attestation.go @@ -0,0 +1,98 @@ +package v1 + +import ( + "fmt" + + "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" + fieldparams "github.com/prysmaticlabs/prysm/config/fieldparams" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" +) + +// RotateAttestations sets the previous epoch attestations to the current epoch attestations and +// then clears the current epoch attestations. +func (b *BeaconState) RotateAttestations() error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.setPreviousEpochAttestations(b.currentEpochAttestations()) + b.setCurrentEpochAttestations([]*ethpb.PendingAttestation{}) + return nil +} + +func (b *BeaconState) setPreviousEpochAttestations(val []*ethpb.PendingAttestation) { + b.sharedFieldReferences[previousEpochAttestations].MinusRef() + b.sharedFieldReferences[previousEpochAttestations] = stateutil.NewRef(1) + + b.state.PreviousEpochAttestations = val + b.markFieldAsDirty(previousEpochAttestations) + b.rebuildTrie[previousEpochAttestations] = true +} + +func (b *BeaconState) setCurrentEpochAttestations(val []*ethpb.PendingAttestation) { + b.sharedFieldReferences[currentEpochAttestations].MinusRef() + b.sharedFieldReferences[currentEpochAttestations] = stateutil.NewRef(1) + + b.state.CurrentEpochAttestations = val + b.markFieldAsDirty(currentEpochAttestations) + b.rebuildTrie[currentEpochAttestations] = true +} + +// AppendCurrentEpochAttestations for the beacon state. Appends the new value +// to the the end of list. +func (b *BeaconState) AppendCurrentEpochAttestations(val *ethpb.PendingAttestation) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + atts := b.state.CurrentEpochAttestations + max := uint64(fieldparams.CurrentEpochAttestationsLength) + if uint64(len(atts)) >= max { + return fmt.Errorf("current pending attestation exceeds max length %d", max) + } + + if b.sharedFieldReferences[currentEpochAttestations].Refs() > 1 { + // Copy elements in underlying array by reference. + atts = make([]*ethpb.PendingAttestation, len(b.state.CurrentEpochAttestations)) + copy(atts, b.state.CurrentEpochAttestations) + b.sharedFieldReferences[currentEpochAttestations].MinusRef() + b.sharedFieldReferences[currentEpochAttestations] = stateutil.NewRef(1) + } + + b.state.CurrentEpochAttestations = append(atts, val) + b.markFieldAsDirty(currentEpochAttestations) + b.addDirtyIndices(currentEpochAttestations, []uint64{uint64(len(b.state.CurrentEpochAttestations) - 1)}) + return nil +} + +// AppendPreviousEpochAttestations for the beacon state. Appends the new value +// to the the end of list. +func (b *BeaconState) AppendPreviousEpochAttestations(val *ethpb.PendingAttestation) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + atts := b.state.PreviousEpochAttestations + max := uint64(fieldparams.PreviousEpochAttestationsLength) + if uint64(len(atts)) >= max { + return fmt.Errorf("previous pending attestation exceeds max length %d", max) + } + + if b.sharedFieldReferences[previousEpochAttestations].Refs() > 1 { + atts = make([]*ethpb.PendingAttestation, len(b.state.PreviousEpochAttestations)) + copy(atts, b.state.PreviousEpochAttestations) + b.sharedFieldReferences[previousEpochAttestations].MinusRef() + b.sharedFieldReferences[previousEpochAttestations] = stateutil.NewRef(1) + } + + b.state.PreviousEpochAttestations = append(atts, val) + b.markFieldAsDirty(previousEpochAttestations) + b.addDirtyIndices(previousEpochAttestations, []uint64{uint64(len(b.state.PreviousEpochAttestations) - 1)}) + return nil +} diff --git a/beacon-chain/state/state-native/v1/setters_attestation_test.go b/beacon-chain/state/state-native/v1/setters_attestation_test.go new file mode 100644 index 0000000000..46fcd3d3e6 --- /dev/null +++ b/beacon-chain/state/state-native/v1/setters_attestation_test.go @@ -0,0 +1,72 @@ +package v1 + +import ( + "context" + "testing" + + types "github.com/prysmaticlabs/eth2-types" + stateTypes "github.com/prysmaticlabs/prysm/beacon-chain/state/types" + "github.com/prysmaticlabs/prysm/config/params" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/testing/assert" + "github.com/prysmaticlabs/prysm/testing/require" +) + +func TestBeaconState_RotateAttestations(t *testing.T) { + st, err := InitializeFromProto(ðpb.BeaconState{ + Slot: 1, + CurrentEpochAttestations: []*ethpb.PendingAttestation{{Data: ðpb.AttestationData{Slot: 456}}}, + PreviousEpochAttestations: []*ethpb.PendingAttestation{{Data: ðpb.AttestationData{Slot: 123}}}, + }) + require.NoError(t, err) + + require.NoError(t, st.RotateAttestations()) + require.Equal(t, 0, len(st.currentEpochAttestations())) + require.Equal(t, types.Slot(456), st.previousEpochAttestations()[0].Data.Slot) +} + +func TestAppendBeyondIndicesLimit(t *testing.T) { + zeroHash := params.BeaconConfig().ZeroHash + mockblockRoots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot) + for i := 0; i < len(mockblockRoots); i++ { + mockblockRoots[i] = zeroHash[:] + } + + mockstateRoots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot) + for i := 0; i < len(mockstateRoots); i++ { + mockstateRoots[i] = zeroHash[:] + } + mockrandaoMixes := make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector) + for i := 0; i < len(mockrandaoMixes); i++ { + mockrandaoMixes[i] = zeroHash[:] + } + st, err := InitializeFromProto(ðpb.BeaconState{ + Slot: 1, + CurrentEpochAttestations: []*ethpb.PendingAttestation{{Data: ðpb.AttestationData{Slot: 456}}}, + PreviousEpochAttestations: []*ethpb.PendingAttestation{{Data: ðpb.AttestationData{Slot: 123}}}, + Validators: []*ethpb.Validator{}, + Eth1Data: ðpb.Eth1Data{}, + BlockRoots: mockblockRoots, + StateRoots: mockstateRoots, + RandaoMixes: mockrandaoMixes, + }) + require.NoError(t, err) + _, err = st.HashTreeRoot(context.Background()) + require.NoError(t, err) + for i := stateTypes.FieldIndex(0); i < stateTypes.FieldIndex(params.BeaconConfig().BeaconStateFieldCount); i++ { + st.dirtyFields[i] = true + } + _, err = st.HashTreeRoot(context.Background()) + require.NoError(t, err) + for i := 0; i < 10; i++ { + assert.NoError(t, st.AppendValidator(ðpb.Validator{})) + } + assert.Equal(t, false, st.rebuildTrie[validators]) + assert.NotEqual(t, len(st.dirtyIndices[validators]), 0) + + for i := 0; i < indicesLimit; i++ { + assert.NoError(t, st.AppendValidator(ðpb.Validator{})) + } + assert.Equal(t, true, st.rebuildTrie[validators]) + assert.Equal(t, len(st.dirtyIndices[validators]), 0) +} diff --git a/beacon-chain/state/state-native/v1/setters_block.go b/beacon-chain/state/state-native/v1/setters_block.go new file mode 100644 index 0000000000..78195cc372 --- /dev/null +++ b/beacon-chain/state/state-native/v1/setters_block.go @@ -0,0 +1,68 @@ +package v1 + +import ( + "fmt" + + "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" +) + +// SetLatestBlockHeader in the beacon state. +func (b *BeaconState) SetLatestBlockHeader(val *ethpb.BeaconBlockHeader) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.state.LatestBlockHeader = ethpb.CopyBeaconBlockHeader(val) + b.markFieldAsDirty(latestBlockHeader) + return nil +} + +// SetBlockRoots for the beacon state. Updates the entire +// list to a new value by overwriting the previous one. +func (b *BeaconState) SetBlockRoots(val [][]byte) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.sharedFieldReferences[blockRoots].MinusRef() + b.sharedFieldReferences[blockRoots] = stateutil.NewRef(1) + + b.state.BlockRoots = val + b.markFieldAsDirty(blockRoots) + b.rebuildTrie[blockRoots] = true + return nil +} + +// UpdateBlockRootAtIndex for the beacon state. Updates the block root +// at a specific index to a new value. +func (b *BeaconState) UpdateBlockRootAtIndex(idx uint64, blockRoot [32]byte) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + if uint64(len(b.state.BlockRoots)) <= idx { + return fmt.Errorf("invalid index provided %d", idx) + } + b.lock.Lock() + defer b.lock.Unlock() + + r := b.state.BlockRoots + if ref := b.sharedFieldReferences[blockRoots]; ref.Refs() > 1 { + // Copy elements in underlying array by reference. + r = make([][]byte, len(b.state.BlockRoots)) + copy(r, b.state.BlockRoots) + ref.MinusRef() + b.sharedFieldReferences[blockRoots] = stateutil.NewRef(1) + } + + r[idx] = blockRoot[:] + b.state.BlockRoots = r + + b.markFieldAsDirty(blockRoots) + b.addDirtyIndices(blockRoots, []uint64{idx}) + return nil +} diff --git a/beacon-chain/state/state-native/v1/setters_checkpoint.go b/beacon-chain/state/state-native/v1/setters_checkpoint.go new file mode 100644 index 0000000000..bf84053ee3 --- /dev/null +++ b/beacon-chain/state/state-native/v1/setters_checkpoint.go @@ -0,0 +1,58 @@ +package v1 + +import ( + "github.com/prysmaticlabs/go-bitfield" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" +) + +// SetJustificationBits for the beacon state. +func (b *BeaconState) SetJustificationBits(val bitfield.Bitvector4) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.state.JustificationBits = val + b.markFieldAsDirty(justificationBits) + return nil +} + +// SetPreviousJustifiedCheckpoint for the beacon state. +func (b *BeaconState) SetPreviousJustifiedCheckpoint(val *ethpb.Checkpoint) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.state.PreviousJustifiedCheckpoint = val + b.markFieldAsDirty(previousJustifiedCheckpoint) + return nil +} + +// SetCurrentJustifiedCheckpoint for the beacon state. +func (b *BeaconState) SetCurrentJustifiedCheckpoint(val *ethpb.Checkpoint) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.state.CurrentJustifiedCheckpoint = val + b.markFieldAsDirty(currentJustifiedCheckpoint) + return nil +} + +// SetFinalizedCheckpoint for the beacon state. +func (b *BeaconState) SetFinalizedCheckpoint(val *ethpb.Checkpoint) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.state.FinalizedCheckpoint = val + b.markFieldAsDirty(finalizedCheckpoint) + return nil +} diff --git a/beacon-chain/state/state-native/v1/setters_eth1.go b/beacon-chain/state/state-native/v1/setters_eth1.go new file mode 100644 index 0000000000..53a5683668 --- /dev/null +++ b/beacon-chain/state/state-native/v1/setters_eth1.go @@ -0,0 +1,74 @@ +package v1 + +import ( + "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" +) + +// SetEth1Data for the beacon state. +func (b *BeaconState) SetEth1Data(val *ethpb.Eth1Data) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.state.Eth1Data = val + b.markFieldAsDirty(eth1Data) + return nil +} + +// SetEth1DataVotes for the beacon state. Updates the entire +// list to a new value by overwriting the previous one. +func (b *BeaconState) SetEth1DataVotes(val []*ethpb.Eth1Data) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.sharedFieldReferences[eth1DataVotes].MinusRef() + b.sharedFieldReferences[eth1DataVotes] = stateutil.NewRef(1) + + b.state.Eth1DataVotes = val + b.markFieldAsDirty(eth1DataVotes) + b.rebuildTrie[eth1DataVotes] = true + return nil +} + +// SetEth1DepositIndex for the beacon state. +func (b *BeaconState) SetEth1DepositIndex(val uint64) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.state.Eth1DepositIndex = val + b.markFieldAsDirty(eth1DepositIndex) + return nil +} + +// AppendEth1DataVotes for the beacon state. Appends the new value +// to the the end of list. +func (b *BeaconState) AppendEth1DataVotes(val *ethpb.Eth1Data) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + votes := b.state.Eth1DataVotes + if b.sharedFieldReferences[eth1DataVotes].Refs() > 1 { + // Copy elements in underlying array by reference. + votes = make([]*ethpb.Eth1Data, len(b.state.Eth1DataVotes)) + copy(votes, b.state.Eth1DataVotes) + b.sharedFieldReferences[eth1DataVotes].MinusRef() + b.sharedFieldReferences[eth1DataVotes] = stateutil.NewRef(1) + } + + b.state.Eth1DataVotes = append(votes, val) + b.markFieldAsDirty(eth1DataVotes) + b.addDirtyIndices(eth1DataVotes, []uint64{uint64(len(b.state.Eth1DataVotes) - 1)}) + return nil +} diff --git a/beacon-chain/state/state-native/v1/setters_misc.go b/beacon-chain/state/state-native/v1/setters_misc.go new file mode 100644 index 0000000000..e6b89626b3 --- /dev/null +++ b/beacon-chain/state/state-native/v1/setters_misc.go @@ -0,0 +1,187 @@ +package v1 + +import ( + "github.com/pkg/errors" + types "github.com/prysmaticlabs/eth2-types" + "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" + stateTypes "github.com/prysmaticlabs/prysm/beacon-chain/state/types" + "github.com/prysmaticlabs/prysm/config/features" + "github.com/prysmaticlabs/prysm/crypto/hash" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" + "google.golang.org/protobuf/proto" +) + +// For our setters, we have a field reference counter through +// which we can track shared field references. This helps when +// performing state copies, as we simply copy the reference to the +// field. When we do need to modify these fields, we +// perform a full copy of the field. This is true of most of our +// fields except for the following below. +// 1) BlockRoots +// 2) StateRoots +// 3) Eth1DataVotes +// 4) RandaoMixes +// 5) HistoricalRoots +// 6) CurrentEpochAttestations +// 7) PreviousEpochAttestations +// 8) Validators +// +// The fields referred to above are instead copied by reference, where +// we simply copy the reference to the underlying object instead of the +// whole object. This is possible due to how we have structured our state +// as we copy the value on read, so as to ensure the underlying object is +// not mutated while it is being accessed during a state read. + +const ( + // This specifies the limit till which we process all dirty indices for a certain field. + // If we have more dirty indices than the threshold, then we rebuild the whole trie. This + // comes due to the fact that O(alogn) > O(n) beyond a certain value of a. + indicesLimit = 8000 +) + +// SetGenesisTime for the beacon state. +func (b *BeaconState) SetGenesisTime(val uint64) error { + b.lock.Lock() + defer b.lock.Unlock() + + b.state.GenesisTime = val + b.markFieldAsDirty(genesisTime) + return nil +} + +// SetGenesisValidatorRoot for the beacon state. +func (b *BeaconState) SetGenesisValidatorRoot(val []byte) error { + b.lock.Lock() + defer b.lock.Unlock() + + b.state.GenesisValidatorsRoot = val + b.markFieldAsDirty(genesisValidatorRoot) + return nil +} + +// SetSlot for the beacon state. +func (b *BeaconState) SetSlot(val types.Slot) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.state.Slot = val + b.markFieldAsDirty(slot) + return nil +} + +// SetFork version for the beacon chain. +func (b *BeaconState) SetFork(val *ethpb.Fork) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + fk, ok := proto.Clone(val).(*ethpb.Fork) + if !ok { + return errors.New("proto.Clone did not return a fork proto") + } + b.state.Fork = fk + b.markFieldAsDirty(fork) + return nil +} + +// SetHistoricalRoots for the beacon state. Updates the entire +// list to a new value by overwriting the previous one. +func (b *BeaconState) SetHistoricalRoots(val [][]byte) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.sharedFieldReferences[historicalRoots].MinusRef() + b.sharedFieldReferences[historicalRoots] = stateutil.NewRef(1) + + b.state.HistoricalRoots = val + b.markFieldAsDirty(historicalRoots) + return nil +} + +// AppendHistoricalRoots for the beacon state. Appends the new value +// to the the end of list. +func (b *BeaconState) AppendHistoricalRoots(root [32]byte) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + roots := b.state.HistoricalRoots + if b.sharedFieldReferences[historicalRoots].Refs() > 1 { + roots = make([][]byte, len(b.state.HistoricalRoots)) + copy(roots, b.state.HistoricalRoots) + b.sharedFieldReferences[historicalRoots].MinusRef() + b.sharedFieldReferences[historicalRoots] = stateutil.NewRef(1) + } + + b.state.HistoricalRoots = append(roots, root[:]) + b.markFieldAsDirty(historicalRoots) + return nil +} + +// Recomputes the branch up the index in the Merkle trie representation +// of the beacon state. This method performs slice reads and the caller MUST +// hold the lock before calling this method. +func (b *BeaconState) recomputeRoot(idx int) { + hashFunc := hash.CustomSHA256Hasher() + layers := b.merkleLayers + // The merkle tree structure looks as follows: + // [[r1, r2, r3, r4], [parent1, parent2], [root]] + // Using information about the index which changed, idx, we recompute + // only its branch up the tree. + currentIndex := idx + root := b.merkleLayers[0][idx] + for i := 0; i < len(layers)-1; i++ { + isLeft := currentIndex%2 == 0 + neighborIdx := currentIndex ^ 1 + + neighbor := make([]byte, 32) + if layers[i] != nil && len(layers[i]) != 0 && neighborIdx < len(layers[i]) { + neighbor = layers[i][neighborIdx] + } + if isLeft { + parentHash := hashFunc(append(root, neighbor...)) + root = parentHash[:] + } else { + parentHash := hashFunc(append(neighbor, root...)) + root = parentHash[:] + } + parentIdx := currentIndex / 2 + // Update the cached layers at the parent index. + layers[i+1][parentIdx] = root + currentIndex = parentIdx + } + b.merkleLayers = layers +} + +func (b *BeaconState) markFieldAsDirty(field stateTypes.FieldIndex) { + b.dirtyFields[field] = true +} + +// addDirtyIndices adds the relevant dirty field indices, so that they +// can be recomputed. +func (b *BeaconState) addDirtyIndices(index stateTypes.FieldIndex, indices []uint64) { + if b.rebuildTrie[index] { + return + } + // Exit early if balance trie computation isn't enabled. + if !features.Get().EnableBalanceTrieComputation && index == balances { + return + } + totalIndicesLen := len(b.dirtyIndices[index]) + len(indices) + if totalIndicesLen > indicesLimit { + b.rebuildTrie[index] = true + b.dirtyIndices[index] = []uint64{} + } else { + b.dirtyIndices[index] = append(b.dirtyIndices[index], indices...) + } +} diff --git a/beacon-chain/state/state-native/v1/setters_randao.go b/beacon-chain/state/state-native/v1/setters_randao.go new file mode 100644 index 0000000000..1f1f337b17 --- /dev/null +++ b/beacon-chain/state/state-native/v1/setters_randao.go @@ -0,0 +1,53 @@ +package v1 + +import ( + "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" +) + +// SetRandaoMixes for the beacon state. Updates the entire +// randao mixes to a new value by overwriting the previous one. +func (b *BeaconState) SetRandaoMixes(val [][]byte) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.sharedFieldReferences[randaoMixes].MinusRef() + b.sharedFieldReferences[randaoMixes] = stateutil.NewRef(1) + + b.state.RandaoMixes = val + b.markFieldAsDirty(randaoMixes) + b.rebuildTrie[randaoMixes] = true + return nil +} + +// UpdateRandaoMixesAtIndex for the beacon state. Updates the randao mixes +// at a specific index to a new value. +func (b *BeaconState) UpdateRandaoMixesAtIndex(idx uint64, val []byte) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + if uint64(len(b.state.RandaoMixes)) <= idx { + return errors.Errorf("invalid index provided %d", idx) + } + b.lock.Lock() + defer b.lock.Unlock() + + mixes := b.state.RandaoMixes + if refs := b.sharedFieldReferences[randaoMixes].Refs(); refs > 1 { + // Copy elements in underlying array by reference. + mixes = make([][]byte, len(b.state.RandaoMixes)) + copy(mixes, b.state.RandaoMixes) + b.sharedFieldReferences[randaoMixes].MinusRef() + b.sharedFieldReferences[randaoMixes] = stateutil.NewRef(1) + } + + mixes[idx] = val + b.state.RandaoMixes = mixes + b.markFieldAsDirty(randaoMixes) + b.addDirtyIndices(randaoMixes, []uint64{idx}) + + return nil +} diff --git a/beacon-chain/state/state-native/v1/setters_state.go b/beacon-chain/state/state-native/v1/setters_state.go new file mode 100644 index 0000000000..1a7d93fad7 --- /dev/null +++ b/beacon-chain/state/state-native/v1/setters_state.go @@ -0,0 +1,59 @@ +package v1 + +import ( + "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" +) + +// SetStateRoots for the beacon state. Updates the state roots +// to a new value by overwriting the previous value. +func (b *BeaconState) SetStateRoots(val [][]byte) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.sharedFieldReferences[stateRoots].MinusRef() + b.sharedFieldReferences[stateRoots] = stateutil.NewRef(1) + + b.state.StateRoots = val + b.markFieldAsDirty(stateRoots) + b.rebuildTrie[stateRoots] = true + return nil +} + +// UpdateStateRootAtIndex for the beacon state. Updates the state root +// at a specific index to a new value. +func (b *BeaconState) UpdateStateRootAtIndex(idx uint64, stateRoot [32]byte) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + + b.lock.RLock() + if uint64(len(b.state.StateRoots)) <= idx { + b.lock.RUnlock() + return errors.Errorf("invalid index provided %d", idx) + } + b.lock.RUnlock() + + b.lock.Lock() + defer b.lock.Unlock() + + // Check if we hold the only reference to the shared state roots slice. + r := b.state.StateRoots + if ref := b.sharedFieldReferences[stateRoots]; ref.Refs() > 1 { + // Copy elements in underlying array by reference. + r = make([][]byte, len(b.state.StateRoots)) + copy(r, b.state.StateRoots) + ref.MinusRef() + b.sharedFieldReferences[stateRoots] = stateutil.NewRef(1) + } + + r[idx] = stateRoot[:] + b.state.StateRoots = r + + b.markFieldAsDirty(stateRoots) + b.addDirtyIndices(stateRoots, []uint64{idx}) + return nil +} diff --git a/beacon-chain/state/state-native/v1/setters_validator.go b/beacon-chain/state/state-native/v1/setters_validator.go new file mode 100644 index 0000000000..598c278408 --- /dev/null +++ b/beacon-chain/state/state-native/v1/setters_validator.go @@ -0,0 +1,228 @@ +package v1 + +import ( + "github.com/pkg/errors" + types "github.com/prysmaticlabs/eth2-types" + "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" + "github.com/prysmaticlabs/prysm/encoding/bytesutil" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" +) + +// SetValidators for the beacon state. Updates the entire +// to a new value by overwriting the previous one. +func (b *BeaconState) SetValidators(val []*ethpb.Validator) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.state.Validators = val + b.sharedFieldReferences[validators].MinusRef() + b.sharedFieldReferences[validators] = stateutil.NewRef(1) + b.markFieldAsDirty(validators) + b.rebuildTrie[validators] = true + b.valMapHandler = stateutil.NewValMapHandler(b.state.Validators) + return nil +} + +// ApplyToEveryValidator applies the provided callback function to each validator in the +// validator registry. +func (b *BeaconState) ApplyToEveryValidator(f func(idx int, val *ethpb.Validator) (bool, *ethpb.Validator, error)) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + v := b.state.Validators + if ref := b.sharedFieldReferences[validators]; ref.Refs() > 1 { + v = b.validatorsReferences() + ref.MinusRef() + b.sharedFieldReferences[validators] = stateutil.NewRef(1) + } + b.lock.Unlock() + var changedVals []uint64 + for i, val := range v { + changed, newVal, err := f(i, val) + if err != nil { + return err + } + if changed { + changedVals = append(changedVals, uint64(i)) + v[i] = newVal + } + } + + b.lock.Lock() + defer b.lock.Unlock() + + b.state.Validators = v + b.markFieldAsDirty(validators) + b.addDirtyIndices(validators, changedVals) + + return nil +} + +// UpdateValidatorAtIndex for the beacon state. Updates the validator +// at a specific index to a new value. +func (b *BeaconState) UpdateValidatorAtIndex(idx types.ValidatorIndex, val *ethpb.Validator) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + if uint64(len(b.state.Validators)) <= uint64(idx) { + return errors.Errorf("invalid index provided %d", idx) + } + b.lock.Lock() + defer b.lock.Unlock() + + v := b.state.Validators + if ref := b.sharedFieldReferences[validators]; ref.Refs() > 1 { + v = b.validatorsReferences() + ref.MinusRef() + b.sharedFieldReferences[validators] = stateutil.NewRef(1) + } + + v[idx] = val + b.state.Validators = v + b.markFieldAsDirty(validators) + b.addDirtyIndices(validators, []uint64{uint64(idx)}) + + return nil +} + +// SetBalances for the beacon state. Updates the entire +// list to a new value by overwriting the previous one. +func (b *BeaconState) SetBalances(val []uint64) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.sharedFieldReferences[balances].MinusRef() + b.sharedFieldReferences[balances] = stateutil.NewRef(1) + + b.state.Balances = val + b.markFieldAsDirty(balances) + b.rebuildTrie[balances] = true + return nil +} + +// UpdateBalancesAtIndex for the beacon state. This method updates the balance +// at a specific index to a new value. +func (b *BeaconState) UpdateBalancesAtIndex(idx types.ValidatorIndex, val uint64) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + if uint64(len(b.state.Balances)) <= uint64(idx) { + return errors.Errorf("invalid index provided %d", idx) + } + b.lock.Lock() + defer b.lock.Unlock() + + bals := b.state.Balances + if b.sharedFieldReferences[balances].Refs() > 1 { + bals = b.balances() + b.sharedFieldReferences[balances].MinusRef() + b.sharedFieldReferences[balances] = stateutil.NewRef(1) + } + + bals[idx] = val + b.state.Balances = bals + b.markFieldAsDirty(balances) + b.addDirtyIndices(balances, []uint64{uint64(idx)}) + return nil +} + +// SetSlashings for the beacon state. Updates the entire +// list to a new value by overwriting the previous one. +func (b *BeaconState) SetSlashings(val []uint64) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.sharedFieldReferences[slashings].MinusRef() + b.sharedFieldReferences[slashings] = stateutil.NewRef(1) + + b.state.Slashings = val + b.markFieldAsDirty(slashings) + return nil +} + +// UpdateSlashingsAtIndex for the beacon state. Updates the slashings +// at a specific index to a new value. +func (b *BeaconState) UpdateSlashingsAtIndex(idx, val uint64) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + if uint64(len(b.state.Slashings)) <= idx { + return errors.Errorf("invalid index provided %d", idx) + } + b.lock.Lock() + defer b.lock.Unlock() + + s := b.state.Slashings + if b.sharedFieldReferences[slashings].Refs() > 1 { + s = b.slashings() + b.sharedFieldReferences[slashings].MinusRef() + b.sharedFieldReferences[slashings] = stateutil.NewRef(1) + } + + s[idx] = val + + b.state.Slashings = s + + b.markFieldAsDirty(slashings) + return nil +} + +// AppendValidator for the beacon state. Appends the new value +// to the the end of list. +func (b *BeaconState) AppendValidator(val *ethpb.Validator) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + vals := b.state.Validators + if b.sharedFieldReferences[validators].Refs() > 1 { + vals = b.validatorsReferences() + b.sharedFieldReferences[validators].MinusRef() + b.sharedFieldReferences[validators] = stateutil.NewRef(1) + } + + // append validator to slice + b.state.Validators = append(vals, val) + valIdx := types.ValidatorIndex(len(b.state.Validators) - 1) + + b.valMapHandler.Set(bytesutil.ToBytes48(val.PublicKey), valIdx) + + b.markFieldAsDirty(validators) + b.addDirtyIndices(validators, []uint64{uint64(valIdx)}) + return nil +} + +// AppendBalance for the beacon state. Appends the new value +// to the the end of list. +func (b *BeaconState) AppendBalance(bal uint64) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + bals := b.state.Balances + if b.sharedFieldReferences[balances].Refs() > 1 { + bals = b.balances() + b.sharedFieldReferences[balances].MinusRef() + b.sharedFieldReferences[balances] = stateutil.NewRef(1) + } + + b.state.Balances = append(bals, bal) + balIdx := len(b.state.Balances) - 1 + b.markFieldAsDirty(balances) + b.addDirtyIndices(balances, []uint64{uint64(balIdx)}) + return nil +} diff --git a/beacon-chain/state/state-native/v1/state_test.go b/beacon-chain/state/state-native/v1/state_test.go new file mode 100644 index 0000000000..93227afd57 --- /dev/null +++ b/beacon-chain/state/state-native/v1/state_test.go @@ -0,0 +1,193 @@ +package v1 + +import ( + "context" + "strconv" + "sync" + "testing" + + types "github.com/prysmaticlabs/eth2-types" + "github.com/prysmaticlabs/go-bitfield" + "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" + fieldparams "github.com/prysmaticlabs/prysm/config/fieldparams" + "github.com/prysmaticlabs/prysm/config/params" + "github.com/prysmaticlabs/prysm/encoding/bytesutil" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/testing/assert" +) + +func TestValidatorMap_DistinctCopy(t *testing.T) { + count := uint64(100) + vals := make([]*ethpb.Validator, 0, count) + for i := uint64(1); i < count; i++ { + someRoot := [32]byte{} + someKey := [fieldparams.BLSPubkeyLength]byte{} + copy(someRoot[:], strconv.Itoa(int(i))) + copy(someKey[:], strconv.Itoa(int(i))) + vals = append(vals, ðpb.Validator{ + PublicKey: someKey[:], + WithdrawalCredentials: someRoot[:], + EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, + Slashed: false, + ActivationEligibilityEpoch: 1, + ActivationEpoch: 1, + ExitEpoch: 1, + WithdrawableEpoch: 1, + }) + } + handler := stateutil.NewValMapHandler(vals) + newHandler := handler.Copy() + wantedPubkey := strconv.Itoa(22) + handler.Set(bytesutil.ToBytes48([]byte(wantedPubkey)), 27) + val1, _ := handler.Get(bytesutil.ToBytes48([]byte(wantedPubkey))) + val2, _ := newHandler.Get(bytesutil.ToBytes48([]byte(wantedPubkey))) + assert.NotEqual(t, val1, val2, "Values are supposed to be unequal due to copy") +} + +func TestBeaconState_NoDeadlock(t *testing.T) { + count := uint64(100) + vals := make([]*ethpb.Validator, 0, count) + for i := uint64(1); i < count; i++ { + someRoot := [32]byte{} + someKey := [fieldparams.BLSPubkeyLength]byte{} + copy(someRoot[:], strconv.Itoa(int(i))) + copy(someKey[:], strconv.Itoa(int(i))) + vals = append(vals, ðpb.Validator{ + PublicKey: someKey[:], + WithdrawalCredentials: someRoot[:], + EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, + Slashed: false, + ActivationEligibilityEpoch: 1, + ActivationEpoch: 1, + ExitEpoch: 1, + WithdrawableEpoch: 1, + }) + } + st, err := InitializeFromProtoUnsafe(ðpb.BeaconState{ + Validators: vals, + }) + assert.NoError(t, err) + + wg := new(sync.WaitGroup) + + wg.Add(1) + go func() { + // Continuously lock and unlock the state + // by acquiring the lock. + for i := 0; i < 1000; i++ { + for _, f := range st.stateFieldLeaves { + f.Lock() + if f.Empty() { + f.InsertFieldLayer(make([][]*[32]byte, 10)) + } + f.Unlock() + f.FieldReference().AddRef() + } + } + wg.Done() + }() + // Constantly read from the offending portion + // of the code to ensure there is no possible + // recursive read locking. + for i := 0; i < 1000; i++ { + go func() { + _ = st.FieldReferencesCount() + }() + } + // Test will not terminate in the event of a deadlock. + wg.Wait() +} + +func TestStateTrie_IsNil(t *testing.T) { + var emptyState *BeaconState + assert.Equal(t, true, emptyState.IsNil()) + + emptyProto := &BeaconState{state: nil} + assert.Equal(t, true, emptyProto.IsNil()) + + nonNilState := &BeaconState{state: ðpb.BeaconState{}} + assert.Equal(t, false, nonNilState.IsNil()) +} + +func TestBeaconState_AppendBalanceWithTrie(t *testing.T) { + count := uint64(100) + vals := make([]*ethpb.Validator, 0, count) + bals := make([]uint64, 0, count) + for i := uint64(1); i < count; i++ { + someRoot := [32]byte{} + someKey := [fieldparams.BLSPubkeyLength]byte{} + copy(someRoot[:], strconv.Itoa(int(i))) + copy(someKey[:], strconv.Itoa(int(i))) + vals = append(vals, ðpb.Validator{ + PublicKey: someKey[:], + WithdrawalCredentials: someRoot[:], + EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, + Slashed: false, + ActivationEligibilityEpoch: 1, + ActivationEpoch: 1, + ExitEpoch: 1, + WithdrawableEpoch: 1, + }) + bals = append(bals, params.BeaconConfig().MaxEffectiveBalance) + } + zeroHash := params.BeaconConfig().ZeroHash + mockblockRoots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot) + for i := 0; i < len(mockblockRoots); i++ { + mockblockRoots[i] = zeroHash[:] + } + + mockstateRoots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot) + for i := 0; i < len(mockstateRoots); i++ { + mockstateRoots[i] = zeroHash[:] + } + mockrandaoMixes := make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector) + for i := 0; i < len(mockrandaoMixes); i++ { + mockrandaoMixes[i] = zeroHash[:] + } + st, err := InitializeFromProto(ðpb.BeaconState{ + Slot: 1, + GenesisValidatorsRoot: make([]byte, 32), + Fork: ðpb.Fork{ + PreviousVersion: make([]byte, 4), + CurrentVersion: make([]byte, 4), + Epoch: 0, + }, + LatestBlockHeader: ðpb.BeaconBlockHeader{ + ParentRoot: make([]byte, fieldparams.RootLength), + StateRoot: make([]byte, fieldparams.RootLength), + BodyRoot: make([]byte, fieldparams.RootLength), + }, + Validators: vals, + Balances: bals, + Eth1Data: ðpb.Eth1Data{ + DepositRoot: make([]byte, 32), + BlockHash: make([]byte, 32), + }, + BlockRoots: mockblockRoots, + StateRoots: mockstateRoots, + RandaoMixes: mockrandaoMixes, + JustificationBits: bitfield.NewBitvector4(), + PreviousJustifiedCheckpoint: ðpb.Checkpoint{Root: make([]byte, fieldparams.RootLength)}, + CurrentJustifiedCheckpoint: ðpb.Checkpoint{Root: make([]byte, fieldparams.RootLength)}, + FinalizedCheckpoint: ðpb.Checkpoint{Root: make([]byte, fieldparams.RootLength)}, + Slashings: make([]uint64, params.BeaconConfig().EpochsPerSlashingsVector), + }) + assert.NoError(t, err) + _, err = st.HashTreeRoot(context.Background()) + assert.NoError(t, err) + + for i := 0; i < 100; i++ { + if i%2 == 0 { + assert.NoError(t, st.UpdateBalancesAtIndex(types.ValidatorIndex(i), 1000)) + } + if i%3 == 0 { + assert.NoError(t, st.AppendBalance(1000)) + } + } + _, err = st.HashTreeRoot(context.Background()) + assert.NoError(t, err) + newRt := bytesutil.ToBytes32(st.merkleLayers[0][balances]) + wantedRt, err := stateutil.Uint64ListRootWithRegistryLimit(st.state.Balances) + assert.NoError(t, err) + assert.Equal(t, wantedRt, newRt, "state roots are unequal") +} diff --git a/beacon-chain/state/state-native/v1/state_trie.go b/beacon-chain/state/state-native/v1/state_trie.go new file mode 100644 index 0000000000..587554e9eb --- /dev/null +++ b/beacon-chain/state/state-native/v1/state_trie.go @@ -0,0 +1,434 @@ +package v1 + +import ( + "context" + "runtime" + "sort" + + "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/beacon-chain/state" + "github.com/prysmaticlabs/prysm/beacon-chain/state/state-native/fieldtrie" + "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" + "github.com/prysmaticlabs/prysm/beacon-chain/state/types" + "github.com/prysmaticlabs/prysm/config/features" + fieldparams "github.com/prysmaticlabs/prysm/config/fieldparams" + "github.com/prysmaticlabs/prysm/config/params" + "github.com/prysmaticlabs/prysm/container/slice" + "github.com/prysmaticlabs/prysm/crypto/hash" + "github.com/prysmaticlabs/prysm/encoding/bytesutil" + "github.com/prysmaticlabs/prysm/encoding/ssz" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" + "go.opencensus.io/trace" + "google.golang.org/protobuf/proto" +) + +// InitializeFromProto the beacon state from a protobuf representation. +func InitializeFromProto(st *ethpb.BeaconState) (*BeaconState, error) { + return InitializeFromProtoUnsafe(proto.Clone(st).(*ethpb.BeaconState)) +} + +// InitializeFromProtoUnsafe directly uses the beacon state protobuf pointer +// and sets it as the inner state of the BeaconState type. +func InitializeFromProtoUnsafe(st *ethpb.BeaconState) (*BeaconState, error) { + if st == nil { + return nil, errors.New("received nil state") + } + + fieldCount := params.BeaconConfig().BeaconStateFieldCount + b := &BeaconState{ + state: st, + dirtyFields: make(map[types.FieldIndex]bool, fieldCount), + dirtyIndices: make(map[types.FieldIndex][]uint64, fieldCount), + stateFieldLeaves: make(map[types.FieldIndex]*fieldtrie.FieldTrie, fieldCount), + sharedFieldReferences: make(map[types.FieldIndex]*stateutil.Reference, 10), + rebuildTrie: make(map[types.FieldIndex]bool, fieldCount), + valMapHandler: stateutil.NewValMapHandler(st.Validators), + } + + var err error + for i := 0; i < fieldCount; i++ { + b.dirtyFields[types.FieldIndex(i)] = true + b.rebuildTrie[types.FieldIndex(i)] = true + b.dirtyIndices[types.FieldIndex(i)] = []uint64{} + b.stateFieldLeaves[types.FieldIndex(i)], err = fieldtrie.NewFieldTrie(types.FieldIndex(i), types.BasicArray, nil, 0) + if err != nil { + return nil, err + } + } + + // Initialize field reference tracking for shared data. + b.sharedFieldReferences[randaoMixes] = stateutil.NewRef(1) + b.sharedFieldReferences[stateRoots] = stateutil.NewRef(1) + b.sharedFieldReferences[blockRoots] = stateutil.NewRef(1) + b.sharedFieldReferences[previousEpochAttestations] = stateutil.NewRef(1) + b.sharedFieldReferences[currentEpochAttestations] = stateutil.NewRef(1) + b.sharedFieldReferences[slashings] = stateutil.NewRef(1) + b.sharedFieldReferences[eth1DataVotes] = stateutil.NewRef(1) + b.sharedFieldReferences[validators] = stateutil.NewRef(1) + b.sharedFieldReferences[balances] = stateutil.NewRef(1) + b.sharedFieldReferences[historicalRoots] = stateutil.NewRef(1) + + state.StateCount.Inc() + return b, nil +} + +// Copy returns a deep copy of the beacon state. +func (b *BeaconState) Copy() state.BeaconState { + if !b.hasInnerState() { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + fieldCount := params.BeaconConfig().BeaconStateFieldCount + dst := &BeaconState{ + state: ðpb.BeaconState{ + // Primitive types, safe to copy. + GenesisTime: b.state.GenesisTime, + Slot: b.state.Slot, + Eth1DepositIndex: b.state.Eth1DepositIndex, + + // Large arrays, infrequently changed, constant size. + RandaoMixes: b.state.RandaoMixes, + StateRoots: b.state.StateRoots, + BlockRoots: b.state.BlockRoots, + PreviousEpochAttestations: b.state.PreviousEpochAttestations, + CurrentEpochAttestations: b.state.CurrentEpochAttestations, + Slashings: b.state.Slashings, + Eth1DataVotes: b.state.Eth1DataVotes, + + // Large arrays, increases over time. + Validators: b.state.Validators, + Balances: b.state.Balances, + HistoricalRoots: b.state.HistoricalRoots, + + // Everything else, too small to be concerned about, constant size. + Fork: b.fork(), + LatestBlockHeader: b.latestBlockHeader(), + Eth1Data: b.eth1Data(), + JustificationBits: b.justificationBits(), + PreviousJustifiedCheckpoint: b.previousJustifiedCheckpoint(), + CurrentJustifiedCheckpoint: b.currentJustifiedCheckpoint(), + FinalizedCheckpoint: b.finalizedCheckpoint(), + GenesisValidatorsRoot: b.genesisValidatorRoot(), + }, + dirtyFields: make(map[types.FieldIndex]bool, fieldCount), + dirtyIndices: make(map[types.FieldIndex][]uint64, fieldCount), + rebuildTrie: make(map[types.FieldIndex]bool, fieldCount), + sharedFieldReferences: make(map[types.FieldIndex]*stateutil.Reference, 10), + stateFieldLeaves: make(map[types.FieldIndex]*fieldtrie.FieldTrie, fieldCount), + + // Share the reference to validator index map. + valMapHandler: b.valMapHandler, + } + + for field, ref := range b.sharedFieldReferences { + ref.AddRef() + dst.sharedFieldReferences[field] = ref + } + + // Increment ref for validator map + b.valMapHandler.AddRef() + + for i := range b.dirtyFields { + dst.dirtyFields[i] = true + } + + for i := range b.dirtyIndices { + indices := make([]uint64, len(b.dirtyIndices[i])) + copy(indices, b.dirtyIndices[i]) + dst.dirtyIndices[i] = indices + } + + for i := range b.rebuildTrie { + dst.rebuildTrie[i] = true + } + + for fldIdx, fieldTrie := range b.stateFieldLeaves { + dst.stateFieldLeaves[fldIdx] = fieldTrie + if fieldTrie.FieldReference() != nil { + fieldTrie.Lock() + fieldTrie.FieldReference().AddRef() + fieldTrie.Unlock() + } + } + + if b.merkleLayers != nil { + dst.merkleLayers = make([][][]byte, len(b.merkleLayers)) + for i, layer := range b.merkleLayers { + dst.merkleLayers[i] = make([][]byte, len(layer)) + for j, content := range layer { + dst.merkleLayers[i][j] = make([]byte, len(content)) + copy(dst.merkleLayers[i][j], content) + } + } + } + + state.StateCount.Inc() + // Finalizer runs when dst is being destroyed in garbage collection. + runtime.SetFinalizer(dst, func(b *BeaconState) { + for field, v := range b.sharedFieldReferences { + v.MinusRef() + if b.stateFieldLeaves[field].FieldReference() != nil { + b.stateFieldLeaves[field].FieldReference().MinusRef() + } + + } + for i := 0; i < fieldCount; i++ { + field := types.FieldIndex(i) + delete(b.stateFieldLeaves, field) + delete(b.dirtyIndices, field) + delete(b.dirtyFields, field) + delete(b.sharedFieldReferences, field) + delete(b.stateFieldLeaves, field) + } + state.StateCount.Sub(1) + }) + return dst +} + +// HashTreeRoot of the beacon state retrieves the Merkle root of the trie +// representation of the beacon state based on the Ethereum Simple Serialize specification. +func (b *BeaconState) HashTreeRoot(ctx context.Context) ([32]byte, error) { + ctx, span := trace.StartSpan(ctx, "beaconState.HashTreeRoot") + defer span.End() + + b.lock.Lock() + defer b.lock.Unlock() + if err := b.initializeMerkleLayers(ctx); err != nil { + return [32]byte{}, err + } + if err := b.recomputeDirtyFields(ctx); err != nil { + return [32]byte{}, err + } + return bytesutil.ToBytes32(b.merkleLayers[len(b.merkleLayers)-1][0]), nil +} + +// Initializes the Merkle layers for the beacon state if they are empty. +// WARNING: Caller must acquire the mutex before using. +func (b *BeaconState) initializeMerkleLayers(ctx context.Context) error { + if len(b.merkleLayers) > 0 { + return nil + } + fieldRoots, err := computeFieldRoots(ctx, b.state) + if err != nil { + return err + } + layers := stateutil.Merkleize(fieldRoots) + b.merkleLayers = layers + b.dirtyFields = make(map[types.FieldIndex]bool, params.BeaconConfig().BeaconStateFieldCount) + return nil +} + +// Recomputes the Merkle layers for the dirty fields in the state. +// WARNING: Caller must acquire the mutex before using. +func (b *BeaconState) recomputeDirtyFields(ctx context.Context) error { + for field := range b.dirtyFields { + root, err := b.rootSelector(ctx, field) + if err != nil { + return err + } + b.merkleLayers[0][field] = root[:] + b.recomputeRoot(int(field)) + delete(b.dirtyFields, field) + } + return nil +} + +// FieldReferencesCount returns the reference count held by each field. This +// also includes the field trie held by each field. +func (b *BeaconState) FieldReferencesCount() map[string]uint64 { + refMap := make(map[string]uint64) + b.lock.RLock() + defer b.lock.RUnlock() + for i, f := range b.sharedFieldReferences { + refMap[i.String(b.Version())] = uint64(f.Refs()) + } + for i, f := range b.stateFieldLeaves { + numOfRefs := uint64(f.FieldReference().Refs()) + f.RLock() + if !f.Empty() { + refMap[i.String(b.Version())+"_trie"] = numOfRefs + } + f.RUnlock() + } + return refMap +} + +// IsNil checks if the state and the underlying proto +// object are nil. +func (b *BeaconState) IsNil() bool { + return b == nil || b.state == nil +} + +func (b *BeaconState) rootSelector(ctx context.Context, field types.FieldIndex) ([32]byte, error) { + _, span := trace.StartSpan(ctx, "beaconState.rootSelector") + defer span.End() + span.AddAttributes(trace.StringAttribute("field", field.String(b.Version()))) + + hasher := hash.CustomSHA256Hasher() + switch field { + case genesisTime: + return ssz.Uint64Root(b.state.GenesisTime), nil + case genesisValidatorRoot: + return bytesutil.ToBytes32(b.state.GenesisValidatorsRoot), nil + case slot: + return ssz.Uint64Root(uint64(b.state.Slot)), nil + case eth1DepositIndex: + return ssz.Uint64Root(b.state.Eth1DepositIndex), nil + case fork: + return ssz.ForkRoot(b.state.Fork) + case latestBlockHeader: + return stateutil.BlockHeaderRoot(b.state.LatestBlockHeader) + case blockRoots: + if b.rebuildTrie[field] { + err := b.resetFieldTrie(field, b.state.BlockRoots, fieldparams.BlockRootsLength) + if err != nil { + return [32]byte{}, err + } + delete(b.rebuildTrie, field) + return b.stateFieldLeaves[field].TrieRoot() + } + return b.recomputeFieldTrie(blockRoots, b.state.BlockRoots) + case stateRoots: + if b.rebuildTrie[field] { + err := b.resetFieldTrie(field, b.state.StateRoots, fieldparams.StateRootsLength) + if err != nil { + return [32]byte{}, err + } + delete(b.rebuildTrie, field) + return b.stateFieldLeaves[field].TrieRoot() + } + return b.recomputeFieldTrie(stateRoots, b.state.StateRoots) + case historicalRoots: + return ssz.ByteArrayRootWithLimit(b.state.HistoricalRoots, fieldparams.HistoricalRootsLength) + case eth1Data: + return stateutil.Eth1Root(hasher, b.state.Eth1Data) + case eth1DataVotes: + if b.rebuildTrie[field] { + err := b.resetFieldTrie( + field, + b.state.Eth1DataVotes, + fieldparams.Eth1DataVotesLength, + ) + if err != nil { + return [32]byte{}, err + } + delete(b.rebuildTrie, field) + return b.stateFieldLeaves[field].TrieRoot() + } + return b.recomputeFieldTrie(field, b.state.Eth1DataVotes) + case validators: + if b.rebuildTrie[field] { + err := b.resetFieldTrie(field, b.state.Validators, fieldparams.ValidatorRegistryLimit) + if err != nil { + return [32]byte{}, err + } + delete(b.rebuildTrie, validators) + return b.stateFieldLeaves[field].TrieRoot() + } + return b.recomputeFieldTrie(validators, b.state.Validators) + case balances: + if features.Get().EnableBalanceTrieComputation { + if b.rebuildTrie[field] { + maxBalCap := uint64(fieldparams.ValidatorRegistryLimit) + elemSize := uint64(8) + balLimit := (maxBalCap*elemSize + 31) / 32 + err := b.resetFieldTrie(field, b.state.Balances, balLimit) + if err != nil { + return [32]byte{}, err + } + delete(b.rebuildTrie, field) + return b.stateFieldLeaves[field].TrieRoot() + } + return b.recomputeFieldTrie(balances, b.state.Balances) + } + return stateutil.Uint64ListRootWithRegistryLimit(b.state.Balances) + case randaoMixes: + if b.rebuildTrie[field] { + err := b.resetFieldTrie(field, b.state.RandaoMixes, fieldparams.RandaoMixesLength) + if err != nil { + return [32]byte{}, err + } + delete(b.rebuildTrie, field) + return b.stateFieldLeaves[field].TrieRoot() + } + return b.recomputeFieldTrie(randaoMixes, b.state.RandaoMixes) + case slashings: + return ssz.SlashingsRoot(b.state.Slashings) + case previousEpochAttestations: + if b.rebuildTrie[field] { + err := b.resetFieldTrie( + field, + b.state.PreviousEpochAttestations, + fieldparams.PreviousEpochAttestationsLength, + ) + if err != nil { + return [32]byte{}, err + } + delete(b.rebuildTrie, field) + return b.stateFieldLeaves[field].TrieRoot() + } + return b.recomputeFieldTrie(field, b.state.PreviousEpochAttestations) + case currentEpochAttestations: + if b.rebuildTrie[field] { + err := b.resetFieldTrie( + field, + b.state.CurrentEpochAttestations, + fieldparams.CurrentEpochAttestationsLength, + ) + if err != nil { + return [32]byte{}, err + } + delete(b.rebuildTrie, field) + return b.stateFieldLeaves[field].TrieRoot() + } + return b.recomputeFieldTrie(field, b.state.CurrentEpochAttestations) + case justificationBits: + return bytesutil.ToBytes32(b.state.JustificationBits), nil + case previousJustifiedCheckpoint: + return ssz.CheckpointRoot(hasher, b.state.PreviousJustifiedCheckpoint) + case currentJustifiedCheckpoint: + return ssz.CheckpointRoot(hasher, b.state.CurrentJustifiedCheckpoint) + case finalizedCheckpoint: + return ssz.CheckpointRoot(hasher, b.state.FinalizedCheckpoint) + } + return [32]byte{}, errors.New("invalid field index provided") +} + +func (b *BeaconState) recomputeFieldTrie(index types.FieldIndex, elements interface{}) ([32]byte, error) { + fTrie := b.stateFieldLeaves[index] + // We can't lock the trie directly because the trie's variable gets reassigned, + // and therefore we would call Unlock() on a different object. + fTrieMutex := fTrie.RWMutex + if fTrie.FieldReference().Refs() > 1 { + fTrieMutex.Lock() + fTrie.FieldReference().MinusRef() + newTrie := fTrie.CopyTrie() + b.stateFieldLeaves[index] = newTrie + fTrie = newTrie + fTrieMutex.Unlock() + } + // remove duplicate indexes + b.dirtyIndices[index] = slice.SetUint64(b.dirtyIndices[index]) + // sort indexes again + sort.Slice(b.dirtyIndices[index], func(i int, j int) bool { + return b.dirtyIndices[index][i] < b.dirtyIndices[index][j] + }) + root, err := fTrie.RecomputeTrie(b.dirtyIndices[index], elements) + if err != nil { + return [32]byte{}, err + } + b.dirtyIndices[index] = []uint64{} + return root, nil +} + +func (b *BeaconState) resetFieldTrie(index types.FieldIndex, elements interface{}, length uint64) error { + fTrie, err := fieldtrie.NewFieldTrie(index, fieldMap[index], elements, length) + if err != nil { + return err + } + b.stateFieldLeaves[index] = fTrie + b.dirtyIndices[index] = []uint64{} + return nil +} diff --git a/beacon-chain/state/state-native/v1/state_trie_test.go b/beacon-chain/state/state-native/v1/state_trie_test.go new file mode 100644 index 0000000000..e24122ba91 --- /dev/null +++ b/beacon-chain/state/state-native/v1/state_trie_test.go @@ -0,0 +1,260 @@ +package v1_test + +import ( + "bytes" + "context" + "testing" + + "github.com/prysmaticlabs/prysm/beacon-chain/state" + v1 "github.com/prysmaticlabs/prysm/beacon-chain/state/state-native/v1" + "github.com/prysmaticlabs/prysm/config/features" + "github.com/prysmaticlabs/prysm/config/params" + "github.com/prysmaticlabs/prysm/encoding/bytesutil" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/testing/assert" + "github.com/prysmaticlabs/prysm/testing/require" + "github.com/prysmaticlabs/prysm/testing/util" +) + +func TestMain(m *testing.M) { + resetCfg := features.InitWithReset(&features.Flags{EnableBalanceTrieComputation: true}) + defer resetCfg() + m.Run() +} + +func TestInitializeFromProto(t *testing.T) { + testState, _ := util.DeterministicGenesisState(t, 64) + pbState, err := v1.ProtobufBeaconState(testState.InnerStateUnsafe()) + require.NoError(t, err) + type test struct { + name string + state *ethpb.BeaconState + error string + } + initTests := []test{ + { + name: "nil state", + state: nil, + error: "received nil state", + }, + { + name: "nil validators", + state: ðpb.BeaconState{ + Slot: 4, + Validators: nil, + }, + }, + { + name: "empty state", + state: ðpb.BeaconState{}, + }, + { + name: "full state", + state: pbState, + }, + } + for _, tt := range initTests { + t.Run(tt.name, func(t *testing.T) { + _, err := v1.InitializeFromProto(tt.state) + if tt.error != "" { + assert.ErrorContains(t, tt.error, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestInitializeFromProtoUnsafe(t *testing.T) { + testState, _ := util.DeterministicGenesisState(t, 64) + pbState, err := v1.ProtobufBeaconState(testState.InnerStateUnsafe()) + require.NoError(t, err) + type test struct { + name string + state *ethpb.BeaconState + error string + } + initTests := []test{ + { + name: "nil state", + state: nil, + error: "received nil state", + }, + { + name: "nil validators", + state: ðpb.BeaconState{ + Slot: 4, + Validators: nil, + }, + }, + { + name: "empty state", + state: ðpb.BeaconState{}, + }, + { + name: "full state", + state: pbState, + }, + } + for _, tt := range initTests { + t.Run(tt.name, func(t *testing.T) { + _, err := v1.InitializeFromProtoUnsafe(tt.state) + if tt.error != "" { + assert.ErrorContains(t, tt.error, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestBeaconState_HashTreeRoot(t *testing.T) { + testState, _ := util.DeterministicGenesisState(t, 64) + + type test struct { + name string + stateModify func(beaconState state.BeaconState) (state.BeaconState, error) + error string + } + initTests := []test{ + { + name: "unchanged state", + stateModify: func(beaconState state.BeaconState) (state.BeaconState, error) { + return beaconState, nil + }, + error: "", + }, + { + name: "different slot", + stateModify: func(beaconState state.BeaconState) (state.BeaconState, error) { + if err := beaconState.SetSlot(5); err != nil { + return nil, err + } + return beaconState, nil + }, + error: "", + }, + { + name: "different validator balance", + stateModify: func(beaconState state.BeaconState) (state.BeaconState, error) { + val, err := beaconState.ValidatorAtIndex(5) + if err != nil { + return nil, err + } + val.EffectiveBalance = params.BeaconConfig().MaxEffectiveBalance - params.BeaconConfig().EffectiveBalanceIncrement + if err := beaconState.UpdateValidatorAtIndex(5, val); err != nil { + return nil, err + } + return beaconState, nil + }, + error: "", + }, + } + + var err error + var oldHTR []byte + for _, tt := range initTests { + t.Run(tt.name, func(t *testing.T) { + testState, err = tt.stateModify(testState) + assert.NoError(t, err) + root, err := testState.HashTreeRoot(context.Background()) + if err == nil && tt.error != "" { + t.Errorf("Expected error, expected %v, recevied %v", tt.error, err) + } + pbState, err := v1.ProtobufBeaconState(testState.InnerStateUnsafe()) + require.NoError(t, err) + genericHTR, err := pbState.HashTreeRoot() + if err == nil && tt.error != "" { + t.Errorf("Expected error, expected %v, recevied %v", tt.error, err) + } + assert.DeepNotEqual(t, []byte{}, root[:], "Received empty hash tree root") + assert.DeepEqual(t, genericHTR[:], root[:], "Expected hash tree root to match generic") + if len(oldHTR) != 0 && bytes.Equal(root[:], oldHTR) { + t.Errorf("Expected HTR to change, received %#x == old %#x", root, oldHTR) + } + oldHTR = root[:] + }) + } +} + +func TestBeaconState_HashTreeRoot_FieldTrie(t *testing.T) { + testState, _ := util.DeterministicGenesisState(t, 64) + + type test struct { + name string + stateModify func(state.BeaconState) (state.BeaconState, error) + error string + } + initTests := []test{ + { + name: "unchanged state", + stateModify: func(beaconState state.BeaconState) (state.BeaconState, error) { + return beaconState, nil + }, + error: "", + }, + { + name: "different slot", + stateModify: func(beaconState state.BeaconState) (state.BeaconState, error) { + if err := beaconState.SetSlot(5); err != nil { + return nil, err + } + return beaconState, nil + }, + error: "", + }, + { + name: "different validator balance", + stateModify: func(beaconState state.BeaconState) (state.BeaconState, error) { + val, err := beaconState.ValidatorAtIndex(5) + if err != nil { + return nil, err + } + val.EffectiveBalance = params.BeaconConfig().MaxEffectiveBalance - params.BeaconConfig().EffectiveBalanceIncrement + if err := beaconState.UpdateValidatorAtIndex(5, val); err != nil { + return nil, err + } + return beaconState, nil + }, + error: "", + }, + } + + var err error + var oldHTR []byte + for _, tt := range initTests { + t.Run(tt.name, func(t *testing.T) { + testState, err = tt.stateModify(testState) + assert.NoError(t, err) + root, err := testState.HashTreeRoot(context.Background()) + if err == nil && tt.error != "" { + t.Errorf("Expected error, expected %v, recevied %v", tt.error, err) + } + pbState, err := v1.ProtobufBeaconState(testState.InnerStateUnsafe()) + require.NoError(t, err) + genericHTR, err := pbState.HashTreeRoot() + if err == nil && tt.error != "" { + t.Errorf("Expected error, expected %v, recevied %v", tt.error, err) + } + assert.DeepNotEqual(t, []byte{}, root[:], "Received empty hash tree root") + assert.DeepEqual(t, genericHTR[:], root[:], "Expected hash tree root to match generic") + if len(oldHTR) != 0 && bytes.Equal(root[:], oldHTR) { + t.Errorf("Expected HTR to change, received %#x == old %#x", root, oldHTR) + } + oldHTR = root[:] + }) + } +} + +func TestBeaconState_AppendValidator_DoesntMutateCopy(t *testing.T) { + st0, err := util.NewBeaconState() + require.NoError(t, err) + st1 := st0.Copy() + originalCount := st1.NumValidators() + + val := ðpb.Validator{Slashed: true} + assert.NoError(t, st0.AppendValidator(val)) + assert.Equal(t, originalCount, st1.NumValidators(), "st1 NumValidators mutated") + _, ok := st1.ValidatorIndexByPubkey(bytesutil.ToBytes48(val.PublicKey)) + assert.Equal(t, false, ok, "Expected no validator index to be present in st1 for the newly inserted pubkey") +} diff --git a/beacon-chain/state/state-native/v1/types.go b/beacon-chain/state/state-native/v1/types.go new file mode 100644 index 0000000000..e08e27555f --- /dev/null +++ b/beacon-chain/state/state-native/v1/types.go @@ -0,0 +1,78 @@ +package v1 + +import ( + "sync" + + "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/beacon-chain/state" + "github.com/prysmaticlabs/prysm/beacon-chain/state/state-native/fieldtrie" + "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" + "github.com/prysmaticlabs/prysm/beacon-chain/state/types" + "github.com/prysmaticlabs/prysm/config/params" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" +) + +// Ensure type BeaconState below implements BeaconState interface. +var _ state.BeaconState = (*BeaconState)(nil) + +func init() { + fieldMap = make(map[types.FieldIndex]types.DataType, params.BeaconConfig().BeaconStateFieldCount) + // Initialize the fixed sized arrays. + fieldMap[types.BlockRoots] = types.BasicArray + fieldMap[types.StateRoots] = types.BasicArray + fieldMap[types.RandaoMixes] = types.BasicArray + + // Initialize the composite arrays. + fieldMap[types.Eth1DataVotes] = types.CompositeArray + fieldMap[types.Validators] = types.CompositeArray + fieldMap[types.PreviousEpochAttestations] = types.CompositeArray + fieldMap[types.CurrentEpochAttestations] = types.CompositeArray + fieldMap[types.Balances] = types.CompressedArray +} + +// fieldMap keeps track of each field +// to its corresponding data type. +var fieldMap map[types.FieldIndex]types.DataType + +// ErrNilInnerState returns when the inner state is nil and no copy set or get +// operations can be performed on state. +var ErrNilInnerState = errors.New("nil inner state") + +// BeaconState defines a struct containing utilities for the Ethereum Beacon Chain state, defining +// getters and setters for its respective values and helpful functions such as HashTreeRoot(). +type BeaconState struct { + state *ethpb.BeaconState + lock sync.RWMutex + dirtyFields map[types.FieldIndex]bool + dirtyIndices map[types.FieldIndex][]uint64 + stateFieldLeaves map[types.FieldIndex]*fieldtrie.FieldTrie + rebuildTrie map[types.FieldIndex]bool + valMapHandler *stateutil.ValidatorMapHandler + merkleLayers [][][]byte + sharedFieldReferences map[types.FieldIndex]*stateutil.Reference +} + +// Field Aliases for values from the types package. +const ( + genesisTime = types.GenesisTime + genesisValidatorRoot = types.GenesisValidatorRoot + slot = types.Slot + fork = types.Fork + latestBlockHeader = types.LatestBlockHeader + blockRoots = types.BlockRoots + stateRoots = types.StateRoots + historicalRoots = types.HistoricalRoots + eth1Data = types.Eth1Data + eth1DataVotes = types.Eth1DataVotes + eth1DepositIndex = types.Eth1DepositIndex + validators = types.Validators + balances = types.Balances + randaoMixes = types.RandaoMixes + slashings = types.Slashings + previousEpochAttestations = types.PreviousEpochAttestations + currentEpochAttestations = types.CurrentEpochAttestations + justificationBits = types.JustificationBits + previousJustifiedCheckpoint = types.PreviousJustifiedCheckpoint + currentJustifiedCheckpoint = types.CurrentJustifiedCheckpoint + finalizedCheckpoint = types.FinalizedCheckpoint +) diff --git a/beacon-chain/state/state-native/v1/types_test.go b/beacon-chain/state/state-native/v1/types_test.go new file mode 100644 index 0000000000..be72103399 --- /dev/null +++ b/beacon-chain/state/state-native/v1/types_test.go @@ -0,0 +1,232 @@ +package v1_test + +import ( + "context" + "reflect" + "strconv" + "testing" + + v1 "github.com/prysmaticlabs/prysm/beacon-chain/state/state-native/v1" + fieldparams "github.com/prysmaticlabs/prysm/config/fieldparams" + "github.com/prysmaticlabs/prysm/config/params" + "github.com/prysmaticlabs/prysm/encoding/bytesutil" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/runtime/interop" + "github.com/prysmaticlabs/prysm/testing/assert" + "github.com/prysmaticlabs/prysm/testing/require" + log "github.com/sirupsen/logrus" + "google.golang.org/protobuf/proto" +) + +func TestBeaconState_ProtoBeaconStateCompatibility(t *testing.T) { + params.SetupTestConfigCleanup(t) + params.OverrideBeaconConfig(params.MinimalSpecConfig()) + ctx := context.Background() + genesis := setupGenesisState(t, 64) + customState, err := v1.InitializeFromProto(genesis) + require.NoError(t, err) + cloned, ok := proto.Clone(genesis).(*ethpb.BeaconState) + assert.Equal(t, true, ok, "Object is not of type *ethpb.BeaconState") + custom := customState.CloneInnerState() + assert.DeepSSZEqual(t, cloned, custom) + + r1, err := customState.HashTreeRoot(ctx) + require.NoError(t, err) + beaconState, err := v1.InitializeFromProto(genesis) + require.NoError(t, err) + r2, err := beaconState.HashTreeRoot(context.Background()) + require.NoError(t, err) + assert.Equal(t, r1, r2, "Mismatched roots") + + // We then write to the the state and compare hash tree roots again. + balances := genesis.Balances + balances[0] = 3823 + require.NoError(t, customState.SetBalances(balances)) + r1, err = customState.HashTreeRoot(ctx) + require.NoError(t, err) + genesis.Balances = balances + beaconState, err = v1.InitializeFromProto(genesis) + require.NoError(t, err) + r2, err = beaconState.HashTreeRoot(context.Background()) + require.NoError(t, err) + assert.Equal(t, r1, r2, "Mismatched roots") +} + +func setupGenesisState(tb testing.TB, count uint64) *ethpb.BeaconState { + genesisState, _, err := interop.GenerateGenesisState(context.Background(), 0, count) + require.NoError(tb, err, "Could not generate genesis beacon state") + for i := uint64(1); i < count; i++ { + someRoot := [32]byte{} + someKey := [fieldparams.BLSPubkeyLength]byte{} + copy(someRoot[:], strconv.Itoa(int(i))) + copy(someKey[:], strconv.Itoa(int(i))) + genesisState.Validators = append(genesisState.Validators, ðpb.Validator{ + PublicKey: someKey[:], + WithdrawalCredentials: someRoot[:], + EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, + Slashed: false, + ActivationEligibilityEpoch: 1, + ActivationEpoch: 1, + ExitEpoch: 1, + WithdrawableEpoch: 1, + }) + genesisState.Balances = append(genesisState.Balances, params.BeaconConfig().MaxEffectiveBalance) + } + return genesisState +} + +func BenchmarkCloneValidators_Proto(b *testing.B) { + b.StopTimer() + validators := make([]*ethpb.Validator, 16384) + somePubKey := [fieldparams.BLSPubkeyLength]byte{1, 2, 3} + someRoot := [32]byte{3, 4, 5} + for i := 0; i < len(validators); i++ { + validators[i] = ðpb.Validator{ + PublicKey: somePubKey[:], + WithdrawalCredentials: someRoot[:], + EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, + Slashed: false, + ActivationEligibilityEpoch: params.BeaconConfig().FarFutureEpoch, + ActivationEpoch: 3, + ExitEpoch: 4, + WithdrawableEpoch: 5, + } + } + b.StartTimer() + for i := 0; i < b.N; i++ { + cloneValidatorsWithProto(validators) + } +} + +func BenchmarkCloneValidators_Manual(b *testing.B) { + b.StopTimer() + validators := make([]*ethpb.Validator, 16384) + somePubKey := [fieldparams.BLSPubkeyLength]byte{1, 2, 3} + someRoot := [32]byte{3, 4, 5} + for i := 0; i < len(validators); i++ { + validators[i] = ðpb.Validator{ + PublicKey: somePubKey[:], + WithdrawalCredentials: someRoot[:], + EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, + Slashed: false, + ActivationEligibilityEpoch: params.BeaconConfig().FarFutureEpoch, + ActivationEpoch: 3, + ExitEpoch: 4, + WithdrawableEpoch: 5, + } + } + b.StartTimer() + for i := 0; i < b.N; i++ { + cloneValidatorsManually(validators) + } +} + +func BenchmarkStateClone_Proto(b *testing.B) { + b.StopTimer() + params.SetupTestConfigCleanup(b) + params.OverrideBeaconConfig(params.MinimalSpecConfig()) + genesis := setupGenesisState(b, 64) + b.StartTimer() + for i := 0; i < b.N; i++ { + _, ok := proto.Clone(genesis).(*ethpb.BeaconState) + assert.Equal(b, true, ok, "Entity is not of type *ethpb.BeaconState") + } +} + +func BenchmarkStateClone_Manual(b *testing.B) { + b.StopTimer() + params.SetupTestConfigCleanup(b) + params.OverrideBeaconConfig(params.MinimalSpecConfig()) + genesis := setupGenesisState(b, 64) + st, err := v1.InitializeFromProto(genesis) + require.NoError(b, err) + b.StartTimer() + for i := 0; i < b.N; i++ { + _ = st.CloneInnerState() + } +} + +func cloneValidatorsWithProto(vals []*ethpb.Validator) []*ethpb.Validator { + var ok bool + res := make([]*ethpb.Validator, len(vals)) + for i := 0; i < len(res); i++ { + res[i], ok = proto.Clone(vals[i]).(*ethpb.Validator) + if !ok { + log.Debug("Entity is not of type *ethpb.Validator") + } + } + return res +} + +func cloneValidatorsManually(vals []*ethpb.Validator) []*ethpb.Validator { + res := make([]*ethpb.Validator, len(vals)) + for i := 0; i < len(res); i++ { + val := vals[i] + res[i] = ðpb.Validator{ + PublicKey: val.PublicKey, + WithdrawalCredentials: val.WithdrawalCredentials, + EffectiveBalance: val.EffectiveBalance, + Slashed: val.Slashed, + ActivationEligibilityEpoch: val.ActivationEligibilityEpoch, + ActivationEpoch: val.ActivationEpoch, + ExitEpoch: val.ExitEpoch, + WithdrawableEpoch: val.WithdrawableEpoch, + } + } + return res +} + +func TestBeaconState_ImmutabilityWithSharedResources(t *testing.T) { + params.SetupTestConfigCleanup(t) + params.OverrideBeaconConfig(params.MinimalSpecConfig()) + genesis := setupGenesisState(t, 64) + a, err := v1.InitializeFromProto(genesis) + require.NoError(t, err) + b := a.Copy() + + // Randao mixes + require.DeepEqual(t, a.RandaoMixes(), b.RandaoMixes(), "Test precondition failed, fields are not equal") + require.NoError(t, a.UpdateRandaoMixesAtIndex(1, []byte("foo"))) + if reflect.DeepEqual(a.RandaoMixes(), b.RandaoMixes()) { + t.Error("Expect a.RandaoMixes() to be different from b.RandaoMixes()") + } + + // Validators + require.DeepEqual(t, a.Validators(), b.Validators(), "Test precondition failed, fields are not equal") + require.NoError(t, a.UpdateValidatorAtIndex(1, ðpb.Validator{Slashed: true})) + if reflect.DeepEqual(a.Validators(), b.Validators()) { + t.Error("Expect a.Validators() to be different from b.Validators()") + } + + // State Roots + require.DeepEqual(t, a.StateRoots(), b.StateRoots(), "Test precondition failed, fields are not equal") + require.NoError(t, a.UpdateStateRootAtIndex(1, bytesutil.ToBytes32([]byte("foo")))) + if reflect.DeepEqual(a.StateRoots(), b.StateRoots()) { + t.Fatal("Expected a.StateRoots() to be different from b.StateRoots()") + } + + // Block Roots + require.DeepEqual(t, a.BlockRoots(), b.BlockRoots(), "Test precondition failed, fields are not equal") + require.NoError(t, a.UpdateBlockRootAtIndex(1, bytesutil.ToBytes32([]byte("foo")))) + if reflect.DeepEqual(a.BlockRoots(), b.BlockRoots()) { + t.Fatal("Expected a.BlockRoots() to be different from b.BlockRoots()") + } +} + +func TestForkManualCopy_OK(t *testing.T) { + params.SetupTestConfigCleanup(t) + params.OverrideBeaconConfig(params.MinimalSpecConfig()) + genesis := setupGenesisState(t, 64) + a, err := v1.InitializeFromProto(genesis) + require.NoError(t, err) + wantedFork := ðpb.Fork{ + PreviousVersion: []byte{'a', 'b', 'c'}, + CurrentVersion: []byte{'d', 'e', 'f'}, + Epoch: 0, + } + require.NoError(t, a.SetFork(wantedFork)) + + pbState, err := v1.ProtobufBeaconState(a.InnerStateUnsafe()) + require.NoError(t, err) + require.DeepEqual(t, pbState.Fork, wantedFork) +} diff --git a/beacon-chain/state/state-native/v1/unsupported_getters.go b/beacon-chain/state/state-native/v1/unsupported_getters.go new file mode 100644 index 0000000000..3717aab456 --- /dev/null +++ b/beacon-chain/state/state-native/v1/unsupported_getters.go @@ -0,0 +1,36 @@ +package v1 + +import ( + "github.com/pkg/errors" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" +) + +// CurrentEpochParticipation is not supported for phase 0 beacon state. +func (*BeaconState) CurrentEpochParticipation() ([]byte, error) { + return nil, errors.New("CurrentEpochParticipation is not supported for phase 0 beacon state") +} + +// PreviousEpochParticipation is not supported for phase 0 beacon state. +func (*BeaconState) PreviousEpochParticipation() ([]byte, error) { + return nil, errors.New("PreviousEpochParticipation is not supported for phase 0 beacon state") +} + +// InactivityScores is not supported for phase 0 beacon state. +func (*BeaconState) InactivityScores() ([]uint64, error) { + return nil, errors.New("InactivityScores is not supported for phase 0 beacon state") +} + +// CurrentSyncCommittee is not supported for phase 0 beacon state. +func (*BeaconState) CurrentSyncCommittee() (*ethpb.SyncCommittee, error) { + return nil, errors.New("CurrentSyncCommittee is not supported for phase 0 beacon state") +} + +// NextSyncCommittee is not supported for phase 0 beacon state. +func (*BeaconState) NextSyncCommittee() (*ethpb.SyncCommittee, error) { + return nil, errors.New("NextSyncCommittee is not supported for phase 0 beacon state") +} + +// LatestExecutionPayloadHeader is not supported for phase 0 beacon state. +func (*BeaconState) LatestExecutionPayloadHeader() (*ethpb.ExecutionPayloadHeader, error) { + return nil, errors.New("LatestExecutionPayloadHeader is not supported for phase 0 beacon state") +} diff --git a/beacon-chain/state/state-native/v1/unsupported_setters.go b/beacon-chain/state/state-native/v1/unsupported_setters.go new file mode 100644 index 0000000000..251253519a --- /dev/null +++ b/beacon-chain/state/state-native/v1/unsupported_setters.go @@ -0,0 +1,51 @@ +package v1 + +import ( + "github.com/pkg/errors" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" +) + +// AppendCurrentParticipationBits is not supported for phase 0 beacon state. +func (*BeaconState) AppendCurrentParticipationBits(_ byte) error { + return errors.New("AppendCurrentParticipationBits is not supported for phase 0 beacon state") +} + +// AppendPreviousParticipationBits is not supported for phase 0 beacon state. +func (*BeaconState) AppendPreviousParticipationBits(_ byte) error { + return errors.New("AppendPreviousParticipationBits is not supported for phase 0 beacon state") +} + +// AppendInactivityScore is not supported for phase 0 beacon state. +func (*BeaconState) AppendInactivityScore(_ uint64) error { + return errors.New("AppendInactivityScore is not supported for phase 0 beacon state") +} + +// SetCurrentSyncCommittee is not supported for phase 0 beacon state. +func (*BeaconState) SetCurrentSyncCommittee(_ *ethpb.SyncCommittee) error { + return errors.New("SetCurrentSyncCommittee is not supported for phase 0 beacon state") +} + +// SetNextSyncCommittee is not supported for phase 0 beacon state. +func (*BeaconState) SetNextSyncCommittee(_ *ethpb.SyncCommittee) error { + return errors.New("SetNextSyncCommittee is not supported for phase 0 beacon state") +} + +// SetPreviousParticipationBits is not supported for phase 0 beacon state. +func (*BeaconState) SetPreviousParticipationBits(_ []byte) error { + return errors.New("SetPreviousParticipationBits is not supported for phase 0 beacon state") +} + +// SetCurrentParticipationBits is not supported for phase 0 beacon state. +func (*BeaconState) SetCurrentParticipationBits(_ []byte) error { + return errors.New("SetCurrentParticipationBits is not supported for phase 0 beacon state") +} + +// SetInactivityScores is not supported for phase 0 beacon state. +func (*BeaconState) SetInactivityScores(_ []uint64) error { + return errors.New("SetInactivityScores is not supported for phase 0 beacon state") +} + +// SetLatestExecutionPayloadHeader is not supported for phase 0 beacon state. +func (*BeaconState) SetLatestExecutionPayloadHeader(val *ethpb.ExecutionPayloadHeader) error { + return errors.New("SetLatestExecutionPayloadHeader is not supported for phase 0 beacon state") +} diff --git a/beacon-chain/state/state-native/v2/BUILD.bazel b/beacon-chain/state/state-native/v2/BUILD.bazel new file mode 100644 index 0000000000..7b3d1a6810 --- /dev/null +++ b/beacon-chain/state/state-native/v2/BUILD.bazel @@ -0,0 +1,91 @@ +load("@prysm//tools/go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = [ + "deprecated_getters.go", + "deprecated_setters.go", + "field_roots.go", + "getters_block.go", + "getters_checkpoint.go", + "getters_eth1.go", + "getters_misc.go", + "getters_participation.go", + "getters_randao.go", + "getters_state.go", + "getters_sync_committee.go", + "getters_validator.go", + "proofs.go", + "setters_block.go", + "setters_checkpoint.go", + "setters_eth1.go", + "setters_misc.go", + "setters_participation.go", + "setters_randao.go", + "setters_state.go", + "setters_sync_committee.go", + "setters_validator.go", + "state_trie.go", + "types.go", + ], + importpath = "github.com/prysmaticlabs/prysm/beacon-chain/state/state-native/v2", + visibility = [ + "//beacon-chain:__subpackages__", + "//proto/migration:__subpackages__", + "//testing/spectest:__subpackages__", + "//testing/util:__pkg__", + ], + deps = [ + "//beacon-chain/state:go_default_library", + "//beacon-chain/state/state-native/fieldtrie:go_default_library", + "//beacon-chain/state/state-native/v1:go_default_library", + "//beacon-chain/state/stateutil:go_default_library", + "//beacon-chain/state/types:go_default_library", + "//config/features:go_default_library", + "//config/fieldparams:go_default_library", + "//config/params:go_default_library", + "//container/slice:go_default_library", + "//crypto/hash:go_default_library", + "//encoding/bytesutil:go_default_library", + "//encoding/ssz:go_default_library", + "//proto/prysm/v1alpha1:go_default_library", + "//runtime/version:go_default_library", + "@com_github_pkg_errors//:go_default_library", + "@com_github_prysmaticlabs_eth2_types//:go_default_library", + "@com_github_prysmaticlabs_go_bitfield//:go_default_library", + "@io_opencensus_go//trace:go_default_library", + "@org_golang_google_protobuf//proto:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = [ + "deprecated_getters_test.go", + "deprecated_setters_test.go", + "getters_block_test.go", + "getters_test.go", + "getters_validator_test.go", + "proofs_test.go", + "setters_test.go", + "state_trie_test.go", + ], + embed = [":go_default_library"], + deps = [ + "//beacon-chain/state/state-native/v1:go_default_library", + "//beacon-chain/state/stateutil:go_default_library", + "//beacon-chain/state/types:go_default_library", + "//config/features:go_default_library", + "//config/fieldparams:go_default_library", + "//config/params:go_default_library", + "//container/trie:go_default_library", + "//crypto/bls:go_default_library", + "//encoding/bytesutil:go_default_library", + "//proto/prysm/v1alpha1:go_default_library", + "//testing/assert:go_default_library", + "//testing/require:go_default_library", + "//testing/util:go_default_library", + "@com_github_prysmaticlabs_eth2_types//:go_default_library", + "@com_github_prysmaticlabs_go_bitfield//:go_default_library", + ], +) diff --git a/beacon-chain/state/state-native/v2/deprecated_getters.go b/beacon-chain/state/state-native/v2/deprecated_getters.go new file mode 100644 index 0000000000..2257b577e8 --- /dev/null +++ b/beacon-chain/state/state-native/v2/deprecated_getters.go @@ -0,0 +1,21 @@ +package v2 + +import ( + "github.com/pkg/errors" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" +) + +// PreviousEpochAttestations is not supported for HF1 beacon state. +func (*BeaconState) PreviousEpochAttestations() ([]*ethpb.PendingAttestation, error) { + return nil, errors.New("PreviousEpochAttestations is not supported for hard fork 1 beacon state") +} + +// CurrentEpochAttestations is not supported for HF1 beacon state. +func (*BeaconState) CurrentEpochAttestations() ([]*ethpb.PendingAttestation, error) { + return nil, errors.New("CurrentEpochAttestations is not supported for hard fork 1 beacon state") +} + +// LatestExecutionPayloadHeader is not supported for hard fork 1 beacon state. +func (*BeaconState) LatestExecutionPayloadHeader() (*ethpb.ExecutionPayloadHeader, error) { + return nil, errors.New("LatestExecutionPayloadHeader is not supported for hard fork 1 beacon state") +} diff --git a/beacon-chain/state/state-native/v2/deprecated_getters_test.go b/beacon-chain/state/state-native/v2/deprecated_getters_test.go new file mode 100644 index 0000000000..6e4aafe62d --- /dev/null +++ b/beacon-chain/state/state-native/v2/deprecated_getters_test.go @@ -0,0 +1,19 @@ +package v2 + +import ( + "testing" + + "github.com/prysmaticlabs/prysm/testing/require" +) + +func TestBeaconState_CurrentEpochAttestations(t *testing.T) { + s := &BeaconState{} + _, err := s.CurrentEpochAttestations() + require.ErrorContains(t, "CurrentEpochAttestations is not supported for hard fork 1 beacon state", err) +} + +func TestBeaconState_PreviousEpochAttestations(t *testing.T) { + s := &BeaconState{} + _, err := s.PreviousEpochAttestations() + require.ErrorContains(t, "PreviousEpochAttestations is not supported for hard fork 1 beacon state", err) +} diff --git a/beacon-chain/state/state-native/v2/deprecated_setters.go b/beacon-chain/state/state-native/v2/deprecated_setters.go new file mode 100644 index 0000000000..449bb82098 --- /dev/null +++ b/beacon-chain/state/state-native/v2/deprecated_setters.go @@ -0,0 +1,36 @@ +package v2 + +import ( + "github.com/pkg/errors" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" +) + +// SetPreviousEpochAttestations is not supported for HF1 beacon state. +func (*BeaconState) SetPreviousEpochAttestations(_ []*ethpb.PendingAttestation) error { + return errors.New("SetPreviousEpochAttestations is not supported for hard fork 1 beacon state") +} + +// SetCurrentEpochAttestations is not supported for HF1 beacon state. +func (*BeaconState) SetCurrentEpochAttestations(_ []*ethpb.PendingAttestation) error { + return errors.New("SetCurrentEpochAttestations is not supported for hard fork 1 beacon state") +} + +// AppendCurrentEpochAttestations is not supported for HF1 beacon state. +func (*BeaconState) AppendCurrentEpochAttestations(_ *ethpb.PendingAttestation) error { + return errors.New("AppendCurrentEpochAttestations is not supported for hard fork 1 beacon state") +} + +// AppendPreviousEpochAttestations is not supported for HF1 beacon state. +func (*BeaconState) AppendPreviousEpochAttestations(_ *ethpb.PendingAttestation) error { + return errors.New("AppendPreviousEpochAttestations is not supported for hard fork 1 beacon state") +} + +// RotateAttestations is not supported for HF1 beacon state. +func (*BeaconState) RotateAttestations() error { + return errors.New("RotateAttestations is not supported for hard fork 1 beacon state") +} + +// SetLatestExecutionPayloadHeader is not supported for hard fork 1 beacon state. +func (*BeaconState) SetLatestExecutionPayloadHeader(_ *ethpb.ExecutionPayloadHeader) error { + return errors.New("SetLatestExecutionPayloadHeader is not supported for hard fork 1 beacon state") +} diff --git a/beacon-chain/state/state-native/v2/deprecated_setters_test.go b/beacon-chain/state/state-native/v2/deprecated_setters_test.go new file mode 100644 index 0000000000..718c4f07b1 --- /dev/null +++ b/beacon-chain/state/state-native/v2/deprecated_setters_test.go @@ -0,0 +1,27 @@ +package v2 + +import ( + "testing" + + "github.com/prysmaticlabs/prysm/testing/require" +) + +func TestBeaconState_AppendCurrentEpochAttestations(t *testing.T) { + s := &BeaconState{} + require.ErrorContains(t, "AppendCurrentEpochAttestations is not supported for hard fork 1 beacon state", s.AppendCurrentEpochAttestations(nil)) +} + +func TestBeaconState_AppendPreviousEpochAttestations(t *testing.T) { + s := &BeaconState{} + require.ErrorContains(t, "AppendPreviousEpochAttestations is not supported for hard fork 1 beacon state", s.AppendPreviousEpochAttestations(nil)) +} + +func TestBeaconState_SetCurrentEpochAttestations(t *testing.T) { + s := &BeaconState{} + require.ErrorContains(t, "SetCurrentEpochAttestations is not supported for hard fork 1 beacon state", s.SetCurrentEpochAttestations(nil)) +} + +func TestBeaconState_SetPreviousEpochAttestations(t *testing.T) { + s := &BeaconState{} + require.ErrorContains(t, "SetPreviousEpochAttestations is not supported for hard fork 1 beacon state", s.SetPreviousEpochAttestations(nil)) +} diff --git a/beacon-chain/state/state-native/v2/field_roots.go b/beacon-chain/state/state-native/v2/field_roots.go new file mode 100644 index 0000000000..1b5a438bc1 --- /dev/null +++ b/beacon-chain/state/state-native/v2/field_roots.go @@ -0,0 +1,18 @@ +package v2 + +import ( + "context" + + "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" + "github.com/prysmaticlabs/prysm/config/features" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" +) + +// computeFieldRoots returns the hash tree root computations of every field in +// the beacon state as a list of 32 byte roots. +func computeFieldRoots(ctx context.Context, state *ethpb.BeaconStateAltair) ([][]byte, error) { + if features.Get().EnableSSZCache { + return stateutil.CachedHasher.ComputeFieldRootsWithHasherAltair(ctx, state) + } + return stateutil.NocachedHasher.ComputeFieldRootsWithHasherAltair(ctx, state) +} diff --git a/beacon-chain/state/state-native/v2/getters_block.go b/beacon-chain/state/state-native/v2/getters_block.go new file mode 100644 index 0000000000..ad321824b5 --- /dev/null +++ b/beacon-chain/state/state-native/v2/getters_block.go @@ -0,0 +1,99 @@ +package v2 + +import ( + "github.com/prysmaticlabs/prysm/encoding/bytesutil" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" +) + +// LatestBlockHeader stored within the beacon state. +func (b *BeaconState) LatestBlockHeader() *ethpb.BeaconBlockHeader { + if !b.hasInnerState() { + return nil + } + if b.state.LatestBlockHeader == nil { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.latestBlockHeader() +} + +// latestBlockHeader stored within the beacon state. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) latestBlockHeader() *ethpb.BeaconBlockHeader { + if !b.hasInnerState() { + return nil + } + if b.state.LatestBlockHeader == nil { + return nil + } + + hdr := ðpb.BeaconBlockHeader{ + Slot: b.state.LatestBlockHeader.Slot, + ProposerIndex: b.state.LatestBlockHeader.ProposerIndex, + } + + parentRoot := make([]byte, len(b.state.LatestBlockHeader.ParentRoot)) + bodyRoot := make([]byte, len(b.state.LatestBlockHeader.BodyRoot)) + stateRoot := make([]byte, len(b.state.LatestBlockHeader.StateRoot)) + + copy(parentRoot, b.state.LatestBlockHeader.ParentRoot) + copy(bodyRoot, b.state.LatestBlockHeader.BodyRoot) + copy(stateRoot, b.state.LatestBlockHeader.StateRoot) + hdr.ParentRoot = parentRoot + hdr.BodyRoot = bodyRoot + hdr.StateRoot = stateRoot + return hdr +} + +// BlockRoots kept track of in the beacon state. +func (b *BeaconState) BlockRoots() [][]byte { + if !b.hasInnerState() { + return nil + } + if b.state.BlockRoots == nil { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.blockRoots() +} + +// blockRoots kept track of in the beacon state. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) blockRoots() [][]byte { + if !b.hasInnerState() { + return nil + } + return bytesutil.SafeCopy2dBytes(b.state.BlockRoots) +} + +// BlockRootAtIndex retrieves a specific block root based on an +// input index value. +func (b *BeaconState) BlockRootAtIndex(idx uint64) ([]byte, error) { + if !b.hasInnerState() { + return nil, ErrNilInnerState + } + if b.state.BlockRoots == nil { + return nil, nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.blockRootAtIndex(idx) +} + +// blockRootAtIndex retrieves a specific block root based on an +// input index value. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) blockRootAtIndex(idx uint64) ([]byte, error) { + if !b.hasInnerState() { + return nil, ErrNilInnerState + } + return bytesutil.SafeCopyRootAtIndex(b.state.BlockRoots, idx) +} diff --git a/beacon-chain/state/state-native/v2/getters_block_test.go b/beacon-chain/state/state-native/v2/getters_block_test.go new file mode 100644 index 0000000000..923bd9b5ec --- /dev/null +++ b/beacon-chain/state/state-native/v2/getters_block_test.go @@ -0,0 +1,60 @@ +package v2 + +import ( + "testing" + + fieldparams "github.com/prysmaticlabs/prysm/config/fieldparams" + "github.com/prysmaticlabs/prysm/encoding/bytesutil" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/testing/require" +) + +func TestBeaconState_LatestBlockHeader(t *testing.T) { + s, err := InitializeFromProto(ðpb.BeaconStateAltair{}) + require.NoError(t, err) + got := s.LatestBlockHeader() + require.DeepEqual(t, (*ethpb.BeaconBlockHeader)(nil), got) + + want := ðpb.BeaconBlockHeader{Slot: 100} + s, err = InitializeFromProto(ðpb.BeaconStateAltair{LatestBlockHeader: want}) + require.NoError(t, err) + got = s.LatestBlockHeader() + require.DeepEqual(t, want, got) + + // Test copy does not mutate. + got.Slot = 101 + require.DeepNotEqual(t, want, got) +} + +func TestBeaconState_BlockRoots(t *testing.T) { + s, err := InitializeFromProto(ðpb.BeaconStateAltair{}) + require.NoError(t, err) + got := s.BlockRoots() + require.DeepEqual(t, ([][]byte)(nil), got) + + want := [][]byte{{'a'}} + s, err = InitializeFromProto(ðpb.BeaconStateAltair{BlockRoots: want}) + require.NoError(t, err) + got = s.BlockRoots() + require.DeepEqual(t, want, got) + + // Test copy does not mutate. + got[0][0] = 'b' + require.DeepNotEqual(t, want, got) +} + +func TestBeaconState_BlockRootAtIndex(t *testing.T) { + s, err := InitializeFromProto(ðpb.BeaconStateAltair{}) + require.NoError(t, err) + got, err := s.BlockRootAtIndex(0) + require.NoError(t, err) + require.DeepEqual(t, ([]byte)(nil), got) + + r := [][]byte{{'a'}} + s, err = InitializeFromProto(ðpb.BeaconStateAltair{BlockRoots: r}) + require.NoError(t, err) + got, err = s.BlockRootAtIndex(0) + require.NoError(t, err) + want := bytesutil.PadTo([]byte{'a'}, fieldparams.RootLength) + require.DeepSSZEqual(t, want, got) +} diff --git a/beacon-chain/state/state-native/v2/getters_checkpoint.go b/beacon-chain/state/state-native/v2/getters_checkpoint.go new file mode 100644 index 0000000000..ebea57ec98 --- /dev/null +++ b/beacon-chain/state/state-native/v2/getters_checkpoint.go @@ -0,0 +1,160 @@ +package v2 + +import ( + "bytes" + + types "github.com/prysmaticlabs/eth2-types" + "github.com/prysmaticlabs/go-bitfield" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" +) + +// JustificationBits marking which epochs have been justified in the beacon chain. +func (b *BeaconState) JustificationBits() bitfield.Bitvector4 { + if !b.hasInnerState() { + return nil + } + if b.state.JustificationBits == nil { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.justificationBits() +} + +// justificationBits marking which epochs have been justified in the beacon chain. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) justificationBits() bitfield.Bitvector4 { + if !b.hasInnerState() { + return nil + } + if b.state.JustificationBits == nil { + return nil + } + + res := make([]byte, len(b.state.JustificationBits.Bytes())) + copy(res, b.state.JustificationBits.Bytes()) + return res +} + +// PreviousJustifiedCheckpoint denoting an epoch and block root. +func (b *BeaconState) PreviousJustifiedCheckpoint() *ethpb.Checkpoint { + if !b.hasInnerState() { + return nil + } + if b.state.PreviousJustifiedCheckpoint == nil { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.previousJustifiedCheckpoint() +} + +// previousJustifiedCheckpoint denoting an epoch and block root. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) previousJustifiedCheckpoint() *ethpb.Checkpoint { + if !b.hasInnerState() { + return nil + } + + return ethpb.CopyCheckpoint(b.state.PreviousJustifiedCheckpoint) +} + +// CurrentJustifiedCheckpoint denoting an epoch and block root. +func (b *BeaconState) CurrentJustifiedCheckpoint() *ethpb.Checkpoint { + if !b.hasInnerState() { + return nil + } + if b.state.CurrentJustifiedCheckpoint == nil { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.currentJustifiedCheckpoint() +} + +// currentJustifiedCheckpoint denoting an epoch and block root. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) currentJustifiedCheckpoint() *ethpb.Checkpoint { + if !b.hasInnerState() { + return nil + } + + return ethpb.CopyCheckpoint(b.state.CurrentJustifiedCheckpoint) +} + +// MatchCurrentJustifiedCheckpoint returns true if input justified checkpoint matches +// the current justified checkpoint in state. +func (b *BeaconState) MatchCurrentJustifiedCheckpoint(c *ethpb.Checkpoint) bool { + if !b.hasInnerState() { + return false + } + if b.state.CurrentJustifiedCheckpoint == nil { + return false + } + + if c.Epoch != b.state.CurrentJustifiedCheckpoint.Epoch { + return false + } + return bytes.Equal(c.Root, b.state.CurrentJustifiedCheckpoint.Root) +} + +// MatchPreviousJustifiedCheckpoint returns true if the input justified checkpoint matches +// the previous justified checkpoint in state. +func (b *BeaconState) MatchPreviousJustifiedCheckpoint(c *ethpb.Checkpoint) bool { + if !b.hasInnerState() { + return false + } + if b.state.PreviousJustifiedCheckpoint == nil { + return false + } + + if c.Epoch != b.state.PreviousJustifiedCheckpoint.Epoch { + return false + } + return bytes.Equal(c.Root, b.state.PreviousJustifiedCheckpoint.Root) +} + +// FinalizedCheckpoint denoting an epoch and block root. +func (b *BeaconState) FinalizedCheckpoint() *ethpb.Checkpoint { + if !b.hasInnerState() { + return nil + } + if b.state.FinalizedCheckpoint == nil { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.finalizedCheckpoint() +} + +// finalizedCheckpoint denoting an epoch and block root. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) finalizedCheckpoint() *ethpb.Checkpoint { + if !b.hasInnerState() { + return nil + } + + return ethpb.CopyCheckpoint(b.state.FinalizedCheckpoint) +} + +// FinalizedCheckpointEpoch returns the epoch value of the finalized checkpoint. +func (b *BeaconState) FinalizedCheckpointEpoch() types.Epoch { + if !b.hasInnerState() { + return 0 + } + if b.state.FinalizedCheckpoint == nil { + return 0 + } + b.lock.RLock() + defer b.lock.RUnlock() + + return b.state.FinalizedCheckpoint.Epoch +} diff --git a/beacon-chain/state/state-native/v2/getters_eth1.go b/beacon-chain/state/state-native/v2/getters_eth1.go new file mode 100644 index 0000000000..2b35eed94d --- /dev/null +++ b/beacon-chain/state/state-native/v2/getters_eth1.go @@ -0,0 +1,91 @@ +package v2 + +import ( + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" +) + +// Eth1Data corresponding to the proof-of-work chain information stored in the beacon state. +func (b *BeaconState) Eth1Data() *ethpb.Eth1Data { + if !b.hasInnerState() { + return nil + } + if b.state.Eth1Data == nil { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.eth1Data() +} + +// eth1Data corresponding to the proof-of-work chain information stored in the beacon state. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) eth1Data() *ethpb.Eth1Data { + if !b.hasInnerState() { + return nil + } + if b.state.Eth1Data == nil { + return nil + } + + return ethpb.CopyETH1Data(b.state.Eth1Data) +} + +// Eth1DataVotes corresponds to votes from Ethereum on the canonical proof-of-work chain +// data retrieved from eth1. +func (b *BeaconState) Eth1DataVotes() []*ethpb.Eth1Data { + if !b.hasInnerState() { + return nil + } + if b.state.Eth1DataVotes == nil { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.eth1DataVotes() +} + +// eth1DataVotes corresponds to votes from Ethereum on the canonical proof-of-work chain +// data retrieved from eth1. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) eth1DataVotes() []*ethpb.Eth1Data { + if !b.hasInnerState() { + return nil + } + if b.state.Eth1DataVotes == nil { + return nil + } + + res := make([]*ethpb.Eth1Data, len(b.state.Eth1DataVotes)) + for i := 0; i < len(res); i++ { + res[i] = ethpb.CopyETH1Data(b.state.Eth1DataVotes[i]) + } + return res +} + +// Eth1DepositIndex corresponds to the index of the deposit made to the +// validator deposit contract at the time of this state's eth1 data. +func (b *BeaconState) Eth1DepositIndex() uint64 { + if !b.hasInnerState() { + return 0 + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.eth1DepositIndex() +} + +// eth1DepositIndex corresponds to the index of the deposit made to the +// validator deposit contract at the time of this state's eth1 data. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) eth1DepositIndex() uint64 { + if !b.hasInnerState() { + return 0 + } + + return b.state.Eth1DepositIndex +} diff --git a/beacon-chain/state/state-native/v2/getters_misc.go b/beacon-chain/state/state-native/v2/getters_misc.go new file mode 100644 index 0000000000..20be2f386b --- /dev/null +++ b/beacon-chain/state/state-native/v2/getters_misc.go @@ -0,0 +1,212 @@ +package v2 + +import ( + "time" + + types "github.com/prysmaticlabs/eth2-types" + fieldparams "github.com/prysmaticlabs/prysm/config/fieldparams" + "github.com/prysmaticlabs/prysm/config/params" + "github.com/prysmaticlabs/prysm/encoding/bytesutil" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/runtime/version" +) + +// GenesisTime of the beacon state as a uint64. +func (b *BeaconState) GenesisTime() uint64 { + if !b.hasInnerState() { + return 0 + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.genesisTime() +} + +// genesisTime of the beacon state as a uint64. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) genesisTime() uint64 { + if !b.hasInnerState() { + return 0 + } + + return b.state.GenesisTime +} + +// GenesisValidatorRoot of the beacon state. +func (b *BeaconState) GenesisValidatorRoot() []byte { + if !b.hasInnerState() { + return nil + } + if b.state.GenesisValidatorsRoot == nil { + return params.BeaconConfig().ZeroHash[:] + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.genesisValidatorRoot() +} + +// genesisValidatorRoot of the beacon state. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) genesisValidatorRoot() []byte { + if !b.hasInnerState() { + return nil + } + if b.state.GenesisValidatorsRoot == nil { + return params.BeaconConfig().ZeroHash[:] + } + + root := make([]byte, fieldparams.RootLength) + copy(root, b.state.GenesisValidatorsRoot) + return root +} + +// GenesisUnixTime returns the genesis time as time.Time. +func (b *BeaconState) GenesisUnixTime() time.Time { + if !b.hasInnerState() { + return time.Unix(0, 0) + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.genesisUnixTime() +} + +// genesisUnixTime returns the genesis time as time.Time. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) genesisUnixTime() time.Time { + if !b.hasInnerState() { + return time.Unix(0, 0) + } + + return time.Unix(int64(b.state.GenesisTime), 0) +} + +// ParentRoot is a convenience method to access state.LatestBlockRoot.ParentRoot. +func (b *BeaconState) ParentRoot() [32]byte { + if !b.hasInnerState() { + return [32]byte{} + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.parentRoot() +} + +// parentRoot is a convenience method to access state.LatestBlockRoot.ParentRoot. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) parentRoot() [32]byte { + if !b.hasInnerState() { + return [32]byte{} + } + + parentRoot := [32]byte{} + copy(parentRoot[:], b.state.LatestBlockHeader.ParentRoot) + return parentRoot +} + +// Version of the beacon state. This method +// is strictly meant to be used without a lock +// internally. +func (_ *BeaconState) Version() int { + return version.Altair +} + +// Slot of the current beacon chain state. +func (b *BeaconState) Slot() types.Slot { + if !b.hasInnerState() { + return 0 + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.slot() +} + +// slot of the current beacon chain state. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) slot() types.Slot { + if !b.hasInnerState() { + return 0 + } + + return b.state.Slot +} + +// Fork version of the beacon chain. +func (b *BeaconState) Fork() *ethpb.Fork { + if !b.hasInnerState() { + return nil + } + if b.state.Fork == nil { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.fork() +} + +// fork version of the beacon chain. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) fork() *ethpb.Fork { + if !b.hasInnerState() { + return nil + } + if b.state.Fork == nil { + return nil + } + + prevVersion := make([]byte, len(b.state.Fork.PreviousVersion)) + copy(prevVersion, b.state.Fork.PreviousVersion) + currVersion := make([]byte, len(b.state.Fork.CurrentVersion)) + copy(currVersion, b.state.Fork.CurrentVersion) + return ðpb.Fork{ + PreviousVersion: prevVersion, + CurrentVersion: currVersion, + Epoch: b.state.Fork.Epoch, + } +} + +// HistoricalRoots based on epochs stored in the beacon state. +func (b *BeaconState) HistoricalRoots() [][]byte { + if !b.hasInnerState() { + return nil + } + if b.state.HistoricalRoots == nil { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.historicalRoots() +} + +// historicalRoots based on epochs stored in the beacon state. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) historicalRoots() [][]byte { + if !b.hasInnerState() { + return nil + } + return bytesutil.SafeCopy2dBytes(b.state.HistoricalRoots) +} + +// balancesLength returns the length of the balances slice. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) balancesLength() int { + if !b.hasInnerState() { + return 0 + } + if b.state.Balances == nil { + return 0 + } + + return len(b.state.Balances) +} diff --git a/beacon-chain/state/state-native/v2/getters_participation.go b/beacon-chain/state/state-native/v2/getters_participation.go new file mode 100644 index 0000000000..9d62b0f203 --- /dev/null +++ b/beacon-chain/state/state-native/v2/getters_participation.go @@ -0,0 +1,53 @@ +package v2 + +// CurrentEpochParticipation corresponding to participation bits on the beacon chain. +func (b *BeaconState) CurrentEpochParticipation() ([]byte, error) { + if !b.hasInnerState() { + return nil, nil + } + if b.state.CurrentEpochParticipation == nil { + return nil, nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.currentEpochParticipation(), nil +} + +// PreviousEpochParticipation corresponding to participation bits on the beacon chain. +func (b *BeaconState) PreviousEpochParticipation() ([]byte, error) { + if !b.hasInnerState() { + return nil, nil + } + if b.state.PreviousEpochParticipation == nil { + return nil, nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.previousEpochParticipation(), nil +} + +// currentEpochParticipation corresponding to participation bits on the beacon chain. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) currentEpochParticipation() []byte { + if !b.hasInnerState() { + return nil + } + tmp := make([]byte, len(b.state.CurrentEpochParticipation)) + copy(tmp, b.state.CurrentEpochParticipation) + return tmp +} + +// previousEpochParticipation corresponding to participation bits on the beacon chain. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) previousEpochParticipation() []byte { + if !b.hasInnerState() { + return nil + } + tmp := make([]byte, len(b.state.PreviousEpochParticipation)) + copy(tmp, b.state.PreviousEpochParticipation) + return tmp +} diff --git a/beacon-chain/state/state-native/v2/getters_randao.go b/beacon-chain/state/state-native/v2/getters_randao.go new file mode 100644 index 0000000000..8f74d0168d --- /dev/null +++ b/beacon-chain/state/state-native/v2/getters_randao.go @@ -0,0 +1,85 @@ +package v2 + +import ( + "github.com/prysmaticlabs/prysm/encoding/bytesutil" +) + +// RandaoMixes of block proposers on the beacon chain. +func (b *BeaconState) RandaoMixes() [][]byte { + if !b.hasInnerState() { + return nil + } + if b.state.RandaoMixes == nil { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.randaoMixes() +} + +// randaoMixes of block proposers on the beacon chain. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) randaoMixes() [][]byte { + if !b.hasInnerState() { + return nil + } + + return bytesutil.SafeCopy2dBytes(b.state.RandaoMixes) +} + +// RandaoMixAtIndex retrieves a specific block root based on an +// input index value. +func (b *BeaconState) RandaoMixAtIndex(idx uint64) ([]byte, error) { + if !b.hasInnerState() { + return nil, ErrNilInnerState + } + if b.state.RandaoMixes == nil { + return nil, nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.randaoMixAtIndex(idx) +} + +// randaoMixAtIndex retrieves a specific block root based on an +// input index value. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) randaoMixAtIndex(idx uint64) ([]byte, error) { + if !b.hasInnerState() { + return nil, ErrNilInnerState + } + + return bytesutil.SafeCopyRootAtIndex(b.state.RandaoMixes, idx) +} + +// RandaoMixesLength returns the length of the randao mixes slice. +func (b *BeaconState) RandaoMixesLength() int { + if !b.hasInnerState() { + return 0 + } + if b.state.RandaoMixes == nil { + return 0 + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.randaoMixesLength() +} + +// randaoMixesLength returns the length of the randao mixes slice. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) randaoMixesLength() int { + if !b.hasInnerState() { + return 0 + } + if b.state.RandaoMixes == nil { + return 0 + } + + return len(b.state.RandaoMixes) +} diff --git a/beacon-chain/state/state-native/v2/getters_state.go b/beacon-chain/state/state-native/v2/getters_state.go new file mode 100644 index 0000000000..f097a7962f --- /dev/null +++ b/beacon-chain/state/state-native/v2/getters_state.go @@ -0,0 +1,126 @@ +package v2 + +import ( + "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/encoding/bytesutil" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" +) + +// InnerStateUnsafe returns the pointer value of the underlying +// beacon state proto object, bypassing immutability. Use with care. +func (b *BeaconState) InnerStateUnsafe() interface{} { + if b == nil { + return nil + } + return b.state +} + +// CloneInnerState the beacon state into a protobuf for usage. +func (b *BeaconState) CloneInnerState() interface{} { + if b == nil || b.state == nil { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + return ðpb.BeaconStateAltair{ + GenesisTime: b.genesisTime(), + GenesisValidatorsRoot: b.genesisValidatorRoot(), + Slot: b.slot(), + Fork: b.fork(), + LatestBlockHeader: b.latestBlockHeader(), + BlockRoots: b.blockRoots(), + StateRoots: b.stateRoots(), + HistoricalRoots: b.historicalRoots(), + Eth1Data: b.eth1Data(), + Eth1DataVotes: b.eth1DataVotes(), + Eth1DepositIndex: b.eth1DepositIndex(), + Validators: b.validators(), + Balances: b.balances(), + RandaoMixes: b.randaoMixes(), + Slashings: b.slashings(), + CurrentEpochParticipation: b.currentEpochParticipation(), + PreviousEpochParticipation: b.previousEpochParticipation(), + JustificationBits: b.justificationBits(), + PreviousJustifiedCheckpoint: b.previousJustifiedCheckpoint(), + CurrentJustifiedCheckpoint: b.currentJustifiedCheckpoint(), + FinalizedCheckpoint: b.finalizedCheckpoint(), + InactivityScores: b.inactivityScores(), + CurrentSyncCommittee: b.currentSyncCommittee(), + NextSyncCommittee: b.nextSyncCommittee(), + } +} + +// hasInnerState detects if the internal reference to the state data structure +// is populated correctly. Returns false if nil. +func (b *BeaconState) hasInnerState() bool { + return b != nil && b.state != nil +} + +// StateRoots kept track of in the beacon state. +func (b *BeaconState) StateRoots() [][]byte { + if !b.hasInnerState() { + return nil + } + if b.state.StateRoots == nil { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.stateRoots() +} + +// StateRoots kept track of in the beacon state. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) stateRoots() [][]byte { + if !b.hasInnerState() { + return nil + } + return bytesutil.SafeCopy2dBytes(b.state.StateRoots) +} + +// StateRootAtIndex retrieves a specific state root based on an +// input index value. +func (b *BeaconState) StateRootAtIndex(idx uint64) ([]byte, error) { + if !b.hasInnerState() { + return nil, ErrNilInnerState + } + if b.state.StateRoots == nil { + return nil, nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.stateRootAtIndex(idx) +} + +// stateRootAtIndex retrieves a specific state root based on an +// input index value. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) stateRootAtIndex(idx uint64) ([]byte, error) { + if !b.hasInnerState() { + return nil, ErrNilInnerState + } + return bytesutil.SafeCopyRootAtIndex(b.state.StateRoots, idx) +} + +// MarshalSSZ marshals the underlying beacon state to bytes. +func (b *BeaconState) MarshalSSZ() ([]byte, error) { + if !b.hasInnerState() { + return nil, errors.New("nil beacon state") + } + return b.state.MarshalSSZ() +} + +// ProtobufBeaconState transforms an input into beacon state hard fork 1 in the form of protobuf. +// Error is returned if the input is not type protobuf beacon state. +func ProtobufBeaconState(s interface{}) (*ethpb.BeaconStateAltair, error) { + pbState, ok := s.(*ethpb.BeaconStateAltair) + if !ok { + return nil, errors.New("input is not type pb.BeaconStateAltair") + } + return pbState, nil +} diff --git a/beacon-chain/state/state-native/v2/getters_sync_committee.go b/beacon-chain/state/state-native/v2/getters_sync_committee.go new file mode 100644 index 0000000000..7717cf2b2a --- /dev/null +++ b/beacon-chain/state/state-native/v2/getters_sync_committee.go @@ -0,0 +1,69 @@ +package v2 + +import ( + "github.com/prysmaticlabs/prysm/encoding/bytesutil" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" +) + +// currentSyncCommittee of the current sync committee in beacon chain state. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) currentSyncCommittee() *ethpb.SyncCommittee { + if !b.hasInnerState() { + return nil + } + + return CopySyncCommittee(b.state.CurrentSyncCommittee) +} + +// nextSyncCommittee of the next sync committee in beacon chain state. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) nextSyncCommittee() *ethpb.SyncCommittee { + if !b.hasInnerState() { + return nil + } + + return CopySyncCommittee(b.state.NextSyncCommittee) +} + +// CurrentSyncCommittee of the current sync committee in beacon chain state. +func (b *BeaconState) CurrentSyncCommittee() (*ethpb.SyncCommittee, error) { + if !b.hasInnerState() { + return nil, nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + if b.state.CurrentSyncCommittee == nil { + return nil, nil + } + + return b.currentSyncCommittee(), nil +} + +// NextSyncCommittee of the next sync committee in beacon chain state. +func (b *BeaconState) NextSyncCommittee() (*ethpb.SyncCommittee, error) { + if !b.hasInnerState() { + return nil, nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + if b.state.NextSyncCommittee == nil { + return nil, nil + } + + return b.nextSyncCommittee(), nil +} + +// CopySyncCommittee copies the provided sync committee object. +func CopySyncCommittee(data *ethpb.SyncCommittee) *ethpb.SyncCommittee { + if data == nil { + return nil + } + return ðpb.SyncCommittee{ + Pubkeys: bytesutil.SafeCopy2dBytes(data.Pubkeys), + AggregatePubkey: bytesutil.SafeCopyBytes(data.AggregatePubkey), + } +} diff --git a/beacon-chain/state/state-native/v2/getters_test.go b/beacon-chain/state/state-native/v2/getters_test.go new file mode 100644 index 0000000000..ef16b29ff0 --- /dev/null +++ b/beacon-chain/state/state-native/v2/getters_test.go @@ -0,0 +1,193 @@ +package v2 + +import ( + "runtime/debug" + "sync" + "testing" + + types "github.com/prysmaticlabs/eth2-types" + fieldparams "github.com/prysmaticlabs/prysm/config/fieldparams" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/testing/assert" + "github.com/prysmaticlabs/prysm/testing/require" +) + +func TestBeaconState_SlotDataRace(t *testing.T) { + headState, err := InitializeFromProto(ðpb.BeaconStateAltair{Slot: 1}) + require.NoError(t, err) + + wg := sync.WaitGroup{} + wg.Add(2) + go func() { + require.NoError(t, headState.SetSlot(0)) + wg.Done() + }() + go func() { + headState.Slot() + wg.Done() + }() + + wg.Wait() +} + +func TestNilState_NoPanic(t *testing.T) { + var st *BeaconState + defer func() { + if r := recover(); r != nil { + t.Errorf("Method panicked when it was not supposed to: %v\n%v\n", r, string(debug.Stack())) + } + }() + // retrieve elements from nil state + _ = st.GenesisTime() + _ = st.GenesisValidatorRoot() + _ = st.GenesisUnixTime() + _ = st.GenesisValidatorRoot() + _ = st.Slot() + _ = st.Fork() + _ = st.LatestBlockHeader() + _ = st.ParentRoot() + _ = st.BlockRoots() + _, err := st.BlockRootAtIndex(0) + _ = err + _ = st.StateRoots() + _ = st.HistoricalRoots() + _ = st.Eth1Data() + _ = st.Eth1DataVotes() + _ = st.Eth1DepositIndex() + _, err = st.ValidatorAtIndex(0) + _ = err + _, err = st.ValidatorAtIndexReadOnly(0) + _ = err + _, _ = st.ValidatorIndexByPubkey([fieldparams.BLSPubkeyLength]byte{}) + _ = st.PubkeyAtIndex(0) + _ = st.NumValidators() + _ = st.Balances() + _, err = st.BalanceAtIndex(0) + _ = err + _ = st.BalancesLength() + _ = st.RandaoMixes() + _, err = st.RandaoMixAtIndex(0) + _ = err + _ = st.RandaoMixesLength() + _ = st.Slashings() + _, err = st.CurrentEpochParticipation() + _ = err + _, err = st.PreviousEpochParticipation() + _ = err + _ = st.JustificationBits() + _ = st.PreviousJustifiedCheckpoint() + _ = st.CurrentJustifiedCheckpoint() + _ = st.FinalizedCheckpoint() + _, err = st.CurrentEpochParticipation() + _ = err + _, err = st.PreviousEpochParticipation() + _ = err + _, err = st.InactivityScores() + _ = err + _, err = st.CurrentSyncCommittee() + _ = err + _, err = st.NextSyncCommittee() + _ = err +} + +func TestBeaconState_ValidatorByPubkey(t *testing.T) { + keyCreator := func(input []byte) [fieldparams.BLSPubkeyLength]byte { + nKey := [fieldparams.BLSPubkeyLength]byte{} + copy(nKey[:1], input) + return nKey + } + + tests := []struct { + name string + modifyFunc func(b *BeaconState, k [fieldparams.BLSPubkeyLength]byte) + exists bool + expectedIdx types.ValidatorIndex + largestIdxInSet types.ValidatorIndex + }{ + { + name: "retrieve validator", + modifyFunc: func(b *BeaconState, key [48]byte) { + assert.NoError(t, b.AppendValidator(ðpb.Validator{PublicKey: key[:]})) + }, + exists: true, + expectedIdx: 0, + }, + { + name: "retrieve validator with multiple validators from the start", + modifyFunc: func(b *BeaconState, key [fieldparams.BLSPubkeyLength]byte) { + key1 := keyCreator([]byte{'C'}) + key2 := keyCreator([]byte{'D'}) + assert.NoError(t, b.AppendValidator(ðpb.Validator{PublicKey: key[:]})) + assert.NoError(t, b.AppendValidator(ðpb.Validator{PublicKey: key1[:]})) + assert.NoError(t, b.AppendValidator(ðpb.Validator{PublicKey: key2[:]})) + }, + exists: true, + expectedIdx: 0, + }, + { + name: "retrieve validator with multiple validators", + modifyFunc: func(b *BeaconState, key [fieldparams.BLSPubkeyLength]byte) { + key1 := keyCreator([]byte{'C'}) + key2 := keyCreator([]byte{'D'}) + assert.NoError(t, b.AppendValidator(ðpb.Validator{PublicKey: key1[:]})) + assert.NoError(t, b.AppendValidator(ðpb.Validator{PublicKey: key2[:]})) + assert.NoError(t, b.AppendValidator(ðpb.Validator{PublicKey: key[:]})) + }, + exists: true, + expectedIdx: 2, + }, + { + name: "retrieve validator with multiple validators from the start with shared state", + modifyFunc: func(b *BeaconState, key [fieldparams.BLSPubkeyLength]byte) { + key1 := keyCreator([]byte{'C'}) + key2 := keyCreator([]byte{'D'}) + assert.NoError(t, b.AppendValidator(ðpb.Validator{PublicKey: key[:]})) + _ = b.Copy() + assert.NoError(t, b.AppendValidator(ðpb.Validator{PublicKey: key1[:]})) + assert.NoError(t, b.AppendValidator(ðpb.Validator{PublicKey: key2[:]})) + }, + exists: true, + expectedIdx: 0, + }, + { + name: "retrieve validator with multiple validators with shared state", + modifyFunc: func(b *BeaconState, key [fieldparams.BLSPubkeyLength]byte) { + key1 := keyCreator([]byte{'C'}) + key2 := keyCreator([]byte{'D'}) + assert.NoError(t, b.AppendValidator(ðpb.Validator{PublicKey: key1[:]})) + assert.NoError(t, b.AppendValidator(ðpb.Validator{PublicKey: key2[:]})) + n := b.Copy() + // Append to another state + assert.NoError(t, n.AppendValidator(ðpb.Validator{PublicKey: key[:]})) + + }, + exists: false, + expectedIdx: 0, + }, + { + name: "retrieve validator with multiple validators with shared state at boundary", + modifyFunc: func(b *BeaconState, key [fieldparams.BLSPubkeyLength]byte) { + key1 := keyCreator([]byte{'C'}) + assert.NoError(t, b.AppendValidator(ðpb.Validator{PublicKey: key1[:]})) + n := b.Copy() + // Append to another state + assert.NoError(t, n.AppendValidator(ðpb.Validator{PublicKey: key[:]})) + + }, + exists: false, + expectedIdx: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s, err := InitializeFromProto(ðpb.BeaconStateAltair{}) + require.NoError(t, err) + nKey := keyCreator([]byte{'A'}) + tt.modifyFunc(s, nKey) + idx, ok := s.ValidatorIndexByPubkey(nKey) + assert.Equal(t, tt.exists, ok) + assert.Equal(t, tt.expectedIdx, idx) + }) + } +} diff --git a/beacon-chain/state/state-native/v2/getters_validator.go b/beacon-chain/state/state-native/v2/getters_validator.go new file mode 100644 index 0000000000..3137a0b9b3 --- /dev/null +++ b/beacon-chain/state/state-native/v2/getters_validator.go @@ -0,0 +1,329 @@ +package v2 + +import ( + "fmt" + + "github.com/pkg/errors" + types "github.com/prysmaticlabs/eth2-types" + "github.com/prysmaticlabs/prysm/beacon-chain/state" + v1 "github.com/prysmaticlabs/prysm/beacon-chain/state/state-native/v1" + fieldparams "github.com/prysmaticlabs/prysm/config/fieldparams" + "github.com/prysmaticlabs/prysm/encoding/bytesutil" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" +) + +// ValidatorIndexOutOfRangeError represents an error scenario where a validator does not exist +// at a given index in the validator's array. +type ValidatorIndexOutOfRangeError struct { + message string +} + +var ( + // ErrNilValidatorsInState returns when accessing validators in the state while the state has a + // nil slice for the validators field. + ErrNilValidatorsInState = errors.New("state has nil validator slice") +) + +// NewValidatorIndexOutOfRangeError creates a new error instance. +func NewValidatorIndexOutOfRangeError(index types.ValidatorIndex) ValidatorIndexOutOfRangeError { + return ValidatorIndexOutOfRangeError{ + message: fmt.Sprintf("index %d out of range", index), + } +} + +// Error returns the underlying error message. +func (e *ValidatorIndexOutOfRangeError) Error() string { + return e.message +} + +// Validators participating in consensus on the beacon chain. +func (b *BeaconState) Validators() []*ethpb.Validator { + if !b.hasInnerState() { + return nil + } + if b.state.Validators == nil { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.validators() +} + +// validators participating in consensus on the beacon chain. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) validators() []*ethpb.Validator { + if !b.hasInnerState() { + return nil + } + if b.state.Validators == nil { + return nil + } + + res := make([]*ethpb.Validator, len(b.state.Validators)) + for i := 0; i < len(res); i++ { + val := b.state.Validators[i] + if val == nil { + continue + } + res[i] = ethpb.CopyValidator(val) + } + return res +} + +// references of validators participating in consensus on the beacon chain. +// This assumes that a lock is already held on BeaconState. This does not +// copy fully and instead just copies the reference. +func (b *BeaconState) validatorsReferences() []*ethpb.Validator { + if !b.hasInnerState() { + return nil + } + if b.state.Validators == nil { + return nil + } + + res := make([]*ethpb.Validator, len(b.state.Validators)) + for i := 0; i < len(res); i++ { + validator := b.state.Validators[i] + if validator == nil { + continue + } + // copy validator reference instead. + res[i] = validator + } + return res +} + +// ValidatorAtIndex is the validator at the provided index. +func (b *BeaconState) ValidatorAtIndex(idx types.ValidatorIndex) (*ethpb.Validator, error) { + if !b.hasInnerState() { + return nil, ErrNilInnerState + } + if b.state.Validators == nil { + return ðpb.Validator{}, nil + } + if uint64(len(b.state.Validators)) <= uint64(idx) { + e := NewValidatorIndexOutOfRangeError(idx) + return nil, &e + } + + b.lock.RLock() + defer b.lock.RUnlock() + + val := b.state.Validators[idx] + return ethpb.CopyValidator(val), nil +} + +// ValidatorAtIndexReadOnly is the validator at the provided index. This method +// doesn't clone the validator. +func (b *BeaconState) ValidatorAtIndexReadOnly(idx types.ValidatorIndex) (state.ReadOnlyValidator, error) { + if !b.hasInnerState() { + return nil, ErrNilInnerState + } + if b.state.Validators == nil { + return nil, ErrNilValidatorsInState + } + if uint64(len(b.state.Validators)) <= uint64(idx) { + e := NewValidatorIndexOutOfRangeError(idx) + return nil, &e + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return v1.NewValidator(b.state.Validators[idx]) +} + +// ValidatorIndexByPubkey returns a given validator by its 48-byte public key. +func (b *BeaconState) ValidatorIndexByPubkey(key [fieldparams.BLSPubkeyLength]byte) (types.ValidatorIndex, bool) { + if b == nil || b.valMapHandler == nil || b.valMapHandler.IsNil() { + return 0, false + } + b.lock.RLock() + defer b.lock.RUnlock() + numOfVals := len(b.state.Validators) + + idx, ok := b.valMapHandler.Get(key) + if ok && numOfVals <= int(idx) { + return types.ValidatorIndex(0), false + } + return idx, ok +} + +// PubkeyAtIndex returns the pubkey at the given +// validator index. +func (b *BeaconState) PubkeyAtIndex(idx types.ValidatorIndex) [fieldparams.BLSPubkeyLength]byte { + if !b.hasInnerState() { + return [fieldparams.BLSPubkeyLength]byte{} + } + if uint64(idx) >= uint64(len(b.state.Validators)) { + return [fieldparams.BLSPubkeyLength]byte{} + } + b.lock.RLock() + defer b.lock.RUnlock() + + if b.state.Validators[idx] == nil { + return [fieldparams.BLSPubkeyLength]byte{} + } + return bytesutil.ToBytes48(b.state.Validators[idx].PublicKey) +} + +// NumValidators returns the size of the validator registry. +func (b *BeaconState) NumValidators() int { + if !b.hasInnerState() { + return 0 + } + b.lock.RLock() + defer b.lock.RUnlock() + + return len(b.state.Validators) +} + +// ReadFromEveryValidator reads values from every validator and applies it to the provided function. +// Warning: This method is potentially unsafe, as it exposes the actual validator registry. +func (b *BeaconState) ReadFromEveryValidator(f func(idx int, val state.ReadOnlyValidator) error) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + if b.state.Validators == nil { + return errors.New("nil validators in state") + } + b.lock.RLock() + validators := b.state.Validators + b.lock.RUnlock() + + for i, v := range validators { + v, err := v1.NewValidator(v) + if err != nil { + return err + } + if err := f(i, v); err != nil { + return err + } + } + return nil +} + +// Balances of validators participating in consensus on the beacon chain. +func (b *BeaconState) Balances() []uint64 { + if !b.hasInnerState() { + return nil + } + if b.state.Balances == nil { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.balances() +} + +// balances of validators participating in consensus on the beacon chain. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) balances() []uint64 { + if !b.hasInnerState() { + return nil + } + if b.state.Balances == nil { + return nil + } + + res := make([]uint64, len(b.state.Balances)) + copy(res, b.state.Balances) + return res +} + +// BalanceAtIndex of validator with the provided index. +func (b *BeaconState) BalanceAtIndex(idx types.ValidatorIndex) (uint64, error) { + if !b.hasInnerState() { + return 0, ErrNilInnerState + } + if b.state.Balances == nil { + return 0, nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + if uint64(len(b.state.Balances)) <= uint64(idx) { + return 0, fmt.Errorf("index of %d does not exist", idx) + } + return b.state.Balances[idx], nil +} + +// BalancesLength returns the length of the balances slice. +func (b *BeaconState) BalancesLength() int { + if !b.hasInnerState() { + return 0 + } + if b.state.Balances == nil { + return 0 + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.balancesLength() +} + +// Slashings of validators on the beacon chain. +func (b *BeaconState) Slashings() []uint64 { + if !b.hasInnerState() { + return nil + } + if b.state.Slashings == nil { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.slashings() +} + +// slashings of validators on the beacon chain. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) slashings() []uint64 { + if !b.hasInnerState() { + return nil + } + if b.state.Slashings == nil { + return nil + } + + res := make([]uint64, len(b.state.Slashings)) + copy(res, b.state.Slashings) + return res +} + +// inactivityScores of validators participating in consensus on the beacon chain. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) inactivityScores() []uint64 { + if !b.hasInnerState() { + return nil + } + if b.state.InactivityScores == nil { + return nil + } + + res := make([]uint64, len(b.state.InactivityScores)) + copy(res, b.state.InactivityScores) + return res +} + +// InactivityScores of validators participating in consensus on the beacon chain. +func (b *BeaconState) InactivityScores() ([]uint64, error) { + if !b.hasInnerState() { + return nil, nil + } + if b.state.InactivityScores == nil { + return nil, nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.inactivityScores(), nil +} diff --git a/beacon-chain/state/state-native/v2/getters_validator_test.go b/beacon-chain/state/state-native/v2/getters_validator_test.go new file mode 100644 index 0000000000..4dfe0095f6 --- /dev/null +++ b/beacon-chain/state/state-native/v2/getters_validator_test.go @@ -0,0 +1,20 @@ +package v2_test + +import ( + "testing" + + v1 "github.com/prysmaticlabs/prysm/beacon-chain/state/state-native/v1" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/testing/assert" + "github.com/prysmaticlabs/prysm/testing/require" +) + +func TestBeaconState_ValidatorAtIndexReadOnly_HandlesNilSlice(t *testing.T) { + st, err := v1.InitializeFromProtoUnsafe(ðpb.BeaconState{ + Validators: nil, + }) + require.NoError(t, err) + + _, err = st.ValidatorAtIndexReadOnly(0) + assert.Equal(t, v1.ErrNilValidatorsInState, err) +} diff --git a/beacon-chain/state/state-native/v2/proofs.go b/beacon-chain/state/state-native/v2/proofs.go new file mode 100644 index 0000000000..597b0de33b --- /dev/null +++ b/beacon-chain/state/state-native/v2/proofs.go @@ -0,0 +1,83 @@ +package v2 + +import ( + "context" + "encoding/binary" + + "github.com/prysmaticlabs/prysm/beacon-chain/state/state-native/fieldtrie" + "github.com/prysmaticlabs/prysm/encoding/bytesutil" +) + +const ( + finalizedRootIndex = uint64(105) // Precomputed value. +) + +// FinalizedRootGeneralizedIndex for the beacon state. +func FinalizedRootGeneralizedIndex() uint64 { + return finalizedRootIndex +} + +// CurrentSyncCommitteeGeneralizedIndex for the beacon state. +func CurrentSyncCommitteeGeneralizedIndex() uint64 { + return uint64(currentSyncCommittee) +} + +// NextSyncCommitteeGeneralizedIndex for the beacon state. +func NextSyncCommitteeGeneralizedIndex() uint64 { + return uint64(nextSyncCommittee) +} + +// CurrentSyncCommitteeProof from the state's Merkle trie representation. +func (b *BeaconState) CurrentSyncCommitteeProof(ctx context.Context) ([][]byte, error) { + b.lock.Lock() + defer b.lock.Unlock() + // In case the Merkle layers of the trie are not populated, we need + // to perform some initialization. + if err := b.initializeMerkleLayers(ctx); err != nil { + return nil, err + } + // Our beacon state uses a "dirty" fields pattern which requires us to + // recompute branches of the Merkle layers that are marked as dirty. + if err := b.recomputeDirtyFields(ctx); err != nil { + return nil, err + } + return fieldtrie.ProofFromMerkleLayers(b.merkleLayers, currentSyncCommittee), nil +} + +// NextSyncCommitteeProof from the state's Merkle trie representation. +func (b *BeaconState) NextSyncCommitteeProof(ctx context.Context) ([][]byte, error) { + b.lock.Lock() + defer b.lock.Unlock() + if err := b.initializeMerkleLayers(ctx); err != nil { + return nil, err + } + if err := b.recomputeDirtyFields(ctx); err != nil { + return nil, err + } + return fieldtrie.ProofFromMerkleLayers(b.merkleLayers, nextSyncCommittee), nil +} + +// FinalizedRootProof crafts a Merkle proof for the finalized root +// contained within the finalized checkpoint of a beacon state. +func (b *BeaconState) FinalizedRootProof(ctx context.Context) ([][]byte, error) { + b.lock.Lock() + defer b.lock.Unlock() + if err := b.initializeMerkleLayers(ctx); err != nil { + return nil, err + } + if err := b.recomputeDirtyFields(ctx); err != nil { + return nil, err + } + cpt := b.state.FinalizedCheckpoint + // The epoch field of a finalized checkpoint is the neighbor + // index of the finalized root field in its Merkle tree representation + // of the checkpoint. This neighbor is the first element added to the proof. + epochBuf := make([]byte, 8) + binary.LittleEndian.PutUint64(epochBuf, uint64(cpt.Epoch)) + epochRoot := bytesutil.ToBytes32(epochBuf) + proof := make([][]byte, 0) + proof = append(proof, epochRoot[:]) + branch := fieldtrie.ProofFromMerkleLayers(b.merkleLayers, finalizedCheckpoint) + proof = append(proof, branch...) + return proof, nil +} diff --git a/beacon-chain/state/state-native/v2/proofs_test.go b/beacon-chain/state/state-native/v2/proofs_test.go new file mode 100644 index 0000000000..576c4ab2cd --- /dev/null +++ b/beacon-chain/state/state-native/v2/proofs_test.go @@ -0,0 +1,105 @@ +package v2_test + +import ( + "context" + "testing" + + v2 "github.com/prysmaticlabs/prysm/beacon-chain/state/state-native/v2" + "github.com/prysmaticlabs/prysm/container/trie" + "github.com/prysmaticlabs/prysm/crypto/bls" + "github.com/prysmaticlabs/prysm/testing/require" + "github.com/prysmaticlabs/prysm/testing/util" +) + +func TestBeaconStateMerkleProofs(t *testing.T) { + ctx := context.Background() + st, _ := util.DeterministicGenesisStateAltair(t, 256) + htr, err := st.HashTreeRoot(ctx) + require.NoError(t, err) + t.Run("current sync committee", func(t *testing.T) { + sc, err := st.CurrentSyncCommittee() + require.NoError(t, err) + + // Verify the Merkle proof. + scRoot, err := sc.HashTreeRoot() + require.NoError(t, err) + proof, err := st.CurrentSyncCommitteeProof(ctx) + require.NoError(t, err) + valid := trie.VerifyMerkleProof(htr[:], scRoot[:], v2.CurrentSyncCommitteeGeneralizedIndex(), proof) + require.Equal(t, true, valid) + }) + t.Run("next sync committee", func(t *testing.T) { + nextSC, err := st.NextSyncCommittee() + require.NoError(t, err) + proof, err := st.NextSyncCommitteeProof(ctx) + require.NoError(t, err) + + // Verify the Merkle proof. + nextSCRoot, err := nextSC.HashTreeRoot() + require.NoError(t, err) + valid := trie.VerifyMerkleProof(htr[:], nextSCRoot[:], v2.NextSyncCommitteeGeneralizedIndex(), proof) + require.Equal(t, true, valid) + + // Edit the sync committee. + privKey, err := bls.RandKey() + require.NoError(t, err) + nextSC.AggregatePubkey = privKey.PublicKey().Marshal() + require.NoError(t, st.SetNextSyncCommittee(nextSC)) + + // Verifying the old Merkle proof for the new value should fail. + nextSCRoot, err = nextSC.HashTreeRoot() + require.NoError(t, err) + valid = trie.VerifyMerkleProof(htr[:], nextSCRoot[:], v2.NextSyncCommitteeGeneralizedIndex(), proof) + require.Equal(t, false, valid) + + // Generating a new, valid proof should pass. + proof, err = st.NextSyncCommitteeProof(ctx) + require.NoError(t, err) + htr, err = st.HashTreeRoot(ctx) + require.NoError(t, err) + valid = trie.VerifyMerkleProof(htr[:], nextSCRoot[:], v2.NextSyncCommitteeGeneralizedIndex(), proof) + require.Equal(t, true, valid) + }) + t.Run("finalized root", func(t *testing.T) { + finalizedRoot := st.FinalizedCheckpoint().Root + + // Verify the Merkle proof. + htr, err = st.HashTreeRoot(ctx) + require.NoError(t, err) + proof, err := st.FinalizedRootProof(ctx) + require.NoError(t, err) + gIndex := v2.FinalizedRootGeneralizedIndex() + valid := trie.VerifyMerkleProof(htr[:], finalizedRoot, gIndex, proof) + require.Equal(t, true, valid) + }) + t.Run("recomputes root on dirty fields", func(t *testing.T) { + currentRoot, err := st.HashTreeRoot(ctx) + require.NoError(t, err) + cpt := st.FinalizedCheckpoint() + require.NoError(t, err) + + // Edit the checkpoint. + cpt.Epoch = 100 + require.NoError(t, st.SetFinalizedCheckpoint(cpt)) + + // Produce a proof for the finalized root. + proof, err := st.FinalizedRootProof(ctx) + require.NoError(t, err) + + // We expect the previous step to have triggered + // a recomputation of dirty fields in the beacon state, resulting + // in a new hash tree root as the finalized checkpoint had previously + // changed and should have been marked as a dirty state field. + // The proof validity should be false for the old root, but true for the new. + finalizedRoot := st.FinalizedCheckpoint().Root + gIndex := v2.FinalizedRootGeneralizedIndex() + valid := trie.VerifyMerkleProof(currentRoot[:], finalizedRoot, gIndex, proof) + require.Equal(t, false, valid) + + newRoot, err := st.HashTreeRoot(ctx) + require.NoError(t, err) + + valid = trie.VerifyMerkleProof(newRoot[:], finalizedRoot, gIndex, proof) + require.Equal(t, true, valid) + }) +} diff --git a/beacon-chain/state/state-native/v2/setters_block.go b/beacon-chain/state/state-native/v2/setters_block.go new file mode 100644 index 0000000000..966c434dcf --- /dev/null +++ b/beacon-chain/state/state-native/v2/setters_block.go @@ -0,0 +1,68 @@ +package v2 + +import ( + "fmt" + + "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" +) + +// SetLatestBlockHeader in the beacon state. +func (b *BeaconState) SetLatestBlockHeader(val *ethpb.BeaconBlockHeader) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.state.LatestBlockHeader = ethpb.CopyBeaconBlockHeader(val) + b.markFieldAsDirty(latestBlockHeader) + return nil +} + +// SetBlockRoots for the beacon state. Updates the entire +// list to a new value by overwriting the previous one. +func (b *BeaconState) SetBlockRoots(val [][]byte) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.sharedFieldReferences[blockRoots].MinusRef() + b.sharedFieldReferences[blockRoots] = stateutil.NewRef(1) + + b.state.BlockRoots = val + b.markFieldAsDirty(blockRoots) + b.rebuildTrie[blockRoots] = true + return nil +} + +// UpdateBlockRootAtIndex for the beacon state. Updates the block root +// at a specific index to a new value. +func (b *BeaconState) UpdateBlockRootAtIndex(idx uint64, blockRoot [32]byte) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + if uint64(len(b.state.BlockRoots)) <= idx { + return fmt.Errorf("invalid index provided %d", idx) + } + b.lock.Lock() + defer b.lock.Unlock() + + r := b.state.BlockRoots + if ref := b.sharedFieldReferences[blockRoots]; ref.Refs() > 1 { + // Copy elements in underlying array by reference. + r = make([][]byte, len(b.state.BlockRoots)) + copy(r, b.state.BlockRoots) + ref.MinusRef() + b.sharedFieldReferences[blockRoots] = stateutil.NewRef(1) + } + + r[idx] = blockRoot[:] + b.state.BlockRoots = r + + b.markFieldAsDirty(blockRoots) + b.addDirtyIndices(blockRoots, []uint64{idx}) + return nil +} diff --git a/beacon-chain/state/state-native/v2/setters_checkpoint.go b/beacon-chain/state/state-native/v2/setters_checkpoint.go new file mode 100644 index 0000000000..666ef8fff8 --- /dev/null +++ b/beacon-chain/state/state-native/v2/setters_checkpoint.go @@ -0,0 +1,58 @@ +package v2 + +import ( + "github.com/prysmaticlabs/go-bitfield" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" +) + +// SetJustificationBits for the beacon state. +func (b *BeaconState) SetJustificationBits(val bitfield.Bitvector4) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.state.JustificationBits = val + b.markFieldAsDirty(justificationBits) + return nil +} + +// SetPreviousJustifiedCheckpoint for the beacon state. +func (b *BeaconState) SetPreviousJustifiedCheckpoint(val *ethpb.Checkpoint) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.state.PreviousJustifiedCheckpoint = val + b.markFieldAsDirty(previousJustifiedCheckpoint) + return nil +} + +// SetCurrentJustifiedCheckpoint for the beacon state. +func (b *BeaconState) SetCurrentJustifiedCheckpoint(val *ethpb.Checkpoint) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.state.CurrentJustifiedCheckpoint = val + b.markFieldAsDirty(currentJustifiedCheckpoint) + return nil +} + +// SetFinalizedCheckpoint for the beacon state. +func (b *BeaconState) SetFinalizedCheckpoint(val *ethpb.Checkpoint) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.state.FinalizedCheckpoint = val + b.markFieldAsDirty(finalizedCheckpoint) + return nil +} diff --git a/beacon-chain/state/state-native/v2/setters_eth1.go b/beacon-chain/state/state-native/v2/setters_eth1.go new file mode 100644 index 0000000000..f3050d30f9 --- /dev/null +++ b/beacon-chain/state/state-native/v2/setters_eth1.go @@ -0,0 +1,74 @@ +package v2 + +import ( + "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" +) + +// SetEth1Data for the beacon state. +func (b *BeaconState) SetEth1Data(val *ethpb.Eth1Data) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.state.Eth1Data = val + b.markFieldAsDirty(eth1Data) + return nil +} + +// SetEth1DataVotes for the beacon state. Updates the entire +// list to a new value by overwriting the previous one. +func (b *BeaconState) SetEth1DataVotes(val []*ethpb.Eth1Data) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.sharedFieldReferences[eth1DataVotes].MinusRef() + b.sharedFieldReferences[eth1DataVotes] = stateutil.NewRef(1) + + b.state.Eth1DataVotes = val + b.markFieldAsDirty(eth1DataVotes) + b.rebuildTrie[eth1DataVotes] = true + return nil +} + +// SetEth1DepositIndex for the beacon state. +func (b *BeaconState) SetEth1DepositIndex(val uint64) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.state.Eth1DepositIndex = val + b.markFieldAsDirty(eth1DepositIndex) + return nil +} + +// AppendEth1DataVotes for the beacon state. Appends the new value +// to the the end of list. +func (b *BeaconState) AppendEth1DataVotes(val *ethpb.Eth1Data) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + votes := b.state.Eth1DataVotes + if b.sharedFieldReferences[eth1DataVotes].Refs() > 1 { + // Copy elements in underlying array by reference. + votes = make([]*ethpb.Eth1Data, len(b.state.Eth1DataVotes)) + copy(votes, b.state.Eth1DataVotes) + b.sharedFieldReferences[eth1DataVotes].MinusRef() + b.sharedFieldReferences[eth1DataVotes] = stateutil.NewRef(1) + } + + b.state.Eth1DataVotes = append(votes, val) + b.markFieldAsDirty(eth1DataVotes) + b.addDirtyIndices(eth1DataVotes, []uint64{uint64(len(b.state.Eth1DataVotes) - 1)}) + return nil +} diff --git a/beacon-chain/state/state-native/v2/setters_misc.go b/beacon-chain/state/state-native/v2/setters_misc.go new file mode 100644 index 0000000000..87da6bcd30 --- /dev/null +++ b/beacon-chain/state/state-native/v2/setters_misc.go @@ -0,0 +1,186 @@ +package v2 + +import ( + "github.com/pkg/errors" + types "github.com/prysmaticlabs/eth2-types" + "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" + stateTypes "github.com/prysmaticlabs/prysm/beacon-chain/state/types" + "github.com/prysmaticlabs/prysm/config/features" + "github.com/prysmaticlabs/prysm/crypto/hash" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" + "google.golang.org/protobuf/proto" +) + +// For our setters, we have a field reference counter through +// which we can track shared field references. This helps when +// performing state copies, as we simply copy the reference to the +// field. When we do need to do need to modify these fields, we +// perform a full copy of the field. This is true of most of our +// fields except for the following below. +// 1) BlockRoots +// 2) StateRoots +// 3) Eth1DataVotes +// 4) RandaoMixes +// 5) HistoricalRoots +// 6) CurrentParticipationBits +// 7) PreviousParticipationBits +// +// The fields referred to above are instead copied by reference, where +// we simply copy the reference to the underlying object instead of the +// whole object. This is possible due to how we have structured our state +// as we copy the value on read, so as to ensure the underlying object is +// not mutated while it is being accessed during a state read. + +const ( + // This specifies the limit till which we process all dirty indices for a certain field. + // If we have more dirty indices than the threshold, then we rebuild the whole trie. This + // comes due to the fact that O(alogn) > O(n) beyond a certain value of a. + indicesLimit = 8000 +) + +// SetGenesisTime for the beacon state. +func (b *BeaconState) SetGenesisTime(val uint64) error { + b.lock.Lock() + defer b.lock.Unlock() + + b.state.GenesisTime = val + b.markFieldAsDirty(genesisTime) + return nil +} + +// SetGenesisValidatorRoot for the beacon state. +func (b *BeaconState) SetGenesisValidatorRoot(val []byte) error { + b.lock.Lock() + defer b.lock.Unlock() + + b.state.GenesisValidatorsRoot = val + b.markFieldAsDirty(genesisValidatorRoot) + return nil +} + +// SetSlot for the beacon state. +func (b *BeaconState) SetSlot(val types.Slot) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.state.Slot = val + b.markFieldAsDirty(slot) + return nil +} + +// SetFork version for the beacon chain. +func (b *BeaconState) SetFork(val *ethpb.Fork) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + fk, ok := proto.Clone(val).(*ethpb.Fork) + if !ok { + return errors.New("proto.Clone did not return a fork proto") + } + b.state.Fork = fk + b.markFieldAsDirty(fork) + return nil +} + +// SetHistoricalRoots for the beacon state. Updates the entire +// list to a new value by overwriting the previous one. +func (b *BeaconState) SetHistoricalRoots(val [][]byte) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.sharedFieldReferences[historicalRoots].MinusRef() + b.sharedFieldReferences[historicalRoots] = stateutil.NewRef(1) + + b.state.HistoricalRoots = val + b.markFieldAsDirty(historicalRoots) + return nil +} + +// AppendHistoricalRoots for the beacon state. Appends the new value +// to the the end of list. +func (b *BeaconState) AppendHistoricalRoots(root [32]byte) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + roots := b.state.HistoricalRoots + if b.sharedFieldReferences[historicalRoots].Refs() > 1 { + roots = make([][]byte, len(b.state.HistoricalRoots)) + copy(roots, b.state.HistoricalRoots) + b.sharedFieldReferences[historicalRoots].MinusRef() + b.sharedFieldReferences[historicalRoots] = stateutil.NewRef(1) + } + + b.state.HistoricalRoots = append(roots, root[:]) + b.markFieldAsDirty(historicalRoots) + return nil +} + +// Recomputes the branch up the index in the Merkle trie representation +// of the beacon state. This method performs slice reads and the caller MUST +// hold the lock before calling this method. +func (b *BeaconState) recomputeRoot(idx int) { + hashFunc := hash.CustomSHA256Hasher() + layers := b.merkleLayers + // The merkle tree structure looks as follows: + // [[r1, r2, r3, r4], [parent1, parent2], [root]] + // Using information about the index which changed, idx, we recompute + // only its branch up the tree. + currentIndex := idx + root := b.merkleLayers[0][idx] + for i := 0; i < len(layers)-1; i++ { + isLeft := currentIndex%2 == 0 + neighborIdx := currentIndex ^ 1 + + neighbor := make([]byte, 32) + if layers[i] != nil && len(layers[i]) != 0 && neighborIdx < len(layers[i]) { + neighbor = layers[i][neighborIdx] + } + if isLeft { + parentHash := hashFunc(append(root, neighbor...)) + root = parentHash[:] + } else { + parentHash := hashFunc(append(neighbor, root...)) + root = parentHash[:] + } + parentIdx := currentIndex / 2 + // Update the cached layers at the parent index. + layers[i+1][parentIdx] = root + currentIndex = parentIdx + } + b.merkleLayers = layers +} + +func (b *BeaconState) markFieldAsDirty(field stateTypes.FieldIndex) { + b.dirtyFields[field] = true +} + +// addDirtyIndices adds the relevant dirty field indices, so that they +// can be recomputed. +func (b *BeaconState) addDirtyIndices(index stateTypes.FieldIndex, indices []uint64) { + if b.rebuildTrie[index] { + return + } + // Exit early if balance trie computation isn't enabled. + if !features.Get().EnableBalanceTrieComputation && index == balances { + return + } + totalIndicesLen := len(b.dirtyIndices[index]) + len(indices) + if totalIndicesLen > indicesLimit { + b.rebuildTrie[index] = true + b.dirtyIndices[index] = []uint64{} + } else { + b.dirtyIndices[index] = append(b.dirtyIndices[index], indices...) + } +} diff --git a/beacon-chain/state/state-native/v2/setters_participation.go b/beacon-chain/state/state-native/v2/setters_participation.go new file mode 100644 index 0000000000..5ca886227b --- /dev/null +++ b/beacon-chain/state/state-native/v2/setters_participation.go @@ -0,0 +1,89 @@ +package v2 + +import ( + "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" +) + +// SetPreviousParticipationBits for the beacon state. Updates the entire +// list to a new value by overwriting the previous one. +func (b *BeaconState) SetPreviousParticipationBits(val []byte) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.sharedFieldReferences[previousEpochParticipationBits].MinusRef() + b.sharedFieldReferences[previousEpochParticipationBits] = stateutil.NewRef(1) + + b.state.PreviousEpochParticipation = val + b.markFieldAsDirty(previousEpochParticipationBits) + b.rebuildTrie[previousEpochParticipationBits] = true + return nil +} + +// SetCurrentParticipationBits for the beacon state. Updates the entire +// list to a new value by overwriting the previous one. +func (b *BeaconState) SetCurrentParticipationBits(val []byte) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.sharedFieldReferences[currentEpochParticipationBits].MinusRef() + b.sharedFieldReferences[currentEpochParticipationBits] = stateutil.NewRef(1) + + b.state.CurrentEpochParticipation = val + b.markFieldAsDirty(currentEpochParticipationBits) + b.rebuildTrie[currentEpochParticipationBits] = true + return nil +} + +// AppendCurrentParticipationBits for the beacon state. Appends the new value +// to the the end of list. +func (b *BeaconState) AppendCurrentParticipationBits(val byte) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + participation := b.state.CurrentEpochParticipation + if b.sharedFieldReferences[currentEpochParticipationBits].Refs() > 1 { + // Copy elements in underlying array by reference. + participation = make([]byte, len(b.state.CurrentEpochParticipation)) + copy(participation, b.state.CurrentEpochParticipation) + b.sharedFieldReferences[currentEpochParticipationBits].MinusRef() + b.sharedFieldReferences[currentEpochParticipationBits] = stateutil.NewRef(1) + } + + b.state.CurrentEpochParticipation = append(participation, val) + b.markFieldAsDirty(currentEpochParticipationBits) + b.addDirtyIndices(currentEpochParticipationBits, []uint64{uint64(len(b.state.CurrentEpochParticipation) - 1)}) + return nil +} + +// AppendPreviousParticipationBits for the beacon state. Appends the new value +// to the the end of list. +func (b *BeaconState) AppendPreviousParticipationBits(val byte) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + bits := b.state.PreviousEpochParticipation + if b.sharedFieldReferences[previousEpochParticipationBits].Refs() > 1 { + bits = make([]byte, len(b.state.PreviousEpochParticipation)) + copy(bits, b.state.PreviousEpochParticipation) + b.sharedFieldReferences[previousEpochParticipationBits].MinusRef() + b.sharedFieldReferences[previousEpochParticipationBits] = stateutil.NewRef(1) + } + + b.state.PreviousEpochParticipation = append(bits, val) + b.markFieldAsDirty(previousEpochParticipationBits) + b.addDirtyIndices(previousEpochParticipationBits, []uint64{uint64(len(b.state.PreviousEpochParticipation) - 1)}) + + return nil +} diff --git a/beacon-chain/state/state-native/v2/setters_randao.go b/beacon-chain/state/state-native/v2/setters_randao.go new file mode 100644 index 0000000000..50f1d05d63 --- /dev/null +++ b/beacon-chain/state/state-native/v2/setters_randao.go @@ -0,0 +1,53 @@ +package v2 + +import ( + "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" +) + +// SetRandaoMixes for the beacon state. Updates the entire +// randao mixes to a new value by overwriting the previous one. +func (b *BeaconState) SetRandaoMixes(val [][]byte) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.sharedFieldReferences[randaoMixes].MinusRef() + b.sharedFieldReferences[randaoMixes] = stateutil.NewRef(1) + + b.state.RandaoMixes = val + b.markFieldAsDirty(randaoMixes) + b.rebuildTrie[randaoMixes] = true + return nil +} + +// UpdateRandaoMixesAtIndex for the beacon state. Updates the randao mixes +// at a specific index to a new value. +func (b *BeaconState) UpdateRandaoMixesAtIndex(idx uint64, val []byte) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + if uint64(len(b.state.RandaoMixes)) <= idx { + return errors.Errorf("invalid index provided %d", idx) + } + b.lock.Lock() + defer b.lock.Unlock() + + mixes := b.state.RandaoMixes + if refs := b.sharedFieldReferences[randaoMixes].Refs(); refs > 1 { + // Copy elements in underlying array by reference. + mixes = make([][]byte, len(b.state.RandaoMixes)) + copy(mixes, b.state.RandaoMixes) + b.sharedFieldReferences[randaoMixes].MinusRef() + b.sharedFieldReferences[randaoMixes] = stateutil.NewRef(1) + } + + mixes[idx] = val + b.state.RandaoMixes = mixes + b.markFieldAsDirty(randaoMixes) + b.addDirtyIndices(randaoMixes, []uint64{idx}) + + return nil +} diff --git a/beacon-chain/state/state-native/v2/setters_state.go b/beacon-chain/state/state-native/v2/setters_state.go new file mode 100644 index 0000000000..e64df5335b --- /dev/null +++ b/beacon-chain/state/state-native/v2/setters_state.go @@ -0,0 +1,59 @@ +package v2 + +import ( + "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" +) + +// SetStateRoots for the beacon state. Updates the state roots +// to a new value by overwriting the previous value. +func (b *BeaconState) SetStateRoots(val [][]byte) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.sharedFieldReferences[stateRoots].MinusRef() + b.sharedFieldReferences[stateRoots] = stateutil.NewRef(1) + + b.state.StateRoots = val + b.markFieldAsDirty(stateRoots) + b.rebuildTrie[stateRoots] = true + return nil +} + +// UpdateStateRootAtIndex for the beacon state. Updates the state root +// at a specific index to a new value. +func (b *BeaconState) UpdateStateRootAtIndex(idx uint64, stateRoot [32]byte) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + + b.lock.RLock() + if uint64(len(b.state.StateRoots)) <= idx { + b.lock.RUnlock() + return errors.Errorf("invalid index provided %d", idx) + } + b.lock.RUnlock() + + b.lock.Lock() + defer b.lock.Unlock() + + // Check if we hold the only reference to the shared state roots slice. + r := b.state.StateRoots + if ref := b.sharedFieldReferences[stateRoots]; ref.Refs() > 1 { + // Copy elements in underlying array by reference. + r = make([][]byte, len(b.state.StateRoots)) + copy(r, b.state.StateRoots) + ref.MinusRef() + b.sharedFieldReferences[stateRoots] = stateutil.NewRef(1) + } + + r[idx] = stateRoot[:] + b.state.StateRoots = r + + b.markFieldAsDirty(stateRoots) + b.addDirtyIndices(stateRoots, []uint64{idx}) + return nil +} diff --git a/beacon-chain/state/state-native/v2/setters_sync_committee.go b/beacon-chain/state/state-native/v2/setters_sync_committee.go new file mode 100644 index 0000000000..c069cf3c12 --- /dev/null +++ b/beacon-chain/state/state-native/v2/setters_sync_committee.go @@ -0,0 +1,31 @@ +package v2 + +import ( + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" +) + +// SetCurrentSyncCommittee for the beacon state. +func (b *BeaconState) SetCurrentSyncCommittee(val *ethpb.SyncCommittee) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.state.CurrentSyncCommittee = val + b.markFieldAsDirty(currentSyncCommittee) + return nil +} + +// SetNextSyncCommittee for the beacon state. +func (b *BeaconState) SetNextSyncCommittee(val *ethpb.SyncCommittee) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.state.NextSyncCommittee = val + b.markFieldAsDirty(nextSyncCommittee) + return nil +} diff --git a/beacon-chain/state/state-native/v2/setters_test.go b/beacon-chain/state/state-native/v2/setters_test.go new file mode 100644 index 0000000000..4db6b8f2d9 --- /dev/null +++ b/beacon-chain/state/state-native/v2/setters_test.go @@ -0,0 +1,161 @@ +package v2 + +import ( + "context" + "strconv" + "testing" + + types "github.com/prysmaticlabs/eth2-types" + "github.com/prysmaticlabs/go-bitfield" + "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" + stateTypes "github.com/prysmaticlabs/prysm/beacon-chain/state/types" + fieldparams "github.com/prysmaticlabs/prysm/config/fieldparams" + "github.com/prysmaticlabs/prysm/config/params" + "github.com/prysmaticlabs/prysm/encoding/bytesutil" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/testing/assert" + "github.com/prysmaticlabs/prysm/testing/require" +) + +func TestAppendBeyondIndicesLimit(t *testing.T) { + zeroHash := params.BeaconConfig().ZeroHash + mockblockRoots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot) + for i := 0; i < len(mockblockRoots); i++ { + mockblockRoots[i] = zeroHash[:] + } + + mockstateRoots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot) + for i := 0; i < len(mockstateRoots); i++ { + mockstateRoots[i] = zeroHash[:] + } + mockrandaoMixes := make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector) + for i := 0; i < len(mockrandaoMixes); i++ { + mockrandaoMixes[i] = zeroHash[:] + } + st, err := InitializeFromProto(ðpb.BeaconStateAltair{ + Slot: 1, + CurrentEpochParticipation: []byte{}, + PreviousEpochParticipation: []byte{}, + Validators: []*ethpb.Validator{}, + Eth1Data: ðpb.Eth1Data{}, + BlockRoots: mockblockRoots, + StateRoots: mockstateRoots, + RandaoMixes: mockrandaoMixes, + }) + require.NoError(t, err) + _, err = st.HashTreeRoot(context.Background()) + require.NoError(t, err) + for i := stateTypes.FieldIndex(0); i < stateTypes.FieldIndex(params.BeaconConfig().BeaconStateAltairFieldCount); i++ { + st.dirtyFields[i] = true + } + _, err = st.HashTreeRoot(context.Background()) + require.NoError(t, err) + for i := 0; i < 10; i++ { + assert.NoError(t, st.AppendValidator(ðpb.Validator{})) + } + assert.Equal(t, false, st.rebuildTrie[validators]) + assert.NotEqual(t, len(st.dirtyIndices[validators]), 0) + + for i := 0; i < indicesLimit; i++ { + assert.NoError(t, st.AppendValidator(ðpb.Validator{})) + } + assert.Equal(t, true, st.rebuildTrie[validators]) + assert.Equal(t, len(st.dirtyIndices[validators]), 0) +} + +func TestBeaconState_AppendBalanceWithTrie(t *testing.T) { + count := uint64(100) + vals := make([]*ethpb.Validator, 0, count) + bals := make([]uint64, 0, count) + for i := uint64(1); i < count; i++ { + someRoot := [32]byte{} + someKey := [fieldparams.BLSPubkeyLength]byte{} + copy(someRoot[:], strconv.Itoa(int(i))) + copy(someKey[:], strconv.Itoa(int(i))) + vals = append(vals, ðpb.Validator{ + PublicKey: someKey[:], + WithdrawalCredentials: someRoot[:], + EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, + Slashed: false, + ActivationEligibilityEpoch: 1, + ActivationEpoch: 1, + ExitEpoch: 1, + WithdrawableEpoch: 1, + }) + bals = append(bals, params.BeaconConfig().MaxEffectiveBalance) + } + zeroHash := params.BeaconConfig().ZeroHash + mockblockRoots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot) + for i := 0; i < len(mockblockRoots); i++ { + mockblockRoots[i] = zeroHash[:] + } + + mockstateRoots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot) + for i := 0; i < len(mockstateRoots); i++ { + mockstateRoots[i] = zeroHash[:] + } + mockrandaoMixes := make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector) + for i := 0; i < len(mockrandaoMixes); i++ { + mockrandaoMixes[i] = zeroHash[:] + } + var pubKeys [][]byte + for i := uint64(0); i < params.BeaconConfig().SyncCommitteeSize; i++ { + pubKeys = append(pubKeys, bytesutil.PadTo([]byte{}, params.BeaconConfig().BLSPubkeyLength)) + } + st, err := InitializeFromProto(ðpb.BeaconStateAltair{ + Slot: 1, + GenesisValidatorsRoot: make([]byte, 32), + Fork: ðpb.Fork{ + PreviousVersion: make([]byte, 4), + CurrentVersion: make([]byte, 4), + Epoch: 0, + }, + LatestBlockHeader: ðpb.BeaconBlockHeader{ + ParentRoot: make([]byte, fieldparams.RootLength), + StateRoot: make([]byte, fieldparams.RootLength), + BodyRoot: make([]byte, fieldparams.RootLength), + }, + CurrentEpochParticipation: []byte{}, + PreviousEpochParticipation: []byte{}, + Validators: vals, + Balances: bals, + Eth1Data: ðpb.Eth1Data{ + DepositRoot: make([]byte, fieldparams.RootLength), + BlockHash: make([]byte, 32), + }, + BlockRoots: mockblockRoots, + StateRoots: mockstateRoots, + RandaoMixes: mockrandaoMixes, + JustificationBits: bitfield.NewBitvector4(), + PreviousJustifiedCheckpoint: ðpb.Checkpoint{Root: make([]byte, fieldparams.RootLength)}, + CurrentJustifiedCheckpoint: ðpb.Checkpoint{Root: make([]byte, fieldparams.RootLength)}, + FinalizedCheckpoint: ðpb.Checkpoint{Root: make([]byte, fieldparams.RootLength)}, + Slashings: make([]uint64, params.BeaconConfig().EpochsPerSlashingsVector), + CurrentSyncCommittee: ðpb.SyncCommittee{ + Pubkeys: pubKeys, + AggregatePubkey: make([]byte, 48), + }, + NextSyncCommittee: ðpb.SyncCommittee{ + Pubkeys: pubKeys, + AggregatePubkey: make([]byte, 48), + }, + }) + assert.NoError(t, err) + _, err = st.HashTreeRoot(context.Background()) + assert.NoError(t, err) + + for i := 0; i < 100; i++ { + if i%2 == 0 { + assert.NoError(t, st.UpdateBalancesAtIndex(types.ValidatorIndex(i), 1000)) + } + if i%3 == 0 { + assert.NoError(t, st.AppendBalance(1000)) + } + } + _, err = st.HashTreeRoot(context.Background()) + assert.NoError(t, err) + newRt := bytesutil.ToBytes32(st.merkleLayers[0][balances]) + wantedRt, err := stateutil.Uint64ListRootWithRegistryLimit(st.state.Balances) + assert.NoError(t, err) + assert.Equal(t, wantedRt, newRt, "state roots are unequal") +} diff --git a/beacon-chain/state/state-native/v2/setters_validator.go b/beacon-chain/state/state-native/v2/setters_validator.go new file mode 100644 index 0000000000..3299c09022 --- /dev/null +++ b/beacon-chain/state/state-native/v2/setters_validator.go @@ -0,0 +1,265 @@ +package v2 + +import ( + "github.com/pkg/errors" + types "github.com/prysmaticlabs/eth2-types" + "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" + "github.com/prysmaticlabs/prysm/encoding/bytesutil" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" +) + +// SetValidators for the beacon state. Updates the entire +// to a new value by overwriting the previous one. +func (b *BeaconState) SetValidators(val []*ethpb.Validator) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.state.Validators = val + b.sharedFieldReferences[validators].MinusRef() + b.sharedFieldReferences[validators] = stateutil.NewRef(1) + b.markFieldAsDirty(validators) + b.rebuildTrie[validators] = true + b.valMapHandler = stateutil.NewValMapHandler(b.state.Validators) + return nil +} + +// ApplyToEveryValidator applies the provided callback function to each validator in the +// validator registry. +func (b *BeaconState) ApplyToEveryValidator(f func(idx int, val *ethpb.Validator) (bool, *ethpb.Validator, error)) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + v := b.state.Validators + if ref := b.sharedFieldReferences[validators]; ref.Refs() > 1 { + v = b.validatorsReferences() + ref.MinusRef() + b.sharedFieldReferences[validators] = stateutil.NewRef(1) + } + b.lock.Unlock() + var changedVals []uint64 + for i, val := range v { + changed, newVal, err := f(i, val) + if err != nil { + return err + } + if changed { + changedVals = append(changedVals, uint64(i)) + v[i] = newVal + } + } + + b.lock.Lock() + defer b.lock.Unlock() + + b.state.Validators = v + b.markFieldAsDirty(validators) + b.addDirtyIndices(validators, changedVals) + + return nil +} + +// UpdateValidatorAtIndex for the beacon state. Updates the validator +// at a specific index to a new value. +func (b *BeaconState) UpdateValidatorAtIndex(idx types.ValidatorIndex, val *ethpb.Validator) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + if uint64(len(b.state.Validators)) <= uint64(idx) { + return errors.Errorf("invalid index provided %d", idx) + } + b.lock.Lock() + defer b.lock.Unlock() + + v := b.state.Validators + if ref := b.sharedFieldReferences[validators]; ref.Refs() > 1 { + v = b.validatorsReferences() + ref.MinusRef() + b.sharedFieldReferences[validators] = stateutil.NewRef(1) + } + + v[idx] = val + b.state.Validators = v + b.markFieldAsDirty(validators) + b.addDirtyIndices(validators, []uint64{uint64(idx)}) + + return nil +} + +// SetBalances for the beacon state. Updates the entire +// list to a new value by overwriting the previous one. +func (b *BeaconState) SetBalances(val []uint64) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.sharedFieldReferences[balances].MinusRef() + b.sharedFieldReferences[balances] = stateutil.NewRef(1) + + b.state.Balances = val + b.rebuildTrie[balances] = true + b.markFieldAsDirty(balances) + return nil +} + +// UpdateBalancesAtIndex for the beacon state. This method updates the balance +// at a specific index to a new value. +func (b *BeaconState) UpdateBalancesAtIndex(idx types.ValidatorIndex, val uint64) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + if uint64(len(b.state.Balances)) <= uint64(idx) { + return errors.Errorf("invalid index provided %d", idx) + } + b.lock.Lock() + defer b.lock.Unlock() + + bals := b.state.Balances + if b.sharedFieldReferences[balances].Refs() > 1 { + bals = b.balances() + b.sharedFieldReferences[balances].MinusRef() + b.sharedFieldReferences[balances] = stateutil.NewRef(1) + } + + bals[idx] = val + b.state.Balances = bals + b.markFieldAsDirty(balances) + b.addDirtyIndices(balances, []uint64{uint64(idx)}) + return nil +} + +// SetSlashings for the beacon state. Updates the entire +// list to a new value by overwriting the previous one. +func (b *BeaconState) SetSlashings(val []uint64) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.sharedFieldReferences[slashings].MinusRef() + b.sharedFieldReferences[slashings] = stateutil.NewRef(1) + + b.state.Slashings = val + b.markFieldAsDirty(slashings) + return nil +} + +// UpdateSlashingsAtIndex for the beacon state. Updates the slashings +// at a specific index to a new value. +func (b *BeaconState) UpdateSlashingsAtIndex(idx, val uint64) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + if uint64(len(b.state.Slashings)) <= idx { + return errors.Errorf("invalid index provided %d", idx) + } + b.lock.Lock() + defer b.lock.Unlock() + + s := b.state.Slashings + if b.sharedFieldReferences[slashings].Refs() > 1 { + s = b.slashings() + b.sharedFieldReferences[slashings].MinusRef() + b.sharedFieldReferences[slashings] = stateutil.NewRef(1) + } + + s[idx] = val + + b.state.Slashings = s + + b.markFieldAsDirty(slashings) + return nil +} + +// AppendValidator for the beacon state. Appends the new value +// to the the end of list. +func (b *BeaconState) AppendValidator(val *ethpb.Validator) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + vals := b.state.Validators + if b.sharedFieldReferences[validators].Refs() > 1 { + vals = b.validatorsReferences() + b.sharedFieldReferences[validators].MinusRef() + b.sharedFieldReferences[validators] = stateutil.NewRef(1) + } + + // append validator to slice + b.state.Validators = append(vals, val) + valIdx := types.ValidatorIndex(len(b.state.Validators) - 1) + + b.valMapHandler.Set(bytesutil.ToBytes48(val.PublicKey), valIdx) + + b.markFieldAsDirty(validators) + b.addDirtyIndices(validators, []uint64{uint64(valIdx)}) + return nil +} + +// AppendBalance for the beacon state. Appends the new value +// to the the end of list. +func (b *BeaconState) AppendBalance(bal uint64) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + bals := b.state.Balances + if b.sharedFieldReferences[balances].Refs() > 1 { + bals = b.balances() + b.sharedFieldReferences[balances].MinusRef() + b.sharedFieldReferences[balances] = stateutil.NewRef(1) + } + + b.state.Balances = append(bals, bal) + balIdx := len(b.state.Balances) - 1 + b.markFieldAsDirty(balances) + b.addDirtyIndices(balances, []uint64{uint64(balIdx)}) + return nil +} + +// AppendInactivityScore for the beacon state. +func (b *BeaconState) AppendInactivityScore(s uint64) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + scores := b.state.InactivityScores + if b.sharedFieldReferences[inactivityScores].Refs() > 1 { + scores = b.inactivityScores() + b.sharedFieldReferences[inactivityScores].MinusRef() + b.sharedFieldReferences[inactivityScores] = stateutil.NewRef(1) + } + + b.state.InactivityScores = append(scores, s) + b.markFieldAsDirty(inactivityScores) + return nil +} + +// SetInactivityScores for the beacon state. Updates the entire +// list to a new value by overwriting the previous one. +func (b *BeaconState) SetInactivityScores(val []uint64) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.sharedFieldReferences[inactivityScores].MinusRef() + b.sharedFieldReferences[inactivityScores] = stateutil.NewRef(1) + + b.state.InactivityScores = val + b.markFieldAsDirty(inactivityScores) + return nil +} diff --git a/beacon-chain/state/state-native/v2/state_trie.go b/beacon-chain/state/state-native/v2/state_trie.go new file mode 100644 index 0000000000..3a4f390233 --- /dev/null +++ b/beacon-chain/state/state-native/v2/state_trie.go @@ -0,0 +1,444 @@ +package v2 + +import ( + "context" + "io" + "io/ioutil" + "runtime" + "sort" + + "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/beacon-chain/state" + "github.com/prysmaticlabs/prysm/beacon-chain/state/state-native/fieldtrie" + "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" + "github.com/prysmaticlabs/prysm/beacon-chain/state/types" + "github.com/prysmaticlabs/prysm/config/features" + fieldparams "github.com/prysmaticlabs/prysm/config/fieldparams" + "github.com/prysmaticlabs/prysm/config/params" + "github.com/prysmaticlabs/prysm/container/slice" + "github.com/prysmaticlabs/prysm/crypto/hash" + "github.com/prysmaticlabs/prysm/encoding/bytesutil" + "github.com/prysmaticlabs/prysm/encoding/ssz" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" + "go.opencensus.io/trace" + "google.golang.org/protobuf/proto" +) + +// InitializeFromProto the beacon state from a protobuf representation. +func InitializeFromProto(st *ethpb.BeaconStateAltair) (*BeaconState, error) { + return InitializeFromProtoUnsafe(proto.Clone(st).(*ethpb.BeaconStateAltair)) +} + +// InitializeFromSSZReader can be used when the source for a serialized BeaconState object +// is an io.Reader. This allows client code to remain agnostic about whether the data comes +// from the network or a file without needing to read the entire state into mem as a large byte slice. +func InitializeFromSSZReader(r io.Reader) (*BeaconState, error) { + b, err := ioutil.ReadAll(r) + if err != nil { + return nil, err + } + return InitializeFromSSZBytes(b) +} + +// InitializeFromSSZBytes is a convenience method to obtain a BeaconState by unmarshaling +// a slice of bytes containing the ssz-serialized representation of the state. +func InitializeFromSSZBytes(marshaled []byte) (*BeaconState, error) { + st := ðpb.BeaconStateAltair{} + if err := st.UnmarshalSSZ(marshaled); err != nil { + return nil, err + } + return InitializeFromProtoUnsafe(st) +} + +// InitializeFromProtoUnsafe directly uses the beacon state protobuf pointer +// and sets it as the inner state of the BeaconState type. +func InitializeFromProtoUnsafe(st *ethpb.BeaconStateAltair) (*BeaconState, error) { + if st == nil { + return nil, errors.New("received nil state") + } + + fieldCount := params.BeaconConfig().BeaconStateAltairFieldCount + b := &BeaconState{ + state: st, + dirtyFields: make(map[types.FieldIndex]bool, fieldCount), + dirtyIndices: make(map[types.FieldIndex][]uint64, fieldCount), + stateFieldLeaves: make(map[types.FieldIndex]*fieldtrie.FieldTrie, fieldCount), + sharedFieldReferences: make(map[types.FieldIndex]*stateutil.Reference, 11), + rebuildTrie: make(map[types.FieldIndex]bool, fieldCount), + valMapHandler: stateutil.NewValMapHandler(st.Validators), + } + + var err error + for i := 0; i < fieldCount; i++ { + b.dirtyFields[types.FieldIndex(i)] = true + b.rebuildTrie[types.FieldIndex(i)] = true + b.dirtyIndices[types.FieldIndex(i)] = []uint64{} + b.stateFieldLeaves[types.FieldIndex(i)], err = fieldtrie.NewFieldTrie(types.FieldIndex(i), types.BasicArray, nil, 0) + if err != nil { + return nil, err + } + } + + // Initialize field reference tracking for shared data. + b.sharedFieldReferences[randaoMixes] = stateutil.NewRef(1) + b.sharedFieldReferences[stateRoots] = stateutil.NewRef(1) + b.sharedFieldReferences[blockRoots] = stateutil.NewRef(1) + b.sharedFieldReferences[previousEpochParticipationBits] = stateutil.NewRef(1) // New in Altair. + b.sharedFieldReferences[currentEpochParticipationBits] = stateutil.NewRef(1) // New in Altair. + b.sharedFieldReferences[slashings] = stateutil.NewRef(1) + b.sharedFieldReferences[eth1DataVotes] = stateutil.NewRef(1) + b.sharedFieldReferences[validators] = stateutil.NewRef(1) + b.sharedFieldReferences[balances] = stateutil.NewRef(1) + b.sharedFieldReferences[inactivityScores] = stateutil.NewRef(1) // New in Altair. + b.sharedFieldReferences[historicalRoots] = stateutil.NewRef(1) + + state.StateCount.Inc() + return b, nil +} + +// Copy returns a deep copy of the beacon state. +func (b *BeaconState) Copy() state.BeaconState { + if !b.hasInnerState() { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + fieldCount := params.BeaconConfig().BeaconStateAltairFieldCount + + dst := &BeaconState{ + state: ðpb.BeaconStateAltair{ + // Primitive types, safe to copy. + GenesisTime: b.state.GenesisTime, + Slot: b.state.Slot, + Eth1DepositIndex: b.state.Eth1DepositIndex, + + // Large arrays, infrequently changed, constant size. + RandaoMixes: b.state.RandaoMixes, + StateRoots: b.state.StateRoots, + BlockRoots: b.state.BlockRoots, + Slashings: b.state.Slashings, + Eth1DataVotes: b.state.Eth1DataVotes, + + // Large arrays, increases over time. + Validators: b.state.Validators, + Balances: b.state.Balances, + HistoricalRoots: b.state.HistoricalRoots, + PreviousEpochParticipation: b.state.PreviousEpochParticipation, + CurrentEpochParticipation: b.state.CurrentEpochParticipation, + InactivityScores: b.state.InactivityScores, + + // Everything else, too small to be concerned about, constant size. + Fork: b.fork(), + LatestBlockHeader: b.latestBlockHeader(), + Eth1Data: b.eth1Data(), + JustificationBits: b.justificationBits(), + PreviousJustifiedCheckpoint: b.previousJustifiedCheckpoint(), + CurrentJustifiedCheckpoint: b.currentJustifiedCheckpoint(), + FinalizedCheckpoint: b.finalizedCheckpoint(), + GenesisValidatorsRoot: b.genesisValidatorRoot(), + CurrentSyncCommittee: b.currentSyncCommittee(), + NextSyncCommittee: b.nextSyncCommittee(), + }, + dirtyFields: make(map[types.FieldIndex]bool, fieldCount), + dirtyIndices: make(map[types.FieldIndex][]uint64, fieldCount), + rebuildTrie: make(map[types.FieldIndex]bool, fieldCount), + sharedFieldReferences: make(map[types.FieldIndex]*stateutil.Reference, 11), + stateFieldLeaves: make(map[types.FieldIndex]*fieldtrie.FieldTrie, fieldCount), + + // Share the reference to validator index map. + valMapHandler: b.valMapHandler, + } + + for field, ref := range b.sharedFieldReferences { + ref.AddRef() + dst.sharedFieldReferences[field] = ref + } + + // Increment ref for validator map + b.valMapHandler.AddRef() + + for i := range b.dirtyFields { + dst.dirtyFields[i] = true + } + + for i := range b.dirtyIndices { + indices := make([]uint64, len(b.dirtyIndices[i])) + copy(indices, b.dirtyIndices[i]) + dst.dirtyIndices[i] = indices + } + + for i := range b.rebuildTrie { + dst.rebuildTrie[i] = true + } + + for fldIdx, fieldTrie := range b.stateFieldLeaves { + dst.stateFieldLeaves[fldIdx] = fieldTrie + if fieldTrie.FieldReference() != nil { + fieldTrie.Lock() + fieldTrie.FieldReference().AddRef() + fieldTrie.Unlock() + } + } + + if b.merkleLayers != nil { + dst.merkleLayers = make([][][]byte, len(b.merkleLayers)) + for i, layer := range b.merkleLayers { + dst.merkleLayers[i] = make([][]byte, len(layer)) + for j, content := range layer { + dst.merkleLayers[i][j] = make([]byte, len(content)) + copy(dst.merkleLayers[i][j], content) + } + } + } + + state.StateCount.Inc() + // Finalizer runs when dst is being destroyed in garbage collection. + runtime.SetFinalizer(dst, func(b *BeaconState) { + for field, v := range b.sharedFieldReferences { + v.MinusRef() + if b.stateFieldLeaves[field].FieldReference() != nil { + b.stateFieldLeaves[field].FieldReference().MinusRef() + } + } + for i := 0; i < fieldCount; i++ { + field := types.FieldIndex(i) + delete(b.stateFieldLeaves, field) + delete(b.dirtyIndices, field) + delete(b.dirtyFields, field) + delete(b.sharedFieldReferences, field) + delete(b.stateFieldLeaves, field) + } + state.StateCount.Sub(1) + }) + + return dst +} + +// HashTreeRoot of the beacon state retrieves the Merkle root of the trie +// representation of the beacon state based on the eth2 Simple Serialize specification. +func (b *BeaconState) HashTreeRoot(ctx context.Context) ([32]byte, error) { + _, span := trace.StartSpan(ctx, "beaconStateAltair.HashTreeRoot") + defer span.End() + + b.lock.Lock() + defer b.lock.Unlock() + if err := b.initializeMerkleLayers(ctx); err != nil { + return [32]byte{}, err + } + if err := b.recomputeDirtyFields(ctx); err != nil { + return [32]byte{}, err + } + return bytesutil.ToBytes32(b.merkleLayers[len(b.merkleLayers)-1][0]), nil +} + +// Initializes the Merkle layers for the beacon state if they are empty. +// WARNING: Caller must acquire the mutex before using. +func (b *BeaconState) initializeMerkleLayers(ctx context.Context) error { + if len(b.merkleLayers) > 0 { + return nil + } + fieldRoots, err := computeFieldRoots(ctx, b.state) + if err != nil { + return err + } + layers := stateutil.Merkleize(fieldRoots) + b.merkleLayers = layers + b.dirtyFields = make(map[types.FieldIndex]bool, params.BeaconConfig().BeaconStateAltairFieldCount) + return nil +} + +// Recomputes the Merkle layers for the dirty fields in the state. +// WARNING: Caller must acquire the mutex before using. +func (b *BeaconState) recomputeDirtyFields(ctx context.Context) error { + for field := range b.dirtyFields { + root, err := b.rootSelector(ctx, field) + if err != nil { + return err + } + b.merkleLayers[0][field] = root[:] + b.recomputeRoot(int(field)) + delete(b.dirtyFields, field) + } + return nil +} + +// FieldReferencesCount returns the reference count held by each field. This +// also includes the field trie held by each field. +func (b *BeaconState) FieldReferencesCount() map[string]uint64 { + refMap := make(map[string]uint64) + b.lock.RLock() + defer b.lock.RUnlock() + for i, f := range b.sharedFieldReferences { + refMap[i.String(b.Version())] = uint64(f.Refs()) + } + for i, f := range b.stateFieldLeaves { + numOfRefs := uint64(f.FieldReference().Refs()) + f.RLock() + if !f.Empty() { + refMap[i.String(b.Version())+"_trie"] = numOfRefs + } + f.RUnlock() + } + return refMap +} + +// IsNil checks if the state and the underlying proto +// object are nil. +func (b *BeaconState) IsNil() bool { + return b == nil || b.state == nil +} + +func (b *BeaconState) rootSelector(ctx context.Context, field types.FieldIndex) ([32]byte, error) { + _, span := trace.StartSpan(ctx, "beaconState.rootSelector") + defer span.End() + span.AddAttributes(trace.StringAttribute("field", field.String(b.Version()))) + + hasher := hash.CustomSHA256Hasher() + switch field { + case genesisTime: + return ssz.Uint64Root(b.state.GenesisTime), nil + case genesisValidatorRoot: + return bytesutil.ToBytes32(b.state.GenesisValidatorsRoot), nil + case slot: + return ssz.Uint64Root(uint64(b.state.Slot)), nil + case eth1DepositIndex: + return ssz.Uint64Root(b.state.Eth1DepositIndex), nil + case fork: + return ssz.ForkRoot(b.state.Fork) + case latestBlockHeader: + return stateutil.BlockHeaderRoot(b.state.LatestBlockHeader) + case blockRoots: + if b.rebuildTrie[field] { + err := b.resetFieldTrie(field, b.state.BlockRoots, fieldparams.BlockRootsLength) + if err != nil { + return [32]byte{}, err + } + delete(b.rebuildTrie, field) + return b.stateFieldLeaves[field].TrieRoot() + } + return b.recomputeFieldTrie(blockRoots, b.state.BlockRoots) + case stateRoots: + if b.rebuildTrie[field] { + err := b.resetFieldTrie(field, b.state.StateRoots, fieldparams.StateRootsLength) + if err != nil { + return [32]byte{}, err + } + delete(b.rebuildTrie, field) + return b.stateFieldLeaves[field].TrieRoot() + } + return b.recomputeFieldTrie(stateRoots, b.state.StateRoots) + case historicalRoots: + return ssz.ByteArrayRootWithLimit(b.state.HistoricalRoots, fieldparams.HistoricalRootsLength) + case eth1Data: + return stateutil.Eth1Root(hasher, b.state.Eth1Data) + case eth1DataVotes: + if b.rebuildTrie[field] { + err := b.resetFieldTrie( + field, + b.state.Eth1DataVotes, + fieldparams.Eth1DataVotesLength, + ) + if err != nil { + return [32]byte{}, err + } + delete(b.rebuildTrie, field) + return b.stateFieldLeaves[field].TrieRoot() + } + return b.recomputeFieldTrie(field, b.state.Eth1DataVotes) + case validators: + if b.rebuildTrie[field] { + err := b.resetFieldTrie(field, b.state.Validators, fieldparams.ValidatorRegistryLimit) + if err != nil { + return [32]byte{}, err + } + delete(b.rebuildTrie, validators) + return b.stateFieldLeaves[field].TrieRoot() + } + return b.recomputeFieldTrie(validators, b.state.Validators) + case balances: + if features.Get().EnableBalanceTrieComputation { + if b.rebuildTrie[field] { + maxBalCap := uint64(fieldparams.ValidatorRegistryLimit) + elemSize := uint64(8) + balLimit := (maxBalCap*elemSize + 31) / 32 + err := b.resetFieldTrie(field, b.state.Balances, balLimit) + if err != nil { + return [32]byte{}, err + } + delete(b.rebuildTrie, field) + return b.stateFieldLeaves[field].TrieRoot() + } + return b.recomputeFieldTrie(balances, b.state.Balances) + } + return stateutil.Uint64ListRootWithRegistryLimit(b.state.Balances) + case randaoMixes: + if b.rebuildTrie[field] { + err := b.resetFieldTrie(field, b.state.RandaoMixes, fieldparams.RandaoMixesLength) + if err != nil { + return [32]byte{}, err + } + delete(b.rebuildTrie, field) + return b.stateFieldLeaves[field].TrieRoot() + } + return b.recomputeFieldTrie(randaoMixes, b.state.RandaoMixes) + case slashings: + return ssz.SlashingsRoot(b.state.Slashings) + case previousEpochParticipationBits: + return stateutil.ParticipationBitsRoot(b.state.PreviousEpochParticipation) + case currentEpochParticipationBits: + return stateutil.ParticipationBitsRoot(b.state.CurrentEpochParticipation) + case justificationBits: + return bytesutil.ToBytes32(b.state.JustificationBits), nil + case previousJustifiedCheckpoint: + return ssz.CheckpointRoot(hasher, b.state.PreviousJustifiedCheckpoint) + case currentJustifiedCheckpoint: + return ssz.CheckpointRoot(hasher, b.state.CurrentJustifiedCheckpoint) + case finalizedCheckpoint: + return ssz.CheckpointRoot(hasher, b.state.FinalizedCheckpoint) + case inactivityScores: + return stateutil.Uint64ListRootWithRegistryLimit(b.state.InactivityScores) + case currentSyncCommittee: + return stateutil.SyncCommitteeRoot(b.state.CurrentSyncCommittee) + case nextSyncCommittee: + return stateutil.SyncCommitteeRoot(b.state.NextSyncCommittee) + } + return [32]byte{}, errors.New("invalid field index provided") +} + +func (b *BeaconState) recomputeFieldTrie(index types.FieldIndex, elements interface{}) ([32]byte, error) { + fTrie := b.stateFieldLeaves[index] + // We can't lock the trie directly because the trie's variable gets reassigned, + // and therefore we would call Unlock() on a different object. + fTrieMutex := fTrie.RWMutex + if fTrie.FieldReference().Refs() > 1 { + fTrieMutex.Lock() + fTrie.FieldReference().MinusRef() + newTrie := fTrie.CopyTrie() + b.stateFieldLeaves[index] = newTrie + fTrie = newTrie + fTrieMutex.Unlock() + } + // remove duplicate indexes + b.dirtyIndices[index] = slice.SetUint64(b.dirtyIndices[index]) + // sort indexes again + sort.Slice(b.dirtyIndices[index], func(i int, j int) bool { + return b.dirtyIndices[index][i] < b.dirtyIndices[index][j] + }) + root, err := fTrie.RecomputeTrie(b.dirtyIndices[index], elements) + if err != nil { + return [32]byte{}, err + } + b.dirtyIndices[index] = []uint64{} + return root, nil +} + +func (b *BeaconState) resetFieldTrie(index types.FieldIndex, elements interface{}, length uint64) error { + fTrie, err := fieldtrie.NewFieldTrie(index, fieldMap[index], elements, length) + if err != nil { + return err + } + b.stateFieldLeaves[index] = fTrie + b.dirtyIndices[index] = []uint64{} + return nil +} diff --git a/beacon-chain/state/state-native/v2/state_trie_test.go b/beacon-chain/state/state-native/v2/state_trie_test.go new file mode 100644 index 0000000000..ede78dc944 --- /dev/null +++ b/beacon-chain/state/state-native/v2/state_trie_test.go @@ -0,0 +1,169 @@ +package v2 + +import ( + "strconv" + "sync" + "testing" + + "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" + "github.com/prysmaticlabs/prysm/config/features" + fieldparams "github.com/prysmaticlabs/prysm/config/fieldparams" + "github.com/prysmaticlabs/prysm/config/params" + "github.com/prysmaticlabs/prysm/encoding/bytesutil" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/testing/assert" + "github.com/prysmaticlabs/prysm/testing/require" +) + +func TestMain(m *testing.M) { + resetCfg := features.InitWithReset(&features.Flags{EnableBalanceTrieComputation: true}) + defer resetCfg() + m.Run() +} + +func TestValidatorMap_DistinctCopy(t *testing.T) { + count := uint64(100) + vals := make([]*ethpb.Validator, 0, count) + for i := uint64(1); i < count; i++ { + someRoot := [32]byte{} + someKey := [fieldparams.BLSPubkeyLength]byte{} + copy(someRoot[:], strconv.Itoa(int(i))) + copy(someKey[:], strconv.Itoa(int(i))) + vals = append(vals, ðpb.Validator{ + PublicKey: someKey[:], + WithdrawalCredentials: someRoot[:], + EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, + Slashed: false, + ActivationEligibilityEpoch: 1, + ActivationEpoch: 1, + ExitEpoch: 1, + WithdrawableEpoch: 1, + }) + } + handler := stateutil.NewValMapHandler(vals) + newHandler := handler.Copy() + wantedPubkey := strconv.Itoa(22) + handler.Set(bytesutil.ToBytes48([]byte(wantedPubkey)), 27) + val1, _ := handler.Get(bytesutil.ToBytes48([]byte(wantedPubkey))) + val2, _ := newHandler.Get(bytesutil.ToBytes48([]byte(wantedPubkey))) + assert.NotEqual(t, val1, val2, "Values are supposed to be unequal due to copy") +} + +func TestInitializeFromProto(t *testing.T) { + type test struct { + name string + state *ethpb.BeaconStateAltair + error string + } + initTests := []test{ + { + name: "nil state", + state: nil, + error: "received nil state", + }, + { + name: "nil validators", + state: ðpb.BeaconStateAltair{ + Slot: 4, + Validators: nil, + }, + }, + { + name: "empty state", + state: ðpb.BeaconStateAltair{}, + }, + // TODO: Add full state. Blocked by testutil migration. + } + for _, tt := range initTests { + t.Run(tt.name, func(t *testing.T) { + _, err := InitializeFromProto(tt.state) + if tt.error != "" { + require.ErrorContains(t, tt.error, err) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestBeaconState_NoDeadlock(t *testing.T) { + count := uint64(100) + vals := make([]*ethpb.Validator, 0, count) + for i := uint64(1); i < count; i++ { + someRoot := [32]byte{} + someKey := [fieldparams.BLSPubkeyLength]byte{} + copy(someRoot[:], strconv.Itoa(int(i))) + copy(someKey[:], strconv.Itoa(int(i))) + vals = append(vals, ðpb.Validator{ + PublicKey: someKey[:], + WithdrawalCredentials: someRoot[:], + EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, + Slashed: false, + ActivationEligibilityEpoch: 1, + ActivationEpoch: 1, + ExitEpoch: 1, + WithdrawableEpoch: 1, + }) + } + st, err := InitializeFromProtoUnsafe(ðpb.BeaconStateAltair{ + Validators: vals, + }) + assert.NoError(t, err) + + wg := new(sync.WaitGroup) + + wg.Add(1) + go func() { + // Continuously lock and unlock the state + // by acquiring the lock. + for i := 0; i < 1000; i++ { + for _, f := range st.stateFieldLeaves { + f.Lock() + if f.Empty() { + f.InsertFieldLayer(make([][]*[32]byte, 10)) + } + f.Unlock() + f.FieldReference().AddRef() + } + } + wg.Done() + }() + // Constantly read from the offending portion + // of the code to ensure there is no possible + // recursive read locking. + for i := 0; i < 1000; i++ { + go func() { + _ = st.FieldReferencesCount() + }() + } + // Test will not terminate in the event of a deadlock. + wg.Wait() +} + +func TestInitializeFromProtoUnsafe(t *testing.T) { + type test struct { + name string + state *ethpb.BeaconStateAltair + error string + } + initTests := []test{ + { + name: "nil state", + state: nil, + error: "received nil state", + }, + { + name: "nil validators", + state: ðpb.BeaconStateAltair{ + Slot: 4, + Validators: nil, + }, + }, + { + name: "empty state", + state: ðpb.BeaconStateAltair{}, + }, + // TODO: Add full state. Blocked by testutil migration. + } + _ = initTests +} diff --git a/beacon-chain/state/state-native/v2/types.go b/beacon-chain/state/state-native/v2/types.go new file mode 100644 index 0000000000..ca72c8ddb9 --- /dev/null +++ b/beacon-chain/state/state-native/v2/types.go @@ -0,0 +1,78 @@ +package v2 + +import ( + "sync" + + "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/beacon-chain/state/state-native/fieldtrie" + "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" + "github.com/prysmaticlabs/prysm/beacon-chain/state/types" + "github.com/prysmaticlabs/prysm/config/params" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" +) + +func init() { + fieldMap = make(map[types.FieldIndex]types.DataType, params.BeaconConfig().BeaconStateFieldCount) + + // Initialize the fixed sized arrays. + fieldMap[types.BlockRoots] = types.BasicArray + fieldMap[types.StateRoots] = types.BasicArray + fieldMap[types.RandaoMixes] = types.BasicArray + + // Initialize the composite arrays. + fieldMap[types.Eth1DataVotes] = types.CompositeArray + fieldMap[types.Validators] = types.CompositeArray + + // Initialize Compressed Arrays + fieldMap[types.Balances] = types.CompressedArray +} + +// fieldMap keeps track of each field +// to its corresponding data type. +var fieldMap map[types.FieldIndex]types.DataType + +// ErrNilInnerState returns when the inner state is nil and no copy set or get +// operations can be performed on state. +var ErrNilInnerState = errors.New("nil inner state") + +// BeaconState defines a struct containing utilities for the eth2 chain state, defining +// getters and setters for its respective values and helpful functions such as HashTreeRoot(). +type BeaconState struct { + state *ethpb.BeaconStateAltair + lock sync.RWMutex + dirtyFields map[types.FieldIndex]bool + dirtyIndices map[types.FieldIndex][]uint64 + stateFieldLeaves map[types.FieldIndex]*fieldtrie.FieldTrie + rebuildTrie map[types.FieldIndex]bool + valMapHandler *stateutil.ValidatorMapHandler + merkleLayers [][][]byte + sharedFieldReferences map[types.FieldIndex]*stateutil.Reference +} + +// Field Aliases for values from the types package. +const ( + genesisTime = types.GenesisTime + genesisValidatorRoot = types.GenesisValidatorRoot + slot = types.Slot + fork = types.Fork + latestBlockHeader = types.LatestBlockHeader + blockRoots = types.BlockRoots + stateRoots = types.StateRoots + historicalRoots = types.HistoricalRoots + eth1Data = types.Eth1Data + eth1DataVotes = types.Eth1DataVotes + eth1DepositIndex = types.Eth1DepositIndex + validators = types.Validators + balances = types.Balances + randaoMixes = types.RandaoMixes + slashings = types.Slashings + previousEpochParticipationBits = types.PreviousEpochParticipationBits + currentEpochParticipationBits = types.CurrentEpochParticipationBits + justificationBits = types.JustificationBits + previousJustifiedCheckpoint = types.PreviousJustifiedCheckpoint + currentJustifiedCheckpoint = types.CurrentJustifiedCheckpoint + finalizedCheckpoint = types.FinalizedCheckpoint + inactivityScores = types.InactivityScores + currentSyncCommittee = types.CurrentSyncCommittee + nextSyncCommittee = types.NextSyncCommittee +) diff --git a/beacon-chain/state/state-native/v3/BUILD.bazel b/beacon-chain/state/state-native/v3/BUILD.bazel new file mode 100644 index 0000000000..2a7efe182d --- /dev/null +++ b/beacon-chain/state/state-native/v3/BUILD.bazel @@ -0,0 +1,92 @@ +load("@prysm//tools/go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = [ + "deprecated_getters.go", + "deprecated_setters.go", + "field_roots.go", + "getters_block.go", + "getters_checkpoint.go", + "getters_eth1.go", + "getters_misc.go", + "getters_participation.go", + "getters_payload_header.go", + "getters_randao.go", + "getters_state.go", + "getters_sync_committee.go", + "getters_validator.go", + "proofs.go", + "setters_block.go", + "setters_checkpoint.go", + "setters_eth1.go", + "setters_misc.go", + "setters_participation.go", + "setters_payload_header.go", + "setters_randao.go", + "setters_state.go", + "setters_sync_committee.go", + "setters_validator.go", + "state_trie.go", + "types.go", + ], + importpath = "github.com/prysmaticlabs/prysm/beacon-chain/state/state-native/v3", + visibility = [ + "//beacon-chain:__subpackages__", + "//testing/spectest:__subpackages__", + "//testing/util:__pkg__", + ], + deps = [ + "//beacon-chain/state:go_default_library", + "//beacon-chain/state/state-native/fieldtrie:go_default_library", + "//beacon-chain/state/state-native/v1:go_default_library", + "//beacon-chain/state/stateutil:go_default_library", + "//beacon-chain/state/types:go_default_library", + "//config/features:go_default_library", + "//config/fieldparams:go_default_library", + "//config/params:go_default_library", + "//container/slice:go_default_library", + "//crypto/hash:go_default_library", + "//encoding/bytesutil:go_default_library", + "//encoding/ssz:go_default_library", + "//proto/prysm/v1alpha1:go_default_library", + "//runtime/version:go_default_library", + "@com_github_pkg_errors//:go_default_library", + "@com_github_prysmaticlabs_eth2_types//:go_default_library", + "@com_github_prysmaticlabs_go_bitfield//:go_default_library", + "@io_opencensus_go//trace:go_default_library", + "@org_golang_google_protobuf//proto:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = [ + "deprecated_getters_test.go", + "deprecated_setters_test.go", + "getters_block_test.go", + "getters_test.go", + "getters_validator_test.go", + "proofs_test.go", + "setters_test.go", + "state_trie_test.go", + ], + embed = [":go_default_library"], + deps = [ + "//beacon-chain/state/state-native/v1:go_default_library", + "//beacon-chain/state/stateutil:go_default_library", + "//beacon-chain/state/types:go_default_library", + "//config/features:go_default_library", + "//config/fieldparams:go_default_library", + "//config/params:go_default_library", + "//container/trie:go_default_library", + "//crypto/bls:go_default_library", + "//encoding/bytesutil:go_default_library", + "//proto/prysm/v1alpha1:go_default_library", + "//testing/assert:go_default_library", + "//testing/require:go_default_library", + "//testing/util:go_default_library", + "@com_github_prysmaticlabs_eth2_types//:go_default_library", + "@com_github_prysmaticlabs_go_bitfield//:go_default_library", + ], +) diff --git a/beacon-chain/state/state-native/v3/deprecated_getters.go b/beacon-chain/state/state-native/v3/deprecated_getters.go new file mode 100644 index 0000000000..d02110aeb6 --- /dev/null +++ b/beacon-chain/state/state-native/v3/deprecated_getters.go @@ -0,0 +1,16 @@ +package v3 + +import ( + "github.com/pkg/errors" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" +) + +// PreviousEpochAttestations is not supported for HF1 beacon state. +func (*BeaconState) PreviousEpochAttestations() ([]*ethpb.PendingAttestation, error) { + return nil, errors.New("PreviousEpochAttestations is not supported for version Bellatrix beacon state") +} + +// CurrentEpochAttestations is not supported for HF1 beacon state. +func (*BeaconState) CurrentEpochAttestations() ([]*ethpb.PendingAttestation, error) { + return nil, errors.New("CurrentEpochAttestations is not supported for version Bellatrix beacon state") +} diff --git a/beacon-chain/state/state-native/v3/deprecated_getters_test.go b/beacon-chain/state/state-native/v3/deprecated_getters_test.go new file mode 100644 index 0000000000..0907bc6391 --- /dev/null +++ b/beacon-chain/state/state-native/v3/deprecated_getters_test.go @@ -0,0 +1,19 @@ +package v3 + +import ( + "testing" + + "github.com/prysmaticlabs/prysm/testing/require" +) + +func TestBeaconState_CurrentEpochAttestations(t *testing.T) { + s := &BeaconState{} + _, err := s.CurrentEpochAttestations() + require.ErrorContains(t, "CurrentEpochAttestations is not supported for version Bellatrix beacon state", err) +} + +func TestBeaconState_PreviousEpochAttestations(t *testing.T) { + s := &BeaconState{} + _, err := s.PreviousEpochAttestations() + require.ErrorContains(t, "PreviousEpochAttestations is not supported for version Bellatrix beacon state", err) +} diff --git a/beacon-chain/state/state-native/v3/deprecated_setters.go b/beacon-chain/state/state-native/v3/deprecated_setters.go new file mode 100644 index 0000000000..42c8677186 --- /dev/null +++ b/beacon-chain/state/state-native/v3/deprecated_setters.go @@ -0,0 +1,31 @@ +package v3 + +import ( + "github.com/pkg/errors" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" +) + +// SetPreviousEpochAttestations is not supported for HF1 beacon state. +func (*BeaconState) SetPreviousEpochAttestations(_ []*ethpb.PendingAttestation) error { + return errors.New("SetPreviousEpochAttestations is not supported for version Bellatrix beacon state") +} + +// SetCurrentEpochAttestations is not supported for HF1 beacon state. +func (*BeaconState) SetCurrentEpochAttestations(_ []*ethpb.PendingAttestation) error { + return errors.New("SetCurrentEpochAttestations is not supported for version Bellatrix beacon state") +} + +// AppendCurrentEpochAttestations is not supported for HF1 beacon state. +func (*BeaconState) AppendCurrentEpochAttestations(_ *ethpb.PendingAttestation) error { + return errors.New("AppendCurrentEpochAttestations is not supported for version Bellatrix beacon state") +} + +// AppendPreviousEpochAttestations is not supported for HF1 beacon state. +func (*BeaconState) AppendPreviousEpochAttestations(_ *ethpb.PendingAttestation) error { + return errors.New("AppendPreviousEpochAttestations is not supported for version Bellatrix beacon state") +} + +// RotateAttestations is not supported for HF1 beacon state. +func (*BeaconState) RotateAttestations() error { + return errors.New("RotateAttestations is not supported for version Bellatrix beacon state") +} diff --git a/beacon-chain/state/state-native/v3/deprecated_setters_test.go b/beacon-chain/state/state-native/v3/deprecated_setters_test.go new file mode 100644 index 0000000000..18c338f8bf --- /dev/null +++ b/beacon-chain/state/state-native/v3/deprecated_setters_test.go @@ -0,0 +1,27 @@ +package v3 + +import ( + "testing" + + "github.com/prysmaticlabs/prysm/testing/require" +) + +func TestBeaconState_AppendCurrentEpochAttestations(t *testing.T) { + s := &BeaconState{} + require.ErrorContains(t, "AppendCurrentEpochAttestations is not supported for version Bellatrix beacon state", s.AppendCurrentEpochAttestations(nil)) +} + +func TestBeaconState_AppendPreviousEpochAttestations(t *testing.T) { + s := &BeaconState{} + require.ErrorContains(t, "AppendPreviousEpochAttestations is not supported for version Bellatrix beacon state", s.AppendPreviousEpochAttestations(nil)) +} + +func TestBeaconState_SetCurrentEpochAttestations(t *testing.T) { + s := &BeaconState{} + require.ErrorContains(t, "SetCurrentEpochAttestations is not supported for version Bellatrix beacon state", s.SetCurrentEpochAttestations(nil)) +} + +func TestBeaconState_SetPreviousEpochAttestations(t *testing.T) { + s := &BeaconState{} + require.ErrorContains(t, "SetPreviousEpochAttestations is not supported for version Bellatrix beacon state", s.SetPreviousEpochAttestations(nil)) +} diff --git a/beacon-chain/state/state-native/v3/field_roots.go b/beacon-chain/state/state-native/v3/field_roots.go new file mode 100644 index 0000000000..626f943252 --- /dev/null +++ b/beacon-chain/state/state-native/v3/field_roots.go @@ -0,0 +1,19 @@ +package v3 + +import ( + "context" + + "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" + "github.com/prysmaticlabs/prysm/config/features" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" +) + +// computeFieldRoots returns the hash tree root computations of every field in +// the beacon state as a list of 32 byte roots. +//nolint:deadcode +func computeFieldRoots(ctx context.Context, state *ethpb.BeaconStateBellatrix) ([][]byte, error) { + if features.Get().EnableSSZCache { + return stateutil.CachedHasher.ComputeFieldRootsWithHasherBellatrix(ctx, state) + } + return stateutil.NocachedHasher.ComputeFieldRootsWithHasherBellatrix(ctx, state) +} diff --git a/beacon-chain/state/state-native/v3/getters_block.go b/beacon-chain/state/state-native/v3/getters_block.go new file mode 100644 index 0000000000..b9f4bb5b3e --- /dev/null +++ b/beacon-chain/state/state-native/v3/getters_block.go @@ -0,0 +1,99 @@ +package v3 + +import ( + "github.com/prysmaticlabs/prysm/encoding/bytesutil" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" +) + +// LatestBlockHeader stored within the beacon state. +func (b *BeaconState) LatestBlockHeader() *ethpb.BeaconBlockHeader { + if !b.hasInnerState() { + return nil + } + if b.state.LatestBlockHeader == nil { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.latestBlockHeader() +} + +// latestBlockHeader stored within the beacon state. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) latestBlockHeader() *ethpb.BeaconBlockHeader { + if !b.hasInnerState() { + return nil + } + if b.state.LatestBlockHeader == nil { + return nil + } + + hdr := ðpb.BeaconBlockHeader{ + Slot: b.state.LatestBlockHeader.Slot, + ProposerIndex: b.state.LatestBlockHeader.ProposerIndex, + } + + parentRoot := make([]byte, len(b.state.LatestBlockHeader.ParentRoot)) + bodyRoot := make([]byte, len(b.state.LatestBlockHeader.BodyRoot)) + stateRoot := make([]byte, len(b.state.LatestBlockHeader.StateRoot)) + + copy(parentRoot, b.state.LatestBlockHeader.ParentRoot) + copy(bodyRoot, b.state.LatestBlockHeader.BodyRoot) + copy(stateRoot, b.state.LatestBlockHeader.StateRoot) + hdr.ParentRoot = parentRoot + hdr.BodyRoot = bodyRoot + hdr.StateRoot = stateRoot + return hdr +} + +// BlockRoots kept track of in the beacon state. +func (b *BeaconState) BlockRoots() [][]byte { + if !b.hasInnerState() { + return nil + } + if b.state.BlockRoots == nil { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.blockRoots() +} + +// blockRoots kept track of in the beacon state. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) blockRoots() [][]byte { + if !b.hasInnerState() { + return nil + } + return bytesutil.SafeCopy2dBytes(b.state.BlockRoots) +} + +// BlockRootAtIndex retrieves a specific block root based on an +// input index value. +func (b *BeaconState) BlockRootAtIndex(idx uint64) ([]byte, error) { + if !b.hasInnerState() { + return nil, ErrNilInnerState + } + if b.state.BlockRoots == nil { + return nil, nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.blockRootAtIndex(idx) +} + +// blockRootAtIndex retrieves a specific block root based on an +// input index value. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) blockRootAtIndex(idx uint64) ([]byte, error) { + if !b.hasInnerState() { + return nil, ErrNilInnerState + } + return bytesutil.SafeCopyRootAtIndex(b.state.BlockRoots, idx) +} diff --git a/beacon-chain/state/state-native/v3/getters_block_test.go b/beacon-chain/state/state-native/v3/getters_block_test.go new file mode 100644 index 0000000000..1e328ee301 --- /dev/null +++ b/beacon-chain/state/state-native/v3/getters_block_test.go @@ -0,0 +1,59 @@ +package v3 + +import ( + "testing" + + "github.com/prysmaticlabs/prysm/encoding/bytesutil" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/testing/require" +) + +func TestBeaconState_LatestBlockHeader(t *testing.T) { + s, err := InitializeFromProto(ðpb.BeaconStateBellatrix{}) + require.NoError(t, err) + got := s.LatestBlockHeader() + require.DeepEqual(t, (*ethpb.BeaconBlockHeader)(nil), got) + + want := ðpb.BeaconBlockHeader{Slot: 100} + s, err = InitializeFromProto(ðpb.BeaconStateBellatrix{LatestBlockHeader: want}) + require.NoError(t, err) + got = s.LatestBlockHeader() + require.DeepEqual(t, want, got) + + // Test copy does not mutate. + got.Slot = 101 + require.DeepNotEqual(t, want, got) +} + +func TestBeaconState_BlockRoots(t *testing.T) { + s, err := InitializeFromProto(ðpb.BeaconStateBellatrix{}) + require.NoError(t, err) + got := s.BlockRoots() + require.DeepEqual(t, ([][]byte)(nil), got) + + want := [][]byte{{'a'}} + s, err = InitializeFromProto(ðpb.BeaconStateBellatrix{BlockRoots: want}) + require.NoError(t, err) + got = s.BlockRoots() + require.DeepEqual(t, want, got) + + // Test copy does not mutate. + got[0][0] = 'b' + require.DeepNotEqual(t, want, got) +} + +func TestBeaconState_BlockRootAtIndex(t *testing.T) { + s, err := InitializeFromProto(ðpb.BeaconStateBellatrix{}) + require.NoError(t, err) + got, err := s.BlockRootAtIndex(0) + require.NoError(t, err) + require.DeepEqual(t, ([]byte)(nil), got) + + r := [][]byte{{'a'}} + s, err = InitializeFromProto(ðpb.BeaconStateBellatrix{BlockRoots: r}) + require.NoError(t, err) + got, err = s.BlockRootAtIndex(0) + require.NoError(t, err) + want := bytesutil.PadTo([]byte{'a'}, 32) + require.DeepSSZEqual(t, want, got) +} diff --git a/beacon-chain/state/state-native/v3/getters_checkpoint.go b/beacon-chain/state/state-native/v3/getters_checkpoint.go new file mode 100644 index 0000000000..f6b6eab16c --- /dev/null +++ b/beacon-chain/state/state-native/v3/getters_checkpoint.go @@ -0,0 +1,160 @@ +package v3 + +import ( + "bytes" + + types "github.com/prysmaticlabs/eth2-types" + "github.com/prysmaticlabs/go-bitfield" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" +) + +// JustificationBits marking which epochs have been justified in the beacon chain. +func (b *BeaconState) JustificationBits() bitfield.Bitvector4 { + if !b.hasInnerState() { + return nil + } + if b.state.JustificationBits == nil { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.justificationBits() +} + +// justificationBits marking which epochs have been justified in the beacon chain. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) justificationBits() bitfield.Bitvector4 { + if !b.hasInnerState() { + return nil + } + if b.state.JustificationBits == nil { + return nil + } + + res := make([]byte, len(b.state.JustificationBits.Bytes())) + copy(res, b.state.JustificationBits.Bytes()) + return res +} + +// PreviousJustifiedCheckpoint denoting an epoch and block root. +func (b *BeaconState) PreviousJustifiedCheckpoint() *ethpb.Checkpoint { + if !b.hasInnerState() { + return nil + } + if b.state.PreviousJustifiedCheckpoint == nil { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.previousJustifiedCheckpoint() +} + +// previousJustifiedCheckpoint denoting an epoch and block root. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) previousJustifiedCheckpoint() *ethpb.Checkpoint { + if !b.hasInnerState() { + return nil + } + + return ethpb.CopyCheckpoint(b.state.PreviousJustifiedCheckpoint) +} + +// CurrentJustifiedCheckpoint denoting an epoch and block root. +func (b *BeaconState) CurrentJustifiedCheckpoint() *ethpb.Checkpoint { + if !b.hasInnerState() { + return nil + } + if b.state.CurrentJustifiedCheckpoint == nil { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.currentJustifiedCheckpoint() +} + +// currentJustifiedCheckpoint denoting an epoch and block root. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) currentJustifiedCheckpoint() *ethpb.Checkpoint { + if !b.hasInnerState() { + return nil + } + + return ethpb.CopyCheckpoint(b.state.CurrentJustifiedCheckpoint) +} + +// MatchCurrentJustifiedCheckpoint returns true if input justified checkpoint matches +// the current justified checkpoint in state. +func (b *BeaconState) MatchCurrentJustifiedCheckpoint(c *ethpb.Checkpoint) bool { + if !b.hasInnerState() { + return false + } + if b.state.CurrentJustifiedCheckpoint == nil { + return false + } + + if c.Epoch != b.state.CurrentJustifiedCheckpoint.Epoch { + return false + } + return bytes.Equal(c.Root, b.state.CurrentJustifiedCheckpoint.Root) +} + +// MatchPreviousJustifiedCheckpoint returns true if the input justified checkpoint matches +// the previous justified checkpoint in state. +func (b *BeaconState) MatchPreviousJustifiedCheckpoint(c *ethpb.Checkpoint) bool { + if !b.hasInnerState() { + return false + } + if b.state.PreviousJustifiedCheckpoint == nil { + return false + } + + if c.Epoch != b.state.PreviousJustifiedCheckpoint.Epoch { + return false + } + return bytes.Equal(c.Root, b.state.PreviousJustifiedCheckpoint.Root) +} + +// FinalizedCheckpoint denoting an epoch and block root. +func (b *BeaconState) FinalizedCheckpoint() *ethpb.Checkpoint { + if !b.hasInnerState() { + return nil + } + if b.state.FinalizedCheckpoint == nil { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.finalizedCheckpoint() +} + +// finalizedCheckpoint denoting an epoch and block root. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) finalizedCheckpoint() *ethpb.Checkpoint { + if !b.hasInnerState() { + return nil + } + + return ethpb.CopyCheckpoint(b.state.FinalizedCheckpoint) +} + +// FinalizedCheckpointEpoch returns the epoch value of the finalized checkpoint. +func (b *BeaconState) FinalizedCheckpointEpoch() types.Epoch { + if !b.hasInnerState() { + return 0 + } + if b.state.FinalizedCheckpoint == nil { + return 0 + } + b.lock.RLock() + defer b.lock.RUnlock() + + return b.state.FinalizedCheckpoint.Epoch +} diff --git a/beacon-chain/state/state-native/v3/getters_eth1.go b/beacon-chain/state/state-native/v3/getters_eth1.go new file mode 100644 index 0000000000..04449eb9d9 --- /dev/null +++ b/beacon-chain/state/state-native/v3/getters_eth1.go @@ -0,0 +1,91 @@ +package v3 + +import ( + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" +) + +// Eth1Data corresponding to the proof-of-work chain information stored in the beacon state. +func (b *BeaconState) Eth1Data() *ethpb.Eth1Data { + if !b.hasInnerState() { + return nil + } + if b.state.Eth1Data == nil { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.eth1Data() +} + +// eth1Data corresponding to the proof-of-work chain information stored in the beacon state. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) eth1Data() *ethpb.Eth1Data { + if !b.hasInnerState() { + return nil + } + if b.state.Eth1Data == nil { + return nil + } + + return ethpb.CopyETH1Data(b.state.Eth1Data) +} + +// Eth1DataVotes corresponds to votes from Ethereum on the canonical proof-of-work chain +// data retrieved from eth1. +func (b *BeaconState) Eth1DataVotes() []*ethpb.Eth1Data { + if !b.hasInnerState() { + return nil + } + if b.state.Eth1DataVotes == nil { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.eth1DataVotes() +} + +// eth1DataVotes corresponds to votes from Ethereum on the canonical proof-of-work chain +// data retrieved from eth1. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) eth1DataVotes() []*ethpb.Eth1Data { + if !b.hasInnerState() { + return nil + } + if b.state.Eth1DataVotes == nil { + return nil + } + + res := make([]*ethpb.Eth1Data, len(b.state.Eth1DataVotes)) + for i := 0; i < len(res); i++ { + res[i] = ethpb.CopyETH1Data(b.state.Eth1DataVotes[i]) + } + return res +} + +// Eth1DepositIndex corresponds to the index of the deposit made to the +// validator deposit contract at the time of this state's eth1 data. +func (b *BeaconState) Eth1DepositIndex() uint64 { + if !b.hasInnerState() { + return 0 + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.eth1DepositIndex() +} + +// eth1DepositIndex corresponds to the index of the deposit made to the +// validator deposit contract at the time of this state's eth1 data. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) eth1DepositIndex() uint64 { + if !b.hasInnerState() { + return 0 + } + + return b.state.Eth1DepositIndex +} diff --git a/beacon-chain/state/state-native/v3/getters_misc.go b/beacon-chain/state/state-native/v3/getters_misc.go new file mode 100644 index 0000000000..e84d682d03 --- /dev/null +++ b/beacon-chain/state/state-native/v3/getters_misc.go @@ -0,0 +1,211 @@ +package v3 + +import ( + "time" + + types "github.com/prysmaticlabs/eth2-types" + "github.com/prysmaticlabs/prysm/config/params" + "github.com/prysmaticlabs/prysm/encoding/bytesutil" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/runtime/version" +) + +// GenesisTime of the beacon state as a uint64. +func (b *BeaconState) GenesisTime() uint64 { + if !b.hasInnerState() { + return 0 + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.genesisTime() +} + +// genesisTime of the beacon state as a uint64. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) genesisTime() uint64 { + if !b.hasInnerState() { + return 0 + } + + return b.state.GenesisTime +} + +// GenesisValidatorRoot of the beacon state. +func (b *BeaconState) GenesisValidatorRoot() []byte { + if !b.hasInnerState() { + return nil + } + if b.state.GenesisValidatorsRoot == nil { + return params.BeaconConfig().ZeroHash[:] + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.genesisValidatorRoot() +} + +// genesisValidatorRoot of the beacon state. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) genesisValidatorRoot() []byte { + if !b.hasInnerState() { + return nil + } + if b.state.GenesisValidatorsRoot == nil { + return params.BeaconConfig().ZeroHash[:] + } + + root := make([]byte, 32) + copy(root, b.state.GenesisValidatorsRoot) + return root +} + +// GenesisUnixTime returns the genesis time as time.Time. +func (b *BeaconState) GenesisUnixTime() time.Time { + if !b.hasInnerState() { + return time.Unix(0, 0) + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.genesisUnixTime() +} + +// genesisUnixTime returns the genesis time as time.Time. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) genesisUnixTime() time.Time { + if !b.hasInnerState() { + return time.Unix(0, 0) + } + + return time.Unix(int64(b.state.GenesisTime), 0) +} + +// ParentRoot is a convenience method to access state.LatestBlockRoot.ParentRoot. +func (b *BeaconState) ParentRoot() [32]byte { + if !b.hasInnerState() { + return [32]byte{} + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.parentRoot() +} + +// parentRoot is a convenience method to access state.LatestBlockRoot.ParentRoot. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) parentRoot() [32]byte { + if !b.hasInnerState() { + return [32]byte{} + } + + parentRoot := [32]byte{} + copy(parentRoot[:], b.state.LatestBlockHeader.ParentRoot) + return parentRoot +} + +// Version of the beacon state. This method +// is strictly meant to be used without a lock +// internally. +func (_ *BeaconState) Version() int { + return version.Bellatrix +} + +// Slot of the current beacon chain state. +func (b *BeaconState) Slot() types.Slot { + if !b.hasInnerState() { + return 0 + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.slot() +} + +// slot of the current beacon chain state. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) slot() types.Slot { + if !b.hasInnerState() { + return 0 + } + + return b.state.Slot +} + +// Fork version of the beacon chain. +func (b *BeaconState) Fork() *ethpb.Fork { + if !b.hasInnerState() { + return nil + } + if b.state.Fork == nil { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.fork() +} + +// fork version of the beacon chain. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) fork() *ethpb.Fork { + if !b.hasInnerState() { + return nil + } + if b.state.Fork == nil { + return nil + } + + prevVersion := make([]byte, len(b.state.Fork.PreviousVersion)) + copy(prevVersion, b.state.Fork.PreviousVersion) + currVersion := make([]byte, len(b.state.Fork.CurrentVersion)) + copy(currVersion, b.state.Fork.CurrentVersion) + return ðpb.Fork{ + PreviousVersion: prevVersion, + CurrentVersion: currVersion, + Epoch: b.state.Fork.Epoch, + } +} + +// HistoricalRoots based on epochs stored in the beacon state. +func (b *BeaconState) HistoricalRoots() [][]byte { + if !b.hasInnerState() { + return nil + } + if b.state.HistoricalRoots == nil { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.historicalRoots() +} + +// historicalRoots based on epochs stored in the beacon state. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) historicalRoots() [][]byte { + if !b.hasInnerState() { + return nil + } + return bytesutil.SafeCopy2dBytes(b.state.HistoricalRoots) +} + +// balancesLength returns the length of the balances slice. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) balancesLength() int { + if !b.hasInnerState() { + return 0 + } + if b.state.Balances == nil { + return 0 + } + + return len(b.state.Balances) +} diff --git a/beacon-chain/state/state-native/v3/getters_participation.go b/beacon-chain/state/state-native/v3/getters_participation.go new file mode 100644 index 0000000000..42358b01de --- /dev/null +++ b/beacon-chain/state/state-native/v3/getters_participation.go @@ -0,0 +1,53 @@ +package v3 + +// CurrentEpochParticipation corresponding to participation bits on the beacon chain. +func (b *BeaconState) CurrentEpochParticipation() ([]byte, error) { + if !b.hasInnerState() { + return nil, nil + } + if b.state.CurrentEpochParticipation == nil { + return nil, nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.currentEpochParticipation(), nil +} + +// PreviousEpochParticipation corresponding to participation bits on the beacon chain. +func (b *BeaconState) PreviousEpochParticipation() ([]byte, error) { + if !b.hasInnerState() { + return nil, nil + } + if b.state.PreviousEpochParticipation == nil { + return nil, nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.previousEpochParticipation(), nil +} + +// currentEpochParticipation corresponding to participation bits on the beacon chain. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) currentEpochParticipation() []byte { + if !b.hasInnerState() { + return nil + } + tmp := make([]byte, len(b.state.CurrentEpochParticipation)) + copy(tmp, b.state.CurrentEpochParticipation) + return tmp +} + +// previousEpochParticipation corresponding to participation bits on the beacon chain. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) previousEpochParticipation() []byte { + if !b.hasInnerState() { + return nil + } + tmp := make([]byte, len(b.state.PreviousEpochParticipation)) + copy(tmp, b.state.PreviousEpochParticipation) + return tmp +} diff --git a/beacon-chain/state/state-native/v3/getters_payload_header.go b/beacon-chain/state/state-native/v3/getters_payload_header.go new file mode 100644 index 0000000000..bfbd893f25 --- /dev/null +++ b/beacon-chain/state/state-native/v3/getters_payload_header.go @@ -0,0 +1,30 @@ +package v3 + +import ( + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" +) + +// LatestExecutionPayloadHeader of the beacon state. +func (b *BeaconState) LatestExecutionPayloadHeader() (*ethpb.ExecutionPayloadHeader, error) { + if !b.hasInnerState() { + return nil, nil + } + if b.state.LatestExecutionPayloadHeader == nil { + return nil, nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.latestExecutionPayloadHeader(), nil +} + +// latestExecutionPayloadHeader of the beacon state. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) latestExecutionPayloadHeader() *ethpb.ExecutionPayloadHeader { + if !b.hasInnerState() { + return nil + } + + return ethpb.CopyExecutionPayloadHeader(b.state.LatestExecutionPayloadHeader) +} diff --git a/beacon-chain/state/state-native/v3/getters_randao.go b/beacon-chain/state/state-native/v3/getters_randao.go new file mode 100644 index 0000000000..660e330ab3 --- /dev/null +++ b/beacon-chain/state/state-native/v3/getters_randao.go @@ -0,0 +1,85 @@ +package v3 + +import ( + "github.com/prysmaticlabs/prysm/encoding/bytesutil" +) + +// RandaoMixes of block proposers on the beacon chain. +func (b *BeaconState) RandaoMixes() [][]byte { + if !b.hasInnerState() { + return nil + } + if b.state.RandaoMixes == nil { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.randaoMixes() +} + +// randaoMixes of block proposers on the beacon chain. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) randaoMixes() [][]byte { + if !b.hasInnerState() { + return nil + } + + return bytesutil.SafeCopy2dBytes(b.state.RandaoMixes) +} + +// RandaoMixAtIndex retrieves a specific block root based on an +// input index value. +func (b *BeaconState) RandaoMixAtIndex(idx uint64) ([]byte, error) { + if !b.hasInnerState() { + return nil, ErrNilInnerState + } + if b.state.RandaoMixes == nil { + return nil, nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.randaoMixAtIndex(idx) +} + +// randaoMixAtIndex retrieves a specific block root based on an +// input index value. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) randaoMixAtIndex(idx uint64) ([]byte, error) { + if !b.hasInnerState() { + return nil, ErrNilInnerState + } + + return bytesutil.SafeCopyRootAtIndex(b.state.RandaoMixes, idx) +} + +// RandaoMixesLength returns the length of the randao mixes slice. +func (b *BeaconState) RandaoMixesLength() int { + if !b.hasInnerState() { + return 0 + } + if b.state.RandaoMixes == nil { + return 0 + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.randaoMixesLength() +} + +// randaoMixesLength returns the length of the randao mixes slice. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) randaoMixesLength() int { + if !b.hasInnerState() { + return 0 + } + if b.state.RandaoMixes == nil { + return 0 + } + + return len(b.state.RandaoMixes) +} diff --git a/beacon-chain/state/state-native/v3/getters_state.go b/beacon-chain/state/state-native/v3/getters_state.go new file mode 100644 index 0000000000..8ebd8642e1 --- /dev/null +++ b/beacon-chain/state/state-native/v3/getters_state.go @@ -0,0 +1,127 @@ +package v3 + +import ( + "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/encoding/bytesutil" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" +) + +// InnerStateUnsafe returns the pointer value of the underlying +// beacon state proto object, bypassing immutability. Use with care. +func (b *BeaconState) InnerStateUnsafe() interface{} { + if b == nil { + return nil + } + return b.state +} + +// CloneInnerState the beacon state into a protobuf for usage. +func (b *BeaconState) CloneInnerState() interface{} { + if b == nil || b.state == nil { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + return ðpb.BeaconStateBellatrix{ + GenesisTime: b.genesisTime(), + GenesisValidatorsRoot: b.genesisValidatorRoot(), + Slot: b.slot(), + Fork: b.fork(), + LatestBlockHeader: b.latestBlockHeader(), + BlockRoots: b.blockRoots(), + StateRoots: b.stateRoots(), + HistoricalRoots: b.historicalRoots(), + Eth1Data: b.eth1Data(), + Eth1DataVotes: b.eth1DataVotes(), + Eth1DepositIndex: b.eth1DepositIndex(), + Validators: b.validators(), + Balances: b.balances(), + RandaoMixes: b.randaoMixes(), + Slashings: b.slashings(), + CurrentEpochParticipation: b.currentEpochParticipation(), + PreviousEpochParticipation: b.previousEpochParticipation(), + JustificationBits: b.justificationBits(), + PreviousJustifiedCheckpoint: b.previousJustifiedCheckpoint(), + CurrentJustifiedCheckpoint: b.currentJustifiedCheckpoint(), + FinalizedCheckpoint: b.finalizedCheckpoint(), + InactivityScores: b.inactivityScores(), + CurrentSyncCommittee: b.currentSyncCommittee(), + NextSyncCommittee: b.nextSyncCommittee(), + LatestExecutionPayloadHeader: b.latestExecutionPayloadHeader(), + } +} + +// hasInnerState detects if the internal reference to the state data structure +// is populated correctly. Returns false if nil. +func (b *BeaconState) hasInnerState() bool { + return b != nil && b.state != nil +} + +// StateRoots kept track of in the beacon state. +func (b *BeaconState) StateRoots() [][]byte { + if !b.hasInnerState() { + return nil + } + if b.state.StateRoots == nil { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.stateRoots() +} + +// StateRoots kept track of in the beacon state. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) stateRoots() [][]byte { + if !b.hasInnerState() { + return nil + } + return bytesutil.SafeCopy2dBytes(b.state.StateRoots) +} + +// StateRootAtIndex retrieves a specific state root based on an +// input index value. +func (b *BeaconState) StateRootAtIndex(idx uint64) ([]byte, error) { + if !b.hasInnerState() { + return nil, ErrNilInnerState + } + if b.state.StateRoots == nil { + return nil, nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.stateRootAtIndex(idx) +} + +// stateRootAtIndex retrieves a specific state root based on an +// input index value. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) stateRootAtIndex(idx uint64) ([]byte, error) { + if !b.hasInnerState() { + return nil, ErrNilInnerState + } + return bytesutil.SafeCopyRootAtIndex(b.state.StateRoots, idx) +} + +// MarshalSSZ marshals the underlying beacon state to bytes. +func (b *BeaconState) MarshalSSZ() ([]byte, error) { + if !b.hasInnerState() { + return nil, errors.New("nil beacon state") + } + return b.state.MarshalSSZ() +} + +// ProtobufBeaconState transforms an input into beacon state Merge in the form of protobuf. +// Error is returned if the input is not type protobuf beacon state. +func ProtobufBeaconState(s interface{}) (*ethpb.BeaconStateBellatrix, error) { + pbState, ok := s.(*ethpb.BeaconStateBellatrix) + if !ok { + return nil, errors.New("input is not type pb.BeaconStateBellatrix") + } + return pbState, nil +} diff --git a/beacon-chain/state/state-native/v3/getters_sync_committee.go b/beacon-chain/state/state-native/v3/getters_sync_committee.go new file mode 100644 index 0000000000..38faf4ac19 --- /dev/null +++ b/beacon-chain/state/state-native/v3/getters_sync_committee.go @@ -0,0 +1,69 @@ +package v3 + +import ( + "github.com/prysmaticlabs/prysm/encoding/bytesutil" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" +) + +// currentSyncCommittee of the current sync committee in beacon chain state. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) currentSyncCommittee() *ethpb.SyncCommittee { + if !b.hasInnerState() { + return nil + } + + return CopySyncCommittee(b.state.CurrentSyncCommittee) +} + +// nextSyncCommittee of the next sync committee in beacon chain state. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) nextSyncCommittee() *ethpb.SyncCommittee { + if !b.hasInnerState() { + return nil + } + + return CopySyncCommittee(b.state.NextSyncCommittee) +} + +// CurrentSyncCommittee of the current sync committee in beacon chain state. +func (b *BeaconState) CurrentSyncCommittee() (*ethpb.SyncCommittee, error) { + if !b.hasInnerState() { + return nil, nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + if b.state.CurrentSyncCommittee == nil { + return nil, nil + } + + return b.currentSyncCommittee(), nil +} + +// NextSyncCommittee of the next sync committee in beacon chain state. +func (b *BeaconState) NextSyncCommittee() (*ethpb.SyncCommittee, error) { + if !b.hasInnerState() { + return nil, nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + if b.state.NextSyncCommittee == nil { + return nil, nil + } + + return b.nextSyncCommittee(), nil +} + +// CopySyncCommittee copies the provided sync committee object. +func CopySyncCommittee(data *ethpb.SyncCommittee) *ethpb.SyncCommittee { + if data == nil { + return nil + } + return ðpb.SyncCommittee{ + Pubkeys: bytesutil.SafeCopy2dBytes(data.Pubkeys), + AggregatePubkey: bytesutil.SafeCopyBytes(data.AggregatePubkey), + } +} diff --git a/beacon-chain/state/state-native/v3/getters_test.go b/beacon-chain/state/state-native/v3/getters_test.go new file mode 100644 index 0000000000..0357ef13d0 --- /dev/null +++ b/beacon-chain/state/state-native/v3/getters_test.go @@ -0,0 +1,193 @@ +package v3 + +import ( + "runtime/debug" + "sync" + "testing" + + types "github.com/prysmaticlabs/eth2-types" + fieldparams "github.com/prysmaticlabs/prysm/config/fieldparams" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/testing/assert" + "github.com/prysmaticlabs/prysm/testing/require" +) + +func TestBeaconState_SlotDataRace(t *testing.T) { + headState, err := InitializeFromProto(ðpb.BeaconStateBellatrix{Slot: 1}) + require.NoError(t, err) + + wg := sync.WaitGroup{} + wg.Add(2) + go func() { + require.NoError(t, headState.SetSlot(0)) + wg.Done() + }() + go func() { + headState.Slot() + wg.Done() + }() + + wg.Wait() +} + +func TestNilState_NoPanic(t *testing.T) { + var st *BeaconState + defer func() { + if r := recover(); r != nil { + t.Errorf("Method panicked when it was not supposed to: %v\n%v\n", r, string(debug.Stack())) + } + }() + // retrieve elements from nil state + _ = st.GenesisTime() + _ = st.GenesisValidatorRoot() + _ = st.GenesisUnixTime() + _ = st.GenesisValidatorRoot() + _ = st.Slot() + _ = st.Fork() + _ = st.LatestBlockHeader() + _ = st.ParentRoot() + _ = st.BlockRoots() + _, err := st.BlockRootAtIndex(0) + _ = err + _ = st.StateRoots() + _ = st.HistoricalRoots() + _ = st.Eth1Data() + _ = st.Eth1DataVotes() + _ = st.Eth1DepositIndex() + _, err = st.ValidatorAtIndex(0) + _ = err + _, err = st.ValidatorAtIndexReadOnly(0) + _ = err + _, _ = st.ValidatorIndexByPubkey([fieldparams.BLSPubkeyLength]byte{}) + _ = st.PubkeyAtIndex(0) + _ = st.NumValidators() + _ = st.Balances() + _, err = st.BalanceAtIndex(0) + _ = err + _ = st.BalancesLength() + _ = st.RandaoMixes() + _, err = st.RandaoMixAtIndex(0) + _ = err + _ = st.RandaoMixesLength() + _ = st.Slashings() + _, err = st.CurrentEpochParticipation() + _ = err + _, err = st.PreviousEpochParticipation() + _ = err + _ = st.JustificationBits() + _ = st.PreviousJustifiedCheckpoint() + _ = st.CurrentJustifiedCheckpoint() + _ = st.FinalizedCheckpoint() + _, err = st.CurrentEpochParticipation() + _ = err + _, err = st.PreviousEpochParticipation() + _ = err + _, err = st.InactivityScores() + _ = err + _, err = st.CurrentSyncCommittee() + _ = err + _, err = st.NextSyncCommittee() + _ = err +} + +func TestBeaconState_ValidatorByPubkey(t *testing.T) { + keyCreator := func(input []byte) [fieldparams.BLSPubkeyLength]byte { + nKey := [fieldparams.BLSPubkeyLength]byte{} + copy(nKey[:1], input) + return nKey + } + + tests := []struct { + name string + modifyFunc func(b *BeaconState, k [fieldparams.BLSPubkeyLength]byte) + exists bool + expectedIdx types.ValidatorIndex + largestIdxInSet types.ValidatorIndex + }{ + { + name: "retrieve validator", + modifyFunc: func(b *BeaconState, key [fieldparams.BLSPubkeyLength]byte) { + assert.NoError(t, b.AppendValidator(ðpb.Validator{PublicKey: key[:]})) + }, + exists: true, + expectedIdx: 0, + }, + { + name: "retrieve validator with multiple validators from the start", + modifyFunc: func(b *BeaconState, key [fieldparams.BLSPubkeyLength]byte) { + key1 := keyCreator([]byte{'C'}) + key2 := keyCreator([]byte{'D'}) + assert.NoError(t, b.AppendValidator(ðpb.Validator{PublicKey: key[:]})) + assert.NoError(t, b.AppendValidator(ðpb.Validator{PublicKey: key1[:]})) + assert.NoError(t, b.AppendValidator(ðpb.Validator{PublicKey: key2[:]})) + }, + exists: true, + expectedIdx: 0, + }, + { + name: "retrieve validator with multiple validators", + modifyFunc: func(b *BeaconState, key [fieldparams.BLSPubkeyLength]byte) { + key1 := keyCreator([]byte{'C'}) + key2 := keyCreator([]byte{'D'}) + assert.NoError(t, b.AppendValidator(ðpb.Validator{PublicKey: key1[:]})) + assert.NoError(t, b.AppendValidator(ðpb.Validator{PublicKey: key2[:]})) + assert.NoError(t, b.AppendValidator(ðpb.Validator{PublicKey: key[:]})) + }, + exists: true, + expectedIdx: 2, + }, + { + name: "retrieve validator with multiple validators from the start with shared state", + modifyFunc: func(b *BeaconState, key [fieldparams.BLSPubkeyLength]byte) { + key1 := keyCreator([]byte{'C'}) + key2 := keyCreator([]byte{'D'}) + assert.NoError(t, b.AppendValidator(ðpb.Validator{PublicKey: key[:]})) + _ = b.Copy() + assert.NoError(t, b.AppendValidator(ðpb.Validator{PublicKey: key1[:]})) + assert.NoError(t, b.AppendValidator(ðpb.Validator{PublicKey: key2[:]})) + }, + exists: true, + expectedIdx: 0, + }, + { + name: "retrieve validator with multiple validators with shared state", + modifyFunc: func(b *BeaconState, key [fieldparams.BLSPubkeyLength]byte) { + key1 := keyCreator([]byte{'C'}) + key2 := keyCreator([]byte{'D'}) + assert.NoError(t, b.AppendValidator(ðpb.Validator{PublicKey: key1[:]})) + assert.NoError(t, b.AppendValidator(ðpb.Validator{PublicKey: key2[:]})) + n := b.Copy() + // Append to another state + assert.NoError(t, n.AppendValidator(ðpb.Validator{PublicKey: key[:]})) + + }, + exists: false, + expectedIdx: 0, + }, + { + name: "retrieve validator with multiple validators with shared state at boundary", + modifyFunc: func(b *BeaconState, key [fieldparams.BLSPubkeyLength]byte) { + key1 := keyCreator([]byte{'C'}) + assert.NoError(t, b.AppendValidator(ðpb.Validator{PublicKey: key1[:]})) + n := b.Copy() + // Append to another state + assert.NoError(t, n.AppendValidator(ðpb.Validator{PublicKey: key[:]})) + + }, + exists: false, + expectedIdx: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s, err := InitializeFromProto(ðpb.BeaconStateBellatrix{}) + require.NoError(t, err) + nKey := keyCreator([]byte{'A'}) + tt.modifyFunc(s, nKey) + idx, ok := s.ValidatorIndexByPubkey(nKey) + assert.Equal(t, tt.exists, ok) + assert.Equal(t, tt.expectedIdx, idx) + }) + } +} diff --git a/beacon-chain/state/state-native/v3/getters_validator.go b/beacon-chain/state/state-native/v3/getters_validator.go new file mode 100644 index 0000000000..9a5c4d5fee --- /dev/null +++ b/beacon-chain/state/state-native/v3/getters_validator.go @@ -0,0 +1,329 @@ +package v3 + +import ( + "fmt" + + "github.com/pkg/errors" + types "github.com/prysmaticlabs/eth2-types" + "github.com/prysmaticlabs/prysm/beacon-chain/state" + v1 "github.com/prysmaticlabs/prysm/beacon-chain/state/state-native/v1" + fieldparams "github.com/prysmaticlabs/prysm/config/fieldparams" + "github.com/prysmaticlabs/prysm/encoding/bytesutil" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" +) + +// ValidatorIndexOutOfRangeError represents an error scenario where a validator does not exist +// at a given index in the validator's array. +type ValidatorIndexOutOfRangeError struct { + message string +} + +var ( + // ErrNilValidatorsInState returns when accessing validators in the state while the state has a + // nil slice for the validators field. + ErrNilValidatorsInState = errors.New("state has nil validator slice") +) + +// NewValidatorIndexOutOfRangeError creates a new error instance. +func NewValidatorIndexOutOfRangeError(index types.ValidatorIndex) ValidatorIndexOutOfRangeError { + return ValidatorIndexOutOfRangeError{ + message: fmt.Sprintf("index %d out of range", index), + } +} + +// Error returns the underlying error message. +func (e *ValidatorIndexOutOfRangeError) Error() string { + return e.message +} + +// Validators participating in consensus on the beacon chain. +func (b *BeaconState) Validators() []*ethpb.Validator { + if !b.hasInnerState() { + return nil + } + if b.state.Validators == nil { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.validators() +} + +// validators participating in consensus on the beacon chain. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) validators() []*ethpb.Validator { + if !b.hasInnerState() { + return nil + } + if b.state.Validators == nil { + return nil + } + + res := make([]*ethpb.Validator, len(b.state.Validators)) + for i := 0; i < len(res); i++ { + val := b.state.Validators[i] + if val == nil { + continue + } + res[i] = ethpb.CopyValidator(val) + } + return res +} + +// references of validators participating in consensus on the beacon chain. +// This assumes that a lock is already held on BeaconState. This does not +// copy fully and instead just copies the reference. +func (b *BeaconState) validatorsReferences() []*ethpb.Validator { + if !b.hasInnerState() { + return nil + } + if b.state.Validators == nil { + return nil + } + + res := make([]*ethpb.Validator, len(b.state.Validators)) + for i := 0; i < len(res); i++ { + validator := b.state.Validators[i] + if validator == nil { + continue + } + // copy validator reference instead. + res[i] = validator + } + return res +} + +// ValidatorAtIndex is the validator at the provided index. +func (b *BeaconState) ValidatorAtIndex(idx types.ValidatorIndex) (*ethpb.Validator, error) { + if !b.hasInnerState() { + return nil, ErrNilInnerState + } + if b.state.Validators == nil { + return ðpb.Validator{}, nil + } + if uint64(len(b.state.Validators)) <= uint64(idx) { + e := NewValidatorIndexOutOfRangeError(idx) + return nil, &e + } + + b.lock.RLock() + defer b.lock.RUnlock() + + val := b.state.Validators[idx] + return ethpb.CopyValidator(val), nil +} + +// ValidatorAtIndexReadOnly is the validator at the provided index. This method +// doesn't clone the validator. +func (b *BeaconState) ValidatorAtIndexReadOnly(idx types.ValidatorIndex) (state.ReadOnlyValidator, error) { + if !b.hasInnerState() { + return nil, ErrNilInnerState + } + if b.state.Validators == nil { + return nil, ErrNilValidatorsInState + } + if uint64(len(b.state.Validators)) <= uint64(idx) { + e := NewValidatorIndexOutOfRangeError(idx) + return nil, &e + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return v1.NewValidator(b.state.Validators[idx]) +} + +// ValidatorIndexByPubkey returns a given validator by its 48-byte public key. +func (b *BeaconState) ValidatorIndexByPubkey(key [fieldparams.BLSPubkeyLength]byte) (types.ValidatorIndex, bool) { + if b == nil || b.valMapHandler == nil || b.valMapHandler.IsNil() { + return 0, false + } + b.lock.RLock() + defer b.lock.RUnlock() + numOfVals := len(b.state.Validators) + + idx, ok := b.valMapHandler.Get(key) + if ok && numOfVals <= int(idx) { + return types.ValidatorIndex(0), false + } + return idx, ok +} + +// PubkeyAtIndex returns the pubkey at the given +// validator index. +func (b *BeaconState) PubkeyAtIndex(idx types.ValidatorIndex) [fieldparams.BLSPubkeyLength]byte { + if !b.hasInnerState() { + return [fieldparams.BLSPubkeyLength]byte{} + } + if uint64(idx) >= uint64(len(b.state.Validators)) { + return [fieldparams.BLSPubkeyLength]byte{} + } + b.lock.RLock() + defer b.lock.RUnlock() + + if b.state.Validators[idx] == nil { + return [fieldparams.BLSPubkeyLength]byte{} + } + return bytesutil.ToBytes48(b.state.Validators[idx].PublicKey) +} + +// NumValidators returns the size of the validator registry. +func (b *BeaconState) NumValidators() int { + if !b.hasInnerState() { + return 0 + } + b.lock.RLock() + defer b.lock.RUnlock() + + return len(b.state.Validators) +} + +// ReadFromEveryValidator reads values from every validator and applies it to the provided function. +// Warning: This method is potentially unsafe, as it exposes the actual validator registry. +func (b *BeaconState) ReadFromEveryValidator(f func(idx int, val state.ReadOnlyValidator) error) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + if b.state.Validators == nil { + return errors.New("nil validators in state") + } + b.lock.RLock() + validators := b.state.Validators + b.lock.RUnlock() + + for i, v := range validators { + v, err := v1.NewValidator(v) + if err != nil { + return err + } + if err := f(i, v); err != nil { + return err + } + } + return nil +} + +// Balances of validators participating in consensus on the beacon chain. +func (b *BeaconState) Balances() []uint64 { + if !b.hasInnerState() { + return nil + } + if b.state.Balances == nil { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.balances() +} + +// balances of validators participating in consensus on the beacon chain. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) balances() []uint64 { + if !b.hasInnerState() { + return nil + } + if b.state.Balances == nil { + return nil + } + + res := make([]uint64, len(b.state.Balances)) + copy(res, b.state.Balances) + return res +} + +// BalanceAtIndex of validator with the provided index. +func (b *BeaconState) BalanceAtIndex(idx types.ValidatorIndex) (uint64, error) { + if !b.hasInnerState() { + return 0, ErrNilInnerState + } + if b.state.Balances == nil { + return 0, nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + if uint64(len(b.state.Balances)) <= uint64(idx) { + return 0, fmt.Errorf("index of %d does not exist", idx) + } + return b.state.Balances[idx], nil +} + +// BalancesLength returns the length of the balances slice. +func (b *BeaconState) BalancesLength() int { + if !b.hasInnerState() { + return 0 + } + if b.state.Balances == nil { + return 0 + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.balancesLength() +} + +// Slashings of validators on the beacon chain. +func (b *BeaconState) Slashings() []uint64 { + if !b.hasInnerState() { + return nil + } + if b.state.Slashings == nil { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.slashings() +} + +// slashings of validators on the beacon chain. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) slashings() []uint64 { + if !b.hasInnerState() { + return nil + } + if b.state.Slashings == nil { + return nil + } + + res := make([]uint64, len(b.state.Slashings)) + copy(res, b.state.Slashings) + return res +} + +// inactivityScores of validators participating in consensus on the beacon chain. +// This assumes that a lock is already held on BeaconState. +func (b *BeaconState) inactivityScores() []uint64 { + if !b.hasInnerState() { + return nil + } + if b.state.InactivityScores == nil { + return nil + } + + res := make([]uint64, len(b.state.InactivityScores)) + copy(res, b.state.InactivityScores) + return res +} + +// InactivityScores of validators participating in consensus on the beacon chain. +func (b *BeaconState) InactivityScores() ([]uint64, error) { + if !b.hasInnerState() { + return nil, nil + } + if b.state.InactivityScores == nil { + return nil, nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + + return b.inactivityScores(), nil +} diff --git a/beacon-chain/state/state-native/v3/getters_validator_test.go b/beacon-chain/state/state-native/v3/getters_validator_test.go new file mode 100644 index 0000000000..a8851815d2 --- /dev/null +++ b/beacon-chain/state/state-native/v3/getters_validator_test.go @@ -0,0 +1,20 @@ +package v3_test + +import ( + "testing" + + v1 "github.com/prysmaticlabs/prysm/beacon-chain/state/state-native/v1" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/testing/assert" + "github.com/prysmaticlabs/prysm/testing/require" +) + +func TestBeaconState_ValidatorAtIndexReadOnly_HandlesNilSlice(t *testing.T) { + st, err := v1.InitializeFromProtoUnsafe(ðpb.BeaconState{ + Validators: nil, + }) + require.NoError(t, err) + + _, err = st.ValidatorAtIndexReadOnly(0) + assert.Equal(t, v1.ErrNilValidatorsInState, err) +} diff --git a/beacon-chain/state/state-native/v3/proofs.go b/beacon-chain/state/state-native/v3/proofs.go new file mode 100644 index 0000000000..0a3e4eddb8 --- /dev/null +++ b/beacon-chain/state/state-native/v3/proofs.go @@ -0,0 +1,83 @@ +package v3 + +import ( + "context" + "encoding/binary" + + "github.com/prysmaticlabs/prysm/beacon-chain/state/state-native/fieldtrie" + "github.com/prysmaticlabs/prysm/encoding/bytesutil" +) + +const ( + finalizedRootIndex = uint64(105) // Precomputed value. +) + +// FinalizedRootGeneralizedIndex for the beacon state. +func FinalizedRootGeneralizedIndex() uint64 { + return finalizedRootIndex +} + +// CurrentSyncCommitteeGeneralizedIndex for the beacon state. +func CurrentSyncCommitteeGeneralizedIndex() uint64 { + return uint64(currentSyncCommittee) +} + +// NextSyncCommitteeGeneralizedIndex for the beacon state. +func NextSyncCommitteeGeneralizedIndex() uint64 { + return uint64(nextSyncCommittee) +} + +// CurrentSyncCommitteeProof from the state's Merkle trie representation. +func (b *BeaconState) CurrentSyncCommitteeProof(ctx context.Context) ([][]byte, error) { + b.lock.Lock() + defer b.lock.Unlock() + // In case the Merkle layers of the trie are not populated, we need + // to perform some initialization. + if err := b.initializeMerkleLayers(ctx); err != nil { + return nil, err + } + // Our beacon state uses a "dirty" fields pattern which requires us to + // recompute branches of the Merkle layers that are marked as dirty. + if err := b.recomputeDirtyFields(ctx); err != nil { + return nil, err + } + return fieldtrie.ProofFromMerkleLayers(b.merkleLayers, currentSyncCommittee), nil +} + +// NextSyncCommitteeProof from the state's Merkle trie representation. +func (b *BeaconState) NextSyncCommitteeProof(ctx context.Context) ([][]byte, error) { + b.lock.Lock() + defer b.lock.Unlock() + if err := b.initializeMerkleLayers(ctx); err != nil { + return nil, err + } + if err := b.recomputeDirtyFields(ctx); err != nil { + return nil, err + } + return fieldtrie.ProofFromMerkleLayers(b.merkleLayers, nextSyncCommittee), nil +} + +// FinalizedRootProof crafts a Merkle proof for the finalized root +// contained within the finalized checkpoint of a beacon state. +func (b *BeaconState) FinalizedRootProof(ctx context.Context) ([][]byte, error) { + b.lock.Lock() + defer b.lock.Unlock() + if err := b.initializeMerkleLayers(ctx); err != nil { + return nil, err + } + if err := b.recomputeDirtyFields(ctx); err != nil { + return nil, err + } + cpt := b.state.FinalizedCheckpoint + // The epoch field of a finalized checkpoint is the neighbor + // index of the finalized root field in its Merkle tree representation + // of the checkpoint. This neighbor is the first element added to the proof. + epochBuf := make([]byte, 8) + binary.LittleEndian.PutUint64(epochBuf, uint64(cpt.Epoch)) + epochRoot := bytesutil.ToBytes32(epochBuf) + proof := make([][]byte, 0) + proof = append(proof, epochRoot[:]) + branch := fieldtrie.ProofFromMerkleLayers(b.merkleLayers, finalizedCheckpoint) + proof = append(proof, branch...) + return proof, nil +} diff --git a/beacon-chain/state/state-native/v3/proofs_test.go b/beacon-chain/state/state-native/v3/proofs_test.go new file mode 100644 index 0000000000..f7724e6861 --- /dev/null +++ b/beacon-chain/state/state-native/v3/proofs_test.go @@ -0,0 +1,105 @@ +package v3_test + +import ( + "context" + "testing" + + v3 "github.com/prysmaticlabs/prysm/beacon-chain/state/state-native/v3" + "github.com/prysmaticlabs/prysm/container/trie" + "github.com/prysmaticlabs/prysm/crypto/bls" + "github.com/prysmaticlabs/prysm/testing/require" + "github.com/prysmaticlabs/prysm/testing/util" +) + +func TestBeaconStateMerkleProofs(t *testing.T) { + ctx := context.Background() + st, _ := util.DeterministicGenesisStateAltair(t, 256) + htr, err := st.HashTreeRoot(ctx) + require.NoError(t, err) + t.Run("current sync committee", func(t *testing.T) { + sc, err := st.CurrentSyncCommittee() + require.NoError(t, err) + + // Verify the Merkle proof. + scRoot, err := sc.HashTreeRoot() + require.NoError(t, err) + proof, err := st.CurrentSyncCommitteeProof(ctx) + require.NoError(t, err) + valid := trie.VerifyMerkleProof(htr[:], scRoot[:], v3.CurrentSyncCommitteeGeneralizedIndex(), proof) + require.Equal(t, true, valid) + }) + t.Run("next sync committee", func(t *testing.T) { + nextSC, err := st.NextSyncCommittee() + require.NoError(t, err) + proof, err := st.NextSyncCommitteeProof(ctx) + require.NoError(t, err) + + // Verify the Merkle proof. + nextSCRoot, err := nextSC.HashTreeRoot() + require.NoError(t, err) + valid := trie.VerifyMerkleProof(htr[:], nextSCRoot[:], v3.NextSyncCommitteeGeneralizedIndex(), proof) + require.Equal(t, true, valid) + + // Edit the sync committee. + privKey, err := bls.RandKey() + require.NoError(t, err) + nextSC.AggregatePubkey = privKey.PublicKey().Marshal() + require.NoError(t, st.SetNextSyncCommittee(nextSC)) + + // Verifying the old Merkle proof for the new value should fail. + nextSCRoot, err = nextSC.HashTreeRoot() + require.NoError(t, err) + valid = trie.VerifyMerkleProof(htr[:], nextSCRoot[:], v3.NextSyncCommitteeGeneralizedIndex(), proof) + require.Equal(t, false, valid) + + // Generating a new, valid proof should pass. + proof, err = st.NextSyncCommitteeProof(ctx) + require.NoError(t, err) + htr, err = st.HashTreeRoot(ctx) + require.NoError(t, err) + valid = trie.VerifyMerkleProof(htr[:], nextSCRoot[:], v3.NextSyncCommitteeGeneralizedIndex(), proof) + require.Equal(t, true, valid) + }) + t.Run("finalized root", func(t *testing.T) { + finalizedRoot := st.FinalizedCheckpoint().Root + + // Verify the Merkle proof. + htr, err = st.HashTreeRoot(ctx) + require.NoError(t, err) + proof, err := st.FinalizedRootProof(ctx) + require.NoError(t, err) + gIndex := v3.FinalizedRootGeneralizedIndex() + valid := trie.VerifyMerkleProof(htr[:], finalizedRoot, gIndex, proof) + require.Equal(t, true, valid) + }) + t.Run("recomputes root on dirty fields", func(t *testing.T) { + currentRoot, err := st.HashTreeRoot(ctx) + require.NoError(t, err) + cpt := st.FinalizedCheckpoint() + require.NoError(t, err) + + // Edit the checkpoint. + cpt.Epoch = 100 + require.NoError(t, st.SetFinalizedCheckpoint(cpt)) + + // Produce a proof for the finalized root. + proof, err := st.FinalizedRootProof(ctx) + require.NoError(t, err) + + // We expect the previous step to have triggered + // a recomputation of dirty fields in the beacon state, resulting + // in a new hash tree root as the finalized checkpoint had previously + // changed and should have been marked as a dirty state field. + // The proof validity should be false for the old root, but true for the new. + finalizedRoot := st.FinalizedCheckpoint().Root + gIndex := v3.FinalizedRootGeneralizedIndex() + valid := trie.VerifyMerkleProof(currentRoot[:], finalizedRoot, gIndex, proof) + require.Equal(t, false, valid) + + newRoot, err := st.HashTreeRoot(ctx) + require.NoError(t, err) + + valid = trie.VerifyMerkleProof(newRoot[:], finalizedRoot, gIndex, proof) + require.Equal(t, true, valid) + }) +} diff --git a/beacon-chain/state/state-native/v3/setters_block.go b/beacon-chain/state/state-native/v3/setters_block.go new file mode 100644 index 0000000000..81fa29f328 --- /dev/null +++ b/beacon-chain/state/state-native/v3/setters_block.go @@ -0,0 +1,68 @@ +package v3 + +import ( + "fmt" + + "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" +) + +// SetLatestBlockHeader in the beacon state. +func (b *BeaconState) SetLatestBlockHeader(val *ethpb.BeaconBlockHeader) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.state.LatestBlockHeader = ethpb.CopyBeaconBlockHeader(val) + b.markFieldAsDirty(latestBlockHeader) + return nil +} + +// SetBlockRoots for the beacon state. Updates the entire +// list to a new value by overwriting the previous one. +func (b *BeaconState) SetBlockRoots(val [][]byte) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.sharedFieldReferences[blockRoots].MinusRef() + b.sharedFieldReferences[blockRoots] = stateutil.NewRef(1) + + b.state.BlockRoots = val + b.markFieldAsDirty(blockRoots) + b.rebuildTrie[blockRoots] = true + return nil +} + +// UpdateBlockRootAtIndex for the beacon state. Updates the block root +// at a specific index to a new value. +func (b *BeaconState) UpdateBlockRootAtIndex(idx uint64, blockRoot [32]byte) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + if uint64(len(b.state.BlockRoots)) <= idx { + return fmt.Errorf("invalid index provided %d", idx) + } + b.lock.Lock() + defer b.lock.Unlock() + + r := b.state.BlockRoots + if ref := b.sharedFieldReferences[blockRoots]; ref.Refs() > 1 { + // Copy elements in underlying array by reference. + r = make([][]byte, len(b.state.BlockRoots)) + copy(r, b.state.BlockRoots) + ref.MinusRef() + b.sharedFieldReferences[blockRoots] = stateutil.NewRef(1) + } + + r[idx] = blockRoot[:] + b.state.BlockRoots = r + + b.markFieldAsDirty(blockRoots) + b.addDirtyIndices(blockRoots, []uint64{idx}) + return nil +} diff --git a/beacon-chain/state/state-native/v3/setters_checkpoint.go b/beacon-chain/state/state-native/v3/setters_checkpoint.go new file mode 100644 index 0000000000..601f89dab6 --- /dev/null +++ b/beacon-chain/state/state-native/v3/setters_checkpoint.go @@ -0,0 +1,58 @@ +package v3 + +import ( + "github.com/prysmaticlabs/go-bitfield" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" +) + +// SetJustificationBits for the beacon state. +func (b *BeaconState) SetJustificationBits(val bitfield.Bitvector4) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.state.JustificationBits = val + b.markFieldAsDirty(justificationBits) + return nil +} + +// SetPreviousJustifiedCheckpoint for the beacon state. +func (b *BeaconState) SetPreviousJustifiedCheckpoint(val *ethpb.Checkpoint) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.state.PreviousJustifiedCheckpoint = val + b.markFieldAsDirty(previousJustifiedCheckpoint) + return nil +} + +// SetCurrentJustifiedCheckpoint for the beacon state. +func (b *BeaconState) SetCurrentJustifiedCheckpoint(val *ethpb.Checkpoint) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.state.CurrentJustifiedCheckpoint = val + b.markFieldAsDirty(currentJustifiedCheckpoint) + return nil +} + +// SetFinalizedCheckpoint for the beacon state. +func (b *BeaconState) SetFinalizedCheckpoint(val *ethpb.Checkpoint) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.state.FinalizedCheckpoint = val + b.markFieldAsDirty(finalizedCheckpoint) + return nil +} diff --git a/beacon-chain/state/state-native/v3/setters_eth1.go b/beacon-chain/state/state-native/v3/setters_eth1.go new file mode 100644 index 0000000000..315bafe4b3 --- /dev/null +++ b/beacon-chain/state/state-native/v3/setters_eth1.go @@ -0,0 +1,74 @@ +package v3 + +import ( + "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" +) + +// SetEth1Data for the beacon state. +func (b *BeaconState) SetEth1Data(val *ethpb.Eth1Data) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.state.Eth1Data = val + b.markFieldAsDirty(eth1Data) + return nil +} + +// SetEth1DataVotes for the beacon state. Updates the entire +// list to a new value by overwriting the previous one. +func (b *BeaconState) SetEth1DataVotes(val []*ethpb.Eth1Data) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.sharedFieldReferences[eth1DataVotes].MinusRef() + b.sharedFieldReferences[eth1DataVotes] = stateutil.NewRef(1) + + b.state.Eth1DataVotes = val + b.markFieldAsDirty(eth1DataVotes) + b.rebuildTrie[eth1DataVotes] = true + return nil +} + +// SetEth1DepositIndex for the beacon state. +func (b *BeaconState) SetEth1DepositIndex(val uint64) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.state.Eth1DepositIndex = val + b.markFieldAsDirty(eth1DepositIndex) + return nil +} + +// AppendEth1DataVotes for the beacon state. Appends the new value +// to the the end of list. +func (b *BeaconState) AppendEth1DataVotes(val *ethpb.Eth1Data) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + votes := b.state.Eth1DataVotes + if b.sharedFieldReferences[eth1DataVotes].Refs() > 1 { + // Copy elements in underlying array by reference. + votes = make([]*ethpb.Eth1Data, len(b.state.Eth1DataVotes)) + copy(votes, b.state.Eth1DataVotes) + b.sharedFieldReferences[eth1DataVotes].MinusRef() + b.sharedFieldReferences[eth1DataVotes] = stateutil.NewRef(1) + } + + b.state.Eth1DataVotes = append(votes, val) + b.markFieldAsDirty(eth1DataVotes) + b.addDirtyIndices(eth1DataVotes, []uint64{uint64(len(b.state.Eth1DataVotes) - 1)}) + return nil +} diff --git a/beacon-chain/state/state-native/v3/setters_misc.go b/beacon-chain/state/state-native/v3/setters_misc.go new file mode 100644 index 0000000000..27839ff993 --- /dev/null +++ b/beacon-chain/state/state-native/v3/setters_misc.go @@ -0,0 +1,186 @@ +package v3 + +import ( + "github.com/pkg/errors" + types "github.com/prysmaticlabs/eth2-types" + "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" + stateTypes "github.com/prysmaticlabs/prysm/beacon-chain/state/types" + "github.com/prysmaticlabs/prysm/config/features" + "github.com/prysmaticlabs/prysm/crypto/hash" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" + "google.golang.org/protobuf/proto" +) + +// For our setters, we have a field reference counter through +// which we can track shared field references. This helps when +// performing state copies, as we simply copy the reference to the +// field. When we do need to do need to modify these fields, we +// perform a full copy of the field. This is true of most of our +// fields except for the following below. +// 1) BlockRoots +// 2) StateRoots +// 3) Eth1DataVotes +// 4) RandaoMixes +// 5) HistoricalRoots +// 6) CurrentParticipationBits +// 7) PreviousParticipationBits +// +// The fields referred to above are instead copied by reference, where +// we simply copy the reference to the underlying object instead of the +// whole object. This is possible due to how we have structured our state +// as we copy the value on read, so as to ensure the underlying object is +// not mutated while it is being accessed during a state read. + +const ( + // This specifies the limit till which we process all dirty indices for a certain field. + // If we have more dirty indices than the threshold, then we rebuild the whole trie. This + // comes due to the fact that O(alogn) > O(n) beyond a certain value of a. + indicesLimit = 8000 +) + +// SetGenesisTime for the beacon state. +func (b *BeaconState) SetGenesisTime(val uint64) error { + b.lock.Lock() + defer b.lock.Unlock() + + b.state.GenesisTime = val + b.markFieldAsDirty(genesisTime) + return nil +} + +// SetGenesisValidatorRoot for the beacon state. +func (b *BeaconState) SetGenesisValidatorRoot(val []byte) error { + b.lock.Lock() + defer b.lock.Unlock() + + b.state.GenesisValidatorsRoot = val + b.markFieldAsDirty(genesisValidatorRoot) + return nil +} + +// SetSlot for the beacon state. +func (b *BeaconState) SetSlot(val types.Slot) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.state.Slot = val + b.markFieldAsDirty(slot) + return nil +} + +// SetFork version for the beacon chain. +func (b *BeaconState) SetFork(val *ethpb.Fork) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + fk, ok := proto.Clone(val).(*ethpb.Fork) + if !ok { + return errors.New("proto.Clone did not return a fork proto") + } + b.state.Fork = fk + b.markFieldAsDirty(fork) + return nil +} + +// SetHistoricalRoots for the beacon state. Updates the entire +// list to a new value by overwriting the previous one. +func (b *BeaconState) SetHistoricalRoots(val [][]byte) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.sharedFieldReferences[historicalRoots].MinusRef() + b.sharedFieldReferences[historicalRoots] = stateutil.NewRef(1) + + b.state.HistoricalRoots = val + b.markFieldAsDirty(historicalRoots) + return nil +} + +// AppendHistoricalRoots for the beacon state. Appends the new value +// to the the end of list. +func (b *BeaconState) AppendHistoricalRoots(root [32]byte) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + roots := b.state.HistoricalRoots + if b.sharedFieldReferences[historicalRoots].Refs() > 1 { + roots = make([][]byte, len(b.state.HistoricalRoots)) + copy(roots, b.state.HistoricalRoots) + b.sharedFieldReferences[historicalRoots].MinusRef() + b.sharedFieldReferences[historicalRoots] = stateutil.NewRef(1) + } + + b.state.HistoricalRoots = append(roots, root[:]) + b.markFieldAsDirty(historicalRoots) + return nil +} + +// Recomputes the branch up the index in the Merkle trie representation +// of the beacon state. This method performs slice reads and the caller MUST +// hold the lock before calling this method. +func (b *BeaconState) recomputeRoot(idx int) { + hashFunc := hash.CustomSHA256Hasher() + layers := b.merkleLayers + // The merkle tree structure looks as follows: + // [[r1, r2, r3, r4], [parent1, parent2], [root]] + // Using information about the index which changed, idx, we recompute + // only its branch up the tree. + currentIndex := idx + root := b.merkleLayers[0][idx] + for i := 0; i < len(layers)-1; i++ { + isLeft := currentIndex%2 == 0 + neighborIdx := currentIndex ^ 1 + + neighbor := make([]byte, 32) + if layers[i] != nil && len(layers[i]) != 0 && neighborIdx < len(layers[i]) { + neighbor = layers[i][neighborIdx] + } + if isLeft { + parentHash := hashFunc(append(root, neighbor...)) + root = parentHash[:] + } else { + parentHash := hashFunc(append(neighbor, root...)) + root = parentHash[:] + } + parentIdx := currentIndex / 2 + // Update the cached layers at the parent index. + layers[i+1][parentIdx] = root + currentIndex = parentIdx + } + b.merkleLayers = layers +} + +func (b *BeaconState) markFieldAsDirty(field stateTypes.FieldIndex) { + b.dirtyFields[field] = true +} + +// addDirtyIndices adds the relevant dirty field indices, so that they +// can be recomputed. +func (b *BeaconState) addDirtyIndices(index stateTypes.FieldIndex, indices []uint64) { + if b.rebuildTrie[index] { + return + } + // Exit early if balance trie computation isn't enabled. + if !features.Get().EnableBalanceTrieComputation && index == balances { + return + } + totalIndicesLen := len(b.dirtyIndices[index]) + len(indices) + if totalIndicesLen > indicesLimit { + b.rebuildTrie[index] = true + b.dirtyIndices[index] = []uint64{} + } else { + b.dirtyIndices[index] = append(b.dirtyIndices[index], indices...) + } +} diff --git a/beacon-chain/state/state-native/v3/setters_participation.go b/beacon-chain/state/state-native/v3/setters_participation.go new file mode 100644 index 0000000000..c5aa72c160 --- /dev/null +++ b/beacon-chain/state/state-native/v3/setters_participation.go @@ -0,0 +1,89 @@ +package v3 + +import ( + "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" +) + +// SetPreviousParticipationBits for the beacon state. Updates the entire +// list to a new value by overwriting the previous one. +func (b *BeaconState) SetPreviousParticipationBits(val []byte) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.sharedFieldReferences[previousEpochParticipationBits].MinusRef() + b.sharedFieldReferences[previousEpochParticipationBits] = stateutil.NewRef(1) + + b.state.PreviousEpochParticipation = val + b.markFieldAsDirty(previousEpochParticipationBits) + b.rebuildTrie[previousEpochParticipationBits] = true + return nil +} + +// SetCurrentParticipationBits for the beacon state. Updates the entire +// list to a new value by overwriting the previous one. +func (b *BeaconState) SetCurrentParticipationBits(val []byte) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.sharedFieldReferences[currentEpochParticipationBits].MinusRef() + b.sharedFieldReferences[currentEpochParticipationBits] = stateutil.NewRef(1) + + b.state.CurrentEpochParticipation = val + b.markFieldAsDirty(currentEpochParticipationBits) + b.rebuildTrie[currentEpochParticipationBits] = true + return nil +} + +// AppendCurrentParticipationBits for the beacon state. Appends the new value +// to the the end of list. +func (b *BeaconState) AppendCurrentParticipationBits(val byte) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + participation := b.state.CurrentEpochParticipation + if b.sharedFieldReferences[currentEpochParticipationBits].Refs() > 1 { + // Copy elements in underlying array by reference. + participation = make([]byte, len(b.state.CurrentEpochParticipation)) + copy(participation, b.state.CurrentEpochParticipation) + b.sharedFieldReferences[currentEpochParticipationBits].MinusRef() + b.sharedFieldReferences[currentEpochParticipationBits] = stateutil.NewRef(1) + } + + b.state.CurrentEpochParticipation = append(participation, val) + b.markFieldAsDirty(currentEpochParticipationBits) + b.addDirtyIndices(currentEpochParticipationBits, []uint64{uint64(len(b.state.CurrentEpochParticipation) - 1)}) + return nil +} + +// AppendPreviousParticipationBits for the beacon state. Appends the new value +// to the the end of list. +func (b *BeaconState) AppendPreviousParticipationBits(val byte) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + bits := b.state.PreviousEpochParticipation + if b.sharedFieldReferences[previousEpochParticipationBits].Refs() > 1 { + bits = make([]byte, len(b.state.PreviousEpochParticipation)) + copy(bits, b.state.PreviousEpochParticipation) + b.sharedFieldReferences[previousEpochParticipationBits].MinusRef() + b.sharedFieldReferences[previousEpochParticipationBits] = stateutil.NewRef(1) + } + + b.state.PreviousEpochParticipation = append(bits, val) + b.markFieldAsDirty(previousEpochParticipationBits) + b.addDirtyIndices(previousEpochParticipationBits, []uint64{uint64(len(b.state.PreviousEpochParticipation) - 1)}) + + return nil +} diff --git a/beacon-chain/state/state-native/v3/setters_payload_header.go b/beacon-chain/state/state-native/v3/setters_payload_header.go new file mode 100644 index 0000000000..516db0f50f --- /dev/null +++ b/beacon-chain/state/state-native/v3/setters_payload_header.go @@ -0,0 +1,16 @@ +package v3 + +import ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" + +// SetLatestExecutionPayloadHeader for the beacon state. +func (b *BeaconState) SetLatestExecutionPayloadHeader(val *ethpb.ExecutionPayloadHeader) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.state.LatestExecutionPayloadHeader = val + b.markFieldAsDirty(latestExecutionPayloadHeader) + return nil +} diff --git a/beacon-chain/state/state-native/v3/setters_randao.go b/beacon-chain/state/state-native/v3/setters_randao.go new file mode 100644 index 0000000000..017ca6a57b --- /dev/null +++ b/beacon-chain/state/state-native/v3/setters_randao.go @@ -0,0 +1,53 @@ +package v3 + +import ( + "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" +) + +// SetRandaoMixes for the beacon state. Updates the entire +// randao mixes to a new value by overwriting the previous one. +func (b *BeaconState) SetRandaoMixes(val [][]byte) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.sharedFieldReferences[randaoMixes].MinusRef() + b.sharedFieldReferences[randaoMixes] = stateutil.NewRef(1) + + b.state.RandaoMixes = val + b.markFieldAsDirty(randaoMixes) + b.rebuildTrie[randaoMixes] = true + return nil +} + +// UpdateRandaoMixesAtIndex for the beacon state. Updates the randao mixes +// at a specific index to a new value. +func (b *BeaconState) UpdateRandaoMixesAtIndex(idx uint64, val []byte) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + if uint64(len(b.state.RandaoMixes)) <= idx { + return errors.Errorf("invalid index provided %d", idx) + } + b.lock.Lock() + defer b.lock.Unlock() + + mixes := b.state.RandaoMixes + if refs := b.sharedFieldReferences[randaoMixes].Refs(); refs > 1 { + // Copy elements in underlying array by reference. + mixes = make([][]byte, len(b.state.RandaoMixes)) + copy(mixes, b.state.RandaoMixes) + b.sharedFieldReferences[randaoMixes].MinusRef() + b.sharedFieldReferences[randaoMixes] = stateutil.NewRef(1) + } + + mixes[idx] = val + b.state.RandaoMixes = mixes + b.markFieldAsDirty(randaoMixes) + b.addDirtyIndices(randaoMixes, []uint64{idx}) + + return nil +} diff --git a/beacon-chain/state/state-native/v3/setters_state.go b/beacon-chain/state/state-native/v3/setters_state.go new file mode 100644 index 0000000000..24a83d2fa9 --- /dev/null +++ b/beacon-chain/state/state-native/v3/setters_state.go @@ -0,0 +1,59 @@ +package v3 + +import ( + "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" +) + +// SetStateRoots for the beacon state. Updates the state roots +// to a new value by overwriting the previous value. +func (b *BeaconState) SetStateRoots(val [][]byte) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.sharedFieldReferences[stateRoots].MinusRef() + b.sharedFieldReferences[stateRoots] = stateutil.NewRef(1) + + b.state.StateRoots = val + b.markFieldAsDirty(stateRoots) + b.rebuildTrie[stateRoots] = true + return nil +} + +// UpdateStateRootAtIndex for the beacon state. Updates the state root +// at a specific index to a new value. +func (b *BeaconState) UpdateStateRootAtIndex(idx uint64, stateRoot [32]byte) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + + b.lock.RLock() + if uint64(len(b.state.StateRoots)) <= idx { + b.lock.RUnlock() + return errors.Errorf("invalid index provided %d", idx) + } + b.lock.RUnlock() + + b.lock.Lock() + defer b.lock.Unlock() + + // Check if we hold the only reference to the shared state roots slice. + r := b.state.StateRoots + if ref := b.sharedFieldReferences[stateRoots]; ref.Refs() > 1 { + // Copy elements in underlying array by reference. + r = make([][]byte, len(b.state.StateRoots)) + copy(r, b.state.StateRoots) + ref.MinusRef() + b.sharedFieldReferences[stateRoots] = stateutil.NewRef(1) + } + + r[idx] = stateRoot[:] + b.state.StateRoots = r + + b.markFieldAsDirty(stateRoots) + b.addDirtyIndices(stateRoots, []uint64{idx}) + return nil +} diff --git a/beacon-chain/state/state-native/v3/setters_sync_committee.go b/beacon-chain/state/state-native/v3/setters_sync_committee.go new file mode 100644 index 0000000000..7b5fa8372b --- /dev/null +++ b/beacon-chain/state/state-native/v3/setters_sync_committee.go @@ -0,0 +1,31 @@ +package v3 + +import ( + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" +) + +// SetCurrentSyncCommittee for the beacon state. +func (b *BeaconState) SetCurrentSyncCommittee(val *ethpb.SyncCommittee) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.state.CurrentSyncCommittee = val + b.markFieldAsDirty(currentSyncCommittee) + return nil +} + +// SetNextSyncCommittee for the beacon state. +func (b *BeaconState) SetNextSyncCommittee(val *ethpb.SyncCommittee) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.state.NextSyncCommittee = val + b.markFieldAsDirty(nextSyncCommittee) + return nil +} diff --git a/beacon-chain/state/state-native/v3/setters_test.go b/beacon-chain/state/state-native/v3/setters_test.go new file mode 100644 index 0000000000..3199de8e14 --- /dev/null +++ b/beacon-chain/state/state-native/v3/setters_test.go @@ -0,0 +1,185 @@ +package v3 + +import ( + "context" + "strconv" + "testing" + + types "github.com/prysmaticlabs/eth2-types" + "github.com/prysmaticlabs/go-bitfield" + "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" + stateTypes "github.com/prysmaticlabs/prysm/beacon-chain/state/types" + fieldparams "github.com/prysmaticlabs/prysm/config/fieldparams" + "github.com/prysmaticlabs/prysm/config/params" + "github.com/prysmaticlabs/prysm/encoding/bytesutil" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/testing/assert" + "github.com/prysmaticlabs/prysm/testing/require" +) + +func TestAppendBeyondIndicesLimit(t *testing.T) { + zeroHash := params.BeaconConfig().ZeroHash + mockblockRoots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot) + for i := 0; i < len(mockblockRoots); i++ { + mockblockRoots[i] = zeroHash[:] + } + + mockstateRoots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot) + for i := 0; i < len(mockstateRoots); i++ { + mockstateRoots[i] = zeroHash[:] + } + mockrandaoMixes := make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector) + for i := 0; i < len(mockrandaoMixes); i++ { + mockrandaoMixes[i] = zeroHash[:] + } + payload := ðpb.ExecutionPayloadHeader{ + ParentHash: make([]byte, 32), + FeeRecipient: make([]byte, 20), + StateRoot: make([]byte, 32), + ReceiptRoot: make([]byte, 32), + LogsBloom: make([]byte, 256), + Random: make([]byte, 32), + BaseFeePerGas: make([]byte, 32), + BlockHash: make([]byte, 32), + TransactionsRoot: make([]byte, 32), + } + st, err := InitializeFromProto(ðpb.BeaconStateBellatrix{ + Slot: 1, + CurrentEpochParticipation: []byte{}, + PreviousEpochParticipation: []byte{}, + Validators: []*ethpb.Validator{}, + Eth1Data: ðpb.Eth1Data{}, + BlockRoots: mockblockRoots, + StateRoots: mockstateRoots, + RandaoMixes: mockrandaoMixes, + LatestExecutionPayloadHeader: payload, + }) + require.NoError(t, err) + _, err = st.HashTreeRoot(context.Background()) + require.NoError(t, err) + for i := stateTypes.FieldIndex(0); i < stateTypes.FieldIndex(params.BeaconConfig().BeaconStateBellatrixFieldCount); i++ { + st.dirtyFields[i] = true + } + _, err = st.HashTreeRoot(context.Background()) + require.NoError(t, err) + for i := 0; i < 10; i++ { + assert.NoError(t, st.AppendValidator(ðpb.Validator{})) + } + assert.Equal(t, false, st.rebuildTrie[validators]) + assert.NotEqual(t, len(st.dirtyIndices[validators]), 0) + + for i := 0; i < indicesLimit; i++ { + assert.NoError(t, st.AppendValidator(ðpb.Validator{})) + } + assert.Equal(t, true, st.rebuildTrie[validators]) + assert.Equal(t, len(st.dirtyIndices[validators]), 0) +} + +func TestBeaconState_AppendBalanceWithTrie(t *testing.T) { + count := uint64(100) + vals := make([]*ethpb.Validator, 0, count) + bals := make([]uint64, 0, count) + for i := uint64(1); i < count; i++ { + someRoot := [32]byte{} + someKey := [fieldparams.BLSPubkeyLength]byte{} + copy(someRoot[:], strconv.Itoa(int(i))) + copy(someKey[:], strconv.Itoa(int(i))) + vals = append(vals, ðpb.Validator{ + PublicKey: someKey[:], + WithdrawalCredentials: someRoot[:], + EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, + Slashed: false, + ActivationEligibilityEpoch: 1, + ActivationEpoch: 1, + ExitEpoch: 1, + WithdrawableEpoch: 1, + }) + bals = append(bals, params.BeaconConfig().MaxEffectiveBalance) + } + zeroHash := params.BeaconConfig().ZeroHash + mockblockRoots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot) + for i := 0; i < len(mockblockRoots); i++ { + mockblockRoots[i] = zeroHash[:] + } + + mockstateRoots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot) + for i := 0; i < len(mockstateRoots); i++ { + mockstateRoots[i] = zeroHash[:] + } + mockrandaoMixes := make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector) + for i := 0; i < len(mockrandaoMixes); i++ { + mockrandaoMixes[i] = zeroHash[:] + } + var pubKeys [][]byte + for i := uint64(0); i < params.BeaconConfig().SyncCommitteeSize; i++ { + pubKeys = append(pubKeys, bytesutil.PadTo([]byte{}, params.BeaconConfig().BLSPubkeyLength)) + } + payload := ðpb.ExecutionPayloadHeader{ + ParentHash: make([]byte, 32), + FeeRecipient: make([]byte, 20), + StateRoot: make([]byte, 32), + ReceiptRoot: make([]byte, 32), + LogsBloom: make([]byte, 256), + Random: make([]byte, 32), + BaseFeePerGas: make([]byte, 32), + BlockHash: make([]byte, 32), + TransactionsRoot: make([]byte, 32), + } + st, err := InitializeFromProto(ðpb.BeaconStateBellatrix{ + Slot: 1, + GenesisValidatorsRoot: make([]byte, 32), + Fork: ðpb.Fork{ + PreviousVersion: make([]byte, 4), + CurrentVersion: make([]byte, 4), + Epoch: 0, + }, + LatestBlockHeader: ðpb.BeaconBlockHeader{ + ParentRoot: make([]byte, 32), + StateRoot: make([]byte, 32), + BodyRoot: make([]byte, 32), + }, + CurrentEpochParticipation: []byte{}, + PreviousEpochParticipation: []byte{}, + Validators: vals, + Balances: bals, + Eth1Data: ðpb.Eth1Data{ + DepositRoot: make([]byte, 32), + BlockHash: make([]byte, 32), + }, + BlockRoots: mockblockRoots, + StateRoots: mockstateRoots, + RandaoMixes: mockrandaoMixes, + JustificationBits: bitfield.NewBitvector4(), + PreviousJustifiedCheckpoint: ðpb.Checkpoint{Root: make([]byte, 32)}, + CurrentJustifiedCheckpoint: ðpb.Checkpoint{Root: make([]byte, 32)}, + FinalizedCheckpoint: ðpb.Checkpoint{Root: make([]byte, 32)}, + Slashings: make([]uint64, params.BeaconConfig().EpochsPerSlashingsVector), + CurrentSyncCommittee: ðpb.SyncCommittee{ + Pubkeys: pubKeys, + AggregatePubkey: make([]byte, 48), + }, + NextSyncCommittee: ðpb.SyncCommittee{ + Pubkeys: pubKeys, + AggregatePubkey: make([]byte, 48), + }, + LatestExecutionPayloadHeader: payload, + }) + assert.NoError(t, err) + _, err = st.HashTreeRoot(context.Background()) + assert.NoError(t, err) + + for i := 0; i < 100; i++ { + if i%2 == 0 { + assert.NoError(t, st.UpdateBalancesAtIndex(types.ValidatorIndex(i), 1000)) + } + if i%3 == 0 { + assert.NoError(t, st.AppendBalance(1000)) + } + } + _, err = st.HashTreeRoot(context.Background()) + assert.NoError(t, err) + newRt := bytesutil.ToBytes32(st.merkleLayers[0][balances]) + wantedRt, err := stateutil.Uint64ListRootWithRegistryLimit(st.state.Balances) + assert.NoError(t, err) + assert.Equal(t, wantedRt, newRt, "state roots are unequal") +} diff --git a/beacon-chain/state/state-native/v3/setters_validator.go b/beacon-chain/state/state-native/v3/setters_validator.go new file mode 100644 index 0000000000..f621ff81b5 --- /dev/null +++ b/beacon-chain/state/state-native/v3/setters_validator.go @@ -0,0 +1,265 @@ +package v3 + +import ( + "github.com/pkg/errors" + types "github.com/prysmaticlabs/eth2-types" + "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" + "github.com/prysmaticlabs/prysm/encoding/bytesutil" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" +) + +// SetValidators for the beacon state. Updates the entire +// to a new value by overwriting the previous one. +func (b *BeaconState) SetValidators(val []*ethpb.Validator) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.state.Validators = val + b.sharedFieldReferences[validators].MinusRef() + b.sharedFieldReferences[validators] = stateutil.NewRef(1) + b.markFieldAsDirty(validators) + b.rebuildTrie[validators] = true + b.valMapHandler = stateutil.NewValMapHandler(b.state.Validators) + return nil +} + +// ApplyToEveryValidator applies the provided callback function to each validator in the +// validator registry. +func (b *BeaconState) ApplyToEveryValidator(f func(idx int, val *ethpb.Validator) (bool, *ethpb.Validator, error)) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + v := b.state.Validators + if ref := b.sharedFieldReferences[validators]; ref.Refs() > 1 { + v = b.validatorsReferences() + ref.MinusRef() + b.sharedFieldReferences[validators] = stateutil.NewRef(1) + } + b.lock.Unlock() + var changedVals []uint64 + for i, val := range v { + changed, newVal, err := f(i, val) + if err != nil { + return err + } + if changed { + changedVals = append(changedVals, uint64(i)) + v[i] = newVal + } + } + + b.lock.Lock() + defer b.lock.Unlock() + + b.state.Validators = v + b.markFieldAsDirty(validators) + b.addDirtyIndices(validators, changedVals) + + return nil +} + +// UpdateValidatorAtIndex for the beacon state. Updates the validator +// at a specific index to a new value. +func (b *BeaconState) UpdateValidatorAtIndex(idx types.ValidatorIndex, val *ethpb.Validator) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + if uint64(len(b.state.Validators)) <= uint64(idx) { + return errors.Errorf("invalid index provided %d", idx) + } + b.lock.Lock() + defer b.lock.Unlock() + + v := b.state.Validators + if ref := b.sharedFieldReferences[validators]; ref.Refs() > 1 { + v = b.validatorsReferences() + ref.MinusRef() + b.sharedFieldReferences[validators] = stateutil.NewRef(1) + } + + v[idx] = val + b.state.Validators = v + b.markFieldAsDirty(validators) + b.addDirtyIndices(validators, []uint64{uint64(idx)}) + + return nil +} + +// SetBalances for the beacon state. Updates the entire +// list to a new value by overwriting the previous one. +func (b *BeaconState) SetBalances(val []uint64) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.sharedFieldReferences[balances].MinusRef() + b.sharedFieldReferences[balances] = stateutil.NewRef(1) + + b.state.Balances = val + b.markFieldAsDirty(balances) + b.rebuildTrie[balances] = true + return nil +} + +// UpdateBalancesAtIndex for the beacon state. This method updates the balance +// at a specific index to a new value. +func (b *BeaconState) UpdateBalancesAtIndex(idx types.ValidatorIndex, val uint64) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + if uint64(len(b.state.Balances)) <= uint64(idx) { + return errors.Errorf("invalid index provided %d", idx) + } + b.lock.Lock() + defer b.lock.Unlock() + + bals := b.state.Balances + if b.sharedFieldReferences[balances].Refs() > 1 { + bals = b.balances() + b.sharedFieldReferences[balances].MinusRef() + b.sharedFieldReferences[balances] = stateutil.NewRef(1) + } + + bals[idx] = val + b.state.Balances = bals + b.markFieldAsDirty(balances) + b.addDirtyIndices(balances, []uint64{uint64(idx)}) + return nil +} + +// SetSlashings for the beacon state. Updates the entire +// list to a new value by overwriting the previous one. +func (b *BeaconState) SetSlashings(val []uint64) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.sharedFieldReferences[slashings].MinusRef() + b.sharedFieldReferences[slashings] = stateutil.NewRef(1) + + b.state.Slashings = val + b.markFieldAsDirty(slashings) + return nil +} + +// UpdateSlashingsAtIndex for the beacon state. Updates the slashings +// at a specific index to a new value. +func (b *BeaconState) UpdateSlashingsAtIndex(idx, val uint64) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + if uint64(len(b.state.Slashings)) <= idx { + return errors.Errorf("invalid index provided %d", idx) + } + b.lock.Lock() + defer b.lock.Unlock() + + s := b.state.Slashings + if b.sharedFieldReferences[slashings].Refs() > 1 { + s = b.slashings() + b.sharedFieldReferences[slashings].MinusRef() + b.sharedFieldReferences[slashings] = stateutil.NewRef(1) + } + + s[idx] = val + + b.state.Slashings = s + + b.markFieldAsDirty(slashings) + return nil +} + +// AppendValidator for the beacon state. Appends the new value +// to the the end of list. +func (b *BeaconState) AppendValidator(val *ethpb.Validator) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + vals := b.state.Validators + if b.sharedFieldReferences[validators].Refs() > 1 { + vals = b.validatorsReferences() + b.sharedFieldReferences[validators].MinusRef() + b.sharedFieldReferences[validators] = stateutil.NewRef(1) + } + + // append validator to slice + b.state.Validators = append(vals, val) + valIdx := types.ValidatorIndex(len(b.state.Validators) - 1) + + b.valMapHandler.Set(bytesutil.ToBytes48(val.PublicKey), valIdx) + + b.markFieldAsDirty(validators) + b.addDirtyIndices(validators, []uint64{uint64(valIdx)}) + return nil +} + +// AppendBalance for the beacon state. Appends the new value +// to the the end of list. +func (b *BeaconState) AppendBalance(bal uint64) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + bals := b.state.Balances + if b.sharedFieldReferences[balances].Refs() > 1 { + bals = b.balances() + b.sharedFieldReferences[balances].MinusRef() + b.sharedFieldReferences[balances] = stateutil.NewRef(1) + } + + b.state.Balances = append(bals, bal) + balIdx := len(b.state.Balances) - 1 + b.markFieldAsDirty(balances) + b.addDirtyIndices(balances, []uint64{uint64(balIdx)}) + return nil +} + +// AppendInactivityScore for the beacon state. +func (b *BeaconState) AppendInactivityScore(s uint64) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + scores := b.state.InactivityScores + if b.sharedFieldReferences[inactivityScores].Refs() > 1 { + scores = b.inactivityScores() + b.sharedFieldReferences[inactivityScores].MinusRef() + b.sharedFieldReferences[inactivityScores] = stateutil.NewRef(1) + } + + b.state.InactivityScores = append(scores, s) + b.markFieldAsDirty(inactivityScores) + return nil +} + +// SetInactivityScores for the beacon state. Updates the entire +// list to a new value by overwriting the previous one. +func (b *BeaconState) SetInactivityScores(val []uint64) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.sharedFieldReferences[inactivityScores].MinusRef() + b.sharedFieldReferences[inactivityScores] = stateutil.NewRef(1) + + b.state.InactivityScores = val + b.markFieldAsDirty(inactivityScores) + return nil +} diff --git a/beacon-chain/state/state-native/v3/state_trie.go b/beacon-chain/state/state-native/v3/state_trie.go new file mode 100644 index 0000000000..e447329fab --- /dev/null +++ b/beacon-chain/state/state-native/v3/state_trie.go @@ -0,0 +1,406 @@ +package v3 + +import ( + "context" + "runtime" + "sort" + + "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/beacon-chain/state" + "github.com/prysmaticlabs/prysm/beacon-chain/state/state-native/fieldtrie" + "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" + "github.com/prysmaticlabs/prysm/beacon-chain/state/types" + fieldparams "github.com/prysmaticlabs/prysm/config/fieldparams" + "github.com/prysmaticlabs/prysm/config/params" + "github.com/prysmaticlabs/prysm/container/slice" + "github.com/prysmaticlabs/prysm/crypto/hash" + "github.com/prysmaticlabs/prysm/encoding/bytesutil" + "github.com/prysmaticlabs/prysm/encoding/ssz" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" + "go.opencensus.io/trace" + "google.golang.org/protobuf/proto" +) + +// InitializeFromProto the beacon state from a protobuf representation. +func InitializeFromProto(st *ethpb.BeaconStateBellatrix) (*BeaconState, error) { + return InitializeFromProtoUnsafe(proto.Clone(st).(*ethpb.BeaconStateBellatrix)) +} + +// InitializeFromProtoUnsafe directly uses the beacon state protobuf pointer +// and sets it as the inner state of the BeaconState type. +func InitializeFromProtoUnsafe(st *ethpb.BeaconStateBellatrix) (*BeaconState, error) { + if st == nil { + return nil, errors.New("received nil state") + } + + fieldCount := params.BeaconConfig().BeaconStateBellatrixFieldCount + b := &BeaconState{ + state: st, + dirtyFields: make(map[types.FieldIndex]bool, fieldCount), + dirtyIndices: make(map[types.FieldIndex][]uint64, fieldCount), + stateFieldLeaves: make(map[types.FieldIndex]*fieldtrie.FieldTrie, fieldCount), + sharedFieldReferences: make(map[types.FieldIndex]*stateutil.Reference, 11), + rebuildTrie: make(map[types.FieldIndex]bool, fieldCount), + valMapHandler: stateutil.NewValMapHandler(st.Validators), + } + + var err error + for i := 0; i < fieldCount; i++ { + b.dirtyFields[types.FieldIndex(i)] = true + b.rebuildTrie[types.FieldIndex(i)] = true + b.dirtyIndices[types.FieldIndex(i)] = []uint64{} + b.stateFieldLeaves[types.FieldIndex(i)], err = fieldtrie.NewFieldTrie(types.FieldIndex(i), types.BasicArray, nil, 0) + if err != nil { + return nil, err + } + } + + // Initialize field reference tracking for shared data. + b.sharedFieldReferences[randaoMixes] = stateutil.NewRef(1) + b.sharedFieldReferences[stateRoots] = stateutil.NewRef(1) + b.sharedFieldReferences[blockRoots] = stateutil.NewRef(1) + b.sharedFieldReferences[previousEpochParticipationBits] = stateutil.NewRef(1) // New in Altair. + b.sharedFieldReferences[currentEpochParticipationBits] = stateutil.NewRef(1) // New in Altair. + b.sharedFieldReferences[slashings] = stateutil.NewRef(1) + b.sharedFieldReferences[eth1DataVotes] = stateutil.NewRef(1) + b.sharedFieldReferences[validators] = stateutil.NewRef(1) + b.sharedFieldReferences[balances] = stateutil.NewRef(1) + b.sharedFieldReferences[inactivityScores] = stateutil.NewRef(1) // New in Altair. + b.sharedFieldReferences[historicalRoots] = stateutil.NewRef(1) + b.sharedFieldReferences[latestExecutionPayloadHeader] = stateutil.NewRef(1) // New in Bellatrix. + state.StateCount.Inc() + return b, nil +} + +// Copy returns a deep copy of the beacon state. +func (b *BeaconState) Copy() state.BeaconState { + if !b.hasInnerState() { + return nil + } + + b.lock.RLock() + defer b.lock.RUnlock() + fieldCount := params.BeaconConfig().BeaconStateBellatrixFieldCount + + dst := &BeaconState{ + state: ðpb.BeaconStateBellatrix{ + // Primitive types, safe to copy. + GenesisTime: b.state.GenesisTime, + Slot: b.state.Slot, + Eth1DepositIndex: b.state.Eth1DepositIndex, + + // Large arrays, infrequently changed, constant size. + RandaoMixes: b.state.RandaoMixes, + StateRoots: b.state.StateRoots, + BlockRoots: b.state.BlockRoots, + Slashings: b.state.Slashings, + Eth1DataVotes: b.state.Eth1DataVotes, + + // Large arrays, increases over time. + Validators: b.state.Validators, + Balances: b.state.Balances, + HistoricalRoots: b.state.HistoricalRoots, + PreviousEpochParticipation: b.state.PreviousEpochParticipation, + CurrentEpochParticipation: b.state.CurrentEpochParticipation, + InactivityScores: b.state.InactivityScores, + + // Everything else, too small to be concerned about, constant size. + Fork: b.fork(), + LatestBlockHeader: b.latestBlockHeader(), + Eth1Data: b.eth1Data(), + JustificationBits: b.justificationBits(), + PreviousJustifiedCheckpoint: b.previousJustifiedCheckpoint(), + CurrentJustifiedCheckpoint: b.currentJustifiedCheckpoint(), + FinalizedCheckpoint: b.finalizedCheckpoint(), + GenesisValidatorsRoot: b.genesisValidatorRoot(), + CurrentSyncCommittee: b.currentSyncCommittee(), + NextSyncCommittee: b.nextSyncCommittee(), + LatestExecutionPayloadHeader: b.latestExecutionPayloadHeader(), + }, + dirtyFields: make(map[types.FieldIndex]bool, fieldCount), + dirtyIndices: make(map[types.FieldIndex][]uint64, fieldCount), + rebuildTrie: make(map[types.FieldIndex]bool, fieldCount), + sharedFieldReferences: make(map[types.FieldIndex]*stateutil.Reference, 11), + stateFieldLeaves: make(map[types.FieldIndex]*fieldtrie.FieldTrie, fieldCount), + + // Copy on write validator index map. + valMapHandler: b.valMapHandler, + } + + for field, ref := range b.sharedFieldReferences { + ref.AddRef() + dst.sharedFieldReferences[field] = ref + } + + // Increment ref for validator map + b.valMapHandler.AddRef() + + for i := range b.dirtyFields { + dst.dirtyFields[i] = true + } + + for i := range b.dirtyIndices { + indices := make([]uint64, len(b.dirtyIndices[i])) + copy(indices, b.dirtyIndices[i]) + dst.dirtyIndices[i] = indices + } + + for i := range b.rebuildTrie { + dst.rebuildTrie[i] = true + } + + for fldIdx, fieldTrie := range b.stateFieldLeaves { + dst.stateFieldLeaves[fldIdx] = fieldTrie + if fieldTrie.FieldReference() != nil { + fieldTrie.Lock() + fieldTrie.FieldReference().AddRef() + fieldTrie.Unlock() + } + } + + if b.merkleLayers != nil { + dst.merkleLayers = make([][][]byte, len(b.merkleLayers)) + for i, layer := range b.merkleLayers { + dst.merkleLayers[i] = make([][]byte, len(layer)) + for j, content := range layer { + dst.merkleLayers[i][j] = make([]byte, len(content)) + copy(dst.merkleLayers[i][j], content) + } + } + } + state.StateCount.Inc() + // Finalizer runs when dst is being destroyed in garbage collection. + runtime.SetFinalizer(dst, func(b *BeaconState) { + for field, v := range b.sharedFieldReferences { + v.MinusRef() + if b.stateFieldLeaves[field].FieldReference() != nil { + b.stateFieldLeaves[field].FieldReference().MinusRef() + } + } + for i := 0; i < fieldCount; i++ { + field := types.FieldIndex(i) + delete(b.stateFieldLeaves, field) + delete(b.dirtyIndices, field) + delete(b.dirtyFields, field) + delete(b.sharedFieldReferences, field) + delete(b.stateFieldLeaves, field) + } + state.StateCount.Sub(1) + }) + + return dst +} + +// HashTreeRoot of the beacon state retrieves the Merkle root of the trie +// representation of the beacon state based on the eth2 Simple Serialize specification. +func (b *BeaconState) HashTreeRoot(ctx context.Context) ([32]byte, error) { + _, span := trace.StartSpan(ctx, "BeaconStateBellatrix.HashTreeRoot") + defer span.End() + + b.lock.Lock() + defer b.lock.Unlock() + if err := b.initializeMerkleLayers(ctx); err != nil { + return [32]byte{}, err + } + if err := b.recomputeDirtyFields(ctx); err != nil { + return [32]byte{}, err + } + return bytesutil.ToBytes32(b.merkleLayers[len(b.merkleLayers)-1][0]), nil +} + +// Initializes the Merkle layers for the beacon state if they are empty. +// WARNING: Caller must acquire the mutex before using. +func (b *BeaconState) initializeMerkleLayers(ctx context.Context) error { + if len(b.merkleLayers) > 0 { + return nil + } + fieldRoots, err := computeFieldRoots(ctx, b.state) + if err != nil { + return err + } + layers := stateutil.Merkleize(fieldRoots) + b.merkleLayers = layers + b.dirtyFields = make(map[types.FieldIndex]bool, params.BeaconConfig().BeaconStateBellatrixFieldCount) + return nil +} + +// Recomputes the Merkle layers for the dirty fields in the state. +// WARNING: Caller must acquire the mutex before using. +func (b *BeaconState) recomputeDirtyFields(_ context.Context) error { + for field := range b.dirtyFields { + root, err := b.rootSelector(field) + if err != nil { + return err + } + b.merkleLayers[0][field] = root[:] + b.recomputeRoot(int(field)) + delete(b.dirtyFields, field) + } + return nil +} + +// FieldReferencesCount returns the reference count held by each field. This +// also includes the field trie held by each field. +func (b *BeaconState) FieldReferencesCount() map[string]uint64 { + refMap := make(map[string]uint64) + b.lock.RLock() + defer b.lock.RUnlock() + for i, f := range b.sharedFieldReferences { + refMap[i.String(b.Version())] = uint64(f.Refs()) + } + for i, f := range b.stateFieldLeaves { + numOfRefs := uint64(f.FieldReference().Refs()) + f.RLock() + if !f.Empty() { + refMap[i.String(b.Version())+"_trie"] = numOfRefs + } + f.RUnlock() + } + return refMap +} + +// IsNil checks if the state and the underlying proto +// object are nil. +func (b *BeaconState) IsNil() bool { + return b == nil || b.state == nil +} + +func (b *BeaconState) rootSelector(field types.FieldIndex) ([32]byte, error) { + hasher := hash.CustomSHA256Hasher() + switch field { + case genesisTime: + return ssz.Uint64Root(b.state.GenesisTime), nil + case genesisValidatorRoot: + return bytesutil.ToBytes32(b.state.GenesisValidatorsRoot), nil + case slot: + return ssz.Uint64Root(uint64(b.state.Slot)), nil + case eth1DepositIndex: + return ssz.Uint64Root(b.state.Eth1DepositIndex), nil + case fork: + return ssz.ForkRoot(b.state.Fork) + case latestBlockHeader: + return stateutil.BlockHeaderRoot(b.state.LatestBlockHeader) + case blockRoots: + if b.rebuildTrie[field] { + err := b.resetFieldTrie(field, b.state.BlockRoots, fieldparams.BlockRootsLength) + if err != nil { + return [32]byte{}, err + } + b.dirtyIndices[field] = []uint64{} + delete(b.rebuildTrie, field) + return b.stateFieldLeaves[field].TrieRoot() + } + return b.recomputeFieldTrie(blockRoots, b.state.BlockRoots) + case stateRoots: + if b.rebuildTrie[field] { + err := b.resetFieldTrie(field, b.state.StateRoots, fieldparams.StateRootsLength) + if err != nil { + return [32]byte{}, err + } + b.dirtyIndices[field] = []uint64{} + delete(b.rebuildTrie, field) + return b.stateFieldLeaves[field].TrieRoot() + } + return b.recomputeFieldTrie(stateRoots, b.state.StateRoots) + case historicalRoots: + return ssz.ByteArrayRootWithLimit(b.state.HistoricalRoots, fieldparams.HistoricalRootsLength) + case eth1Data: + return stateutil.Eth1Root(hasher, b.state.Eth1Data) + case eth1DataVotes: + if b.rebuildTrie[field] { + err := b.resetFieldTrie( + field, + b.state.Eth1DataVotes, + fieldparams.Eth1DataVotesLength, + ) + if err != nil { + return [32]byte{}, err + } + b.dirtyIndices[field] = []uint64{} + delete(b.rebuildTrie, field) + return b.stateFieldLeaves[field].TrieRoot() + } + return b.recomputeFieldTrie(field, b.state.Eth1DataVotes) + case validators: + if b.rebuildTrie[field] { + err := b.resetFieldTrie(field, b.state.Validators, fieldparams.ValidatorRegistryLimit) + if err != nil { + return [32]byte{}, err + } + b.dirtyIndices[validators] = []uint64{} + delete(b.rebuildTrie, validators) + return b.stateFieldLeaves[field].TrieRoot() + } + return b.recomputeFieldTrie(validators, b.state.Validators) + case balances: + return stateutil.Uint64ListRootWithRegistryLimit(b.state.Balances) + case randaoMixes: + if b.rebuildTrie[field] { + err := b.resetFieldTrie(field, b.state.RandaoMixes, fieldparams.RandaoMixesLength) + if err != nil { + return [32]byte{}, err + } + b.dirtyIndices[field] = []uint64{} + delete(b.rebuildTrie, field) + return b.stateFieldLeaves[field].TrieRoot() + } + return b.recomputeFieldTrie(randaoMixes, b.state.RandaoMixes) + case slashings: + return ssz.SlashingsRoot(b.state.Slashings) + case previousEpochParticipationBits: + return stateutil.ParticipationBitsRoot(b.state.PreviousEpochParticipation) + case currentEpochParticipationBits: + return stateutil.ParticipationBitsRoot(b.state.CurrentEpochParticipation) + case justificationBits: + return bytesutil.ToBytes32(b.state.JustificationBits), nil + case previousJustifiedCheckpoint: + return ssz.CheckpointRoot(hasher, b.state.PreviousJustifiedCheckpoint) + case currentJustifiedCheckpoint: + return ssz.CheckpointRoot(hasher, b.state.CurrentJustifiedCheckpoint) + case finalizedCheckpoint: + return ssz.CheckpointRoot(hasher, b.state.FinalizedCheckpoint) + case inactivityScores: + return stateutil.Uint64ListRootWithRegistryLimit(b.state.InactivityScores) + case currentSyncCommittee: + return stateutil.SyncCommitteeRoot(b.state.CurrentSyncCommittee) + case nextSyncCommittee: + return stateutil.SyncCommitteeRoot(b.state.NextSyncCommittee) + case latestExecutionPayloadHeader: + return b.state.LatestExecutionPayloadHeader.HashTreeRoot() + } + return [32]byte{}, errors.New("invalid field index provided") +} + +func (b *BeaconState) recomputeFieldTrie(index types.FieldIndex, elements interface{}) ([32]byte, error) { + fTrie := b.stateFieldLeaves[index] + if fTrie.FieldReference().Refs() > 1 { + fTrie.Lock() + defer fTrie.Unlock() + fTrie.FieldReference().MinusRef() + newTrie := fTrie.CopyTrie() + b.stateFieldLeaves[index] = newTrie + fTrie = newTrie + } + // remove duplicate indexes + b.dirtyIndices[index] = slice.SetUint64(b.dirtyIndices[index]) + // sort indexes again + sort.Slice(b.dirtyIndices[index], func(i int, j int) bool { + return b.dirtyIndices[index][i] < b.dirtyIndices[index][j] + }) + root, err := fTrie.RecomputeTrie(b.dirtyIndices[index], elements) + if err != nil { + return [32]byte{}, err + } + b.dirtyIndices[index] = []uint64{} + return root, nil +} + +func (b *BeaconState) resetFieldTrie(index types.FieldIndex, elements interface{}, length uint64) error { + fTrie, err := fieldtrie.NewFieldTrie(index, fieldMap[index], elements, length) + if err != nil { + return err + } + b.stateFieldLeaves[index] = fTrie + b.dirtyIndices[index] = []uint64{} + return nil +} diff --git a/beacon-chain/state/state-native/v3/state_trie_test.go b/beacon-chain/state/state-native/v3/state_trie_test.go new file mode 100644 index 0000000000..863f191f79 --- /dev/null +++ b/beacon-chain/state/state-native/v3/state_trie_test.go @@ -0,0 +1,168 @@ +package v3 + +import ( + "strconv" + "sync" + "testing" + + "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" + "github.com/prysmaticlabs/prysm/config/features" + fieldparams "github.com/prysmaticlabs/prysm/config/fieldparams" + "github.com/prysmaticlabs/prysm/config/params" + "github.com/prysmaticlabs/prysm/encoding/bytesutil" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/testing/assert" + "github.com/prysmaticlabs/prysm/testing/require" +) + +func TestMain(m *testing.M) { + resetCfg := features.InitWithReset(&features.Flags{EnableBalanceTrieComputation: true}) + defer resetCfg() + m.Run() +} + +func TestValidatorMap_DistinctCopy(t *testing.T) { + count := uint64(100) + vals := make([]*ethpb.Validator, 0, count) + for i := uint64(1); i < count; i++ { + someRoot := [32]byte{} + someKey := [fieldparams.BLSPubkeyLength]byte{} + copy(someRoot[:], strconv.Itoa(int(i))) + copy(someKey[:], strconv.Itoa(int(i))) + vals = append(vals, ðpb.Validator{ + PublicKey: someKey[:], + WithdrawalCredentials: someRoot[:], + EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, + Slashed: false, + ActivationEligibilityEpoch: 1, + ActivationEpoch: 1, + ExitEpoch: 1, + WithdrawableEpoch: 1, + }) + } + handler := stateutil.NewValMapHandler(vals) + newHandler := handler.Copy() + wantedPubkey := strconv.Itoa(22) + handler.Set(bytesutil.ToBytes48([]byte(wantedPubkey)), 27) + val1, _ := handler.Get(bytesutil.ToBytes48([]byte(wantedPubkey))) + val2, _ := newHandler.Get(bytesutil.ToBytes48([]byte(wantedPubkey))) + assert.NotEqual(t, val1, val2, "Values are supposed to be unequal due to copy") +} + +func TestInitializeFromProto(t *testing.T) { + type test struct { + name string + state *ethpb.BeaconStateBellatrix + error string + } + initTests := []test{ + { + name: "nil state", + state: nil, + error: "received nil state", + }, + { + name: "nil validators", + state: ðpb.BeaconStateBellatrix{ + Slot: 4, + Validators: nil, + }, + }, + { + name: "empty state", + state: ðpb.BeaconStateBellatrix{}, + }, + } + for _, tt := range initTests { + t.Run(tt.name, func(t *testing.T) { + _, err := InitializeFromProto(tt.state) + if tt.error != "" { + require.ErrorContains(t, tt.error, err) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestBeaconState_NoDeadlock(t *testing.T) { + count := uint64(100) + vals := make([]*ethpb.Validator, 0, count) + for i := uint64(1); i < count; i++ { + someRoot := [32]byte{} + someKey := [fieldparams.BLSPubkeyLength]byte{} + copy(someRoot[:], strconv.Itoa(int(i))) + copy(someKey[:], strconv.Itoa(int(i))) + vals = append(vals, ðpb.Validator{ + PublicKey: someKey[:], + WithdrawalCredentials: someRoot[:], + EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, + Slashed: false, + ActivationEligibilityEpoch: 1, + ActivationEpoch: 1, + ExitEpoch: 1, + WithdrawableEpoch: 1, + }) + } + st, err := InitializeFromProtoUnsafe(ðpb.BeaconStateBellatrix{ + Validators: vals, + }) + assert.NoError(t, err) + + wg := new(sync.WaitGroup) + + wg.Add(1) + go func() { + // Continuously lock and unlock the state + // by acquiring the lock. + for i := 0; i < 1000; i++ { + for _, f := range st.stateFieldLeaves { + f.Lock() + if f.Empty() { + f.InsertFieldLayer(make([][]*[32]byte, 10)) + } + f.Unlock() + f.FieldReference().AddRef() + } + } + wg.Done() + }() + // Constantly read from the offending portion + // of the code to ensure there is no possible + // recursive read locking. + for i := 0; i < 1000; i++ { + go func() { + _ = st.FieldReferencesCount() + }() + } + // Test will not terminate in the event of a deadlock. + wg.Wait() +} + +func TestInitializeFromProtoUnsafe(t *testing.T) { + type test struct { + name string + state *ethpb.BeaconStateBellatrix + error string + } + initTests := []test{ + { + name: "nil state", + state: nil, + error: "received nil state", + }, + { + name: "nil validators", + state: ðpb.BeaconStateBellatrix{ + Slot: 4, + Validators: nil, + }, + }, + { + name: "empty state", + state: ðpb.BeaconStateBellatrix{}, + }, + // TODO: Add full state. Blocked by testutil migration. + } + _ = initTests +} diff --git a/beacon-chain/state/state-native/v3/types.go b/beacon-chain/state/state-native/v3/types.go new file mode 100644 index 0000000000..d896c768c8 --- /dev/null +++ b/beacon-chain/state/state-native/v3/types.go @@ -0,0 +1,77 @@ +package v3 + +import ( + "sync" + + "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/beacon-chain/state/state-native/fieldtrie" + "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" + "github.com/prysmaticlabs/prysm/beacon-chain/state/types" + "github.com/prysmaticlabs/prysm/config/params" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" +) + +func init() { + fieldMap = make(map[types.FieldIndex]types.DataType, params.BeaconConfig().BeaconStateBellatrixFieldCount) + + // Initialize the fixed sized arrays. + fieldMap[types.BlockRoots] = types.BasicArray + fieldMap[types.StateRoots] = types.BasicArray + fieldMap[types.RandaoMixes] = types.BasicArray + + // Initialize the composite arrays. + fieldMap[types.Eth1DataVotes] = types.CompositeArray + fieldMap[types.Validators] = types.CompositeArray + fieldMap[types.Balances] = types.CompressedArray +} + +// Field Aliases for values from the types package. +const ( + genesisTime = types.GenesisTime + genesisValidatorRoot = types.GenesisValidatorRoot + slot = types.Slot + fork = types.Fork + latestBlockHeader = types.LatestBlockHeader + blockRoots = types.BlockRoots + stateRoots = types.StateRoots + historicalRoots = types.HistoricalRoots + eth1Data = types.Eth1Data + eth1DataVotes = types.Eth1DataVotes + eth1DepositIndex = types.Eth1DepositIndex + validators = types.Validators + balances = types.Balances + randaoMixes = types.RandaoMixes + slashings = types.Slashings + previousEpochParticipationBits = types.PreviousEpochParticipationBits + currentEpochParticipationBits = types.CurrentEpochParticipationBits + justificationBits = types.JustificationBits + previousJustifiedCheckpoint = types.PreviousJustifiedCheckpoint + currentJustifiedCheckpoint = types.CurrentJustifiedCheckpoint + finalizedCheckpoint = types.FinalizedCheckpoint + inactivityScores = types.InactivityScores + currentSyncCommittee = types.CurrentSyncCommittee + nextSyncCommittee = types.NextSyncCommittee + latestExecutionPayloadHeader = types.LatestExecutionPayloadHeader +) + +// fieldMap keeps track of each field +// to its corresponding data type. +var fieldMap map[types.FieldIndex]types.DataType + +// ErrNilInnerState returns when the inner state is nil and no copy set or get +// operations can be performed on state. +var ErrNilInnerState = errors.New("nil inner state") + +// BeaconState defines a struct containing utilities for the eth2 chain state, defining +// getters and setters for its respective values and helpful functions such as HashTreeRoot(). +type BeaconState struct { + state *ethpb.BeaconStateBellatrix + lock sync.RWMutex + dirtyFields map[types.FieldIndex]bool + dirtyIndices map[types.FieldIndex][]uint64 + stateFieldLeaves map[types.FieldIndex]*fieldtrie.FieldTrie + rebuildTrie map[types.FieldIndex]bool + valMapHandler *stateutil.ValidatorMapHandler + merkleLayers [][][]byte + sharedFieldReferences map[types.FieldIndex]*stateutil.Reference +} diff --git a/beacon-chain/state/v1/BUILD.bazel b/beacon-chain/state/v1/BUILD.bazel index 6da917f934..55b7b57591 100644 --- a/beacon-chain/state/v1/BUILD.bazel +++ b/beacon-chain/state/v1/BUILD.bazel @@ -59,8 +59,6 @@ go_library( "//proto/prysm/v1alpha1:go_default_library", "//runtime/version:go_default_library", "@com_github_pkg_errors//:go_default_library", - "@com_github_prometheus_client_golang//prometheus:go_default_library", - "@com_github_prometheus_client_golang//prometheus/promauto:go_default_library", "@com_github_prysmaticlabs_eth2_types//:go_default_library", "@com_github_prysmaticlabs_go_bitfield//:go_default_library", "@io_opencensus_go//trace:go_default_library", diff --git a/beacon-chain/state/v1/setters_state.go b/beacon-chain/state/v1/setters_state.go index ef2c19c2fa..1a7d93fad7 100644 --- a/beacon-chain/state/v1/setters_state.go +++ b/beacon-chain/state/v1/setters_state.go @@ -5,6 +5,24 @@ import ( "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" ) +// SetStateRoots for the beacon state. Updates the state roots +// to a new value by overwriting the previous value. +func (b *BeaconState) SetStateRoots(val [][]byte) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.sharedFieldReferences[stateRoots].MinusRef() + b.sharedFieldReferences[stateRoots] = stateutil.NewRef(1) + + b.state.StateRoots = val + b.markFieldAsDirty(stateRoots) + b.rebuildTrie[stateRoots] = true + return nil +} + // UpdateStateRootAtIndex for the beacon state. Updates the state root // at a specific index to a new value. func (b *BeaconState) UpdateStateRootAtIndex(idx uint64, stateRoot [32]byte) error { diff --git a/beacon-chain/state/v1/state_trie.go b/beacon-chain/state/v1/state_trie.go index 1a02f73433..a0068e655b 100644 --- a/beacon-chain/state/v1/state_trie.go +++ b/beacon-chain/state/v1/state_trie.go @@ -6,8 +6,6 @@ import ( "sort" "github.com/pkg/errors" - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promauto" "github.com/prysmaticlabs/prysm/beacon-chain/state" "github.com/prysmaticlabs/prysm/beacon-chain/state/fieldtrie" "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" @@ -24,13 +22,6 @@ import ( "google.golang.org/protobuf/proto" ) -var ( - stateCount = promauto.NewGauge(prometheus.GaugeOpts{ - Name: "beacon_state_count", - Help: "Count the number of active beacon state objects.", - }) -) - // InitializeFromProto the beacon state from a protobuf representation. func InitializeFromProto(st *ethpb.BeaconState) (*BeaconState, error) { return InitializeFromProtoUnsafe(proto.Clone(st).(*ethpb.BeaconState)) @@ -77,7 +68,7 @@ func InitializeFromProtoUnsafe(st *ethpb.BeaconState) (*BeaconState, error) { b.sharedFieldReferences[balances] = stateutil.NewRef(1) b.sharedFieldReferences[historicalRoots] = stateutil.NewRef(1) - stateCount.Inc() + state.StateCount.Inc() return b, nil } @@ -173,7 +164,7 @@ func (b *BeaconState) Copy() state.BeaconState { } } - stateCount.Inc() + state.StateCount.Inc() // Finalizer runs when dst is being destroyed in garbage collection. runtime.SetFinalizer(dst, func(b *BeaconState) { for field, v := range b.sharedFieldReferences { @@ -191,7 +182,7 @@ func (b *BeaconState) Copy() state.BeaconState { delete(b.sharedFieldReferences, field) delete(b.stateFieldLeaves, field) } - stateCount.Sub(1) + state.StateCount.Sub(1) }) return dst } diff --git a/beacon-chain/state/v2/BUILD.bazel b/beacon-chain/state/v2/BUILD.bazel index 2fc8b6ae74..f047c0139e 100644 --- a/beacon-chain/state/v2/BUILD.bazel +++ b/beacon-chain/state/v2/BUILD.bazel @@ -51,8 +51,6 @@ go_library( "//proto/prysm/v1alpha1:go_default_library", "//runtime/version:go_default_library", "@com_github_pkg_errors//:go_default_library", - "@com_github_prometheus_client_golang//prometheus:go_default_library", - "@com_github_prometheus_client_golang//prometheus/promauto:go_default_library", "@com_github_prysmaticlabs_eth2_types//:go_default_library", "@com_github_prysmaticlabs_go_bitfield//:go_default_library", "@io_opencensus_go//trace:go_default_library", diff --git a/beacon-chain/state/v2/setters_state.go b/beacon-chain/state/v2/setters_state.go index 27792194ab..e64df5335b 100644 --- a/beacon-chain/state/v2/setters_state.go +++ b/beacon-chain/state/v2/setters_state.go @@ -5,6 +5,24 @@ import ( "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" ) +// SetStateRoots for the beacon state. Updates the state roots +// to a new value by overwriting the previous value. +func (b *BeaconState) SetStateRoots(val [][]byte) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.sharedFieldReferences[stateRoots].MinusRef() + b.sharedFieldReferences[stateRoots] = stateutil.NewRef(1) + + b.state.StateRoots = val + b.markFieldAsDirty(stateRoots) + b.rebuildTrie[stateRoots] = true + return nil +} + // UpdateStateRootAtIndex for the beacon state. Updates the state root // at a specific index to a new value. func (b *BeaconState) UpdateStateRootAtIndex(idx uint64, stateRoot [32]byte) error { diff --git a/beacon-chain/state/v2/state_trie.go b/beacon-chain/state/v2/state_trie.go index 1c2d7db956..76437c219e 100644 --- a/beacon-chain/state/v2/state_trie.go +++ b/beacon-chain/state/v2/state_trie.go @@ -8,8 +8,6 @@ import ( "sort" "github.com/pkg/errors" - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promauto" "github.com/prysmaticlabs/prysm/beacon-chain/state" "github.com/prysmaticlabs/prysm/beacon-chain/state/fieldtrie" "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" @@ -26,13 +24,6 @@ import ( "google.golang.org/protobuf/proto" ) -var ( - stateCount = promauto.NewGauge(prometheus.GaugeOpts{ - Name: "beacon_state_altair_count", - Help: "Count the number of active beacon state objects.", - }) -) - // InitializeFromProto the beacon state from a protobuf representation. func InitializeFromProto(st *ethpb.BeaconStateAltair) (*BeaconState, error) { return InitializeFromProtoUnsafe(proto.Clone(st).(*ethpb.BeaconStateAltair)) @@ -101,7 +92,7 @@ func InitializeFromProtoUnsafe(st *ethpb.BeaconStateAltair) (*BeaconState, error b.sharedFieldReferences[inactivityScores] = stateutil.NewRef(1) // New in Altair. b.sharedFieldReferences[historicalRoots] = stateutil.NewRef(1) - stateCount.Inc() + state.StateCount.Inc() return b, nil } @@ -201,7 +192,7 @@ func (b *BeaconState) Copy() state.BeaconState { } } - stateCount.Inc() + state.StateCount.Inc() // Finalizer runs when dst is being destroyed in garbage collection. runtime.SetFinalizer(dst, func(b *BeaconState) { for field, v := range b.sharedFieldReferences { @@ -218,7 +209,7 @@ func (b *BeaconState) Copy() state.BeaconState { delete(b.sharedFieldReferences, field) delete(b.stateFieldLeaves, field) } - stateCount.Sub(1) + state.StateCount.Sub(1) }) return dst diff --git a/beacon-chain/state/v3/BUILD.bazel b/beacon-chain/state/v3/BUILD.bazel index 7bad588a7f..a45dd34b42 100644 --- a/beacon-chain/state/v3/BUILD.bazel +++ b/beacon-chain/state/v3/BUILD.bazel @@ -52,8 +52,6 @@ go_library( "//proto/prysm/v1alpha1:go_default_library", "//runtime/version:go_default_library", "@com_github_pkg_errors//:go_default_library", - "@com_github_prometheus_client_golang//prometheus:go_default_library", - "@com_github_prometheus_client_golang//prometheus/promauto:go_default_library", "@com_github_prysmaticlabs_eth2_types//:go_default_library", "@com_github_prysmaticlabs_go_bitfield//:go_default_library", "@io_opencensus_go//trace:go_default_library", diff --git a/beacon-chain/state/v3/setters_state.go b/beacon-chain/state/v3/setters_state.go index 1fee76364e..24a83d2fa9 100644 --- a/beacon-chain/state/v3/setters_state.go +++ b/beacon-chain/state/v3/setters_state.go @@ -5,6 +5,24 @@ import ( "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" ) +// SetStateRoots for the beacon state. Updates the state roots +// to a new value by overwriting the previous value. +func (b *BeaconState) SetStateRoots(val [][]byte) error { + if !b.hasInnerState() { + return ErrNilInnerState + } + b.lock.Lock() + defer b.lock.Unlock() + + b.sharedFieldReferences[stateRoots].MinusRef() + b.sharedFieldReferences[stateRoots] = stateutil.NewRef(1) + + b.state.StateRoots = val + b.markFieldAsDirty(stateRoots) + b.rebuildTrie[stateRoots] = true + return nil +} + // UpdateStateRootAtIndex for the beacon state. Updates the state root // at a specific index to a new value. func (b *BeaconState) UpdateStateRootAtIndex(idx uint64, stateRoot [32]byte) error { diff --git a/beacon-chain/state/v3/state_trie.go b/beacon-chain/state/v3/state_trie.go index 0dec65e078..97284885b8 100644 --- a/beacon-chain/state/v3/state_trie.go +++ b/beacon-chain/state/v3/state_trie.go @@ -6,8 +6,6 @@ import ( "sort" "github.com/pkg/errors" - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promauto" "github.com/prysmaticlabs/prysm/beacon-chain/state" "github.com/prysmaticlabs/prysm/beacon-chain/state/fieldtrie" "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" @@ -23,13 +21,6 @@ import ( "google.golang.org/protobuf/proto" ) -var ( - stateCount = promauto.NewGauge(prometheus.GaugeOpts{ - Name: "beacon_state_bellatrix_count", - Help: "Count the number of active beacon state objects.", - }) -) - // InitializeFromProto the beacon state from a protobuf representation. func InitializeFromProto(st *ethpb.BeaconStateBellatrix) (*BeaconState, error) { return InitializeFromProtoUnsafe(proto.Clone(st).(*ethpb.BeaconStateBellatrix)) @@ -77,7 +68,7 @@ func InitializeFromProtoUnsafe(st *ethpb.BeaconStateBellatrix) (*BeaconState, er b.sharedFieldReferences[inactivityScores] = stateutil.NewRef(1) // New in Altair. b.sharedFieldReferences[historicalRoots] = stateutil.NewRef(1) b.sharedFieldReferences[latestExecutionPayloadHeader] = stateutil.NewRef(1) // New in Bellatrix. - stateCount.Inc() + state.StateCount.Inc() return b, nil } @@ -177,7 +168,7 @@ func (b *BeaconState) Copy() state.BeaconState { } } } - stateCount.Inc() + state.StateCount.Inc() // Finalizer runs when dst is being destroyed in garbage collection. runtime.SetFinalizer(dst, func(b *BeaconState) { for field, v := range b.sharedFieldReferences { @@ -194,7 +185,7 @@ func (b *BeaconState) Copy() state.BeaconState { delete(b.sharedFieldReferences, field) delete(b.stateFieldLeaves, field) } - stateCount.Sub(1) + state.StateCount.Sub(1) }) return dst diff --git a/deps.bzl b/deps.bzl index 2d7d77da81..5aa9221553 100644 --- a/deps.bzl +++ b/deps.bzl @@ -3715,8 +3715,8 @@ def prysm_deps(): go_repository( name = "com_github_yuin_goldmark", importpath = "github.com/yuin/goldmark", - sum = "h1:dPmz1Snjq0kmkz159iL7S6WzdahUTHnHB5M56WFVifs=", - version = "v1.3.5", + sum = "h1:/vn0k+RBvwlxEmP5E7SZMqNxPhfMVFEJiykr15/0XKM=", + version = "v1.4.1", ) go_repository( @@ -4223,8 +4223,8 @@ def prysm_deps(): go_repository( name = "org_golang_x_mod", importpath = "golang.org/x/mod", - sum = "h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=", - version = "v0.4.2", + sum = "h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38=", + version = "v0.5.1", ) go_repository( @@ -4280,8 +4280,8 @@ def prysm_deps(): go_repository( name = "org_golang_x_tools", importpath = "golang.org/x/tools", - sum = "h1:wGiQel/hW0NnEkJUk8lbzkX2gFJU6PFxf1v5OlCfuOs=", - version = "v0.1.1", + sum = "h1:P1HhGGuLW4aAclzjtmJdf0mJOjVUZUzOTqkAkWL+l6w=", + version = "v0.1.8", ) go_repository( diff --git a/go.mod b/go.mod index e1f47b84e7..32dec54a5f 100644 --- a/go.mod +++ b/go.mod @@ -84,7 +84,7 @@ require ( golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 golang.org/x/exp v0.0.0-20200513190911-00229845015e golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - golang.org/x/tools v0.1.1 + golang.org/x/tools v0.1.8 google.golang.org/genproto v0.0.0-20210426193834-eac7f76ac494 google.golang.org/grpc v1.40.0 google.golang.org/protobuf v1.27.1 diff --git a/go.sum b/go.sum index 597b964517..b1d177c534 100644 --- a/go.sum +++ b/go.sum @@ -1381,6 +1381,7 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0= @@ -1502,8 +1503,9 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1564,6 +1566,7 @@ golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20170912212905-13449ad91cb2/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1682,6 +1685,7 @@ golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881 h1:TyHqChC80pFkXWraUUf6RuB5IqFdQieMLwwCJokV2pc= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= @@ -1770,8 +1774,9 @@ golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82u golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.1 h1:wGiQel/hW0NnEkJUk8lbzkX2gFJU6PFxf1v5OlCfuOs= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.8 h1:P1HhGGuLW4aAclzjtmJdf0mJOjVUZUzOTqkAkWL+l6w= +golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=