(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
- When someone sends MsgStartAuction , the auction created.
- 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.
- 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.
- 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
- The user sends MsgStartAuction
- Module takes the MsgStartAuction and run ValidateMsgStartAuction() function
- If ValidateMsgStartAuction() return True, BeginAuction() add new value to the ActiveAcution
Bid-Period
- During the Bid period, users send MsgBid
- Module takes MsgBid and run ValidateMsgBid() function
- If ValidateMsgBid() return True, WriteBid() function add valid bid to the AciveAuction
Reveal-Period
- During the Reveal period, users send MsgRevealBid
- Module takes MsgRevealBid and run ValidateMsgRevealBid() function.
After Auction
-
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.
-
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
-
After the SlashLazyBidder( ) function ends, the EndAuction() function automatically operated.
-
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)
-
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.
- ActiveAuction
- ActiveNames
ActiveAuction field represents list of currently active auctions. There is five data in the each names.
- Period
- Bids
- total_deposits
- RevealBids
- Current winner
ActiveName field represents list of Active Name. There is five data in the each names.
- owner
- resolver
- deposit
- starttimestamp
- endtimestamp
Functions
func ValidateMsgStartAuction(){ }
Function that check MsgStartAuction is valid or not
-
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.
- If ValidateMsgStartAuction() returns True, BeginAuction() automatically operates
- Add new value to the ActiveAuction
func ValidateMsgBid(){ }
Function that check MsgBid is valid or not
- Check whether the auction is exist at the ActiveAuction or not.
- Check whether the auctionâs period is Bid period or not
- Check whether the deposit is same or bigger than 1 Luna
- Return True when MsgBid is valid
func WriteBid(){ }
Function that add valid MsgBid to the ActiveAuctionâs Bid section
- If ValidateBid() returns True, add MsgBid to the ActiveAuctionâs Bids section
func ValidateMsgRevealBid(){ }
Function that check MsgRevealBid is valid or not
- Check whether the name auction of MsgRevealBid is available in ActiveAuction
- If it exists, check whether the name auction is on the Reveal period
- Check if there is Msgbid that have the same hash with MsgRevealBid is in the ActiveAuctionâs bids section
- If it exists, check whether both Msgs are from same account
- Check whether deposit is same or bigger than bid amount
- Check whether MsgRevealBid use same salt value with the MsgBid
- Check whether MsgRevealBid use same bid amount
- Return True if MsgRevealBid is valid
func WriteRevealBid(){ }
Function that add valid MsgRevealBid to the ActiveAuctionâs Bid section
- If ValidateMsgRevealBid() returns True, add MsgRevealBid to the ActiveAuctionâs RevealBids section
func Tally(){ }
Tally() function operates right after the end of reveal period
- Set winner of auction using ActiveAuctionâs RevealBids section
- 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
- Right after the end of reveal period, SlashLazyBidder() function operates
- 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
- Operates right after the SlashLazyBidder() function ends
- 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.
- If winner successfully got the ownership of the name, EndAuction() function refund portion of deposit to the winner (refund amount = deposit - bid amount)
- 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
