aboutsummaryrefslogtreecommitdiffstats
path: root/common/resolver/resolver.go
blob: 9016547e10cec644e11199f765c753ff8ffeaaf1 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
package resolver

import (
    "encoding/binary"
    "fmt"

    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/crypto"
    "github.com/ethereum/go-ethereum/logger"
    "github.com/ethereum/go-ethereum/logger/glog"
)

/*
Resolver implements the Ethereum DNS mapping
HashReg : Key Hash (hash of domain name or contract code) -> Content Hash
UrlHint : Content Hash -> Url Hint

The resolver is meant to be called by the roundtripper transport implementation
of a url scheme
*/

// // contract addresses will be hardcoded after they're created
var UrlHintContractAddress, HashRegContractAddress string

const (
    txValue    = "0"
    txGas      = "100000"
    txGasPrice = "1000000000000"
)

func abi(s string) string {
    return common.ToHex(crypto.Sha3([]byte(s))[:4])
}

var (
    registerContentHashAbi = abi("register(uint256,uint256)")
    registerUrlAbi         = abi("register(uint256,uint8,uint256)")
    setOwnerAbi            = abi("setowner()")
)

type Backend interface {
    StorageAt(string, string) string
    Transact(fromStr, toStr, nonceStr, valueStr, gasStr, gasPriceStr, codeStr string) (string, error)
}

type Resolver struct {
    backend Backend
}

func New(eth Backend) *Resolver {
    return &Resolver{eth}
}

// for testing and play temporarily
// ideally the HashReg and UrlHint contracts should be in the genesis block
// if we got build-in support for natspec/contract info
// there should be only one of these officially endorsed
// addresses as constants
// TODO: could get around this with namereg, check
func (self *Resolver) CreateContracts(addr common.Address) (err error) {
    HashRegContractAddress, err = self.backend.Transact(addr.Hex(), "", "", txValue, txGas, txGasPrice, ContractCodeHashReg)
    if err != nil {
        return
    }
    UrlHintContractAddress, err = self.backend.Transact(addr.Hex(), "", "", txValue, txGas, txGasPrice, ContractCodeURLhint)
    glog.V(logger.Detail).Infof("HashReg @ %v\nUrlHint @ %v\n", HashRegContractAddress, UrlHintContractAddress)
    return
}

// called as first step in the registration process on HashReg
func (self *Resolver) SetOwner(address common.Address) (txh string, err error) {
    return self.backend.Transact(
        address.Hex(),
        HashRegContractAddress,
        "", txValue, txGas, txGasPrice,
        setOwnerAbi,
    )
}

// registers some content hash to a key/code hash
// e.g., the contract Info combined Json Doc's ContentHash
// to CodeHash of a contract or hash of a domain
// kept
func (self *Resolver) RegisterContentHash(address common.Address, codehash, dochash common.Hash) (txh string, err error) {
    _, err = self.SetOwner(address)
    if err != nil {
        return
    }
    codehex := common.Bytes2Hex(codehash[:])
    dochex := common.Bytes2Hex(dochash[:])

    data := registerContentHashAbi + codehex + dochex
    return self.backend.Transact(
        address.Hex(),
        HashRegContractAddress,
        "", txValue, txGas, txGasPrice,
        data,
    )
}

// registers a url to a content hash so that the content can be fetched
// address is used as sender for the transaction and will be the owner of a new
// registry entry on first time use
// FIXME: silently doing nothing if sender is not the owner
// note that with content addressed storage, this step is no longer necessary
// it could be purely
func (self *Resolver) RegisterUrl(address common.Address, hash common.Hash, url string) (txh string, err error) {
    hashHex := common.Bytes2Hex(hash[:])
    var urlHex string
    urlb := []byte(url)
    var cnt byte
    n := len(urlb)

    for n > 0 {
        if n > 32 {
            n = 32
        }
        urlHex = common.Bytes2Hex(urlb[:n])
        urlb = urlb[n:]
        n = len(urlb)
        bcnt := make([]byte, 32)
        bcnt[31] = cnt
        data := registerUrlAbi +
            hashHex +
            common.Bytes2Hex(bcnt) +
            common.Bytes2Hex(common.Hex2BytesFixed(urlHex, 32))
        txh, err = self.backend.Transact(
            address.Hex(),
            UrlHintContractAddress,
            "", txValue, txGas, txGasPrice,
            data,
        )
        if err != nil {
            return
        }
        cnt++
    }
    return
}

func (self *Resolver) Register(address common.Address, codehash, dochash common.Hash, url string) (txh string, err error) {

    _, err = self.RegisterContentHash(address, codehash, dochash)
    if err != nil {
        return
    }
    return self.RegisterUrl(address, dochash, url)
}

// resolution is costless non-transactional
// implemented as direct retrieval from  db
func (self *Resolver) KeyToContentHash(khash common.Hash) (chash common.Hash, err error) {
    // look up in hashReg
    at := common.Bytes2Hex(common.FromHex(HashRegContractAddress))
    key := storageAddress(storageMapping(storageIdx2Addr(1), khash[:]))
    hash := self.backend.StorageAt(at, key)

    if hash == "0x0" || len(hash) < 3 {
        err = fmt.Errorf("content hash not found for '%v'", khash.Hex())
        return
    }
    copy(chash[:], common.Hex2BytesFixed(hash[2:], 32))
    return
}

// retrieves the url-hint for the content hash -
// if we use content addressed storage, this step is no longer necessary
func (self *Resolver) ContentHashToUrl(chash common.Hash) (uri string, err error) {
    // look up in URL reg
    var str string = " "
    var idx uint32
    for len(str) > 0 {
        mapaddr := storageMapping(storageIdx2Addr(1), chash[:])
        key := storageAddress(storageFixedArray(mapaddr, storageIdx2Addr(idx)))
        hex := self.backend.StorageAt(UrlHintContractAddress, key)
        str = string(common.Hex2Bytes(hex[2:]))
        l := len(str)
        for (l > 0) && (str[l-1] == 0) {
            l--
        }
        str = str[:l]
        uri = uri + str
        idx++
    }

    if len(uri) == 0 {
        err = fmt.Errorf("GetURLhint: URL hint not found for '%v'", chash.Hex())
    }
    return
}

func (self *Resolver) KeyToUrl(key common.Hash) (uri string, hash common.Hash, err error) {
    // look up in urlHint
    hash, err = self.KeyToContentHash(key)
    if err != nil {
        return
    }
    uri, err = self.ContentHashToUrl(hash)
    return
}

func storageIdx2Addr(varidx uint32) []byte {
    data := make([]byte, 32)
    binary.BigEndian.PutUint32(data[28:32], varidx)
    return data
}

func storageMapping(addr, key []byte) []byte {
    data := make([]byte, 64)
    copy(data[0:32], key[0:32])
    copy(data[32:64], addr[0:32])
    sha := crypto.Sha3(data)
    return sha
}

func storageFixedArray(addr, idx []byte) []byte {
    var carry byte
    for i := 31; i >= 0; i-- {
        var b byte = addr[i] + idx[i] + carry
        if b < addr[i] {
            carry = 1
        } else {
            carry = 0
        }
        addr[i] = b
    }
    return addr
}

func storageAddress(addr []byte) string {
    return common.ToHex(addr)
}