Initial commit

Initial public review release v0.10.0
This commit is contained in:
alexvanin 2020-07-10 17:17:51 +03:00 committed by Stanislav Bogatyrev
commit dadfd90dcd
276 changed files with 46331 additions and 0 deletions

View file

@ -0,0 +1,31 @@
package event
// Type is a notification event enumeration type.
type Type string
// Event is an interface that is
// provided by Neo:Morph event structures.
type Event interface {
MorphEvent()
}
// Equal compares two Type values and
// returns true if they are equal.
func (t Type) Equal(t2 Type) bool {
return string(t) == string(t2)
}
// String returns casted to string Type.
func (t Type) String() string {
return string(t)
}
// TypeFromBytes converts bytes slice to Type.
func TypeFromBytes(data []byte) Type {
return Type(data)
}
// TypeFromString converts string to Type.
func TypeFromString(str string) Type {
return Type(str)
}

View file

@ -0,0 +1,22 @@
package event
// Handler is an Event processing function.
type Handler func(Event)
// HandlerInfo is a structure that groups
// the parameters of the handler of particular
// contract event.
type HandlerInfo struct {
scriptHashWithType
h Handler
}
// SetHandler is an event handler setter.
func (s *HandlerInfo) SetHandler(v Handler) {
s.h = v
}
func (s HandlerInfo) handler() Handler {
return s.h
}

View file

