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 }