Merge pull request #3229 from nspcc-dev/fix-service-init

core, services: fix Designation-dependant service initialisation
This commit is contained in:
Roman Khimov 2023-11-24 15:47:42 +03:00 committed by GitHub
commit 7a1bf77585
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 114 additions and 2 deletions

View file

@ -332,6 +332,16 @@ func NewBlockchain(s storage.Store, cfg config.Blockchain, log *zap.Logger) (*Bl
return bc, nil
}
// GetDesignatedByRole returns a set of designated public keys for the given role
// relevant for the next block.
func (bc *Blockchain) GetDesignatedByRole(r noderoles.Role) (keys.PublicKeys, uint32, error) {
// Retrieve designated nodes starting from the next block, because the current
// block is already stored, thus, dependant services can't use PostPersist callback
// to fetch relevant information at their start.
res, h, err := bc.contracts.Designate.GetDesignatedByRole(bc.dao, r, bc.BlockHeight()+1)
return res, h, err
}
// SetOracle sets oracle module. It can safely be called on the running blockchain.
// To unregister Oracle service use SetOracle(nil).
func (bc *Blockchain) SetOracle(mod native.OracleService) {
@ -343,7 +353,7 @@ func (bc *Blockchain) SetOracle(mod native.OracleService) {
}
mod.UpdateNativeContract(orc.NEF.Script, orc.GetOracleResponseScript(),
orc.Hash, md.MD.Offset)
keys, _, err := bc.contracts.Designate.GetDesignatedByRole(bc.dao, noderoles.Oracle, bc.BlockHeight())
keys, _, err := bc.GetDesignatedByRole(noderoles.Oracle)
if err != nil {
bc.log.Error("failed to get oracle key list")
return
@ -364,7 +374,7 @@ func (bc *Blockchain) SetOracle(mod native.OracleService) {
// To unregister Notary service use SetNotary(nil).
func (bc *Blockchain) SetNotary(mod native.NotaryService) {
if mod != nil {
keys, _, err := bc.contracts.Designate.GetDesignatedByRole(bc.dao, noderoles.P2PNotary, bc.BlockHeight())
keys, _, err := bc.GetDesignatedByRole(noderoles.P2PNotary)
if err != nil {
bc.log.Error("failed to get notary key list")
return

View file

@ -747,3 +747,28 @@ func TestNotary(t *testing.T) {
}, 3*time.Second, 100*time.Millisecond)
checkFallbackTxs(t, requests, false)
}
func TestNotary_GenesisRoles(t *testing.T) {
const (
notaryPath = "./testdata/notary1.json"
notaryPass = "one"
)
w, err := wallet.NewWalletFromFile(notaryPath)
require.NoError(t, err)
require.NoError(t, w.Accounts[0].Decrypt(notaryPass, w.Scrypt))
acc := w.Accounts[0]
bc, _, _ := chain.NewMultiWithCustomConfig(t, func(c *config.Blockchain) {
c.P2PSigExtensions = true
c.Genesis.Roles = map[noderoles.Role]keys.PublicKeys{
noderoles.P2PNotary: {acc.PublicKey()},
}
})
_, ntr, _ := getTestNotary(t, bc, "./testdata/notary1.json", "one", func(tx *transaction.Transaction) error { return nil })
require.False(t, ntr.IsAuthorized())
bc.SetNotary(ntr)
require.True(t, ntr.IsAuthorized())
}

View file

@ -230,6 +230,13 @@ func (n *Notary) Shutdown() {
n.wallet.Close()
}
// IsAuthorized returns whether Notary service currently is authorized to collect
// signatures. It returnes true iff designated Notary node's account provided to
// the Notary service in decrypted state.
func (n *Notary) IsAuthorized() bool {
return n.getAccount() != nil
}
// OnNewRequest is a callback method which is called after a new notary request is added to the notary request pool.
func (n *Notary) OnNewRequest(payload *payload.P2PNotaryRequest) {
if !n.started.Load() {

View file

@ -206,6 +206,13 @@ func (o *Oracle) Start() {
go o.start()
}
// IsAuthorized returns whether Oracle service currently is authorized to collect
// signatures. It returns true iff designated Oracle node's account provided to
// the Oracle service in decrypted state.
func (o *Oracle) IsAuthorized() bool {
return o.getAccount() != nil
}
func (o *Oracle) start() {
o.requestMap <- o.pending // Guaranteed to not block, only AddRequests sends to it.
o.pending = nil

View file

@ -20,6 +20,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core"
"github.com/nspcc-dev/neo-go/pkg/core/native"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
@ -339,6 +340,30 @@ func TestOracle(t *testing.T) {
})
}
func TestOracle_GenesisRole(t *testing.T) {
const (
oraclePath = "./testdata/oracle1.json"
oraclePass = "one"
)
w, err := wallet.NewWalletFromFile(oraclePath)
require.NoError(t, err)
require.NoError(t, w.Accounts[0].Decrypt(oraclePass, w.Scrypt))
acc := w.Accounts[0]
bc, _, _ := chain.NewMultiWithCustomConfig(t, func(c *config.Blockchain) {
c.Genesis.Roles = map[noderoles.Role]keys.PublicKeys{
noderoles.Oracle: {acc.PublicKey()},
}
})
orc, err := oracle.NewOracle(getOracleConfig(t, bc, "./testdata/oracle1.json", "one", nil))
require.NoError(t, err)
require.False(t, orc.IsAuthorized())
bc.SetOracle(orc)
require.True(t, orc.IsAuthorized())
}
func TestOracleFull(t *testing.T) {
bc, validator, committee := chain.NewMultiWithCustomConfigAndStore(t, nil, nil, false)
e := neotest.NewExecutor(t, bc, validator, committee)

View file

@ -2,6 +2,7 @@ package stateroot
import (
"errors"
"fmt"
"sync"
"sync/atomic"
"time"
@ -9,6 +10,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/config"
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
"github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/stateroot"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
@ -22,6 +24,7 @@ type (
// Ledger is an interface to Blockchain sufficient for Service.
Ledger interface {
GetConfig() config.Blockchain
GetDesignatedByRole(role noderoles.Role) (keys.PublicKeys, uint32, error)
HeaderHeight() uint32
SubscribeForBlocks(ch chan *block.Block)
UnsubscribeFromBlocks(ch chan *block.Block)
@ -40,6 +43,10 @@ type (
// 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).
Shutdown()
// 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
}
service struct {
@ -116,6 +123,12 @@ func New(cfg config.StateRoot, sm *stateroot.Module, log *zap.Logger, bc Ledger,
return nil, errors.New("no wallet account could be unlocked")
}
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)
s.SetUpdateValidatorsCallback(s.updateValidators)
}
return s, nil
@ -173,3 +186,9 @@ func (s *service) updateValidators(height uint32, pubs keys.PublicKeys) {
}
}
}
// IsAuthorized implements Service interface.
func (s *service) IsAuthorized() bool {
_, acc := s.getAccount()
return acc != nil
}

View file

@ -148,6 +148,25 @@ func TestStateRoot(t *testing.T) {
require.Equal(t, h, r.Witness[0].ScriptHash())
}
func TestStateRoot_GenesisRole(t *testing.T) {
_, _, accs := newMajorityMultisigWithGAS(t, 2)
bc, _, _ := chain.NewMultiWithCustomConfig(t, func(c *config.Blockchain) {
c.Genesis.Roles = map[noderoles.Role]keys.PublicKeys{
noderoles.StateValidator: {accs[0].PublicKey(), accs[1].PublicKey()},
}
})
tmpDir := t.TempDir()
w := createAndWriteWallet(t, accs[0], filepath.Join(tmpDir, "w"), "pass")
cfg := createStateRootConfig(w.Path(), "pass")
srMod := bc.GetStateModule().(*corestate.Module) // Take full responsibility here.
srv, err := stateroot.New(cfg, srMod, zaptest.NewLogger(t), bc, nil)
require.NoError(t, err)
require.True(t, srv.IsAuthorized())
}
type memoryStore struct {
*storage.MemoryStore
}