@ -0,0 +1,309 @@
package event
import (
"context"
"sync"
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neofs-node/internal"
"github.com/nspcc-dev/neofs-node/lib/blockchain/goclient"
"github.com/nspcc-dev/neofs-node/lib/blockchain/subscriber"
"github.com/pkg/errors"
"go.uber.org/zap"
)
// Listener is an interface of smart contract notification event listener.
type Listener interface {
// Must start the event listener.
//
// Must listen to events with the parser installed.
//
// Must return an error if event listening could not be started.
Listen(context.Context)
// Must set the parser of particular contract event.
//
// Parser of each event must be set once. All parsers must be set before Listen call.
//
// Must ignore nil parsers and all calls after listener has been started.
SetParser(ParserInfo)
// Must register the event handler for particular notification event of contract.
//
// The specified handler must be called after each capture and parsing of the event
//
// Must ignore nil handlers.
RegisterHandler(HandlerInfo)
}
// ListenerParams is a group of parameters
// for Listener constructor.
type ListenerParams struct {
Logger *zap.Logger
Subscriber subscriber.Subscriber
}
type listener struct {
mtx *sync.RWMutex
once *sync.Once
started bool
parsers map[scriptHashWithType]Parser
handlers map[scriptHashWithType][]Handler
log *zap.Logger
subscriber subscriber.Subscriber
}
const (
newListenerFailMsg = "could not instantiate Listener"
errNilLogger = internal.Error("nil logger")
errNilSubscriber = internal.Error("nil event subscriber")
)
// Listen starts the listening for events with registered handlers.
//
// Executes once, all subsequent calls do nothing.
//
// Returns an error if listener was already started.
func (s listener) Listen(ctx context.Context) {
s.once.Do(func() {
if err := s.listen(ctx); err != nil {
s.log.Error("could not start listen to events",
zap.String("error", err.Error()),
)
}
})
}
func (s listener) listen(ctx context.Context) error {
// create the list of listening contract hashes
hashes := make([]util.Uint160, 0)
// fill the list with the contracts with set event parsers.
s.mtx.RLock()
for hashType := range s.parsers {
scHash := hashType.scriptHash()
// prevent repetitions
for _, hash := range hashes {
if hash.Equals(scHash) {
continue
}
}
hashes = append(hashes, hashType.scriptHash())
}
// mark listener as started
s.started = true
s.mtx.RUnlock()
chEvent, err := s.subscriber.SubscribeForNotification(hashes...)
if err != nil {
return err
}
s.listenLoop(ctx, chEvent)
return nil
}
func (s listener) listenLoop(ctx context.Context, chEvent <-chan *result.NotificationEvent) {
loop:
for {
select {
case <-ctx.Done():
s.log.Warn("stop event listener by context",
zap.String("error", ctx.Err().Error()),
)
break loop
case notifyEvent, ok := <-chEvent:
if !ok {
s.log.Warn("stop event listener by channel")
break loop
} else if notifyEvent == nil {
s.log.Warn("nil notification event was caught")
continue loop
}
s.parseAndHandle(notifyEvent)
}
}
}
func (s listener) parseAndHandle(notifyEvent *result.NotificationEvent) {
log := s.log.With(
zap.String("script hash LE", notifyEvent.Contract.StringLE()),
)
// stack item must be an array of items
arr, err := goclient.ArrayFromStackParameter(notifyEvent.Item)
if err != nil {
log.Warn("stack item is not an array type",
zap.String("error", err.Error()),
)
return
} else if len(arr) == 0 {
log.Warn("stack item array is empty")
return
}
// first item must be a byte array
typBytes, err := goclient.BytesFromStackParameter(arr[0])
if err != nil {
log.Warn("first array item is not a byte array",
zap.String("error", err.Error()),
)
return
}
// calculate event type from bytes
typEvent := TypeFromBytes(typBytes)
log = log.With(
zap.Stringer("event type", typEvent),
)
// get the event parser
keyEvent := scriptHashWithType{}
keyEvent.SetScriptHash(notifyEvent.Contract)
keyEvent.SetType(typEvent)
s.mtx.RLock()
parser, ok := s.parsers[keyEvent]
s.mtx.RUnlock()
if !ok {
log.Warn("event parser not set")
return
}
// parse the notification event
event, err := parser(arr[1:])
if err != nil {
log.Warn("could not parse notification event",
zap.String("error", err.Error()),
)
return
}
// handler the event
s.mtx.RLock()
handlers := s.handlers[keyEvent]
s.mtx.RUnlock()
if len(handlers) == 0 {
log.Info("handlers for parsed notification event were not registered",
zap.Any("event", event),
)
return
}
for _, handler := range handlers {
handler(event)
}
}
// SetParser sets the parser of particular contract event.
//
// Ignores nil and already set parsers.
// Ignores the parser if listener is started.
func (s listener) SetParser(p ParserInfo) {
log := s.log.With(
zap.String("script hash LE", p.scriptHash().StringLE()),
zap.Stringer("event type", p.getType()),
)
parser := p.parser()
if parser == nil {
log.Info("ignore nil event parser")
return
}
s.mtx.Lock()
defer s.mtx.Unlock()
// check if the listener was started
if s.started {
log.Warn("listener has been already started, ignore parser")
return
}
// add event parser
if _, ok := s.parsers[p.scriptHashWithType]; !ok {
s.parsers[p.scriptHashWithType] = p.parser()
}
log.Info("registered new event parser")
}
// RegisterHandler registers the handler for particular notification event of contract.
//
// Ignores nil handlers.
// Ignores handlers of event without parser.
func (s listener) RegisterHandler(p HandlerInfo) {
log := s.log.With(
zap.String("script hash LE", p.scriptHash().StringLE()),
zap.Stringer("event type", p.getType()),
)
handler := p.handler()
if handler == nil {
log.Warn("ignore nil event handler")
return
}
// check if parser was set
s.mtx.RLock()
_, ok := s.parsers[p.scriptHashWithType]
s.mtx.RUnlock()
if !ok {
log.Warn("ignore handler of event w/o parser")
return
}
// add event handler
s.mtx.Lock()
s.handlers[p.scriptHashWithType] = append(
s.handlers[p.scriptHashWithType],
p.handler(),
)
s.mtx.Unlock()
log.Info("registered new event handler")
}
// NewListener create the notification event listener instance and returns Listener interface.
func NewListener(p ListenerParams) (Listener, error) {
switch {
case p.Logger == nil:
return nil, errors.Wrap(errNilLogger, newListenerFailMsg)
case p.Subscriber == nil:
return nil, errors.Wrap(errNilSubscriber, newListenerFailMsg)
}
return &listener{
mtx: new(sync.RWMutex),
once: new(sync.Once),
parsers: make(map[scriptHashWithType]Parser),
handlers: make(map[scriptHashWithType][]Handler),
log: p.Logger,
subscriber: p.Subscriber,
}, nil
}

