From b412909f4080a2a544e0fcafa4166ccb24d8cd69 Mon Sep 17 00:00:00 2001 From: Denis Kirillov Date: Tue, 28 Sep 2021 11:54:55 +0300 Subject: [PATCH] [#125] Support multiple records of the same type Signed-off-by: Denis Kirillov --- nns/nns_contract.go | 76 +++++++++++++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 27 deletions(-) diff --git a/nns/nns_contract.go b/nns/nns_contract.go index f1174e3..fb589c8 100644 --- a/nns/nns_contract.go +++ b/nns/nns_contract.go @@ -340,29 +340,33 @@ func SetRecord(name string, typ RecordType, data string) { putRecord(ctx, tokenID, name, typ, data) } -// GetRecord returns domain record of the specified type if it exists or an empty +// GetRecords returns domain record of the specified type if it exists or an empty // string if not. -func GetRecord(name string, typ RecordType) string { +func GetRecords(name string, typ RecordType) []string { tokenID := []byte(tokenIDFromName(name)) ctx := storage.GetReadOnlyContext() _ = getNameState(ctx, tokenID) // ensure not expired - return getRecord(ctx, tokenID, name, typ) + return getRecords(ctx, tokenID, name, typ) } -// DeleteRecord removes domain record with the specified type. -func DeleteRecord(name string, typ RecordType) { +// DeleteRecords removes domain record with the specified type. +func DeleteRecords(name string, typ RecordType) { tokenID := []byte(tokenIDFromName(name)) ctx := storage.GetContext() ns := getNameState(ctx, tokenID) ns.checkAdmin() - recordKey := getRecordKey(tokenID, name, typ) - storage.Delete(ctx, recordKey) + recordsKey := getRecordKey(tokenID, name, typ) + records := storage.Find(ctx, recordsKey, storage.KeysOnly) + for iterator.Next(records) { + r := iterator.Value(records).(string) + storage.Delete(ctx, r) + } } // Resolve resolves given name (not more then three redirects are allowed). -func Resolve(name string, typ RecordType) string { +func Resolve(name string, typ RecordType) []string { ctx := storage.GetReadOnlyContext() - return resolve(ctx, name, typ, 2) + return resolve(ctx, nil, name, typ, 2) } // GetAllRecords returns an Iterator with RecordState items for given name. @@ -455,20 +459,29 @@ func putNameStateWithKey(ctx storage.Context, tokenKey []byte, ns NameState) { storage.Put(ctx, nameKey, nsBytes) } -// getRecord returns domain record. -func getRecord(ctx storage.Context, tokenId []byte, name string, typ RecordType) string { - recordKey := getRecordKey(tokenId, name, typ) - recBytes := storage.Get(ctx, recordKey) - if recBytes == nil { - return recBytes.(string) // A hack to actually return NULL. +// getRecords returns domain record. +func getRecords(ctx storage.Context, tokenId []byte, name string, typ RecordType) []string { + recordsKey := getRecordKey(tokenId, name, typ) + + var result []string + records := storage.Find(ctx, recordsKey, storage.DeserializeValues) + for iterator.Next(records) { + r := iterator.Value(records).(struct { + key string + rs RecordState + }) + value := r.rs.Data + rTyp := r.key[len(r.key)-5] + if rTyp == byte(typ) { + result = append(result, value) + } } - record := std.Deserialize(recBytes.([]byte)).(RecordState) - return record.Data + return result } // putRecord stores domain record. func putRecord(ctx storage.Context, tokenId []byte, name string, typ RecordType, record string) { - recordKey := getRecordKey(tokenId, name, typ) + recordKey := addUniqueBytesToRecordKey(getRecordKey(tokenId, name, typ)) rs := RecordState{ Name: name, Type: typ, @@ -490,6 +503,13 @@ func getRecordKey(tokenId []byte, name string, typ RecordType) []byte { return append(recordKey, []byte{byte(typ)}...) } +// addUniqueBytesToRecordKey add to key 4 unique bytes to allow store several records with the same type. +func addUniqueBytesToRecordKey(recordKey []byte) []byte { + tx := runtime.GetScriptContainer() + bb := []byte(tx.Hash) + return append(recordKey, bb[:4]...) +} + // isValid returns true if the provided address is a valid Uint160. func isValid(address interop.Hash160) bool { return address != nil && len(address) == 20 @@ -684,11 +704,11 @@ func tokenIDFromName(name string) string { // resolve resolves provided name using record with the specified type and given // maximum redirections constraint. -func resolve(ctx storage.Context, name string, typ RecordType, redirect int) string { +func resolve(ctx storage.Context, res []string, name string, typ RecordType, redirect int) []string { if redirect < 0 { panic("invalid redirect") } - records := getRecords(ctx, name) + records := getAllRecords(ctx, name) cname := "" for iterator.Next(records) { r := iterator.Value(records).(struct { @@ -696,23 +716,25 @@ func resolve(ctx storage.Context, name string, typ RecordType, redirect int) str rs RecordState }) value := r.rs.Data - rTyp := r.key[len(r.key)-1] + rTyp := r.key[len(r.key)-5] if rTyp == byte(typ) { - return value + res = append(res, value) } if rTyp == byte(CNAME) { cname = value } } - if cname == "" { - return string([]byte(nil)) + if cname == "" || typ == CNAME { + return res } - return resolve(ctx, cname, typ, redirect-1) + + res = append(res, cname) + return resolve(ctx, res, cname, typ, redirect-1) } -// getRecords returns iterator over the set of records corresponded with the +// getAllRecords returns iterator over the set of records corresponded with the // specified name. -func getRecords(ctx storage.Context, name string) iterator.Iterator { +func getAllRecords(ctx storage.Context, name string) iterator.Iterator { tokenID := []byte(tokenIDFromName(name)) _ = getNameState(ctx, tokenID) recordsKey := getRecordsKey(tokenID, name)