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

     1  package stateroot
     2  
     3  import (
     4  	"time"
     5  
     6  	"github.com/nspcc-dev/neo-go/pkg/core/state"
     7  	"github.com/nspcc-dev/neo-go/pkg/core/transaction"
     8  	"github.com/nspcc-dev/neo-go/pkg/io"
     9  	"github.com/nspcc-dev/neo-go/pkg/network/payload"
    10  	"github.com/nspcc-dev/neo-go/pkg/vm/emit"
    11  	"github.com/nspcc-dev/neo-go/pkg/wallet"
    12  	"go.uber.org/zap"
    13  )
    14  
    15  const (
    16  	voteValidEndInc      = 10
    17  	firstVoteResendDelay = 3 * time.Second
    18  )
    19  
    20  // Name returns service name.
    21  func (s *service) Name() string {
    22  	return "stateroot"
    23  }
    24  
    25  // Start runs service instance in a separate goroutine.
    26  // The service only starts once, subsequent calls to Start are no-op.
    27  func (s *service) Start() {
    28  	if !s.started.CompareAndSwap(false, true) {
    29  		return
    30  	}
    31  	s.log.Info("starting state validation service")
    32  	go s.run()
    33  }
    34  
    35  func (s *service) run() {
    36  	s.chain.SubscribeForBlocks(s.blockCh)
    37  runloop:
    38  	for {
    39  		select {
    40  		case b := <-s.blockCh:
    41  			r, err := s.GetStateRoot(b.Index)
    42  			if err != nil {
    43  				s.log.Error("can't get state root for new block", zap.Error(err))
    44  			} else if err := s.signAndSend(r); err != nil {
    45  				s.log.Error("can't sign or send state root", zap.Error(err))
    46  			}
    47  			s.srMtx.Lock()
    48  			delete(s.incompleteRoots, b.Index-voteValidEndInc)
    49  			s.srMtx.Unlock()
    50  		case <-s.stopCh:
    51  			break runloop
    52  		}
    53  	}
    54  	s.chain.UnsubscribeFromBlocks(s.blockCh)
    55  drainloop:
    56  	for {
    57  		select {
    58  		case <-s.blockCh:
    59  		default:
    60  			break drainloop
    61  		}
    62  	}
    63  	close(s.blockCh)
    64  	close(s.done)
    65  }
    66  
    67  // Shutdown stops the service. It can only be called once, subsequent calls
    68  // to Shutdown on the same instance are no-op. The instance that was stopped can
    69  // not be started again by calling Start (use a new instance if needed).
    70  func (s *service) Shutdown() {
    71  	if !s.started.CompareAndSwap(true, false) {
    72  		return
    73  	}
    74  	s.log.Info("stopping state validation service")
    75  	close(s.stopCh)
    76  	<-s.done
    77  	if s.wallet != nil {
    78  		s.wallet.Close()
    79  	}
    80  	_ = s.log.Sync()
    81  }
    82  
    83  func (s *service) signAndSend(r *state.MPTRoot) error {
    84  	if !s.MainCfg.Enabled {
    85  		return nil
    86  	}
    87  
    88  	myIndex, acc := s.getAccount()
    89  	if acc == nil {
    90  		return nil
    91  	}
    92  
    93  	sig := acc.SignHashable(s.Network, r)
    94  	incRoot := s.getIncompleteRoot(r.Index, myIndex)
    95  	incRoot.Lock()
    96  	defer incRoot.Unlock()
    97  	incRoot.root = r
    98  	incRoot.addSignature(acc.PublicKey(), sig)
    99  	incRoot.reverify(s.Network)
   100  	s.trySendRoot(incRoot, acc)
   101  
   102  	msg := NewMessage(VoteT, &Vote{
   103  		ValidatorIndex: int32(myIndex),
   104  		Height:         r.Index,
   105  		Signature:      sig,
   106  	})
   107  
   108  	w := io.NewBufBinWriter()
   109  	msg.EncodeBinary(w.BinWriter)
   110  	if w.Err != nil {
   111  		return w.Err
   112  	}
   113  	e := &payload.Extensible{
   114  		Category:        Category,
   115  		ValidBlockStart: r.Index,
   116  		ValidBlockEnd:   r.Index + voteValidEndInc,
   117  		Sender:          acc.ScriptHash(),
   118  		Data:            w.Bytes(),
   119  		Witness: transaction.Witness{
   120  			VerificationScript: acc.GetVerificationScript(),
   121  		},
   122  	}
   123  	sig = acc.SignHashable(s.Network, e)
   124  	buf := io.NewBufBinWriter()
   125  	emit.Bytes(buf.BinWriter, sig)
   126  	e.Witness.InvocationScript = buf.Bytes()
   127  	incRoot.myVote = e
   128  	incRoot.retries = -1
   129  	s.sendVote(incRoot)
   130  	return nil
   131  }
   132  
   133  // sendVote attempts to send a vote if it's still valid and if stateroot message
   134  // has not been sent yet. It must be called with the ir locked.
   135  func (s *service) sendVote(ir *incompleteRoot) {
   136  	if ir.isSent || ir.retries >= s.maxRetries ||
   137  		s.chain.HeaderHeight() >= ir.myVote.ValidBlockEnd {
   138  		return
   139  	}
   140  	s.relayExtensible(ir.myVote)
   141  	delay := firstVoteResendDelay
   142  	if ir.retries > 0 {
   143  		delay = s.timePerBlock << ir.retries
   144  	}
   145  	_ = time.AfterFunc(delay, func() {
   146  		ir.Lock()
   147  		s.sendVote(ir)
   148  		ir.Unlock()
   149  	})
   150  	ir.retries++
   151  }
   152  
   153  // getAccount returns the current index and account for the node running this service.
   154  func (s *service) getAccount() (byte, *wallet.Account) {
   155  	s.accMtx.RLock()
   156  	defer s.accMtx.RUnlock()
   157  	return s.myIndex, s.acc
   158  }