View file

@ -0,0 +1,39 @@
package netmap
import (
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neofs-node/lib/blockchain/event"
"github.com/nspcc-dev/neofs-node/lib/blockchain/goclient"
"github.com/pkg/errors"
)
// NewEpoch is a new epoch Neo:Morph event.
type NewEpoch struct {
num uint64
}
// MorphEvent implements Neo:Morph Event interface.
func (NewEpoch) MorphEvent() {}
// EpochNumber returns new epoch number.
func (s NewEpoch) EpochNumber() uint64 {
return s.num
}
// ParseNewEpoch is a parser of new epoch notification event.
//
// Result is type of NewEpoch.
func ParseNewEpoch(prms []smartcontract.Parameter) (event.Event, error) {
if ln := len(prms); ln != 1 {
return nil, event.WrongNumberOfParameters(1, ln)
}
prmEpochNum, err := goclient.IntFromStackParameter(prms[0])
if err != nil {
return nil, errors.Wrap(err, "could not get integer epoch number")
}
return NewEpoch{
num: uint64(prmEpochNum),
}, nil
}

View file

@ -0,0 +1,47 @@
package netmap
import (
"testing"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neofs-node/lib/blockchain/event"
"github.com/stretchr/testify/require"
)
func TestParseNewEpoch(t *testing.T) {
t.Run("wrong number of parameters", func(t *testing.T) {
prms := []smartcontract.Parameter{
{},
{},
}
_, err := ParseNewEpoch(prms)
require.EqualError(t, err, event.WrongNumberOfParameters(1, len(prms)).Error())
})
t.Run("wrong first parameter type", func(t *testing.T) {
_, err := ParseNewEpoch([]smartcontract.Parameter{
{
Type: smartcontract.ByteArrayType,
},
})
require.Error(t, err)
})
t.Run("correct behavior", func(t *testing.T) {
epochNum := uint64(100)
ev, err := ParseNewEpoch([]smartcontract.Parameter{
{
Type: smartcontract.IntegerType,
Value: int64(epochNum),
},
})
require.NoError(t, err)
require.Equal(t, NewEpoch{
num: epochNum,
}, ev)
})
}

View file

@ -0,0 +1,53 @@
package event
import (
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/pkg/errors"
)
// Parser is a function that constructs Event
// from the StackItem list.
type Parser func([]smartcontract.Parameter) (Event, error)
// ParserInfo is a structure that groups
// the parameters of particular contract
// notification event parser.
type ParserInfo struct {
scriptHashWithType
p Parser
}
type wrongPrmNumber struct {
exp, act int
}
// WrongNumberOfParameters returns an error about wrong number of smart contract parameters.
func WrongNumberOfParameters(exp, act int) error {
return &wrongPrmNumber{
exp: exp,
act: act,
}
}
func (s wrongPrmNumber) Error() string {
return errors.Errorf("wrong parameter count: expected %d, has %d", s.exp, s.act).Error()
}
// SetParser is an event parser setter.
func (s *ParserInfo) SetParser(v Parser) {
s.p = v
}
func (s ParserInfo) parser() Parser {
return s.p
}
// SetType is an event type setter.
func (s *ParserInfo) SetType(v Type) {
s.typ = v
}
func (s ParserInfo) getType() Type {
return s.typ
}

View file

@ -0,0 +1,34 @@
package event
import "github.com/nspcc-dev/neo-go/pkg/util"
type scriptHashValue struct {
hash util.Uint160
}
type typeValue struct {
typ Type
}
type scriptHashWithType struct {
scriptHashValue
typeValue
}
// SetScriptHash is a script hash setter.
func (s *scriptHashValue) SetScriptHash(v util.Uint160) {
s.hash = v
}
func (s scriptHashValue) scriptHash() util.Uint160 {
return s.hash
}
// SetType is an event type setter.
func (s *typeValue) SetType(v Type) {
s.typ = v
}
func (s typeValue) getType() Type {
return s.typ
}