oracle: integrate module in core and RPC

1. Initialization is performed via `Blockchain` methods.
2. Native Oracle contract updates list of oracle nodes
  and in-fly requests in `PostPersist`.
3. RPC uses Oracle module directly.
This commit is contained in:
Evgenii Stratonikov 2020-09-28 14:58:04 +03:00 committed by Evgeniy Stratonikov
parent 7e16bea126
commit 43e4d3af88
31 changed files with 590 additions and 44 deletions

View file

@ -7,7 +7,9 @@ import (
"math"
"math/big"
"strings"
"sync/atomic"
"github.com/nspcc-dev/neo-go/pkg/core/blockchainer/services"
"github.com/nspcc-dev/neo-go/pkg/core/dao"
"github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/interop/contract"
@ -36,6 +38,11 @@ type Oracle struct {
Desig *Designate
oracleScript []byte
// Module is an oracle module capable of talking with the external world.
Module atomic.Value
// newRequests contains new requests created during current block.
newRequests map[uint64]*state.OracleRequest
}
const (
@ -112,7 +119,11 @@ func (o *Oracle) GetOracleResponseScript() []byte {
// OnPersist implements Contract interface.
func (o *Oracle) OnPersist(ic *interop.Context) error {
return nil
var err error
if o.newRequests == nil {
o.newRequests, err = o.getRequests(ic.DAO)
}
return err
}
// PostPersist represents `postPersist` method.
@ -120,6 +131,9 @@ func (o *Oracle) PostPersist(ic *interop.Context) error {
var nodes keys.PublicKeys
var reward []big.Int
single := new(big.Int).SetUint64(oracleRequestPrice)
var removedIDs []uint64
orc, _ := o.Module.Load().(services.Oracle)
for _, tx := range ic.Block.Transactions {
resp := getResponse(tx)
if resp == nil {
@ -133,6 +147,9 @@ func (o *Oracle) PostPersist(ic *interop.Context) error {
if err := ic.DAO.DeleteStorageItem(o.ContractID, reqKey); err != nil {
return err
}
if orc != nil {
removedIDs = append(removedIDs, resp.ID)
}
idKey := makeIDListKey(req.URL)
idList := new(IDList)
@ -170,7 +187,11 @@ func (o *Oracle) PostPersist(ic *interop.Context) error {
for i := range reward {
o.GAS.mint(ic, nodes[i].GetScriptHash(), &reward[i], false)
}
return nil
if len(removedIDs) != 0 && orc != nil {
orc.RemoveRequests(removedIDs)
}
return o.updateCache(ic.DAO)
}
// Metadata returns contract metadata.
@ -338,6 +359,7 @@ func (o *Oracle) PutRequestInternal(id uint64, req *state.OracleRequest, d dao.D
if err := d.PutStorageItem(o.ContractID, reqKey, reqItem); err != nil {
return err
}
o.newRequests[id] = req
// Add request ID to the id list.
lst := new(IDList)
@ -393,6 +415,29 @@ func (o *Oracle) getOriginalTxID(d dao.DAO, tx *transaction.Transaction) util.Ui
return tx.Hash()
}
// getRequests returns all requests which have not been finished yet.
func (o *Oracle) getRequests(d dao.DAO) (map[uint64]*state.OracleRequest, error) {
m, err := d.GetStorageItemsWithPrefix(o.ContractID, prefixRequest)
if err != nil {
return nil, err
}
reqs := make(map[uint64]*state.OracleRequest, len(m))
for k, si := range m {
if len(k) != 8 {
return nil, errors.New("invalid request ID")
}
r := io.NewBinReaderFromBuf(si.Value)
req := new(state.OracleRequest)
req.DecodeBinary(r)
if r.Err != nil {
return nil, r.Err
}
id := binary.LittleEndian.Uint64([]byte(k))
reqs[id] = req
}
return reqs, nil
}
func makeRequestKey(id uint64) []byte {
k := make([]byte, 9)
k[0] = prefixRequest[0]
@ -407,3 +452,22 @@ func makeIDListKey(url string) []byte {
func (o *Oracle) getSerializableFromDAO(d dao.DAO, key []byte, item io.Serializable) error {
return getSerializableFromDAO(o.ContractID, d, key, item)
}
// updateCache updates cached Oracle values if they've been changed
func (o *Oracle) updateCache(d dao.DAO) error {
orc, _ := o.Module.Load().(services.Oracle)
if orc == nil {
return nil
}
reqs := o.newRequests
o.newRequests = make(map[uint64]*state.OracleRequest)
for id := range reqs {
key := makeRequestKey(id)
if si := d.GetStorageItem(o.ContractID, key); si == nil { // tx has failed
delete(reqs, id)
}
}
orc.AddRequests(reqs)
return nil
}