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
// ContextNonceDataLen is a length of [Context.NonceData] in bytes.
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.
@ -564,10 +567,18 @@ func (ic *Context) IsHardforkActivation(hf config.Hardfork) bool {
}
// 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{
ScriptHash: hash,
Name: name,
Item: item,
})
return nil
}

View file

@ -114,8 +114,7 @@ func Notify(ic *interop.Context) error {
if len(bytes) > MaxNotificationSize {
return fmt.Errorf("notification size shouldn't exceed %d", MaxNotificationSize)
}
ic.AddNotification(curHash, name, stackitem.DeepCopy(stackitem.NewArray(args), true).(*stackitem.Array))
return nil
return ic.AddNotification(curHash, name, stackitem.DeepCopy(stackitem.NewArray(args), true).(*stackitem.Array))
}
// 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)
}
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(ic.Block.Index))),
}))
return nil
}
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)
}
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)
}
@ -444,7 +447,10 @@ func (m *Management) updateWithData(ic *interop.Context, args []stackitem.Item)
panic(err)
}
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{}
}
@ -497,7 +503,10 @@ func (m *Management) destroy(ic *interop.Context, sis []stackitem.Item) stackite
if err != nil {
panic(err)
}
m.emitNotification(ic, contractDestroyNotificationName, hash)
err = m.emitNotification(ic, contractDestroyNotificationName, hash)
if err != nil {
panic(err)
}
return stackitem.Null{}
}
@ -681,7 +690,10 @@ func (m *Management) OnPersist(ic *interop.Context) error {
if isUpdate {
ntfName = contractUpdateNotificationName
}
m.emitNotification(ic, ntfName, cs.Hash)
err = m.emitNotification(ic, ntfName, cs.Hash)
if err != nil {
return err
}
}
return nil
@ -806,8 +818,8 @@ func (m *Management) getNextContractID(d *dao.Simple) (int32, error) {
return ret, nil
}
func (m *Management) emitNotification(ic *interop.Context, name string, hash util.Uint160) {
ic.AddNotification(m.Hash, name, stackitem.NewArray([]stackitem.Item{addrToStackItem(&hash)}))
func (m *Management) emitNotification(ic *interop.Context, name string, hash util.Uint160) error {
return ic.AddNotification(m.Hash, name, stackitem.NewArray([]stackitem.Item{addrToStackItem(&hash)}))
}
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()))
if oldCommittee != nil {
ic.AddNotification(n.Hash, "CommitteeChanged", stackitem.NewArray([]stackitem.Item{
err := ic.AddNotification(n.Hash, "CommitteeChanged", stackitem.NewArray([]stackitem.Item{
oldCommittee, newCommittee,
}))
if err != nil {
return err
}
}
}
return nil
@ -828,7 +831,8 @@ func (n *NEO) registerCandidate(ic *interop.Context, args []stackitem.Item) stac
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 {
var emitEvent = true
@ -843,16 +847,23 @@ func (n *NEO) RegisterCandidateInternal(ic *interop.Context, pub *keys.PublicKey
c.Registered = true
}
err := putConvertibleToDAO(n.ID, ic.DAO, key, c)
if err != nil {
return err
}
if emitEvent {
cache := ic.DAO.GetRWCache(n.ID).(*NeoCache)
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.NewBool(c.Registered),
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 {
@ -867,7 +878,8 @@ func (n *NEO) unregisterCandidate(ic *interop.Context, args []stackitem.Item) st
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 {
var err error
@ -887,14 +899,21 @@ func (n *NEO) UnregisterCandidateInternal(ic *interop.Context, pub *keys.PublicK
if !ok {
err = putConvertibleToDAO(n.ID, ic.DAO, key, c)
}
if err != nil {
return err
}
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.NewBool(c.Registered),
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 {
@ -907,7 +926,8 @@ func (n *NEO) vote(ic *interop.Context, args []stackitem.Item) stackitem.Item {
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 {
ok, err := runtime.CheckHashedWitness(ic, h)
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.AddNotification(n.Hash, "Vote", stackitem.NewArray([]stackitem.Item{
err = ic.AddNotification(n.Hash, "Vote", stackitem.NewArray([]stackitem.Item{
stackitem.NewByteArray(h.BytesBE()),
keyToStackItem(oldVote),
keyToStackItem(pub),
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.
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 {
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) {
ic.AddNotification(c.Hash, "Transfer", stackitem.NewArray([]stackitem.Item{
func (c *nep17TokenNative) emitTransfer(ic *interop.Context, from, to *util.Uint160, amount *big.Int) error {
return ic.AddNotification(c.Hash, "Transfer", stackitem.NewArray([]stackitem.Item{
addrToStackItem(from),
addrToStackItem(to),
stackitem.NewBigInteger(amount),

View file

@ -316,10 +316,13 @@ func (o *Oracle) FinishInternal(ic *interop.Context) error {
if err != nil {
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(req.OriginalTxID.BytesBE()),
}))
if err != nil {
return err
}
origTx, _, err := ic.DAO.GetTransaction(req.OriginalTxID)
if err != nil {
@ -422,12 +425,15 @@ func (o *Oracle) RequestInternal(ic *interop.Context, url string, filter *string
} else {
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(ic.VM.GetCallingScriptHash().BytesBE()),
stackitem.Make(url),
filterNotif,
}))
if err != nil {
return err
}
req := &state.OracleRequest{
OriginalTxID: o.getOriginalTxID(ic.DAO, ic.Tx),
GasForResponse: gas.Uint64(),