(TIP) Terra Name Service - SPEC

(TIP)TNS - Spec

The TNS spec mostly based on EIP-137, Ethereum Domain Name Service - Specification.

Abstract

This draft TIP describes the details of the Terra Name Service(TNS), a proposed protocol and ABI definition that provides flexible resolution of short, human-readable names to service and resource identifiers. This permits users and developers to refer to human-readable and easy to remember names, and permits those names to be updated as necessary when the underlying resource (content-address) changes. In addition, the hierarchical structure of TNS allows for network contribution assessment.

The goal of domain names is to provide stable, human-readable identifiers that can be used to specify network resources. In this way, users can enter a memorable string, such as “dokwon.wallet” or “terra.money”, and be directed to the appropriate account address. The mapping between names and resources may change over time, so a user may change wallets, a website may change hosts, or a swarm document may be updated to a new version, without the domain name changing. Moreover, the structure of the subdomains allows us to identify accounts owned by a particular organization, which can be used as a measure to automatically calculate the network contribution of that organization.

Motivation

Purpose

  • Provide human readable names for Terra end-user
  • Track proof-of-contribution

Features

  • Support for subnames/sub-domains
  • Single service under a single name
  • Support only for account address record type

TNS, in particular, is designed only for two main purposes so it will not support legacy DNS system.

This document does specify how domains should be resolved or stored, or how systems can find the owner responsible for a given domain.

Specification

Overview

The TNS system comprises three main features:

  • Registration of TNS registry for second level domains (*.terra)
  • Resolve
  • Reverse Resolve

The registry is a data structure that keeps information of any registered second level name like the owner address, expiry dates, and # of subdomains. This information can be used to check permission of the owner to create/delete subdomains, and to set the owner address.

TNS has to provide resource lookups and modification for a name - for instance, returning an address as appropriate or creating/deleting subdomain. The resource management specification, defined here and extended in other TIPs, defines what methods TNS may implement to support various features like PoC or domain expiration.

TNS also is responsible for allocating domain names to users of the system, and updating the TNS registry. TNS may host and close the auction and track the expire date of each names.

Resolving a name in TNS is a two-step process. First, lookup the TNS registry with the name to solve and check the current name state(active|inactive|grace). If the record exists, lookup the resource to get the mapped address.

For example, suppose you wish to find the address of foundation wallet associated with ‘foundation.terra’. First, the querier try to lookup the registry of the name:

names := strings.split("foundation.terra")
secondLevelName := names[len(names)-2:len(names)-1]
registry, err := keeper.GetRegistry(secondLevelName);
if err != nil {
return nil, err
}

if registry.state == "grace" {
return nil, err("grace period")
}

Then, lookup the address of the name with prefix

address, err := keeper.GetAddress(registry.prefix, "foundation.terra");

TNS has to provide reserve resolve feature to support PoC(Proof-of-Contribution) of Dapps. Reverse resolving returns second level hash to imply the organization the address belongs to.

Name Syntax

TNS names must conform to the following syntax:

