core: restrict the number of allowed SC notifications

Close #3490.

Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
This commit is contained in:
Anna Shaleva 2024-10-23 11:56:02 +03:00
parent 7b09812069
commit 59d942ef0d
7 changed files with 80 additions and 25 deletions

View file

@ -35,6 +35,9 @@ const (
DefaultBaseExecFee = 30 DefaultBaseExecFee = 30
// ContextNonceDataLen is a length of [Context.NonceData] in bytes. // ContextNonceDataLen is a length of [Context.NonceData] in bytes.
ContextNonceDataLen = 16 ContextNonceDataLen = 16
// MaxNotificationCount is the maximum number of notifications per a single
// application execution.
MaxNotificationCount = 512
) )
// Ledger is the interface to Blockchain required for Context functionality. // Ledger is the interface to Blockchain required for Context functionality.
@ -564,10 +567,18 @@ func (ic *Context) IsHardforkActivation(hf config.Hardfork) bool {
} }
// AddNotification creates notification event and appends it to the notification list. // AddNotification creates notification event and appends it to the notification list.
func (ic *Context) AddNotification(hash util.Uint160, name string, item *stackitem.Array) { func (ic *Context) AddNotification(hash util.Uint160, name string, item *stackitem.Array) error {
if ic.IsHardforkEnabled(config.HFEchidna) {
// Do not check persisting triggers to avoid native persist failure. Do not check
// verification trigger since verification context is loaded with ReadOnly flag.
if ic.Trigger == trigger.Application && len(ic.Notifications) == MaxNotificationCount {
return fmt.Errorf("notification count shouldn't exceed %d", MaxNotificationCount)
}
}
ic.Notifications = append(ic.Notifications, state.NotificationEvent{ ic.Notifications = append(ic.Notifications, state.NotificationEvent{
ScriptHash: hash, ScriptHash: hash,
Name: name, Name: name,
Item: item, Item: item,
}) })
return nil
} }

View file

@ -114,8 +114,7 @@ func Notify(ic *interop.Context) error {
if len(bytes) > MaxNotificationSize { if len(bytes) > MaxNotificationSize {
return fmt.Errorf("notification size shouldn't exceed %d", MaxNotificationSize) return fmt.Errorf("notification size shouldn't exceed %d", MaxNotificationSize)
} }
ic.AddNotification(curHash, name, stackitem.DeepCopy(stackitem.NewArray(args), true).(*stackitem.Array)) return ic.AddNotification(curHash, name, stackitem.DeepCopy(stackitem.NewArray(args), true).(*stackitem.Array))
return nil
} }
// LoadScript takes a script and arguments from the stack and loads it into the VM. // LoadScript takes a script and arguments from the stack and loads it into the VM.

View file

