Merge pull request #1667 from nspcc-dev/extensible
Introduce extensible payloads
This commit is contained in:
commit
dfc23c4246
19 changed files with 623 additions and 371 deletions
2
go.mod
2
go.mod
|
@ -11,7 +11,7 @@ require (
|
|||
github.com/gorilla/websocket v1.4.2
|
||||
github.com/hashicorp/golang-lru v0.5.4
|
||||
github.com/mr-tron/base58 v1.1.2
|
||||
github.com/nspcc-dev/dbft v0.0.0-20201221101812-e13a1a1c3cb2
|
||||
github.com/nspcc-dev/dbft v0.0.0-20210122071512-d9a728094f0d
|
||||
github.com/nspcc-dev/rfc6979 v0.2.0
|
||||
github.com/pierrec/lz4 v2.5.2+incompatible
|
||||
github.com/prometheus/client_golang v1.2.1
|
||||
|
|
4
go.sum
4
go.sum
|
@ -166,8 +166,8 @@ github.com/nspcc-dev/dbft v0.0.0-20200117124306-478e5cfbf03a h1:ajvxgEe9qY4vvoSm
|
|||
github.com/nspcc-dev/dbft v0.0.0-20200117124306-478e5cfbf03a/go.mod h1:/YFK+XOxxg0Bfm6P92lY5eDSLYfp06XOdL8KAVgXjVk=
|
||||
github.com/nspcc-dev/dbft v0.0.0-20200219114139-199d286ed6c1 h1:yEx9WznS+rjE0jl0dLujCxuZSIb+UTjF+005TJu/nNI=
|
||||
github.com/nspcc-dev/dbft v0.0.0-20200219114139-199d286ed6c1/go.mod h1:O0qtn62prQSqizzoagHmuuKoz8QMkU3SzBoKdEvm3aQ=
|
||||
github.com/nspcc-dev/dbft v0.0.0-20201221101812-e13a1a1c3cb2 h1:vbPjd6xbX8w61abcNfzUvSI7WT0QeS9fHWp1Mocv9N0=
|
||||
github.com/nspcc-dev/dbft v0.0.0-20201221101812-e13a1a1c3cb2/go.mod h1:I5D0W3tu3epdt2RMCTxS//HDr4S+OHRqajouQTOAHI8=
|
||||
github.com/nspcc-dev/dbft v0.0.0-20210122071512-d9a728094f0d h1:uUaRysqa/9VtHETVARUlteqfbXAgwxR2nvUc4DzK4pI=
|
||||
github.com/nspcc-dev/dbft v0.0.0-20210122071512-d9a728094f0d/go.mod h1:I5D0W3tu3epdt2RMCTxS//HDr4S+OHRqajouQTOAHI8=
|
||||
github.com/nspcc-dev/neo-go v0.73.1-pre.0.20200303142215-f5a1b928ce09/go.mod h1:pPYwPZ2ks+uMnlRLUyXOpLieaDQSEaf4NM3zHVbRjmg=
|
||||
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=
|
||||
|
|
|
@ -52,12 +52,12 @@ func getDifferentPayloads(t *testing.T, n int) (payloads []Payload) {
|
|||
var sign [signatureSize]byte
|
||||
random.Fill(sign[:])
|
||||
|
||||
payloads[i].message = &message{}
|
||||
payloads[i].SetValidatorIndex(uint16(i))
|
||||
payloads[i].SetType(payload.MessageType(commitType))
|
||||
payloads[i].payload = &commit{
|
||||
signature: sign,
|
||||
}
|
||||
payloads[i].encodeData()
|
||||
}
|
||||
|
||||
return
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
npayload "github.com/nspcc-dev/neo-go/pkg/network/payload"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||
|
@ -39,6 +40,9 @@ const defaultTimePerBlock = 15 * time.Second
|
|||
// Number of nanoseconds in millisecond.
|
||||
const nsInMs = 1000000
|
||||
|
||||
// Category is message category for extensible payloads.
|
||||
const Category = "Consensus"
|
||||
|
||||
// Service represents consensus instance.
|
||||
type Service interface {
|
||||
// Start initializes dBFT and starts event loop for consensus service.
|
||||
|
@ -48,11 +52,11 @@ type Service interface {
|
|||
Shutdown()
|
||||
|
||||
// OnPayload is a callback to notify Service about new received payload.
|
||||
OnPayload(p *Payload)
|
||||
OnPayload(p *npayload.Extensible)
|
||||
// OnTransaction is a callback to notify Service about new received transaction.
|
||||
OnTransaction(tx *transaction.Transaction)
|
||||
// GetPayload returns Payload with specified hash if it is present in the local cache.
|
||||
GetPayload(h util.Uint256) *Payload
|
||||
GetPayload(h util.Uint256) *npayload.Extensible
|
||||
}
|
||||
|
||||
type service struct {
|
||||
|
@ -94,7 +98,7 @@ type Config struct {
|
|||
Logger *zap.Logger
|
||||
// Broadcast is a callback which is called to notify server
|
||||
// about new consensus payload to sent.
|
||||
Broadcast func(p *Payload)
|
||||
Broadcast func(p *npayload.Extensible)
|
||||
// Chain is a core.Blockchainer instance.
|
||||
Chain blockchainer.Blockchainer
|
||||
// RequestTx is a callback to which will be called
|
||||
|
@ -204,15 +208,33 @@ var (
|
|||
// NewPayload creates new consensus payload for the provided network.
|
||||
func NewPayload(m netmode.Magic, stateRootEnabled bool) *Payload {
|
||||
return &Payload{
|
||||
network: m,
|
||||
message: &message{
|
||||
Extensible: npayload.Extensible{
|
||||
Network: m,
|
||||
Category: Category,
|
||||
},
|
||||
message: message{
|
||||
stateRootEnabled: stateRootEnabled,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *service) newPayload() payload.ConsensusPayload {
|
||||
return NewPayload(s.network, s.stateRootEnabled)
|
||||
func (s *service) newPayload(c *dbft.Context, t payload.MessageType, msg interface{}) payload.ConsensusPayload {
|
||||
cp := NewPayload(s.network, s.stateRootEnabled)
|
||||
cp.SetHeight(c.BlockIndex)
|
||||
cp.SetValidatorIndex(uint16(c.MyIndex))
|
||||
cp.SetViewNumber(c.ViewNumber)
|
||||
cp.SetType(t)
|
||||
if pr, ok := msg.(*prepareRequest); ok {
|
||||
pr.SetPrevHash(s.dbft.PrevHash)
|
||||
pr.SetVersion(s.dbft.Version)
|
||||
}
|
||||
cp.SetPayload(msg)
|
||||
|
||||
cp.Extensible.ValidBlockStart = 0
|
||||
cp.Extensible.ValidBlockEnd = c.BlockIndex
|
||||
cp.Extensible.Sender = c.Validators[c.MyIndex].(*publicKey).GetScriptHash()
|
||||
|
||||
return cp
|
||||
}
|
||||
|
||||
func (s *service) newPrepareRequest() payload.PrepareRequest {
|
||||
|
@ -257,7 +279,7 @@ events:
|
|||
s.dbft.OnTimeout(hv)
|
||||
case msg := <-s.messages:
|
||||
fields := []zap.Field{
|
||||
zap.Uint8("from", msg.validatorIndex),
|
||||
zap.Uint8("from", msg.message.ValidatorIndex),
|
||||
zap.Stringer("type", msg.Type()),
|
||||
}
|
||||
|
||||
|
@ -312,14 +334,13 @@ func (s *service) handleChainBlock(b *coreb.Block) {
|
|||
|
||||
func (s *service) validatePayload(p *Payload) bool {
|
||||
validators := s.getValidators()
|
||||
if int(p.validatorIndex) >= len(validators) {
|
||||
if int(p.message.ValidatorIndex) >= len(validators) {
|
||||
return false
|
||||
}
|
||||
|
||||
pub := validators[p.validatorIndex]
|
||||
pub := validators[p.message.ValidatorIndex]
|
||||
h := pub.(*publicKey).GetScriptHash()
|
||||
|
||||
return s.Chain.VerifyWitness(h, p, &p.Witness, payloadGasLimit) == nil
|
||||
return p.Sender == h
|
||||
}
|
||||
|
||||
func (s *service) getKeyPair(pubs []crypto.PublicKey) (int, crypto.PrivateKey, crypto.PublicKey) {
|
||||
|
@ -346,14 +367,27 @@ func (s *service) getKeyPair(pubs []crypto.PublicKey) (int, crypto.PrivateKey, c
|
|||
return -1, nil, nil
|
||||
}
|
||||
|
||||
func (s *service) payloadFromExtensible(ep *npayload.Extensible) *Payload {
|
||||
return &Payload{
|
||||
Extensible: *ep,
|
||||
message: message{
|
||||
stateRootEnabled: s.stateRootEnabled,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// OnPayload handles Payload receive.
|
||||
func (s *service) OnPayload(cp *Payload) {
|
||||
func (s *service) OnPayload(cp *npayload.Extensible) {
|
||||
log := s.log.With(zap.Stringer("hash", cp.Hash()))
|
||||
if s.cache.Has(cp.Hash()) {
|
||||
log.Debug("payload is already in cache")
|
||||
return
|
||||
} else if !s.validatePayload(cp) {
|
||||
log.Debug("can't validate payload")
|
||||
}
|
||||
|
||||
p := s.payloadFromExtensible(cp)
|
||||
p.decodeData()
|
||||
if !s.validatePayload(p) {
|
||||
log.Info("can't validate payload")
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -366,14 +400,14 @@ func (s *service) OnPayload(cp *Payload) {
|
|||
}
|
||||
|
||||
// decode payload data into message
|
||||
if cp.message.payload == nil {
|
||||
if err := cp.decodeData(); err != nil {
|
||||
log.Debug("can't decode payload data")
|
||||
if p.message.payload == nil {
|
||||
if err := p.decodeData(); err != nil {
|
||||
log.Info("can't decode payload data")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
s.messages <- *cp
|
||||
s.messages <- *p
|
||||
}
|
||||
|
||||
func (s *service) OnTransaction(tx *transaction.Transaction) {
|
||||
|
@ -383,13 +417,13 @@ func (s *service) OnTransaction(tx *transaction.Transaction) {
|
|||
}
|
||||
|
||||
// GetPayload returns payload stored in cache.
|
||||
func (s *service) GetPayload(h util.Uint256) *Payload {
|
||||
func (s *service) GetPayload(h util.Uint256) *npayload.Extensible {
|
||||
p := s.cache.Get(h)
|
||||
if p == nil {
|
||||
return (*Payload)(nil)
|
||||
return (*npayload.Extensible)(nil)
|
||||
}
|
||||
|
||||
cp := *p.(*Payload)
|
||||
cp := *p.(*npayload.Extensible)
|
||||
|
||||
return &cp
|
||||
}
|
||||
|
@ -399,8 +433,9 @@ func (s *service) broadcast(p payload.ConsensusPayload) {
|
|||
s.log.Warn("can't sign consensus payload", zap.Error(err))
|
||||
}
|
||||
|
||||
s.cache.Add(p)
|
||||
s.Config.Broadcast(p.(*Payload))
|
||||
ep := &p.(*Payload).Extensible
|
||||
s.cache.Add(ep)
|
||||
s.Config.Broadcast(ep)
|
||||
}
|
||||
|
||||
func (s *service) getTx(h util.Uint256) block.Transaction {
|
||||
|
@ -479,14 +514,26 @@ func (s *service) verifyBlock(b block.Block) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
var (
|
||||
errInvalidPrevHash = errors.New("invalid PrevHash")
|
||||
errInvalidVersion = errors.New("invalid Version")
|
||||
errInvalidStateRoot = errors.New("state root mismatch")
|
||||
)
|
||||
|
||||
func (s *service) verifyRequest(p payload.ConsensusPayload) error {
|
||||
req := p.GetPrepareRequest().(*prepareRequest)
|
||||
if req.prevHash != s.dbft.PrevHash {
|
||||
return errInvalidPrevHash
|
||||
}
|
||||
if req.version != s.dbft.Version {
|
||||
return errInvalidVersion
|
||||
}
|
||||
if s.stateRootEnabled {
|
||||
sr, err := s.Chain.GetStateRoot(s.dbft.BlockIndex - 1)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if sr.Root != req.stateRoot {
|
||||
return fmt.Errorf("state root mismatch: %s != %s", sr.Root, req.stateRoot)
|
||||
return fmt.Errorf("%w: %s != %s", errInvalidStateRoot, sr.Root, req.stateRoot)
|
||||
}
|
||||
}
|
||||
// Save lastProposal for getVerified().
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
package consensus
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/nspcc-dev/dbft/block"
|
||||
"github.com/nspcc-dev/dbft/payload"
|
||||
"github.com/nspcc-dev/dbft/timer"
|
||||
"github.com/nspcc-dev/neo-go/internal/random"
|
||||
"github.com/nspcc-dev/neo-go/internal/testchain"
|
||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
||||
|
@ -19,6 +21,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
npayload "github.com/nspcc-dev/neo-go/pkg/network/payload"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
|
@ -180,11 +183,10 @@ func TestService_GetVerified(t *testing.T) {
|
|||
// Everyone sends a message.
|
||||
for i := 0; i < 4; i++ {
|
||||
p := new(Payload)
|
||||
p.message = &message{}
|
||||
// One PrepareRequest and three ChangeViews.
|
||||
if i == 1 {
|
||||
p.SetType(payload.PrepareRequestType)
|
||||
p.SetPayload(&prepareRequest{transactionHashes: hashes})
|
||||
p.SetPayload(&prepareRequest{prevHash: srv.Chain.CurrentBlockHash(), transactionHashes: hashes})
|
||||
} else {
|
||||
p.SetType(payload.ChangeViewType)
|
||||
p.SetPayload(&changeView{newViewNumber: 1, timestamp: uint64(time.Now().UnixNano() / nsInMs)})
|
||||
|
@ -224,8 +226,7 @@ func TestService_ValidatePayload(t *testing.T) {
|
|||
srv := newTestService(t)
|
||||
priv, _ := getTestValidator(1)
|
||||
p := new(Payload)
|
||||
p.message = &message{}
|
||||
|
||||
p.Sender = priv.GetScriptHash()
|
||||
p.SetPayload(&prepareRequest{})
|
||||
|
||||
t.Run("invalid validator index", func(t *testing.T) {
|
||||
|
@ -243,8 +244,16 @@ func TestService_ValidatePayload(t *testing.T) {
|
|||
require.False(t, srv.validatePayload(p))
|
||||
})
|
||||
|
||||
t.Run("invalid sender", func(t *testing.T) {
|
||||
p.SetValidatorIndex(1)
|
||||
p.Sender = util.Uint160{}
|
||||
require.NoError(t, p.Sign(priv))
|
||||
require.False(t, srv.validatePayload(p))
|
||||
})
|
||||
|
||||
t.Run("normal case", func(t *testing.T) {
|
||||
p.SetValidatorIndex(1)
|
||||
p.Sender = priv.GetScriptHash()
|
||||
require.NoError(t, p.Sign(priv))
|
||||
require.True(t, srv.validatePayload(p))
|
||||
})
|
||||
|
@ -295,22 +304,35 @@ func TestService_PrepareRequest(t *testing.T) {
|
|||
|
||||
priv, _ := getTestValidator(1)
|
||||
p := new(Payload)
|
||||
p.message = &message{}
|
||||
p.SetValidatorIndex(1)
|
||||
|
||||
p.SetPayload(&prepareRequest{})
|
||||
require.NoError(t, p.Sign(priv))
|
||||
require.Error(t, srv.verifyRequest(p), "invalid stateroot setting")
|
||||
prevHash := srv.Chain.CurrentBlockHash()
|
||||
|
||||
p.SetPayload(&prepareRequest{stateRootEnabled: true})
|
||||
require.NoError(t, p.Sign(priv))
|
||||
require.Error(t, srv.verifyRequest(p), "invalid state root")
|
||||
checkRequest := func(t *testing.T, expectedErr error, req *prepareRequest) {
|
||||
p.SetPayload(req)
|
||||
require.NoError(t, p.Sign(priv))
|
||||
err := srv.verifyRequest(p)
|
||||
if expectedErr == nil {
|
||||
require.NoError(t, err)
|
||||
return
|
||||
}
|
||||
require.True(t, errors.Is(err, expectedErr), "got: %v", err)
|
||||
}
|
||||
|
||||
checkRequest(t, errInvalidVersion, &prepareRequest{version: 0xFF, prevHash: prevHash})
|
||||
checkRequest(t, errInvalidPrevHash, &prepareRequest{prevHash: random.Uint256()})
|
||||
checkRequest(t, errInvalidStateRoot, &prepareRequest{
|
||||
stateRootEnabled: true,
|
||||
prevHash: prevHash,
|
||||
})
|
||||
|
||||
sr, err := srv.Chain.GetStateRoot(srv.dbft.BlockIndex - 1)
|
||||
require.NoError(t, err)
|
||||
p.SetPayload(&prepareRequest{stateRootEnabled: true, stateRoot: sr.Root})
|
||||
require.NoError(t, p.Sign(priv))
|
||||
require.NoError(t, srv.verifyRequest(p))
|
||||
checkRequest(t, nil, &prepareRequest{
|
||||
stateRootEnabled: true,
|
||||
prevHash: prevHash,
|
||||
stateRoot: sr.Root,
|
||||
})
|
||||
}
|
||||
|
||||
func TestService_OnPayload(t *testing.T) {
|
||||
|
@ -322,22 +344,26 @@ func TestService_OnPayload(t *testing.T) {
|
|||
|
||||
priv, _ := getTestValidator(1)
|
||||
p := new(Payload)
|
||||
p.message = &message{}
|
||||
p.SetValidatorIndex(1)
|
||||
p.SetPayload(&prepareRequest{})
|
||||
p.encodeData()
|
||||
|
||||
// payload is not signed
|
||||
srv.OnPayload(p)
|
||||
// sender is invalid
|
||||
srv.OnPayload(&p.Extensible)
|
||||
shouldNotReceive(t, srv.messages)
|
||||
require.Nil(t, srv.GetPayload(p.Hash()))
|
||||
|
||||
p = new(Payload)
|
||||
p.SetValidatorIndex(1)
|
||||
p.Sender = priv.GetScriptHash()
|
||||
p.SetPayload(&prepareRequest{})
|
||||
require.NoError(t, p.Sign(priv))
|
||||
srv.OnPayload(p)
|
||||
srv.OnPayload(&p.Extensible)
|
||||
shouldReceive(t, srv.messages)
|
||||
require.Equal(t, p, srv.GetPayload(p.Hash()))
|
||||
require.Equal(t, &p.Extensible, srv.GetPayload(p.Hash()))
|
||||
|
||||
// payload has already been received
|
||||
srv.OnPayload(p)
|
||||
srv.OnPayload(&p.Extensible)
|
||||
shouldNotReceive(t, srv.messages)
|
||||
srv.Chain.Close()
|
||||
}
|
||||
|
@ -453,7 +479,7 @@ func newTestService(t *testing.T) *service {
|
|||
func newTestServiceWithChain(t *testing.T, bc *core.Blockchain) *service {
|
||||
srv, err := NewService(Config{
|
||||
Logger: zaptest.NewLogger(t),
|
||||
Broadcast: func(*Payload) {},
|
||||
Broadcast: func(*npayload.Extensible) {},
|
||||
Chain: bc,
|
||||
RequestTx: func(...util.Uint256) {},
|
||||
TimePerBlock: time.Duration(bc.GetConfig().SecondsPerBlock) * time.Second,
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
package consensus
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/nspcc-dev/dbft/payload"
|
||||
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
npayload "github.com/nspcc-dev/neo-go/pkg/network/payload"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||
)
|
||||
|
@ -17,8 +14,10 @@ type (
|
|||
messageType byte
|
||||
|
||||
message struct {
|
||||
Type messageType
|
||||
ViewNumber byte
|
||||
Type messageType
|
||||
BlockIndex uint32
|
||||
ValidatorIndex byte
|
||||
ViewNumber byte
|
||||
|
||||
payload io.Serializable
|
||||
// stateRootEnabled specifies if state root is exchanged during consensus.
|
||||
|
@ -27,20 +26,8 @@ type (
|
|||
|
||||
// Payload is a type for consensus-related messages.
|
||||
Payload struct {
|
||||
*message
|
||||
|
||||
network netmode.Magic
|
||||
data []byte
|
||||
version uint32
|
||||
validatorIndex uint8
|
||||
prevHash util.Uint256
|
||||
height uint32
|
||||
|
||||
Witness transaction.Witness
|
||||
|
||||
hash util.Uint256
|
||||
signedHash util.Uint256
|
||||
signedpart []byte
|
||||
npayload.Extensible
|
||||
message
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -111,99 +98,36 @@ func (p Payload) GetRecoveryMessage() payload.RecoveryMessage {
|
|||
return p.payload.(payload.RecoveryMessage)
|
||||
}
|
||||
|
||||
// MarshalUnsigned implements payload.ConsensusPayload interface.
|
||||
func (p *Payload) MarshalUnsigned() []byte {
|
||||
if p.signedpart == nil {
|
||||
w := io.NewBufBinWriter()
|
||||
p.encodeHashData(w.BinWriter)
|
||||
p.signedpart = w.Bytes()
|
||||
}
|
||||
return p.signedpart
|
||||
}
|
||||
|
||||
// UnmarshalUnsigned implements payload.ConsensusPayload interface.
|
||||
func (p *Payload) UnmarshalUnsigned(data []byte) error {
|
||||
r := io.NewBinReaderFromBuf(data)
|
||||
p.network = netmode.Magic(r.ReadU32LE())
|
||||
p.DecodeBinaryUnsigned(r)
|
||||
|
||||
return r.Err
|
||||
}
|
||||
|
||||
// Version implements payload.ConsensusPayload interface.
|
||||
func (p Payload) Version() uint32 {
|
||||
return p.version
|
||||
}
|
||||
|
||||
// SetVersion implements payload.ConsensusPayload interface.
|
||||
func (p *Payload) SetVersion(v uint32) {
|
||||
p.version = v
|
||||
}
|
||||
|
||||
// ValidatorIndex implements payload.ConsensusPayload interface.
|
||||
func (p Payload) ValidatorIndex() uint16 {
|
||||
return uint16(p.validatorIndex)
|
||||
return uint16(p.message.ValidatorIndex)
|
||||
}
|
||||
|
||||
// SetValidatorIndex implements payload.ConsensusPayload interface.
|
||||
func (p *Payload) SetValidatorIndex(i uint16) {
|
||||
p.validatorIndex = uint8(i)
|
||||
}
|
||||
|
||||
// PrevHash implements payload.ConsensusPayload interface.
|
||||
func (p Payload) PrevHash() util.Uint256 {
|
||||
return p.prevHash
|
||||
}
|
||||
|
||||
// SetPrevHash implements payload.ConsensusPayload interface.
|
||||
func (p *Payload) SetPrevHash(h util.Uint256) {
|
||||
p.prevHash = h
|
||||
p.message.ValidatorIndex = byte(i)
|
||||
}
|
||||
|
||||
// Height implements payload.ConsensusPayload interface.
|
||||
func (p Payload) Height() uint32 {
|
||||
return p.height
|
||||
return p.message.BlockIndex
|
||||
}
|
||||
|
||||
// SetHeight implements payload.ConsensusPayload interface.
|
||||
func (p *Payload) SetHeight(h uint32) {
|
||||
p.height = h
|
||||
}
|
||||
|
||||
// EncodeBinaryUnsigned writes payload to w excluding signature.
|
||||
func (p *Payload) EncodeBinaryUnsigned(w *io.BinWriter) {
|
||||
w.WriteU32LE(p.version)
|
||||
w.WriteBytes(p.prevHash[:])
|
||||
w.WriteU32LE(p.height)
|
||||
w.WriteB(p.validatorIndex)
|
||||
|
||||
if p.data == nil {
|
||||
ww := io.NewBufBinWriter()
|
||||
p.message.EncodeBinary(ww.BinWriter)
|
||||
p.data = ww.Bytes()
|
||||
}
|
||||
w.WriteVarBytes(p.data)
|
||||
p.message.BlockIndex = h
|
||||
}
|
||||
|
||||
// EncodeBinary implements io.Serializable interface.
|
||||
func (p *Payload) EncodeBinary(w *io.BinWriter) {
|
||||
if p.signedpart == nil {
|
||||
_ = p.MarshalUnsigned()
|
||||
}
|
||||
w.WriteBytes(p.signedpart[4:])
|
||||
|
||||
w.WriteB(1)
|
||||
p.Witness.EncodeBinary(w)
|
||||
}
|
||||
|
||||
func (p *Payload) encodeHashData(w *io.BinWriter) {
|
||||
w.WriteU32LE(uint32(p.network))
|
||||
p.EncodeBinaryUnsigned(w)
|
||||
p.encodeData()
|
||||
p.Extensible.EncodeBinary(w)
|
||||
}
|
||||
|
||||
// Sign signs payload using the private key.
|
||||
// It also sets corresponding verification and invocation scripts.
|
||||
func (p *Payload) Sign(key *privateKey) error {
|
||||
p.encodeData()
|
||||
sig := key.SignHash(p.GetSignedHash())
|
||||
|
||||
buf := io.NewBufBinWriter()
|
||||
|
@ -216,78 +140,39 @@ func (p *Payload) Sign(key *privateKey) error {
|
|||
|
||||
// GetSignedPart implements crypto.Verifiable interface.
|
||||
func (p *Payload) GetSignedPart() []byte {
|
||||
return p.MarshalUnsigned()
|
||||
}
|
||||
|
||||
// DecodeBinaryUnsigned reads payload from w excluding signature.
|
||||
func (p *Payload) DecodeBinaryUnsigned(r *io.BinReader) {
|
||||
p.version = r.ReadU32LE()
|
||||
r.ReadBytes(p.prevHash[:])
|
||||
p.height = r.ReadU32LE()
|
||||
p.validatorIndex = r.ReadB()
|
||||
|
||||
p.data = r.ReadVarBytes()
|
||||
if r.Err != nil {
|
||||
return
|
||||
if p.Extensible.Data == nil {
|
||||
p.encodeData()
|
||||
}
|
||||
return p.Extensible.GetSignedPart()
|
||||
}
|
||||
|
||||
// GetSignedHash returns a hash of the payload used to verify it.
|
||||
func (p *Payload) GetSignedHash() util.Uint256 {
|
||||
if p.signedHash.Equals(util.Uint256{}) {
|
||||
if p.createHash() != nil {
|
||||
panic("failed to compute hash!")
|
||||
}
|
||||
if p.Extensible.Data == nil {
|
||||
p.encodeData()
|
||||
}
|
||||
return p.signedHash
|
||||
return p.Extensible.GetSignedHash()
|
||||
}
|
||||
|
||||
// Hash implements payload.ConsensusPayload interface.
|
||||
func (p *Payload) Hash() util.Uint256 {
|
||||
if p.hash.Equals(util.Uint256{}) {
|
||||
if p.createHash() != nil {
|
||||
panic("failed to compute hash!")
|
||||
}
|
||||
if p.Extensible.Data == nil {
|
||||
p.encodeData()
|
||||
}
|
||||
return p.hash
|
||||
}
|
||||
|
||||
// createHash creates hashes of the payload.
|
||||
func (p *Payload) createHash() error {
|
||||
b := p.GetSignedPart()
|
||||
if b == nil {
|
||||
return errors.New("failed to serialize hashable data")
|
||||
}
|
||||
p.updateHashes(b)
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateHashes updates Payload's hashes based on the given buffer which should
|
||||
// be a signable data slice.
|
||||
func (p *Payload) updateHashes(b []byte) {
|
||||
p.signedHash = hash.Sha256(b)
|
||||
p.hash = hash.Sha256(p.signedHash.BytesBE())
|
||||
return p.Extensible.Hash()
|
||||
}
|
||||
|
||||
// DecodeBinary implements io.Serializable interface.
|
||||
func (p *Payload) DecodeBinary(r *io.BinReader) {
|
||||
p.DecodeBinaryUnsigned(r)
|
||||
if r.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var b = r.ReadB()
|
||||
if b != 1 {
|
||||
r.Err = errors.New("invalid format")
|
||||
return
|
||||
}
|
||||
|
||||
p.Witness.DecodeBinary(r)
|
||||
p.Extensible.DecodeBinary(r)
|
||||
p.decodeData()
|
||||
}
|
||||
|
||||
// EncodeBinary implements io.Serializable interface.
|
||||
func (m *message) EncodeBinary(w *io.BinWriter) {
|
||||
w.WriteBytes([]byte{byte(m.Type)})
|
||||
w.WriteB(byte(m.Type))
|
||||
w.WriteU32LE(m.BlockIndex)
|
||||
w.WriteB(m.ValidatorIndex)
|
||||
w.WriteB(m.ViewNumber)
|
||||
m.payload.EncodeBinary(w)
|
||||
}
|
||||
|
@ -295,6 +180,8 @@ func (m *message) EncodeBinary(w *io.BinWriter) {
|
|||
// DecodeBinary implements io.Serializable interface.
|
||||
func (m *message) DecodeBinary(r *io.BinReader) {
|
||||
m.Type = messageType(r.ReadB())
|
||||
m.BlockIndex = r.ReadU32LE()
|
||||
m.ValidatorIndex = r.ReadB()
|
||||
m.ViewNumber = r.ReadB()
|
||||
|
||||
switch m.Type {
|
||||
|
@ -348,14 +235,22 @@ func (t messageType) String() string {
|
|||
}
|
||||
}
|
||||
|
||||
func (p *Payload) encodeData() {
|
||||
if p.Extensible.Data == nil {
|
||||
p.Extensible.ValidBlockStart = 0
|
||||
p.Extensible.ValidBlockEnd = p.BlockIndex
|
||||
bw := io.NewBufBinWriter()
|
||||
p.message.EncodeBinary(bw.BinWriter)
|
||||
p.Extensible.Data = bw.Bytes()
|
||||
}
|
||||
}
|
||||
|
||||
// decode data of payload into it's message
|
||||
func (p *Payload) decodeData() error {
|
||||
m := p.message
|
||||
br := io.NewBinReaderFromBuf(p.data)
|
||||
m.DecodeBinary(br)
|
||||
br := io.NewBinReaderFromBuf(p.Extensible.Data)
|
||||
p.message.DecodeBinary(br)
|
||||
if br.Err != nil {
|
||||
return fmt.Errorf("can't decode message: %w", br.Err)
|
||||
}
|
||||
p.message = m
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,18 +1,16 @@
|
|||
package consensus
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
gio "io"
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/dbft/payload"
|
||||
"github.com/nspcc-dev/neo-go/internal/random"
|
||||
"github.com/nspcc-dev/neo-go/internal/testserdes"
|
||||
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
npayload "github.com/nspcc-dev/neo-go/pkg/network/payload"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -30,13 +28,12 @@ var messageTypes = []messageType{
|
|||
|
||||
func TestConsensusPayload_Setters(t *testing.T) {
|
||||
var p Payload
|
||||
p.message = &message{}
|
||||
|
||||
p.SetVersion(1)
|
||||
assert.EqualValues(t, 1, p.Version())
|
||||
//p.SetVersion(1)
|
||||
//assert.EqualValues(t, 1, p.Version())
|
||||
|
||||
p.SetPrevHash(util.Uint256{1, 2, 3})
|
||||
assert.Equal(t, util.Uint256{1, 2, 3}, p.PrevHash())
|
||||
//p.SetPrevHash(util.Uint256{1, 2, 3})
|
||||
//assert.Equal(t, util.Uint256{1, 2, 3}, p.PrevHash())
|
||||
|
||||
p.SetValidatorIndex(4)
|
||||
assert.EqualValues(t, 4, p.ValidatorIndex())
|
||||
|
@ -76,22 +73,22 @@ func TestConsensusPayload_Setters(t *testing.T) {
|
|||
require.Equal(t, pl, p.GetRecoveryMessage())
|
||||
}
|
||||
|
||||
func TestConsensusPayload_Verify(t *testing.T) {
|
||||
// signed payload from mixed privnet (Go + 3C# nodes)
|
||||
dataHex := "000000004c02d52305a6a8981bd1598f0c3076d6de15a44a60ca692e189cd8a7249f175c0800000003222100f24b9147a21e09562c68abdec56d3c5fc09936592933aea5692800b75edbab2301420c40b2b8080ab02b703bc4e64407a6f31bb7ae4c9b1b1c8477668afa752eba6148e03b3ffc7e06285c09bdce4582188466209f876c38f9921a88b545393543ab201a290c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee6990b4195440d78"
|
||||
data, err := hex.DecodeString(dataHex)
|
||||
require.NoError(t, err)
|
||||
|
||||
h, err := util.Uint160DecodeStringLE("a8826043c40abacfac1d9acc6b92a4458308ca18")
|
||||
require.NoError(t, err)
|
||||
|
||||
p := NewPayload(netmode.PrivNet, false)
|
||||
require.NoError(t, testserdes.DecodeBinary(data, p))
|
||||
require.NoError(t, p.decodeData())
|
||||
bc := newTestChain(t, false)
|
||||
defer bc.Close()
|
||||
require.NoError(t, bc.VerifyWitness(h, p, &p.Witness, payloadGasLimit))
|
||||
}
|
||||
//func TestConsensusPayload_Verify(t *testing.T) {
|
||||
// // signed payload from mixed privnet (Go + 3C# nodes)
|
||||
// dataHex := "000000004c02d52305a6a8981bd1598f0c3076d6de15a44a60ca692e189cd8a7249f175c0800000003222100f24b9147a21e09562c68abdec56d3c5fc09936592933aea5692800b75edbab2301420c40b2b8080ab02b703bc4e64407a6f31bb7ae4c9b1b1c8477668afa752eba6148e03b3ffc7e06285c09bdce4582188466209f876c38f9921a88b545393543ab201a290c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee6990b4195440d78"
|
||||
// data, err := hex.DecodeString(dataHex)
|
||||
// require.NoError(t, err)
|
||||
//
|
||||
// h, err := util.Uint160DecodeStringLE("a8826043c40abacfac1d9acc6b92a4458308ca18")
|
||||
// require.NoError(t, err)
|
||||
//
|
||||
// p := NewPayload(netmode.PrivNet, false)
|
||||
// require.NoError(t, testserdes.DecodeBinary(data, p))
|
||||
// require.NoError(t, p.decodeData())
|
||||
// bc := newTestChain(t, false)
|
||||
// defer bc.Close()
|
||||
// require.NoError(t, bc.VerifyWitness(h, p, &p.Witness, payloadGasLimit))
|
||||
//}
|
||||
|
||||
func TestConsensusPayload_Serializable(t *testing.T) {
|
||||
for _, mt := range messageTypes {
|
||||
|
@ -99,85 +96,70 @@ func TestConsensusPayload_Serializable(t *testing.T) {
|
|||
actual := new(Payload)
|
||||
data, err := testserdes.EncodeBinary(p)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, testserdes.DecodeBinary(data, actual))
|
||||
// message is nil after decoding as we didn't yet call decodeData
|
||||
require.Nil(t, actual.message)
|
||||
actual.message = new(message)
|
||||
// message should now be decoded from actual.data byte array
|
||||
actual.message = new(message)
|
||||
require.NoError(t, testserdes.DecodeBinary(data, &actual.Extensible))
|
||||
assert.NoError(t, actual.decodeData())
|
||||
assert.NotNil(t, actual.MarshalUnsigned())
|
||||
require.Equal(t, p, actual)
|
||||
|
||||
data = p.MarshalUnsigned()
|
||||
pu := NewPayload(netmode.Magic(rand.Uint32()), false)
|
||||
require.NoError(t, pu.UnmarshalUnsigned(data))
|
||||
assert.NoError(t, pu.decodeData())
|
||||
_ = pu.MarshalUnsigned()
|
||||
|
||||
p.Witness = transaction.Witness{}
|
||||
require.Equal(t, p, pu)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConsensusPayload_DecodeBinaryInvalid(t *testing.T) {
|
||||
// PrepareResponse ConsensusPayload consists of:
|
||||
// 41-byte common prefix
|
||||
// 1-byte varint length of the payload (34),
|
||||
// - 1-byte view number
|
||||
// - 1-byte message type (PrepareResponse)
|
||||
// - 32-byte preparation hash
|
||||
// 1-byte delimiter (1)
|
||||
// 2-byte for empty invocation and verification scripts
|
||||
const (
|
||||
lenIndex = 41
|
||||
typeIndex = lenIndex + 1
|
||||
delimeterIndex = typeIndex + 34
|
||||
)
|
||||
|
||||
buf := make([]byte, delimeterIndex+1+2)
|
||||
|
||||
expected := &Payload{
|
||||
message: &message{
|
||||
Type: prepareResponseType,
|
||||
payload: &prepareResponse{},
|
||||
},
|
||||
Witness: transaction.Witness{
|
||||
InvocationScript: []byte{},
|
||||
VerificationScript: []byte{},
|
||||
},
|
||||
}
|
||||
// fill `data` for next check
|
||||
_ = expected.Hash()
|
||||
|
||||
// valid payload
|
||||
buf[delimeterIndex] = 1
|
||||
buf[lenIndex] = 34
|
||||
buf[typeIndex] = byte(prepareResponseType)
|
||||
p := &Payload{message: new(message)}
|
||||
require.NoError(t, testserdes.DecodeBinary(buf, p))
|
||||
// decode `data` into `message`
|
||||
_ = p.Hash()
|
||||
assert.NoError(t, p.decodeData())
|
||||
require.Equal(t, expected, p)
|
||||
|
||||
// invalid type
|
||||
buf[typeIndex] = 0xFF
|
||||
actual := &Payload{message: new(message)}
|
||||
require.NoError(t, testserdes.DecodeBinary(buf, actual))
|
||||
require.Error(t, actual.decodeData())
|
||||
|
||||
// invalid format
|
||||
buf[delimeterIndex] = 0
|
||||
buf[typeIndex] = byte(prepareResponseType)
|
||||
require.Error(t, testserdes.DecodeBinary(buf, new(Payload)))
|
||||
|
||||
// invalid message length
|
||||
buf[delimeterIndex] = 1
|
||||
buf[lenIndex] = 0xFF
|
||||
buf[typeIndex] = byte(prepareResponseType)
|
||||
require.Error(t, testserdes.DecodeBinary(buf, new(Payload)))
|
||||
}
|
||||
//func TestConsensusPayload_DecodeBinaryInvalid(t *testing.T) {
|
||||
// // PrepareResponse ConsensusPayload consists of:
|
||||
// // 41-byte common prefix
|
||||
// // 1-byte varint length of the payload (34),
|
||||
// // - 1-byte view number
|
||||
// // - 1-byte message type (PrepareResponse)
|
||||
// // - 32-byte preparation hash
|
||||
// // 1-byte delimiter (1)
|
||||
// // 2-byte for empty invocation and verification scripts
|
||||
// const (
|
||||
// lenIndex = 41
|
||||
// typeIndex = lenIndex + 1
|
||||
// delimeterIndex = typeIndex + 34
|
||||
// )
|
||||
//
|
||||
// buf := make([]byte, delimeterIndex+1+2)
|
||||
//
|
||||
// expected := &Payload{
|
||||
// message: &message{
|
||||
// Type: prepareResponseType,
|
||||
// payload: &prepareResponse{},
|
||||
// },
|
||||
// Extensible: transaction.Witness{
|
||||
// InvocationScript: []byte{},
|
||||
// VerificationScript: []byte{},
|
||||
// },
|
||||
// }
|
||||
// // fill `data` for next check
|
||||
// _ = expected.Hash()
|
||||
//
|
||||
// // valid payload
|
||||
// buf[delimeterIndex] = 1
|
||||
// buf[lenIndex] = 34
|
||||
// buf[typeIndex] = byte(prepareResponseType)
|
||||
// p := &Payload{message: new(message)}
|
||||
// require.NoError(t, testserdes.DecodeBinary(buf, p))
|
||||
// // decode `data` into `message`
|
||||
// _ = p.Hash()
|
||||
// assert.NoError(t, p.decodeData())
|
||||
// require.Equal(t, expected, p)
|
||||
//
|
||||
// // invalid type
|
||||
// buf[typeIndex] = 0xFF
|
||||
// actual := &Payload{message: new(message)}
|
||||
// require.NoError(t, testserdes.DecodeBinary(buf, actual))
|
||||
// require.Error(t, actual.decodeData())
|
||||
//
|
||||
// // invalid format
|
||||
// buf[delimeterIndex] = 0
|
||||
// buf[typeIndex] = byte(prepareResponseType)
|
||||
// require.Error(t, testserdes.DecodeBinary(buf, new(Payload)))
|
||||
//
|
||||
// // invalid message length
|
||||
// buf[delimeterIndex] = 1
|
||||
// buf[lenIndex] = 0xFF
|
||||
// buf[typeIndex] = byte(prepareResponseType)
|
||||
// require.Error(t, testserdes.DecodeBinary(buf, new(Payload)))
|
||||
//}
|
||||
|
||||
func TestCommit_Serializable(t *testing.T) {
|
||||
c := randomMessage(t, commitType)
|
||||
|
@ -206,18 +188,18 @@ func TestRecoveryMessage_Serializable(t *testing.T) {
|
|||
|
||||
func randomPayload(t *testing.T, mt messageType) *Payload {
|
||||
p := &Payload{
|
||||
message: &message{
|
||||
Type: mt,
|
||||
ViewNumber: byte(rand.Uint32()),
|
||||
payload: randomMessage(t, mt),
|
||||
message: message{
|
||||
Type: mt,
|
||||
ValidatorIndex: byte(rand.Uint32()),
|
||||
BlockIndex: rand.Uint32(),
|
||||
ViewNumber: byte(rand.Uint32()),
|
||||
payload: randomMessage(t, mt),
|
||||
},
|
||||
version: 1,
|
||||
validatorIndex: 13,
|
||||
height: rand.Uint32(),
|
||||
prevHash: random.Uint256(),
|
||||
Witness: transaction.Witness{
|
||||
InvocationScript: random.Bytes(3),
|
||||
VerificationScript: []byte{byte(opcode.PUSH0)},
|
||||
Extensible: npayload.Extensible{
|
||||
Witness: transaction.Witness{
|
||||
InvocationScript: random.Bytes(3),
|
||||
VerificationScript: []byte{byte(opcode.PUSH0)},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -334,19 +316,19 @@ func TestMessageType_String(t *testing.T) {
|
|||
require.Equal(t, "UNKNOWN(0xff)", messageType(0xff).String())
|
||||
}
|
||||
|
||||
func TestPayload_DecodeFromPrivnet(t *testing.T) {
|
||||
hexDump := "000000004c02d52305a6a8981bd1598f0c3076d6de15a44a60ca692e189cd8a7249f175c08000000004230000368c5c5401d40eef6b8a9899d2041d29fd2e6300980fdcaa6660c10b85965f57852193cdb6f0d1e9f91dc510dff6df3a004b569fe2ad456d07007f6ccd55b1d01420c40e760250b821a4dcfc4b8727ecc409a758ab4bd3b288557fd3c3d76e083fe7c625b4ed25e763ad96c4eb0abc322600d82651fd32f8866fca1403fa04d3acc4675290c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0b4195440d78"
|
||||
data, err := hex.DecodeString(hexDump)
|
||||
require.NoError(t, err)
|
||||
|
||||
buf := io.NewBinReaderFromBuf(data)
|
||||
p := NewPayload(netmode.PrivNet, false)
|
||||
p.DecodeBinary(buf)
|
||||
require.NoError(t, buf.Err)
|
||||
require.NoError(t, p.decodeData())
|
||||
require.Equal(t, payload.CommitType, p.Type())
|
||||
require.Equal(t, uint32(8), p.Height())
|
||||
|
||||
buf.ReadB()
|
||||
require.Equal(t, gio.EOF, buf.Err)
|
||||
}
|
||||
//func TestPayload_DecodeFromPrivnet(t *testing.T) {
|
||||
// hexDump := "000000004c02d52305a6a8981bd1598f0c3076d6de15a44a60ca692e189cd8a7249f175c08000000004230000368c5c5401d40eef6b8a9899d2041d29fd2e6300980fdcaa6660c10b85965f57852193cdb6f0d1e9f91dc510dff6df3a004b569fe2ad456d07007f6ccd55b1d01420c40e760250b821a4dcfc4b8727ecc409a758ab4bd3b288557fd3c3d76e083fe7c625b4ed25e763ad96c4eb0abc322600d82651fd32f8866fca1403fa04d3acc4675290c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0b4195440d78"
|
||||
// data, err := hex.DecodeString(hexDump)
|
||||
// require.NoError(t, err)
|
||||
//
|
||||
// buf := io.NewBinReaderFromBuf(data)
|
||||
// p := NewPayload(netmode.PrivNet, false)
|
||||
// p.DecodeBinary(buf)
|
||||
// require.NoError(t, buf.Err)
|
||||
// require.NoError(t, p.decodeData())
|
||||
// require.Equal(t, payload.CommitType, p.Type())
|
||||
// require.Equal(t, uint32(8), p.Height())
|
||||
//
|
||||
// buf.ReadB()
|
||||
// require.Equal(t, gio.EOF, buf.Err)
|
||||
//}
|
||||
|
|
|
@ -9,6 +9,8 @@ import (
|
|||
|
||||
// prepareRequest represents dBFT prepareRequest message.
|
||||
type prepareRequest struct {
|
||||
version uint32
|
||||
prevHash util.Uint256
|
||||
timestamp uint64
|
||||
nonce uint64
|
||||
transactionHashes []util.Uint256
|
||||
|
@ -20,6 +22,8 @@ var _ payload.PrepareRequest = (*prepareRequest)(nil)
|
|||
|
||||
// EncodeBinary implements io.Serializable interface.
|
||||
func (p *prepareRequest) EncodeBinary(w *io.BinWriter) {
|
||||
w.WriteU32LE(p.version)
|
||||
w.WriteBytes(p.prevHash[:])
|
||||
w.WriteU64LE(p.timestamp)
|
||||
w.WriteU64LE(p.nonce)
|
||||
w.WriteArray(p.transactionHashes)
|
||||
|
@ -30,6 +34,8 @@ func (p *prepareRequest) EncodeBinary(w *io.BinWriter) {
|
|||
|
||||
// DecodeBinary implements io.Serializable interface.
|
||||
func (p *prepareRequest) DecodeBinary(r *io.BinReader) {
|
||||
p.version = r.ReadU32LE()
|
||||
r.ReadBytes(p.prevHash[:])
|
||||
p.timestamp = r.ReadU64LE()
|
||||
p.nonce = r.ReadU64LE()
|
||||
r.ReadArray(&p.transactionHashes, block.MaxTransactionsPerBlock)
|
||||
|
@ -38,6 +44,26 @@ func (p *prepareRequest) DecodeBinary(r *io.BinReader) {
|
|||
}
|
||||
}
|
||||
|
||||
// Version implements payload.PrepareRequest interface.
|
||||
func (p prepareRequest) Version() uint32 {
|
||||
return p.version
|
||||
}
|
||||
|
||||
// SetVersion implements payload.PrepareRequest interface.
|
||||
func (p *prepareRequest) SetVersion(v uint32) {
|
||||
p.version = v
|
||||
}
|
||||
|
||||
// PrevHash implements payload.PrepareRequest interface.
|
||||
func (p prepareRequest) PrevHash() util.Uint256 {
|
||||
return p.prevHash
|
||||
}
|
||||
|
||||
// SetPrevHash implements payload.PrepareRequest interface.
|
||||
func (p *prepareRequest) SetPrevHash(h util.Uint256) {
|
||||
p.prevHash = h
|
||||
}
|
||||
|
||||
// Timestamp implements payload.PrepareRequest interface.
|
||||
func (p *prepareRequest) Timestamp() uint64 { return p.timestamp * nsInMs }
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"github.com/nspcc-dev/dbft/crypto"
|
||||
"github.com/nspcc-dev/dbft/payload"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
npayload "github.com/nspcc-dev/neo-go/pkg/network/payload"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
)
|
||||
|
||||
|
@ -202,6 +203,7 @@ func (m *recoveryMessage) GetPrepareRequest(p payload.ConsensusPayload, validato
|
|||
|
||||
req := fromPayload(prepareRequestType, p.(*Payload), m.prepareRequest.payload)
|
||||
req.SetValidatorIndex(primary)
|
||||
req.Sender = validators[primary].(*publicKey).GetScriptHash()
|
||||
req.Witness.InvocationScript = compact.InvocationScript
|
||||
req.Witness.VerificationScript = getVerificationScript(uint8(primary), validators)
|
||||
|
||||
|
@ -221,6 +223,7 @@ func (m *recoveryMessage) GetPrepareResponses(p payload.ConsensusPayload, valida
|
|||
preparationHash: *m.preparationHash,
|
||||
})
|
||||
r.SetValidatorIndex(uint16(resp.ValidatorIndex))
|
||||
r.Sender = validators[resp.ValidatorIndex].(*publicKey).GetScriptHash()
|
||||
r.Witness.InvocationScript = resp.InvocationScript
|
||||
r.Witness.VerificationScript = getVerificationScript(resp.ValidatorIndex, validators)
|
||||
|
||||
|
@ -241,6 +244,7 @@ func (m *recoveryMessage) GetChangeViews(p payload.ConsensusPayload, validators
|
|||
})
|
||||
c.message.ViewNumber = cv.OriginalViewNumber
|
||||
c.SetValidatorIndex(uint16(cv.ValidatorIndex))
|
||||
c.Sender = validators[cv.ValidatorIndex].(*publicKey).GetScriptHash()
|
||||
c.Witness.InvocationScript = cv.InvocationScript
|
||||
c.Witness.VerificationScript = getVerificationScript(cv.ValidatorIndex, validators)
|
||||
|
||||
|
@ -257,6 +261,7 @@ func (m *recoveryMessage) GetCommits(p payload.ConsensusPayload, validators []cr
|
|||
for i, c := range m.commitPayloads {
|
||||
cc := fromPayload(commitType, p.(*Payload), &commit{signature: c.Signature})
|
||||
cc.SetValidatorIndex(uint16(c.ValidatorIndex))
|
||||
cc.Sender = validators[c.ValidatorIndex].(*publicKey).GetScriptHash()
|
||||
cc.Witness.InvocationScript = c.InvocationScript
|
||||
cc.Witness.VerificationScript = getVerificationScript(c.ValidatorIndex, validators)
|
||||
|
||||
|
@ -291,15 +296,17 @@ func getVerificationScript(i uint8, validators []crypto.PublicKey) []byte {
|
|||
|
||||
func fromPayload(t messageType, recovery *Payload, p io.Serializable) *Payload {
|
||||
return &Payload{
|
||||
network: recovery.network,
|
||||
message: &message{
|
||||
Extensible: npayload.Extensible{
|
||||
Category: Category,
|
||||
Network: recovery.Network,
|
||||
ValidBlockEnd: recovery.BlockIndex,
|
||||
},
|
||||
message: message{
|
||||
Type: t,
|
||||
BlockIndex: recovery.BlockIndex,
|
||||
ViewNumber: recovery.message.ViewNumber,
|
||||
payload: p,
|
||||
stateRootEnabled: recovery.stateRootEnabled,
|
||||
},
|
||||
version: recovery.Version(),
|
||||
prevHash: recovery.PrevHash(),
|
||||
height: recovery.Height(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,9 +30,12 @@ func testRecoveryMessageSetters(t *testing.T, enableStateRoot bool) {
|
|||
privs[i], pubs[i] = getTestValidator(i)
|
||||
}
|
||||
|
||||
const msgHeight = 10
|
||||
|
||||
r := &recoveryMessage{stateRootEnabled: enableStateRoot}
|
||||
p := NewPayload(netmode.UnitTestNet, enableStateRoot)
|
||||
p.SetType(payload.RecoveryMessageType)
|
||||
p.SetHeight(msgHeight)
|
||||
p.SetPayload(r)
|
||||
// sign payload to have verification script
|
||||
require.NoError(t, p.Sign(privs[0]))
|
||||
|
@ -45,17 +48,21 @@ func testRecoveryMessageSetters(t *testing.T, enableStateRoot bool) {
|
|||
}
|
||||
p1 := NewPayload(netmode.UnitTestNet, enableStateRoot)
|
||||
p1.SetType(payload.PrepareRequestType)
|
||||
p1.SetHeight(msgHeight)
|
||||
p1.SetPayload(req)
|
||||
p1.SetValidatorIndex(0)
|
||||
p1.Sender = privs[0].GetScriptHash()
|
||||
require.NoError(t, p1.Sign(privs[0]))
|
||||
|
||||
t.Run("prepare response is added", func(t *testing.T) {
|
||||
p2 := NewPayload(netmode.UnitTestNet, enableStateRoot)
|
||||
p2.SetType(payload.PrepareResponseType)
|
||||
p2.SetHeight(msgHeight)
|
||||
p2.SetPayload(&prepareResponse{
|
||||
preparationHash: p1.Hash(),
|
||||
})
|
||||
p2.SetValidatorIndex(1)
|
||||
p2.Sender = privs[1].GetScriptHash()
|
||||
require.NoError(t, p2.Sign(privs[1]))
|
||||
|
||||
r.AddPayload(p2)
|
||||
|
@ -88,11 +95,13 @@ func testRecoveryMessageSetters(t *testing.T, enableStateRoot bool) {
|
|||
t.Run("change view is added", func(t *testing.T) {
|
||||
p3 := NewPayload(netmode.UnitTestNet, enableStateRoot)
|
||||
p3.SetType(payload.ChangeViewType)
|
||||
p3.SetHeight(msgHeight)
|
||||
p3.SetPayload(&changeView{
|
||||
newViewNumber: 1,
|
||||
timestamp: 12345,
|
||||
})
|
||||
p3.SetValidatorIndex(3)
|
||||
p3.Sender = privs[3].GetScriptHash()
|
||||
require.NoError(t, p3.Sign(privs[3]))
|
||||
|
||||
r.AddPayload(p3)
|
||||
|
@ -110,8 +119,10 @@ func testRecoveryMessageSetters(t *testing.T, enableStateRoot bool) {
|
|||
t.Run("commit is added", func(t *testing.T) {
|
||||
p4 := NewPayload(netmode.UnitTestNet, enableStateRoot)
|
||||
p4.SetType(payload.CommitType)
|
||||
p4.SetHeight(msgHeight)
|
||||
p4.SetPayload(randomMessage(t, commitType))
|
||||
p4.SetValidatorIndex(3)
|
||||
p4.Sender = privs[3].GetScriptHash()
|
||||
require.NoError(t, p4.Sign(privs[3]))
|
||||
|
||||
r.AddPayload(p4)
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
||||
"github.com/nspcc-dev/neo-go/pkg/consensus"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
|
@ -74,7 +73,7 @@ const (
|
|||
CMDNotFound CommandType = 0x2a
|
||||
CMDTX = CommandType(payload.TXType)
|
||||
CMDBlock = CommandType(payload.BlockType)
|
||||
CMDConsensus = CommandType(payload.ConsensusType)
|
||||
CMDExtensible = CommandType(payload.ExtensibleType)
|
||||
CMDP2PNotaryRequest = CommandType(payload.P2PNotaryRequestType)
|
||||
CMDReject CommandType = 0x2f
|
||||
|
||||
|
@ -147,8 +146,8 @@ func (m *Message) decodePayload() error {
|
|||
p = &payload.AddressList{}
|
||||
case CMDBlock:
|
||||
p = block.New(m.Network, m.StateRootInHeader)
|
||||
case CMDConsensus:
|
||||
p = consensus.NewPayload(m.Network, m.StateRootInHeader)
|
||||
case CMDExtensible:
|
||||
p = payload.NewExtensible(m.Network)
|
||||
case CMDP2PNotaryRequest:
|
||||
p = &payload.P2PNotaryRequest{Network: m.Network}
|
||||
case CMDGetBlocks:
|
||||
|
|
|
@ -24,7 +24,8 @@ func _() {
|
|||
_ = x[CMDNotFound-42]
|
||||
_ = x[CMDTX-43]
|
||||
_ = x[CMDBlock-44]
|
||||
_ = x[CMDConsensus-45]
|
||||
_ = x[CMDExtensible-46]
|
||||
_ = x[CMDP2PNotaryRequest-80]
|
||||
_ = x[CMDReject-47]
|
||||
_ = x[CMDFilterLoad-48]
|
||||
_ = x[CMDFilterAdd-49]
|
||||
|
@ -39,10 +40,11 @@ const (
|
|||
_CommandType_name_2 = "CMDPingCMDPong"
|
||||
_CommandType_name_3 = "CMDGetHeadersCMDHeaders"
|
||||
_CommandType_name_4 = "CMDGetBlocksCMDMempool"
|
||||
_CommandType_name_5 = "CMDInvCMDGetDataCMDGetBlockByIndexCMDNotFoundCMDTXCMDBlockCMDConsensus"
|
||||
_CommandType_name_6 = "CMDRejectCMDFilterLoadCMDFilterAddCMDFilterClear"
|
||||
_CommandType_name_5 = "CMDInvCMDGetDataCMDGetBlockByIndexCMDNotFoundCMDTXCMDBlock"
|
||||
_CommandType_name_6 = "CMDExtensibleCMDRejectCMDFilterLoadCMDFilterAddCMDFilterClear"
|
||||
_CommandType_name_7 = "CMDMerkleBlock"
|
||||
_CommandType_name_8 = "CMDAlert"
|
||||
_CommandType_name_9 = "CMDP2PNotaryRequest"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -51,8 +53,8 @@ var (
|
|||
_CommandType_index_2 = [...]uint8{0, 7, 14}
|
||||
_CommandType_index_3 = [...]uint8{0, 13, 23}
|
||||
_CommandType_index_4 = [...]uint8{0, 12, 22}
|
||||
_CommandType_index_5 = [...]uint8{0, 6, 16, 34, 45, 50, 58, 70}
|
||||
_CommandType_index_6 = [...]uint8{0, 9, 22, 34, 48}
|
||||
_CommandType_index_5 = [...]uint8{0, 6, 16, 34, 45, 50, 58}
|
||||
_CommandType_index_6 = [...]uint8{0, 13, 22, 35, 47, 61}
|
||||
)
|
||||
|
||||
func (i CommandType) String() string {
|
||||
|
@ -71,16 +73,18 @@ func (i CommandType) String() string {
|
|||
case 36 <= i && i <= 37:
|
||||
i -= 36
|
||||
return _CommandType_name_4[_CommandType_index_4[i]:_CommandType_index_4[i+1]]
|
||||
case 39 <= i && i <= 45:
|
||||
case 39 <= i && i <= 44:
|
||||
i -= 39
|
||||
return _CommandType_name_5[_CommandType_index_5[i]:_CommandType_index_5[i+1]]
|
||||
case 47 <= i && i <= 50:
|
||||
i -= 47
|
||||
case 46 <= i && i <= 50:
|
||||
i -= 46
|
||||
return _CommandType_name_6[_CommandType_index_6[i]:_CommandType_index_6[i+1]]
|
||||
case i == 56:
|
||||
return _CommandType_name_7
|
||||
case i == 64:
|
||||
return _CommandType_name_8
|
||||
case i == 80:
|
||||
return _CommandType_name_9
|
||||
default:
|
||||
return "CommandType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
|
|
|
@ -96,7 +96,7 @@ func TestEncodeDecodePing(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestEncodeDecodeInventory(t *testing.T) {
|
||||
testEncodeDecode(t, CMDInv, payload.NewInventory(payload.ConsensusType, []util.Uint256{{1, 2, 3}}))
|
||||
testEncodeDecode(t, CMDInv, payload.NewInventory(payload.ExtensibleType, []util.Uint256{{1, 2, 3}}))
|
||||
}
|
||||
|
||||
func TestEncodeDecodeAddr(t *testing.T) {
|
||||
|
|
126
pkg/network/payload/extensible.go
Normal file
126
pkg/network/payload/extensible.go
Normal file
|
@ -0,0 +1,126 @@
|
|||
package payload
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
)
|
||||
|
||||
const (
|
||||
maxExtensibleCategorySize = 32
|
||||
maxExtensibleDataSize = math.MaxUint16
|
||||
)
|
||||
|
||||
// Extensible represents payload containing arbitrary data.
|
||||
type Extensible struct {
|
||||
// Network represents network magic.
|
||||
Network netmode.Magic
|
||||
// Category is payload type.
|
||||
Category string
|
||||
// ValidBlockStart is starting height for payload to be valid.
|
||||
ValidBlockStart uint32
|
||||
// ValidBlockEnd is height after which payload becomes invalid.
|
||||
ValidBlockEnd uint32
|
||||
// Sender is payload sender or signer.
|
||||
Sender util.Uint160
|
||||
// Data is custom payload data.
|
||||
Data []byte
|
||||
// Witness is payload witness.
|
||||
Witness transaction.Witness
|
||||
|
||||
hash util.Uint256
|
||||
signedHash util.Uint256
|
||||
signedpart []byte
|
||||
}
|
||||
|
||||
var errInvalidPadding = errors.New("invalid padding")
|
||||
|
||||
// NewExtensible creates new extensible payload.
|
||||
func NewExtensible(network netmode.Magic) *Extensible {
|
||||
return &Extensible{Network: network}
|
||||
}
|
||||
|
||||
func (e *Extensible) encodeBinaryUnsigned(w *io.BinWriter) {
|
||||
w.WriteString(e.Category)
|
||||
w.WriteU32LE(e.ValidBlockStart)
|
||||
w.WriteU32LE(e.ValidBlockEnd)
|
||||
w.WriteBytes(e.Sender[:])
|
||||
w.WriteVarBytes(e.Data)
|
||||
}
|
||||
|
||||
// EncodeBinary implements io.Serializable.
|
||||
func (e *Extensible) EncodeBinary(w *io.BinWriter) {
|
||||
e.encodeBinaryUnsigned(w)
|
||||
w.WriteB(1)
|
||||
e.Witness.EncodeBinary(w)
|
||||
}
|
||||
|
||||
func (e *Extensible) decodeBinaryUnsigned(r *io.BinReader) {
|
||||
e.Category = r.ReadString(maxExtensibleCategorySize)
|
||||
e.ValidBlockStart = r.ReadU32LE()
|
||||
e.ValidBlockEnd = r.ReadU32LE()
|
||||
r.ReadBytes(e.Sender[:])
|
||||
e.Data = r.ReadVarBytes(maxExtensibleDataSize)
|
||||
}
|
||||
|
||||
// DecodeBinary implements io.Serializable.
|
||||
func (e *Extensible) DecodeBinary(r *io.BinReader) {
|
||||
e.decodeBinaryUnsigned(r)
|
||||
if r.ReadB() != 1 {
|
||||
if r.Err != nil {
|
||||
return
|
||||
}
|
||||
r.Err = errInvalidPadding
|
||||
return
|
||||
}
|
||||
e.Witness.DecodeBinary(r)
|
||||
}
|
||||
|
||||
// GetSignedPart implements crypto.Verifiable.
|
||||
func (e *Extensible) GetSignedPart() []byte {
|
||||
if e.signedpart == nil {
|
||||
e.updateSignedPart()
|
||||
}
|
||||
return e.signedpart
|
||||
}
|
||||
|
||||
// GetSignedHash implements crypto.Verifiable.
|
||||
func (e *Extensible) GetSignedHash() util.Uint256 {
|
||||
if e.signedHash.Equals(util.Uint256{}) {
|
||||
e.createHash()
|
||||
}
|
||||
return e.signedHash
|
||||
}
|
||||
|
||||
// Hash returns payload hash.
|
||||
func (e *Extensible) Hash() util.Uint256 {
|
||||
if e.hash.Equals(util.Uint256{}) {
|
||||
e.createHash()
|
||||
}
|
||||
return e.hash
|
||||
}
|
||||
|
||||
// createHash creates hashes of the payload.
|
||||
func (e *Extensible) createHash() {
|
||||
b := e.GetSignedPart()
|
||||
e.updateHashes(b)
|
||||
}
|
||||
|
||||
// updateHashes updates hashes based on the given buffer which should
|
||||
// be a signable data slice.
|
||||
func (e *Extensible) updateHashes(b []byte) {
|
||||
e.signedHash = hash.Sha256(b)
|
||||
e.hash = hash.Sha256(e.signedHash.BytesBE())
|
||||
}
|
||||
|
||||
// updateSignedPart updates serialized message if needed.
|
||||
func (e *Extensible) updateSignedPart() {
|
||||
w := io.NewBufBinWriter()
|
||||
e.encodeBinaryUnsigned(w.BinWriter)
|
||||
e.signedpart = w.Bytes()
|
||||
}
|
71
pkg/network/payload/extensible_test.go
Normal file
71
pkg/network/payload/extensible_test.go
Normal file
|
@ -0,0 +1,71 @@
|
|||
package payload
|
||||
|
||||
import (
|
||||
"errors"
|
||||
gio "io"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/internal/random"
|
||||
"github.com/nspcc-dev/neo-go/internal/testserdes"
|
||||
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestExtensible_Serializable(t *testing.T) {
|
||||
expected := &Extensible{
|
||||
Category: "test",
|
||||
ValidBlockStart: 12,
|
||||
ValidBlockEnd: 1234,
|
||||
Sender: random.Uint160(),
|
||||
Data: random.Bytes(4),
|
||||
Witness: transaction.Witness{
|
||||
InvocationScript: random.Bytes(3),
|
||||
VerificationScript: random.Bytes(3),
|
||||
},
|
||||
}
|
||||
|
||||
testserdes.EncodeDecodeBinary(t, expected, new(Extensible))
|
||||
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
w := io.NewBufBinWriter()
|
||||
expected.encodeBinaryUnsigned(w.BinWriter)
|
||||
unsigned := w.Bytes()
|
||||
|
||||
t.Run("unexpected EOF", func(t *testing.T) {
|
||||
err := testserdes.DecodeBinary(unsigned, new(Extensible))
|
||||
require.True(t, errors.Is(err, gio.EOF))
|
||||
})
|
||||
t.Run("invalid padding", func(t *testing.T) {
|
||||
err := testserdes.DecodeBinary(append(unsigned, 42), new(Extensible))
|
||||
require.True(t, errors.Is(err, errInvalidPadding))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestExtensible_Hashes(t *testing.T) {
|
||||
getExtensiblePair := func() (*Extensible, *Extensible) {
|
||||
p1 := NewExtensible(netmode.UnitTestNet)
|
||||
p1.Data = []byte{1, 2, 3}
|
||||
p2 := NewExtensible(netmode.UnitTestNet)
|
||||
p2.Data = []byte{3, 2, 1}
|
||||
return p1, p2
|
||||
}
|
||||
|
||||
t.Run("GetSignedPart", func(t *testing.T) {
|
||||
p1, p2 := getExtensiblePair()
|
||||
require.NotEqual(t, p1.GetSignedPart(), p2.GetSignedPart())
|
||||
require.NotEqual(t, p1.GetSignedPart(), p2.GetSignedPart())
|
||||
})
|
||||
t.Run("GetSignedHash", func(t *testing.T) {
|
||||
p1, p2 := getExtensiblePair()
|
||||
require.NotEqual(t, p1.GetSignedHash(), p2.GetSignedHash())
|
||||
require.NotEqual(t, p1.GetSignedHash(), p2.GetSignedHash())
|
||||
})
|
||||
t.Run("Hash", func(t *testing.T) {
|
||||
p1, p2 := getExtensiblePair()
|
||||
require.NotEqual(t, p1.Hash(), p2.Hash())
|
||||
require.NotEqual(t, p1.Hash(), p2.Hash())
|
||||
})
|
||||
}
|
|
@ -18,8 +18,8 @@ func (i InventoryType) String() string {
|
|||
return "TX"
|
||||
case BlockType:
|
||||
return "block"
|
||||
case ConsensusType:
|
||||
return "consensus"
|
||||
case ExtensibleType:
|
||||
return "extensible"
|
||||
case P2PNotaryRequestType:
|
||||
return "p2pNotaryRequest"
|
||||
default:
|
||||
|
@ -29,14 +29,14 @@ func (i InventoryType) String() string {
|
|||
|
||||
// Valid returns true if the inventory (type) is known.
|
||||
func (i InventoryType) Valid(p2pSigExtensionsEnabled bool) bool {
|
||||
return i == BlockType || i == TXType || i == ConsensusType || (p2pSigExtensionsEnabled && i == P2PNotaryRequestType)
|
||||
return i == BlockType || i == TXType || i == ExtensibleType || (p2pSigExtensionsEnabled && i == P2PNotaryRequestType)
|
||||
}
|
||||
|
||||
// List of valid InventoryTypes.
|
||||
const (
|
||||
TXType InventoryType = 0x2b
|
||||
BlockType InventoryType = 0x2c
|
||||
ConsensusType InventoryType = 0x2d
|
||||
ExtensibleType InventoryType = 0x2e
|
||||
P2PNotaryRequestType InventoryType = 0x50
|
||||
)
|
||||
|
||||
|
|
|
@ -35,8 +35,8 @@ func TestValid(t *testing.T) {
|
|||
require.True(t, TXType.Valid(true))
|
||||
require.True(t, BlockType.Valid(false))
|
||||
require.True(t, BlockType.Valid(true))
|
||||
require.True(t, ConsensusType.Valid(false))
|
||||
require.True(t, ConsensusType.Valid(true))
|
||||
require.True(t, ExtensibleType.Valid(false))
|
||||
require.True(t, ExtensibleType.Valid(true))
|
||||
require.False(t, P2PNotaryRequestType.Valid(false))
|
||||
require.True(t, P2PNotaryRequestType.Valid(true))
|
||||
require.False(t, InventoryType(0xFF).Valid(false))
|
||||
|
@ -46,7 +46,7 @@ func TestValid(t *testing.T) {
|
|||
func TestString(t *testing.T) {
|
||||
require.Equal(t, "TX", TXType.String())
|
||||
require.Equal(t, "block", BlockType.String())
|
||||
require.Equal(t, "consensus", ConsensusType.String())
|
||||
require.Equal(t, "extensible", ExtensibleType.String())
|
||||
require.Equal(t, "p2pNotaryRequest", P2PNotaryRequestType.String())
|
||||
require.True(t, strings.Contains(InventoryType(0xFF).String(), "unknown"))
|
||||
}
|
||||
|
|
|
@ -536,7 +536,7 @@ func (s *Server) handleInvCmd(p Peer, inv *payload.Inventory) error {
|
|||
var typExists = map[payload.InventoryType]func(util.Uint256) bool{
|
||||
payload.TXType: s.chain.HasTransaction,
|
||||
payload.BlockType: s.chain.HasBlock,
|
||||
payload.ConsensusType: func(h util.Uint256) bool {
|
||||
payload.ExtensibleType: func(h util.Uint256) bool {
|
||||
cp := s.consensus.GetPayload(h)
|
||||
return cp != nil
|
||||
},
|
||||
|
@ -557,7 +557,7 @@ func (s *Server) handleInvCmd(p Peer, inv *payload.Inventory) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if inv.Type == payload.ConsensusType {
|
||||
if inv.Type == payload.ExtensibleType {
|
||||
return p.EnqueueHPPacket(true, pkt)
|
||||
}
|
||||
return p.EnqueueP2PPacket(pkt)
|
||||
|
@ -605,9 +605,9 @@ func (s *Server) handleGetDataCmd(p Peer, inv *payload.Inventory) error {
|
|||
} else {
|
||||
notFound = append(notFound, hash)
|
||||
}
|
||||
case payload.ConsensusType:
|
||||
case payload.ExtensibleType:
|
||||
if cp := s.consensus.GetPayload(hash); cp != nil {
|
||||
msg = NewMessage(CMDConsensus, cp)
|
||||
msg = NewMessage(CMDExtensible, cp)
|
||||
}
|
||||
case payload.P2PNotaryRequestType:
|
||||
if nrp, ok := s.notaryRequestPool.TryGetData(hash); ok { // already have checked P2PSigExtEnabled
|
||||
|
@ -619,7 +619,7 @@ func (s *Server) handleGetDataCmd(p Peer, inv *payload.Inventory) error {
|
|||
if msg != nil {
|
||||
pkt, err := msg.Bytes()
|
||||
if err == nil {
|
||||
if inv.Type == payload.ConsensusType {
|
||||
if inv.Type == payload.ExtensibleType {
|
||||
err = p.EnqueueHPPacket(true, pkt)
|
||||
} else {
|
||||
err = p.EnqueueP2PPacket(pkt)
|
||||
|
@ -715,10 +715,29 @@ func (s *Server) handleGetHeadersCmd(p Peer, gh *payload.GetBlockByIndex) error
|
|||
return p.EnqueueP2PMessage(msg)
|
||||
}
|
||||
|
||||
// handleConsensusCmd processes received consensus payload.
|
||||
// It never returns an error.
|
||||
func (s *Server) handleConsensusCmd(cp *consensus.Payload) error {
|
||||
s.consensus.OnPayload(cp)
|
||||
const extensibleVerifyMaxGAS = 2000000
|
||||
|
||||
// handleExtensibleCmd processes received extensible payload.
|
||||
func (s *Server) handleExtensibleCmd(e *payload.Extensible) error {
|
||||
if err := s.chain.VerifyWitness(e.Sender, e, &e.Witness, extensibleVerifyMaxGAS); err != nil {
|
||||
return err
|
||||
}
|
||||
h := s.chain.BlockHeight()
|
||||
if h < e.ValidBlockStart || e.ValidBlockEnd <= h {
|
||||
// We can receive consensus payload for the last or next block
|
||||
// which leads to unwanted node disconnect.
|
||||
if e.ValidBlockEnd == h {
|
||||
return nil
|
||||
}
|
||||
return errors.New("invalid height")
|
||||
}
|
||||
|
||||
switch e.Category {
|
||||
case consensus.Category:
|
||||
s.consensus.OnPayload(e)
|
||||
default:
|
||||
return errors.New("invalid category")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -895,9 +914,9 @@ func (s *Server) handleMessage(peer Peer, msg *Message) error {
|
|||
case CMDBlock:
|
||||
block := msg.Payload.(*block.Block)
|
||||
return s.handleBlockCmd(peer, block)
|
||||
case CMDConsensus:
|
||||
cp := msg.Payload.(*consensus.Payload)
|
||||
return s.handleConsensusCmd(cp)
|
||||
case CMDExtensible:
|
||||
cp := msg.Payload.(*payload.Extensible)
|
||||
return s.handleExtensibleCmd(cp)
|
||||
case CMDTX:
|
||||
tx := msg.Payload.(*transaction.Transaction)
|
||||
return s.handleTxCmd(tx)
|
||||
|
@ -933,8 +952,8 @@ func (s *Server) handleMessage(peer Peer, msg *Message) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) handleNewPayload(p *consensus.Payload) {
|
||||
msg := NewMessage(CMDInv, payload.NewInventory(payload.ConsensusType, []util.Uint256{p.Hash()}))
|
||||
func (s *Server) handleNewPayload(p *payload.Extensible) {
|
||||
msg := NewMessage(CMDInv, payload.NewInventory(payload.ExtensibleType, []util.Uint256{p.Hash()}))
|
||||
// It's high priority because it directly affects consensus process,
|
||||
// even though it's just an inv.
|
||||
s.broadcastHPMessage(msg)
|
||||
|
|
|
@ -31,7 +31,7 @@ import (
|
|||
type fakeConsensus struct {
|
||||
started atomic.Bool
|
||||
stopped atomic.Bool
|
||||
payloads []*consensus.Payload
|
||||
payloads []*payload.Extensible
|
||||
txs []*transaction.Transaction
|
||||
}
|
||||
|
||||
|
@ -40,11 +40,11 @@ var _ consensus.Service = (*fakeConsensus)(nil)
|
|||
func newFakeConsensus(c consensus.Config) (consensus.Service, error) {
|
||||
return new(fakeConsensus), nil
|
||||
}
|
||||
func (f *fakeConsensus) Start() { f.started.Store(true) }
|
||||
func (f *fakeConsensus) Shutdown() { f.stopped.Store(true) }
|
||||
func (f *fakeConsensus) OnPayload(p *consensus.Payload) { f.payloads = append(f.payloads, p) }
|
||||
func (f *fakeConsensus) OnTransaction(tx *transaction.Transaction) { f.txs = append(f.txs, tx) }
|
||||
func (f *fakeConsensus) GetPayload(h util.Uint256) *consensus.Payload { panic("implement me") }
|
||||
func (f *fakeConsensus) Start() { f.started.Store(true) }
|
||||
func (f *fakeConsensus) Shutdown() { f.stopped.Store(true) }
|
||||
func (f *fakeConsensus) OnPayload(p *payload.Extensible) { f.payloads = append(f.payloads, p) }
|
||||
func (f *fakeConsensus) OnTransaction(tx *transaction.Transaction) { f.txs = append(f.txs, tx) }
|
||||
func (f *fakeConsensus) GetPayload(h util.Uint256) *payload.Extensible { panic("implement me") }
|
||||
|
||||
func TestNewServer(t *testing.T) {
|
||||
bc := &testChain{}
|
||||
|
@ -405,9 +405,48 @@ func TestConsensus(t *testing.T) {
|
|||
s, shutdown := startTestServer(t)
|
||||
defer shutdown()
|
||||
|
||||
pl := consensus.NewPayload(netmode.UnitTestNet, false)
|
||||
s.testHandleMessage(t, nil, CMDConsensus, pl)
|
||||
require.Contains(t, s.consensus.(*fakeConsensus).payloads, pl)
|
||||
atomic2.StoreUint32(&s.chain.(*testChain).blockheight, 4)
|
||||
p := newLocalPeer(t, s)
|
||||
p.handshaked = true
|
||||
|
||||
newConsensusMessage := func(start, end uint32) *Message {
|
||||
pl := payload.NewExtensible(netmode.UnitTestNet)
|
||||
pl.Category = consensus.Category
|
||||
pl.ValidBlockStart = start
|
||||
pl.ValidBlockEnd = end
|
||||
return NewMessage(CMDExtensible, pl)
|
||||
}
|
||||
|
||||
s.chain.(*testChain).verifyWitnessF = func() error { return errors.New("invalid") }
|
||||
msg := newConsensusMessage(0, s.chain.BlockHeight()+1)
|
||||
require.Error(t, s.handleMessage(p, msg))
|
||||
|
||||
s.chain.(*testChain).verifyWitnessF = func() error { return nil }
|
||||
require.NoError(t, s.handleMessage(p, msg))
|
||||
require.Contains(t, s.consensus.(*fakeConsensus).payloads, msg.Payload.(*payload.Extensible))
|
||||
|
||||
t.Run("small ValidUntilBlockEnd", func(t *testing.T) {
|
||||
t.Run("current height", func(t *testing.T) {
|
||||
msg := newConsensusMessage(0, s.chain.BlockHeight())
|
||||
require.NoError(t, s.handleMessage(p, msg))
|
||||
require.NotContains(t, s.consensus.(*fakeConsensus).payloads, msg.Payload.(*payload.Extensible))
|
||||
})
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
msg := newConsensusMessage(0, s.chain.BlockHeight()-1)
|
||||
require.Error(t, s.handleMessage(p, msg))
|
||||
})
|
||||
})
|
||||
t.Run("big ValidUntiLBlockStart", func(t *testing.T) {
|
||||
msg := newConsensusMessage(s.chain.BlockHeight()+1, s.chain.BlockHeight()+2)
|
||||
require.Error(t, s.handleMessage(p, msg))
|
||||
})
|
||||
t.Run("invalid category", func(t *testing.T) {
|
||||
pl := payload.NewExtensible(netmode.UnitTestNet)
|
||||
pl.Category = "invalid"
|
||||
pl.ValidBlockEnd = s.chain.BlockHeight() + 1
|
||||
msg := NewMessage(CMDExtensible, pl)
|
||||
require.Error(t, s.handleMessage(p, msg))
|
||||
})
|
||||
}
|
||||
|
||||
func TestTransaction(t *testing.T) {
|
||||
|
@ -448,7 +487,7 @@ func (s *Server) testHandleGetData(t *testing.T, invType payload.InventoryType,
|
|||
p.handshaked = true
|
||||
p.messageHandler = func(t *testing.T, msg *Message) {
|
||||
switch msg.Command {
|
||||
case CMDTX, CMDBlock, CMDConsensus, CMDP2PNotaryRequest:
|
||||
case CMDTX, CMDBlock, CMDExtensible, CMDP2PNotaryRequest:
|
||||
require.Equal(t, found, msg.Payload)
|
||||
recvResponse.Store(true)
|
||||
case CMDNotFound:
|
||||
|
|
Loading…
Reference in a new issue