Skip to content

Commit 6615b68

Browse files
committed
consensus: log rejected hashes on CV
Close #4148. Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
1 parent ef8950a commit 6615b68

5 files changed

Lines changed: 69 additions & 27 deletions

File tree

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ require (
1515
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
1616
github.com/mr-tron/base58 v1.2.0
1717
github.com/nspcc-dev/bbolt v0.0.0-20250911202005-807225ebb0c8
18-
github.com/nspcc-dev/dbft v0.4.0
18+
github.com/nspcc-dev/dbft v0.4.1-0.20260327124130-39ecd4a1a336
1919
github.com/nspcc-dev/go-ordered-json v0.0.0-20260302080601-ff7471f924b3
2020
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20260303143317-87ace720748a
2121
github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.17

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,8 @@ github.com/nspcc-dev/bbolt v0.0.0-20250911202005-807225ebb0c8 h1:lYMHisGPtL70vqC
151151
github.com/nspcc-dev/bbolt v0.0.0-20250911202005-807225ebb0c8/go.mod h1:iYl+DCkSLXgVCeQWyC+kqS9V1fAQCA74JZtptwjNYpc=
152152
github.com/nspcc-dev/dbft v0.4.0 h1:4/atD4GrrMEtrYBDiZPrPzdKZ6ws7PR/cg0M4DEdVeI=
153153
github.com/nspcc-dev/dbft v0.4.0/go.mod h1:msYlF5GIGwOZ9jUIHttBAAtiqJ29jzV8PPKKv1avXAI=
154+
github.com/nspcc-dev/dbft v0.4.1-0.20260327124130-39ecd4a1a336 h1:q8FGMjSqskV1Qt/M/9+Oki8wu8yHw+OuOViNAHiHosc=
155+
github.com/nspcc-dev/dbft v0.4.1-0.20260327124130-39ecd4a1a336/go.mod h1:wlDUe85rc9UVf4ojolXJ5mml1O/B1VFz4oGOE0Acc2E=
154156
github.com/nspcc-dev/go-ordered-json v0.0.0-20260302080601-ff7471f924b3 h1:txQ9PnyK2SW8aLmQMatGDDeZqQX8XdslrMnbcptmW5A=
155157
github.com/nspcc-dev/go-ordered-json v0.0.0-20260302080601-ff7471f924b3/go.mod h1:LtSGm9lKJLWHnB9w4q0ZPfA9bvt/D3FZcm2vHmbsfxQ=
156158
github.com/nspcc-dev/hrw/v2 v2.0.4 h1:o3Zh/2aF+IgGpvt414f46Ya20WG9u9vWxVd16ErFI8w=

pkg/consensus/change_view.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ package consensus
33
import (
44
"github.com/nspcc-dev/dbft"
55
"github.com/nspcc-dev/neo-go/pkg/io"
6+
"github.com/nspcc-dev/neo-go/pkg/util"
67
)
78

89
// changeView represents dBFT ChangeView message.
910
type changeView struct {
10-
newViewNumber byte
11-
timestamp uint64
12-
reason dbft.ChangeViewReason
11+
newViewNumber byte
12+
timestamp uint64
13+
reason dbft.ChangeViewReason
14+
rejectedHashes []util.Uint256
1315
}
1416

1517
var _ dbft.ChangeView = (*changeView)(nil)
@@ -18,12 +20,18 @@ var _ dbft.ChangeView = (*changeView)(nil)
1820
func (c *changeView) EncodeBinary(w *io.BinWriter) {
1921
w.WriteU64LE(c.timestamp)
2022
w.WriteB(byte(c.reason))
23+
if c.reason == dbft.CVTxInvalid || c.reason == dbft.CVTxRejectedByPolicy {
24+
w.WriteArray(c.rejectedHashes)
25+
}
2126
}
2227

2328
// DecodeBinary implements the io.Serializable interface.
2429
func (c *changeView) DecodeBinary(r *io.BinReader) {
2530
c.timestamp = r.ReadU64LE()
2631
c.reason = dbft.ChangeViewReason(r.ReadB())
32+
if c.reason == dbft.CVTxInvalid || c.reason == dbft.CVTxRejectedByPolicy {
33+
r.ReadArray(&c.rejectedHashes)
34+
}
2735
}
2836

2937
// NewViewNumber implements the payload.ChangeView interface.

pkg/consensus/consensus.go

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"errors"
55
"fmt"
66
"slices"
7+
"strings"
78
"sync/atomic"
89
"time"
910

@@ -270,11 +271,16 @@ func (s *service) newPrepareResponse(preparationHash util.Uint256) dbft.PrepareR
270271
}
271272
}
272273

