From 1891f40553d090fcba2bf635790f470566b14446 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Fri, 2 Sep 2022 12:14:51 +0300 Subject: [PATCH 1/3] NameService: require admin signature for subdomain registration Originally implemented in https://github.com/nspcc-dev/neofs-contract/pull/139/commits/14fc08629180e9d53d7efe431fb20b245c6ecf78. --- src/NameService/NameService.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/NameService/NameService.cs b/src/NameService/NameService.cs index a16f829..a5288ec 100644 --- a/src/NameService/NameService.cs +++ b/src/NameService/NameService.cs @@ -209,6 +209,11 @@ public static bool Register(string name, UInt160 owner) string[] fragments = SplitAndCheck(name, false); if (fragments is null) throw new FormatException("The format of the name is incorrect."); if (rootMap[fragments[^1]] is null) throw new Exception("The root does not exist."); + ByteString parentKey = GetKey(fragments[1]); + ByteString parentBuffer = nameMap[parentKey]; + if (parentBuffer is null) throw new InvalidOperationException("Unknown parent domain."); + NameState parent = (NameState)StdLib.Deserialize(parentBuffer); + parent.CheckAdmin(); if (!Runtime.CheckWitness(owner)) throw new InvalidOperationException("No authorization."); long price = GetPrice((byte)fragments[0].Length); if (price < 0) From f397dc8a6817608ebe07810142ec9dc8410a2854 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Fri, 9 Sep 2022 12:23:11 +0300 Subject: [PATCH 2/3] NameService: check domain expiration for read functions And refactor GetRecords/GetAllRecords along the way, move commonly used code to a separate function. Originally introduced in https://github.com/nspcc-dev/neofs-contract/pull/139/commits/432c02a3696e42a03863dbe3fae2a889ec8224e6. --- src/NameService/NameService.cs | 126 +++++++++++++++++++++------------ 1 file changed, 80 insertions(+), 46 deletions(-) diff --git a/src/NameService/NameService.cs b/src/NameService/NameService.cs index a5288ec..851748f 100644 --- a/src/NameService/NameService.cs +++ b/src/NameService/NameService.cs @@ -52,8 +52,7 @@ public sealed class NameService : Framework.SmartContract public static UInt160 OwnerOf(ByteString tokenId) { StorageMap nameMap = new(Storage.CurrentContext, Prefix_Name); - NameState token = (NameState)StdLib.Deserialize(nameMap[GetKey(tokenId)]); - token.EnsureNotExpired(); + NameState token = getNameState(nameMap, tokenId); return token.Owner; } @@ -61,8 +60,7 @@ public static UInt160 OwnerOf(ByteString tokenId) public static Map Properties(ByteString tokenId) { StorageMap nameMap = new(Storage.CurrentContext, Prefix_Name); - NameState token = (NameState)StdLib.Deserialize(nameMap[GetKey(tokenId)]); - token.EnsureNotExpired(); + NameState token = getNameState(nameMap, tokenId); Map map = new(); map["name"] = token.Name; map["expiration"] = token.Expiration; @@ -193,10 +191,7 @@ public static bool IsAvailable(string name) if (rootMap[fragments[^1]] is null) throw new Exception("The root does not exist."); long price = GetPrice((byte)fragments[0].Length); if (price < 0) return false; - ByteString buffer = nameMap[GetKey(name)]; - if (buffer is null) return true; - NameState token = (NameState)StdLib.Deserialize(buffer); - return Runtime.Time >= token.Expiration; + return parentExpired(nameMap, 0, fragments); } public static bool Register(string name, UInt160 owner) @@ -209,10 +204,9 @@ public static bool Register(string name, UInt160 owner) string[] fragments = SplitAndCheck(name, false); if (fragments is null) throw new FormatException("The format of the name is incorrect."); if (rootMap[fragments[^1]] is null) throw new Exception("The root does not exist."); + if (parentExpired(nameMap, 1, fragments)) throw new InvalidOperationException("One of the parent domains has expired."); ByteString parentKey = GetKey(fragments[1]); - ByteString parentBuffer = nameMap[parentKey]; - if (parentBuffer is null) throw new InvalidOperationException("Unknown parent domain."); - NameState parent = (NameState)StdLib.Deserialize(parentBuffer); + NameState parent = (NameState)StdLib.Deserialize(nameMap[parentKey]); parent.CheckAdmin(); if (!Runtime.CheckWitness(owner)) throw new InvalidOperationException("No authorization."); long price = GetPrice((byte)fragments[0].Length); @@ -283,12 +277,11 @@ public static ulong Renew(string name, byte years) else Runtime.BurnGas(price * years); StorageMap nameMap = new(Storage.CurrentContext, Prefix_Name); - ByteString tokenKey = GetKey(name); - NameState token = (NameState)StdLib.Deserialize(nameMap[tokenKey]); - token.EnsureNotExpired(); + NameState token = getNameState(nameMap, name); token.Expiration += OneYear * years; if (token.Expiration > Runtime.Time + TenYears) throw new ArgumentException("You can't renew a domain name for more than 10 years in total."); + ByteString tokenKey = GetKey(name); nameMap[tokenKey] = StdLib.Serialize(token); return token.Expiration; } @@ -298,12 +291,11 @@ public static void SetAdmin(string name, UInt160 admin) if (name.Length > NameMaxLength) throw new FormatException("The format of the name is incorrect."); if (admin is not null && !Runtime.CheckWitness(admin)) throw new InvalidOperationException("No authorization."); StorageMap nameMap = new(Storage.CurrentContext, Prefix_Name); - ByteString tokenKey = GetKey(name); - NameState token = (NameState)StdLib.Deserialize(nameMap[tokenKey]); - token.EnsureNotExpired(); + NameState token = getNameState(nameMap, name); if (!Runtime.CheckWitness(token.Owner)) throw new InvalidOperationException("No authorization."); UInt160 old = token.Admin; token.Admin = admin; + ByteString tokenKey = GetKey(name); nameMap[tokenKey] = StdLib.Serialize(token); OnSetAdmin(name, old, admin); } @@ -313,8 +305,7 @@ public static void SetRecord(string name, RecordType type, string data) StorageContext context = Storage.CurrentContext; StorageMap nameMap = new(context, Prefix_Name); StorageMap recordMap = new(context, Prefix_Record); - string[] fragments = SplitAndCheck(name, true); - if (fragments is null) throw new FormatException("The format of the name is incorrect."); + string tokenId = tokenIDFromName(name); switch (type) { case RecordType.A: @@ -332,11 +323,9 @@ public static void SetRecord(string name, RecordType type, string data) default: throw new InvalidOperationException("The record type is not supported."); } - string tokenId = name[^(fragments[^2].Length + fragments[^1].Length + 1)..]; - ByteString tokenKey = GetKey(tokenId); - NameState token = (NameState)StdLib.Deserialize(nameMap[tokenKey]); - token.EnsureNotExpired(); + NameState token = getNameState(nameMap, tokenId); token.CheckAdmin(); + ByteString tokenKey = GetKey(tokenId); byte[] recordKey = GetRecordKey(tokenKey, name, type); recordMap.PutObject(recordKey, new RecordState { @@ -352,12 +341,9 @@ public static string GetRecord(string name, RecordType type) StorageContext context = Storage.CurrentContext; StorageMap nameMap = new(context, Prefix_Name); StorageMap recordMap = new(context, Prefix_Record); - string[] fragments = SplitAndCheck(name, true); - if (fragments is null) throw new FormatException("The format of the name is incorrect."); - string tokenId = name[^(fragments[^2].Length + fragments[^1].Length + 1)..]; + string tokenId = tokenIDFromName(name); + getNameState(nameMap, tokenId); // ensure not expired ByteString tokenKey = GetKey(tokenId); - NameState token = (NameState)StdLib.Deserialize(nameMap[tokenKey]); - token.EnsureNotExpired(); byte[] recordKey = GetRecordKey(tokenKey, name, type); RecordState record = (RecordState)recordMap.GetObject(recordKey); if (record is null) return null; @@ -370,10 +356,11 @@ public static Iterator GetAllRecords(string name) StorageContext context = Storage.CurrentContext; StorageMap nameMap = new(context, Prefix_Name); StorageMap recordMap = new(context, Prefix_Record); - ByteString tokenKey = GetKey(name); - NameState token = (NameState)StdLib.Deserialize(nameMap[tokenKey]); - token.EnsureNotExpired(); - return (Iterator)recordMap.Find(tokenKey, FindOptions.ValuesOnly | FindOptions.DeserializeValues); + string tokenId = tokenIDFromName(name); + getNameState(nameMap, tokenId); // ensure not expired + ByteString tokenKey = GetKey(tokenId); + byte[] recordsKey = GetRecordsKey(tokenKey, name); + return (Iterator)recordMap.Find(recordsKey, FindOptions.ValuesOnly | FindOptions.DeserializeValues); } public static void DeleteRecord(string name, RecordType type) @@ -381,13 +368,10 @@ public static void DeleteRecord(string name, RecordType type) StorageContext context = Storage.CurrentContext; StorageMap nameMap = new(context, Prefix_Name); StorageMap recordMap = new(context, Prefix_Record); - string[] fragments = SplitAndCheck(name, true); - if (fragments is null) throw new FormatException("The format of the name is incorrect."); - string tokenId = name[^(fragments[^2].Length + fragments[^1].Length + 1)..]; - ByteString tokenKey = GetKey(tokenId); - NameState token = (NameState)StdLib.Deserialize(nameMap[tokenKey]); - token.EnsureNotExpired(); + string tokenId = tokenIDFromName(name); + NameState token = getNameState(nameMap, tokenId); token.CheckAdmin(); + ByteString tokenKey = GetKey(tokenId); byte[] recordKey = GetRecordKey(tokenKey, name, type); recordMap.Delete(recordKey); } @@ -422,14 +406,11 @@ private static string Resolve(string name, RecordType type, int redirect) StorageContext context = Storage.CurrentContext; StorageMap nameMap = new(context, Prefix_Name); StorageMap recordMap = new(context, Prefix_Record); - string[] fragments = SplitAndCheck(name, true); - if (fragments is null) throw new FormatException("The format of the name is incorrect."); - string tokenId = name[^(fragments[^2].Length + fragments[^1].Length + 1)..]; + string tokenId = tokenIDFromName(name); + getNameState(nameMap, tokenId); // ensure not expired ByteString tokenKey = GetKey(tokenId); - NameState token = (NameState)StdLib.Deserialize(nameMap[tokenKey]); - token.EnsureNotExpired(); - byte[] recordKey = Helper.Concat((byte[])tokenKey, GetKey(name)); - return (Iterator<(ByteString, RecordState)>)recordMap.Find(recordKey, FindOptions.DeserializeValues); + byte[] recordsKey = GetRecordsKey(tokenKey, name); + return (Iterator<(ByteString, RecordState)>)recordMap.Find(recordsKey, FindOptions.DeserializeValues); } [DisplayName("_deploy")] @@ -463,10 +444,15 @@ private static ByteString GetKey(string tokenId) private static byte[] GetRecordKey(ByteString tokenKey, string name, RecordType type) { - byte[] key = Helper.Concat((byte[])tokenKey, GetKey(name)); + byte[] key = GetRecordsKey(tokenKey, name); return Helper.Concat(key, ((byte)type).ToByteArray()); } + private static byte[] GetRecordsKey(ByteString tokenKey, string name) + { + return Helper.Concat((byte[])tokenKey, GetKey(name)); + } + private static void PostTransfer(UInt160 from, UInt160 to, ByteString tokenId, object data) { OnTransfer(from, to, 1, tokenId); @@ -620,5 +606,53 @@ private static bool CheckIPv6(string ipv6) } return true; } + + /// + /// Checks provided name for validness and returns corresponding token ID. + /// + private static string tokenIDFromName(string name) + { + string[] fragments = SplitAndCheck(name, true); + if (fragments is null) throw new FormatException("The format of the name is incorrect."); + if (fragments.Length == 1) return name; + return name[^(fragments[^2].Length + fragments[^1].Length + 1)..]; + } + + /// + /// Retrieves NameState from storage and checks that it's not expired as far as the parent domain. + /// + private static NameState getNameState(StorageMap nameMap, string tokenId) + { + ByteString tokenBytes = nameMap[GetKey(tokenId)]; + if (tokenBytes is null) throw new InvalidOperationException("Unknown token."); + NameState token = (NameState)StdLib.Deserialize(tokenBytes); + token.EnsureNotExpired(); + string[] fragments = StdLib.StringSplit(tokenId, "."); + if (parentExpired(nameMap, 1, fragments)) throw new InvalidOperationException("Parent domain has expired."); + return token; + } + + /// + /// Returns true if any domain from fragments doesn't exist or expired. + /// + /// Registered domain names storage map. + /// The deepest subdomain to check. + /// The array of domain name fragments. + /// Whether any domain fragment doesn't exist or expired. + private static bool parentExpired(StorageMap nameMap, int first, string[] fragments) + { + int last = fragments.Length - 1; + string name = fragments[last]; + for (int i = last; i >= first; i--) { + if (i != last) { + name = fragments[i] + "." + name; + } + ByteString buffer = nameMap[GetKey(name)]; + if (buffer is null) return true; + NameState token = (NameState)StdLib.Deserialize(buffer); + if (Runtime.Time >= token.Expiration) return true; + } + return false; + } } } From 3846bcc22909cf45f60ec1942311a7da3293d5e7 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Thu, 1 Sep 2022 19:55:48 +0300 Subject: [PATCH 3/3] NameService: replace root with TLD And move commonly used code to a separate methods. Originally introduced in https://github.com/nspcc-dev/neofs-contract/pull/139/commits/4b86891d57ef12c3f37a9ef27902c73d96812934. --- src/NameService/NameService.cs | 35 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/src/NameService/NameService.cs b/src/NameService/NameService.cs index 851748f..8a6cb6b 100644 --- a/src/NameService/NameService.cs +++ b/src/NameService/NameService.cs @@ -140,17 +140,6 @@ public static void Update(ByteString nef, string manifest) ContractManagement.Update(nef, manifest); } - public static void AddRoot(string root) - { - CheckCommittee(); - if (!CheckFragment(root, true)) - throw new FormatException("The format of the root is incorrect."); - StorageMap rootMap = new(Storage.CurrentContext, Prefix_Root); - if (rootMap[root] is not null) - throw new InvalidOperationException("The root already exists."); - rootMap.Put(root, 0); - } - [Safe] public static Iterator Roots() { @@ -188,7 +177,10 @@ public static bool IsAvailable(string name) StorageMap nameMap = new(context, Prefix_Name); string[] fragments = SplitAndCheck(name, false); if (fragments is null) throw new FormatException("The format of the name is incorrect."); - if (rootMap[fragments[^1]] is null) throw new Exception("The root does not exist."); + if (rootMap[fragments[^1]] is null) { + if (fragments.Length != 1) throw new InvalidOperationException("The TLD is not found"); + return true; + } long price = GetPrice((byte)fragments[0].Length); if (price < 0) return false; return parentExpired(nameMap, 0, fragments); @@ -203,11 +195,18 @@ public static bool Register(string name, UInt160 owner) StorageMap nameMap = new(context, Prefix_Name); string[] fragments = SplitAndCheck(name, false); if (fragments is null) throw new FormatException("The format of the name is incorrect."); - if (rootMap[fragments[^1]] is null) throw new Exception("The root does not exist."); - if (parentExpired(nameMap, 1, fragments)) throw new InvalidOperationException("One of the parent domains has expired."); - ByteString parentKey = GetKey(fragments[1]); - NameState parent = (NameState)StdLib.Deserialize(nameMap[parentKey]); - parent.CheckAdmin(); + ByteString tld = rootMap[fragments[^1]]; + if (fragments.Length == 1) { + CheckCommittee(); + if (tld is not null) throw new InvalidOperationException("TLD already exists."); + rootMap.Put(fragments[^1], 0); + } else { + if (tld is null) throw new InvalidOperationException("TLD does not exist."); + if (parentExpired(nameMap, 1, fragments)) throw new InvalidOperationException("One of the parent domains has expired."); + ByteString parentKey = GetKey(fragments[1]); + NameState parent = (NameState)StdLib.Deserialize(nameMap[parentKey]); + parent.CheckAdmin(); + } if (!Runtime.CheckWitness(owner)) throw new InvalidOperationException("No authorization."); long price = GetPrice((byte)fragments[0].Length); if (price < 0) @@ -505,7 +504,7 @@ private static string[] SplitAndCheck(string name, bool allowMultipleFragments) if (length < 3 || length > NameMaxLength) return null; string[] fragments = StdLib.StringSplit(name, "."); length = fragments.Length; - if (length < 2 || length > 8) return null; + if (length > 8) return null; if (length > 2 && !allowMultipleFragments) return null; for (int i = 0; i < length; i++) if (!CheckFragment(fragments[i], i == length - 1))