From dc472ec2b27ba4bd2daae596c317219efd04b852 Mon Sep 17 00:00:00 2001 From: Dhruba Basu <7675102+dhrubabasu@users.noreply.github.com> Date: Tue, 12 Dec 2023 14:37:43 -0500 Subject: [PATCH] `vms/platformvm`: Permit usage of the `Transactions` field in `BanffProposalBlock` (#2451) Co-authored-by: Stephen Buttolph --- vms/avm/block/builder/builder.go | 16 +- vms/avm/state/diff.go | 17 +- vms/platformvm/block/executor/acceptor.go | 14 +- vms/platformvm/block/executor/backend.go | 8 +- vms/platformvm/block/executor/block_state.go | 1 + .../block/executor/proposal_block_test.go | 171 +++++++++++++++++- vms/platformvm/block/executor/verifier.go | 77 +++++--- .../block/executor/verifier_test.go | 34 ++-- vms/platformvm/state/diff.go | 17 +- vms/platformvm/state/diff_test.go | 16 +- 10 files changed, 290 insertions(+), 81 deletions(-) diff --git a/vms/avm/block/builder/builder.go b/vms/avm/block/builder/builder.go index 97610bb1ca2d..6b7a643d19c9 100644 --- a/vms/avm/block/builder/builder.go +++ b/vms/avm/block/builder/builder.go @@ -105,7 +105,7 @@ func (b *builder) BuildBlock(context.Context) (snowman.Block, error) { // Invariant: [tx] has already been syntactically verified. - txDiff, err := wrapState(stateDiff) + txDiff, err := state.NewDiffOn(stateDiff) if err != nil { return nil, err } @@ -170,17 +170,3 @@ func (b *builder) BuildBlock(context.Context) (snowman.Block, error) { return b.manager.NewBlock(statelessBlk), nil } - -type stateGetter struct { - state state.Chain -} - -func (s stateGetter) GetState(ids.ID) (state.Chain, bool) { - return s.state, true -} - -func wrapState(parentState state.Chain) (state.Diff, error) { - return state.NewDiff(ids.Empty, stateGetter{ - state: parentState, - }) -} diff --git a/vms/avm/state/diff.go b/vms/avm/state/diff.go index 1d53fa37da3e..1f1a98d38a96 100644 --- a/vms/avm/state/diff.go +++ b/vms/avm/state/diff.go @@ -16,7 +16,8 @@ import ( ) var ( - _ Diff = (*diff)(nil) + _ Diff = (*diff)(nil) + _ Versions = stateGetter{} ErrMissingParentState = errors.New("missing parent state") ) @@ -61,6 +62,20 @@ func NewDiff( }, nil } +type stateGetter struct { + state Chain +} + +func (s stateGetter) GetState(ids.ID) (Chain, bool) { + return s.state, true +} + +func NewDiffOn(parentState Chain) (Diff, error) { + return NewDiff(ids.Empty, stateGetter{ + state: parentState, + }) +} + func (d *diff) GetUTXO(utxoID ids.ID) (*avax.UTXO, error) { if utxo, modified := d.modifiedUTXOs[utxoID]; modified { if utxo == nil { diff --git a/vms/platformvm/block/executor/acceptor.go b/vms/platformvm/block/executor/acceptor.go index ff5542f65157..af26c931d088 100644 --- a/vms/platformvm/block/executor/acceptor.go +++ b/vms/platformvm/block/executor/acceptor.go @@ -172,6 +172,16 @@ func (a *acceptor) optionBlock(b, parent block.Block, blockType string) error { return err } + parentState, ok := a.blkIDToState[parentID] + if !ok { + return fmt.Errorf("%w %s", errMissingBlockState, parentID) + } + if parentState.onDecisionState != nil { + if err := parentState.onDecisionState.Apply(a.state); err != nil { + return err + } + } + blkState, ok := a.blkIDToState[blkID] if !ok { return fmt.Errorf("%w %s", errMissingBlockState, blkID) @@ -191,11 +201,11 @@ func (a *acceptor) optionBlock(b, parent block.Block, blockType string) error { } // Note that this method writes [batch] to the database. - if err := a.ctx.SharedMemory.Apply(blkState.atomicRequests, batch); err != nil { + if err := a.ctx.SharedMemory.Apply(parentState.atomicRequests, batch); err != nil { return fmt.Errorf("failed to apply vm's state to shared memory: %w", err) } - if onAcceptFunc := blkState.onAcceptFunc; onAcceptFunc != nil { + if onAcceptFunc := parentState.onAcceptFunc; onAcceptFunc != nil { onAcceptFunc() } diff --git a/vms/platformvm/block/executor/backend.go b/vms/platformvm/block/executor/backend.go index 94b301c89ddb..4d915047f560 100644 --- a/vms/platformvm/block/executor/backend.go +++ b/vms/platformvm/block/executor/backend.go @@ -51,20 +51,20 @@ func (b *backend) GetState(blkID ids.ID) (state.Chain, bool) { return b.state, blkID == b.state.GetLastAccepted() } -func (b *backend) getBlkWithOnAbortState(blkID ids.ID) (*blockState, bool) { +func (b *backend) getOnAbortState(blkID ids.ID) (state.Diff, bool) { state, ok := b.blkIDToState[blkID] if !ok || state.onAbortState == nil { return nil, false } - return state, true + return state.onAbortState, true } -func (b *backend) getBlkWithOnCommitState(blkID ids.ID) (*blockState, bool) { +func (b *backend) getOnCommitState(blkID ids.ID) (state.Diff, bool) { state, ok := b.blkIDToState[blkID] if !ok || state.onCommitState == nil { return nil, false } - return state, true + return state.onCommitState, true } func (b *backend) GetBlock(blkID ids.ID) (block.Block, error) { diff --git a/vms/platformvm/block/executor/block_state.go b/vms/platformvm/block/executor/block_state.go index 1386abd58bd4..abec214ae244 100644 --- a/vms/platformvm/block/executor/block_state.go +++ b/vms/platformvm/block/executor/block_state.go @@ -15,6 +15,7 @@ import ( type proposalBlockState struct { initiallyPreferCommit bool + onDecisionState state.Diff onCommitState state.Diff onAbortState state.Diff } diff --git a/vms/platformvm/block/executor/proposal_block_test.go b/vms/platformvm/block/executor/proposal_block_test.go index 18a1131b6126..610fdef0cc87 100644 --- a/vms/platformvm/block/executor/proposal_block_test.go +++ b/vms/platformvm/block/executor/proposal_block_test.go @@ -18,6 +18,7 @@ import ( "github.com/ava-labs/avalanchego/snow/consensus/snowman" "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" + "github.com/ava-labs/avalanchego/utils/timer/mockable" "github.com/ava-labs/avalanchego/vms/components/avax" "github.com/ava-labs/avalanchego/vms/platformvm/block" "github.com/ava-labs/avalanchego/vms/platformvm/reward" @@ -151,7 +152,8 @@ func TestBanffProposalBlockTimeVerification(t *testing.T) { require.NoError(shutdownEnvironment(env)) }() env.clk.Set(defaultGenesisTime) - env.config.BanffTime = time.Time{} // activate Banff + env.config.BanffTime = time.Time{} // activate Banff + env.config.DurangoTime = mockable.MaxTime // deactivate Durango // create parentBlock. It's a standard one for simplicity parentTime := defaultGenesisTime @@ -1335,3 +1337,170 @@ func TestBanffProposalBlockDelegatorStakers(t *testing.T) { vdrWeight = env.config.Validators.GetWeight(constants.PrimaryNetworkID, nodeID) require.Equal(env.config.MinDelegatorStake+env.config.MinValidatorStake, vdrWeight) } + +func TestAddValidatorProposalBlock(t *testing.T) { + require := require.New(t) + env := newEnvironment(t, nil) + defer func() { + require.NoError(shutdownEnvironment(env)) + }() + env.config.BanffTime = time.Time{} // activate Banff + env.config.DurangoTime = time.Time{} // activate Durango + + now := env.clk.Time() + + // Create validator tx + var ( + validatorStartTime = now.Add(2 * executor.SyncBound) + validatorEndTime = validatorStartTime.Add(env.config.MinStakeDuration) + nodeID = ids.GenerateTestNodeID() + ) + + addValidatorTx, err := env.txBuilder.NewAddValidatorTx( + env.config.MinValidatorStake, + uint64(validatorStartTime.Unix()), + uint64(validatorEndTime.Unix()), + nodeID, + preFundedKeys[0].PublicKey().Address(), + 10000, + []*secp256k1.PrivateKey{ + preFundedKeys[0], + preFundedKeys[1], + preFundedKeys[4], + }, + ids.ShortEmpty, + ) + require.NoError(err) + + // Add validator through a [StandardBlock] + preferredID := env.blkManager.Preferred() + preferred, err := env.blkManager.GetStatelessBlock(preferredID) + require.NoError(err) + + statelessBlk, err := block.NewBanffStandardBlock( + now.Add(executor.SyncBound), + preferredID, + preferred.Height()+1, + []*txs.Tx{addValidatorTx}, + ) + require.NoError(err) + blk := env.blkManager.NewBlock(statelessBlk) + require.NoError(blk.Verify(context.Background())) + require.NoError(blk.Accept(context.Background())) + require.True(env.blkManager.SetPreference(statelessBlk.ID())) + + // Should be pending + staker, err := env.state.GetPendingValidator(constants.PrimaryNetworkID, nodeID) + require.NoError(err) + require.NotNil(staker) + + // Promote validator from pending to current + env.clk.Set(validatorStartTime) + now = env.clk.Time() + + preferredID = env.blkManager.Preferred() + preferred, err = env.blkManager.GetStatelessBlock(preferredID) + require.NoError(err) + + statelessBlk, err = block.NewBanffStandardBlock( + now, + preferredID, + preferred.Height()+1, + nil, + ) + require.NoError(err) + blk = env.blkManager.NewBlock(statelessBlk) + require.NoError(blk.Verify(context.Background())) + require.NoError(blk.Accept(context.Background())) + require.True(env.blkManager.SetPreference(statelessBlk.ID())) + + // Should be current + staker, err = env.state.GetCurrentValidator(constants.PrimaryNetworkID, nodeID) + require.NoError(err) + require.NotNil(staker) + + // Advance time until next staker change time is [validatorEndTime] + for { + nextStakerChangeTime, err := executor.GetNextStakerChangeTime(env.state) + require.NoError(err) + if nextStakerChangeTime.Equal(validatorEndTime) { + break + } + + preferredID = env.blkManager.Preferred() + preferred, err = env.blkManager.GetStatelessBlock(preferredID) + require.NoError(err) + + statelessBlk, err = block.NewBanffStandardBlock( + nextStakerChangeTime, + preferredID, + preferred.Height()+1, + nil, + ) + require.NoError(err) + blk = env.blkManager.NewBlock(statelessBlk) + require.NoError(blk.Verify(context.Background())) + require.NoError(blk.Accept(context.Background())) + require.True(env.blkManager.SetPreference(statelessBlk.ID())) + } + + env.clk.Set(validatorEndTime) + now = env.clk.Time() + + // Create another validator tx + validatorStartTime = now.Add(2 * executor.SyncBound) + validatorEndTime = validatorStartTime.Add(env.config.MinStakeDuration) + nodeID = ids.GenerateTestNodeID() + + addValidatorTx2, err := env.txBuilder.NewAddValidatorTx( + env.config.MinValidatorStake, + uint64(validatorStartTime.Unix()), + uint64(validatorEndTime.Unix()), + nodeID, + preFundedKeys[0].PublicKey().Address(), + 10000, + []*secp256k1.PrivateKey{ + preFundedKeys[0], + preFundedKeys[1], + preFundedKeys[4], + }, + ids.ShortEmpty, + ) + require.NoError(err) + + // Add validator through a [ProposalBlock] and reward the last one + preferredID = env.blkManager.Preferred() + preferred, err = env.blkManager.GetStatelessBlock(preferredID) + require.NoError(err) + + rewardValidatorTx, err := env.txBuilder.NewRewardValidatorTx(addValidatorTx.ID()) + require.NoError(err) + + statelessProposalBlk, err := block.NewBanffProposalBlock( + now, + preferredID, + preferred.Height()+1, + rewardValidatorTx, + []*txs.Tx{addValidatorTx2}, + ) + require.NoError(err) + blk = env.blkManager.NewBlock(statelessProposalBlk) + require.NoError(blk.Verify(context.Background())) + + options, err := blk.(snowman.OracleBlock).Options(context.Background()) + require.NoError(err) + commitBlk := options[0] + require.NoError(commitBlk.Verify(context.Background())) + + require.NoError(blk.Accept(context.Background())) + require.NoError(commitBlk.Accept(context.Background())) + + // Should be pending + staker, err = env.state.GetPendingValidator(constants.PrimaryNetworkID, nodeID) + require.NoError(err) + require.NotNil(staker) + + rewardUTXOs, err := env.state.GetRewardUTXOs(addValidatorTx.ID()) + require.NoError(err) + require.NotEmpty(rewardUTXOs) +} diff --git a/vms/platformvm/block/executor/verifier.go b/vms/platformvm/block/executor/verifier.go index fd2ce999bd5f..4e81c8470ba3 100644 --- a/vms/platformvm/block/executor/verifier.go +++ b/vms/platformvm/block/executor/verifier.go @@ -50,7 +50,7 @@ func (v *verifier) BanffCommitBlock(b *block.BanffCommitBlock) error { } func (v *verifier) BanffProposalBlock(b *block.BanffProposalBlock) error { - if len(b.Transactions) != 0 { + if !v.txExecutorBackend.Config.IsDurangoActivated(b.Timestamp()) && len(b.Transactions) != 0 { return errBanffProposalBlockWithMultipleTransactions } @@ -59,11 +59,7 @@ func (v *verifier) BanffProposalBlock(b *block.BanffProposalBlock) error { } parentID := b.Parent() - onCommitState, err := state.NewDiff(parentID, v.backend) - if err != nil { - return err - } - onAbortState, err := state.NewDiff(parentID, v.backend) + onDecisionState, err := state.NewDiff(parentID, v.backend) if err != nil { return err } @@ -72,20 +68,40 @@ func (v *verifier) BanffProposalBlock(b *block.BanffProposalBlock) error { nextChainTime := b.Timestamp() changes, err := executor.AdvanceTimeTo( v.txExecutorBackend, - onCommitState, + onDecisionState, nextChainTime, ) if err != nil { return err } - onCommitState.SetTimestamp(nextChainTime) - changes.Apply(onCommitState) + onDecisionState.SetTimestamp(nextChainTime) + changes.Apply(onDecisionState) + + inputs, atomicRequests, onAcceptFunc, err := v.processStandardTxs(b.Transactions, onDecisionState, b.Parent()) + if err != nil { + return err + } - onAbortState.SetTimestamp(nextChainTime) - changes.Apply(onAbortState) + onCommitState, err := state.NewDiffOn(onDecisionState) + if err != nil { + return err + } - return v.proposalBlock(&b.ApricotProposalBlock, onCommitState, onAbortState) + onAbortState, err := state.NewDiffOn(onDecisionState) + if err != nil { + return err + } + + return v.proposalBlock( + &b.ApricotProposalBlock, + onDecisionState, + onCommitState, + onAbortState, + inputs, + atomicRequests, + onAcceptFunc, + ) } func (v *verifier) BanffStandardBlock(b *block.BanffStandardBlock) error { @@ -151,7 +167,7 @@ func (v *verifier) ApricotProposalBlock(b *block.ApricotProposalBlock) error { return err } - return v.proposalBlock(b, onCommitState, onAbortState) + return v.proposalBlock(b, nil, onCommitState, onAbortState, nil, nil, nil) } func (v *verifier) ApricotStandardBlock(b *block.ApricotStandardBlock) error { @@ -319,7 +335,7 @@ func (v *verifier) commonBlock(b block.Block) error { // abortBlock populates the state of this block if [nil] is returned func (v *verifier) abortBlock(b block.Block) error { parentID := b.Parent() - parentState, ok := v.getBlkWithOnAbortState(parentID) + onAbortState, ok := v.getOnAbortState(parentID) if !ok { return fmt.Errorf("%w: %s", state.ErrMissingParentState, parentID) } @@ -327,13 +343,8 @@ func (v *verifier) abortBlock(b block.Block) error { blkID := b.ID() v.blkIDToState[blkID] = &blockState{ statelessBlock: b, - - onAcceptState: parentState.onAbortState, - onAcceptFunc: parentState.onAcceptFunc, - - inputs: parentState.inputs, - timestamp: parentState.onAbortState.GetTimestamp(), - atomicRequests: parentState.atomicRequests, + onAcceptState: onAbortState, + timestamp: onAbortState.GetTimestamp(), } return nil } @@ -341,7 +352,7 @@ func (v *verifier) abortBlock(b block.Block) error { // commitBlock populates the state of this block if [nil] is returned func (v *verifier) commitBlock(b block.Block) error { parentID := b.Parent() - parentState, ok := v.getBlkWithOnCommitState(parentID) + onCommitState, ok := v.getOnCommitState(parentID) if !ok { return fmt.Errorf("%w: %s", state.ErrMissingParentState, parentID) } @@ -349,13 +360,8 @@ func (v *verifier) commitBlock(b block.Block) error { blkID := b.ID() v.blkIDToState[blkID] = &blockState{ statelessBlock: b, - - onAcceptState: parentState.onCommitState, - onAcceptFunc: parentState.onAcceptFunc, - - inputs: parentState.inputs, - timestamp: parentState.onCommitState.GetTimestamp(), - atomicRequests: parentState.atomicRequests, + onAcceptState: onCommitState, + timestamp: onCommitState.GetTimestamp(), } return nil } @@ -363,8 +369,12 @@ func (v *verifier) commitBlock(b block.Block) error { // proposalBlock populates the state of this block if [nil] is returned func (v *verifier) proposalBlock( b *block.ApricotProposalBlock, + onDecisionState state.Diff, onCommitState state.Diff, onAbortState state.Diff, + inputs set.Set[ids.ID], + atomicRequests map[ids.ID]*atomic.Requests, + onAcceptFunc func(), ) error { txExecutor := executor.ProposalTxExecutor{ OnCommitState: onCommitState, @@ -387,15 +397,22 @@ func (v *verifier) proposalBlock( blkID := b.ID() v.blkIDToState[blkID] = &blockState{ proposalBlockState: proposalBlockState{ + onDecisionState: onDecisionState, onCommitState: onCommitState, onAbortState: onAbortState, initiallyPreferCommit: txExecutor.PrefersCommit, }, + statelessBlock: b, + + onAcceptFunc: onAcceptFunc, + + inputs: inputs, // It is safe to use [b.onAbortState] here because the timestamp will // never be modified by an Apricot Abort block and the timestamp will // always be the same as the Banff Proposal Block. - timestamp: onAbortState.GetTimestamp(), + timestamp: onAbortState.GetTimestamp(), + atomicRequests: atomicRequests, } return nil } diff --git a/vms/platformvm/block/executor/verifier_test.go b/vms/platformvm/block/executor/verifier_test.go index e4095f6b4f77..6d4e32e0bee5 100644 --- a/vms/platformvm/block/executor/verifier_test.go +++ b/vms/platformvm/block/executor/verifier_test.go @@ -311,6 +311,7 @@ func TestVerifierVisitCommitBlock(t *testing.T) { mempool := mempool.NewMockMempool(ctrl) parentID := ids.GenerateTestID() parentStatelessBlk := block.NewMockBlock(ctrl) + parentOnDecisionState := state.NewMockDiff(ctrl) parentOnCommitState := state.NewMockDiff(ctrl) parentOnAbortState := state.NewMockDiff(ctrl) @@ -319,8 +320,9 @@ func TestVerifierVisitCommitBlock(t *testing.T) { parentID: { statelessBlock: parentStatelessBlk, proposalBlockState: proposalBlockState{ - onCommitState: parentOnCommitState, - onAbortState: parentOnAbortState, + onDecisionState: parentOnDecisionState, + onCommitState: parentOnCommitState, + onAbortState: parentOnAbortState, }, }, }, @@ -380,6 +382,7 @@ func TestVerifierVisitAbortBlock(t *testing.T) { mempool := mempool.NewMockMempool(ctrl) parentID := ids.GenerateTestID() parentStatelessBlk := block.NewMockBlock(ctrl) + parentOnDecisionState := state.NewMockDiff(ctrl) parentOnCommitState := state.NewMockDiff(ctrl) parentOnAbortState := state.NewMockDiff(ctrl) @@ -388,8 +391,9 @@ func TestVerifierVisitAbortBlock(t *testing.T) { parentID: { statelessBlock: parentStatelessBlk, proposalBlockState: proposalBlockState{ - onCommitState: parentOnCommitState, - onAbortState: parentOnAbortState, + onDecisionState: parentOnDecisionState, + onCommitState: parentOnCommitState, + onAbortState: parentOnAbortState, }, }, }, @@ -547,9 +551,11 @@ func TestBanffAbortBlockTimestampChecks(t *testing.T) { // setup parent state parentTime := defaultGenesisTime - s.EXPECT().GetLastAccepted().Return(parentID).Times(2) - s.EXPECT().GetTimestamp().Return(parentTime).Times(2) + s.EXPECT().GetLastAccepted().Return(parentID).Times(3) + s.EXPECT().GetTimestamp().Return(parentTime).Times(3) + onDecisionState, err := state.NewDiff(parentID, backend) + require.NoError(err) onCommitState, err := state.NewDiff(parentID, backend) require.NoError(err) onAbortState, err := state.NewDiff(parentID, backend) @@ -558,8 +564,9 @@ func TestBanffAbortBlockTimestampChecks(t *testing.T) { timestamp: test.parentTime, statelessBlock: parentStatelessBlk, proposalBlockState: proposalBlockState{ - onCommitState: onCommitState, - onAbortState: onAbortState, + onDecisionState: onDecisionState, + onCommitState: onCommitState, + onAbortState: onAbortState, }, } @@ -640,9 +647,11 @@ func TestBanffCommitBlockTimestampChecks(t *testing.T) { // setup parent state parentTime := defaultGenesisTime - s.EXPECT().GetLastAccepted().Return(parentID).Times(2) - s.EXPECT().GetTimestamp().Return(parentTime).Times(2) + s.EXPECT().GetLastAccepted().Return(parentID).Times(3) + s.EXPECT().GetTimestamp().Return(parentTime).Times(3) + onDecisionState, err := state.NewDiff(parentID, backend) + require.NoError(err) onCommitState, err := state.NewDiff(parentID, backend) require.NoError(err) onAbortState, err := state.NewDiff(parentID, backend) @@ -651,8 +660,9 @@ func TestBanffCommitBlockTimestampChecks(t *testing.T) { timestamp: test.parentTime, statelessBlock: parentStatelessBlk, proposalBlockState: proposalBlockState{ - onCommitState: onCommitState, - onAbortState: onAbortState, + onDecisionState: onDecisionState, + onCommitState: onCommitState, + onAbortState: onAbortState, }, } diff --git a/vms/platformvm/state/diff.go b/vms/platformvm/state/diff.go index d509fa69e0dd..bb308c9c28e2 100644 --- a/vms/platformvm/state/diff.go +++ b/vms/platformvm/state/diff.go @@ -17,7 +17,8 @@ import ( ) var ( - _ Diff = (*diff)(nil) + _ Diff = (*diff)(nil) + _ Versions = stateGetter{} ErrMissingParentState = errors.New("missing parent state") ) @@ -74,6 +75,20 @@ func NewDiff( }, nil } +type stateGetter struct { + state Chain +} + +func (s stateGetter) GetState(ids.ID) (Chain, bool) { + return s.state, true +} + +func NewDiffOn(parentState Chain) (Diff, error) { + return NewDiff(ids.Empty, stateGetter{ + state: parentState, + }) +} + func (d *diff) GetTimestamp() time.Time { return d.timestamp } diff --git a/vms/platformvm/state/diff_test.go b/vms/platformvm/state/diff_test.go index 50c87b2d3a53..e9eecdb182cb 100644 --- a/vms/platformvm/state/diff_test.go +++ b/vms/platformvm/state/diff_test.go @@ -637,7 +637,7 @@ func TestDiffStacking(t *testing.T) { require.Equal(owner1, owner) // Create a second diff on first diff and verify that subnet owner returns correctly - stackedDiff, err := wrapState(statesDiff) + stackedDiff, err := NewDiffOn(statesDiff) require.NoError(err) owner, err = stackedDiff.GetSubnetOwner(subnetID) require.NoError(err) @@ -674,17 +674,3 @@ func TestDiffStacking(t *testing.T) { require.NoError(err) require.Equal(owner3, owner) } - -type stateGetter struct { - state Chain -} - -func (s stateGetter) GetState(ids.ID) (Chain, bool) { - return s.state, true -} - -func wrapState(parentState Chain) (Diff, error) { - return NewDiff(ids.Empty, stateGetter{ - state: parentState, - }) -}