<domain> ::= <label> | <domain> "." <label
<label> ::= any valid string label per [UTS46](_https://unicode.org/reports/tr46/_ (https://unicode.org/reports/tr46/))

In short, names consist of a series of dot-separated labels. Each label must be a valid normalised label as described in UTS46 with options transitional=false and useSTD3AsciiRules=true. For Javascript implementations, a library is available that normalises and checks names.

Note that while upper and lower case letters are allowed in names, the UTS46 normalisation process case-folds labels to lowercase, so two names with different case but identical spelling will produce the same result.

Labels and domains may be of any length, but for compatibility with legacy DNS, it is recommended that labels be restricted to no more than 64 characters each, and complete TNS names to no more than 255 characters. For the same reason, it is recommended that labels do not start or end with hyphens, or start with digits.

Types

type Registry {
Name string
Owner sdk.AccAddress
ExpireDate time.Duration
}

Keys

0x01<sha256(second_level_name)_bytes>: Registry
0x02<sha256(second_level_name)_bytes><sha256(name)_bytes>: sdk.AccAddress
0x03<address_bytes>: sha256(second_level_name) (reverse resolve name hash with address)
0x04<time_bytes>: []sha256(second_level_name) (keep this for expire tracking)

The keeper stores each address mapping with second level name hash prefix to give dependency on iteration. This structure helps to access all subdomain of a specific second level domain.

Registry Specification

The TNS registry keeper exposes the following functions:

func (keeper Keeper) GetRegistry(second_level_name string) (Registry, err)
func (keeper Keeper) UpdateOwner(second_level_name string, owner sdk.AccAddress) err
func (keeper Keeper) SetRegistry(prefix []byte, name string, owner sdk.AccAddress, expireDate time.Duration, numSub int64) err
func (keeper Keeper) Resolve(name string) (sdk.AccAddress, err)
func (keeper Keeper) ReverseResolve(addr sdk.AccAddress) (sha256(second_level_name), err)

Auction Specification

The TNS will use a Vickrey auction for name allocation.

Abstract

TNS.png

  1. When someone sends MsgStartAuction , the auction created.
  2. During the Bid period, people send a deposit and make a concealed bid by hashing bid amount and salt value. This is done by sending MsgBid transaction. Bid period ends 72hours after the MsgStartAuction.
  3. In the Reveal period, people reveal concealed bid by revealing the real value including bid amount, salt value. This is done by sending MsgRevealBid transaction.
  4. After the Reveal period ends, the module automatically calculates the winner of the auction using MsgBid and MsgRevealBid . Winner of auction takes the Name, and the second biggest bid automatically subtracts from the winner’s deposit. The winner takes the leftover deposit and the ownership of the Name. A Person who fails to reveal their bid amount will be get slashed.

Auction Process

Pre-Auction

  1. The user sends MsgStartAuction
  2. Module takes the MsgStartAuction and run ValidateMsgStartAuction() function
  3. If ValidateMsgStartAuction() return True, BeginAuction() add new value to the ActiveAcution

Bid-Period

  1. During the Bid period, users send MsgBid
  2. Module takes MsgBid and run ValidateMsgBid() function
  3. If ValidateMsgBid() return True, WriteBid() function add valid bid to the AciveAuction

Reveal-Period

  1. During the Reveal period, users send MsgRevealBid
  2. Module takes MsgRevealBid and run ValidateMsgRevealBid() function.

After Auction

  1. Right after the revealing period, Tally() function operates, Tally() function only use valid MsgRevealBid to set winner. Users who did not win the auction and send valid MsgRevealBid get refund.

  2. After the Tally() function, SlashLazyBidder( ) function automatically operates, and It slashes the deposit of user who didn’t get a refund and also didn’t win the auction

  3. After the SlashLazyBidder( ) function ends, the EndAuction() function automatically operated.

  4. If there is a winner, EndAuction() function delete Auction from the end Auction, and add Name to the ActiveNames . Winner gets the refund (refund amount = deposit - bid amount)

  5. If no winner, EndAuction() function delete Name auction from the ActiveAuction .

Message types

type MsgStartAuction struct {
Name
}

type MsgBid struct {
Name
Hash # including bid amount and salt and name
Deposit_amount # denom = uluna
}

type MsgRevealBid struct{
Name
BidAmount
Salt
}

State

#AuctionName : [[period], [Bids], total_deposits, [RevealBids], Current winner]
ActiveAuctions{
"Google" : [
[RevealPeriod, timestamp, timestamp, timestamp],
[["Google", 8C8FFF5...., 100000000], ...],
234950000,
[["Google", 8C8FFF5...., 1000000, 1234],...]
"terra1dp0taj85ruc299rkdvzp4z5pfg6z6swaed74e6"
]
}

#Name : ["owner", "resolver", deposit, starttimestamp, endtimestamp]
ActiveNames{
"Terraformlabs" : [
"terra1dp0taj85ruc299rkdvzp4z5pfg6z6swaed74e6",
"terra1dp0taj85ruc299rkdvzp4z5pfg6z6swaed74e6",
100000000000,
1583055924,
1614591981
]
}

State can changed by the functions.There’s two types of State.

  1. ActiveAuction
  2. ActiveNames

ActiveAuction field represents list of currently active auctions. There is five data in the each names.

  1. Period
  2. Bids
  3. total_deposits
  4. RevealBids
  5. Current winner

ActiveName field represents list of Active Name. There is five data in the each names.

  1. owner
  2. resolver
  3. deposit
  4. starttimestamp
  5. endtimestamp

Functions

func ValidateMsgStartAuction(){

}

Function that check MsgStartAuction is valid or not

  1. Check If the Name is on ActiveAuction or ActiveNames

    a. If the Name is not in both state, return True
    b. Else, return False

func BeginAuction(){

}

Function that takes valid MsgStartAuction and create the name auction.

  1. If ValidateMsgStartAuction() returns True, BeginAuction() automatically operates
  2. Add new value to the ActiveAuction
func ValidateMsgBid(){

}

Function that check MsgBid is valid or not

  1. Check whether the auction is exist at the ActiveAuction or not.
  2. Check whether the auction’s period is Bid period or not
  3. Check whether the deposit is same or bigger than 1 Luna
  4. Return True when MsgBid is valid
func WriteBid(){

}

Function that add valid MsgBid to the ActiveAuction’s Bid section

  1. If ValidateBid() returns True, add MsgBid to the ActiveAuction’s Bids section
func ValidateMsgRevealBid(){

}

Function that check MsgRevealBid is valid or not

  1. Check whether the name auction of MsgRevealBid is available in ActiveAuction
  2. If it exists, check whether the name auction is on the Reveal period
  3. Check if there is Msgbid that have the same hash with MsgRevealBid is in the ActiveAuction’s bids section
  4. If it exists, check whether both Msgs are from same account
  5. Check whether deposit is same or bigger than bid amount
  6. Check whether MsgRevealBid use same salt value with the MsgBid
  7. Check whether MsgRevealBid use same bid amount
  8. Return True if MsgRevealBid is valid
func WriteRevealBid(){

}

Function that add valid MsgRevealBid to the ActiveAuction’s Bid section

  1. If ValidateMsgRevealBid() returns True, add MsgRevealBid to the ActiveAuction’s RevealBids section
func Tally(){

}

Tally() function operates right after the end of reveal period

  1. Set winner of auction using ActiveAuction’s RevealBids section
  2. Refund deposit to user who is not a winner but send valid MsgRevealBid to the module
func SlashLazyBidder(){

}

SlashLazyBidder() function Slash deposit of lazy bidders right after the reveal period ends

  1. Right after the end of reveal period, SlashLazyBidder() function operates
  2. SlashLazyBidder() function slashes deposit of user who is not a winner but didn’t get refund by the Tally() function
func EndAuction(){

}

Transfer name ownership to the auction winner, or if there is no winner, delete data of this name auction from the ActiveAuction

  1. Operates right after the SlashLazyBidder() function ends
  2. If there is winner, delete that name auction form ActiveAcution, transfer name ownership to winner by adding new name and owner’s address in the ActiveName.
  3. If winner successfully got the ownership of the name, EndAuction() function refund portion of deposit to the winner (refund amount = deposit - bid amount)
  4. If there is no winner, delete that name auction from the ActiveAuction

Refund policy

Reason for Refund Refund Recipient Refund Percentage
valid non-winning bid bidder 99.50%
bid is not revealed until the auction ends bidder 0.50%
expiration owner 99.50%

Renewal & Expiration

Expiration

#Name : ["owner", "resolver", deposit, **starttimestamp, endtimestamp** ]
ActiveNames{
"Terraformlabs" : [
"terra1dp0taj85ruc299rkdvzp4z5pfg6z6swaed74e6",
"terra1dp0taj85ruc299rkdvzp4z5pfg6z6swaed74e6",
100000000000,
<b>1583055924,
1614591981</b>
]
}

Expiration is recorded at the endtimestamp. When Auction ends, the EndAuction() function automatically records the start/endtimestamp of the name. The starttimestamp is defined by the time when the auction ends and the endtimestamp is defined by the starttimestamp + X month(TBD).

When the current block time is same or bigger than the endtimestamp, the name expired.

Renewal

type MsgNameRenewal struct {
Name
Denom
Amount
}

Renewal is done by MsgNameRenewal. Each name is distinguished by the number of characters in name.

TNS
Yearly payment 5 characters or longer 5SDT worth of Luna/Terra burn/distribute/communitypool
4 characters 100SDT worth of Luna/Terra burn/distribute/communitypool
3 characters 400SDT worth of Luna/Terra burn/distribute/communitypool

When someone makes MsgNameRenewal Tx, the TNS module automatically calculates the current value of the MsgNameRenewal into the SDT. Then the TNS module increase endtimestamp value by ( the SDT value of MsgNameRenewal Tx)/( Yearly payment standard) * (31556926).

Open Questions

  • How do we determine X → for the endtimestamp?
  • Should we accept only one denom for Renewal?
  • Should we charge swap fee for the MsgNameRenewalTx?
  • How do we distribute the renewal fee?

References


3 Likes
  • How do we determine X → for the endtimestamp?

Why don’t we let the user pick it? 1, 2, 3, 5, 10 years, maybe with a rebate for longer durations?

  • Should we accept only one denom for Renewal?

Imo yes, Luna.

  • Should we charge swap fee for the MsgNameRenewalTx?

Would there be a swap involved of some kind? I don’t see how it relates otherwise

  • How do we distribute the renewal fee?

Burn. The community pool is already growing at an okay speed. What we need is a mechanism to take more Luna out of circulation imo.

How did you arrive at these prices? (5, 100, 400 SDT?)

Isn’t “the mapping between names and resources may change over time” a security issue for wallet mapped addresses??

Why don’t we let the user pick it? 1, 2, 3, 5, 10 years, maybe with a rebate for longer durations?

I think X should be fixed because we have renewal, If user wants extra 10 year ownership, they can just send MsgRenewal with 10x of standard annual renewal fee.

Would there be a swap involved of some kind? I don’t see how it relates otherwise

As the current standard Denom for renewal is SDT, we need to convert Luna or other Terra currencies to the SDT to calculate renewal period.

How did you arrive at these prices? (5, 100, 400 SDT?)

I get it from the current renewal fee of ENS.

I don’t think so, any rationale for that? Are you talking about security issue from the human mistakes or protocol?

Solely human bad actors.

A name expires and is taken over by a bad actor.
The name is hardcoded in some clients/sites and is temporarily redirected to a different wallet.
Etc

@gaia I think that it should be the responsibility of the application layer to make sure that domain names point to the correct address. This is how DNS works. It’s straightforward for an application to detect if a domain’s address has changed, in which case it should warn the user and ask for additional confirmation.

A static mapping would have serious problems – what happens if you decide to change your address because you lost your private key? Do you need to buy a new domain? What happens if people still send money to your old domain?

1 Like