From 765c3547935078b25a0f4d61f6aed6c6c8550005 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Mon, 2 Dec 2019 18:32:14 +0300 Subject: [PATCH 1/2] consensus: return signed messages from recovery.Get* --- go.mod | 2 +- go.sum | 4 +- pkg/consensus/recovery_message.go | 70 +++++++++++++++++++++----- pkg/consensus/recovery_message_test.go | 47 +++++++++++++++-- 4 files changed, 103 insertions(+), 20 deletions(-) diff --git a/go.mod b/go.mod index 7706c27d6..16d201058 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/mattn/go-colorable v0.1.4 // indirect github.com/mattn/go-isatty v0.0.10 // indirect github.com/mr-tron/base58 v1.1.2 - github.com/nspcc-dev/dbft v0.0.0-20191125155719-1c35ab053055 + github.com/nspcc-dev/dbft v0.0.0-20191205084618-dacb1a30c254 github.com/nspcc-dev/rfc6979 v0.1.0 github.com/onsi/ginkgo v1.10.3 // indirect github.com/onsi/gomega v1.7.1 // indirect diff --git a/go.sum b/go.sum index 7b4593fee..5d727e76e 100644 --- a/go.sum +++ b/go.sum @@ -88,8 +88,8 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb github.com/mr-tron/base58 v1.1.2 h1:ZEw4I2EgPKDJ2iEw0cNmLB3ROrEmkOtXIkaG7wZg+78= github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/nspcc-dev/dbft v0.0.0-20191125155719-1c35ab053055 h1:Rs8zeSFfXOX2RuUjWwsUrQpNbxoWyeXU8lWPlYkxDo0= -github.com/nspcc-dev/dbft v0.0.0-20191125155719-1c35ab053055/go.mod h1:w1Ln2aT+dBlPhLnuZhBV+DfPEdS2CHWWLp5JTScY3bw= +github.com/nspcc-dev/dbft v0.0.0-20191205084618-dacb1a30c254 h1:A4OkQDQOSPsJF8qUmqNvFDzmIGALrvOCZrMktllDoKc= +github.com/nspcc-dev/dbft v0.0.0-20191205084618-dacb1a30c254/go.mod h1:w1Ln2aT+dBlPhLnuZhBV+DfPEdS2CHWWLp5JTScY3bw= github.com/nspcc-dev/neofs-crypto v0.2.0 h1:ftN+59WqxSWz/RCgXYOfhmltOOqU+udsNQSvN6wkFck= github.com/nspcc-dev/neofs-crypto v0.2.0/go.mod h1:F/96fUzPM3wR+UGsPi3faVNmFlA9KAEAUQR7dMxZmNA= github.com/nspcc-dev/rfc6979 v0.1.0 h1:Lwg7esRRoyK1Up/IN1vAef1EmvrBeMHeeEkek2fAJ6c= diff --git a/pkg/consensus/recovery_message.go b/pkg/consensus/recovery_message.go index 3a01a168e..f9b8eac2d 100644 --- a/pkg/consensus/recovery_message.go +++ b/pkg/consensus/recovery_message.go @@ -3,6 +3,7 @@ package consensus import ( "github.com/CityOfZion/neo-go/pkg/io" "github.com/CityOfZion/neo-go/pkg/util" + "github.com/nspcc-dev/dbft/crypto" "github.com/nspcc-dev/dbft/payload" "github.com/pkg/errors" ) @@ -138,6 +139,10 @@ func (m *recoveryMessage) AddPayload(p payload.ConsensusPayload) { m.prepareRequest = p.GetPrepareRequest().(*prepareRequest) h := p.Hash() m.preparationHash = &h + m.preparationPayloads = append(m.preparationPayloads, &preparationCompact{ + ValidatorIndex: p.ValidatorIndex(), + InvocationScript: p.(*Payload).Witness.InvocationScript, + }) case payload.PrepareResponseType: m.preparationPayloads = append(m.preparationPayloads, &preparationCompact{ ValidatorIndex: p.ValidatorIndex(), @@ -166,16 +171,33 @@ func (m *recoveryMessage) AddPayload(p payload.ConsensusPayload) { } // GetPrepareRequest implements payload.RecoveryMessage interface. -func (m *recoveryMessage) GetPrepareRequest(p payload.ConsensusPayload) payload.ConsensusPayload { +func (m *recoveryMessage) GetPrepareRequest(p payload.ConsensusPayload, validators []crypto.PublicKey, primary uint16) payload.ConsensusPayload { if m.prepareRequest == nil { return nil } - return fromPayload(prepareRequestType, p.(*Payload), m.prepareRequest) + var compact *preparationCompact + for _, p := range m.preparationPayloads { + if p != nil && p.ValidatorIndex == primary { + compact = p + break + } + } + + if compact == nil { + return nil + } + + req := fromPayload(prepareRequestType, p.(*Payload), m.prepareRequest) + req.SetValidatorIndex(primary) + req.Witness.InvocationScript = compact.InvocationScript + req.Witness.VerificationScript = getVerificationScript(primary, validators) + + return req } // GetPrepareResponses implements payload.RecoveryMessage interface. -func (m *recoveryMessage) GetPrepareResponses(p payload.ConsensusPayload) []payload.ConsensusPayload { +func (m *recoveryMessage) GetPrepareResponses(p payload.ConsensusPayload, validators []crypto.PublicKey) []payload.ConsensusPayload { if m.preparationHash == nil { return nil } @@ -183,38 +205,49 @@ func (m *recoveryMessage) GetPrepareResponses(p payload.ConsensusPayload) []payl ps := make([]payload.ConsensusPayload, len(m.preparationPayloads)) for i, resp := range m.preparationPayloads { - ps[i] = fromPayload(prepareResponseType, p.(*Payload), &prepareResponse{ + r := fromPayload(prepareResponseType, p.(*Payload), &prepareResponse{ preparationHash: *m.preparationHash, }) - ps[i].SetValidatorIndex(resp.ValidatorIndex) + r.SetValidatorIndex(resp.ValidatorIndex) + r.Witness.InvocationScript = resp.InvocationScript + r.Witness.VerificationScript = getVerificationScript(resp.ValidatorIndex, validators) + + ps[i] = r } return ps } // GetChangeViews implements payload.RecoveryMessage interface. -func (m *recoveryMessage) GetChangeViews(p payload.ConsensusPayload) []payload.ConsensusPayload { +func (m *recoveryMessage) GetChangeViews(p payload.ConsensusPayload, validators []crypto.PublicKey) []payload.ConsensusPayload { ps := make([]payload.ConsensusPayload, len(m.changeViewPayloads)) for i, cv := range m.changeViewPayloads { - ps[i] = fromPayload(changeViewType, p.(*Payload), &changeView{ + c := fromPayload(changeViewType, p.(*Payload), &changeView{ newViewNumber: cv.OriginalViewNumber + 1, timestamp: cv.Timestamp, }) - ps[i].SetValidatorIndex(cv.ValidatorIndex) + c.SetValidatorIndex(cv.ValidatorIndex) + c.Witness.InvocationScript = cv.InvocationScript + c.Witness.VerificationScript = getVerificationScript(cv.ValidatorIndex, validators) + + ps[i] = c } return ps } // GetCommits implements payload.RecoveryMessage interface. -func (m *recoveryMessage) GetCommits(p payload.ConsensusPayload) []payload.ConsensusPayload { +func (m *recoveryMessage) GetCommits(p payload.ConsensusPayload, validators []crypto.PublicKey) []payload.ConsensusPayload { ps := make([]payload.ConsensusPayload, len(m.commitPayloads)) for i, c := range m.commitPayloads { - cc := commit{signature: c.Signature} - ps[i] = fromPayload(commitType, p.(*Payload), &cc) - ps[i].SetValidatorIndex(c.ValidatorIndex) + cc := fromPayload(commitType, p.(*Payload), &commit{signature: c.Signature}) + cc.SetValidatorIndex(c.ValidatorIndex) + cc.Witness.InvocationScript = c.InvocationScript + cc.Witness.VerificationScript = getVerificationScript(c.ValidatorIndex, validators) + + ps[i] = cc } return ps @@ -230,6 +263,19 @@ func (m *recoveryMessage) SetPreparationHash(h *util.Uint256) { m.preparationHash = h } +func getVerificationScript(i uint16, validators []crypto.PublicKey) []byte { + if int(i) >= len(validators) { + return nil + } + + pub, ok := validators[i].(*publicKey) + if !ok { + return nil + } + + return pub.GetVerificationScript() +} + func fromPayload(t messageType, recovery *Payload, p io.Serializable) *Payload { return &Payload{ message: message{ diff --git a/pkg/consensus/recovery_message_test.go b/pkg/consensus/recovery_message_test.go index 438235d3d..061ac1731 100644 --- a/pkg/consensus/recovery_message_test.go +++ b/pkg/consensus/recovery_message_test.go @@ -3,16 +3,28 @@ package consensus import ( "testing" + "github.com/CityOfZion/neo-go/pkg/crypto/keys" "github.com/CityOfZion/neo-go/pkg/util" + "github.com/nspcc-dev/dbft/crypto" "github.com/nspcc-dev/dbft/payload" "github.com/stretchr/testify/require" ) func TestRecoveryMessage_Setters(t *testing.T) { + const size = 5 + + privs := getKeys(t, size) + pubs := make([]crypto.PublicKey, 5) + for i := range pubs { + pubs[i] = &publicKey{privs[i].PublicKey()} + } + r := &recoveryMessage{} p := new(Payload) p.SetType(payload.RecoveryMessageType) p.SetPayload(r) + // sign payload to have verification script + require.NoError(t, p.Sign(privs[0])) req := &prepareRequest{ timestamp: 87, @@ -24,6 +36,8 @@ func TestRecoveryMessage_Setters(t *testing.T) { p1 := new(Payload) p1.SetType(payload.PrepareRequestType) p1.SetPayload(req) + p1.SetValidatorIndex(0) + require.NoError(t, p1.Sign(privs[0])) t.Run("prepare response is added", func(t *testing.T) { p2 := new(Payload) @@ -31,24 +45,28 @@ func TestRecoveryMessage_Setters(t *testing.T) { p2.SetPayload(&prepareResponse{ preparationHash: p1.Hash(), }) + p2.SetValidatorIndex(1) + require.NoError(t, p2.Sign(privs[1])) r.AddPayload(p2) require.NotNil(t, r.PreparationHash()) require.Equal(t, p1.Hash(), *r.PreparationHash()) - ps := r.GetPrepareResponses(p) + ps := r.GetPrepareResponses(p, pubs) require.Len(t, ps, 1) require.Equal(t, p2, ps[0]) + require.True(t, ps[0].(*Payload).Verify()) }) t.Run("prepare request is added", func(t *testing.T) { - pr := r.GetPrepareRequest(p) + pr := r.GetPrepareRequest(p, pubs, p1.ValidatorIndex()) require.Nil(t, pr) r.AddPayload(p1) - pr = r.GetPrepareRequest(p) + pr = r.GetPrepareRequest(p, pubs, p1.ValidatorIndex()) require.NotNil(t, pr) require.Equal(t, p1, pr) + require.True(t, pr.(*Payload).Verify()) }) t.Run("change view is added", func(t *testing.T) { @@ -58,23 +76,42 @@ func TestRecoveryMessage_Setters(t *testing.T) { newViewNumber: 1, timestamp: 12345, }) + p3.SetValidatorIndex(3) + require.NoError(t, p3.Sign(privs[3])) r.AddPayload(p3) - ps := r.GetChangeViews(p) + ps := r.GetChangeViews(p, pubs) require.Len(t, ps, 1) require.Equal(t, p3, ps[0]) + require.True(t, ps[0].(*Payload).Verify()) }) t.Run("commit is added", func(t *testing.T) { p4 := new(Payload) p4.SetType(payload.CommitType) p4.SetPayload(randomMessage(t, commitType)) + p4.SetValidatorIndex(4) + require.NoError(t, p4.Sign(privs[4])) r.AddPayload(p4) - ps := r.GetCommits(p) + ps := r.GetCommits(p, pubs) require.Len(t, ps, 1) require.Equal(t, p4, ps[0]) + require.True(t, ps[0].(*Payload).Verify()) }) } + +func getKeys(t *testing.T, n int) []*privateKey { + privs := make([]*privateKey, 0, n) + for i := 0; i < n; i++ { + priv, err := keys.NewPrivateKey() + require.NoError(t, err) + require.NotNil(t, priv) + + privs = append(privs, &privateKey{PrivateKey: priv}) + } + + return privs +} From 138c94eda34c7b85f8eb855ec2da3556ff1a3e6a Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Thu, 5 Dec 2019 12:07:09 +0300 Subject: [PATCH 2/2] consensus: sign and verify consensus messages --- pkg/consensus/consensus.go | 9 ++++- pkg/consensus/consensus_test.go | 71 +++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/pkg/consensus/consensus.go b/pkg/consensus/consensus.go index c404561d0..9aeeeb516 100644 --- a/pkg/consensus/consensus.go +++ b/pkg/consensus/consensus.go @@ -179,7 +179,10 @@ func getKeyPair(cfg *config.WalletConfig) (crypto.PrivateKey, crypto.PublicKey) // OnPayload handles Payload receive. func (s *service) OnPayload(cp *Payload) { - if s.cache.Has(cp.Hash()) { + if !cp.Verify() { + s.log.Debug("can't verify payload from #%d", cp.validatorIndex) + return + } else if s.cache.Has(cp.Hash()) { return } @@ -224,6 +227,10 @@ func (s *service) broadcast(p payload.ConsensusPayload) { pr.minerTx = *s.txx.Get(pr.transactionHashes[0]).(*transaction.Transaction) } + if err := p.(*Payload).Sign(s.dbft.Priv.(*privateKey)); err != nil { + s.log.Warnf("can't sign consensus payload: %v", err) + } + s.cache.Add(p) s.Config.Broadcast(p.(*Payload)) } diff --git a/pkg/consensus/consensus_test.go b/pkg/consensus/consensus_test.go index e62d53185..12af0e89d 100644 --- a/pkg/consensus/consensus_test.go +++ b/pkg/consensus/consensus_test.go @@ -27,6 +27,45 @@ func TestNewService(t *testing.T) { require.Equal(t, tx, txx[1]) } +func TestService_OnPayload(t *testing.T) { + srv := newTestService(t) + + priv, _ := getTestValidator(1) + p := new(Payload) + p.SetValidatorIndex(1) + p.SetPayload(&prepareRequest{}) + + // payload is not signed + srv.OnPayload(p) + shouldNotReceive(t, srv.messages) + require.Nil(t, srv.GetPayload(p.Hash())) + + require.NoError(t, p.Sign(priv)) + srv.OnPayload(p) + shouldReceive(t, srv.messages) + require.Equal(t, p, srv.GetPayload(p.Hash())) + + // payload has already been received + srv.OnPayload(p) + shouldNotReceive(t, srv.messages) +} + +func shouldReceive(t *testing.T, ch chan Payload) { + select { + case <-ch: + default: + require.Fail(t, "missing expected message") + } +} + +func shouldNotReceive(t *testing.T, ch chan Payload) { + select { + case <-ch: + require.Fail(t, "unexpected message receive") + default: + } +} + func newTestService(t *testing.T) *service { srv, err := NewService(Config{ Broadcast: func(*Payload) {}, @@ -42,6 +81,38 @@ func newTestService(t *testing.T) *service { return srv.(*service) } +func getTestValidator(i int) (*privateKey, *publicKey) { + var wallet *config.WalletConfig + switch i { + case 0: + wallet = &config.WalletConfig{ + Path: "6PYLmjBYJ4wQTCEfqvnznGJwZeW9pfUcV5m5oreHxqryUgqKpTRAFt9L8Y", + Password: "one", + } + case 1: + wallet = &config.WalletConfig{ + Path: "6PYXHjPaNvW8YknSXaKsTWjf9FRxo1s4naV2jdmSQEgzaqKGX368rndN3L", + Password: "two", + } + case 2: + wallet = &config.WalletConfig{ + Path: "6PYX86vYiHfUbpD95hfN1xgnvcSxy5skxfWYKu3ztjecxk6ikYs2kcWbeh", + Password: "three", + } + case 3: + wallet = &config.WalletConfig{ + Path: "6PYRXVwHSqFSukL3CuXxdQ75VmsKpjeLgQLEjt83FrtHf1gCVphHzdD4nc", + Password: "four", + } + default: + return nil, nil + } + + priv, pub := getKeyPair(wallet) + + return priv.(*privateKey), pub.(*publicKey) +} + func newTestChain(t *testing.T) *core.Blockchain { unitTestNetCfg, err := config.Load("../../config", config.ModeUnitTestNet) require.NoError(t, err)