cli: reload Oracle service on USR1

Which allows to enable/disable the service, change nodes, keys and other
settings. Unfortunately, atomic.Value doesn't allow Store(nil), so we have to
store a pointer there that can point to nil interface.
This commit is contained in:
Roman Khimov 2022-07-26 21:36:37 +03:00
parent 98e2c5568c
commit 2adcf406d3
7 changed files with 85 additions and 39 deletions

View file

@ -12,6 +12,7 @@ import (
"github.com/nspcc-dev/neo-go/cli/options" "github.com/nspcc-dev/neo-go/cli/options"
"github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/config"
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
"github.com/nspcc-dev/neo-go/pkg/consensus" "github.com/nspcc-dev/neo-go/pkg/consensus"
"github.com/nspcc-dev/neo-go/pkg/core" "github.com/nspcc-dev/neo-go/pkg/core"
"github.com/nspcc-dev/neo-go/pkg/core/block" "github.com/nspcc-dev/neo-go/pkg/core/block"
@ -385,14 +386,14 @@ func restoreDB(ctx *cli.Context) error {
return nil return nil
} }
func mkOracle(config network.ServerConfig, chain *core.Blockchain, serv *network.Server, log *zap.Logger) (*oracle.Oracle, error) { func mkOracle(config config.OracleConfiguration, magic netmode.Magic, chain *core.Blockchain, serv *network.Server, log *zap.Logger) (*oracle.Oracle, error) {
if !config.OracleCfg.Enabled { if !config.Enabled {
return nil, nil return nil, nil
} }
orcCfg := oracle.Config{ orcCfg := oracle.Config{
Log: log, Log: log,
Network: config.Net, Network: magic,
MainCfg: config.OracleCfg, MainCfg: config,
Chain: chain, Chain: chain,
OnTransaction: serv.RelayTxn, OnTransaction: serv.RelayTxn,
} }
@ -492,7 +493,7 @@ func startServer(ctx *cli.Context) error {
} }
serv.AddExtensibleService(sr, stateroot.Category, sr.OnPayload) serv.AddExtensibleService(sr, stateroot.Category, sr.OnPayload)
oracleSrv, err := mkOracle(serverConfig, chain, serv, log) oracleSrv, err := mkOracle(cfg.ApplicationConfiguration.Oracle, cfg.ProtocolConfiguration.Magic, chain, serv, log)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
} }
@ -513,8 +514,9 @@ func startServer(ctx *cli.Context) error {
rpcServer.Start() rpcServer.Start()
} }
sighupCh := make(chan os.Signal, 1) sigCh := make(chan os.Signal, 1)
signal.Notify(sighupCh, syscall.SIGHUP) signal.Notify(sigCh, syscall.SIGHUP)
signal.Notify(sigCh, syscall.SIGUSR1)
fmt.Fprintln(ctx.App.Writer, Logo()) fmt.Fprintln(ctx.App.Writer, Logo())
fmt.Fprintln(ctx.App.Writer, serv.UserAgent) fmt.Fprintln(ctx.App.Writer, serv.UserAgent)
@ -527,7 +529,7 @@ Main:
case err := <-errChan: case err := <-errChan:
shutdownErr = fmt.Errorf("server error: %w", err) shutdownErr = fmt.Errorf("server error: %w", err)
cancel() cancel()
case sig := <-sighupCh: case sig := <-sigCh:
log.Info("signal received", zap.Stringer("name", sig)) log.Info("signal received", zap.Stringer("name", sig))
cfgnew, err := getConfigFromContext(ctx) cfgnew, err := getConfigFromContext(ctx)
if err != nil { if err != nil {
@ -557,10 +559,27 @@ Main:
prometheus.ShutDown() prometheus.ShutDown()
prometheus = metrics.NewPrometheusService(cfgnew.ApplicationConfiguration.Prometheus, log) prometheus = metrics.NewPrometheusService(cfgnew.ApplicationConfiguration.Prometheus, log)
go prometheus.Start() go prometheus.Start()
case syscall.SIGUSR1:
if oracleSrv != nil {
chain.SetOracle(nil)
rpcServer.SetOracleHandler(nil)
oracleSrv.Shutdown()
}
oracleSrv, err = mkOracle(cfgnew.ApplicationConfiguration.Oracle, cfgnew.ProtocolConfiguration.Magic, chain, serv, log)
if err != nil {
log.Error("failed to create oracle service", zap.Error(err))
break // Keep going.
}
if oracleSrv != nil {
rpcServer.SetOracleHandler(oracleSrv)
if serv.IsInSync() {
oracleSrv.Start()
}
}
} }
cfg = cfgnew cfg = cfgnew
case <-grace.Done(): case <-grace.Done():
signal.Stop(sighupCh) signal.Stop(sigCh)
serv.Shutdown() serv.Shutdown()
break Main break Main
} }

