Merge pull request #1291 from nspcc-dev/stateroot-relaying-during-commit-2.x

Stateroot relaying during commit 2.x
This commit is contained in:
Roman Khimov 2020-08-10 19:22:17 +03:00 committed by GitHub
commit d8aac7c675
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 197 additions and 144 deletions

View file

@ -2,6 +2,8 @@ ProtocolConfiguration:
Magic: 7630401 Magic: 7630401
AddressVersion: 23 AddressVersion: 23
SecondsPerBlock: 15 SecondsPerBlock: 15
EnableStateRoot: true
StateRootEnableIndex: 6016000
LowPriorityThreshold: 0.001 LowPriorityThreshold: 0.001
MemPoolSize: 50000 MemPoolSize: 50000
StandbyValidators: StandbyValidators:
@ -28,7 +30,7 @@ ProtocolConfiguration:
IssueTransaction: 500 IssueTransaction: 500
PublishTransaction: 500 PublishTransaction: 500
RegisterTransaction: 10000 RegisterTransaction: 10000
VerifyBlocks: true VerifyBlocks: false
VerifyTransactions: false VerifyTransactions: false
FreeGasLimit: 10.0 FreeGasLimit: 10.0
MaxTransactionsPerBlock: 500 MaxTransactionsPerBlock: 500
@ -40,7 +42,7 @@ ApplicationConfiguration:
# LogPath could be set up in case you need stdout logs to some proper file. # LogPath could be set up in case you need stdout logs to some proper file.
# LogPath: "./log/neogo.log" # LogPath: "./log/neogo.log"
DBConfiguration: DBConfiguration:
Type: "leveldb" #other options: 'inmemory','redis','boltdb', 'badgerdb'. Type: "boltdb" #other options: 'inmemory','redis','boltdb', 'badgerdb'.
# DB type options. Uncomment those you need in case you want to switch DB type. # DB type options. Uncomment those you need in case you want to switch DB type.
LevelDBOptions: LevelDBOptions:
DataDirectoryPath: "./chains/mainnet" DataDirectoryPath: "./chains/mainnet"
@ -48,10 +50,10 @@ ApplicationConfiguration:
# Addr: "localhost:6379" # Addr: "localhost:6379"
# Password: "" # Password: ""
# DB: 0 # DB: 0
# BoltDBOptions: BoltDBOptions:
# FilePath: "./chains/mainnet.bolt" FilePath: "./chains/mainnet.bolt"
# BadgerDBOptions: BadgerDBOptions:
# BadgerDir: "./chains/mainnet.badger" BadgerDir: "./chains/mainnet.badger"
# Uncomment in order to set up custom address for node. # Uncomment in order to set up custom address for node.
# Address: 127.0.0.1 # Address: 127.0.0.1
NodePort: 10333 NodePort: 10333

View file

@ -6,8 +6,9 @@ with two configuration options.
## What is neox ## What is neox
Neox is an extension of original Neo 2 node implemented in neox-2.x branch of Neox is an extension of original Neo 2 node originally implemented in neox-2.x
C# implementation. It includes the following main changes: branch of C# implementation (and then presented in the 2.11.0 official
release). It includes the following main changes:
* local state root generation for contract storages based on MPT * local state root generation for contract storages based on MPT
* consensus updates for state root exchange between CNs and generation of * consensus updates for state root exchange between CNs and generation of
verified (signed by CNs) state root verified (signed by CNs) state root
@ -43,22 +44,16 @@ entities which are consensus nodes.
### How and why consensus process was changed in neox ### How and why consensus process was changed in neox
Consensus nodes now exchange state root information with PrepareRequest Consensus nodes now exchange state root signatures for height N-1 during
messages, so the Primary node tells everyone its current state root hash consensus process for block N with PrepareRequest and PrepareResponse
(along with the block index that state root corresponds to) and the hash of messages.
the previous state root message. This data might also be versioned in case of
future updates, so there is a special field reserved for that too, but at the
moment it's always 0. Backups either confirm this data (if it matches their
local state) by proceeding with PrepareResponse or request a ChangeView if
there is some mismatch detected.
If all goes well CNs generate a signature for this state root data and If all goes well CNs collect enough signatures for this state root data and
exchange it with their Commit messages (along with new block generate (and broadcast) a `stateroot` message along with regular Commit
signatures). Effectively this creates another signed chain on the network that consensus messages. Effectively this creates another signed chain on the
is always one block behind from the main chain because the process of block `N` network that is always one block behind from the main chain because the
creation confirms the state resulting from processing of block `N - 1`. A process of block `N` creation confirms the state resulting from processing of
separate `stateroot` message is generated and sent along with the new block block `N - 1`.
broadcast.
### How P2P protocol was changed ### How P2P protocol was changed

2
go.mod
View file

