mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-11-27 13:58:05 +00:00
Merge pull request #544 from nspcc-dev/fix/sign
consensus: sign and verify consensus payloads
This commit is contained in:
commit
5d2fb41991
6 changed files with 182 additions and 21 deletions
2
go.mod
2
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
|
||||
|
|
4
go.sum
4
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=
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue