Skip to content

Commit 59d942e

Browse files
committed
core: restrict the number of allowed SC notifications
Close #3490. Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
1 parent 7b09812 commit 59d942e

File tree

7 files changed

+80
-25
lines changed

7 files changed

+80
-25
lines changed

pkg/core/interop/context.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ const (
3535
DefaultBaseExecFee = 30
3636
// ContextNonceDataLen is a length of [Context.NonceData] in bytes.
3737
ContextNonceDataLen = 16
38+
// MaxNotificationCount is the maximum number of notifications per a single
39+
// application execution.
40+
MaxNotificationCount = 512
3841
)
3942

4043
// Ledger is the interface to Blockchain required for Context functionality.
@@ -564,10 +567,18 @@ func (ic *Context) IsHardforkActivation(hf config.Hardfork) bool {
564567
}
565568

566569
// AddNotification creates notification event and appends it to the notification list.
567-
func (ic *Context) AddNotification(hash util.Uint160, name string, item *stackitem.Array) {
570+
func (ic *Context) AddNotification(hash util.Uint160, name string, item *stackitem.Array) error {
571+
if ic.IsHardforkEnabled(config.HFEchidna) {
572+
// Do not check persisting triggers to avoid native persist failure. Do not check
573+
// verification trigger since verification context is loaded with ReadOnly flag.
574+
if ic.Trigger == trigger.Application && len(ic.Notifications) == MaxNotificationCount {
575+
return fmt.Errorf("notification count shouldn't exceed %d", MaxNotificationCount)
576+
}
577+
}
568578
ic.Notifications = append(ic.Notifications, state.NotificationEvent{
569579
ScriptHash: hash,
570580
Name: name,
571581
Item: item,
572582
})
583+
return nil
573584
}

pkg/core/interop/runtime/engine.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,7 @@ func Notify(ic *interop.Context) error {
114114
if len(bytes) > MaxNotificationSize {
115115
return fmt.Errorf("notification size shouldn't exceed %d", MaxNotificationSize)
116116
}
117-
ic.AddNotification(curHash, name, stackitem.DeepCopy(stackitem.NewArray(args), true).(*stackitem.Array))
118-
return nil
117+
return ic.AddNotification(curHash, name, stackitem.DeepCopy(stackitem.NewArray(args), true).(*stackitem.Array))
119118
}
120119

121120
// LoadScript takes a script and arguments from the stack and loads it into the VM.

pkg/core/native/designate.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -412,11 +412,10 @@ func (s *Designate) DesignateAsRole(ic *interop.Context, r noderoles.Role, pubs
412412
return fmt.Errorf("failed to update Designation role data cache: %w", err)
413413
}
414414

415-
ic.AddNotification(s.Hash, DesignationEventName, stackitem.NewArray([]stackitem.Item{
415+
return ic.AddNotification(s.Hash, DesignationEventName, stackitem.NewArray([]stackitem.Item{
416416
stackitem.NewBigInteger(big.NewInt(int64(r))),
417417
stackitem.NewBigInteger(big.NewInt(int64(ic.Block.Index))),
418418
}))
419-
return nil
420419
}
421420

422421
func (s *Designate) getRole(item stackitem.Item) (noderoles.Role, bool) {

pkg/core/native/management.go

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,10 @@ func (m *Management) deployWithData(ic *interop.Context, args []stackitem.Item)
372372
panic(err)
373373
}
374374
m.callDeploy(ic, newcontract, args[2], false)
375-
m.emitNotification(ic, contractDeployNotificationName, newcontract.Hash)
375+
err = m.emitNotification(ic, contractDeployNotificationName, newcontract.Hash)
376+
if err != nil {
377+
panic(err)
378+
}
376379
return contractToStack(newcontract)
377380
}
378381

@@ -444,7 +447,10 @@ func (m *Management) updateWithData(ic *interop.Context, args []stackitem.Item)
444447
panic(err)
445448
}
446449
m.callDeploy(ic, contract, args[2], true)
447-
m.emitNotification(ic, contractUpdateNotificationName, contract.Hash)
450+
err = m.emitNotification(ic, contractUpdateNotificationName, contract.Hash)
451+
if err != nil {
452+
panic(err)
453+
}
448454
return stackitem.Null{}
449455
}
450456

@@ -497,7 +503,10 @@ func (m *Management) destroy(ic *interop.Context, sis []stackitem.Item) stackite
497503
if err != nil {
498504
panic(err)
499505
}
500-
m.emitNotification(ic, contractDestroyNotificationName, hash)
506+
err = m.emitNotification(ic, contractDestroyNotificationName, hash)
507+
if err != nil {
508+
panic(err)
509+
}
501510
return stackitem.Null{}
502511
}
503512

