Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 95 additions & 57 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 @@ -142,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()
{
Expand Down Expand Up @@ -190,13 +177,13 @@ 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;
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 @@ -208,7 +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.");
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)
Expand Down Expand Up @@ -278,12 +276,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 @@ -293,12 +290,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 @@ -308,8 +304,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 @@ -327,11 +322,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 @@ -347,12 +340,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 @@ -365,24 +355,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 @@ -417,14 +405,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")]
Expand Down Expand Up @@ -458,10 +443,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 @@ -514,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))
Expand Down Expand Up @@ -615,5 +605,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;
}
}
}