rpc: move session maintenance related code out of the result.Invoke

It's server who should be responsible for iterator ID creation and
iterator registration.
This commit is contained in:
Anna Shaleva 2022-07-07 22:03:11 +03:00
parent 4581cc386b
commit 8f73ce08c8
2 changed files with 126 additions and 133 deletions

View file

@ -10,8 +10,6 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/storage" "github.com/nspcc-dev/neo-go/pkg/core/storage"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
) )
@ -30,21 +28,11 @@ type Invoke struct {
maxIteratorResultItems int maxIteratorResultItems int
Session uuid.UUID Session uuid.UUID
finalize func() finalize func()
onNewSession OnNewSession registerIterator RegisterIterator
// invocationParams is non-nil iff MPT-based iterator sessions are supported.
invocationParams *InvocationParams
} }
type OnNewSession func(sessionID string, iterators []IteratorIdentifier, params *InvocationParams, finalize func()) // RegisterIterator is a callback used to register new iterator on the server side.
type RegisterIterator func(sessionID string, item stackitem.Item, id int, finalize func()) uuid.UUID
// InvocationParams is a set of parameters used for invoke* calls.
type InvocationParams struct {
Trigger trigger.Type
Script []byte
ContractScriptHash util.Uint160
Transaction *transaction.Transaction
NextBlockHeight uint32
}
// InvokeDiag is an additional diagnostic data for invocation. // InvokeDiag is an additional diagnostic data for invocation.
type InvokeDiag struct { type InvokeDiag struct {
@ -53,7 +41,7 @@ type InvokeDiag struct {
} }
// NewInvoke returns a new Invoke structure with the given fields set. // NewInvoke returns a new Invoke structure with the given fields set.
func NewInvoke(ic *interop.Context, script []byte, faultException string, registerSession OnNewSession, maxIteratorResultItems int, params *InvocationParams) *Invoke { func NewInvoke(ic *interop.Context, script []byte, faultException string, registerIterator RegisterIterator, maxIteratorResultItems int) *Invoke {
var diag *InvokeDiag var diag *InvokeDiag
tree := ic.VM.GetInvocationTree() tree := ic.VM.GetInvocationTree()
if tree != nil { if tree != nil {
@ -75,9 +63,8 @@ func NewInvoke(ic *interop.Context, script []byte, faultException string, regist
Notifications: notifications, Notifications: notifications,
Diagnostics: diag, Diagnostics: diag,
finalize: ic.Finalize, finalize: ic.Finalize,
onNewSession: registerSession,
maxIteratorResultItems: maxIteratorResultItems, maxIteratorResultItems: maxIteratorResultItems,
invocationParams: params, registerIterator: registerIterator,
} }
} }
@ -119,15 +106,6 @@ type Iterator struct {
Truncated bool Truncated bool
} }
// IteratorIdentifier represents Iterator identifier on the server side. It is not for Client usage.
type IteratorIdentifier struct {
ID string
// Item represents Iterator stackitem. It is nil if SessionBackedByMPT is set to true.
Item stackitem.Item
// StackIndex represents Iterator stackitem index on the stack. It is valid iff Item is nil.
StackIndex int
}
// Finalize releases resources occupied by Iterators created at the script invocation. // Finalize releases resources occupied by Iterators created at the script invocation.
// This method will be called automatically on Invoke marshalling or by the Server's // This method will be called automatically on Invoke marshalling or by the Server's
// sessions handler. // sessions handler.
@ -144,9 +122,8 @@ func (r Invoke) MarshalJSON() ([]byte, error) {
err error err error
faultSep string faultSep string
arr = make([]json.RawMessage, len(r.Stack)) arr = make([]json.RawMessage, len(r.Stack))
sessionsEnabled = r.onNewSession != nil sessionsEnabled = r.registerIterator != nil
sessionID string sessionID string
iterators []IteratorIdentifier
) )
if len(r.FaultException) != 0 { if len(r.FaultException) != 0 {
faultSep = " / " faultSep = " / "
@ -156,23 +133,19 @@ arrloop:
var data []byte var data []byte
if (r.Stack[i].Type() == stackitem.InteropT) && iterator.IsIterator(r.Stack[i]) { if (r.Stack[i].Type() == stackitem.InteropT) && iterator.IsIterator(r.Stack[i]) {
if sessionsEnabled { if sessionsEnabled {
iteratorID := uuid.NewString() if sessionID == "" {
sessionID = uuid.NewString()
}
iteratorID := r.registerIterator(sessionID, r.Stack[i], i, r.finalize)
data, err = json.Marshal(iteratorAux{ data, err = json.Marshal(iteratorAux{
Type: stackitem.InteropT.String(), Type: stackitem.InteropT.String(),
Interface: iteratorInterfaceName, Interface: iteratorInterfaceName,
ID: iteratorID, ID: iteratorID.String(),
}) })
if err != nil { if err != nil {
r.FaultException += fmt.Sprintf("%sjson error: failed to marshal iterator: %v", faultSep, err) r.FaultException += fmt.Sprintf("%sjson error: failed to marshal iterator: %v", faultSep, err)
break break
} }
ident := IteratorIdentifier{ID: iteratorID}
if r.invocationParams == nil {
ident.Item = r.Stack[i]
} else {
ident.StackIndex = i
}
iterators = append(iterators, ident)
} else { } else {
iteratorValues, truncated := iterator.ValuesTruncated(r.Stack[i], r.maxIteratorResultItems) iteratorValues, truncated := iterator.ValuesTruncated(r.Stack[i], r.maxIteratorResultItems)
value := make([]json.RawMessage, len(iteratorValues)) value := make([]json.RawMessage, len(iteratorValues))
@ -203,17 +176,8 @@ arrloop:
arr[i] = data arr[i] = data
} }
if sessionsEnabled && len(iterators) != 0 { if !sessionsEnabled || sessionID == "" {
sessionID = uuid.NewString() // Call finalizer manually if iterators are disabled or there's no unnested iterators on estack.
if r.invocationParams == nil {
r.onNewSession(sessionID, iterators, nil, r.Finalize)
} else {
// Call finalizer manually if MPT-based iterator sessions are enabled.
defer r.Finalize()
r.onNewSession(sessionID, iterators, r.invocationParams, nil)
}
} else {
// Call finalizer manually if iterators are disabled or there's no iterator on stack.
defer r.Finalize() defer r.Finalize()
} }
if err == nil { if err == nil {

View file

@ -18,6 +18,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/google/uuid"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/config/netmode"
"github.com/nspcc-dev/neo-go/pkg/core" "github.com/nspcc-dev/neo-go/pkg/core"
@ -91,17 +92,38 @@ type (
} }
// session holds a set of iterators got after invoke* call with corresponding // session holds a set of iterators got after invoke* call with corresponding
// finalizer and session expiration time. // finalizer and session expiration timer.
session struct { session struct {
params *result.InvocationParams // iteratorsLock protects iteratorIdentifiers of the current session.
iteratorsLock sync.Mutex iteratorsLock sync.Mutex
iteratorIdentifiers []result.IteratorIdentifier // iteratorIdentifiers stores the set of Iterator stackitems got either from original invocation
// iterators stores the set of Iterator stackitems for the current session got from MPT-backed storage. // or from historic MPT-based invocation. In the second case, iteratorIdentifiers are supposed
// iterators is non-nil iff SessionBackedByMPT is enabled. // to be filled during the first `traverseiterator` call using corresponding params.
iterators []stackitem.Item iteratorIdentifiers []*iteratorIdentifier
// params stores invocation params for historic MPT-based iterator traversing. It is nil in case
// of default non-MPT-based sessions mechanism enabled.
params *invocationParams
timer *time.Timer timer *time.Timer
finalize func() finalize func()
} }
// iteratorIdentifier represents Iterator on the server side, holding iterator ID, Iterator stackitem
// and iterator index on stack.
iteratorIdentifier struct {
ID string
// Item represents Iterator stackitem. It is nil if SessionBackedByMPT is set to true and no `traverseiterator`
// call was called for the corresponding session.
Item stackitem.Item
// StackIndex represents Iterator stackitem index on the stack. It can be used only for SessionBackedByMPT configuration.
StackIndex int
}
// invocationParams is a set of parameters used for invoke* calls.
invocationParams struct {
Trigger trigger.Type
Script []byte
ContractScriptHash util.Uint160
Transaction *transaction.Transaction
NextBlockHeight uint32
}
) )
const ( const (
@ -1974,29 +1996,13 @@ func (s *Server) runScriptInVM(t trigger.Type, script []byte, contractScriptHash
if err != nil { if err != nil {
faultException = err.Error() faultException = err.Error()
} }
var ( var registerIterator result.RegisterIterator
registerSession result.OnNewSession
params *result.InvocationParams
)
if s.config.SessionEnabled { if s.config.SessionEnabled {
registerSession = s.registerSession registerIterator = func(sessionID string, item stackitem.Item, stackIndex int, finalize func()) uuid.UUID {
if s.config.SessionBackedByMPT { iterID := uuid.New()
params = &result.InvocationParams{
Trigger: t,
Script: script,
ContractScriptHash: contractScriptHash,
Transaction: tx,
NextBlockHeight: ic.Block.Index,
}
}
}
return result.NewInvoke(ic, script, faultException, registerSession, s.config.MaxIteratorResultItems, params), nil
}
// registerSession is a callback used to add new iterator session to the sessions list.
// It performs no check whether sessions are enabled.
func (s *Server) registerSession(sessionID string, iterators []result.IteratorIdentifier, params *result.InvocationParams, finalize func()) {
s.sessionsLock.Lock() s.sessionsLock.Lock()
sess, ok := s.sessions[sessionID]
if !ok {
timer := time.AfterFunc(time.Second*time.Duration(s.config.SessionExpirationTime), func() { timer := time.AfterFunc(time.Second*time.Duration(s.config.SessionExpirationTime), func() {
s.sessionsLock.Lock() s.sessionsLock.Lock()
defer s.sessionsLock.Unlock() defer s.sessionsLock.Unlock()
@ -2014,14 +2020,37 @@ func (s *Server) registerSession(sessionID string, iterators []result.IteratorId
delete(s.sessions, sessionID) delete(s.sessions, sessionID)
sess.iteratorsLock.Unlock() sess.iteratorsLock.Unlock()
}) })
sess := &session{ sess = &session{
params: params,
iteratorIdentifiers: iterators,
finalize: finalize, finalize: finalize,
timer: timer, timer: timer,
} }
if s.config.SessionBackedByMPT {
sess.params = &invocationParams{
Trigger: t,
Script: script,
ContractScriptHash: contractScriptHash,
Transaction: tx,
NextBlockHeight: ic.Block.Index,
}
// Call finalizer manually if MPT-based iterator sessions are enabled. If disabled, then register finalizator.
if finalize != nil {
finalize()
sess.finalize = nil
}
item = nil
}
}
sess.iteratorIdentifiers = append(sess.iteratorIdentifiers, &iteratorIdentifier{
ID: iterID.String(),
Item: item,
StackIndex: stackIndex,
})
s.sessions[sessionID] = sess s.sessions[sessionID] = sess
s.sessionsLock.Unlock() s.sessionsLock.Unlock()
return iterID
}
}
return result.NewInvoke(ic, script, faultException, registerIterator, s.config.MaxIteratorResultItems), nil
} }
func (s *Server) traverseIterator(reqParams request.Params) (interface{}, *response.Error) { func (s *Server) traverseIterator(reqParams request.Params) (interface{}, *response.Error) {
@ -2064,12 +2093,11 @@ func (s *Server) traverseIterator(reqParams request.Params) (interface{}, *respo
iVals []stackitem.Item iVals []stackitem.Item
respErr *response.Error respErr *response.Error
) )
for i, it := range session.iteratorIdentifiers { for _, it := range session.iteratorIdentifiers {
if iIDStr == it.ID { if iIDStr == it.ID {
if it.Item != nil { // If Iterator stackitem is there, then use it to retrieve iterator elements. // If SessionBackedByMPT is enabled, then use MPT-backed historic call to retrieve and traverse iterator.
iVals = iterator.Values(it.Item, count) // Otherwise, iterator stackitem is ready and can be used.
} else { // Otherwise, use MPT-backed historic call to retrieve and traverse iterator. if s.config.SessionBackedByMPT && it.Item == nil {
if len(session.iterators) == 0 {
var ( var (
b *block.Block b *block.Block
ic *interop.Context ic *interop.Context
@ -2086,18 +2114,19 @@ func (s *Server) traverseIterator(reqParams request.Params) (interface{}, *respo
} }
_ = ic.VM.Run() // No error check because FAULTed invocations could also contain iterator on stack. _ = ic.VM.Run() // No error check because FAULTed invocations could also contain iterator on stack.
stack := ic.VM.Estack().ToArray() stack := ic.VM.Estack().ToArray()
// Fill in the whole set of iterators for the current session in order not to repeat test invocation one more time for other session iterators.
for _, itID := range session.iteratorIdentifiers { for _, itID := range session.iteratorIdentifiers {
j := itID.StackIndex j := itID.StackIndex
if (stack[j].Type() != stackitem.InteropT) || !iterator.IsIterator(stack[j]) { if (stack[j].Type() != stackitem.InteropT) || !iterator.IsIterator(stack[j]) {
session.iteratorsLock.Unlock() session.iteratorsLock.Unlock()
return nil, response.NewInternalServerError(fmt.Sprintf("inconsistent historic call result: expected %s, got %s at stack position #%d", stackitem.InteropT, stack[j].Type(), j)) return nil, response.NewInternalServerError(fmt.Sprintf("inconsistent historic call result: expected %s, got %s at stack position #%d", stackitem.InteropT, stack[j].Type(), j))
} }
session.iterators = append(session.iterators, stack[j]) session.iteratorIdentifiers[j].Item = stack[j]
} }
session.finalize = ic.Finalize session.finalize = ic.Finalize
} }
iVals = iterator.Values(session.iterators[i], count) iVals = iterator.Values(it.Item, count)
}
break break
} }
} }