From 555f6d38e89cc5a9be7dc369ad0dda0156b91b21 Mon Sep 17 00:00:00 2001 From: William Banfield Date: Fri, 18 Feb 2022 19:15:16 -0500 Subject: [PATCH] implement line 22 --- internal/consensus/stepper/stepper.go | 19 ++- internal/consensus/stepper/stepper_test.go | 135 +++++++++++++++++++++ internal/consensus/types/round_state.go | 1 + 3 files changed, 149 insertions(+), 6 deletions(-) create mode 100644 internal/consensus/stepper/stepper_test.go diff --git a/internal/consensus/stepper/stepper.go b/internal/consensus/stepper/stepper.go index dd5219a4c..855da06ba 100644 --- a/internal/consensus/stepper/stepper.go +++ b/internal/consensus/stepper/stepper.go @@ -1,8 +1,10 @@ package stepper import ( + "context" "errors" + "github.com/tendermint/tendermint/internal/consensus" "github.com/tendermint/tendermint/internal/consensus/types" ) @@ -11,11 +13,13 @@ var ( ) type ( - Transition func(types.RoundState) types.RoundState + Transition func(types.RoundState) (types.RoundState, consensus.Message) Predicate func(types.RoundState) bool ) -var emptyTransition = func(types.RoundState) types.RoundState { return types.RoundState{} } +var emptyTransition = func(types.RoundState) (types.RoundState, consensus.Message) { + return types.RoundState{}, &consensus.VoteMessage{} +} type Operation struct { P Predicate @@ -30,16 +34,19 @@ func New(ops []Operation) stepper { return stepper{ops: ops} } -func (s *stepper) Next(state types.RoundState) (types.RoundState, error) { +func (s *stepper) Next(ctx context.Context, state types.RoundState) (types.RoundState, consensus.Message, error) { if t, ok := s.pickTransition(state); ok { - return t(state), nil + s, msg := t(state) + return s, msg, nil } - return types.RoundState{}, ErrNoValidTransition + return types.RoundState{}, nil, ErrNoValidTransition } func (s *stepper) pickTransition(state types.RoundState) (Transition, bool) { for _, op := range s.ops { - return op.T, true + if op.P(state) { + return op.T, true + } } return emptyTransition, false } diff --git a/internal/consensus/stepper/stepper_test.go b/internal/consensus/stepper/stepper_test.go new file mode 100644 index 000000000..a06d682c2 --- /dev/null +++ b/internal/consensus/stepper/stepper_test.go @@ -0,0 +1,135 @@ +package stepper_test + +import ( + "bytes" + "context" + "testing" + + "github.com/tendermint/tendermint/internal/consensus" + "github.com/tendermint/tendermint/internal/consensus/stepper" + "github.com/tendermint/tendermint/internal/consensus/types" + tmtypes "github.com/tendermint/tendermint/types" +) + +func TestRunStep(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + wasCalled := false + alwaysTrue := func(types.RoundState) bool { + return true + } + emptyTransition := func(types.RoundState) (types.RoundState, consensus.Message) { + wasCalled = true + return types.RoundState{}, &consensus.VoteMessage{} + } + + ops := []stepper.Operation{ + { + P: alwaysTrue, + T: emptyTransition, + }, + } + s := stepper.New(ops) + _, _, err := s.Next(ctx, types.RoundState{}) + if err != nil { + t.Fatalf("unexepected error from Next %v", err) + } + if !wasCalled { + t.Fatal("expected transition to be call") + } +} + +var line22Predicate = func(s types.RoundState) bool { + return s.Step == types.RoundStepPropose && + s.ProposalBlockParts.IsComplete() && + s.Proposal.POLRound == -1 && + s.Proposal.Round == s.Round && + s.Proposal.Height == s.Height && + bytes.Equal(s.Validators.GetProposer().Address, s.ProposalBlock.ProposerAddress) +} + +var line22Transition = func(s types.RoundState) (types.RoundState, consensus.Message) { + msg := &consensus.VoteMessage{ + Vote: &tmtypes.Vote{ + Height: s.Height, + Round: s.Round, + BlockID: tmtypes.BlockID{}, + }, + } + if valid(s.ProposalBlock) && (s.LockedRound == -1 || s.LockedBlock != nil && s.LockedBlock.HashesTo(s.ProposalBlock.Hash())) { + msg = &consensus.VoteMessage{ + Vote: &tmtypes.Vote{ + Height: s.Height, + Round: s.Round, + BlockID: tmtypes.BlockID{Hash: s.ProposalBlockParts.Hash(), PartSetHeader: s.ProposalBlockParts.Header()}, + }, + } + } + s.Step = types.RoundStepPrevote + return s, msg +} + +func valid(b *tmtypes.Block) bool { + return true +} + +func TestLine22(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + ops := []stepper.Operation{ + { + P: line22Predicate, + T: line22Transition, + }, + } + s := stepper.New(ops) + + addr := []byte("proposer") + r := types.RoundState{ + Round: 1, + Height: 1, + LockedRound: -1, + Step: types.RoundStepPropose, + Proposal: &tmtypes.Proposal{ + POLRound: -1, + Round: 1, + Height: 1, + }, + ProposalBlockParts: tmtypes.NewPartSetFromData([]byte("part set"), 5), + ProposalBlock: &tmtypes.Block{ + Header: tmtypes.Header{ + ProposerAddress: addr, + }, + }, + Validators: &tmtypes.ValidatorSet{ + Validators: []*tmtypes.Validator{ + { + Address: addr, + }, + }, + Proposer: &tmtypes.Validator{ + Address: addr, + }, + }, + } + + r, msg, err := s.Next(ctx, r) + if err != nil { + t.Fatalf("unexepected error from Next %v", err) + } + if msg == nil { + t.Fatalf("expected message to not be nil") + } + + if vote, ok := msg.(*consensus.VoteMessage); ok { + if vote.Vote.BlockID.IsNil() { + t.Fatalf("expected vote to be for block") + } + } else { + t.Fatalf("expected message to not be vote") + } + if r.Step != types.RoundStepPrevote { + t.Fatalf("expected step to be %v but saw %v", types.RoundStepPrevote, r.Step) + } +} diff --git a/internal/consensus/types/round_state.go b/internal/consensus/types/round_state.go index b1cf30f38..3e834a252 100644 --- a/internal/consensus/types/round_state.go +++ b/internal/consensus/types/round_state.go @@ -68,6 +68,7 @@ type RoundState struct { Height int64 `json:"height,string"` // Height we are working on Round int32 `json:"round"` Step RoundStepType `json:"step"` + StepTime time.Time `json:"step_time"` StartTime time.Time `json:"start_time"` // Subjective time when +2/3 precommits for Block at Round were found