[#125] Refactoring

Signed-off-by: Denis Kirillov <denis@nspcc.ru>
This commit is contained in:
Denis Kirillov 2021-09-28 15:40:14 +03:00 committed by Alex Vanin
parent 2a4fc45c25
commit 4a805dba43
2 changed files with 139 additions and 56 deletions

View file

@ -62,8 +62,8 @@ const (
const ( const (
// defaultRegisterPrice is the default price for new domain registration. // defaultRegisterPrice is the default price for new domain registration.
defaultRegisterPrice = 10_0000_0000 defaultRegisterPrice = 10_0000_0000
// millisecondsInSecond is amount of milliseconds per second. // millisecondsInYear is amount of milliseconds per year.
millisecondsInSecond = 1000 millisecondsInYear = 365 * 24 * 3600 * 1000
) )
// RecordState is a type that registered entities are saved to. // RecordState is a type that registered entities are saved to.
@ -71,6 +71,7 @@ type RecordState struct {
Name string Name string
Type RecordType Type RecordType
Data string Data string
ID byte
} }
// Update updates NameService contract. // Update updates NameService contract.
@ -277,7 +278,7 @@ func Register(name string, owner interop.Hash160, email string, refresh, retry,
ns := NameState{ ns := NameState{
Owner: owner, Owner: owner,
Name: name, Name: name,
Expiration: runtime.GetTime() + expire*millisecondsInSecond, Expiration: runtime.GetTime() + millisecondsInYear,
} }
putNameStateWithKey(ctx, tokenKey, ns) putNameStateWithKey(ctx, tokenKey, ns)
putSoaRecord(ctx, name, email, refresh, retry, expire, ttl) putSoaRecord(ctx, name, email, refresh, retry, expire, ttl)
@ -287,18 +288,29 @@ func Register(name string, owner interop.Hash160, email string, refresh, retry,
} }
// Renew increases domain expiration date. // Renew increases domain expiration date.
func Renew(name string, expire int) int { func Renew(name string) int {
if len(name) > maxDomainNameLength { if len(name) > maxDomainNameLength {
panic("invalid domain name format") panic("invalid domain name format")
} }
runtime.BurnGas(GetPrice()) runtime.BurnGas(GetPrice())
ctx := storage.GetContext() ctx := storage.GetContext()
ns := getNameState(ctx, []byte(name)) ns := getNameState(ctx, []byte(name))
ns.Expiration += expire * millisecondsInSecond ns.Expiration += millisecondsInYear
putNameState(ctx, ns) putNameState(ctx, ns)
return ns.Expiration return ns.Expiration
} }
// UpdateSOA update soa record.
func UpdateSOA(name, email string, refresh, retry, expire, ttl int) {
if len(name) > maxDomainNameLength {
panic("invalid domain name format")
}
ctx := storage.GetContext()
ns := getNameState(ctx, []byte(name))
ns.checkAdmin()
putSoaRecord(ctx, name, email, refresh, retry, expire, ttl)
}
// SetAdmin updates domain admin. // SetAdmin updates domain admin.
func SetAdmin(name string, admin interop.Hash160) { func SetAdmin(name string, admin interop.Hash160) {
if len(name) > maxDomainNameLength { if len(name) > maxDomainNameLength {
@ -317,28 +329,44 @@ func SetAdmin(name string, admin interop.Hash160) {
} }
// SetRecord adds new record of the specified type to the provided domain. // SetRecord adds new record of the specified type to the provided domain.
func SetRecord(name string, typ RecordType, data string) { func SetRecord(name string, typ RecordType, id byte, data string) {
tokenID := []byte(tokenIDFromName(name)) tokenID := []byte(tokenIDFromName(name))
var ok bool if !checkBaseRecords(typ, data) {
switch typ {
case A:
ok = checkIPv4(data)
case CNAME:
ok = splitAndCheck(data, true) != nil
case TXT:
ok = len(data) <= maxTXTRecordLength
case AAAA:
ok = checkIPv6(data)
default:
panic("unsupported record type")
}
if !ok {
panic("invalid record data") panic("invalid record data")
} }
ctx := storage.GetContext() ctx := storage.GetContext()
ns := getNameState(ctx, tokenID) ns := getNameState(ctx, tokenID)
ns.checkAdmin() ns.checkAdmin()
putRecord(ctx, tokenID, name, typ, data) putRecord(ctx, tokenID, name, typ, id, data)
updateSoaSerial(ctx, tokenID)
}
func checkBaseRecords(typ RecordType, data string) bool {
switch typ {
case A:
return checkIPv4(data)
case CNAME:
return splitAndCheck(data, true) != nil
case TXT:
return len(data) <= maxTXTRecordLength
case AAAA:
return checkIPv6(data)
default:
panic("unsupported record type")
}
}
// AddRecord adds new record of the specified type to the provided domain.
func AddRecord(name string, typ RecordType, data string) {
tokenID := []byte(tokenIDFromName(name))
if !checkBaseRecords(typ, data) {
panic("invalid record data")
}
ctx := storage.GetContext()
ns := getNameState(ctx, tokenID)
ns.checkAdmin()
addRecord(ctx, tokenID, name, typ, data)
updateSoaSerial(ctx, tokenID)
} }
// GetRecords 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
@ -347,21 +375,25 @@ func GetRecords(name string, typ RecordType) []string {
tokenID := []byte(tokenIDFromName(name)) tokenID := []byte(tokenIDFromName(name))
ctx := storage.GetReadOnlyContext() ctx := storage.GetReadOnlyContext()
_ = getNameState(ctx, tokenID) // ensure not expired _ = getNameState(ctx, tokenID) // ensure not expired
return getRecords(ctx, tokenID, name, typ) return getRecordsByType(ctx, tokenID, name, typ)
} }
// DeleteRecords removes domain record with the specified type. // DeleteRecords removes domain records with the specified type.
func DeleteRecords(name string, typ RecordType) { func DeleteRecords(name string, typ RecordType) {
if typ == SOA {
panic("you cannot delete soa record")
}
tokenID := []byte(tokenIDFromName(name)) tokenID := []byte(tokenIDFromName(name))
ctx := storage.GetContext() ctx := storage.GetContext()
ns := getNameState(ctx, tokenID) ns := getNameState(ctx, tokenID)
ns.checkAdmin() ns.checkAdmin()
recordsKey := getRecordKey(tokenID, name, typ) recordsKey := getRecordsKeyByType(tokenID, name, typ)
records := storage.Find(ctx, recordsKey, storage.KeysOnly) records := storage.Find(ctx, recordsKey, storage.KeysOnly)
for iterator.Next(records) { for iterator.Next(records) {
r := iterator.Value(records).(string) r := iterator.Value(records).(string)
storage.Delete(ctx, r) storage.Delete(ctx, r)
} }
updateSoaSerial(ctx, tokenID)
} }
// Resolve resolves given name (not more then three redirects are allowed). // Resolve resolves given name (not more then three redirects are allowed).
@ -460,33 +492,57 @@ func putNameStateWithKey(ctx storage.Context, tokenKey []byte, ns NameState) {
storage.Put(ctx, nameKey, nsBytes) storage.Put(ctx, nameKey, nsBytes)
} }
// getRecords returns domain record. // getRecordsByType returns domain record.
func getRecords(ctx storage.Context, tokenId []byte, name string, typ RecordType) []string { func getRecordsByType(ctx storage.Context, tokenId []byte, name string, typ RecordType) []string {
recordsKey := getRecordKey(tokenId, name, typ) recordsKey := getRecordsKeyByType(tokenId, name, typ)
var result []string var result []string
records := storage.Find(ctx, recordsKey, storage.DeserializeValues) records := storage.Find(ctx, recordsKey, storage.ValuesOnly|storage.DeserializeValues)
for iterator.Next(records) { for iterator.Next(records) {
r := iterator.Value(records).(struct { r := iterator.Value(records).(RecordState)
key string if r.Type == typ {
rs RecordState result = append(result, r.Data)
})
value := r.rs.Data
rTyp := r.key[len(r.key)-5]
if rTyp == byte(typ) {
result = append(result, value)
} }
} }
return result return result
} }
// putRecord stores domain record. // putRecord stores domain record.
func putRecord(ctx storage.Context, tokenId []byte, name string, typ RecordType, record string) { func putRecord(ctx storage.Context, tokenId []byte, name string, typ RecordType, id byte, data string) {
recordKey := addUniqueBytesToRecordKey(getRecordKey(tokenId, name, typ)) recordKey := getIdRecordKey(tokenId, name, typ, id)
recBytes := storage.Get(ctx, recordKey)
if recBytes == nil {
panic("invalid record id")
}
storeRecord(ctx, recordKey, name, typ, id, data)
}
// addRecord stores domain record.
func addRecord(ctx storage.Context, tokenId []byte, name string, typ RecordType, data string) {
recordsKey := getRecordsKeyByType(tokenId, name, typ)
var id byte
records := storage.Find(ctx, recordsKey, storage.KeysOnly)
for iterator.Next(records) {
id++
}
if typ == CNAME && id != 0 {
panic("you shouldn't have more than one CNAME record")
}
recordKey := append(recordsKey, id) // the same as getIdRecordKey
storeRecord(ctx, recordKey, name, typ, id, data)
}
// storeRecord put record to storage.
func storeRecord(ctx storage.Context, recordKey []byte, name string, typ RecordType, id byte, data string) {
rs := RecordState{ rs := RecordState{
Name: name, Name: name,
Type: typ, Type: typ,
Data: record, Data: data,
ID: id,
} }
recBytes := std.Serialize(rs) recBytes := std.Serialize(rs)
storage.Put(ctx, recordKey, recBytes) storage.Put(ctx, recordKey, recBytes)
@ -494,12 +550,13 @@ func putRecord(ctx storage.Context, tokenId []byte, name string, typ RecordType,
// putSoaRecord stores soa domain record. // putSoaRecord stores soa domain record.
func putSoaRecord(ctx storage.Context, name, email string, refresh, retry, expire, ttl int) { func putSoaRecord(ctx storage.Context, name, email string, refresh, retry, expire, ttl int) {
var id byte
tokenId := []byte(tokenIDFromName(name)) tokenId := []byte(tokenIDFromName(name))
recordKey := getRecordKey(tokenId, name, SOA) recordKey := getIdRecordKey(tokenId, name, SOA, id)
recordKey = append(recordKey, 0, 0, 0, 0) // emulate first 4 bytes of tx hash
rs := RecordState{ rs := RecordState{
Name: name, Name: name,
Type: SOA, Type: SOA,
ID: id,
Data: name + " " + email + " " + Data: name + " " + email + " " +
std.Itoa(runtime.GetTime(), 10) + " " + std.Itoa(runtime.GetTime(), 10) + " " +
std.Itoa(refresh, 10) + " " + std.Itoa(refresh, 10) + " " +
@ -511,16 +568,47 @@ func putSoaRecord(ctx storage.Context, name, email string, refresh, retry, expir
storage.Put(ctx, recordKey, recBytes) storage.Put(ctx, recordKey, recBytes)
} }
// updateSoaSerial stores soa domain record.
func updateSoaSerial(ctx storage.Context, tokenId []byte) {
var id byte
recordKey := getIdRecordKey(tokenId, string(tokenId), SOA, id)
recBytes := storage.Get(ctx, recordKey)
if recBytes == nil {
panic("not found soa record")
}
rec := std.Deserialize(recBytes.([]byte)).(RecordState)
split := std.StringSplitNonEmpty(rec.Data, " ")
if len(split) != 7 {
panic("invalid soa record")
}
split[2] = std.Itoa(runtime.GetTime(), 10) // update serial
rec.Data = split[0] + " " + split[1] + " " +
split[2] + " " + split[3] + " " +
split[4] + " " + split[5] + " " +
split[6]
recBytes = std.Serialize(rec)
storage.Put(ctx, recordKey, recBytes)
}
// getRecordsKey returns prefix used to store domain records of different types. // getRecordsKey returns prefix used to store domain records of different types.
func getRecordsKey(tokenId []byte, name string) []byte { func getRecordsKey(tokenId []byte, name string) []byte {
recordKey := append([]byte{prefixRecord}, getTokenKey(tokenId)...) recordKey := append([]byte{prefixRecord}, getTokenKey(tokenId)...)
return append(recordKey, getTokenKey([]byte(name))...) return append(recordKey, getTokenKey([]byte(name))...)
} }
// getRecordKey returns key used to store domain records. // getRecordsKeyByType returns key used to store domain records.
func getRecordKey(tokenId []byte, name string, typ RecordType) []byte { func getRecordsKeyByType(tokenId []byte, name string, typ RecordType) []byte {
recordKey := getRecordsKey(tokenId, name) recordKey := getRecordsKey(tokenId, name)
return append(recordKey, []byte{byte(typ)}...) return append(recordKey, byte(typ))
}
// getIdRecordKey returns key used to store domain records.
func getIdRecordKey(tokenId []byte, name string, typ RecordType, id byte) []byte {
recordKey := getRecordsKey(tokenId, name)
return append(recordKey, byte(typ), id)
} }
// addUniqueBytesToRecordKey add to key 4 unique bytes to allow store several records with the same type. // addUniqueBytesToRecordKey add to key 4 unique bytes to allow store several records with the same type.
@ -731,17 +819,12 @@ func resolve(ctx storage.Context, res []string, name string, typ RecordType, red
records := getAllRecords(ctx, name) records := getAllRecords(ctx, name)
cname := "" cname := ""
for iterator.Next(records) { for iterator.Next(records) {
r := iterator.Value(records).(struct { r := iterator.Value(records).(RecordState)
key string if r.Type == typ {
rs RecordState res = append(res, r.Data)
})
value := r.rs.Data
rTyp := r.key[len(r.key)-5]
if rTyp == byte(typ) {
res = append(res, value)
} }
if rTyp == byte(CNAME) { if r.Type == CNAME {
cname = value cname = r.Data
} }
} }
if cname == "" || typ == CNAME { if cname == "" || typ == CNAME {
@ -758,5 +841,5 @@ func getAllRecords(ctx storage.Context, name string) iterator.Iterator {
tokenID := []byte(tokenIDFromName(name)) tokenID := []byte(tokenIDFromName(name))
_ = getNameState(ctx, tokenID) _ = getNameState(ctx, tokenID)
recordsKey := getRecordsKey(tokenID, name) recordsKey := getRecordsKey(tokenID, name)
return storage.Find(ctx, recordsKey, storage.DeserializeValues) return storage.Find(ctx, recordsKey, storage.ValuesOnly|storage.DeserializeValues)
} }

View file

@ -9,7 +9,7 @@ const (
A RecordType = 1 A RecordType = 1
// CNAME represents canonical name record type. // CNAME represents canonical name record type.
CNAME RecordType = 5 CNAME RecordType = 5
// SOA represents start of a zone of authority record type. // SOA represents start of authority record type.
SOA RecordType = 6 SOA RecordType = 6
// TXT represents text record type. // TXT represents text record type.
TXT RecordType = 16 TXT RecordType = 16