diff --git a/go.mod b/go.mod index bd4fdb3e..7bdcb7b6 100644 --- a/go.mod +++ b/go.mod @@ -6,8 +6,7 @@ require ( github.com/ethereum/go-ethereum v1.9.5 github.com/onsi/ginkgo v1.11.0 github.com/onsi/gomega v1.8.1 - github.com/renproject/abi v0.4.1 // indirect github.com/renproject/id v0.3.3 - github.com/renproject/surge v1.1.3 + github.com/renproject/surge v1.1.5 github.com/sirupsen/logrus v1.4.2 ) diff --git a/go.sum b/go.sum index 04f95e4b..0dbc144a 100644 --- a/go.sum +++ b/go.sum @@ -133,6 +133,8 @@ github.com/renproject/surge v1.1.2 h1:Yy3pTlRyaMJGLfn64JHgCnWs3cWbRJjE+aFxZXRGfW github.com/renproject/surge v1.1.2/go.mod h1:UnnFYpLSD0T9MzCcyHjbNdmxiQsDVyBDCuqcbhcaLCY= github.com/renproject/surge v1.1.3 h1:nCN3yWUbIbSDWyMaU6aCIidCE15yEcZb8Bcuziog/wU= github.com/renproject/surge v1.1.3/go.mod h1:UnnFYpLSD0T9MzCcyHjbNdmxiQsDVyBDCuqcbhcaLCY= +github.com/renproject/surge v1.1.5 h1:uoup398vYr7NYYWFwes4Hmsa6AMFujfUEhYZKZ0nuCs= +github.com/renproject/surge v1.1.5/go.mod h1:UnnFYpLSD0T9MzCcyHjbNdmxiQsDVyBDCuqcbhcaLCY= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= diff --git a/process/message_test.go b/process/message_test.go index 5a8536ab..c77b7d78 100644 --- a/process/message_test.go +++ b/process/message_test.go @@ -1 +1,205 @@ -package process_test \ No newline at end of file +package process_test + +import ( + "testing/quick" + + "github.com/renproject/hyperdrive/process" + "github.com/renproject/id" + "github.com/renproject/surge" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Propose", func() { + Context("when unmarshaling fuzz", func() { + It("should not panic", func() { + f := func(fuzz []byte) bool { + msg := process.Propose{} + Expect(surge.FromBinary(fuzz, &msg)).ToNot(Succeed()) + return true + } + Expect(quick.Check(f, nil)).To(Succeed()) + }) + }) + + Context("when marshaling and then unmarshaling", func() { + It("should equal itself", func() { + f := func(height process.Height, round, validRound process.Round, value process.Value, from id.Signatory, signature id.Signature) bool { + expected := process.Propose{ + Height: height, + Round: round, + ValidRound: validRound, + Value: value, + From: from, + Signature: signature, + } + data, err := surge.ToBinary(expected) + Expect(err).ToNot(HaveOccurred()) + got := process.Propose{} + err = surge.FromBinary(data, &got) + Expect(err).ToNot(HaveOccurred()) + Expect(got.Equal(&expected)).To(BeTrue()) + return true + } + Expect(quick.Check(f, nil)).To(Succeed()) + }) + }) + + Context("when compute the hash", func() { + It("should not be random", func() { + f := func(height process.Height, round, validRound process.Round, value process.Value) bool { + expected, err := process.NewProposeHash(height, round, validRound, value) + Expect(err).ToNot(HaveOccurred()) + got, err := process.NewProposeHash(height, round, validRound, value) + Expect(err).ToNot(HaveOccurred()) + Expect(got.Equal(&expected)).To(BeTrue()) + return true + } + Expect(quick.Check(f, nil)).To(Succeed()) + }) + + It("should the expected signatory", func() { + f := func(height process.Height, round, validRound process.Round, value process.Value) bool { + privKey := id.NewPrivKey() + hash, err := process.NewProposeHash(height, round, validRound, value) + Expect(err).ToNot(HaveOccurred()) + signature, err := privKey.Sign(&hash) + Expect(err).ToNot(HaveOccurred()) + signatory, err := signature.Signatory(&hash) + Expect(err).ToNot(HaveOccurred()) + Expect(privKey.Signatory().Equal(&signatory)).To(BeTrue()) + return true + } + Expect(quick.Check(f, nil)).To(Succeed()) + }) + }) +}) + +var _ = Describe("Prevote", func() { + Context("when unmarshaling fuzz", func() { + It("should not panic", func() { + f := func(fuzz []byte) bool { + msg := process.Prevote{} + Expect(surge.FromBinary(fuzz, &msg)).ToNot(Succeed()) + return true + } + Expect(quick.Check(f, nil)).To(Succeed()) + }) + }) + + Context("when marshaling and then unmarshaling", func() { + It("should equal itself", func() { + f := func(height process.Height, round process.Round, value process.Value, from id.Signatory, signature id.Signature) bool { + expected := process.Prevote{ + Height: height, + Round: round, + Value: value, + From: from, + Signature: signature, + } + data, err := surge.ToBinary(expected) + Expect(err).ToNot(HaveOccurred()) + got := process.Prevote{} + err = surge.FromBinary(data, &got) + Expect(err).ToNot(HaveOccurred()) + Expect(got.Equal(&expected)).To(BeTrue()) + return true + } + Expect(quick.Check(f, nil)).To(Succeed()) + }) + }) + + Context("when compute the hash", func() { + It("should not be random", func() { + f := func(height process.Height, round process.Round, value process.Value) bool { + expected, err := process.NewPrevoteHash(height, round, value) + Expect(err).ToNot(HaveOccurred()) + got, err := process.NewPrevoteHash(height, round, value) + Expect(err).ToNot(HaveOccurred()) + Expect(got.Equal(&expected)).To(BeTrue()) + return true + } + Expect(quick.Check(f, nil)).To(Succeed()) + }) + + It("should the expected signatory", func() { + f := func(height process.Height, round process.Round, value process.Value) bool { + privKey := id.NewPrivKey() + hash, err := process.NewPrevoteHash(height, round, value) + Expect(err).ToNot(HaveOccurred()) + signature, err := privKey.Sign(&hash) + Expect(err).ToNot(HaveOccurred()) + signatory, err := signature.Signatory(&hash) + Expect(err).ToNot(HaveOccurred()) + Expect(privKey.Signatory().Equal(&signatory)).To(BeTrue()) + return true + } + Expect(quick.Check(f, nil)).To(Succeed()) + }) + }) +}) + +var _ = Describe("Precommit", func() { + Context("when unmarshaling fuzz", func() { + It("should not panic", func() { + f := func(fuzz []byte) bool { + msg := process.Precommit{} + Expect(surge.FromBinary(fuzz, &msg)).ToNot(Succeed()) + return true + } + Expect(quick.Check(f, nil)).To(Succeed()) + }) + }) + + Context("when marshaling and then unmarshaling", func() { + It("should equal itself", func() { + f := func(height process.Height, round process.Round, value process.Value, from id.Signatory, signature id.Signature) bool { + expected := process.Precommit{ + Height: height, + Round: round, + Value: value, + From: from, + Signature: signature, + } + data, err := surge.ToBinary(expected) + Expect(err).ToNot(HaveOccurred()) + got := process.Precommit{} + err = surge.FromBinary(data, &got) + Expect(err).ToNot(HaveOccurred()) + Expect(got.Equal(&expected)).To(BeTrue()) + return true + } + Expect(quick.Check(f, nil)).To(Succeed()) + }) + }) + + Context("when compute the hash", func() { + It("should not be random", func() { + f := func(height process.Height, round process.Round, value process.Value) bool { + expected, err := process.NewPrecommitHash(height, round, value) + Expect(err).ToNot(HaveOccurred()) + got, err := process.NewPrecommitHash(height, round, value) + Expect(err).ToNot(HaveOccurred()) + Expect(got.Equal(&expected)).To(BeTrue()) + return true + } + Expect(quick.Check(f, nil)).To(Succeed()) + }) + + It("should the expected signatory", func() { + f := func(height process.Height, round process.Round, value process.Value) bool { + privKey := id.NewPrivKey() + hash, err := process.NewPrecommitHash(height, round, value) + Expect(err).ToNot(HaveOccurred()) + signature, err := privKey.Sign(&hash) + Expect(err).ToNot(HaveOccurred()) + signatory, err := signature.Signatory(&hash) + Expect(err).ToNot(HaveOccurred()) + Expect(privKey.Signatory().Equal(&signatory)).To(BeTrue()) + return true + } + Expect(quick.Check(f, nil)).To(Succeed()) + }) + }) +}) diff --git a/process/process.go b/process/process.go index f184c9e7..f5da8102 100644 --- a/process/process.go +++ b/process/process.go @@ -115,14 +115,6 @@ type Process struct { // State of the Process. State `json:"state"` - // ProposeLogs store the Proposes for all Rounds. - ProposeLogs map[Round]Propose `json:"proposeLogs"` - // PrevoteLogs store the Prevotes for all Processes in all Rounds. - PrevoteLogs map[Round]map[id.Signatory]Prevote `json:"prevoteLogs"` - // PrecommitLogs store the Precommits for all Processes in all Rounds. - PrecommitLogs map[Round]map[id.Signatory]Precommit `json:"precommitLogs"` - // OnceFlags prevents events from happening more than once. - OnceFlags map[Round]OnceFlag `json:"onceFlags"` } // New returns a new Process that is in the default State with empty message @@ -151,22 +143,14 @@ func New( committer: committer, catcher: catcher, - State: DefaultState(), - ProposeLogs: make(map[Round]Propose), - PrevoteLogs: make(map[Round]map[id.Signatory]Prevote), - PrecommitLogs: make(map[Round]map[id.Signatory]Precommit), - OnceFlags: make(map[Round]OnceFlag), + State: DefaultState(), } } // SizeHint returns the number of bytes required to represent this Process in // binary. func (p Process) SizeHint() int { - return surge.SizeHint(p.State) + - surge.SizeHint(p.ProposeLogs) + - surge.SizeHint(p.PrevoteLogs) + - surge.SizeHint(p.PrecommitLogs) + - surge.SizeHint(p.OnceFlags) + return surge.SizeHint(p.State) } // Marshal this Process into binary. @@ -175,22 +159,6 @@ func (p Process) Marshal(w io.Writer, m int) (int, error) { if err != nil { return m, fmt.Errorf("marshaling state: %v", err) } - m, err = surge.Marshal(w, p.ProposeLogs, m) - if err != nil { - return m, fmt.Errorf("marshaling propose logs: %v", err) - } - m, err = surge.Marshal(w, p.PrevoteLogs, m) - if err != nil { - return m, fmt.Errorf("marshaling prevote logs: %v", err) - } - m, err = surge.Marshal(w, p.PrecommitLogs, m) - if err != nil { - return m, fmt.Errorf("marshaling precommit logs: %v", err) - } - m, err = surge.Marshal(w, p.OnceFlags, m) - if err != nil { - return m, fmt.Errorf("marshaling once flags: %v", err) - } return m, nil } @@ -200,22 +168,6 @@ func (p *Process) Unmarshal(r io.Reader, m int) (int, error) { if err != nil { return m, fmt.Errorf("unmarshaling state: %v", err) } - m, err = surge.Unmarshal(r, &p.ProposeLogs, m) - if err != nil { - return m, fmt.Errorf("unmarshaling propose logs: %v", err) - } - m, err = surge.Unmarshal(r, &p.PrevoteLogs, m) - if err != nil { - return m, fmt.Errorf("unmarshaling prevote logs: %v", err) - } - m, err = surge.Unmarshal(r, &p.PrecommitLogs, m) - if err != nil { - return m, fmt.Errorf("unmarshaling precommit logs: %v", err) - } - m, err = surge.Unmarshal(r, &p.OnceFlags, m) - if err != nil { - return m, fmt.Errorf("unmarshaling once flags: %v", err) - } return m, nil } diff --git a/process/process_test.go b/process/process_test.go index 05f732af..e1f83e83 100644 --- a/process/process_test.go +++ b/process/process_test.go @@ -1 +1,51 @@ package process_test + +import ( + "math/rand" + "testing/quick" + "time" + + "github.com/renproject/hyperdrive/process" + "github.com/renproject/hyperdrive/process/processutil" + "github.com/renproject/id" + "github.com/renproject/surge" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Process", func() { + Context("when unmarshaling fuzz", func() { + It("should not panic", func() { + f := func(fuzz []byte) bool { + msg := process.Process{} + Expect(surge.FromBinary(fuzz, &msg)).ToNot(Succeed()) + return true + } + Expect(quick.Check(f, nil)).To(Succeed()) + }) + }) + + Context("when marshaling and then unmarshaling", func() { + It("should equal itself", func() { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + f := func(proposeLogs map[process.Round]process.Propose, prevoteLogs map[process.Round]map[id.Signatory]process.Prevote, precommitLogs map[process.Round]map[id.Signatory]process.Precommit, onceFlags map[process.Round]process.OnceFlag) bool { + expected := process.Process{ + State: processutil.RandomState(r), + } + expected.ProposeLogs = proposeLogs + expected.PrevoteLogs = prevoteLogs + expected.PrecommitLogs = precommitLogs + expected.OnceFlags = onceFlags + data, err := surge.ToBinary(expected) + Expect(err).ToNot(HaveOccurred()) + got := process.Process{} + err = surge.FromBinary(data, &got) + Expect(err).ToNot(HaveOccurred()) + Expect(got.State.Equal(&expected.State)).To(BeTrue()) + return true + } + Expect(quick.Check(f, nil)).To(Succeed()) + }) + }) +}) diff --git a/process/processutil/processutil.go b/process/processutil/processutil.go new file mode 100644 index 00000000..1e4b13a7 --- /dev/null +++ b/process/processutil/processutil.go @@ -0,0 +1,236 @@ +package processutil + +import ( + "math/rand" + + "github.com/renproject/hyperdrive/process" + "github.com/renproject/id" +) + +func RandomHeight(r *rand.Rand) process.Height { + switch r.Int() % 10 { + case 0: + return process.Height(-1) + case 1: + return process.Height(0) + case 2: + return process.Height(9223372036854775807) + default: + return process.Height(r.Int63()) + } +} + +func RandomRound(r *rand.Rand) process.Round { + switch r.Int() % 10 { + case 0: + return process.Round(-1) + case 1: + return process.Round(0) + case 2: + return process.Round(9223372036854775807) + default: + return process.Round(r.Int63()) + } +} + +func RandomStep(r *rand.Rand) process.Step { + switch r.Int() % 10 { + case 0: + return process.Proposing + case 1: + return process.Prevoting + case 2: + return process.Prevoting + case 3: + return process.Step(255) + default: + return process.Step(uint8(r.Int63())) + } +} + +func RandomValue(r *rand.Rand) process.Value { + switch r.Int() % 10 { + case 0: + return process.Value{} + case 1: + return process.Value{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} + default: + v := process.Value{} + for i := range v { + v[i] = byte(r.Int()) + } + return v + } +} + +func RandomState(r *rand.Rand) process.State { + switch r.Int() % 10 { + case 0: + return process.DefaultState() + default: + return process.State{ + CurrentHeight: RandomHeight(r), + CurrentRound: RandomRound(r), + CurrentStep: RandomStep(r), + LockedRound: RandomRound(r), + LockedValue: RandomValue(r), + ValidRound: RandomRound(r), + ValidValue: RandomValue(r), + + ProposeLogs: make(map[process.Round]process.Propose), + PrevoteLogs: make(map[process.Round]map[id.Signatory]process.Prevote), + PrecommitLogs: make(map[process.Round]map[id.Signatory]process.Precommit), + OnceFlags: make(map[process.Round]process.OnceFlag), + } + } +} + +func RandomPropose(r *rand.Rand) process.Propose { + switch r.Int() % 10 { + case 0: + return process.Propose{} + case 1: + return process.Propose{ + Height: RandomHeight(r), + Round: RandomRound(r), + ValidRound: RandomRound(r), + Value: RandomValue(r), + From: id.Signatory{}, + Signature: id.Signature{}, + } + case 2: + signatory := id.Signatory{} + for i := range signatory { + signatory[i] = byte(r.Int()) + } + signature := id.Signature{} + for i := range signature { + signature[i] = byte(r.Int()) + } + return process.Propose{ + Height: RandomHeight(r), + Round: RandomRound(r), + ValidRound: RandomRound(r), + Value: RandomValue(r), + From: signatory, + Signature: signature, + } + default: + msg := process.Propose{ + Height: RandomHeight(r), + Round: RandomRound(r), + ValidRound: RandomRound(r), + Value: RandomValue(r), + } + privKey := id.NewPrivKey() + hash, err := process.NewProposeHash(msg.Height, msg.Round, msg.ValidRound, msg.Value) + if err != nil { + panic(err) + } + signature, err := privKey.Sign(&hash) + if err != nil { + panic(err) + } + msg.From = privKey.Signatory() + msg.Signature = signature + return msg + } +} + +func RandomPrevote(r *rand.Rand) process.Prevote { + switch r.Int() % 10 { + case 0: + return process.Prevote{} + case 1: + return process.Prevote{ + Height: RandomHeight(r), + Round: RandomRound(r), + Value: RandomValue(r), + From: id.Signatory{}, + Signature: id.Signature{}, + } + case 2: + signatory := id.Signatory{} + for i := range signatory { + signatory[i] = byte(r.Int()) + } + signature := id.Signature{} + for i := range signature { + signature[i] = byte(r.Int()) + } + return process.Prevote{ + Height: RandomHeight(r), + Round: RandomRound(r), + Value: RandomValue(r), + From: signatory, + Signature: signature, + } + default: + msg := process.Prevote{ + Height: RandomHeight(r), + Round: RandomRound(r), + Value: RandomValue(r), + } + privKey := id.NewPrivKey() + hash, err := process.NewPrevoteHash(msg.Height, msg.Round, msg.Value) + if err != nil { + panic(err) + } + signature, err := privKey.Sign(&hash) + if err != nil { + panic(err) + } + msg.From = privKey.Signatory() + msg.Signature = signature + return msg + } +} + +func RandomPrecommit(r *rand.Rand) process.Precommit { + switch r.Int() % 10 { + case 0: + return process.Precommit{} + case 1: + return process.Precommit{ + Height: RandomHeight(r), + Round: RandomRound(r), + Value: RandomValue(r), + From: id.Signatory{}, + Signature: id.Signature{}, + } + case 2: + signatory := id.Signatory{} + for i := range signatory { + signatory[i] = byte(r.Int()) + } + signature := id.Signature{} + for i := range signature { + signature[i] = byte(r.Int()) + } + return process.Precommit{ + Height: RandomHeight(r), + Round: RandomRound(r), + Value: RandomValue(r), + From: signatory, + Signature: signature, + } + default: + msg := process.Precommit{ + Height: RandomHeight(r), + Round: RandomRound(r), + Value: RandomValue(r), + } + privKey := id.NewPrivKey() + hash, err := process.NewPrecommitHash(msg.Height, msg.Round, msg.Value) + if err != nil { + panic(err) + } + signature, err := privKey.Sign(&hash) + if err != nil { + panic(err) + } + msg.From = privKey.Signatory() + msg.Signature = signature + return msg + } +} diff --git a/process/processutil/processutil_suite_test.go b/process/processutil/processutil_suite_test.go new file mode 100644 index 00000000..e68ea63f --- /dev/null +++ b/process/processutil/processutil_suite_test.go @@ -0,0 +1,13 @@ +package processutil_test + +import ( + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestProcessutil(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Process Utilities Suite") +} diff --git a/process/processutil/processutil_test.go b/process/processutil/processutil_test.go new file mode 100644 index 00000000..c7991ea6 --- /dev/null +++ b/process/processutil/processutil_test.go @@ -0,0 +1,86 @@ +package processutil_test + +import ( + "math/rand" + "time" + + "github.com/renproject/hyperdrive/process" + "github.com/renproject/hyperdrive/process/processutil" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Process utilities", func() { + Context("when generating random proposes", func() { + It("should not generate the same propose multiple times", func() { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + proposes := make([]process.Propose, 100) + for i := range proposes { + proposes[i] = processutil.RandomPropose(r) + } + all := true + for i := range proposes { + if !proposes[0].Equal(&proposes[i]) { + all = false + break + } + } + Expect(all).To(BeFalse()) + }) + }) + + Context("when generating random prevotes", func() { + It("should not generate the same prevote multiple times", func() { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + prevotes := make([]process.Prevote, 100) + for i := range prevotes { + prevotes[i] = processutil.RandomPrevote(r) + } + all := true + for i := range prevotes { + if !prevotes[0].Equal(&prevotes[i]) { + all = false + break + } + } + Expect(all).To(BeFalse()) + }) + }) + + Context("when generating random precommits", func() { + It("should not generate the same precommit multiple times", func() { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + precommits := make([]process.Precommit, 100) + for i := range precommits { + precommits[i] = processutil.RandomPrecommit(r) + } + all := true + for i := range precommits { + if !precommits[0].Equal(&precommits[i]) { + all = false + break + } + } + Expect(all).To(BeFalse()) + }) + }) + + Context("when generating random states", func() { + It("should not generate the same state multiple times", func() { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + states := make([]process.State, 100) + for i := range states { + states[i] = processutil.RandomState(r) + } + all := true + for i := range states { + if !states[0].Equal(&states[i]) { + all = false + break + } + } + Expect(all).To(BeFalse()) + }) + }) +}) diff --git a/process/state.go b/process/state.go index 7c6fa472..6919d9e1 100644 --- a/process/state.go +++ b/process/state.go @@ -34,6 +34,15 @@ type State struct { LockedRound Round `json:"lockedRound"` // The last round in which the process sent a precommit message that is not nil. ValidValue Value `json:"validValue"` // The most recent possible decision value. ValidRound Round `json:"validRound"` // The last round in which valid value is updated. + + // ProposeLogs store the Proposes for all Rounds. + ProposeLogs map[Round]Propose `json:"proposeLogs"` + // PrevoteLogs store the Prevotes for all Processes in all Rounds. + PrevoteLogs map[Round]map[id.Signatory]Prevote `json:"prevoteLogs"` + // PrecommitLogs store the Precommits for all Processes in all Rounds. + PrecommitLogs map[Round]map[id.Signatory]Precommit `json:"precommitLogs"` + // OnceFlags prevents events from happening more than once. + OnceFlags map[Round]OnceFlag `json:"onceFlags"` } // DefaultState returns a State with all fields set to their default values. The @@ -48,11 +57,55 @@ func DefaultState() State { LockedRound: InvalidRound, ValidValue: NilValue, ValidRound: InvalidRound, + + ProposeLogs: make(map[Round]Propose), + PrevoteLogs: make(map[Round]map[id.Signatory]Prevote), + PrecommitLogs: make(map[Round]map[id.Signatory]Precommit), + OnceFlags: make(map[Round]OnceFlag), + } +} + +// Clone the State into another copy that can be modified without affecting the +// original. +func (state State) Clone() State { + cloned := State{ + CurrentHeight: state.CurrentHeight, + CurrentRound: state.CurrentRound, + CurrentStep: state.CurrentStep, + LockedValue: state.LockedValue, + LockedRound: state.LockedRound, + ValidValue: state.ValidValue, + ValidRound: state.ValidRound, + + ProposeLogs: make(map[Round]Propose), + PrevoteLogs: make(map[Round]map[id.Signatory]Prevote), + PrecommitLogs: make(map[Round]map[id.Signatory]Precommit), + OnceFlags: make(map[Round]OnceFlag), + } + for round, propose := range state.ProposeLogs { + cloned.ProposeLogs[round] = propose + } + for round, prevotes := range state.PrevoteLogs { + cloned.PrevoteLogs[round] = make(map[id.Signatory]Prevote) + for signatory, prevote := range prevotes { + cloned.PrevoteLogs[round][signatory] = prevote + } + } + for round, precommits := range state.PrecommitLogs { + cloned.PrecommitLogs[round] = make(map[id.Signatory]Precommit) + for signatory, precommit := range precommits { + cloned.PrecommitLogs[round][signatory] = precommit + } } + for round, onceFlag := range state.OnceFlags { + cloned.OnceFlags[round] = onceFlag + } + return cloned } // Equal compares two States. If they are equal, then it returns true, otherwise -// it returns false. +// it returns false. Message logs and once-flags are ignored for the purpose of +// equality. func (state State) Equal(other *State) bool { return state.CurrentHeight == other.CurrentHeight && state.CurrentRound == other.CurrentRound && @@ -70,7 +123,11 @@ func (state State) SizeHint() int { surge.SizeHint(state.LockedValue) + surge.SizeHint(state.LockedRound) + surge.SizeHint(state.ValidValue) + - surge.SizeHint(state.ValidRound) + surge.SizeHint(state.ValidRound) + + surge.SizeHint(state.ProposeLogs) + + surge.SizeHint(state.PrevoteLogs) + + surge.SizeHint(state.PrecommitLogs) + + surge.SizeHint(state.OnceFlags) } func (state State) Marshal(w io.Writer, m int) (int, error) { @@ -102,6 +159,22 @@ func (state State) Marshal(w io.Writer, m int) (int, error) { if err != nil { return m, fmt.Errorf("marshaling valid round=%v: %v", state.ValidRound, err) } + m, err = surge.Marshal(w, state.ProposeLogs, m) + if err != nil { + return m, fmt.Errorf("marshaling %v propose logs: %v", len(state.ProposeLogs), err) + } + m, err = surge.Marshal(w, state.PrevoteLogs, m) + if err != nil { + return m, fmt.Errorf("marshaling %v prevote logs: %v", len(state.PrevoteLogs), err) + } + m, err = surge.Marshal(w, state.PrecommitLogs, m) + if err != nil { + return m, fmt.Errorf("marshaling %v precommit logs: %v", len(state.PrecommitLogs), err) + } + m, err = surge.Marshal(w, state.OnceFlags, m) + if err != nil { + return m, fmt.Errorf("marshaling %v once flags: %v", len(state.OnceFlags), err) + } return m, nil } @@ -134,6 +207,22 @@ func (state *State) Unmarshal(r io.Reader, m int) (int, error) { if err != nil { return m, fmt.Errorf("unmarshaling valid round: %v", err) } + m, err = surge.Unmarshal(r, &state.ProposeLogs, m) + if err != nil { + return m, fmt.Errorf("unmarshaling propose logs: %v", err) + } + m, err = surge.Unmarshal(r, &state.PrevoteLogs, m) + if err != nil { + return m, fmt.Errorf("unmarshaling prevote logs: %v", err) + } + m, err = surge.Unmarshal(r, &state.PrecommitLogs, m) + if err != nil { + return m, fmt.Errorf("unmarshaling precommit logs: %v", err) + } + m, err = surge.Unmarshal(r, &state.OnceFlags, m) + if err != nil { + return m, fmt.Errorf("unmarshaling once flags: %v", err) + } return m, nil } @@ -180,51 +269,3 @@ var ( // the next Round). NilValue = Value(id.Hash{}) ) - -// Pid defines a type alias for hashes that represent the unique identity of a -// Process in the consensus algorithm. No distrinct Processes should ever have -// the same Pid, and a Process must maintain the same Pid for its entire life. -type Pid = id.Signatory - -// Pids defines a typedef for a slice of Pids. -type Pids []Pid - -// Equal compares two slices of Pids. If they are equal, the it returns true, -// otherwise it returns false. -func (pids Pids) Equal(other Pids) bool { - if len(pids) != len(other) { - return false - } - for i := range pids { - if !pids[i].Equal(&other[i]) { - return false - } - } - return true -} - -// Contains checks for the existence of a Pid in the slice of Pids. If the Pid -// is in the slice, then it returns true, otherwise it returns false. The -// complexity of this method is O(n), so it is recommended that the results are -// cached whenever the Pids slice is large. -func (pids Pids) Contains(pid Pid) bool { - for i := range pids { - if pids[i].Equal(&pid) { - return true - } - } - return false -} - -// Set returns the slice of Pids, converted into a PidSet. This is convenient -// for checking the existence of Pids when ordering does not matter. -func (pids Pids) Set() PidSet { - set := PidSet{} - for _, pid := range pids { - set[pid] = true - } - return set -} - -// PidSet defines a typedef for a map of Pids. -type PidSet map[Pid]bool diff --git a/process/state_test.go b/process/state_test.go index 05f732af..c1424bd6 100644 --- a/process/state_test.go +++ b/process/state_test.go @@ -1 +1,84 @@ package process_test + +import ( + "testing/quick" + + "github.com/renproject/hyperdrive/process" + "github.com/renproject/id" + "github.com/renproject/surge" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("State", func() { + Context("when unmarshaling fuzz", func() { + It("should not panic", func() { + f := func(fuzz []byte) bool { + msg := process.State{} + Expect(surge.FromBinary(fuzz, &msg)).ToNot(Succeed()) + return true + } + Expect(quick.Check(f, nil)).To(Succeed()) + }) + }) + + Context("when marshaling and then unmarshaling", func() { + It("should equal itself", func() { + f := func(currentHeight process.Height, currentRound process.Round, currentStep process.Step, lockedRound process.Round, lockedValue process.Value, validRound process.Round, validValue process.Value, proposeLogs map[process.Round]process.Propose, prevoteLogs map[process.Round]map[id.Signatory]process.Prevote, precommitLogs map[process.Round]map[id.Signatory]process.Precommit, onceFlags map[process.Round]process.OnceFlag) bool { + expected := process.State{ + CurrentHeight: currentHeight, + CurrentRound: currentRound, + CurrentStep: currentStep, + LockedRound: lockedRound, + LockedValue: lockedValue, + ValidRound: validRound, + ValidValue: validValue, + + ProposeLogs: proposeLogs, + PrevoteLogs: prevoteLogs, + PrecommitLogs: precommitLogs, + OnceFlags: onceFlags, + } + data, err := surge.ToBinary(expected) + Expect(err).ToNot(HaveOccurred()) + got := process.State{} + err = surge.FromBinary(data, &got) + Expect(err).ToNot(HaveOccurred()) + Expect(got.Equal(&expected)).To(BeTrue()) + return true + } + Expect(quick.Check(f, nil)).To(Succeed()) + }) + }) + + Context("when initialising the default state", func() { + It("should have height=1", func() { + Expect(process.DefaultState().CurrentHeight).To(Equal(process.Height(1))) + }) + + It("should have round=0", func() { + Expect(process.DefaultState().CurrentRound).To(Equal(process.Round(0))) + }) + + It("should have step=proposing", func() { + Expect(process.DefaultState().CurrentStep).To(Equal(process.Proposing)) + }) + + It("should have locked round=invalid", func() { + Expect(process.DefaultState().LockedRound).To(Equal(process.InvalidRound)) + }) + + It("should have locked value=nil", func() { + Expect(process.DefaultState().LockedValue).To(Equal(process.NilValue)) + }) + + It("should have valid round=invalid", func() { + Expect(process.DefaultState().ValidRound).To(Equal(process.InvalidRound)) + }) + + It("should have valid value=nil", func() { + Expect(process.DefaultState().ValidValue).To(Equal(process.NilValue)) + }) + }) +})