[#1632] node: Subscribe on the successful container creations/removals

There is a need to sync container-related caching mechanism with the
actual Sidechain changes. To do this, node should be able to listen
incoming notifications about container ops.

Define `PutSuccess` / `DeleteSuccess` notification event's parsers.
Subscribe to these events in node app. As initial implementation node
will log event receipts. Later handling is going to be practically
complicated.

Signed-off-by: Leonard Lyubich <leonard@nspcc.ru>
This commit is contained in:
Leonard Lyubich 2022-08-11 17:24:26 +04:00 committed by LeL
parent d8a00c365a
commit 7c1babb7d6
5 changed files with 219 additions and 0 deletions

View file

@ -63,6 +63,18 @@ func initContainerService(c *cfg) {
neoClient: wrap,
}
subscribeToContainerCreation(c, func(e event.Event) {
c.log.Debug("container creation event's receipt",
zap.Stringer("id", e.(containerEvent.PutSuccess).ID),
)
})
subscribeToContainerRemoval(c, func(e event.Event) {
c.log.Debug("container removal event's receipt",
zap.Stringer("id", e.(containerEvent.DeleteSuccess).ID),
)
})
if c.cfgMorph.disableCache {
c.cfgObject.eaclSource = eACLFetcher
cnrRdr.eacl = eACLFetcher
@ -195,6 +207,34 @@ func addContainerAsyncNotificationHandler(c *cfg, sTyp string, h event.Handler)
)
}
// stores already registered parsers of the notification events thrown by Container contract.
// MUST NOT be used concurrently.
var mRegisteredParsersContainer = make(map[string]struct{})
// registers event parser by name once. MUST NOT be called concurrently.
func registerEventParserOnceContainer(c *cfg, name string, p event.NotificationParser) {
if _, ok := mRegisteredParsersContainer[name]; !ok {
setContainerNotificationParser(c, name, p)
mRegisteredParsersContainer[name] = struct{}{}
}
}
// subscribes to successful container creation. Provided handler is called asynchronously
// on corresponding routine pool. MUST NOT be called concurrently with itself and other
// similar functions.
func subscribeToContainerCreation(c *cfg, h event.Handler) {
const eventNameContainerCreated = "PutSuccess"
registerEventParserOnceContainer(c, eventNameContainerCreated, containerEvent.ParsePutSuccess)
addContainerAsyncNotificationHandler(c, eventNameContainerCreated, h)
}
// like subscribeToContainerCreation but for removal.
func subscribeToContainerRemoval(c *cfg, h event.Handler) {
const eventNameContainerRemoved = "DeleteSuccess"
registerEventParserOnceContainer(c, eventNameContainerRemoved, containerEvent.ParseDeleteSuccess)
addContainerAsyncNotificationHandler(c, eventNameContainerRemoved, h)
}
func setContainerNotificationParser(c *cfg, sTyp string, p event.NotificationParser) {
typ := event.TypeFromString(sTyp)

View file

@ -7,6 +7,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/network/payload"
"github.com/nspcc-dev/neofs-node/pkg/morph/client"
"github.com/nspcc-dev/neofs-node/pkg/morph/event"
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
)
// Delete structure of container.Delete notification from morph chain.
@ -81,3 +82,42 @@ func ParseDelete(e *state.ContainedNotificationEvent) (event.Event, error) {
return ev, nil
}
// DeleteSuccess structures notification event of successful container removal
// thrown by Container contract.
type DeleteSuccess struct {
// Identifier of the removed container.
ID cid.ID
}
// MorphEvent implements Neo:Morph Event interface.
func (DeleteSuccess) MorphEvent() {}
// ParseDeleteSuccess decodes notification event thrown by Container contract into
// DeleteSuccess and returns it as event.Event.
func ParseDeleteSuccess(e *state.ContainedNotificationEvent) (event.Event, error) {
items, err := event.ParseStackArray(e)
if err != nil {
return nil, fmt.Errorf("parse stack array from raw notification event: %w", err)
}
const expectedItemNumDeleteSuccess = 1
if ln := len(items); ln != expectedItemNumDeleteSuccess {
return nil, event.WrongNumberOfParameters(expectedItemNumDeleteSuccess, ln)
}
binID, err := client.BytesFromStackItem(items[0])
if err != nil {
return nil, fmt.Errorf("parse container ID item: %w", err)
}
var res DeleteSuccess
err = res.ID.Decode(binID)
if err != nil {
return nil, fmt.Errorf("decode container ID: %w", err)
}
return res, nil
}

View file