@@ -681,7 +690,10 @@ func (m *Management) OnPersist(ic *interop.Context) error {
681690
if isUpdate {
682691
ntfName = contractUpdateNotificationName
683692
}
684-
m.emitNotification(ic, ntfName, cs.Hash)
693+
err = m.emitNotification(ic, ntfName, cs.Hash)
694+
if err != nil {
695+
return err
696+
}
685697
}
686698

687699
return nil
@@ -806,8 +818,8 @@ func (m *Management) getNextContractID(d *dao.Simple) (int32, error) {
806818
return ret, nil
807819
}
808820

809-
func (m *Management) emitNotification(ic *interop.Context, name string, hash util.Uint160) {
810-
ic.AddNotification(m.Hash, name, stackitem.NewArray([]stackitem.Item{addrToStackItem(&hash)}))
821+
func (m *Management) emitNotification(ic *interop.Context, name string, hash util.Uint160) error {
822+
return ic.AddNotification(m.Hash, name, stackitem.NewArray([]stackitem.Item{addrToStackItem(&hash)}))
811823
}
812824

813825
func checkScriptAndMethods(ic *interop.Context, script []byte, methods []manifest.Method) error {

pkg/core/native/native_neo.go

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -459,9 +459,12 @@ func (n *NEO) OnPersist(ic *interop.Context) error {
459459
ic.DAO.PutStorageItem(n.ID, prefixCommittee, cache.committee.Bytes(ic.DAO.GetItemCtx()))
460460

461461
if oldCommittee != nil {
462-
ic.AddNotification(n.Hash, "CommitteeChanged", stackitem.NewArray([]stackitem.Item{
462+
err := ic.AddNotification(n.Hash, "CommitteeChanged", stackitem.NewArray([]stackitem.Item{
463463
oldCommittee, newCommittee,
464464
}))
465+
if err != nil {
466+
return err
467+
}
465468
}
466469
}
467470
return nil
@@ -828,7 +831,8 @@ func (n *NEO) registerCandidate(ic *interop.Context, args []stackitem.Item) stac
828831
return stackitem.NewBool(err == nil)
829832
}
830833

831-
// RegisterCandidateInternal registers pub as a new candidate.
834+
// RegisterCandidateInternal registers pub as a new candidate. This method must not be
835+
// called outside of VM since it panics on critical errors.
832836
func (n *NEO) RegisterCandidateInternal(ic *interop.Context, pub *keys.PublicKey) error {
833837
var emitEvent = true
834838

@@ -843,16 +847,23 @@ func (n *NEO) RegisterCandidateInternal(ic *interop.Context, pub *keys.PublicKey
843847
c.Registered = true
844848
}
845849
err := putConvertibleToDAO(n.ID, ic.DAO, key, c)
850+
if err != nil {
851+
return err
852+
}
846853
if emitEvent {
847854
cache := ic.DAO.GetRWCache(n.ID).(*NeoCache)
848855
cache.votesChanged = true
849-
ic.AddNotification(n.Hash, "CandidateStateChanged", stackitem.NewArray([]stackitem.Item{
856+
err = ic.AddNotification(n.Hash, "CandidateStateChanged", stackitem.NewArray([]stackitem.Item{
850857
stackitem.NewByteArray(pub.Bytes()),
851858
stackitem.NewBool(c.Registered),
852859
stackitem.NewBigInteger(&c.Votes),
853860
}))
861+
if err != nil {
862+
// Panic since it's a critical error that must abort execution.
863+
panic(err)
864+
}
854865
}
855-
return err
866+
return nil
856867
}
857868

858869
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
867878
return stackitem.NewBool(err == nil)
868879
}
869880

870-
// UnregisterCandidateInternal unregisters pub as a candidate.
881+
// UnregisterCandidateInternal unregisters pub as a candidate. This method must not be
882+
// called outside of VM since it panics on critical errors.
871883
func (n *NEO) UnregisterCandidateInternal(ic *interop.Context, pub *keys.PublicKey) error {
872884
var err error
873885

@@ -887,14 +899,21 @@ func (n *NEO) UnregisterCandidateInternal(ic *interop.Context, pub *keys.PublicK
887899
if !ok {
888900
err = putConvertibleToDAO(n.ID, ic.DAO, key, c)
889901
}
902+
if err != nil {
903+
return err
904+
}
890905
if emitEvent {
891-
ic.AddNotification(n.Hash, "CandidateStateChanged", stackitem.NewArray([]stackitem.Item{
906+
err := ic.AddNotification(n.Hash, "CandidateStateChanged", stackitem.NewArray([]stackitem.Item{
892907
stackitem.NewByteArray(pub.Bytes()),
893908
stackitem.NewBool(c.Registered),
894909
stackitem.NewBigInteger(&c.Votes),
895910
}))
911+
if err != nil {
912+
// Panic since it's a critical error that must abort execution.
913+
panic(err)
914+
}
896915
}
897-
return err
916+
return nil
898917
}
899918

900919
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 {
907926
return stackitem.NewBool(err == nil)
908927
}
909928

910-
// VoteInternal votes from account h for validarors specified in pubs.
929+
// VoteInternal votes from account h for validarors specified in pubs. This method
930+
// must not be called outside of VM since it panics on critical errors.
911931
func (n *NEO) VoteInternal(ic *interop.Context, h util.Uint160, pub *keys.PublicKey) error {
912932
ok, err := runtime.CheckHashedWitness(ic, h)
913933
if err != nil {
@@ -969,12 +989,16 @@ func (n *NEO) VoteInternal(ic *interop.Context, h util.Uint160, pub *keys.Public
969989
}
970990
ic.DAO.PutStorageItem(n.ID, key, acc.Bytes(ic.DAO.GetItemCtx()))
971991

972-
ic.AddNotification(n.Hash, "Vote", stackitem.NewArray([]stackitem.Item{
992+
err = ic.AddNotification(n.Hash, "Vote", stackitem.NewArray([]stackitem.Item{
973993
stackitem.NewByteArray(h.BytesBE()),
974994
keyToStackItem(oldVote),
975995
keyToStackItem(pub),
976996
stackitem.NewBigInteger(&acc.Balance),
977997
}))
998+
if err != nil {
999+
// Panic since it's a critical error that must abort execution.
1000+
panic(err)
1001+
}
9781002

9791003
if newGas != nil { // Can be if it was already distributed in the same block.
9801004
n.GAS.mint(ic, h, newGas, true)

pkg/core/native/native_nep17.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,11 @@ func (c *nep17TokenNative) postTransfer(ic *interop.Context, from, to *util.Uint
143143
}
144144
}
145145
}()
146-
c.emitTransfer(ic, from, to, amount)
146+
err := c.emitTransfer(ic, from, to, amount)
147+
if err != nil {
148+
skipPostCalls = true
149+
panic(err)
150+
}
147151
if to == nil || !callOnPayment {
148152
return
149153
}
@@ -167,8 +171,8 @@ func (c *nep17TokenNative) postTransfer(ic *interop.Context, from, to *util.Uint
167171
}
168172
}
169173

170-
func (c *nep17TokenNative) emitTransfer(ic *interop.Context, from, to *util.Uint160, amount *big.Int) {
171-
ic.AddNotification(c.Hash, "Transfer", stackitem.NewArray([]stackitem.Item{
174+
func (c *nep17TokenNative) emitTransfer(ic *interop.Context, from, to *util.Uint160, amount *big.Int) error {
175+
return ic.AddNotification(c.Hash, "Transfer", stackitem.NewArray([]stackitem.Item{
172176
addrToStackItem(from),
173177
addrToStackItem(to),
174178
stackitem.NewBigInteger(amount),

pkg/core/native/oracle.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -316,10 +316,13 @@ func (o *Oracle) FinishInternal(ic *interop.Context) error {
316316
if err != nil {
317317
return ErrRequestNotFound
318318
}
319-
ic.AddNotification(o.Hash, "OracleResponse", stackitem.NewArray([]stackitem.Item{
319+
err = ic.AddNotification(o.Hash, "OracleResponse", stackitem.NewArray([]stackitem.Item{
320320
stackitem.Make(resp.ID),
321321
stackitem.Make(req.OriginalTxID.BytesBE()),
322322
}))
323+
if err != nil {
324+
return err
325+
}
323326

324327
origTx, _, err := ic.DAO.GetTransaction(req.OriginalTxID)
325328
if err != nil {
@@ -422,12 +425,15 @@ func (o *Oracle) RequestInternal(ic *interop.Context, url string, filter *string
422425
} else {
423426
filterNotif = stackitem.Null{}
424427
}
425-
ic.AddNotification(o.Hash, "OracleRequest", stackitem.NewArray([]stackitem.Item{
428+
err = ic.AddNotification(o.Hash, "OracleRequest", stackitem.NewArray([]stackitem.Item{
426429
stackitem.Make(id),
427430
stackitem.Make(ic.VM.GetCallingScriptHash().BytesBE()),
428431
stackitem.Make(url),
429432
filterNotif,
430433
}))
434+
if err != nil {
435+
return err
436+
}
431437
req := &state.OracleRequest{
432438
OriginalTxID: o.getOriginalTxID(ic.DAO, ic.Tx),
433439
GasForResponse: gas.Uint64(),

0 commit comments

Comments
 (0)