nns: Add notification sending #109
4 changed files with 414 additions and 4 deletions
|
@ -4,6 +4,26 @@ safemethods: ["balanceOf", "decimals", "symbol", "totalSupply", "tokensOf", "own
|
|||
"tokens", "properties", "roots", "getPrice", "isAvailable", "getRecords",
|
||||
"resolve", "version"]
|
||||
events:
|
||||
- name: RegisterDomain
|
||||
parameters:
|
||||
- name: name
|
||||
type: String
|
||||
- name: AddRecord
|
||||
parameters:
|
||||
- name: name
|
||||
type: String
|
||||
- name: type
|
||||
type: Integer
|
||||
- name: DeleteRecords
|
||||
parameters:
|
||||
- name: name
|
||||
type: String
|
||||
- name: type
|
||||
type: Integer
|
||||
- name: DeleteDomain
|
||||
parameters:
|
||||
- name: name
|
||||
type: String
|
||||
- name: Transfer
|
||||
parameters:
|
||||
- name: from
|
||||
|
|
|
@ -407,6 +407,7 @@ func register(ctx storage.Context, name string, owner interop.Hash160, email str
|
|||
putSoaRecord(ctx, name, email, refresh, retry, expire, ttl)
|
||||
updateBalance(ctx, []byte(name), owner, +1)
|
||||
postTransfer(oldOwner, owner, []byte(name), nil)
|
||||
runtime.Notify("RegisterDomain", name)
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -523,6 +524,7 @@ func deleteRecords(ctx storage.Context, name string, typ RecordType) {
|
|||
storage.Delete(ctx, r)
|
||||
}
|
||||
updateSoaSerial(ctx, tokenID)
|
||||
runtime.Notify("DeleteRecords", name, typ)
|
||||
}
|
||||
|
||||
// DeleteDomain deletes the domain with the given name.
|
||||
|
@ -549,6 +551,7 @@ func deleteDomain(ctx storage.Context, name string) {
|
|||
deleteRecords(ctx, name, A)
|
||||
deleteRecords(ctx, name, AAAA)
|
||||
storage.Delete(ctx, nameKey)
|
||||
runtime.Notify("DeleteDomain", name)
|
||||
}
|
||||
|
||||
// Resolve resolves given name (not more then three redirects are allowed).
|
||||
|
@ -736,6 +739,7 @@ func storeRecord(ctx storage.Context, recordKey []byte, name string, typ RecordT
|
|||
}
|
||||
recBytes := std.Serialize(rs)
|
||||
storage.Put(ctx, recordKey, recBytes)
|
||||
runtime.Notify("AddRecord", name, typ)
|
||||
}
|
||||
|
||||
// putSoaRecord stores soa domain record.
|
||||
|
@ -756,6 +760,7 @@ func putSoaRecord(ctx storage.Context, name, email string, refresh, retry, expir
|
|||
}
|
||||
recBytes := std.Serialize(rs)
|
||||
storage.Put(ctx, recordKey, recBytes)
|
||||
runtime.Notify("AddRecord", name, SOA)
|
||||
}
|
||||
|
||||
// putCnameRecord stores CNAME domain record.
|
||||
|
@ -772,6 +777,7 @@ func putCnameRecord(ctx storage.Context, name, data string) {
|
|||
}
|
||||
recBytes := std.Serialize(rs)
|
||||
storage.Put(ctx, recordKey, recBytes)
|
||||
runtime.Notify("AddRecord", name, CNAME)
|
||||
}
|
||||
|
||||
// updateSoaSerial stores soa domain record.
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
package nameservice
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/google/uuid"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||
|
@ -13,8 +15,37 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"math/big"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// RegisterDomainEvent represents "RegisterDomain" event emitted by the contract.
|
||||
type RegisterDomainEvent struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
// AddRecordEvent represents "AddRecord" event emitted by the contract.
|
||||
type AddRecordEvent struct {
|
||||
Name string
|
||||
Type *big.Int
|
||||
}
|
||||
|
||||
// SetRecordEvent represents "SetRecord" event emitted by the contract.
|
||||
type SetRecordEvent struct {
|
||||
Name string
|
||||
Type *big.Int
|
||||
}
|
||||
|
||||
// DeleteRecordsEvent represents "DeleteRecords" event emitted by the contract.
|
||||
type DeleteRecordsEvent struct {
|
||||
Name string
|
||||
Type *big.Int
|
||||
}
|
||||
|
||||
// DeleteDomainEvent represents "DeleteDomain" event emitted by the contract.
|
||||
type DeleteDomainEvent struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
// Invoker is used by ContractReader to call various safe methods.
|
||||
type Invoker interface {
|
||||
nep11.Invoker
|
||||
|
@ -356,3 +387,326 @@ func (c *Contract) UpdateSOATransaction(name string, email string, refresh *big.
|
|||
func (c *Contract) UpdateSOAUnsigned(name string, email string, refresh *big.Int, retry *big.Int, expire *big.Int, ttl *big.Int) (*transaction.Transaction, error) {
|
||||
return c.actor.MakeUnsignedCall(c.hash, "updateSOA", nil, name, email, refresh, retry, expire, ttl)
|
||||
}
|
||||
|
||||
// RegisterDomainEventsFromApplicationLog retrieves a set of all emitted events
|
||||
// with "RegisterDomain" name from the provided [result.ApplicationLog].
|
||||
func RegisterDomainEventsFromApplicationLog(log *result.ApplicationLog) ([]*RegisterDomainEvent, error) {
|
||||
if log == nil {
|
||||
return nil, errors.New("nil application log")
|
||||
}
|
||||
|
||||
var res []*RegisterDomainEvent
|
||||
for i, ex := range log.Executions {
|
||||
for j, e := range ex.Events {
|
||||
if e.Name != "RegisterDomain" {
|
||||
continue
|
||||
}
|
||||
event := new(RegisterDomainEvent)
|
||||
err := event.FromStackItem(e.Item)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to deserialize RegisterDomainEvent from stackitem (execution #%d, event #%d): %w", i, j, err)
|
||||
}
|
||||
res = append(res, event)
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// FromStackItem converts provided [stackitem.Array] to RegisterDomainEvent or
|
||||
// returns an error if it's not possible to do to so.
|
||||
func (e *RegisterDomainEvent) FromStackItem(item *stackitem.Array) error {
|
||||
if item == nil {
|
||||
return errors.New("nil item")
|
||||
}
|
||||
arr, ok := item.Value().([]stackitem.Item)
|
||||
if !ok {
|
||||
return errors.New("not an array")
|
||||
}
|
||||
if len(arr) != 1 {
|
||||
return errors.New("wrong number of structure elements")
|
||||
}
|
||||
|
||||
var (
|
||||
index = -1
|
||||
err error
|
||||
)
|
||||
index++
|
||||
e.Name, err = func(item stackitem.Item) (string, error) {
|
||||
acid-ant marked this conversation as resolved
|
||||
b, err := item.TryBytes()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !utf8.Valid(b) {
|
||||
return "", errors.New("not a UTF-8 string")
|
||||
}
|
||||
return string(b), nil
|
||||
}(arr[index])
|
||||
if err != nil {
|
||||
return fmt.Errorf("field Name: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddRecordEventsFromApplicationLog retrieves a set of all emitted events
|
||||
// with "AddRecord" name from the provided [result.ApplicationLog].
|
||||
func AddRecordEventsFromApplicationLog(log *result.ApplicationLog) ([]*AddRecordEvent, error) {
|
||||
if log == nil {
|
||||
return nil, errors.New("nil application log")
|
||||
}
|
||||
|
||||
var res []*AddRecordEvent
|
||||
for i, ex := range log.Executions {
|
||||
for j, e := range ex.Events {
|
||||
if e.Name != "AddRecord" {
|
||||
continue
|
||||
}
|
||||
event := new(AddRecordEvent)
|
||||
err := event.FromStackItem(e.Item)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to deserialize AddRecordEvent from stackitem (execution #%d, event #%d): %w", i, j, err)
|
||||
}
|
||||
res = append(res, event)
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// FromStackItem converts provided [stackitem.Array] to AddRecordEvent or
|
||||
// returns an error if it's not possible to do to so.
|
||||
func (e *AddRecordEvent) FromStackItem(item *stackitem.Array) error {
|
||||
if item == nil {
|
||||
return errors.New("nil item")
|
||||
}
|
||||
arr, ok := item.Value().([]stackitem.Item)
|
||||
if !ok {
|
||||
return errors.New("not an array")
|
||||
}
|
||||
if len(arr) != 2 {
|
||||
return errors.New("wrong number of structure elements")
|
||||
}
|
||||
|
||||
var (
|
||||
index = -1
|
||||
err error
|
||||
)
|
||||
index++
|
||||
e.Name, err = func(item stackitem.Item) (string, error) {
|
||||
b, err := item.TryBytes()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !utf8.Valid(b) {
|
||||
return "", errors.New("not a UTF-8 string")
|
||||
}
|
||||
return string(b), nil
|
||||
}(arr[index])
|
||||
if err != nil {
|
||||
return fmt.Errorf("field Name: %w", err)
|
||||
}
|
||||
|
||||
index++
|
||||
e.Type, err = arr[index].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("field Type: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetRecordEventsFromApplicationLog retrieves a set of all emitted events
|
||||
// with "SetRecord" name from the provided [result.ApplicationLog].
|
||||
func SetRecordEventsFromApplicationLog(log *result.ApplicationLog) ([]*SetRecordEvent, error) {
|
||||
if log == nil {
|
||||
return nil, errors.New("nil application log")
|
||||
}
|
||||
|
||||
var res []*SetRecordEvent
|
||||
for i, ex := range log.Executions {
|
||||
for j, e := range ex.Events {
|
||||
if e.Name != "SetRecord" {
|
||||
continue
|
||||
}
|
||||
event := new(SetRecordEvent)
|
||||
err := event.FromStackItem(e.Item)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to deserialize SetRecordEvent from stackitem (execution #%d, event #%d): %w", i, j, err)
|
||||
}
|
||||
res = append(res, event)
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// FromStackItem converts provided [stackitem.Array] to SetRecordEvent or
|
||||
// returns an error if it's not possible to do to so.
|
||||
func (e *SetRecordEvent) FromStackItem(item *stackitem.Array) error {
|
||||
if item == nil {
|
||||
return errors.New("nil item")
|
||||
}
|
||||
arr, ok := item.Value().([]stackitem.Item)
|
||||
if !ok {
|
||||
return errors.New("not an array")
|
||||
}
|
||||
if len(arr) != 2 {
|
||||
return errors.New("wrong number of structure elements")
|
||||
}
|
||||
|
||||
var (
|
||||
index = -1
|
||||
err error
|
||||
)
|
||||
index++
|
||||
e.Name, err = func(item stackitem.Item) (string, error) {
|
||||
b, err := item.TryBytes()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !utf8.Valid(b) {
|
||||
return "", errors.New("not a UTF-8 string")
|
||||
}
|
||||
return string(b), nil
|
||||
}(arr[index])
|
||||
if err != nil {
|
||||
return fmt.Errorf("field Name: %w", err)
|
||||
}
|
||||
|
||||
index++
|
||||
e.Type, err = arr[index].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("field Type: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteRecordsEventsFromApplicationLog retrieves a set of all emitted events
|
||||
// with "DeleteRecords" name from the provided [result.ApplicationLog].
|
||||
func DeleteRecordsEventsFromApplicationLog(log *result.ApplicationLog) ([]*DeleteRecordsEvent, error) {
|
||||
if log == nil {
|
||||
return nil, errors.New("nil application log")
|
||||
}
|
||||
|
||||
var res []*DeleteRecordsEvent
|
||||
for i, ex := range log.Executions {
|
||||
for j, e := range ex.Events {
|
||||
if e.Name != "DeleteRecords" {
|
||||
continue
|
||||
}
|
||||
event := new(DeleteRecordsEvent)
|
||||
err := event.FromStackItem(e.Item)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to deserialize DeleteRecordsEvent from stackitem (execution #%d, event #%d): %w", i, j, err)
|
||||
}
|
||||
res = append(res, event)
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// FromStackItem converts provided [stackitem.Array] to DeleteRecordsEvent or
|
||||
// returns an error if it's not possible to do to so.
|
||||
func (e *DeleteRecordsEvent) FromStackItem(item *stackitem.Array) error {
|
||||
if item == nil {
|
||||
return errors.New("nil item")
|
||||
}
|
||||
arr, ok := item.Value().([]stackitem.Item)
|
||||
if !ok {
|
||||
return errors.New("not an array")
|
||||
}
|
||||
if len(arr) != 2 {
|
||||
return errors.New("wrong number of structure elements")
|
||||
}
|
||||
|
||||
var (
|
||||
index = -1
|
||||
err error
|
||||
)
|
||||
index++
|
||||
e.Name, err = func(item stackitem.Item) (string, error) {
|
||||
b, err := item.TryBytes()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !utf8.Valid(b) {
|
||||
return "", errors.New("not a UTF-8 string")
|
||||
}
|
||||
return string(b), nil
|
||||
}(arr[index])
|
||||
if err != nil {
|
||||
return fmt.Errorf("field Name: %w", err)
|
||||
}
|
||||
|
||||
index++
|
||||
e.Type, err = arr[index].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("field Type: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteDomainEventsFromApplicationLog retrieves a set of all emitted events
|
||||
// with "DeleteDomain" name from the provided [result.ApplicationLog].
|
||||
func DeleteDomainEventsFromApplicationLog(log *result.ApplicationLog) ([]*DeleteDomainEvent, error) {
|
||||
if log == nil {
|
||||
return nil, errors.New("nil application log")
|
||||
}
|
||||
|
||||
var res []*DeleteDomainEvent
|
||||
for i, ex := range log.Executions {
|
||||
for j, e := range ex.Events {
|
||||
if e.Name != "DeleteDomain" {
|
||||
continue
|
||||
}
|
||||
event := new(DeleteDomainEvent)
|
||||
err := event.FromStackItem(e.Item)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to deserialize DeleteDomainEvent from stackitem (execution #%d, event #%d): %w", i, j, err)
|
||||
}
|
||||
res = append(res, event)
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// FromStackItem converts provided [stackitem.Array] to DeleteDomainEvent or
|
||||
// returns an error if it's not possible to do to so.
|
||||
func (e *DeleteDomainEvent) FromStackItem(item *stackitem.Array) error {
|
||||
if item == nil {
|
||||
return errors.New("nil item")
|
||||
}
|
||||
arr, ok := item.Value().([]stackitem.Item)
|
||||
if !ok {
|
||||
return errors.New("not an array")
|
||||
}
|
||||
if len(arr) != 1 {
|
||||
return errors.New("wrong number of structure elements")
|
||||
}
|
||||
|
||||
var (
|
||||
index = -1
|
||||
err error
|
||||
)
|
||||
index++
|
||||
e.Name, err = func(item stackitem.Item) (string, error) {
|
||||
b, err := item.TryBytes()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !utf8.Valid(b) {
|
||||
return "", errors.New("not a UTF-8 string")
|
||||
}
|
||||
return string(b), nil
|
||||
}(arr[index])
|
||||
if err != nil {
|
||||
return fmt.Errorf("field Name: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/storage"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/gas"
|
||||
|
@ -125,19 +126,28 @@ func TestNNSRegister(t *testing.T) {
|
|||
"test-domain.com", acc.ScriptHash(),
|
||||
"myemail@frostfs.info", refresh, retry, expire, ttl)
|
||||
})
|
||||
c3.Invoke(t, true, "register",
|
||||
expected := stackitem.NewArray([]stackitem.Item{
|
||||
stackitem.NewByteArray([]byte("testdomain.com")),
|
||||
})
|
||||
tx := c3.Invoke(t, true, "register",
|
||||
"testdomain.com", acc.ScriptHash(),
|
||||
"myemail@frostfs.info", refresh, retry, expire, ttl)
|
||||
c.CheckTxNotificationEvent(t, tx, -1, state.NotificationEvent{ScriptHash: c.Hash, Name: "RegisterDomain", Item: expected})
|
||||
|
||||
b := c.TopBlock(t)
|
||||
expected := stackitem.NewArray([]stackitem.Item{stackitem.NewBuffer(
|
||||
expected = stackitem.NewArray([]stackitem.Item{stackitem.NewBuffer(
|
||||
[]byte(fmt.Sprintf("testdomain.com myemail@frostfs.info %d %d %d %d %d",
|
||||
b.Timestamp, refresh, retry, expire, ttl)))})
|
||||
c.Invoke(t, expected, "getRecords", "testdomain.com", int64(nns.SOA))
|
||||
|
||||
cAcc := c.WithSigners(acc)
|
||||
cAcc.Invoke(t, stackitem.Null{}, "addRecord",
|
||||
|
||||
expected = stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte("testdomain.com")),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(nns.TXT)))})
|
||||
tx = cAcc.Invoke(t, stackitem.Null{}, "addRecord",
|
||||
"testdomain.com", int64(nns.TXT), "first TXT record")
|
||||
c.CheckTxNotificationEvent(t, tx, 0, state.NotificationEvent{ScriptHash: c.Hash, Name: "AddRecord", Item: expected})
|
||||
|
||||
cAcc.InvokeFail(t, "record already exists", "addRecord",
|
||||
"testdomain.com", int64(nns.TXT), "first TXT record")
|
||||
cAcc.Invoke(t, stackitem.Null{}, "addRecord",
|
||||
|
@ -149,14 +159,34 @@ func TestNNSRegister(t *testing.T) {
|
|||
})
|
||||
c.Invoke(t, expected, "getRecords", "testdomain.com", int64(nns.TXT))
|
||||
|
||||
cAcc.Invoke(t, stackitem.Null{}, "setRecord",
|
||||
expected = stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte("testdomain.com")),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(nns.TXT)))})
|
||||
tx = cAcc.Invoke(t, stackitem.Null{}, "setRecord",
|
||||
"testdomain.com", int64(nns.TXT), int64(0), "replaced first")
|
||||
c.CheckTxNotificationEvent(t, tx, 0, state.NotificationEvent{ScriptHash: c.Hash, Name: "AddRecord", Item: expected})
|
||||
|
||||
expected = stackitem.NewArray([]stackitem.Item{
|
||||
stackitem.NewByteArray([]byte("replaced first")),
|
||||
stackitem.NewByteArray([]byte("second TXT record")),
|
||||
})
|
||||
c.Invoke(t, expected, "getRecords", "testdomain.com", int64(nns.TXT))
|
||||
|
||||
tx = cAcc.Invoke(t, stackitem.Null{}, "deleteRecords", "testdomain.com", int64(nns.TXT))
|
||||
expected = stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte("testdomain.com")),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(nns.TXT)))})
|
||||
c.CheckTxNotificationEvent(t, tx, 0, state.NotificationEvent{ScriptHash: c.Hash, Name: "DeleteRecords", Item: expected})
|
||||
|
||||
c.Invoke(t, stackitem.Null{}, "getRecords", "testdomain.com", int64(nns.TXT))
|
||||
|
||||
tx = cAcc.Invoke(t, stackitem.Null{}, "deleteDomain", "testdomain.com")
|
||||
expected = stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte("testdomain.com")),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(nns.CNAME)))})
|
||||
c.CheckTxNotificationEvent(t, tx, 0, state.NotificationEvent{ScriptHash: c.Hash, Name: "DeleteRecords", Item: expected})
|
||||
|
||||
expected = stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte("testdomain.com"))})
|
||||
c.CheckTxNotificationEvent(t, tx, 4, state.NotificationEvent{ScriptHash: c.Hash, Name: "DeleteDomain", Item: expected})
|
||||
|
||||
c.InvokeFail(t, "token not found", "getRecords", "testdomain.com", int64(nns.SOA))
|
||||
}
|
||||
|
||||
func TestGlobalDomain(t *testing.T) {
|
||||
|
|
Loading…
Reference in a new issue
Why we need
func
here?It is automatically generated code, makes it easier to generate
Oops.