2021-02-01 16:00:07 +00:00
|
|
|
package stateroot
|
|
|
|
|
|
|
|
import (
|
2021-02-02 09:34:27 +00:00
|
|
|
"errors"
|
2023-11-24 12:24:02 +00:00
|
|
|
"fmt"
|
2021-02-02 09:34:27 +00:00
|
|
|
"sync"
|
2023-10-11 10:24:16 +00:00
|
|
|
"sync/atomic"
|
2021-05-07 09:19:48 +00:00
|
|
|
"time"
|
2021-02-02 09:34:27 +00:00
|
|
|
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/config"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
2021-03-03 09:37:06 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
2023-11-24 12:24:02 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
|
2021-02-01 16:00:07 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
2021-03-26 21:32:15 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/stateroot"
|
2021-02-02 09:34:27 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
2021-02-01 16:00:07 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/network/payload"
|
2021-02-02 09:34:27 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
|
|
|
"go.uber.org/zap"
|
2021-02-01 16:00:07 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type (
|
2022-04-20 18:30:09 +00:00
|
|
|
// Ledger is an interface to Blockchain sufficient for Service.
|
2022-01-13 00:19:10 +00:00
|
|
|
Ledger interface {
|
2022-12-06 13:34:38 +00:00
|
|
|
GetConfig() config.Blockchain
|
2023-11-24 12:24:02 +00:00
|
|
|
GetDesignatedByRole(role noderoles.Role) (keys.PublicKeys, uint32, error)
|
2022-01-13 00:19:10 +00:00
|
|
|
HeaderHeight() uint32
|
2022-08-19 17:47:55 +00:00
|
|
|
SubscribeForBlocks(ch chan *block.Block)
|
|
|
|
UnsubscribeFromBlocks(ch chan *block.Block)
|
2022-01-13 00:19:10 +00:00
|
|
|
}
|
|
|
|
|
2022-04-20 18:30:09 +00:00
|
|
|
// Service represents a state root service.
|
2021-02-01 16:00:07 +00:00
|
|
|
Service interface {
|
2022-04-22 08:33:56 +00:00
|
|
|
Name() string
|
2021-02-01 16:00:07 +00:00
|
|
|
OnPayload(p *payload.Extensible) error
|
2021-02-02 09:34:27 +00:00
|
|
|
AddSignature(height uint32, validatorIndex int32, sig []byte) error
|
|
|
|
GetConfig() config.StateRoot
|
2022-07-04 20:03:50 +00:00
|
|
|
// Start runs service instance in a separate goroutine.
|
|
|
|
// The service only starts once, subsequent calls to Start are no-op.
|
2022-01-12 01:11:21 +00:00
|
|
|
Start()
|
2022-07-04 20:03:50 +00:00
|
|
|
// Shutdown stops the service. It can only be called once, subsequent calls
|
|
|
|
// to Shutdown on the same instance are no-op. The instance that was stopped can
|
|
|
|
// not be started again by calling Start (use a new instance if needed).
|
2021-03-03 09:37:06 +00:00
|
|
|
Shutdown()
|
2023-11-24 12:24:02 +00:00
|
|
|
// IsAuthorized returns whether state root service currently is authorized to sign
|
|
|
|
// state roots. It returns true iff designated StateValidator node's account
|
|
|
|
// provided to the state root service in decrypted state.
|
|
|
|
IsAuthorized() bool
|
2021-02-01 16:00:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
service struct {
|
2022-01-13 23:09:16 +00:00
|
|
|
*stateroot.Module
|
2022-01-13 00:19:10 +00:00
|
|
|
chain Ledger
|
2021-02-02 09:34:27 +00:00
|
|
|
|
|
|
|
MainCfg config.StateRoot
|
|
|
|
Network netmode.Magic
|
|
|
|
|
2021-03-03 09:37:06 +00:00
|
|
|
log *zap.Logger
|
2023-10-11 10:24:16 +00:00
|
|
|
started atomic.Bool
|
2021-03-03 09:37:06 +00:00
|
|
|
accMtx sync.RWMutex
|
|
|
|
accHeight uint32
|
|
|
|
myIndex byte
|
|
|
|
wallet *wallet.Wallet
|
|
|
|
acc *wallet.Account
|
2021-02-02 09:34:27 +00:00
|
|
|
|
|
|
|
srMtx sync.Mutex
|
|
|
|
incompleteRoots map[uint32]*incompleteRoot
|
|
|
|
|
2021-05-07 09:19:48 +00:00
|
|
|
timePerBlock time.Duration
|
|
|
|
maxRetries int
|
2021-05-06 19:29:19 +00:00
|
|
|
relayExtensible RelayCallback
|
2023-03-02 13:44:18 +00:00
|
|
|
// blockCh is a channel used to receive block notifications from the
|
|
|
|
// Blockchain. It has a tiny buffer in order to avoid Blockchain blocking
|
|
|
|
// on block addition under the high load.
|
|
|
|
blockCh chan *block.Block
|
|
|
|
stopCh chan struct{}
|
|
|
|
done chan struct{}
|
2021-02-01 16:00:07 +00:00
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2022-04-20 18:30:09 +00:00
|
|
|
// Category is a message category for extensible payloads.
|
2021-02-01 16:00:07 +00:00
|
|
|
Category = "StateService"
|
|
|
|
)
|
|
|
|
|
2022-04-20 18:30:09 +00:00
|
|
|
// New returns a new state root service instance using the underlying module.
|
2022-01-13 23:09:16 +00:00
|
|
|
func New(cfg config.StateRoot, sm *stateroot.Module, log *zap.Logger, bc Ledger, cb RelayCallback) (Service, error) {
|
2021-05-07 09:19:48 +00:00
|
|
|
bcConf := bc.GetConfig()
|
2021-02-02 09:34:27 +00:00
|
|
|
s := &service{
|
2022-01-13 23:09:16 +00:00
|
|
|
Module: sm,
|
2021-05-07 09:19:48 +00:00
|
|
|
Network: bcConf.Magic,
|
2021-03-03 09:37:06 +00:00
|
|
|
chain: bc,
|
2021-02-02 09:34:27 +00:00
|
|
|
log: log,
|
|
|
|
incompleteRoots: make(map[uint32]*incompleteRoot),
|
2023-03-02 13:44:18 +00:00
|
|
|
blockCh: make(chan *block.Block, 1),
|
2022-07-04 07:59:34 +00:00
|
|
|
stopCh: make(chan struct{}),
|
2021-03-03 09:37:06 +00:00
|
|
|
done: make(chan struct{}),
|
2022-12-02 16:10:45 +00:00
|
|
|
timePerBlock: bcConf.TimePerBlock,
|
2021-05-07 09:22:29 +00:00
|
|
|
maxRetries: voteValidEndInc,
|
2021-05-06 19:29:19 +00:00
|
|
|
relayExtensible: cb,
|
2021-02-02 09:34:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
s.MainCfg = cfg
|
|
|
|
if cfg.Enabled {
|
2022-01-12 18:09:37 +00:00
|
|
|
if bcConf.StateRootInHeader {
|
|
|
|
return nil, errors.New("`StateRootInHeader` should be disabled when state service is enabled")
|
|
|
|
}
|
2021-02-02 09:34:27 +00:00
|
|
|
var err error
|
|
|
|
w := cfg.UnlockWallet
|
|
|
|
if s.wallet, err = wallet.NewWalletFromFile(w.Path); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
haveAccount := false
|
|
|
|
for _, acc := range s.wallet.Accounts {
|
2021-06-04 11:27:22 +00:00
|
|
|
if err := acc.Decrypt(w.Password, s.wallet.Scrypt); err == nil {
|
2021-02-02 09:34:27 +00:00
|
|
|
haveAccount = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !haveAccount {
|
|
|
|
return nil, errors.New("no wallet account could be unlocked")
|
|
|
|
}
|
|
|
|
|
2023-11-24 12:24:02 +00:00
|
|
|
keys, h, err := bc.GetDesignatedByRole(noderoles.StateValidator)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to get designated StateValidators: %w", err)
|
|
|
|
}
|
|
|
|
s.updateValidators(h, keys)
|
|
|
|
|
2021-02-02 09:34:27 +00:00
|
|
|
s.SetUpdateValidatorsCallback(s.updateValidators)
|
|
|
|
}
|
|
|
|
return s, nil
|
2021-02-01 16:00:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// OnPayload implements Service interface.
|
|
|
|
func (s *service) OnPayload(ep *payload.Extensible) error {
|
2021-03-25 19:11:55 +00:00
|
|
|
m := &Message{}
|
2021-02-01 16:00:07 +00:00
|
|
|
r := io.NewBinReaderFromBuf(ep.Data)
|
|
|
|
m.DecodeBinary(r)
|
|
|
|
if r.Err != nil {
|
|
|
|
return r.Err
|
|
|
|
}
|
|
|
|
switch m.Type {
|
|
|
|
case RootT:
|
|
|
|
sr := m.Payload.(*state.MPTRoot)
|
|
|
|
if sr.Index == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
2021-03-26 21:32:15 +00:00
|
|
|
err := s.AddStateRoot(sr)
|
|
|
|
if errors.Is(err, stateroot.ErrStateMismatch) {
|
|
|
|
s.log.Error("can't add SV-signed state root", zap.Error(err))
|
|
|
|
return nil
|
|
|
|
}
|
2021-05-07 10:19:40 +00:00
|
|
|
s.srMtx.Lock()
|
|
|
|
ir, ok := s.incompleteRoots[sr.Index]
|
|
|
|
s.srMtx.Unlock()
|
|
|
|
if ok {
|
|
|
|
ir.Lock()
|
|
|
|
ir.isSent = true
|
|
|
|
ir.Unlock()
|
|
|
|
}
|
2021-03-26 21:32:15 +00:00
|
|
|
return err
|
2021-02-02 09:34:27 +00:00
|
|
|
case VoteT:
|
|
|
|
v := m.Payload.(*Vote)
|
|
|
|
return s.AddSignature(v.Height, v.ValidatorIndex, v.Signature)
|
2021-02-01 16:00:07 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2021-02-02 09:34:27 +00:00
|
|
|
|
2021-03-03 09:37:06 +00:00
|
|
|
func (s *service) updateValidators(height uint32, pubs keys.PublicKeys) {
|
2021-02-02 09:34:27 +00:00
|
|
|
s.accMtx.Lock()
|
|
|
|
defer s.accMtx.Unlock()
|
|
|
|
|
|
|
|
s.acc = nil
|
|
|
|
for i := range pubs {
|
|
|
|
if acc := s.wallet.GetAccount(pubs[i].GetScriptHash()); acc != nil {
|
2021-06-04 11:27:22 +00:00
|
|
|
err := acc.Decrypt(s.MainCfg.UnlockWallet.Password, s.wallet.Scrypt)
|
2021-02-02 09:34:27 +00:00
|
|
|
if err == nil {
|
|
|
|
s.acc = acc
|
2021-03-03 09:37:06 +00:00
|
|
|
s.accHeight = height
|
2021-02-02 09:34:27 +00:00
|
|
|
s.myIndex = byte(i)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-11-24 12:24:02 +00:00
|
|
|
|
|
|
|
// IsAuthorized implements Service interface.
|
|
|
|
func (s *service) IsAuthorized() bool {
|
|
|
|
_, acc := s.getAccount()
|
|
|
|
return acc != nil
|
|
|
|
}
|