Skip to content

Commit 0c57843

Browse files
committed
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 nspcc-dev/neofs-contract@432c02a.
1 parent cc580f9 commit 0c57843

File tree

1 file changed

+80
-46
lines changed

1 file changed

+80
-46
lines changed

src/NameService/NameService.cs

Lines changed: 80 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -52,17 +52,15 @@ public sealed class NameService : Framework.SmartContract
5252
public static UInt160 OwnerOf(ByteString tokenId)
5353
{
5454
StorageMap nameMap = new(Storage.CurrentContext, Prefix_Name);
55-
NameState token = (NameState)StdLib.Deserialize(nameMap[GetKey(tokenId)]);
56-
token.EnsureNotExpired();
55+
NameState token = getNameState(nameMap, tokenId);
5756
return token.Owner;
5857
}
5958

6059
[Safe]
6160
public static Map<string, object> Properties(ByteString tokenId)
6261
{
6362
StorageMap nameMap = new(Storage.CurrentContext, Prefix_Name);
64-
NameState token = (NameState)StdLib.Deserialize(nameMap[GetKey(tokenId)]);
65-
token.EnsureNotExpired();
63+
NameState token = getNameState(nameMap, tokenId);
6664
Map<string, object> map = new();
6765
map["name"] = token.Name;
6866
map["expiration"] = token.Expiration;
@@ -193,10 +191,7 @@ public static bool IsAvailable(string name)
193191
if (rootMap[fragments[^1]] is null) throw new Exception("The root does not exist.");
194192
long price = GetPrice((byte)fragments[0].Length);
195193
if (price < 0) return false;
196-
ByteString buffer = nameMap[GetKey(name)];
197-
if (buffer is null) return true;
198-
NameState token = (NameState)StdLib.Deserialize(buffer);
199-
return Runtime.Time >= token.Expiration;
194+
return parentExpired(nameMap, 0, fragments);
200195
}
201196

