Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vms/platformvm: Permit usage of the Transactions field in BanffProposalBlock #2451

Merged
merged 25 commits into from
Dec 12, 2023
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions vms/platformvm/block/executor/block_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type proposalBlockState struct {
initiallyPreferCommit bool
onCommitState state.Diff
onAbortState state.Diff
onDecisionState state.Diff
}

// The state of a block.
Expand Down
171 changes: 170 additions & 1 deletion vms/platformvm/block/executor/proposal_block_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
57 changes: 53 additions & 4 deletions vms/platformvm/block/executor/verifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was certainly forward thinking of us.

if !v.txExecutorBackend.Config.IsDurangoActivated(b.Timestamp()) && len(b.Transactions) != 0 {
return errBanffProposalBlockWithMultipleTransactions
}

Expand Down Expand Up @@ -85,7 +85,23 @@ func (v *verifier) BanffProposalBlock(b *block.BanffProposalBlock) error {
onAbortState.SetTimestamp(nextChainTime)
changes.Apply(onAbortState)

return v.proposalBlock(&b.ApricotProposalBlock, onCommitState, onAbortState)
// Apply the changes, if any, from processing the decision txs.
// [onCommitState] = [onAbortState] here, either one can be used. It is
// only used to ensure that [onDecisionState] contains only the change diff
// of all the standard txs *after* the timestamp advancement changes are
// applied. [onDecisionState] will be applied to [onCommitState] or
// [onAbortState] depending on if the proposal is committed or aborted.
onDecisionState, err := wrapState(onCommitState)
patrick-ogrady marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return err
}

inputs, atomicRequests, onAcceptFunc, err := v.processStandardTxs(b.Transactions, onDecisionState, b.Parent())
if err != nil {
return err
}

return v.proposalBlock(&b.ApricotProposalBlock, onDecisionState, onCommitState, onAbortState, inputs, atomicRequests, onAcceptFunc)
}

func (v *verifier) BanffStandardBlock(b *block.BanffStandardBlock) error {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -324,6 +340,10 @@ func (v *verifier) abortBlock(b block.Block) error {
return fmt.Errorf("%w: %s", state.ErrMissingParentState, parentID)
}

if err := parentState.onDecisionState.Apply(parentState.onAbortState); err != nil {
return err
}

blkID := b.ID()
v.blkIDToState[blkID] = &blockState{
statelessBlock: b,
Expand All @@ -346,6 +366,10 @@ func (v *verifier) commitBlock(b block.Block) error {
return fmt.Errorf("%w: %s", state.ErrMissingParentState, parentID)
}

if err := parentState.onDecisionState.Apply(parentState.onCommitState); err != nil {
return err
}

blkID := b.ID()
v.blkIDToState[blkID] = &blockState{
statelessBlock: b,
Expand All @@ -363,8 +387,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,
Expand All @@ -387,15 +415,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
}
Expand Down Expand Up @@ -490,3 +525,17 @@ func (v *verifier) processStandardTxs(txs []*txs.Tx, state state.Diff, parentID

return inputs, atomicRequests, onAcceptFunc, 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,
})
}
Loading
Loading