Skip to content

Commit

Permalink
rpc/server: add notification subscription
Browse files Browse the repository at this point in the history
Note that the protocol differs a bit from #895 in its notifications format,
to avoid additional server-side processing we're omitting some metadata like:
 * block size and confirmations
 * transaction fees, confirmations, block hash and timestamp
 * application execution doesn't have ScriptHash populated

Some block fields may also differ in encoding compared to `getblock` results
(like nonce field).

I think these differences are unnoticieable for most use cases, so we can
leave them as is, but it can be changed in the future.
  • Loading branch information
roman-khimov committed May 26, 2020
1 parent 03ecab5 commit fc22a46
Show file tree
Hide file tree
Showing 7 changed files with 688 additions and 24 deletions.
79 changes: 79 additions & 0 deletions pkg/rpc/response/events.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package response

import (
"encoding/json"

"github.com/pkg/errors"
)

type (
// EventID represents an event type happening on the chain.
EventID byte
)

const (
// InvalidEventID is an invalid event id that is the default value of
// EventID. It's only used as an initial value similar to nil.
InvalidEventID EventID = iota
// BlockEventID is a `block_added` event.
BlockEventID
// TransactionEventID corresponds to `transaction_added` event.
TransactionEventID
// NotificationEventID represents `notification_from_execution` events.
NotificationEventID
// ExecutionEventID is used for `transaction_executed` events.
ExecutionEventID
)

// String is a good old Stringer implementation.
func (e EventID) String() string {
switch e {
case BlockEventID:
return "block_added"
case TransactionEventID:
return "transaction_added"
case NotificationEventID:
return "notification_from_execution"
case ExecutionEventID:
return "transaction_executed"
default:
return "unknown"
}
}

// GetEventIDFromString converts input string into an EventID if it's possible.
func GetEventIDFromString(s string) (EventID, error) {
switch s {
case "block_added":
return BlockEventID, nil
case "transaction_added":
return TransactionEventID, nil
case "notification_from_execution":
return NotificationEventID, nil
case "transaction_executed":
return ExecutionEventID, nil
default:
return 255, errors.New("invalid stream name")
}
}

// MarshalJSON implements json.Marshaler interface.
func (e EventID) MarshalJSON() ([]byte, error) {
return json.Marshal(e.String())
}

// UnmarshalJSON implements json.Unmarshaler interface.
func (e *EventID) UnmarshalJSON(b []byte) error {
var s string

err := json.Unmarshal(b, &s)
if err != nil {
return err
}
id, err := GetEventIDFromString(s)
if err != nil {
return err
}
*e = id
return nil
}
18 changes: 12 additions & 6 deletions pkg/rpc/response/result/application_log.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,22 @@ type NotificationEvent struct {
Item smartcontract.Parameter `json:"state"`
}

// StateEventToResultNotification converts state.NotificationEvent to
// result.NotificationEvent.
func StateEventToResultNotification(event state.NotificationEvent) NotificationEvent {
seen := make(map[vm.StackItem]bool)
item := event.Item.ToContractParameter(seen)
return NotificationEvent{
Contract: event.ScriptHash,
Item: item,
}
}

// NewApplicationLog creates a new ApplicationLog wrapper.
func NewApplicationLog(appExecRes *state.AppExecResult, scriptHash util.Uint160) ApplicationLog {
events := make([]NotificationEvent, 0, len(appExecRes.Events))
for _, e := range appExecRes.Events {
seen := make(map[vm.StackItem]bool)
item := e.Item.ToContractParameter(seen)
events = append(events, NotificationEvent{
Contract: e.ScriptHash,
Item: item,
})
events = append(events, StateEventToResultNotification(e))
}

triggerString := appExecRes.Trigger.String()
Expand Down
9 changes: 9 additions & 0 deletions pkg/rpc/response/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,12 @@ type GetRawTx struct {
HeaderAndError
Result *result.TransactionOutputRaw `json:"result"`
}

// Notification is a type used to represent wire format of events, they're
// special in that they look like requests but they don't have IDs and their
// "method" is actually an event name.
type Notification struct {
JSONRPC string `json:"jsonrpc"`
Event EventID `json:"method"`
Payload []interface{} `json:"params"`
}
Loading

0 comments on commit fc22a46

Please sign in to comment.