@ -412,11 +412,10 @@ func (s *Designate) DesignateAsRole(ic *interop.Context, r noderoles.Role, pubs
return fmt.Errorf("failed to update Designation role data cache: %w", err) return fmt.Errorf("failed to update Designation role data cache: %w", err)
} }
ic.AddNotification(s.Hash, DesignationEventName, stackitem.NewArray([]stackitem.Item{ return ic.AddNotification(s.Hash, DesignationEventName, stackitem.NewArray([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(r))), stackitem.NewBigInteger(big.NewInt(int64(r))),
stackitem.NewBigInteger(big.NewInt(int64(ic.Block.Index))), stackitem.NewBigInteger(big.NewInt(int64(ic.Block.Index))),
})) }))
return nil
} }
func (s *Designate) getRole(item stackitem.Item) (noderoles.Role, bool) { func (s *Designate) getRole(item stackitem.Item) (noderoles.Role, bool) {

View file

@ -372,7 +372,10 @@ func (m *Management) deployWithData(ic *interop.Context, args []stackitem.Item)
panic(err) panic(err)
} }
m.callDeploy(ic, newcontract, args[2], false) m.callDeploy(ic, newcontract, args[2], false)
m.emitNotification(ic, contractDeployNotificationName, newcontract.Hash) err = m.emitNotification(ic, contractDeployNotificationName, newcontract.Hash)
if err != nil {
panic(err)
}
return contractToStack(newcontract) return contractToStack(newcontract)
} }
@ -444,7 +447,10 @@ func (m *Management) updateWithData(ic *interop.Context, args []stackitem.Item)
panic(err) panic(err)
} }
m.callDeploy(ic, contract, args[2], true) m.callDeploy(ic, contract, args[2], true)
m.emitNotification(ic, contractUpdateNotificationName, contract.Hash) err = m.emitNotification(ic, contractUpdateNotificationName, contract.Hash)
if err != nil {
panic(err)
}
return stackitem.Null{} return stackitem.Null{}
} }
@ -497,7 +503,10 @@ func (m *Management) destroy(ic *interop.Context, sis []stackitem.Item) stackite
if err != nil { if err != nil {
panic(err) panic(err)
} }
m.emitNotification(ic, contractDestroyNotificationName, hash) err = m.emitNotification(ic, contractDestroyNotificationName, hash)
if err != nil {
panic(err)
}
return stackitem.Null{} return stackitem.Null{}
} }
@ -681,7 +690,10 @@ func (m *Management) OnPersist(ic *interop.Context) error {
if isUpdate { if isUpdate {
ntfName = contractUpdateNotificationName ntfName = contractUpdateNotificationName
} }
m.emitNotification(ic, ntfName, cs.Hash) err = m.emitNotification(ic, ntfName, cs.Hash)
if err != nil {
return err
}
} }
return nil return nil
@ -806,8 +818,8 @@ func (m *Management) getNextContractID(d *dao.Simple) (int32, error) {
return ret, nil return ret, nil
} }
func (m *Management) emitNotification(ic *interop.Context, name string, hash util.Uint160) { func (m *Management) emitNotification(ic *interop.Context, name string, hash util.Uint160) error {
ic.AddNotification(m.Hash, name, stackitem.NewArray([]stackitem.Item{addrToStackItem(&hash)})) return ic.AddNotification(m.Hash, name, stackitem.NewArray([]stackitem.Item{addrToStackItem(&hash)}))
} }
func checkScriptAndMethods(ic *interop.Context, script []byte, methods []manifest.Method) error { func checkScriptAndMethods(ic *interop.Context, script []byte, methods []manifest.Method) error {

View file

@ -459,9 +459,12 @@ func (n *NEO) OnPersist(ic *interop.Context) error {
ic.DAO.PutStorageItem(n.ID, prefixCommittee, cache.committee.Bytes(ic.DAO.GetItemCtx())) ic.DAO.PutStorageItem(n.ID, prefixCommittee, cache.committee.Bytes(ic.DAO.GetItemCtx()))
if oldCommittee != nil { if oldCommittee != nil {
ic.AddNotification(n.Hash, "CommitteeChanged", stackitem.NewArray([]stackitem.Item{ err := ic.AddNotification(n.Hash, "CommitteeChanged", stackitem.NewArray([]stackitem.Item{
oldCommittee, newCommittee, oldCommittee, newCommittee,
})) }))
if err != nil {
return err
}
} }
} }
return nil return nil
@ -828,7 +831,8 @@ func (n *NEO) registerCandidate(ic *interop.Context, args []stackitem.Item) stac
return stackitem.NewBool(err == nil) return stackitem.NewBool(err == nil)
} }
// RegisterCandidateInternal registers pub as a new candidate. // RegisterCandidateInternal registers pub as a new candidate. This method must not be
// called outside of VM since it panics on critical errors.
func (n *NEO) RegisterCandidateInternal(ic *interop.Context, pub *keys.PublicKey) error { func (n *NEO) RegisterCandidateInternal(ic *interop.Context, pub *keys.PublicKey) error {
var emitEvent = true var emitEvent = true
@ -843,16 +847,23 @@ func (n *NEO) RegisterCandidateInternal(ic *interop.Context, pub *keys.PublicKey
c.Registered = true c.Registered = true
} }
err := putConvertibleToDAO(n.ID, ic.DAO, key, c) err := putConvertibleToDAO(n.ID, ic.DAO, key, c)
if err != nil {
return err
}
if emitEvent { if emitEvent {
cache := ic.DAO.GetRWCache(n.ID).(*NeoCache) cache := ic.DAO.GetRWCache(n.ID).(*NeoCache)
cache.votesChanged = true cache.votesChanged = true
ic.AddNotification(n.Hash, "CandidateStateChanged", stackitem.NewArray([]stackitem.Item{ err = ic.AddNotification(n.Hash, "CandidateStateChanged", stackitem.NewArray([]stackitem.Item{
stackitem.NewByteArray(pub.Bytes()), stackitem.NewByteArray(pub.Bytes()),
stackitem.NewBool(c.Registered), stackitem.NewBool(c.Registered),
stackitem.NewBigInteger(&c.Votes), stackitem.NewBigInteger(&c.Votes),
})) }))
if err != nil {
// Panic since it's a critical error that must abort execution.
panic(err)
} }
return err }
return nil
} }
func (n *NEO) unregisterCandidate(ic *interop.Context, args []stackitem.Item) stackitem.Item { func (n *NEO) unregisterCandidate(ic *interop.Context, args []stackitem.Item) stackitem.Item {
@ -867,7 +878,8 @@ func (n *NEO) unregisterCandidate(ic *interop.Context, args []stackitem.Item) st
return stackitem.NewBool(err == nil) return stackitem.NewBool(err == nil)
} }
// UnregisterCandidateInternal unregisters pub as a candidate. // UnregisterCandidateInternal unregisters pub as a candidate. This method must not be
// called outside of VM since it panics on critical errors.
func (n *NEO) UnregisterCandidateInternal(ic *interop.Context, pub *keys.PublicKey) error { func (n *NEO) UnregisterCandidateInternal(ic *interop.Context, pub *keys.PublicKey) error {
var err error var err error
@ -887,14 +899,21 @@ func (n *NEO) UnregisterCandidateInternal(ic *interop.Context, pub *keys.PublicK
if !ok { if !ok {
err = putConvertibleToDAO(n.ID, ic.DAO, key, c) err = putConvertibleToDAO(n.ID, ic.DAO, key, c)
} }
if err != nil {
return err
}
if emitEvent { if emitEvent {
ic.AddNotification(n.Hash, "CandidateStateChanged", stackitem.NewArray([]stackitem.Item{ err := ic.AddNotification(n.Hash, "CandidateStateChanged", stackitem.NewArray([]stackitem.Item{
stackitem.NewByteArray(pub.Bytes()), stackitem.NewByteArray(pub.Bytes()),
stackitem.NewBool(c.Registered), stackitem.NewBool(c.Registered),
stackitem.NewBigInteger(&c.Votes), stackitem.NewBigInteger(&c.Votes),
})) }))
if err != nil {
// Panic since it's a critical error that must abort execution.
panic(err)
} }
return err }
return nil
} }
func (n *NEO) vote(ic *interop.Context, args []stackitem.Item) stackitem.Item { func (n *NEO) vote(ic *interop.Context, args []stackitem.Item) stackitem.Item {
@ -907,7 +926,8 @@ func (n *NEO) vote(ic *interop.Context, args []stackitem.Item) stackitem.Item {
return stackitem.NewBool(err == nil) return stackitem.NewBool(err == nil)
} }
// VoteInternal votes from account h for validarors specified in pubs. // VoteInternal votes from account h for validarors specified in pubs. This method
// must not be called outside of VM since it panics on critical errors.
func (n *NEO) VoteInternal(ic *interop.Context, h util.Uint160, pub *keys.PublicKey) error { func (n *NEO) VoteInternal(ic *interop.Context, h util.Uint160, pub *keys.PublicKey) error {
ok, err := runtime.CheckHashedWitness(ic, h) ok, err := runtime.CheckHashedWitness(ic, h)
if err != nil { if err != nil {
@ -969,12 +989,16 @@ func (n *NEO) VoteInternal(ic *interop.Context, h util.Uint160, pub *keys.Public
} }
ic.DAO.PutStorageItem(n.ID, key, acc.Bytes(ic.DAO.GetItemCtx())) ic.DAO.PutStorageItem(n.ID, key, acc.Bytes(ic.DAO.GetItemCtx()))
ic.AddNotification(n.Hash, "Vote", stackitem.NewArray([]stackitem.Item{ err = ic.AddNotification(n.Hash, "Vote", stackitem.NewArray([]stackitem.Item{
stackitem.NewByteArray(h.BytesBE()), stackitem.NewByteArray(h.BytesBE()),
keyToStackItem(oldVote), keyToStackItem(oldVote),
keyToStackItem(pub), keyToStackItem(pub),
stackitem.NewBigInteger(&acc.Balance), stackitem.NewBigInteger(&acc.Balance),
})) }))
if err != nil {
// Panic since it's a critical error that must abort execution.
panic(err)
}
if newGas != nil { // Can be if it was already distributed in the same block. if newGas != nil { // Can be if it was already distributed in the same block.
n.GAS.mint(ic, h, newGas, true) n.GAS.mint(ic, h, newGas, true)

View file

@ -143,7 +143,11 @@ func (c *nep17TokenNative) postTransfer(ic *interop.Context, from, to *util.Uint
} }
} }
}() }()
c.emitTransfer(ic, from, to, amount) err := c.emitTransfer(ic, from, to, amount)
if err != nil {
skipPostCalls = true
panic(err)
}
if to == nil || !callOnPayment { if to == nil || !callOnPayment {
return return
} }
@ -167,8 +171,8 @@ func (c *nep17TokenNative) postTransfer(ic *interop.Context, from, to *util.Uint
} }
} }
func (c *nep17TokenNative) emitTransfer(ic *interop.Context, from, to *util.Uint160, amount *big.Int) { func (c *nep17TokenNative) emitTransfer(ic *interop.Context, from, to *util.Uint160, amount *big.Int) error {
ic.AddNotification(c.Hash, "Transfer", stackitem.NewArray([]stackitem.Item{ return ic.AddNotification(c.Hash, "Transfer", stackitem.NewArray([]stackitem.Item{
addrToStackItem(from), addrToStackItem(from),
addrToStackItem(to), addrToStackItem(to),
stackitem.NewBigInteger(amount), stackitem.NewBigInteger(amount),

View file

@ -316,10 +316,13 @@ func (o *Oracle) FinishInternal(ic *interop.Context) error {
if err != nil { if err != nil {
return ErrRequestNotFound return ErrRequestNotFound
} }
ic.AddNotification(o.Hash, "OracleResponse", stackitem.NewArray([]stackitem.Item{ err = ic.AddNotification(o.Hash, "OracleResponse", stackitem.NewArray([]stackitem.Item{
stackitem.Make(resp.ID), stackitem.Make(resp.ID),
stackitem.Make(req.OriginalTxID.BytesBE()), stackitem.Make(req.OriginalTxID.BytesBE()),
})) }))
if err != nil {
return err
}
origTx, _, err := ic.DAO.GetTransaction(req.OriginalTxID) origTx, _, err := ic.DAO.GetTransaction(req.OriginalTxID)
if err != nil { if err != nil {
@ -422,12 +425,15 @@ func (o *Oracle) RequestInternal(ic *interop.Context, url string, filter *string
} else { } else {
filterNotif = stackitem.Null{} filterNotif = stackitem.Null{}
} }
ic.AddNotification(o.Hash, "OracleRequest", stackitem.NewArray([]stackitem.Item{ err = ic.AddNotification(o.Hash, "OracleRequest", stackitem.NewArray([]stackitem.Item{
stackitem.Make(id), stackitem.Make(id),
stackitem.Make(ic.VM.GetCallingScriptHash().BytesBE()), stackitem.Make(ic.VM.GetCallingScriptHash().BytesBE()),
stackitem.Make(url), stackitem.Make(url),
filterNotif, filterNotif,
})) }))
if err != nil {
return err
}
req := &state.OracleRequest{ req := &state.OracleRequest{
OriginalTxID: o.getOriginalTxID(ic.DAO, ic.Tx), OriginalTxID: o.getOriginalTxID(ic.DAO, ic.Tx),
GasForResponse: gas.Uint64(), GasForResponse: gas.Uint64(),