View file

@ -303,14 +303,28 @@ func NewBlockchain(s storage.Store, cfg config.ProtocolConfiguration, log *zap.L
// must be called before `bc.Run()` to avoid data race. // must be called before `bc.Run()` to avoid data race.
func (bc *Blockchain) SetOracle(mod native.OracleService) { func (bc *Blockchain) SetOracle(mod native.OracleService) {
orc := bc.contracts.Oracle orc := bc.contracts.Oracle
md, ok := orc.GetMethod(manifest.MethodVerify, -1) if mod != nil {
if !ok { md, ok := orc.GetMethod(manifest.MethodVerify, -1)
panic(fmt.Errorf("%s method not found", manifest.MethodVerify)) if !ok {
panic(fmt.Errorf("%s method not found", manifest.MethodVerify))
}
mod.UpdateNativeContract(orc.NEF.Script, orc.GetOracleResponseScript(),
orc.Hash, md.MD.Offset)
keys, _, err := bc.contracts.Designate.GetDesignatedByRole(bc.dao, noderoles.Oracle, bc.BlockHeight())
if err != nil {
bc.log.Error("failed to get oracle key list")
return
}
mod.UpdateOracleNodes(keys)
reqs, err := bc.contracts.Oracle.GetRequests(bc.dao)
if err != nil {
bc.log.Error("failed to get current oracle request list")
return
}
mod.AddRequests(reqs)
} }
mod.UpdateNativeContract(orc.NEF.Script, orc.GetOracleResponseScript(), orc.Module.Store(&mod)
orc.Hash, md.MD.Offset) bc.contracts.Designate.OracleService.Store(&mod)
orc.Module.Store(mod)
bc.contracts.Designate.OracleService.Store(mod)
} }
// SetNotary sets notary module. It doesn't protected by mutex and // SetNotary sets notary module. It doesn't protected by mutex and

View file

@ -238,8 +238,8 @@ func (s *Designate) updateCachedRoleData(cache *DesignationCache, d *dao.Simple,
func (s *Designate) notifyRoleChanged(v *roleData, r noderoles.Role) { func (s *Designate) notifyRoleChanged(v *roleData, r noderoles.Role) {
switch r { switch r {
case noderoles.Oracle: case noderoles.Oracle:
if orc, _ := s.OracleService.Load().(OracleService); orc != nil { if orc, _ := s.OracleService.Load().(*OracleService); orc != nil && *orc != nil {
orc.UpdateOracleNodes(v.nodes.Copy()) (*orc).UpdateOracleNodes(v.nodes.Copy())
} }
case noderoles.P2PNotary: case noderoles.P2PNotary:
if ntr, _ := s.NotaryService.Load().(NotaryService); ntr != nil { if ntr, _ := s.NotaryService.Load().(NotaryService); ntr != nil {

View file

@ -113,7 +113,10 @@ func copyOracleCache(src, dst *OracleCache) {
} }
func newOracle() *Oracle { func newOracle() *Oracle {
o := &Oracle{ContractMD: *interop.NewContractMD(nativenames.Oracle, oracleContractID)} o := &Oracle{
ContractMD: *interop.NewContractMD(nativenames.Oracle, oracleContractID),
newRequests: make(map[uint64]*state.OracleRequest),
}
defer o.UpdateHash() defer o.UpdateHash()
o.oracleScript = CreateOracleResponseScript(o.Hash) o.oracleScript = CreateOracleResponseScript(o.Hash)
@ -161,11 +164,7 @@ func (o *Oracle) GetOracleResponseScript() []byte {
// OnPersist implements the Contract interface. // OnPersist implements the Contract interface.
func (o *Oracle) OnPersist(ic *interop.Context) error { func (o *Oracle) OnPersist(ic *interop.Context) error {
var err error return nil
if o.newRequests == nil {
o.newRequests, err = o.getRequests(ic.DAO)
}
return err
} }
// PostPersist represents `postPersist` method. // PostPersist represents `postPersist` method.
@ -177,7 +176,7 @@ func (o *Oracle) PostPersist(ic *interop.Context) error {
single := big.NewInt(p) single := big.NewInt(p)
var removedIDs []uint64 var removedIDs []uint64
orc, _ := o.Module.Load().(OracleService) orc, _ := o.Module.Load().(*OracleService)
for _, tx := range ic.Block.Transactions { for _, tx := range ic.Block.Transactions {
resp := getResponse(tx) resp := getResponse(tx)
if resp == nil { if resp == nil {
@ -189,7 +188,7 @@ func (o *Oracle) PostPersist(ic *interop.Context) error {
continue continue
} }
ic.DAO.DeleteStorageItem(o.ID, reqKey) ic.DAO.DeleteStorageItem(o.ID, reqKey)
if orc != nil { if orc != nil && *orc != nil {
removedIDs = append(removedIDs, resp.ID) removedIDs = append(removedIDs, resp.ID)
} }
@ -229,8 +228,8 @@ func (o *Oracle) PostPersist(ic *interop.Context) error {
o.GAS.mint(ic, nodes[i].GetScriptHash(), &reward[i], false) o.GAS.mint(ic, nodes[i].GetScriptHash(), &reward[i], false)
} }
if len(removedIDs) != 0 && orc != nil { if len(removedIDs) != 0 {
orc.RemoveRequests(removedIDs) (*orc).RemoveRequests(removedIDs)
} }
return o.updateCache(ic.DAO) return o.updateCache(ic.DAO)
} }
@ -415,7 +414,10 @@ func (o *Oracle) PutRequestInternal(id uint64, req *state.OracleRequest, d *dao.
if err := putConvertibleToDAO(o.ID, d, reqKey, req); err != nil { if err := putConvertibleToDAO(o.ID, d, reqKey, req); err != nil {
return err return err
} }
o.newRequests[id] = req orc, _ := o.Module.Load().(*OracleService)
if orc != nil && *orc != nil {
o.newRequests[id] = req
}
// Add request ID to the id list. // Add request ID to the id list.
lst := new(IDList) lst := new(IDList)
@ -493,8 +495,8 @@ func (o *Oracle) getOriginalTxID(d *dao.Simple, tx *transaction.Transaction) uti
return tx.Hash() return tx.Hash()
} }
// getRequests returns all requests which have not been finished yet. // GetRequests returns all requests which have not been finished yet.
func (o *Oracle) getRequests(d *dao.Simple) (map[uint64]*state.OracleRequest, error) { func (o *Oracle) GetRequests(d *dao.Simple) (map[uint64]*state.OracleRequest, error) {
var reqs = make(map[uint64]*state.OracleRequest) var reqs = make(map[uint64]*state.OracleRequest)
var err error var err error
d.Seek(o.ID, storage.SeekRange{Prefix: prefixRequest}, func(k, v []byte) bool { d.Seek(o.ID, storage.SeekRange{Prefix: prefixRequest}, func(k, v []byte) bool {
@ -534,8 +536,8 @@ func (o *Oracle) getConvertibleFromDAO(d *dao.Simple, key []byte, item stackitem
// updateCache updates cached Oracle values if they've been changed. // updateCache updates cached Oracle values if they've been changed.
func (o *Oracle) updateCache(d *dao.Simple) error { func (o *Oracle) updateCache(d *dao.Simple) error {
orc, _ := o.Module.Load().(OracleService) orc, _ := o.Module.Load().(*OracleService)
if orc == nil { if orc == nil || *orc == nil {
return nil return nil
} }
@ -547,7 +549,7 @@ func (o *Oracle) updateCache(d *dao.Simple) error {
delete(reqs, id) delete(reqs, id)
} }
} }
orc.AddRequests(reqs) (*orc).AddRequests(reqs)
return nil return nil
} }

View file

@ -186,6 +186,7 @@ func (o *Oracle) Shutdown() {
if !o.running { if !o.running {
return return
} }
o.Log.Info("stopping oracle service")
o.running = false o.running = false
close(o.close) close(o.close)
o.ResponseHandler.Shutdown() o.ResponseHandler.Shutdown()

View file

@ -121,8 +121,8 @@ func TestCreateResponseTx(t *testing.T) {
Result: []byte{0}, Result: []byte{0},
} }
cInvoker.Invoke(t, stackitem.Null{}, "requestURL", req.URL, *req.Filter, req.CallbackMethod, req.UserData, int64(req.GasForResponse)) cInvoker.Invoke(t, stackitem.Null{}, "requestURL", req.URL, *req.Filter, req.CallbackMethod, req.UserData, int64(req.GasForResponse))
orc.UpdateOracleNodes(keys.PublicKeys{acc.PrivateKey().PublicKey()})
bc.SetOracle(orc) bc.SetOracle(orc)
orc.UpdateOracleNodes(keys.PublicKeys{acc.PrivateKey().PublicKey()})
tx, err = orc.CreateResponseTx(int64(req.GasForResponse), 1, resp) tx, err = orc.CreateResponseTx(int64(req.GasForResponse), 1, resp)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, 166, tx.Size()) assert.Equal(t, 166, tx.Size())

View file

@ -122,7 +122,7 @@ type (
network netmode.Magic network netmode.Magic
stateRootEnabled bool stateRootEnabled bool
coreServer *network.Server coreServer *network.Server
oracle OracleHandler oracle *atomic.Value
log *zap.Logger log *zap.Logger
https *http.Server https *http.Server
shutdown chan struct{} shutdown chan struct{}
@ -275,6 +275,10 @@ func New(chain Ledger, conf config.RPC, coreServer *network.Server,
log.Info("SessionPoolSize is not set or wrong, setting default value", zap.Int("SessionPoolSize", defaultSessionPoolSize)) log.Info("SessionPoolSize is not set or wrong, setting default value", zap.Int("SessionPoolSize", defaultSessionPoolSize))
} }
} }
var oracleWrapped = new(atomic.Value)
if orc != nil {
oracleWrapped.Store(&orc)
}
return Server{ return Server{
Server: httpServer, Server: httpServer,
chain: chain, chain: chain,
@ -284,7 +288,7 @@ func New(chain Ledger, conf config.RPC, coreServer *network.Server,
stateRootEnabled: protoCfg.StateRootInHeader, stateRootEnabled: protoCfg.StateRootInHeader,
coreServer: coreServer, coreServer: coreServer,
log: log, log: log,
oracle: orc, oracle: oracleWrapped,
https: tlsServer, https: tlsServer,
shutdown: make(chan struct{}), shutdown: make(chan struct{}),
started: atomic.NewBool(false), started: atomic.NewBool(false),
@ -400,6 +404,11 @@ func (s *Server) Shutdown() {
<-s.executionCh <-s.executionCh
} }
// SetOracleHandler allows to update oracle handler used by the Server.
func (s *Server) SetOracleHandler(orc OracleHandler) {
s.oracle.Store(&orc)
}
func (s *Server) handleHTTPRequest(w http.ResponseWriter, httpRequest *http.Request) { func (s *Server) handleHTTPRequest(w http.ResponseWriter, httpRequest *http.Request) {
req := params.NewRequest() req := params.NewRequest()
@ -2328,7 +2337,8 @@ func getRelayResult(err error, hash util.Uint256) (interface{}, *neorpc.Error) {
} }
func (s *Server) submitOracleResponse(ps params.Params) (interface{}, *neorpc.Error) { func (s *Server) submitOracleResponse(ps params.Params) (interface{}, *neorpc.Error) {
if s.oracle == nil { oracle := s.oracle.Load().(*OracleHandler)
if oracle == nil || *oracle == nil {
return nil, neorpc.NewRPCError("Oracle is not enabled", "") return nil, neorpc.NewRPCError("Oracle is not enabled", "")
} }
var pub *keys.PublicKey var pub *keys.PublicKey
@ -2355,7 +2365,7 @@ func (s *Server) submitOracleResponse(ps params.Params) (interface{}, *neorpc.Er
if !pub.Verify(msgSig, hash.Sha256(data).BytesBE()) { if !pub.Verify(msgSig, hash.Sha256(data).BytesBE()) {
return nil, neorpc.NewRPCError("Invalid request signature", "") return nil, neorpc.NewRPCError("Invalid request signature", "")
} }
s.oracle.AddResponse(pub, uint64(reqID), txSig) (*oracle).AddResponse(pub, uint64(reqID), txSig)
return json.RawMessage([]byte("{}")), nil return json.RawMessage([]byte("{}")), nil
} }