Skip to content

Commit

Permalink
NameService: check domain expiration for read functions
Browse files Browse the repository at this point in the history
And refactor GetRecords/GetAllRecords along the way, move commonly
used code to a separate function.

Originally introduced in
nspcc-dev/neofs-contract@432c02a.
  • Loading branch information
AnnaShaleva committed Sep 9, 2022
1 parent cc580f9 commit 20d34ea
Showing 1 changed file with 80 additions and 45 deletions.
125 changes: 80 additions & 45 deletions src/NameService/NameService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,17 +52,15 @@ 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;
}

[Safe]
public static Map<string, object> 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<string, object> map = new();
map["name"] = token.Name;
map["expiration"] = token.Expiration;
Expand Down Expand Up @@ -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)
Expand All @@ -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);
Expand Down Expand Up @@ -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;
}
Expand All @@ -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);
}
Expand All @@ -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:
Expand All @@ -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
{
Expand All @@ -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;
Expand All @@ -370,24 +356,22 @@ public static Iterator<RecordState> 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<RecordState>)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<RecordState>)recordMap.Find(recordsKey, FindOptions.ValuesOnly | FindOptions.DeserializeValues);
}

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);
}
Expand Down Expand Up @@ -423,13 +407,11 @@ private static string Resolve(string name, RecordType type, int redirect)
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")]
Expand Down Expand Up @@ -463,10 +445,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);
Expand Down Expand Up @@ -619,5 +606,53 @@ private static bool CheckIPv6(string ipv6)
}
return true;
}

/// <summary>
/// Checks provided name for validness and returns corresponding token ID.
/// </summary>
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)..];
}

/// <summary>
/// Retrieves NameState from storage and checks that it's not expired as far as the parent domain.
/// </summary>
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;
}

/// <summary>
/// Returns true if any domain from fragments doesn't exist or expired.
/// </summary>
/// <param name="nameMap">Registered domain names storage map.</param>
/// <param name="first">The deepest subdomain to check.</param>
/// <param name="fragments">The array of domain name fragments.</param>
/// <returns>Whether any domain fragment doesn't exist or expired.</returns>
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;
}
}
}

0 comments on commit 20d34ea

Please sign in to comment.