[#109] nns: Add notification sending
All checks were successful
DCO action / DCO (pull_request) Successful in 38s
Tests / Tests (pull_request) Successful in 1m6s

Signed-off-by: Alexander Chuprov <a.chuprov@yadro.com>
This commit is contained in:
Alexander Chuprov 2024-09-05 18:17:04 +03:00
parent c142971bfd
commit fbb5b41f26
4 changed files with 419 additions and 5 deletions

View file

@ -4,6 +4,32 @@ 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: SetRecord
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

View file

@ -336,7 +336,11 @@ func parentExpired(ctx storage.Context, fragments []string) string {
// Register registers a new domain with the specified owner and name if it's available.
func Register(name string, owner interop.Hash160, email string, refresh, retry, expire, ttl int) bool {
ctx := storage.GetContext()
return register(ctx, name, owner, email, refresh, retry, expire, ttl)
st := register(ctx, name, owner, email, refresh, retry, expire, ttl)
if st == true {
runtime.Notify("RegisterDomain", name)
}
return st
}
// Register registers a new domain with the specified owner and name if it's available.
@ -455,6 +459,7 @@ func SetRecord(name string, typ RecordType, id byte, data string) {
ns.checkAdmin()
putRecord(ctx, tokenID, name, typ, id, data)
updateSoaSerial(ctx, tokenID)
runtime.Notify("SetRecord", name, typ)
}
func checkBaseRecords(typ RecordType, data string) bool {
@ -483,6 +488,7 @@ func AddRecord(name string, typ RecordType, data string) {
ns.checkAdmin()
addRecord(ctx, tokenID, name, typ, data)
updateSoaSerial(ctx, tokenID)
runtime.Notify("AddRecord", name, typ)
}
// GetRecords returns domain record of the specified type if it exists or an empty
@ -498,6 +504,7 @@ func GetRecords(name string, typ RecordType) []string {
func DeleteRecords(name string, typ RecordType) {
ctx := storage.GetContext()
deleteRecords(ctx, name, typ)
runtime.Notify("DeleteRecords", name, typ)
}
// DeleteRecords removes domain records with the specified type.
@ -529,6 +536,7 @@ func deleteRecords(ctx storage.Context, name string, typ RecordType) {
func DeleteDomain(name string) {
ctx := storage.GetContext()
deleteDomain(ctx, name)
runtime.Notify("DeleteDomain", name)
}
func deleteDomain(ctx storage.Context, name string) {

View file

@ -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) {
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
}

View file

@ -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,30 @@ 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: "SetRecord", 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"))})
c.CheckTxNotificationEvent(t, tx, 0, 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) {