202197
public static bool Register(string name, UInt160 owner)
@@ -209,10 +204,9 @@ public static bool Register(string name, UInt160 owner)
209204
string[] fragments = SplitAndCheck(name, false);
210205
if (fragments is null) throw new FormatException("The format of the name is incorrect.");
211206
if (rootMap[fragments[^1]] is null) throw new Exception("The root does not exist.");
207+
if (parentExpired(nameMap, 1, fragments)) throw new InvalidOperationException("One of the parent domains has expired.");
212208
ByteString parentKey = GetKey(fragments[1]);
213-
ByteString parentBuffer = nameMap[parentKey];
214-
if (parentBuffer is null) throw new InvalidOperationException("Unknown parent domain.");
215-
NameState parent = (NameState)StdLib.Deserialize(parentBuffer);
209+
NameState parent = (NameState)StdLib.Deserialize(nameMap[parentKey]);
216210
parent.CheckAdmin();
217211
if (!Runtime.CheckWitness(owner)) throw new InvalidOperationException("No authorization.");
218212
long price = GetPrice((byte)fragments[0].Length);
@@ -283,12 +277,11 @@ public static ulong Renew(string name, byte years)
283277
else
284278
Runtime.BurnGas(price * years);
285279
StorageMap nameMap = new(Storage.CurrentContext, Prefix_Name);
286-
ByteString tokenKey = GetKey(name);
287-
NameState token = (NameState)StdLib.Deserialize(nameMap[tokenKey]);
288-
token.EnsureNotExpired();
280+
NameState token = getNameState(nameMap, name);
289281
token.Expiration += OneYear * years;
290282
if (token.Expiration > Runtime.Time + TenYears)
291283
throw new ArgumentException("You can't renew a domain name for more than 10 years in total.");
284+
ByteString tokenKey = GetKey(name);
292285
nameMap[tokenKey] = StdLib.Serialize(token);
293286
return token.Expiration;
294287
}
@@ -298,12 +291,11 @@ public static void SetAdmin(string name, UInt160 admin)
298291
if (name.Length > NameMaxLength) throw new FormatException("The format of the name is incorrect.");
299292
if (admin is not null && !Runtime.CheckWitness(admin)) throw new InvalidOperationException("No authorization.");
300293
StorageMap nameMap = new(Storage.CurrentContext, Prefix_Name);
301-
ByteString tokenKey = GetKey(name);
302-
NameState token = (NameState)StdLib.Deserialize(nameMap[tokenKey]);
303-
token.EnsureNotExpired();
294+
NameState token = getNameState(nameMap, name);
304295
if (!Runtime.CheckWitness(token.Owner)) throw new InvalidOperationException("No authorization.");
305296
UInt160 old = token.Admin;
306297
token.Admin = admin;
298+
ByteString tokenKey = GetKey(name);
307299
nameMap[tokenKey] = StdLib.Serialize(token);
308300
OnSetAdmin(name, old, admin);
309301
}
@@ -313,8 +305,7 @@ public static void SetRecord(string name, RecordType type, string data)
313305
StorageContext context = Storage.CurrentContext;
314306
StorageMap nameMap = new(context, Prefix_Name);
315307
StorageMap recordMap = new(context, Prefix_Record);
316-
string[] fragments = SplitAndCheck(name, true);
317-
if (fragments is null) throw new FormatException("The format of the name is incorrect.");
308+
string tokenId = tokenIDFromName(name);
318309
switch (type)
319310
{
320311
case RecordType.A:
@@ -332,11 +323,9 @@ public static void SetRecord(string name, RecordType type, string data)
332323
default:
333324
throw new InvalidOperationException("The record type is not supported.");
334325
}
335-
string tokenId = name[^(fragments[^2].Length + fragments[^1].Length + 1)..];
336-
ByteString tokenKey = GetKey(tokenId);
337-
NameState token = (NameState)StdLib.Deserialize(nameMap[tokenKey]);
338-
token.EnsureNotExpired();
326+
NameState token = getNameState(nameMap, tokenId);
339327
token.CheckAdmin();
328+
ByteString tokenKey = GetKey(tokenId);
340329
byte[] recordKey = GetRecordKey(tokenKey, name, type);
341330
recordMap.PutObject(recordKey, new RecordState
342331
{
@@ -352,12 +341,9 @@ public static string GetRecord(string name, RecordType type)
352341
StorageContext context = Storage.CurrentContext;
353342
StorageMap nameMap = new(context, Prefix_Name);
354343
StorageMap recordMap = new(context, Prefix_Record);
355-
string[] fragments = SplitAndCheck(name, true);
356-
if (fragments is null) throw new FormatException("The format of the name is incorrect.");
357-
string tokenId = name[^(fragments[^2].Length + fragments[^1].Length + 1)..];
344+
string tokenId = tokenIDFromName(name);
345+
getNameState(nameMap, tokenId); // ensure not expired
358346
ByteString tokenKey = GetKey(tokenId);
359-
NameState token = (NameState)StdLib.Deserialize(nameMap[tokenKey]);
360-
token.EnsureNotExpired();
361347
byte[] recordKey = GetRecordKey(tokenKey, name, type);
362348
RecordState record = (RecordState)recordMap.GetObject(recordKey);
363349
if (record is null) return null;
@@ -370,24 +356,22 @@ public static Iterator<RecordState> GetAllRecords(string name)
370356
StorageContext context = Storage.CurrentContext;
371357
StorageMap nameMap = new(context, Prefix_Name);
372358
StorageMap recordMap = new(context, Prefix_Record);
373-
ByteString tokenKey = GetKey(name);
374-
NameState token = (NameState)StdLib.Deserialize(nameMap[tokenKey]);
375-
token.EnsureNotExpired();
376-
return (Iterator<RecordState>)recordMap.Find(tokenKey, FindOptions.ValuesOnly | FindOptions.DeserializeValues);
359+
string tokenId = tokenIDFromName(name);
360+
getNameState(nameMap, tokenId); // ensure not expired
361+
ByteString tokenKey = GetKey(tokenId);
362+
byte[] recordsKey = GetRecordsKey(tokenKey, name);
363+
return (Iterator<RecordState>)recordMap.Find(recordsKey, FindOptions.ValuesOnly | FindOptions.DeserializeValues);
377364
}
378365

379366
public static void DeleteRecord(string name, RecordType type)
380367
{
381368
StorageContext context = Storage.CurrentContext;
382369
StorageMap nameMap = new(context, Prefix_Name);
383370
StorageMap recordMap = new(context, Prefix_Record);
384-
string[] fragments = SplitAndCheck(name, true);
385-
if (fragments is null) throw new FormatException("The format of the name is incorrect.");
386-
string tokenId = name[^(fragments[^2].Length + fragments[^1].Length + 1)..];
387-
ByteString tokenKey = GetKey(tokenId);
388-
NameState token = (NameState)StdLib.Deserialize(nameMap[tokenKey]);
389-
token.EnsureNotExpired();
371+
string tokenId = tokenIDFromName(name);
372+
NameState token = getNameState(nameMap, tokenId);
390373
token.CheckAdmin();
374+
ByteString tokenKey = GetKey(tokenId);
391375
byte[] recordKey = GetRecordKey(tokenKey, name, type);
392376
recordMap.Delete(recordKey);
393377
}
@@ -422,14 +406,11 @@ private static string Resolve(string name, RecordType type, int redirect)
422406
StorageContext context = Storage.CurrentContext;
423407
StorageMap nameMap = new(context, Prefix_Name);
424408
StorageMap recordMap = new(context, Prefix_Record);
425-
string[] fragments = SplitAndCheck(name, true);
426-
if (fragments is null) throw new FormatException("The format of the name is incorrect.");
427-
string tokenId = name[^(fragments[^2].Length + fragments[^1].Length + 1)..];
409+
string tokenId = tokenIDFromName(name);
410+
getNameState(nameMap, tokenId); // ensure not expired
428411
ByteString tokenKey = GetKey(tokenId);
429-
NameState token = (NameState)StdLib.Deserialize(nameMap[tokenKey]);
430-
token.EnsureNotExpired();
431-
byte[] recordKey = Helper.Concat((byte[])tokenKey, GetKey(name));
432-
return (Iterator<(ByteString, RecordState)>)recordMap.Find(recordKey, FindOptions.DeserializeValues);
412+
byte[] recordsKey = GetRecordsKey(tokenKey, name);
413+
return (Iterator<(ByteString, RecordState)>)recordMap.Find(recordsKey, FindOptions.DeserializeValues);
433414
}
434415

