github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/consensus/consensus.go (about)

     1  package consensus
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"sort"
     7  	"sync/atomic"
     8  	"time"
     9  
    10  	"github.com/nspcc-dev/dbft"
    11  	"github.com/nspcc-dev/dbft/timer"
    12  	"github.com/nspcc-dev/neo-go/pkg/config"
    13  	"github.com/nspcc-dev/neo-go/pkg/config/netmode"
    14  	coreb "github.com/nspcc-dev/neo-go/pkg/core/block"
    15  	"github.com/nspcc-dev/neo-go/pkg/core/interop"
    16  	"github.com/nspcc-dev/neo-go/pkg/core/mempool"
    17  	"github.com/nspcc-dev/neo-go/pkg/core/state"
    18  	"github.com/nspcc-dev/neo-go/pkg/core/transaction"
    19  	"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
    20  	"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
    21  	"github.com/nspcc-dev/neo-go/pkg/encoding/address"
    22  	"github.com/nspcc-dev/neo-go/pkg/io"
    23  	npayload "github.com/nspcc-dev/neo-go/pkg/network/payload"
    24  	"github.com/nspcc-dev/neo-go/pkg/smartcontract"
    25  	"github.com/nspcc-dev/neo-go/pkg/util"
    26  	"github.com/nspcc-dev/neo-go/pkg/vm/emit"
    27  	"github.com/nspcc-dev/neo-go/pkg/wallet"
    28  	"go.uber.org/zap"
    29  )
    30  
    31  // cacheMaxCapacity is the default cache capacity taken
    32  // from C# implementation https://github.com/neo-project/neo/blob/master/neo/Ledger/Blockchain.cs#L64
    33  const cacheMaxCapacity = 100
    34  
    35  // defaultTimePerBlock is a period between blocks which is used in Neo.
    36  const defaultTimePerBlock = 15 * time.Second
    37  
    38  // Number of nanoseconds in millisecond.
    39  const nsInMs = 1000000
    40  
    41  // Ledger is the interface to Blockchain sufficient for Service.
    42  type Ledger interface {
    43  	ApplyPolicyToTxSet([]*transaction.Transaction) []*transaction.Transaction
    44  	GetConfig() config.Blockchain
    45  	GetMemPool() *mempool.Pool
    46  	GetNextBlockValidators() ([]*keys.PublicKey, error)
    47  	GetStateRoot(height uint32) (*state.MPTRoot, error)
    48  	GetTransaction(util.Uint256) (*transaction.Transaction, uint32, error)
    49  	ComputeNextBlockValidators() []*keys.PublicKey
    50  	PoolTx(t *transaction.Transaction, pools ...*mempool.Pool) error
    51  	SubscribeForBlocks(ch chan *coreb.Block)
    52  	UnsubscribeFromBlocks(ch chan *coreb.Block)
    53  	GetBaseExecFee() int64
    54  	CalculateAttributesFee(tx *transaction.Transaction) int64
    55  	interop.Ledger
    56  	mempool.Feer
    57  }
    58  
    59  // BlockQueuer is an interface to the block queue manager sufficient for Service.
    60  type BlockQueuer interface {
    61  	PutBlock(block *coreb.Block) error
    62  }
    63  
    64  // Service represents a consensus instance.
    65  type Service interface {
    66  	// Name returns service name.
    67  	Name() string
    68  	// Start initializes dBFT and starts event loop for consensus service.
    69  	// It must be called only when the sufficient amount of peers are connected.
    70  	// The service only starts once, subsequent calls to Start are no-op.
    71  	Start()
    72  	// Shutdown stops dBFT event loop. It can only be called once, subsequent calls
    73  	// to Shutdown on the same instance are no-op. The instance that was stopped can
    74  	// not be started again by calling Start (use a new instance if needed).
    75  	Shutdown()
    76  
    77  	// OnPayload is a callback to notify the Service about a newly received payload.
    78  	OnPayload(p *npayload.Extensible) error
    79  	// OnTransaction is a callback to notify the Service about a newly received transaction.
    80  	OnTransaction(tx *transaction.Transaction)
    81  }
    82  
    83  type service struct {
    84  	Config
    85  
    86  	log *zap.Logger
    87  	// txx is a fifo cache which stores miner transactions.
    88  	txx  *relayCache
    89  	dbft *dbft.DBFT[util.Uint256]
    90  	// messages and transactions are channels needed to process
    91  	// everything in single thread.
    92  	messages     chan Payload
    93  	transactions chan *transaction.Transaction
    94  	// blockEvents is used to pass a new block event to the consensus
    95  	// process. It has a tiny buffer in order to avoid Blockchain blocking
    96  	// on block addition under the high load.
    97  	blockEvents  chan *coreb.Block
    98  	lastProposal []util.Uint256
    99  	wallet       *wallet.Wallet
   100  	// started is a flag set with Start method that runs an event handling
   101  	// goroutine.
   102  	started  atomic.Bool
   103  	quit     chan struct{}
   104  	finished chan struct{}
   105  	// lastTimestamp contains timestamp for the last processed block.
   106  	// We can't rely on timestamp from dbft context because it is changed
   107  	// before the block is accepted. So, in case of change view, it will contain
   108  	// an updated value.
   109  	lastTimestamp uint64
   110  }
   111  
   112  // Config is a configuration for consensus services.
   113  type Config struct {
   114  	// Logger is a logger instance.
   115  	Logger *zap.Logger
   116  	// Broadcast is a callback which is called to notify the server
   117  	// about a new consensus payload to be sent.
   118  	Broadcast func(p *npayload.Extensible)
   119  	// Chain is a Ledger instance.
   120  	Chain Ledger
   121  	// BlockQueue is a BlockQueuer instance.
   122  	BlockQueue BlockQueuer
   123  	// ProtocolConfiguration contains protocol settings.
   124  	ProtocolConfiguration config.ProtocolConfiguration
   125  	// RequestTx is a callback to which will be called
   126  	// when a node lacks transactions present in the block.
   127  	RequestTx func(h ...util.Uint256)
   128  	// StopTxFlow is a callback that is called after the consensus
   129  	// process stops accepting incoming transactions.
   130  	StopTxFlow func()
   131  	// TimePerBlock is minimal time that should pass before the next block is accepted.
   132  	TimePerBlock time.Duration
   133  	// Wallet is a local-node wallet configuration. If the path is empty, then
   134  	// no wallet will be initialized and the service will be in watch-only mode.
   135  	Wallet config.Wallet
   136  }
   137  
   138  // NewService returns a new consensus.Service instance.
   139  func NewService(cfg Config) (Service, error) {
   140  	if cfg.TimePerBlock <= 0 {
   141  		cfg.TimePerBlock = defaultTimePerBlock
   142  	}
   143  
   144  	if cfg.Logger == nil {
   145  		return nil, errors.New("empty logger")
   146  	}
   147  
   148  	srv := &service{
   149  		Config: cfg,
   150  
   151  		log:      cfg.Logger,
   152  		txx:      newFIFOCache(cacheMaxCapacity),
   153  		messages: make(chan Payload, 100),
   154  
   155  		transactions: make(chan *transaction.Transaction, 100),
   156  		blockEvents:  make(chan *coreb.Block, 1),
   157  		quit:         make(chan struct{}),
   158  		finished:     make(chan struct{}),
   159  	}
   160  
   161  	var err error
   162  
   163  	if len(cfg.Wallet.Path) > 0 {
   164  		if srv.wallet, err = wallet.NewWalletFromFile(cfg.Wallet.Path); err != nil {
   165  			return nil, err
   166  		}
   167  
   168  		// Check that the wallet password is correct for at least one account.
   169  		var ok bool
   170  		for _, acc := range srv.wallet.Accounts {
   171  			err := acc.Decrypt(srv.Config.Wallet.Password, srv.wallet.Scrypt)
   172  			if err == nil {
   173  				ok = true
   174  				break
   175  			}
   176  		}
   177  		if !ok {
   178  			return nil, errors.New("no account with provided password was found")
   179  		}
   180  	}
   181  
   182  	srv.dbft, err = dbft.New[util.Uint256](
   183  		dbft.WithTimer[util.Uint256](timer.New()),
   184  		dbft.WithLogger[util.Uint256](srv.log),
   185  		dbft.WithSecondsPerBlock[util.Uint256](cfg.TimePerBlock),
   186  		dbft.WithGetKeyPair[util.Uint256](srv.getKeyPair),
   187  		dbft.WithRequestTx(cfg.RequestTx),
   188  		dbft.WithStopTxFlow[util.Uint256](cfg.StopTxFlow),
   189  		dbft.WithGetTx[util.Uint256](srv.getTx),
   190  		dbft.WithGetVerified[util.Uint256](srv.getVerifiedTx),
   191  		dbft.WithBroadcast[util.Uint256](srv.broadcast),
   192  		dbft.WithProcessBlock[util.Uint256](srv.processBlock),
   193  		dbft.WithVerifyBlock[util.Uint256](srv.verifyBlock),
   194  		dbft.WithGetBlock[util.Uint256](srv.getBlock),
   195  		dbft.WithWatchOnly[util.Uint256](func() bool { return false }),
   196  		dbft.WithNewBlockFromContext[util.Uint256](srv.newBlockFromContext),
   197  		dbft.WithCurrentHeight[util.Uint256](cfg.Chain.BlockHeight),
   198  		dbft.WithCurrentBlockHash(cfg.Chain.CurrentBlockHash),
   199  		dbft.WithGetValidators[util.Uint256](srv.getValidators),
   200  
   201  		dbft.WithNewConsensusPayload[util.Uint256](srv.newPayload),
   202  		dbft.WithNewPrepareRequest[util.Uint256](srv.newPrepareRequest),
   203  		dbft.WithNewPrepareResponse[util.Uint256](srv.newPrepareResponse),
   204  		dbft.WithNewChangeView[util.Uint256](srv.newChangeView),
   205  		dbft.WithNewCommit[util.Uint256](srv.newCommit),
   206  		dbft.WithNewRecoveryRequest[util.Uint256](srv.newRecoveryRequest),
   207  		dbft.WithNewRecoveryMessage[util.Uint256](srv.newRecoveryMessage),
   208  		dbft.WithVerifyPrepareRequest[util.Uint256](srv.verifyRequest),
   209  		dbft.WithVerifyPrepareResponse[util.Uint256](srv.verifyResponse),
   210  	)
   211  
   212  	if err != nil {
   213  		return nil, fmt.Errorf("can't initialize dBFT: %w", err)
   214  	}
   215  
   216  	return srv, nil
   217  }
   218  
   219  var (
   220  	_ dbft.Transaction[util.Uint256] = (*transaction.Transaction)(nil)
   221  	_ dbft.Block[util.Uint256]       = (*neoBlock)(nil)
   222  )
   223  
   224  // NewPayload creates a new consensus payload for the provided network.
   225  func NewPayload(m netmode.Magic, stateRootEnabled bool) *Payload {
   226  	return &Payload{
   227  		Extensible: npayload.Extensible{
   228  			Category: npayload.ConsensusCategory,
   229  		},
   230  		message: message{
   231  			stateRootEnabled: stateRootEnabled,
   232  		},
   233  		network: m,
   234  	}
   235  }
   236  
   237  func (s *service) newPayload(c *dbft.Context[util.Uint256], t dbft.MessageType, msg any) dbft.ConsensusPayload[util.Uint256] {
   238  	cp := NewPayload(s.ProtocolConfiguration.Magic, s.ProtocolConfiguration.StateRootInHeader)
   239  	cp.BlockIndex = c.BlockIndex
   240  	cp.message.ValidatorIndex = byte(c.MyIndex)
   241  	cp.message.ViewNumber = c.ViewNumber
   242  	cp.message.Type = messageType(t)
   243  	if pr, ok := msg.(*prepareRequest); ok {
   244  		pr.prevHash = s.dbft.PrevHash
   245  		pr.version = coreb.VersionInitial
   246  	}
   247  	cp.payload = msg.(io.Serializable)
   248  
   249  	cp.Extensible.ValidBlockStart = 0
   250  	cp.Extensible.ValidBlockEnd = c.BlockIndex
   251  	cp.Extensible.Sender = c.Validators[c.MyIndex].(*publicKey).GetScriptHash()
   252  
   253  	return cp
   254  }
   255  
   256  func (s *service) newPrepareRequest(ts uint64, nonce uint64, transactionsHashes []util.Uint256) dbft.PrepareRequest[util.Uint256] {
   257  	r := &prepareRequest{
   258  		timestamp:         ts / nsInMs,
   259  		nonce:             nonce,
   260  		transactionHashes: transactionsHashes,
   261  	}
   262  	if s.ProtocolConfiguration.StateRootInHeader {
   263  		r.stateRootEnabled = true
   264  		if sr, err := s.Chain.GetStateRoot(s.dbft.BlockIndex - 1); err == nil {
   265  			r.stateRoot = sr.Root
   266  		} else {
   267  			panic(err)
   268  		}
   269  	}
   270  	return r
   271  }
   272  
   273  func (s *service) newPrepareResponse(preparationHash util.Uint256) dbft.PrepareResponse[util.Uint256] {
   274  	return &prepareResponse{
   275  		preparationHash: preparationHash,
   276  	}
   277  }
   278  
   279  func (s *service) newChangeView(newViewNumber byte, reason dbft.ChangeViewReason, ts uint64) dbft.ChangeView {
   280  	return &changeView{
   281  		newViewNumber: newViewNumber,
   282  		timestamp:     ts / nsInMs,
   283  		reason:        reason,
   284  	}
   285  }
   286  
   287  func (s *service) newCommit(signature []byte) dbft.Commit {
   288  	c := new(commit)
   289  	copy(c.signature[:], signature)
   290  	return c
   291  }
   292  
   293  func (s *service) newRecoveryRequest(ts uint64) dbft.RecoveryRequest {
   294  	return &recoveryRequest{
   295  		timestamp: ts / nsInMs,
   296  	}
   297  }
   298  
   299  func (s *service) newRecoveryMessage() dbft.RecoveryMessage[util.Uint256] {
   300  	return &recoveryMessage{
   301  		stateRootEnabled: s.ProtocolConfiguration.StateRootInHeader,
   302  	}
   303  }
   304  
   305  // Name returns service name.
   306  func (s *service) Name() string {
   307  	return "consensus"
   308  }
   309  
   310  func (s *service) Start() {
   311  	if s.started.CompareAndSwap(false, true) {
   312  		s.log.Info("starting consensus service")
   313  		b, _ := s.Chain.GetBlock(s.Chain.CurrentBlockHash()) // Can't fail, we have some current block!
   314  		s.lastTimestamp = b.Timestamp
   315  		s.dbft.Start(s.lastTimestamp * nsInMs)
   316  		go s.eventLoop()
   317  	}
   318  }
   319  
   320  // Shutdown implements the Service interface.
   321  func (s *service) Shutdown() {
   322  	if s.started.CompareAndSwap(true, false) {
   323  		s.log.Info("stopping consensus service")
   324  		close(s.quit)
   325  		<-s.finished
   326  		if s.wallet != nil {
   327  			s.wallet.Close()
   328  		}
   329  	}
   330  	_ = s.log.Sync()
   331  }
   332  
   333  func (s *service) eventLoop() {
   334  	s.Chain.SubscribeForBlocks(s.blockEvents)
   335  
   336  	// Manually sync up with potentially missed fresh blocks that may be added by blockchain
   337  	// before the subscription.
   338  	b, _ := s.Chain.GetBlock(s.Chain.CurrentBlockHash()) // Can't fail, we have some current block!
   339  	if b.Timestamp >= s.lastTimestamp {
   340  		s.handleChainBlock(b)
   341  	}
   342  events:
   343  	for {
   344  		select {
   345  		case <-s.quit:
   346  			s.dbft.Timer.Stop()
   347  			s.Chain.UnsubscribeFromBlocks(s.blockEvents)
   348  			break events
   349  		case <-s.dbft.Timer.C():
   350  			h, v := s.dbft.Timer.Height(), s.dbft.Timer.View()
   351  			s.log.Debug("timer fired",
   352  				zap.Uint32("height", h),
   353  				zap.Uint("view", uint(v)))
   354  			s.dbft.OnTimeout(h, v)
   355  		case msg := <-s.messages:
   356  			fields := []zap.Field{
   357  				zap.Uint8("from", msg.message.ValidatorIndex),
   358  				zap.Stringer("type", msg.Type()),
   359  			}
   360  
   361  			if msg.Type() == dbft.RecoveryMessageType {
   362  				rec := msg.GetRecoveryMessage().(*recoveryMessage)
   363  				if rec.preparationHash == nil {
   364  					req := rec.GetPrepareRequest(&msg, s.dbft.Validators, uint16(s.dbft.PrimaryIndex))
   365  					if req != nil {
   366  						h := req.Hash()
   367  						rec.preparationHash = &h
   368  					}
   369  				}
   370  
   371  				fields = append(fields,
   372  					zap.Int("#preparation", len(rec.preparationPayloads)),
   373  					zap.Int("#commit", len(rec.commitPayloads)),
   374  					zap.Int("#changeview", len(rec.changeViewPayloads)),
   375  					zap.Bool("#request", rec.prepareRequest != nil),
   376  					zap.Bool("#hash", rec.preparationHash != nil))
   377  			}
   378  
   379  			s.log.Debug("received message", fields...)
   380  			s.dbft.OnReceive(&msg)
   381  		case tx := <-s.transactions:
   382  			s.dbft.OnTransaction(tx)
   383  		case b := <-s.blockEvents:
   384  			s.handleChainBlock(b)
   385  		}
   386  		// Always process block event if there is any, we can add one above or external
   387  		// services can add several blocks during message processing.
   388  		var latestBlock *coreb.Block
   389  	syncLoop:
   390  		for {
   391  			select {
   392  			case latestBlock = <-s.blockEvents:
   393  			default:
   394  				break syncLoop
   395  			}
   396  		}
   397  		if latestBlock != nil {
   398  			s.handleChainBlock(latestBlock)
   399  		}
   400  	}
   401  drainLoop:
   402  	for {
   403  		select {
   404  		case <-s.messages:
   405  		case <-s.transactions:
   406  		case <-s.blockEvents:
   407  		default:
   408  			break drainLoop
   409  		}
   410  	}
   411  	close(s.messages)
   412  	close(s.transactions)
   413  	close(s.blockEvents)
   414  	close(s.finished)
   415  }
   416  
   417  func (s *service) handleChainBlock(b *coreb.Block) {
   418  	// We can get our own block here, so check for index.
   419  	if b.Index >= s.dbft.BlockIndex {
   420  		s.log.Debug("new block in the chain",
   421  			zap.Uint32("dbft index", s.dbft.BlockIndex),
   422  			zap.Uint32("chain index", s.Chain.BlockHeight()))
   423  		s.postBlock(b)
   424  		s.dbft.Reset(b.Timestamp * nsInMs)
   425  	}
   426  }
   427  
   428  func (s *service) validatePayload(p *Payload) bool {
   429  	validators := s.getValidators()
   430  	if int(p.message.ValidatorIndex) >= len(validators) {
   431  		return false
   432  	}
   433  
   434  	pub := validators[p.message.ValidatorIndex]
   435  	h := pub.(*publicKey).GetScriptHash()
   436  	return p.Sender == h
   437  }
   438  
   439  func (s *service) getKeyPair(pubs []dbft.PublicKey) (int, dbft.PrivateKey, dbft.PublicKey) {
   440  	if s.wallet != nil {
   441  		for i := range pubs {
   442  			sh := pubs[i].(*publicKey).GetScriptHash()
   443  			acc := s.wallet.GetAccount(sh)
   444  			if acc == nil {
   445  				continue
   446  			}
   447  
   448  			if !acc.CanSign() {
   449  				err := acc.Decrypt(s.Config.Wallet.Password, s.wallet.Scrypt)
   450  				if err != nil {
   451  					s.log.Fatal("can't unlock account", zap.String("address", address.Uint160ToString(sh)))
   452  					break
   453  				}
   454  			}
   455  
   456  			return i, &privateKey{PrivateKey: acc.PrivateKey()}, &publicKey{PublicKey: acc.PublicKey()}
   457  		}
   458  	}
   459  	return -1, nil, nil
   460  }
   461  
   462  func (s *service) payloadFromExtensible(ep *npayload.Extensible) *Payload {
   463  	return &Payload{
   464  		Extensible: *ep,
   465  		message: message{
   466  			stateRootEnabled: s.ProtocolConfiguration.StateRootInHeader,
   467  		},
   468  	}
   469  }
   470  
   471  // OnPayload handles Payload receive.
   472  func (s *service) OnPayload(cp *npayload.Extensible) error {
   473  	log := s.log.With(zap.Stringer("hash", cp.Hash()))
   474  	p := s.payloadFromExtensible(cp)
   475  	// decode payload data into message
   476  	if err := p.decodeData(); err != nil {
   477  		log.Info("can't decode payload data", zap.Error(err))
   478  		return nil
   479  	}
   480  
   481  	if !s.validatePayload(p) {
   482  		log.Info("can't validate payload")
   483  		return nil
   484  	}
   485  
   486  	if s.dbft == nil || !s.started.Load() {
   487  		log.Debug("dbft is inactive or not started yet")
   488  		return nil
   489  	}
   490  
   491  	s.messages <- *p
   492  	return nil
   493  }
   494  
   495  func (s *service) OnTransaction(tx *transaction.Transaction) {
   496  	if s.dbft != nil && s.started.Load() {
   497  		s.transactions <- tx
   498  	}
   499  }
   500  
   501  func (s *service) broadcast(p dbft.ConsensusPayload[util.Uint256]) {
   502  	if err := p.(*Payload).Sign(s.dbft.Priv.(*privateKey)); err != nil {
   503  		s.log.Warn("can't sign consensus payload", zap.Error(err))
   504  	}
   505  
   506  	ep := &p.(*Payload).Extensible
   507  	s.Config.Broadcast(ep)
   508  }
   509  
   510  func (s *service) getTx(h util.Uint256) dbft.Transaction[util.Uint256] {
   511  	if tx := s.txx.Get(h); tx != nil {
   512  		return tx.(*transaction.Transaction)
   513  	}
   514  
   515  	tx, _, _ := s.Config.Chain.GetTransaction(h)
   516  
   517  	// this is needed because in case of absent tx dBFT expects to
   518  	// get nil interface, not a nil pointer to any concrete type
   519  	if tx != nil {
   520  		return tx
   521  	}
   522  
   523  	return nil
   524  }
   525  
   526  func (s *service) verifyBlock(b dbft.Block[util.Uint256]) bool {
   527  	coreb := &b.(*neoBlock).Block
   528  
   529  	if s.Chain.BlockHeight() >= coreb.Index {
   530  		s.log.Warn("proposed block has already outdated")
   531  		return false
   532  	}
   533  	if s.lastTimestamp >= coreb.Timestamp {
   534  		s.log.Warn("proposed block has small timestamp",
   535  			zap.Uint64("ts", coreb.Timestamp),
   536  			zap.Uint64("last", s.lastTimestamp))
   537  		return false
   538  	}
   539  
   540  	size := coreb.GetExpectedBlockSize()
   541  	if size > int(s.ProtocolConfiguration.MaxBlockSize) {
   542  		s.log.Warn("proposed block size exceeds config MaxBlockSize",
   543  			zap.Uint32("max size allowed", s.ProtocolConfiguration.MaxBlockSize),
   544  			zap.Int("block size", size))
   545  		return false
   546  	}
   547  
   548  	var fee int64
   549  	var pool = mempool.New(len(coreb.Transactions), 0, false, nil)
   550  	var mainPool = s.Chain.GetMemPool()
   551  	for _, tx := range coreb.Transactions {
   552  		var err error
   553  
   554  		fee += tx.SystemFee
   555  		if mainPool.ContainsKey(tx.Hash()) {
   556  			err = pool.Add(tx, s.Chain)
   557  			if err == nil {
   558  				continue
   559  			}
   560  		} else {
   561  			err = s.Chain.PoolTx(tx, pool)
   562  		}
   563  		if err != nil {
   564  			s.log.Warn("invalid transaction in proposed block",
   565  				zap.Stringer("hash", tx.Hash()),
   566  				zap.Error(err))
   567  			return false
   568  		}
   569  		if s.Chain.BlockHeight() >= coreb.Index {
   570  			s.log.Warn("proposed block has already outdated")
   571  			return false
   572  		}
   573  	}
   574  
   575  	maxBlockSysFee := s.ProtocolConfiguration.MaxBlockSystemFee
   576  	if fee > maxBlockSysFee {
   577  		s.log.Warn("proposed block system fee exceeds config MaxBlockSystemFee",
   578  			zap.Int("max system fee allowed", int(maxBlockSysFee)),
   579  			zap.Int("block system fee", int(fee)))
   580  		return false
   581  	}
   582  
   583  	return true
   584  }
   585  
   586  var (
   587  	errInvalidPrevHash          = errors.New("invalid PrevHash")
   588  	errInvalidVersion           = errors.New("invalid Version")
   589  	errInvalidStateRoot         = errors.New("state root mismatch")
   590  	errInvalidTransactionsCount = errors.New("invalid transactions count")
   591  )
   592  
   593  func (s *service) verifyRequest(p dbft.ConsensusPayload[util.Uint256]) error {
   594  	req := p.GetPrepareRequest().(*prepareRequest)
   595  	if req.prevHash != s.dbft.PrevHash {
   596  		return errInvalidPrevHash
   597  	}
   598  	if req.version != coreb.VersionInitial {
   599  		return errInvalidVersion
   600  	}
   601  	if s.ProtocolConfiguration.StateRootInHeader {
   602  		sr, err := s.Chain.GetStateRoot(s.dbft.BlockIndex - 1)
   603  		if err != nil {
   604  			return err
   605  		} else if sr.Root != req.stateRoot {
   606  			return fmt.Errorf("%w: %s != %s", errInvalidStateRoot, sr.Root, req.stateRoot)
   607  		}
   608  	}
   609  	if len(req.TransactionHashes()) > int(s.ProtocolConfiguration.MaxTransactionsPerBlock) {
   610  		return fmt.Errorf("%w: max = %d, got %d", errInvalidTransactionsCount, s.ProtocolConfiguration.MaxTransactionsPerBlock, len(req.TransactionHashes()))
   611  	}
   612  	// Save lastProposal for getVerified().
   613  	s.lastProposal = req.transactionHashes
   614  
   615  	return nil
   616  }
   617  
   618  func (s *service) verifyResponse(p dbft.ConsensusPayload[util.Uint256]) error {
   619  	return nil
   620  }
   621  
   622  func (s *service) processBlock(b dbft.Block[util.Uint256]) {
   623  	bb := &b.(*neoBlock).Block
   624  	bb.Script = *(s.getBlockWitness(bb))
   625  
   626  	if err := s.BlockQueue.PutBlock(bb); err != nil {
   627  		// The block might already be added via the regular network
   628  		// interaction.
   629  		if _, errget := s.Chain.GetBlock(bb.Hash()); errget != nil {
   630  			s.log.Warn("error on enqueue block", zap.Error(err))
   631  		}
   632  	}
   633  	s.postBlock(bb)
   634  }
   635  
   636  func (s *service) postBlock(b *coreb.Block) {
   637  	if s.lastTimestamp < b.Timestamp {
   638  		s.lastTimestamp = b.Timestamp
   639  	}
   640  	s.lastProposal = nil
   641  }
   642  
   643  func (s *service) getBlockWitness(b *coreb.Block) *transaction.Witness {
   644  	dctx := s.dbft.Context
   645  	pubs := convertKeys(dctx.Validators)
   646  	sigs := make(map[*keys.PublicKey][]byte)
   647  
   648  	for i := range pubs {
   649  		if p := dctx.CommitPayloads[i]; p != nil && p.ViewNumber() == dctx.ViewNumber {
   650  			sigs[pubs[i]] = p.GetCommit().Signature()
   651  		}
   652  	}
   653  
   654  	m := s.dbft.Context.M()
   655  	verif, err := smartcontract.CreateMultiSigRedeemScript(m, pubs)
   656  	if err != nil {
   657  		s.log.Warn("can't create multisig redeem script", zap.Error(err))
   658  		return nil
   659  	}
   660  
   661  	sort.Sort(keys.PublicKeys(pubs))
   662  
   663  	buf := io.NewBufBinWriter()
   664  	for i, j := 0, 0; i < len(pubs) && j < m; i++ {
   665  		if sig, ok := sigs[pubs[i]]; ok {
   666  			emit.Bytes(buf.BinWriter, sig)
   667  			j++
   668  		}
   669  	}
   670  
   671  	return &transaction.Witness{
   672  		InvocationScript:   buf.Bytes(),
   673  		VerificationScript: verif,
   674  	}
   675  }
   676  
   677  func (s *service) getBlock(h util.Uint256) dbft.Block[util.Uint256] {
   678  	b, err := s.Chain.GetBlock(h)
   679  	if err != nil {
   680  		return nil
   681  	}
   682  
   683  	return &neoBlock{network: s.ProtocolConfiguration.Magic, Block: *b}
   684  }
   685  
   686  func (s *service) getVerifiedTx() []dbft.Transaction[util.Uint256] {
   687  	pool := s.Config.Chain.GetMemPool()
   688  
   689  	var txx []*transaction.Transaction
   690  
   691  	if s.dbft.ViewNumber > 0 && len(s.lastProposal) > 0 {
   692  		txx = make([]*transaction.Transaction, 0, len(s.lastProposal))
   693  		for i := range s.lastProposal {
   694  			if tx, ok := pool.TryGetValue(s.lastProposal[i]); ok {
   695  				txx = append(txx, tx)
   696  			}
   697  		}
   698  
   699  		if len(txx) < len(s.lastProposal)/2 {
   700  			txx = pool.GetVerifiedTransactions()
   701  		}
   702  	} else {
   703  		txx = pool.GetVerifiedTransactions()
   704  	}
   705  
   706  	if len(txx) > 0 {
   707  		txx = s.Config.Chain.ApplyPolicyToTxSet(txx)
   708  	}
   709  
   710  	res := make([]dbft.Transaction[util.Uint256], len(txx))
   711  	for i := range txx {
   712  		res[i] = txx[i]
   713  	}
   714  
   715  	return res
   716  }
   717  
   718  func (s *service) getValidators(txes ...dbft.Transaction[util.Uint256]) []dbft.PublicKey {
   719  	var (
   720  		pKeys []*keys.PublicKey
   721  		err   error
   722  	)
   723  	if txes == nil {
   724  		// getValidators with empty args is used by dbft to fill the list of
   725  		// block's validators, thus should return validators from the current
   726  		// epoch without recalculation.
   727  		pKeys, err = s.Chain.GetNextBlockValidators()
   728  	}
   729  	// getValidators with non-empty args is used by dbft to fill block's
   730  	// NextConsensus field, but NeoGo doesn't provide WithGetConsensusAddress
   731  	// callback and fills NextConsensus by itself via WithNewBlockFromContext
   732  	// callback. Thus, leave pKeys empty if txes != nil.
   733  
   734  	if err != nil {
   735  		s.log.Error("error while trying to get validators", zap.Error(err))
   736  	}
   737  
   738  	pubs := make([]dbft.PublicKey, len(pKeys))
   739  	for i := range pKeys {
   740  		pubs[i] = &publicKey{PublicKey: pKeys[i]}
   741  	}
   742  
   743  	return pubs
   744  }
   745  
   746  func convertKeys(validators []dbft.PublicKey) (pubs []*keys.PublicKey) {
   747  	pubs = make([]*keys.PublicKey, len(validators))
   748  	for i, k := range validators {
   749  		pubs[i] = k.(*publicKey).PublicKey
   750  	}
   751  
   752  	return
   753  }
   754  
   755  func (s *service) newBlockFromContext(ctx *dbft.Context[util.Uint256]) dbft.Block[util.Uint256] {
   756  	block := &neoBlock{network: s.ProtocolConfiguration.Magic}
   757  
   758  	block.Block.Timestamp = ctx.Timestamp / nsInMs
   759  	block.Block.Nonce = ctx.Nonce
   760  	block.Block.Index = ctx.BlockIndex
   761  	if s.ProtocolConfiguration.StateRootInHeader {
   762  		sr, err := s.Chain.GetStateRoot(ctx.BlockIndex - 1)
   763  		if err != nil {
   764  			s.log.Fatal(fmt.Sprintf("failed to get state root: %s", err.Error()))
   765  		}
   766  		block.StateRootEnabled = true
   767  		block.PrevStateRoot = sr.Root
   768  	}
   769  
   770  	// ComputeNextBlockValidators returns proper set of validators wrt dBFT epochs
   771  	// boundary. I.e. for the last block in the dBFT epoch this method returns the
   772  	// list of validators recalculated from the latest relevant information about
   773  	// NEO votes; in this case list of validators may differ from the one returned
   774  	// by GetNextBlockValidators. For the not-last block of dBFT epoch this method
   775  	// returns the same list as GetNextBlockValidators. Note, that by this moment
   776  	// we must be sure that previous block was successfully persisted to chain
   777  	// (i.e. PostPersist was completed for native Neo contract and PostPersist
   778  	// execution cache was persisted to s.Chain's DAO), otherwise the wrong
   779  	// (outdated, relevant for the previous dBFT epoch) value will be returned.
   780  	var validators = s.Chain.ComputeNextBlockValidators()
   781  	script, err := smartcontract.CreateDefaultMultiSigRedeemScript(validators)
   782  	if err != nil {
   783  		s.log.Fatal(fmt.Sprintf("failed to create multisignature script: %s", err.Error()))
   784  	}
   785  	block.Block.NextConsensus = hash.Hash160(script)
   786  	block.Block.PrevHash = ctx.PrevHash
   787  	block.Block.Version = coreb.VersionInitial
   788  
   789  	primaryIndex := byte(ctx.PrimaryIndex)
   790  	block.Block.PrimaryIndex = primaryIndex
   791  
   792  	// it's OK to have ctx.TransactionsHashes == nil here
   793  	hashes := make([]util.Uint256, len(ctx.TransactionHashes))
   794  	copy(hashes, ctx.TransactionHashes)
   795  	block.Block.MerkleRoot = hash.CalcMerkleRoot(hashes)
   796  
   797  	return block
   798  }