forked from TrueCloudLab/frostfs-contract
[#114] nns: Add 'DeleteRecord'
Signed-off-by: Alexander Chuprov <a.chuprov@yadro.com>
This commit is contained in:
parent
3e221b973a
commit
8b7f44adef
4 changed files with 202 additions and 0 deletions
|
@ -14,6 +14,12 @@ events:
|
||||||
type: String
|
type: String
|
||||||
- name: type
|
- name: type
|
||||||
type: Integer
|
type: Integer
|
||||||
|
- name: DeleteRecord
|
||||||
|
parameters:
|
||||||
|
- name: name
|
||||||
|
type: String
|
||||||
|
- name: type
|
||||||
|
type: Integer
|
||||||
- name: DeleteRecords
|
- name: DeleteRecords
|
||||||
parameters:
|
parameters:
|
||||||
- name: name
|
- name: name
|
||||||
|
|
|
@ -527,6 +527,50 @@ func deleteRecords(ctx storage.Context, name string, typ RecordType) {
|
||||||
runtime.Notify("DeleteRecords", name, typ)
|
runtime.Notify("DeleteRecords", name, typ)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteRecord delete a record of the specified type by data in the provided domain.
|
||||||
|
// Returns false if the record was not found.
|
||||||
|
func DeleteRecord(name string, typ RecordType, data string) bool {
|
||||||
|
tokenID := []byte(tokenIDFromName(name))
|
||||||
|
if !checkBaseRecords(typ, data) {
|
||||||
|
panic("invalid record data")
|
||||||
|
}
|
||||||
|
ctx := storage.GetContext()
|
||||||
|
ns := getNameState(ctx, tokenID)
|
||||||
|
ns.checkAdmin()
|
||||||
|
return deleteRecord(ctx, tokenID, name, typ, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteRecord(ctx storage.Context, tokenId []byte, name string, typ RecordType, data string) bool {
|
||||||
|
recordsKey := getRecordsKeyByType(tokenId, name, typ)
|
||||||
|
|
||||||
|
var previousKey any
|
||||||
|
it := storage.Find(ctx, recordsKey, storage.KeysOnly)
|
||||||
|
for iterator.Next(it) {
|
||||||
|
key := iterator.Value(it).([]byte)
|
||||||
|
ss := storage.Get(ctx, key).([]byte)
|
||||||
|
|
||||||
|
ns := std.Deserialize(ss).(RecordState)
|
||||||
|
if ns.Name == name && ns.Type == typ && ns.Data == data {
|
||||||
|
previousKey = key
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if previousKey != nil {
|
||||||
|
data := storage.Get(ctx, key)
|
||||||
|
storage.Put(ctx, previousKey, data)
|
||||||
|
previousKey = key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if previousKey == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
storage.Delete(ctx, previousKey)
|
||||||
|
runtime.Notify("DeleteRecord", name, typ)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// DeleteDomain deletes the domain with the given name.
|
// DeleteDomain deletes the domain with the given name.
|
||||||
func DeleteDomain(name string) {
|
func DeleteDomain(name string) {
|
||||||
ctx := storage.GetContext()
|
ctx := storage.GetContext()
|
||||||
|
|
|
@ -29,6 +29,12 @@ type AddRecordEvent struct {
|
||||||
Type *big.Int
|
Type *big.Int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteRecordEvent represents "DeleteRecord" event emitted by the contract.
|
||||||
|
type DeleteRecordEvent struct {
|
||||||
|
Name string
|
||||||
|
Type *big.Int
|
||||||
|
}
|
||||||
|
|
||||||
// DeleteRecordsEvent represents "DeleteRecords" event emitted by the contract.
|
// DeleteRecordsEvent represents "DeleteRecords" event emitted by the contract.
|
||||||
type DeleteRecordsEvent struct {
|
type DeleteRecordsEvent struct {
|
||||||
Name string
|
Name string
|
||||||
|
@ -168,6 +174,44 @@ func (c *Contract) DeleteDomainUnsigned(name string) (*transaction.Transaction,
|
||||||
return c.actor.MakeUnsignedCall(c.hash, "deleteDomain", nil, name)
|
return c.actor.MakeUnsignedCall(c.hash, "deleteDomain", nil, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Contract) scriptForDeleteRecord(name string, typ *big.Int, data string) ([]byte, error) {
|
||||||
|
return smartcontract.CreateCallWithAssertScript(c.hash, "deleteRecord", name, typ, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteRecord creates a transaction invoking `deleteRecord` method of the contract.
|
||||||
|
// This transaction is signed and immediately sent to the network.
|
||||||
|
// The values returned are its hash, ValidUntilBlock value and error if any.
|
||||||
|
func (c *Contract) DeleteRecord(name string, typ *big.Int, data string) (util.Uint256, uint32, error) {
|
||||||
|
script, err := c.scriptForDeleteRecord(name, typ, data)
|
||||||
|
if err != nil {
|
||||||
|
return util.Uint256{}, 0, err
|
||||||
|
}
|
||||||
|
return c.actor.SendRun(script)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteRecordTransaction creates a transaction invoking `deleteRecord` method of the contract.
|
||||||
|
// This transaction is signed, but not sent to the network, instead it's
|
||||||
|
// returned to the caller.
|
||||||
|
func (c *Contract) DeleteRecordTransaction(name string, typ *big.Int, data string) (*transaction.Transaction, error) {
|
||||||
|
script, err := c.scriptForDeleteRecord(name, typ, data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return c.actor.MakeRun(script)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteRecordUnsigned creates a transaction invoking `deleteRecord` method of the contract.
|
||||||
|
// This transaction is not signed, it's simply returned to the caller.
|
||||||
|
// Any fields of it that do not affect fees can be changed (ValidUntilBlock,
|
||||||
|
// Nonce), fee values (NetworkFee, SystemFee) can be increased as well.
|
||||||
|
func (c *Contract) DeleteRecordUnsigned(name string, typ *big.Int, data string) (*transaction.Transaction, error) {
|
||||||
|
script, err := c.scriptForDeleteRecord(name, typ, data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return c.actor.MakeUnsignedRun(script, nil)
|
||||||
|
}
|
||||||
|
|
||||||
// DeleteRecords creates a transaction invoking `deleteRecords` method of the contract.
|
// DeleteRecords creates a transaction invoking `deleteRecords` method of the contract.
|
||||||
// This transaction is signed and immediately sent to the network.
|
// This transaction is signed and immediately sent to the network.
|
||||||
// The values returned are its hash, ValidUntilBlock value and error if any.
|
// The values returned are its hash, ValidUntilBlock value and error if any.
|
||||||
|
@ -510,6 +554,73 @@ func (e *AddRecordEvent) FromStackItem(item *stackitem.Array) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteRecordEventsFromApplicationLog retrieves a set of all emitted events
|
||||||
|
// with "DeleteRecord" name from the provided [result.ApplicationLog].
|
||||||
|
func DeleteRecordEventsFromApplicationLog(log *result.ApplicationLog) ([]*DeleteRecordEvent, error) {
|
||||||
|
if log == nil {
|
||||||
|
return nil, errors.New("nil application log")
|
||||||
|
}
|
||||||
|
|
||||||
|
var res []*DeleteRecordEvent
|
||||||
|
for i, ex := range log.Executions {
|
||||||
|
for j, e := range ex.Events {
|
||||||
|
if e.Name != "DeleteRecord" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
event := new(DeleteRecordEvent)
|
||||||
|
err := event.FromStackItem(e.Item)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to deserialize DeleteRecordEvent from stackitem (execution #%d, event #%d): %w", i, j, err)
|
||||||
|
}
|
||||||
|
res = append(res, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromStackItem converts provided [stackitem.Array] to DeleteRecordEvent or
|
||||||
|
// returns an error if it's not possible to do to so.
|
||||||
|
func (e *DeleteRecordEvent) 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
|
// DeleteRecordsEventsFromApplicationLog retrieves a set of all emitted events
|
||||||
// with "DeleteRecords" name from the provided [result.ApplicationLog].
|
// with "DeleteRecords" name from the provided [result.ApplicationLog].
|
||||||
func DeleteRecordsEventsFromApplicationLog(log *result.ApplicationLog) ([]*DeleteRecordsEvent, error) {
|
func DeleteRecordsEventsFromApplicationLog(log *result.ApplicationLog) ([]*DeleteRecordsEvent, error) {
|
||||||
|
|
|
@ -178,6 +178,47 @@ func TestNNSRegister(t *testing.T) {
|
||||||
|
|
||||||
c.Invoke(t, stackitem.Null{}, "getRecords", "testdomain.com", int64(nns.TXT))
|
c.Invoke(t, stackitem.Null{}, "getRecords", "testdomain.com", int64(nns.TXT))
|
||||||
|
|
||||||
|
cAcc.Invoke(t, stackitem.Null{}, "addRecord",
|
||||||
|
"testdomain.com", int64(nns.TXT), "rec1")
|
||||||
|
|
||||||
|
cAcc.Invoke(t, stackitem.Null{}, "addRecord",
|
||||||
|
"testdomain.com", int64(nns.TXT), "rec2")
|
||||||
|
|
||||||
|
cAcc.Invoke(t, stackitem.Null{}, "addRecord",
|
||||||
|
"testdomain.com", int64(nns.TXT), "rec3")
|
||||||
|
|
||||||
|
cAcc.Invoke(t, stackitem.Null{}, "addRecord",
|
||||||
|
"testdomain.com", int64(nns.TXT), "rec4")
|
||||||
|
|
||||||
|
cAcc.Invoke(t, false, "deleteRecord",
|
||||||
|
"testdomain.com", int64(nns.TXT), "rec9999")
|
||||||
|
|
||||||
|
cAcc.Invoke(t, true, "deleteRecord",
|
||||||
|
"testdomain.com", int64(nns.TXT), "rec1")
|
||||||
|
|
||||||
|
expected = stackitem.NewArray([]stackitem.Item{
|
||||||
|
stackitem.NewByteArray([]byte("rec2")),
|
||||||
|
stackitem.NewByteArray([]byte("rec3")),
|
||||||
|
stackitem.NewByteArray([]byte("rec4")),
|
||||||
|
})
|
||||||
|
c.Invoke(t, expected, "getRecords", "testdomain.com", int64(nns.TXT))
|
||||||
|
|
||||||
|
cAcc.Invoke(t, true, "deleteRecord",
|
||||||
|
"testdomain.com", int64(nns.TXT), "rec4")
|
||||||
|
|
||||||
|
cAcc.Invoke(t, true, "deleteRecord",
|
||||||
|
"testdomain.com", int64(nns.TXT), "rec2")
|
||||||
|
|
||||||
|
expected = stackitem.NewArray([]stackitem.Item{
|
||||||
|
stackitem.NewByteArray([]byte("rec3")),
|
||||||
|
})
|
||||||
|
c.Invoke(t, expected, "getRecords", "testdomain.com", int64(nns.TXT))
|
||||||
|
|
||||||
|
cAcc.Invoke(t, true, "deleteRecord",
|
||||||
|
"testdomain.com", int64(nns.TXT), "rec3")
|
||||||
|
|
||||||
|
c.Invoke(t, stackitem.Null{}, "getRecords", "testdomain.com", int64(nns.TXT))
|
||||||
|
|
||||||
tx = cAcc.Invoke(t, stackitem.Null{}, "deleteDomain", "testdomain.com")
|
tx = cAcc.Invoke(t, stackitem.Null{}, "deleteDomain", "testdomain.com")
|
||||||
expected = stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte("testdomain.com")),
|
expected = stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte("testdomain.com")),
|
||||||
stackitem.NewBigInteger(big.NewInt(int64(nns.CNAME)))})
|
stackitem.NewBigInteger(big.NewInt(int64(nns.CNAME)))})
|
||||||
|
|
Loading…
Reference in a new issue