github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/services/stateroot/service.go (about) 1 package stateroot 2 3 import ( 4 "errors" 5 "fmt" 6 "sync" 7 "sync/atomic" 8 "time" 9 10 "github.com/nspcc-dev/neo-go/pkg/config" 11 "github.com/nspcc-dev/neo-go/pkg/config/netmode" 12 "github.com/nspcc-dev/neo-go/pkg/core/block" 13 "github.com/nspcc-dev/neo-go/pkg/core/native/noderoles" 14 "github.com/nspcc-dev/neo-go/pkg/core/state" 15 "github.com/nspcc-dev/neo-go/pkg/core/stateroot" 16 "github.com/nspcc-dev/neo-go/pkg/crypto/keys" 17 "github.com/nspcc-dev/neo-go/pkg/io" 18 "github.com/nspcc-dev/neo-go/pkg/network/payload" 19 "github.com/nspcc-dev/neo-go/pkg/wallet" 20 "go.uber.org/zap" 21 ) 22 23 type ( 24 // Ledger is an interface to Blockchain sufficient for Service. 25 Ledger interface { 26 GetConfig() config.Blockchain 27 GetDesignatedByRole(role noderoles.Role) (keys.PublicKeys, uint32, error) 28 HeaderHeight() uint32 29 SubscribeForBlocks(ch chan *block.Block) 30 UnsubscribeFromBlocks(ch chan *block.Block) 31 } 32 33 // Service represents a state root service. 34 Service interface { 35 Name() string 36 OnPayload(p *payload.Extensible) error 37 AddSignature(height uint32, validatorIndex int32, sig []byte) error 38 GetConfig() config.StateRoot 39 // Start runs service instance in a separate goroutine. 40 // The service only starts once, subsequent calls to Start are no-op. 41 Start() 42 // Shutdown stops the service. It can only be called once, subsequent calls 43 // to Shutdown on the same instance are no-op. The instance that was stopped can 44 // not be started again by calling Start (use a new instance if needed). 45 Shutdown() 46 // IsAuthorized returns whether state root service currently is authorized to sign 47 // state roots. It returns true iff designated StateValidator node's account 48 // provided to the state root service in decrypted state. 49 IsAuthorized() bool 50 } 51 52 service struct { 53 *stateroot.Module 54 chain Ledger 55 56 MainCfg config.StateRoot 57 Network netmode.Magic 58 59 log *zap.Logger 60 started atomic.Bool 61 accMtx sync.RWMutex 62 accHeight uint32 63 myIndex byte 64 wallet *wallet.Wallet 65 acc *wallet.Account 66 67 srMtx sync.Mutex 68 incompleteRoots map[uint32]*incompleteRoot 69 70 timePerBlock time.Duration 71 maxRetries int 72 relayExtensible RelayCallback 73 // blockCh is a channel used to receive block notifications from the 74 // Blockchain. It has a tiny buffer in order to avoid Blockchain blocking 75 // on block addition under the high load. 76 blockCh chan *block.Block 77 stopCh chan struct{} 78 done chan struct{} 79 } 80 ) 81 82 const ( 83 // Category is a message category for extensible payloads. 84 Category = "StateService" 85 ) 86 87 // New returns a new state root service instance using the underlying module. 88 func New(cfg config.StateRoot, sm *stateroot.Module, log *zap.Logger, bc Ledger, cb RelayCallback) (Service, error) { 89 bcConf := bc.GetConfig() 90 s := &service{ 91 Module: sm, 92 Network: bcConf.Magic, 93 chain: bc, 94 log: log, 95 incompleteRoots: make(map[uint32]*incompleteRoot), 96 blockCh: make(chan *block.Block, 1), 97 stopCh: make(chan struct{}), 98 done: make(chan struct{}), 99 timePerBlock: bcConf.TimePerBlock, 100 maxRetries: voteValidEndInc, 101 relayExtensible: cb, 102 } 103 104 s.MainCfg = cfg 105 if cfg.Enabled { 106 if bcConf.StateRootInHeader { 107 return nil, errors.New("`StateRootInHeader` should be disabled when state service is enabled") 108 } 109 var err error 110 w := cfg.UnlockWallet 111 if s.wallet, err = wallet.NewWalletFromFile(w.Path); err != nil { 112 return nil, err 113 } 114 115 haveAccount := false 116 for _, acc := range s.wallet.Accounts { 117 if err := acc.Decrypt(w.Password, s.wallet.Scrypt); err == nil { 118 haveAccount = true 119 break 120 } 121 } 122 if !haveAccount { 123 return nil, errors.New("no wallet account could be unlocked") 124 } 125 126 keys, h, err := bc.GetDesignatedByRole(noderoles.StateValidator) 127 if err != nil { 128 return nil, fmt.Errorf("failed to get designated StateValidators: %w", err) 129 } 130 s.updateValidators(h, keys) 131 132 s.SetUpdateValidatorsCallback(s.updateValidators) 133 } 134 return s, nil 135 } 136 137 // OnPayload implements Service interface. 138 func (s *service) OnPayload(ep *payload.Extensible) error { 139 m := &Message{} 140 r := io.NewBinReaderFromBuf(ep.Data) 141 m.DecodeBinary(r) 142 if r.Err != nil { 143 return r.Err 144 } 145 switch m.Type { 146 case RootT: 147 sr := m.Payload.(*state.MPTRoot) 148 if sr.Index == 0 { 149 return nil 150 } 151 err := s.AddStateRoot(sr) 152 if errors.Is(err, stateroot.ErrStateMismatch) { 153 s.log.Error("can't add SV-signed state root", zap.Error(err)) 154 return nil 155 } 156 s.srMtx.Lock() 157 ir, ok := s.incompleteRoots[sr.Index] 158 s.srMtx.Unlock() 159 if ok { 160 ir.Lock() 161 ir.isSent = true 162 ir.Unlock() 163 } 164 return err 165 case VoteT: 166 v := m.Payload.(*Vote) 167 return s.AddSignature(v.Height, v.ValidatorIndex, v.Signature) 168 } 169 return nil 170 } 171 172 func (s *service) updateValidators(height uint32, pubs keys.PublicKeys) { 173 s.accMtx.Lock() 174 defer s.accMtx.Unlock() 175 176 s.acc = nil 177 for i := range pubs { 178 if acc := s.wallet.GetAccount(pubs[i].GetScriptHash()); acc != nil { 179 err := acc.Decrypt(s.MainCfg.UnlockWallet.Password, s.wallet.Scrypt) 180 if err == nil { 181 s.acc = acc 182 s.accHeight = height 183 s.myIndex = byte(i) 184 break 185 } 186 } 187 } 188 } 189 190 // IsAuthorized implements Service interface. 191 func (s *service) IsAuthorized() bool { 192 _, acc := s.getAccount() 193 return acc != nil 194 }