435416
[DisplayName("_deploy")]
@@ -463,10 +444,15 @@ private static ByteString GetKey(string tokenId)
463444

464445
private static byte[] GetRecordKey(ByteString tokenKey, string name, RecordType type)
465446
{
466-
byte[] key = Helper.Concat((byte[])tokenKey, GetKey(name));
447+
byte[] key = GetRecordsKey(tokenKey, name);
467448
return Helper.Concat(key, ((byte)type).ToByteArray());
468449
}
469450

451+
private static byte[] GetRecordsKey(ByteString tokenKey, string name)
452+
{
453+
return Helper.Concat((byte[])tokenKey, GetKey(name));
454+
}
455+
470456
private static void PostTransfer(UInt160 from, UInt160 to, ByteString tokenId, object data)
471457
{
472458
OnTransfer(from, to, 1, tokenId);
@@ -619,5 +605,53 @@ private static bool CheckIPv6(string ipv6)
619605
}
620606
return true;
621607
}
608+
609+
/// <summary>
610+
/// Checks provided name for validness and returns corresponding token ID.
611+
/// </summary>
612+
private static string tokenIDFromName(string name)
613+
{
614+
string[] fragments = SplitAndCheck(name, true);
615+
if (fragments is null) throw new FormatException("The format of the name is incorrect.");
616+
if (fragments.Length == 1) return name;
617+
return name[^(fragments[^2].Length + fragments[^1].Length + 1)..];
618+
}
619+
620+
/// <summary>
621+
/// Retrieves NameState from storage and checks that it's not expired as far as the parent domain.
622+
/// </summary>
623+
private static NameState getNameState(StorageMap nameMap, string tokenId)
624+
{
625+
ByteString tokenBytes = nameMap[GetKey(tokenId)];
626+
if (tokenBytes is null) throw new InvalidOperationException("Unknown token.");
627+
NameState token = (NameState)StdLib.Deserialize(tokenBytes);
628+
token.EnsureNotExpired();
629+
string[] fragments = StdLib.StringSplit(tokenId, ".");
630+
if (parentExpired(nameMap, 1, fragments)) throw new InvalidOperationException("Parent domain has expired.");
631+
return token;
632+
}
633+
634+
/// <summary>
635+
/// Returns true if any domain from fragments doesn't exist or expired.
636+
/// </summary>
637+
/// <param name="nameMap">Registered domain names storage map.</param>
638+
/// <param name="first">The deepest subdomain to check.</param>
639+
/// <param name="fragments">The array of domain name fragments.</param>
640+
/// <returns>Whether any domain fragment doesn't exist or expired.</returns>
641+
private static bool parentExpired(StorageMap nameMap, int first, string[] fragments)
642+
{
643+
int last = fragments.Length - 1;
644+
string name = fragments[last];
645+
for (int i = last; i >= first; i--) {
646+
if (i != last) {
647+
name = fragments[i] + "." + name;
648+
}
649+
ByteString buffer = nameMap[GetKey(name)];
650+
if (buffer is null) return true;
651+
NameState token = (NameState)StdLib.Deserialize(buffer);
652+
if (Runtime.Time >= token.Expiration) return true;
653+
}
654+
return false;
655+
}
622656
}
623657
}

0 commit comments

Comments
 (0)