273-
func (s *service) newChangeView(newViewNumber byte, reason dbft.ChangeViewReason, ts uint64) dbft.ChangeView {
274+
func (s *service) newChangeView(newViewNumber byte, reason dbft.ChangeViewReason, ts uint64, rejectedH ...util.Uint256) dbft.ChangeView {
275+
var rejected []util.Uint256
276+
if len(rejectedH) > 0 {
277+
rejected = append(rejected, rejectedH...)
278+
}
274279
return &changeView{
275-
newViewNumber: newViewNumber,
276-
timestamp: ts / nsInMs,
277-
reason: reason,
280+
newViewNumber: newViewNumber,
281+
timestamp: ts / nsInMs,
282+
reason: reason,
283+
rejectedHashes: rejected,
278284
}
279285
}
280286

@@ -359,7 +365,9 @@ events:
359365
zap.Stringer("type", msg.Type()),
360366
}
361367

362-
if msg.Type() == dbft.RecoveryMessageType {
368+
log := s.log.Debug
369+
switch msg.Type() {
370+
case dbft.RecoveryMessageType:
363371
rec := msg.GetRecoveryMessage().(*recoveryMessage)
364372
if rec.preparationHash == nil {
365373
req := rec.GetPrepareRequest(&msg, s.dbft.Validators, uint16(s.dbft.PrimaryIndex))
@@ -375,9 +383,23 @@ events:
375383
zap.Int("#changeview", len(rec.changeViewPayloads)),
376384
zap.Bool("#request", rec.prepareRequest != nil),
377385
zap.Bool("#hash", rec.preparationHash != nil))
386+
case dbft.ChangeViewType:
387+
cv := msg.GetChangeView().(*changeView)
388+
if len(cv.rejectedHashes) > 0 {
389+
const maxHashes = 10
390+
var rejected strings.Builder
391+
log = s.log.Warn
392+
for _, h := range cv.rejectedHashes[:min(len(cv.rejectedHashes), maxHashes)] { // don't pollute logs with too many hashes.
393+
fmt.Fprintf(&rejected, "%s ", h.StringLE())
394+
}
395+
if len(cv.rejectedHashes) > maxHashes {
396+
rejected.WriteString("...")
397+
}
398+
fields = append(fields, zap.String("rejected", rejected.String()))
399+
}
378400
}
379401

380-
s.log.Debug("received message", fields...)
402+
log("received message", fields...)
381403
s.dbft.OnReceive(&msg)
382404
case tx := <-s.transactions:
383405
s.dbft.OnTransaction(tx)
@@ -525,26 +547,26 @@ func (s *service) getTx(h util.Uint256) dbft.Transaction[util.Uint256] {
525547
return nil
526548
}
527549

528-
func (s *service) verifyBlock(b dbft.Block[util.Uint256]) bool {
550+
func (s *service) verifyBlock(b dbft.Block[util.Uint256]) (bool, util.Uint256) {
529551
coreb := &b.(*neoBlock).Block
530552

531553
if s.Chain.BlockHeight() >= coreb.Index {
532554
s.log.Warn("proposed block has already outdated")
533-
return false
555+
return false, util.Uint256{}
534556
}
535557
if s.lastTimestamp >= coreb.Timestamp {
536558
s.log.Warn("proposed block has small timestamp",
537559
zap.Uint64("ts", coreb.Timestamp),
538560
zap.Uint64("last", s.lastTimestamp))
539-
return false
561+
return false, util.Uint256{}
540562
}
541563

542564
size := coreb.GetExpectedBlockSize()
543565
if size > int(s.ProtocolConfiguration.MaxBlockSize) {
544566
s.log.Warn("proposed block size exceeds config MaxBlockSize",
545567
zap.Uint32("max size allowed", s.ProtocolConfiguration.MaxBlockSize),
546568
zap.Int("block size", size))
547-
return false
569+
return false, util.Uint256{}
548570
}
549571

550572
var fee int64
@@ -566,11 +588,11 @@ func (s *service) verifyBlock(b dbft.Block[util.Uint256]) bool {
566588
s.log.Warn("invalid transaction in proposed block",
567589
zap.Stringer("hash", tx.Hash()),
568590
zap.Error(err))
569-
return false
591+
return false, tx.Hash()
570592
}
571593
if s.Chain.BlockHeight() >= coreb.Index {
572594
s.log.Warn("proposed block has already outdated")
573-
return false
595+
return false, util.Uint256{}
574596
}
575597
}
576598

@@ -579,10 +601,10 @@ func (s *service) verifyBlock(b dbft.Block[util.Uint256]) bool {
579601
s.log.Warn("proposed block system fee exceeds config MaxBlockSystemFee",
580602
zap.Int("max system fee allowed", int(maxBlockSysFee)),
581603
zap.Int("block system fee", int(fee)))
582-
return false
604+
return false, util.Uint256{}
583605
}
584606

585-
return true
607+
return true, util.Uint256{}
586608
}
587609

588610
var (

pkg/consensus/consensus_test.go

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -397,7 +397,8 @@ func TestVerifyBlock(t *testing.T) {
397397
srv.lastTimestamp = 1
398398
t.Run("good empty", func(t *testing.T) {
399399
b := testchain.NewBlock(t, bc, 1, 0)
400-
require.True(t, srv.verifyBlock(&neoBlock{Block: *b}))
400+
ok, _ := srv.verifyBlock(&neoBlock{Block: *b})
401+
require.True(t, ok)
401402
})
402403
t.Run("good pooled tx", func(t *testing.T) {
403404
tx := transaction.New([]byte{byte(opcode.RET)}, 100000)
@@ -406,15 +407,17 @@ func TestVerifyBlock(t *testing.T) {
406407
signTx(t, srv.Chain, tx)
407408
require.NoError(t, srv.Chain.PoolTx(tx))
408409
b := testchain.NewBlock(t, bc, 1, 0, tx)
409-
require.True(t, srv.verifyBlock(&neoBlock{Block: *b}))
410+
ok, _ := srv.verifyBlock(&neoBlock{Block: *b})
411+
require.True(t, ok)
410412
})
411413
t.Run("good non-pooled tx", func(t *testing.T) {
412414
tx := transaction.New([]byte{byte(opcode.RET)}, 100000)
413415
tx.ValidUntilBlock = 1
414416
addSender(t, tx)
415417
signTx(t, srv.Chain, tx)
416418
b := testchain.NewBlock(t, bc, 1, 0, tx)
417-
require.True(t, srv.verifyBlock(&neoBlock{Block: *b}))
419+
ok, _ := srv.verifyBlock(&neoBlock{Block: *b})
420+
require.True(t, ok)
418421
})
419422
t.Run("good conflicting tx", func(t *testing.T) {
420423
initGAS := srv.Chain.GetConfig().InitialGASSupply
@@ -431,12 +434,14 @@ func TestVerifyBlock(t *testing.T) {
431434
require.NoError(t, srv.Chain.PoolTx(tx1))
432435
require.Error(t, srv.Chain.PoolTx(tx2))
433436
b := testchain.NewBlock(t, bc, 1, 0, tx2)
434-
require.True(t, srv.verifyBlock(&neoBlock{Block: *b}))
437+
ok, _ := srv.verifyBlock(&neoBlock{Block: *b})
438+
require.True(t, ok)
435439
})
436440
t.Run("bad old", func(t *testing.T) {
437441
b := testchain.NewBlock(t, bc, 1, 0)
438442
b.Index = srv.Chain.BlockHeight()
439-
require.False(t, srv.verifyBlock(&neoBlock{Block: *b}))
443+
ok, _ := srv.verifyBlock(&neoBlock{Block: *b})
444+
require.False(t, ok)
440445
})
441446
t.Run("bad big size", func(t *testing.T) {
442447
script := make([]byte, int(srv.ProtocolConfiguration.MaxBlockSize))
@@ -446,12 +451,14 @@ func TestVerifyBlock(t *testing.T) {
446451
addSender(t, tx)
447452
signTx(t, srv.Chain, tx)
448453
b := testchain.NewBlock(t, bc, 1, 0, tx)
449-
require.False(t, srv.verifyBlock(&neoBlock{Block: *b}))
454+
ok, _ := srv.verifyBlock(&neoBlock{Block: *b})
455+
require.False(t, ok)
450456
})
451457
t.Run("bad timestamp", func(t *testing.T) {
452458
b := testchain.NewBlock(t, bc, 1, 0)
453459
b.Timestamp = srv.lastTimestamp - 1
454-
require.False(t, srv.verifyBlock(&neoBlock{Block: *b}))
460+
ok, _ := srv.verifyBlock(&neoBlock{Block: *b})
461+
require.False(t, ok)
455462
})
456463
t.Run("bad tx", func(t *testing.T) {
457464
tx := transaction.New([]byte{byte(opcode.RET)}, 100000)
@@ -460,7 +467,9 @@ func TestVerifyBlock(t *testing.T) {
460467
signTx(t, srv.Chain, tx)
461468
tx.Scripts[0].InvocationScript[16] = ^tx.Scripts[0].InvocationScript[16]
462469
b := testchain.NewBlock(t, bc, 1, 0, tx)
463-
require.False(t, srv.verifyBlock(&neoBlock{Block: *b}))
470+
ok, rejected := srv.verifyBlock(&neoBlock{Block: *b})
471+
require.False(t, ok)
472+
require.Equal(t, rejected, tx.Hash())
464473
})
465474
t.Run("bad big sys fee", func(t *testing.T) {
466475
txes := make([]*transaction.Transaction, 2)
@@ -471,7 +480,8 @@ func TestVerifyBlock(t *testing.T) {
471480
signTx(t, srv.Chain, txes[i])
472481
}
473482
b := testchain.NewBlock(t, bc, 1, 0, txes...)
474-
require.False(t, srv.verifyBlock(&neoBlock{Block: *b}))
483+
ok, _ := srv.verifyBlock(&neoBlock{Block: *b})
484+
require.False(t, ok)
475485
})
476486
}
477487

0 commit comments

Comments
 (0)