@ -9,7 +9,7 @@ require (
github.com/go-yaml/yaml v2.1.0+incompatible github.com/go-yaml/yaml v2.1.0+incompatible
github.com/gorilla/websocket v1.4.2 github.com/gorilla/websocket v1.4.2
github.com/mr-tron/base58 v1.1.2 github.com/mr-tron/base58 v1.1.2
github.com/nspcc-dev/dbft v0.0.0-20200623100921-5a182c20965e github.com/nspcc-dev/dbft v0.0.0-20200810081309-f40804dbf8a0
github.com/nspcc-dev/rfc6979 v0.2.0 github.com/nspcc-dev/rfc6979 v0.2.0
github.com/pkg/errors v0.8.1 github.com/pkg/errors v0.8.1
github.com/prometheus/client_golang v1.2.1 github.com/prometheus/client_golang v1.2.1

4
go.sum
View file

@ -156,8 +156,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-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 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-20200219114139-199d286ed6c1/go.mod h1:O0qtn62prQSqizzoagHmuuKoz8QMkU3SzBoKdEvm3aQ=
github.com/nspcc-dev/dbft v0.0.0-20200623100921-5a182c20965e h1:QOT9slflIkEKb5wY0ZUC0dCmCgoqGlhOAh9+xWMIxfg= github.com/nspcc-dev/dbft v0.0.0-20200810081309-f40804dbf8a0 h1:4XrUJSvClcBQVZJQqI9EHW/kAIWcrycgTa5J0lBO3R8=
github.com/nspcc-dev/dbft v0.0.0-20200623100921-5a182c20965e/go.mod h1:1FYQXSbb6/9HQIkoF8XO7W/S8N7AZRkBsgwbcXRvk0E= github.com/nspcc-dev/dbft v0.0.0-20200810081309-f40804dbf8a0/go.mod h1:1FYQXSbb6/9HQIkoF8XO7W/S8N7AZRkBsgwbcXRvk0E=
github.com/nspcc-dev/neo-go v0.73.1-pre.0.20200303142215-f5a1b928ce09/go.mod h1:pPYwPZ2ks+uMnlRLUyXOpLieaDQSEaf4NM3zHVbRjmg= 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 h1:ftN+59WqxSWz/RCgXYOfhmltOOqU+udsNQSvN6wkFck=
github.com/nspcc-dev/neofs-crypto v0.2.0/go.mod h1:F/96fUzPM3wR+UGsPi3faVNmFlA9KAEAUQR7dMxZmNA= github.com/nspcc-dev/neofs-crypto v0.2.0/go.mod h1:F/96fUzPM3wR+UGsPi3faVNmFlA9KAEAUQR7dMxZmNA=

View file

@ -30,7 +30,13 @@ func (c changeView) NewViewNumber() byte { return c.newViewNumber }
func (c *changeView) SetNewViewNumber(view byte) { c.newViewNumber = view } func (c *changeView) SetNewViewNumber(view byte) { c.newViewNumber = view }
// Timestamp implements payload.ChangeView interface. // Timestamp implements payload.ChangeView interface.
func (c changeView) Timestamp() uint32 { return c.timestamp } func (c changeView) Timestamp() uint64 { return uint64(c.timestamp) * nanoInSec }
// SetTimestamp implements payload.ChangeView interface. // SetTimestamp implements payload.ChangeView interface.
func (c *changeView) SetTimestamp(ts uint32) { c.timestamp = ts } func (c *changeView) SetTimestamp(ts uint64) { c.timestamp = uint32(ts / nanoInSec) }
// Reason implements payload.ChangeView interface.
func (c changeView) Reason() payload.ChangeViewReason { return payload.CVUnknown }
// SetReason implements payload.ChangeView interface.
func (c *changeView) SetReason(_ payload.ChangeViewReason) {}

View file

@ -9,8 +9,8 @@ import (
func TestChangeView_Setters(t *testing.T) { func TestChangeView_Setters(t *testing.T) {
var c changeView var c changeView
c.SetTimestamp(123) c.SetTimestamp(123 * nanoInSec)
require.EqualValues(t, 123, c.Timestamp()) require.EqualValues(t, 123*nanoInSec, c.Timestamp())
c.SetNewViewNumber(2) c.SetNewViewNumber(2)
require.EqualValues(t, 2, c.NewViewNumber()) require.EqualValues(t, 2, c.NewViewNumber())

View file

@ -8,9 +8,6 @@ import (
// commit represents dBFT Commit message. // commit represents dBFT Commit message.
type commit struct { type commit struct {
signature [signatureSize]byte signature [signatureSize]byte
stateSig [signatureSize]byte
stateRootEnabled bool
} }
// signatureSize is an rfc6989 signature size in bytes // signatureSize is an rfc6989 signature size in bytes
@ -22,17 +19,11 @@ var _ payload.Commit = (*commit)(nil)
// EncodeBinary implements io.Serializable interface. // EncodeBinary implements io.Serializable interface.
func (c *commit) EncodeBinary(w *io.BinWriter) { func (c *commit) EncodeBinary(w *io.BinWriter) {
w.WriteBytes(c.signature[:]) w.WriteBytes(c.signature[:])
if c.stateRootEnabled {
w.WriteBytes(c.stateSig[:])
}
} }
// DecodeBinary implements io.Serializable interface. // DecodeBinary implements io.Serializable interface.
func (c *commit) DecodeBinary(r *io.BinReader) { func (c *commit) DecodeBinary(r *io.BinReader) {
r.ReadBytes(c.signature[:]) r.ReadBytes(c.signature[:])
if c.stateRootEnabled {
r.ReadBytes(c.stateSig[:])
}
} }
// Signature implements payload.Commit interface. // Signature implements payload.Commit interface.

View file

@ -16,7 +16,6 @@ import (
coreb "github.com/nspcc-dev/neo-go/pkg/core/block" coreb "github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/core/cache" "github.com/nspcc-dev/neo-go/pkg/core/cache"
"github.com/nspcc-dev/neo-go/pkg/core/mempool" "github.com/nspcc-dev/neo-go/pkg/core/mempool"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "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/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract"
@ -136,7 +135,7 @@ func NewService(cfg Config) (Service, error) {
dbft.WithVerifyBlock(srv.verifyBlock), dbft.WithVerifyBlock(srv.verifyBlock),
dbft.WithGetBlock(srv.getBlock), dbft.WithGetBlock(srv.getBlock),
dbft.WithWatchOnly(func() bool { return false }), dbft.WithWatchOnly(func() bool { return false }),
dbft.WithNewBlockFromContext(newBlockFromContext), dbft.WithNewBlockFromContext(srv.newBlockFromContext),
dbft.WithCurrentHeight(cfg.Chain.BlockHeight), dbft.WithCurrentHeight(cfg.Chain.BlockHeight),
dbft.WithCurrentBlockHash(cfg.Chain.CurrentBlockHash), dbft.WithCurrentBlockHash(cfg.Chain.CurrentBlockHash),
dbft.WithGetValidators(srv.getValidators), dbft.WithGetValidators(srv.getValidators),
@ -144,12 +143,13 @@ func NewService(cfg Config) (Service, error) {
dbft.WithNewConsensusPayload(srv.newPayload), dbft.WithNewConsensusPayload(srv.newPayload),
dbft.WithNewPrepareRequest(srv.newPrepareRequest), dbft.WithNewPrepareRequest(srv.newPrepareRequest),
dbft.WithNewPrepareResponse(func() payload.PrepareResponse { return new(prepareResponse) }), dbft.WithNewPrepareResponse(srv.newPrepareResponse),
dbft.WithNewChangeView(func() payload.ChangeView { return new(changeView) }), dbft.WithNewChangeView(func() payload.ChangeView { return new(changeView) }),
dbft.WithNewCommit(srv.newCommit), dbft.WithNewCommit(func() payload.Commit { return new(commit) }),
dbft.WithNewRecoveryRequest(func() payload.RecoveryRequest { return new(recoveryRequest) }), dbft.WithNewRecoveryRequest(func() payload.RecoveryRequest { return new(recoveryRequest) }),
dbft.WithNewRecoveryMessage(srv.newRecoveryMessage), dbft.WithNewRecoveryMessage(srv.newRecoveryMessage),
dbft.WithVerifyPrepareRequest(srv.verifyRequest), dbft.WithVerifyPrepareRequest(srv.verifyRequest),
dbft.WithVerifyPrepareResponse(srv.verifyResponse),
) )
if srv.dbft == nil { if srv.dbft == nil {
@ -235,36 +235,42 @@ func (s *service) stateRootEnabled() bool {
} }
func (s *service) newPrepareRequest() payload.PrepareRequest { func (s *service) newPrepareRequest() payload.PrepareRequest {
res := &prepareRequest{
stateRootEnabled: s.stateRootEnabled(),
}
if !s.stateRootEnabled() { if !s.stateRootEnabled() {
return new(prepareRequest) return res
} }
sr, err := s.Chain.GetStateRoot(s.Chain.BlockHeight()) sig := s.getStateRootSig()
if err == nil { if sig != nil {
return &prepareRequest{ copy(res.stateRootSig[:], sig)
stateRootEnabled: true,
proposalStateRoot: sr.MPTRootBase,
}
} }
return &prepareRequest{stateRootEnabled: true} return res
} }
func (s *service) newCommit() payload.Commit { func (s *service) getStateRootSig() []byte {
var sig []byte
sr, err := s.Chain.GetStateRoot(s.dbft.BlockIndex - 1)
if err == nil {
data := sr.GetSignedPart()
sig, _ = s.dbft.Priv.Sign(data)
}
return sig
}
func (s *service) newPrepareResponse() payload.PrepareResponse {
res := &prepareResponse{
stateRootEnabled: s.stateRootEnabled(),
}
if !s.stateRootEnabled() { if !s.stateRootEnabled() {
return new(commit) return res
} }
c := &commit{stateRootEnabled: true} sig := s.getStateRootSig()
for _, p := range s.dbft.Context.PreparationPayloads { if sig != nil {
if p != nil && p.ViewNumber() == s.dbft.ViewNumber && p.Type() == payload.PrepareRequestType { copy(res.stateRootSig[:], sig)
pr := p.GetPrepareRequest().(*prepareRequest)
data := pr.proposalStateRoot.GetSignedPart()
sign, err := s.dbft.Priv.Sign(data)
if err == nil {
copy(c.stateSig[:], sign)
}
break
}
} }
return c return res
} }
func (s *service) newRecoveryMessage() payload.RecoveryMessage { func (s *service) newRecoveryMessage() payload.RecoveryMessage {
@ -393,15 +399,29 @@ func (s *service) verifyBlock(b block.Block) bool {
return true return true
} }
func (s *service) verifyStateRootSig(index int, sig []byte) error {
r, err := s.Chain.GetStateRoot(s.dbft.BlockIndex - 1)
if err != nil {
return fmt.Errorf("can't get local state root: %v", err)
}
validators := s.getValidators()
if index >= len(validators) {
return errors.New("bad validator index")
}
pub := validators[index]
if pub.Verify(r.GetSignedPart(), sig) != nil {
return errors.New("bad state root signature")
}
return nil
}
func (s *service) verifyRequest(p payload.ConsensusPayload) error { func (s *service) verifyRequest(p payload.ConsensusPayload) error {
req := p.GetPrepareRequest().(*prepareRequest) req := p.GetPrepareRequest().(*prepareRequest)
if s.stateRootEnabled() { if s.stateRootEnabled() {
r, err := s.Chain.GetStateRoot(s.dbft.BlockIndex - 1) err := s.verifyStateRootSig(int(p.ValidatorIndex()), req.stateRootSig[:])
if err != nil { if err != nil {
return fmt.Errorf("can't get local state root: %v", err) return err
}
if !r.Equals(&req.proposalStateRoot) {
return errors.New("state root mismatch")
} }
} }
// Save lastProposal for getVerified(). // Save lastProposal for getVerified().
@ -411,6 +431,14 @@ func (s *service) verifyRequest(p payload.ConsensusPayload) error {
return nil return nil
} }
func (s *service) verifyResponse(p payload.ConsensusPayload) error {
if !s.stateRootEnabled() {
return nil
}
resp := p.GetPrepareResponse().(*prepareResponse)
return s.verifyStateRootSig(int(p.ValidatorIndex()), resp.stateRootSig[:])
}
func (s *service) processBlock(b block.Block) { func (s *service) processBlock(b block.Block) {
bb := &b.(*neoBlock).Block bb := &b.(*neoBlock).Block
bb.Script = *(s.getBlockWitness(bb)) bb.Script = *(s.getBlockWitness(bb))
@ -422,36 +450,26 @@ func (s *service) processBlock(b block.Block) {
s.log.Warn("error on add block", zap.Error(err)) s.log.Warn("error on add block", zap.Error(err))
} }
} }
var rb *state.MPTRootBase
for _, p := range s.dbft.PreparationPayloads {
if p != nil && p.Type() == payload.PrepareRequestType {
rb = &p.GetPrepareRequest().(*prepareRequest).proposalStateRoot
}
}
w := s.getWitness(func(p payload.Commit) []byte { return p.(*commit).stateSig[:] })
r := &state.MPTRoot{
MPTRootBase: *rb,
Witness: w,
}
if err := s.Chain.AddStateRoot(r); err != nil {
s.log.Warn("errors while adding state root", zap.Error(err))
}
s.Broadcast(r)
} }
func (s *service) getBlockWitness(_ *coreb.Block) *transaction.Witness { func (s *service) getBlockWitness(_ *coreb.Block) *transaction.Witness {
return s.getWitness(func(p payload.Commit) []byte { return p.Signature() }) return s.getWitness(func(ctx dbft.Context, i int) []byte {
if p := ctx.CommitPayloads[i]; p != nil && p.ViewNumber() == ctx.ViewNumber {
return p.GetCommit().Signature()
}
return nil
})
} }
func (s *service) getWitness(f func(p payload.Commit) []byte) *transaction.Witness { func (s *service) getWitness(f func(dbft.Context, int) []byte) *transaction.Witness {
dctx := s.dbft.Context dctx := s.dbft.Context
pubs := convertKeys(dctx.Validators) pubs := convertKeys(dctx.Validators)
sigs := make(map[*keys.PublicKey][]byte) sigs := make(map[*keys.PublicKey][]byte)
for i := range pubs { for i := range pubs {
if p := dctx.CommitPayloads[i]; p != nil && p.ViewNumber() == dctx.ViewNumber { sig := f(dctx, i)
sigs[pubs[i]] = f(p.GetCommit()) if sig != nil {
sigs[pubs[i]] = sig
} }
} }
@ -603,7 +621,29 @@ func convertKeys(validators []crypto.PublicKey) (pubs []*keys.PublicKey) {
return return
} }
func newBlockFromContext(ctx *dbft.Context) block.Block { func (s *service) newBlockFromContext(ctx *dbft.Context) block.Block {
if s.stateRootEnabled() {
// This is being called when we're ready to commit, so we can safely
// relay stateroot here.
stateRoot, err := s.Chain.GetStateRoot(s.dbft.Context.BlockIndex - 1)
if err != nil {
s.log.Warn("can't get stateroot", zap.Uint32("block", s.dbft.Context.BlockIndex-1))
}
r := stateRoot.MPTRoot
r.Witness = s.getWitness(func(ctx dbft.Context, i int) []byte {
if p := ctx.PreparationPayloads[i]; p != nil && p.ViewNumber() == ctx.ViewNumber {
if int(ctx.PrimaryIndex) == i {
return p.GetPrepareRequest().(*prepareRequest).stateRootSig[:]
}
return p.GetPrepareResponse().(*prepareResponse).stateRootSig[:]
}
return nil
})
if err := s.Chain.AddStateRoot(&r); err != nil {
s.log.Warn("errors while adding state root", zap.Error(err))
}
s.Broadcast(&r)
}
block := new(neoBlock) block := new(neoBlock)
if len(ctx.TransactionHashes) == 0 { if len(ctx.TransactionHashes) == 0 {
return nil return nil

View file

@ -49,20 +49,24 @@ func TestService_GetVerified(t *testing.T) {
// Everyone sends a message. // Everyone sends a message.
for i := 0; i < 4; i++ { for i := 0; i < 4; i++ {
p := new(Payload) p := srv.newPayload().(*Payload)
p.message = &message{} p.SetHeight(1)
p.SetValidatorIndex(uint16(i))
priv, _ := getTestValidator(i)
// To properly sign stateroot in prepare request.
srv.dbft.Priv = priv
// One PrepareRequest and three ChangeViews. // One PrepareRequest and three ChangeViews.
if i == 1 { if i == 1 {
p.SetType(payload.PrepareRequestType) p.SetType(payload.PrepareRequestType)
p.SetPayload(&prepareRequest{transactionHashes: hashes, minerTx: *newMinerTx(999)}) preq := srv.newPrepareRequest().(*prepareRequest)
preq.transactionHashes = hashes
preq.minerTx = *newMinerTx(999)
p.SetPayload(preq)
} else { } else {
p.SetType(payload.ChangeViewType) p.SetType(payload.ChangeViewType)
p.SetPayload(&changeView{newViewNumber: 1, timestamp: uint32(time.Now().Unix())}) p.SetPayload(&changeView{newViewNumber: 1, timestamp: uint32(time.Now().Unix())})
} }
p.SetHeight(1)
p.SetValidatorIndex(uint16(i))
priv, _ := getTestValidator(i)
require.NoError(t, p.Sign(priv)) require.NoError(t, p.Sign(priv))
// Skip srv.OnPayload, because the service is not really started. // Skip srv.OnPayload, because the service is not really started.

View file

@ -49,6 +49,8 @@ const (
commitType messageType = 0x30 commitType messageType = 0x30
recoveryRequestType messageType = 0x40 recoveryRequestType messageType = 0x40
recoveryMessageType messageType = 0x41 recoveryMessageType messageType = 0x41
nanoInSec = 1000_000_000
) )
// ViewNumber implements payload.ConsensusPayload interface. // ViewNumber implements payload.ConsensusPayload interface.
@ -289,11 +291,11 @@ func (m *message) DecodeBinary(r *io.BinReader) {
stateRootEnabled: m.stateRootEnabled, stateRootEnabled: m.stateRootEnabled,
} }
case prepareResponseType: case prepareResponseType:
m.payload = new(prepareResponse) m.payload = &prepareResponse{
case commitType:
m.payload = &commit{
stateRootEnabled: m.stateRootEnabled, stateRootEnabled: m.stateRootEnabled,
} }
case commitType:
m.payload = new(commit)
case recoveryRequestType: case recoveryRequestType:
m.payload = new(recoveryRequest) m.payload = new(recoveryRequest)
case recoveryMessageType: case recoveryMessageType:

View file

@ -173,13 +173,13 @@ func testEncodeDecode(srEnabled bool, mt messageType, actual io.Serializable) fu
} }
func TestCommit_Serializable(t *testing.T) { func TestCommit_Serializable(t *testing.T) {
t.Run("WithStateRoot", testEncodeDecode(true, commitType, &commit{stateRootEnabled: true})) testEncodeDecode(false, commitType, &commit{})
t.Run("NoStateRoot", testEncodeDecode(false, commitType, &commit{stateRootEnabled: false}))
} }
func TestPrepareResponse_Serializable(t *testing.T) { func TestPrepareResponse_Serializable(t *testing.T) {
resp := randomMessage(t, prepareResponseType) t.Run("WithStateRoot", testEncodeDecode(true, prepareResponseType, &prepareResponse{stateRootEnabled: true}))
testserdes.EncodeDecodeBinary(t, resp, new(prepareResponse)) t.Run("NoStateRoot", testEncodeDecode(false, prepareResponseType, &prepareResponse{stateRootEnabled: false}))
} }
func TestPrepareRequest_Serializable(t *testing.T) { func TestPrepareRequest_Serializable(t *testing.T) {
@ -231,14 +231,15 @@ func randomMessage(t *testing.T, mt messageType, srEnabled ...bool) io.Serializa
case prepareRequestType: case prepareRequestType:
return randomPrepareRequest(t, srEnabled...) return randomPrepareRequest(t, srEnabled...)
case prepareResponseType: case prepareResponseType:
return &prepareResponse{preparationHash: random.Uint256()} var p = prepareResponse{preparationHash: random.Uint256()}
if len(srEnabled) > 0 && srEnabled[0] {
p.stateRootEnabled = true
random.Fill(p.stateRootSig[:])
}
return &p
case commitType: case commitType:
var c commit var c commit
random.Fill(c.signature[:]) random.Fill(c.signature[:])
if len(srEnabled) > 0 && srEnabled[0] {
c.stateRootEnabled = true
random.Fill(c.stateSig[:])
}
return &c return &c
case recoveryRequestType: case recoveryRequestType:
return &recoveryRequest{timestamp: rand.Uint32()} return &recoveryRequest{timestamp: rand.Uint32()}
@ -268,9 +269,7 @@ func randomPrepareRequest(t *testing.T, srEnabled ...bool) *prepareRequest {
if len(srEnabled) > 0 && srEnabled[0] { if len(srEnabled) > 0 && srEnabled[0] {
req.stateRootEnabled = true req.stateRootEnabled = true
req.proposalStateRoot.Index = rand.Uint32() random.Fill(req.stateRootSig[:])
req.proposalStateRoot.PrevHash = random.Uint256()
req.proposalStateRoot.Root = random.Uint256()
} }
return req return req
@ -318,9 +317,10 @@ func randomRecoveryMessage(t *testing.T, srEnabled ...bool) *recoveryMessage {
if len(srEnabled) > 0 && srEnabled[0] { if len(srEnabled) > 0 && srEnabled[0] {
rec.stateRootEnabled = true rec.stateRootEnabled = true
rec.prepareRequest.stateRootEnabled = true rec.prepareRequest.stateRootEnabled = true
for _, c := range rec.commitPayloads { random.Fill(prepReq.stateRootSig[:])
c.stateRootEnabled = true for _, p := range rec.preparationPayloads {
random.Fill(c.StateSignature[:]) p.stateRootEnabled = true
random.Fill(p.StateRootSig[:])
} }
} }
return rec return rec

View file

@ -2,7 +2,6 @@ package consensus
import ( import (
"github.com/nspcc-dev/dbft/payload" "github.com/nspcc-dev/dbft/payload"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
@ -15,7 +14,7 @@ type prepareRequest struct {
transactionHashes []util.Uint256 transactionHashes []util.Uint256
minerTx transaction.Transaction minerTx transaction.Transaction
nextConsensus util.Uint160 nextConsensus util.Uint160
proposalStateRoot state.MPTRootBase stateRootSig [signatureSize]byte
stateRootEnabled bool stateRootEnabled bool
} }
@ -30,7 +29,7 @@ func (p *prepareRequest) EncodeBinary(w *io.BinWriter) {
w.WriteArray(p.transactionHashes) w.WriteArray(p.transactionHashes)
p.minerTx.EncodeBinary(w) p.minerTx.EncodeBinary(w)
if p.stateRootEnabled { if p.stateRootEnabled {
p.proposalStateRoot.EncodeBinary(w) w.WriteBytes(p.stateRootSig[:])
} }
} }
@ -42,7 +41,7 @@ func (p *prepareRequest) DecodeBinary(r *io.BinReader) {
r.ReadArray(&p.transactionHashes) r.ReadArray(&p.transactionHashes)
p.minerTx.DecodeBinary(r) p.minerTx.DecodeBinary(r)
if p.stateRootEnabled { if p.stateRootEnabled {
p.proposalStateRoot.DecodeBinary(r) r.ReadBytes(p.stateRootSig[:])
} }
} }

View file

@ -9,6 +9,9 @@ import (
// prepareResponse represents dBFT PrepareResponse message. // prepareResponse represents dBFT PrepareResponse message.
type prepareResponse struct { type prepareResponse struct {
preparationHash util.Uint256 preparationHash util.Uint256
stateRootSig [signatureSize]byte
stateRootEnabled bool
} }
var _ payload.PrepareResponse = (*prepareResponse)(nil) var _ payload.PrepareResponse = (*prepareResponse)(nil)
@ -16,11 +19,17 @@ var _ payload.PrepareResponse = (*prepareResponse)(nil)
// EncodeBinary implements io.Serializable interface. // EncodeBinary implements io.Serializable interface.
func (p *prepareResponse) EncodeBinary(w *io.BinWriter) { func (p *prepareResponse) EncodeBinary(w *io.BinWriter) {
w.WriteBytes(p.preparationHash[:]) w.WriteBytes(p.preparationHash[:])
if p.stateRootEnabled {
w.WriteBytes(p.stateRootSig[:])
}
} }
// DecodeBinary implements io.Serializable interface. // DecodeBinary implements io.Serializable interface.
func (p *prepareResponse) DecodeBinary(r *io.BinReader) { func (p *prepareResponse) DecodeBinary(r *io.BinReader) {
r.ReadBytes(p.preparationHash[:]) r.ReadBytes(p.preparationHash[:])
if p.stateRootEnabled {
r.ReadBytes(p.stateRootSig[:])
}
} }
// PreparationHash implements payload.PrepareResponse interface. // PreparationHash implements payload.PrepareResponse interface.

View file

@ -32,15 +32,15 @@ type (
ViewNumber byte ViewNumber byte
ValidatorIndex uint16 ValidatorIndex uint16
Signature [signatureSize]byte Signature [signatureSize]byte
StateSignature [signatureSize]byte
InvocationScript []byte InvocationScript []byte
stateRootEnabled bool
} }
preparationCompact struct { preparationCompact struct {
ValidatorIndex uint16 ValidatorIndex uint16
InvocationScript []byte InvocationScript []byte
StateRootSig [signatureSize]byte
stateRootEnabled bool
} }
) )
@ -72,17 +72,17 @@ func (m *recoveryMessage) DecodeBinary(r *io.BinReader) {
} }
} }
r.ReadArray(&m.preparationPayloads)
lu := r.ReadVarUint() lu := r.ReadVarUint()
if lu > state.MaxValidatorsVoted { if lu > state.MaxValidatorsVoted {
r.Err = errors.New("too many preparation payloads") r.Err = errors.New("too many preparation payloads")
return return
} }
m.commitPayloads = make([]*commitCompact, lu) m.preparationPayloads = make([]*preparationCompact, lu)
for i := uint64(0); i < lu; i++ { for i := uint64(0); i < lu; i++ {
m.commitPayloads[i] = &commitCompact{stateRootEnabled: m.stateRootEnabled} m.preparationPayloads[i] = &preparationCompact{stateRootEnabled: m.stateRootEnabled}
m.commitPayloads[i].DecodeBinary(r) m.preparationPayloads[i].DecodeBinary(r)
} }
r.ReadArray(&m.commitPayloads)
} }
// EncodeBinary implements io.Serializable interface. // EncodeBinary implements io.Serializable interface.
@ -127,9 +127,6 @@ func (p *commitCompact) DecodeBinary(r *io.BinReader) {
p.ViewNumber = r.ReadB() p.ViewNumber = r.ReadB()
p.ValidatorIndex = r.ReadU16LE() p.ValidatorIndex = r.ReadU16LE()
r.ReadBytes(p.Signature[:]) r.ReadBytes(p.Signature[:])
if p.stateRootEnabled {
r.ReadBytes(p.StateSignature[:])
}
p.InvocationScript = r.ReadVarBytes(1024) p.InvocationScript = r.ReadVarBytes(1024)
} }
@ -138,9 +135,6 @@ func (p *commitCompact) EncodeBinary(w *io.BinWriter) {
w.WriteB(p.ViewNumber) w.WriteB(p.ViewNumber)
w.WriteU16LE(p.ValidatorIndex) w.WriteU16LE(p.ValidatorIndex)
w.WriteBytes(p.Signature[:]) w.WriteBytes(p.Signature[:])
if p.stateRootEnabled {
w.WriteBytes(p.StateSignature[:])
}
w.WriteVarBytes(p.InvocationScript) w.WriteVarBytes(p.InvocationScript)
} }
@ -148,12 +142,18 @@ func (p *commitCompact) EncodeBinary(w *io.BinWriter) {
func (p *preparationCompact) DecodeBinary(r *io.BinReader) { func (p *preparationCompact) DecodeBinary(r *io.BinReader) {
p.ValidatorIndex = r.ReadU16LE() p.ValidatorIndex = r.ReadU16LE()
p.InvocationScript = r.ReadVarBytes(1024) p.InvocationScript = r.ReadVarBytes(1024)
if p.stateRootEnabled {
r.ReadBytes(p.StateRootSig[:])
}
} }
// EncodeBinary implements io.Serializable interface. // EncodeBinary implements io.Serializable interface.
func (p *preparationCompact) EncodeBinary(w *io.BinWriter) { func (p *preparationCompact) EncodeBinary(w *io.BinWriter) {
w.WriteU16LE(p.ValidatorIndex) w.WriteU16LE(p.ValidatorIndex)
w.WriteVarBytes(p.InvocationScript) w.WriteVarBytes(p.InvocationScript)
if p.stateRootEnabled {
w.WriteBytes(p.StateRootSig[:])
}
} }
// AddPayload implements payload.RecoveryMessage interface. // AddPayload implements payload.RecoveryMessage interface.
@ -170,13 +170,17 @@ func (m *recoveryMessage) AddPayload(p payload.ConsensusPayload) {
h := p.Hash() h := p.Hash()
m.preparationHash = &h m.preparationHash = &h
m.preparationPayloads = append(m.preparationPayloads, &preparationCompact{ m.preparationPayloads = append(m.preparationPayloads, &preparationCompact{
stateRootEnabled: m.stateRootEnabled,
ValidatorIndex: p.ValidatorIndex(), ValidatorIndex: p.ValidatorIndex(),
InvocationScript: p.(*Payload).Witness.InvocationScript, InvocationScript: p.(*Payload).Witness.InvocationScript,
StateRootSig: p.GetPrepareRequest().(*prepareRequest).stateRootSig,
}) })
case payload.PrepareResponseType: case payload.PrepareResponseType:
m.preparationPayloads = append(m.preparationPayloads, &preparationCompact{ m.preparationPayloads = append(m.preparationPayloads, &preparationCompact{
stateRootEnabled: m.stateRootEnabled,
ValidatorIndex: p.ValidatorIndex(), ValidatorIndex: p.ValidatorIndex(),
InvocationScript: p.(*Payload).Witness.InvocationScript, InvocationScript: p.(*Payload).Witness.InvocationScript,
StateRootSig: p.GetPrepareResponse().(*prepareResponse).stateRootSig,
}) })
if m.preparationHash == nil { if m.preparationHash == nil {
@ -187,16 +191,14 @@ func (m *recoveryMessage) AddPayload(p payload.ConsensusPayload) {
m.changeViewPayloads = append(m.changeViewPayloads, &changeViewCompact{ m.changeViewPayloads = append(m.changeViewPayloads, &changeViewCompact{
ValidatorIndex: p.ValidatorIndex(), ValidatorIndex: p.ValidatorIndex(),
OriginalViewNumber: p.ViewNumber(), OriginalViewNumber: p.ViewNumber(),
Timestamp: p.GetChangeView().Timestamp(), Timestamp: p.GetChangeView().(*changeView).timestamp,
InvocationScript: p.(*Payload).Witness.InvocationScript, InvocationScript: p.(*Payload).Witness.InvocationScript,
}) })
case payload.CommitType: case payload.CommitType:
m.commitPayloads = append(m.commitPayloads, &commitCompact{ m.commitPayloads = append(m.commitPayloads, &commitCompact{
stateRootEnabled: m.stateRootEnabled,
ValidatorIndex: p.ValidatorIndex(), ValidatorIndex: p.ValidatorIndex(),
ViewNumber: p.ViewNumber(), ViewNumber: p.ViewNumber(),
Signature: p.GetCommit().(*commit).signature, Signature: p.GetCommit().(*commit).signature,
StateSignature: p.GetCommit().(*commit).stateSig,
InvocationScript: p.(*Payload).Witness.InvocationScript, InvocationScript: p.(*Payload).Witness.InvocationScript,
}) })
} }
@ -239,6 +241,9 @@ func (m *recoveryMessage) GetPrepareResponses(p payload.ConsensusPayload, valida
for i, resp := range m.preparationPayloads { for i, resp := range m.preparationPayloads {
r := fromPayload(prepareResponseType, p.(*Payload), &prepareResponse{ r := fromPayload(prepareResponseType, p.(*Payload), &prepareResponse{
preparationHash: *m.preparationHash, preparationHash: *m.preparationHash,
stateRootSig: resp.StateRootSig,
stateRootEnabled: m.stateRootEnabled,
}) })
r.SetValidatorIndex(resp.ValidatorIndex) r.SetValidatorIndex(resp.ValidatorIndex)
r.Witness.InvocationScript = resp.InvocationScript r.Witness.InvocationScript = resp.InvocationScript
@ -277,9 +282,6 @@ func (m *recoveryMessage) GetCommits(p payload.ConsensusPayload, validators []cr
for i, c := range m.commitPayloads { for i, c := range m.commitPayloads {
cc := fromPayload(commitType, p.(*Payload), &commit{ cc := fromPayload(commitType, p.(*Payload), &commit{
signature: c.Signature, signature: c.Signature,
stateSig: c.StateSignature,
stateRootEnabled: m.stateRootEnabled,
}) })
cc.SetValidatorIndex(c.ValidatorIndex) cc.SetValidatorIndex(c.ValidatorIndex)
cc.Witness.InvocationScript = c.InvocationScript cc.Witness.InvocationScript = c.InvocationScript

View file

@ -23,7 +23,7 @@ func (m *recoveryRequest) EncodeBinary(w *io.BinWriter) {
} }
// Timestamp implements payload.RecoveryRequest interface. // Timestamp implements payload.RecoveryRequest interface.
func (m *recoveryRequest) Timestamp() uint32 { return m.timestamp } func (m *recoveryRequest) Timestamp() uint64 { return uint64(m.timestamp) * nanoInSec }
// SetTimestamp implements payload.RecoveryRequest interface. // SetTimestamp implements payload.RecoveryRequest interface.
func (m *recoveryRequest) SetTimestamp(ts uint32) { m.timestamp = ts } func (m *recoveryRequest) SetTimestamp(ts uint64) { m.timestamp = uint32(ts / nanoInSec) }

View file

@ -9,6 +9,6 @@ import (
func TestRecoveryRequest_Setters(t *testing.T) { func TestRecoveryRequest_Setters(t *testing.T) {
var r recoveryRequest var r recoveryRequest
r.SetTimestamp(123) r.SetTimestamp(123 * nanoInSec)
require.EqualValues(t, 123, r.Timestamp()) require.EqualValues(t, 123*nanoInSec, r.Timestamp())
} }

View file

@ -657,7 +657,7 @@ func (s *Server) requestStateRoot(p Peer) error {
if diff := hdrHeight - stateHeight; diff < count { if diff := hdrHeight - stateHeight; diff < count {
count = diff count = diff
} }
if count == 0 { if count <= 1 {
return nil return nil
} }
gr := &payload.GetStateRoots{ gr := &payload.GetStateRoots{
@ -852,7 +852,10 @@ func (s *Server) handleNewPayload(item cache.Hashable) {
case *state.MPTRoot: case *state.MPTRoot:
s.stateCache.Add(p) s.stateCache.Add(p)
msg := s.MkMsg(CMDStateRoot, p) msg := s.MkMsg(CMDStateRoot, p)
s.broadcastMessage(msg) // Stalling on broadcast here would mean delaying commit which
// is not good for consensus. MPTRoot is being generated once
// per block, so it shouldn't be a problem.
s.broadcastHPMessage(msg)
default: default:
s.log.Warn("unknown item type", zap.String("type", fmt.Sprintf("%T", p))) s.log.Warn("unknown item type", zap.String("type", fmt.Sprintf("%T", p)))
} }