@ -1,10 +1,12 @@
package container
import (
"crypto/sha256"
"testing"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neofs-node/pkg/morph/event"
cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test"
"github.com/stretchr/testify/require"
)
@ -67,3 +69,46 @@ func TestParseDelete(t *testing.T) {
}, ev)
})
}
func TestParseDeleteSuccess(t *testing.T) {
t.Run("wrong number of parameters", func(t *testing.T) {
prms := []stackitem.Item{
stackitem.NewMap(),
stackitem.NewMap(),
}
_, err := ParseDeleteSuccess(createNotifyEventFromItems(prms))
require.EqualError(t, err, event.WrongNumberOfParameters(1, len(prms)).Error())
})
t.Run("wrong container parameter", func(t *testing.T) {
_, err := ParseDeleteSuccess(createNotifyEventFromItems([]stackitem.Item{
stackitem.NewMap(),
}))
require.Error(t, err)
_, err = ParseDeleteSuccess(createNotifyEventFromItems([]stackitem.Item{
stackitem.NewByteArray([]byte{1, 2, 3}),
}))
require.Error(t, err)
})
t.Run("correct behavior", func(t *testing.T) {
id := cidtest.ID()
binID := make([]byte, sha256.Size)
id.Encode(binID)
ev, err := ParseDeleteSuccess(createNotifyEventFromItems([]stackitem.Item{
stackitem.NewByteArray(binID),
}))
require.NoError(t, err)
require.Equal(t, DeleteSuccess{
ID: id,
}, ev)
})
}

View file

@ -7,6 +7,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/network/payload"
"github.com/nspcc-dev/neofs-node/pkg/morph/client"
"github.com/nspcc-dev/neofs-node/pkg/morph/event"
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
)
// Put structure of container.Put notification from morph chain.
@ -106,3 +107,47 @@ func ParsePut(e *state.ContainedNotificationEvent) (event.Event, error) {
return ev, nil
}
// PutSuccess structures notification event of successful container creation
// thrown by Container contract.
type PutSuccess struct {
// Identifier of the newly created container.
ID cid.ID
}
// MorphEvent implements Neo:Morph Event interface.
func (PutSuccess) MorphEvent() {}
// ParsePutSuccess decodes notification event thrown by Container contract into
// PutSuccess and returns it as event.Event.
func ParsePutSuccess(e *state.ContainedNotificationEvent) (event.Event, error) {
items, err := event.ParseStackArray(e)
if err != nil {
return nil, fmt.Errorf("parse stack array from raw notification event: %w", err)
}
const expectedItemNumPutSuccess = 2
if ln := len(items); ln != expectedItemNumPutSuccess {
return nil, event.WrongNumberOfParameters(expectedItemNumPutSuccess, ln)
}
binID, err := client.BytesFromStackItem(items[0])
if err != nil {
return nil, fmt.Errorf("parse container ID item: %w", err)
}
_, err = client.BytesFromStackItem(items[1])
if err != nil {
return nil, fmt.Errorf("parse public key item: %w", err)
}
var res PutSuccess
err = res.ID.Decode(binID)
if err != nil {
return nil, fmt.Errorf("decode container ID: %w", err)
}
return res, nil
}

View file

@ -1,10 +1,12 @@
package container
import (
"crypto/sha256"
"testing"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neofs-node/pkg/morph/event"
cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test"
"github.com/stretchr/testify/require"
)
@ -81,3 +83,50 @@ func TestParsePut(t *testing.T) {
}, ev)
})
}
func TestParsePutSuccess(t *testing.T) {
t.Run("wrong number of parameters", func(t *testing.T) {
prms := []stackitem.Item{
stackitem.NewMap(),
}
_, err := ParsePutSuccess(createNotifyEventFromItems(prms))
require.EqualError(t, err, event.WrongNumberOfParameters(2, len(prms)).Error())
})
t.Run("wrong container ID parameter", func(t *testing.T) {
_, err := ParsePutSuccess(createNotifyEventFromItems([]stackitem.Item{
stackitem.NewMap(),
stackitem.NewMap(),
}))
require.Error(t, err)
})
id := cidtest.ID()
binID := make([]byte, sha256.Size)
id.Encode(binID)
t.Run("wrong public key parameter", func(t *testing.T) {
_, err := ParsePutSuccess(createNotifyEventFromItems([]stackitem.Item{
stackitem.NewByteArray(binID),
stackitem.NewMap(),
}))
require.Error(t, err)
})
t.Run("correct behavior", func(t *testing.T) {
ev, err := ParsePutSuccess(createNotifyEventFromItems([]stackitem.Item{
stackitem.NewByteArray(binID),
stackitem.NewByteArray([]byte("key")),
}))
require.NoError(t, err)
require.Equal(t, PutSuccess{
ID: id,
}, ev)
})
}