From 11ec0db3500f94120a475b5df7f147d9ac4da5b8 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Fri, 24 Nov 2023 15:23:19 +0300 Subject: [PATCH 1/2] core, services: fix Designation-dependant service initialisation Initialise services dependant on roles designation based on N+1 block so that Genesis roles extension work properly. There's not much sence to fetch node roles information for the latest persisted block because Designation contract itself makes designated nodes responsible since the next subsequent block. A part of #3228. Signed-off-by: Anna Shaleva --- pkg/core/blockchain.go | 14 ++++++++++++-- pkg/services/notary/core_test.go | 25 +++++++++++++++++++++++++ pkg/services/notary/notary.go | 7 +++++++ pkg/services/oracle/oracle.go | 7 +++++++ pkg/services/oracle/oracle_test.go | 25 +++++++++++++++++++++++++ 5 files changed, 76 insertions(+), 2 deletions(-) diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 49f6ddffc..8cf2251f1 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -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 diff --git a/pkg/services/notary/core_test.go b/pkg/services/notary/core_test.go index 638da3887..2a00651da 100644 --- a/pkg/services/notary/core_test.go +++ b/pkg/services/notary/core_test.go @@ -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()) +} diff --git a/pkg/services/notary/notary.go b/pkg/services/notary/notary.go index a567b4d74..55ba8748d 100644 --- a/pkg/services/notary/notary.go +++ b/pkg/services/notary/notary.go @@ -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() { diff --git a/pkg/services/oracle/oracle.go b/pkg/services/oracle/oracle.go index d1212aff7..8bce1bf95 100644 --- a/pkg/services/oracle/oracle.go +++ b/pkg/services/oracle/oracle.go @@ -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 diff --git a/pkg/services/oracle/oracle_test.go b/pkg/services/oracle/oracle_test.go index acc0f6509..74b33f4cf 100644 --- a/pkg/services/oracle/oracle_test.go +++ b/pkg/services/oracle/oracle_test.go @@ -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) From 15b4e0a8cd91ce7b0c0b8a14af9c5e83267add6e Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Fri, 24 Nov 2023 15:24:02 +0300 Subject: [PATCH 2/2] services: adjust StateRoot service initialisation Perform initialisation of StateRoot service with designated StateValidator's node list in the service constructor. There's no need to wait until the next role update event, and it may lead to inaccuraces in service work on SIGHUP/server restart. A part of #3228. Signed-off-by: Anna Shaleva --- pkg/services/stateroot/service.go | 19 +++++++++++++++++++ pkg/services/stateroot/service_test.go | 19 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/pkg/services/stateroot/service.go b/pkg/services/stateroot/service.go index 5e34546e4..e7123e88c 100644 --- a/pkg/services/stateroot/service.go +++ b/pkg/services/stateroot/service.go @@ -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 +} diff --git a/pkg/services/stateroot/service_test.go b/pkg/services/stateroot/service_test.go index 18c6ea2ff..5f75d4cff 100644 --- a/pkg/services/stateroot/service_test.go +++ b/pkg/services/stateroot/service_test.go @@ -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 }