Initial commit
Initial public review release v0.10.0
This commit is contained in:
commit
dadfd90dcd
276 changed files with 46331 additions and 0 deletions
31
lib/blockchain/event/event.go
Normal file
31
lib/blockchain/event/event.go
Normal 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)
|
||||
}
|
22
lib/blockchain/event/handler.go
Normal file
22
lib/blockchain/event/handler.go
Normal 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
|
||||
}
|
309
lib/blockchain/event/listener.go
Normal file
309
lib/blockchain/event/listener.go
Normal 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
|
||||
}
|
39
lib/blockchain/event/netmap/epoch.go
Normal file
39
lib/blockchain/event/netmap/epoch.go
Normal 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
|
||||
}
|
47
lib/blockchain/event/netmap/epoch_test.go
Normal file
47
lib/blockchain/event/netmap/epoch_test.go
Normal 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)
|
||||
})
|
||||
}
|
53
lib/blockchain/event/parser.go
Normal file
53
lib/blockchain/event/parser.go
Normal 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
|
||||
}
|
34
lib/blockchain/event/utils.go
Normal file
34
lib/blockchain/event/utils.go
Normal 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
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue