From 9e538ed6f7a3e34918df813c75a6469132a4b10a Mon Sep 17 00:00:00 2001 From: zelig Date: Fri, 22 May 2015 04:30:51 +0100 Subject: [PATCH] Resolver: registries use global registrar * global registrar now in resolver, console jsre refers to resolver.abi/addr, geth/contracts.go removed * add Call to resolver backend interface * global registrar name registry interface in resolver * the hashReg and UrlHing contracts now initialised from global registry * if not registered one can call the CreateContracts which creates them, reserved and registers them * tests * newRegistry returns the contract addresses for HashReg and UrlHint --- cmd/geth/admin.go | 13 ++- cmd/geth/contracts.go | 6 - cmd/geth/js.go | 3 +- cmd/geth/js_test.go | 4 +- common/natspec/natspec_e2e_test.go | 5 +- common/resolver/contracts.go | 2 + common/resolver/resolver.go | 181 ++++++++++++++++++++++++++--- common/resolver/resolver_test.go | 30 +++-- 8 files changed, 201 insertions(+), 43 deletions(-) delete mode 100644 cmd/geth/contracts.go diff --git a/cmd/geth/admin.go b/cmd/geth/admin.go index f0be444c68..b35cc35399 100644 --- a/cmd/geth/admin.go +++ b/cmd/geth/admin.go @@ -815,22 +815,23 @@ func (js *jsre) newRegistry(call otto.FunctionCall) otto.Value { if len(call.ArgumentList) != 1 { fmt.Println("requires 1 argument: admin.contractInfo.newRegistry(adminaddress)") - return otto.FalseValue() + return otto.UndefinedValue() } addr, err := call.Argument(0).ToString() if err != nil { fmt.Println(err) - return otto.FalseValue() + return otto.UndefinedValue() } - + var hashReg, urlHint string registry := resolver.New(js.xeth) - err = registry.CreateContracts(common.HexToAddress(addr)) + hashReg, urlHint, err = registry.CreateContracts(common.HexToAddress(addr)) if err != nil { fmt.Println(err) - return otto.FalseValue() + return otto.UndefinedValue() } - return otto.TrueValue() + v, _ := call.Otto.ToValue([]string{hashReg, urlHint}) + return v } // internal transaction type which will allow us to resend transactions using `eth.resend` diff --git a/cmd/geth/contracts.go b/cmd/geth/contracts.go deleted file mode 100644 index 1f27838d15..0000000000 --- a/cmd/geth/contracts.go +++ /dev/null @@ -1,6 +0,0 @@ -package main - -var ( - globalRegistrar = `var GlobalRegistrar = web3.eth.contract([{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"name","outputs":[{"name":"o_name","type":"bytes32"}],"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"owner","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"content","outputs":[{"name":"","type":"bytes32"}],"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"addr","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserve","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"subRegistrar","outputs":[{"name":"o_subRegistrar","type":"address"}],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_newOwner","type":"address"}],"name":"transfer","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_registrar","type":"address"}],"name":"setSubRegistrar","outputs":[],"type":"function"},{"constant":false,"inputs":[],"name":"Registrar","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_a","type":"address"},{"name":"_primary","type":"bool"}],"name":"setAddress","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_content","type":"bytes32"}],"name":"setContent","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"disown","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"register","outputs":[{"name":"","type":"address"}],"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"}],"name":"Changed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"indexed":true,"name":"addr","type":"address"}],"name":"PrimaryChanged","type":"event"}]);` - globalRegistrarAddr = "0xc6d9d2cd449a754c494264e1809c50e34d64562b" -) diff --git a/cmd/geth/js.go b/cmd/geth/js.go index 706bc6554f..0c0ade6be2 100644 --- a/cmd/geth/js.go +++ b/cmd/geth/js.go @@ -30,6 +30,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/docserver" "github.com/ethereum/go-ethereum/common/natspec" + "github.com/ethereum/go-ethereum/common/resolver" "github.com/ethereum/go-ethereum/eth" re "github.com/ethereum/go-ethereum/jsre" "github.com/ethereum/go-ethereum/rpc" @@ -143,7 +144,7 @@ var net = web3.net; utils.Fatalf("Error setting namespaces: %v", err) } - js.re.Eval(globalRegistrar + "registrar = GlobalRegistrar.at(\"" + globalRegistrarAddr + "\");") + js.re.Eval(resolver.GlobalRegistrar + "registrar = GlobalRegistrar.at(\"" + resolver.GlobalRegistrarAddr + "\");") } var ds, _ = docserver.New("/") diff --git a/cmd/geth/js_test.go b/cmd/geth/js_test.go index dee25e44e6..49ff4b70c7 100644 --- a/cmd/geth/js_test.go +++ b/cmd/geth/js_test.go @@ -307,7 +307,7 @@ func TestContract(t *testing.T) { checkEvalJSON( t, repl, `contractaddress = eth.sendTransaction({from: primary, data: contract.code })`, - `"0x5dcaace5982778b409c524873b319667eba5d074"`, + `"0x291293d57e0a0ab47effe97c02577f90d9211567"`, ) callSetup := `abiDef = JSON.parse('[{"constant":false,"inputs":[{"name":"a","type":"uint256"}],"name":"multiply","outputs":[{"name":"d","type":"uint256"}],"type":"function"}]'); @@ -333,7 +333,7 @@ multiply7 = Multiply7.at(contractaddress); checkEvalJSON(t, repl, `admin.contractInfo.start()`, `true`) checkEvalJSON(t, repl, `multiply7.multiply.sendTransaction(6, { from: primary, gas: "1000000", gasPrice: "100000" })`, `undefined`) - expNotice = `About to submit transaction (no NatSpec info found for contract: content hash not found for '0x87e2802265838c7f14bb69eecd2112911af6767907a702eeaa445239fb20711b'): {"params":[{"to":"0x5dcaace5982778b409c524873b319667eba5d074","data": "0xc6888fa10000000000000000000000000000000000000000000000000000000000000006"}]}` + expNotice = `About to submit transaction (no NatSpec info found for contract: content hash not found for '0x87e2802265838c7f14bb69eecd2112911af6767907a702eeaa445239fb20711b'): {"params":[{"to":"0x291293d57e0a0ab47effe97c02577f90d9211567","data": "0xc6888fa10000000000000000000000000000000000000000000000000000000000000006"}]}` if repl.lastConfirm != expNotice { t.Errorf("incorrect confirmation message: expected %v, got %v", expNotice, repl.lastConfirm) } diff --git a/common/natspec/natspec_e2e_test.go b/common/natspec/natspec_e2e_test.go index a8d318b57a..f6368c9c56 100644 --- a/common/natspec/natspec_e2e_test.go +++ b/common/natspec/natspec_e2e_test.go @@ -197,8 +197,11 @@ func TestNatspecE2E(t *testing.T) { codehash := common.BytesToHash(crypto.Sha3(codeb)) // use resolver to register codehash->dochash->url + // test if globalregistry works + // resolver.HashRefContractAddress = "0x0" + // resolver.UrlHintContractAddress = "0x0" registry := resolver.New(tf.xeth) - _, err := registry.Register(tf.coinbase, codehash, dochash, "file:///"+testFileName) + _, err := registry.RegisterAddrWithUrl(tf.coinbase, codehash, dochash, "file:///"+testFileName) if err != nil { t.Errorf("error registering: %v", err) } diff --git a/common/resolver/contracts.go b/common/resolver/contracts.go index 4aad95e436..eba5de1b58 100644 --- a/common/resolver/contracts.go +++ b/common/resolver/contracts.go @@ -33,4 +33,6 @@ const ( // built-in contracts address and code } */ + GlobalRegistrar = `var GlobalRegistrar = web3.eth.contract([{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"name","outputs":[{"name":"o_name","type":"bytes32"}],"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"owner","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"content","outputs":[{"name":"","type":"bytes32"}],"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"addr","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserve","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"subRegistrar","outputs":[{"name":"o_subRegistrar","type":"address"}],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_newOwner","type":"address"}],"name":"transfer","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_registrar","type":"address"}],"name":"setSubRegistrar","outputs":[],"type":"function"},{"constant":false,"inputs":[],"name":"Registrar","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_a","type":"address"},{"name":"_primary","type":"bool"}],"name":"setAddress","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_content","type":"bytes32"}],"name":"setContent","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"disown","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"register","outputs":[{"name":"","type":"address"}],"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"}],"name":"Changed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"indexed":true,"name":"addr","type":"address"}],"name":"PrimaryChanged","type":"event"}]);` + GlobalRegistrarAddr = "0xc6d9d2cd449a754c494264e1809c50e34d64562b" ) diff --git a/common/resolver/resolver.go b/common/resolver/resolver.go index 9016547e10..1f44f638c1 100644 --- a/common/resolver/resolver.go +++ b/common/resolver/resolver.go @@ -20,7 +20,10 @@ of a url scheme */ // // contract addresses will be hardcoded after they're created -var UrlHintContractAddress, HashRegContractAddress string +var ( + UrlHintContractAddress = "0x0" + HashRegContractAddress = "0x0" +) const ( txValue = "0" @@ -28,42 +31,167 @@ const ( txGasPrice = "1000000000000" ) -func abi(s string) string { +func abiSignature(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()") + HashReg = "HashReg" + UrlHint = "UrlHint" + + registerContentHashAbi = abiSignature("register(uint256,uint256)") + registerUrlAbi = abiSignature("register(uint256,uint8,uint256)") + setOwnerAbi = abiSignature("setowner()") + reserveAbi = abiSignature("reserve(bytes32)") + resolveAbi = abiSignature("addr(bytes32)") + registerAbi = abiSignature("setAddress(bytes32,address,bool)") + addressAbiPrefix = string(make([]byte, 24)) ) type Backend interface { StorageAt(string, string) string Transact(fromStr, toStr, nonceStr, valueStr, gasStr, gasPriceStr, codeStr string) (string, error) + Call(fromStr, toStr, valueStr, gasStr, gasPriceStr, codeStr string) (string, string, error) } type Resolver struct { backend Backend } -func New(eth Backend) *Resolver { - return &Resolver{eth} +func New(eth Backend) (res *Resolver) { + res = &Resolver{eth} + res.setContracts() + return } -// 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) +func (self *Resolver) setContracts() { + var err error + if HashRegContractAddress != "0x0" { + return + } + // reset iff error anywhere + defer func() { + if err != nil { + HashRegContractAddress = "0x0" + } + }() + hashRegAbi := registerAbi + string(common.Hex2BytesFixed(HashRegContractAddress[2:], 32)) + HashRegContractAddress, _, err = self.backend.Call("", GlobalRegistrarAddr, "", "", "", hashRegAbi) + if err != nil { + err = fmt.Errorf("HashReg address not found: %v", err) + return + } + + if UrlHintContractAddress != "0x0" { + return + } + // reset iff error anywhere + defer func() { + if err != nil { + UrlHintContractAddress = "0x0" + } + }() + + urlHintAbi := registerAbi + string(common.Hex2BytesFixed(UrlHintContractAddress[2:], 32)) + UrlHintContractAddress, _, err = self.backend.Call("", GlobalRegistrarAddr, "", "", "", urlHintAbi) + if err != nil { + err = fmt.Errorf("UrlHint address not found: %v", err) + return + } + + glog.V(logger.Detail).Infof("HashReg @ %v\nUrlHint @ %v\n", HashRegContractAddress, UrlHintContractAddress) +} + +// This can be safely called from tests to or private chains to create +// new HashReg and UrlHint contracts (requires transaction) +// It does nothing if addresses are set +func (self *Resolver) CreateContracts(addr common.Address) (hashReg, urlHint string, err error) { + if HashRegContractAddress != "0x0" { + err = fmt.Errorf("HashReg already exists at %v", HashRegContractAddress) + return + } + hashReg, 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) + _, err = self.Reserve(addr, HashReg) + if err != nil { + return + } + _, err = self.RegisterAddress(addr, HashReg, common.HexToAddress(hashReg)) + if err != nil { + return + } + + if UrlHintContractAddress != "0x0" { + err = fmt.Errorf("UrlHint already exists at %v", UrlHintContractAddress) + return + } + urlHint, err = self.backend.Transact(addr.Hex(), "", "", txValue, txGas, txGasPrice, ContractCodeURLhint) + if err != nil { + return + } + _, err = self.Reserve(addr, UrlHint) + if err != nil { + return + } + _, err = self.RegisterAddress(addr, UrlHint, common.HexToAddress(urlHint)) + if err != nil { + return + } + HashRegContractAddress = hashReg + UrlHintContractAddress = urlHint glog.V(logger.Detail).Infof("HashReg @ %v\nUrlHint @ %v\n", HashRegContractAddress, UrlHintContractAddress) + + return +} + +// Reserve(from, name) reserves name for the sender address in the globalRegistrar +// the tx needs to be mined to take effect +func (self *Resolver) Reserve(address common.Address, name string) (txh string, err error) { + nameHex, extra := encodeName(name, 6) + abi := reserveAbi + nameHex + extra + return self.backend.Transact( + address.Hex(), + GlobalRegistrarAddr, + "", txValue, txGas, txGasPrice, + abi, + ) +} + +// RegisterAddress(from, name, addr) will set the Address to address for name +// in the globalRegistrar using from as the sender of the transaction +// the tx needs to be mined to take effect +func (self *Resolver) RegisterAddress(from common.Address, name string, address common.Address) (txh string, err error) { + nameHex, extra := encodeName(name, 6) + addrHex := encodeAddress(address) + + trueHex := make([]byte, 64) + trueHex[63] = 1 + + abi := registerAbi + nameHex + addrHex + extra + return self.backend.Transact( + from.Hex(), + GlobalRegistrarAddr, + "", txValue, txGas, txGasPrice, + abi, + ) +} + +// NameToAddr(from, name) queries the registrar for the address on +func (self *Resolver) NameToAddr(from common.Address, name string) (address common.Address, err error) { + nameHex, extra := encodeName(name, 2) + abi := resolveAbi + nameHex + extra + res, _, err := self.backend.Call( + from.Hex(), + GlobalRegistrarAddr, + txValue, txGas, txGasPrice, + abi, + ) + if err != nil { + return + } + address = common.HexToAddress(res) return } @@ -138,7 +266,7 @@ func (self *Resolver) RegisterUrl(address common.Address, hash common.Hash, url return } -func (self *Resolver) Register(address common.Address, codehash, dochash common.Hash, url string) (txh string, err error) { +func (self *Resolver) RegisterAddrWithUrl(address common.Address, codehash, dochash common.Hash, url string) (txh string, err error) { _, err = self.RegisterContentHash(address, codehash, dochash) if err != nil { @@ -151,7 +279,7 @@ func (self *Resolver) Register(address common.Address, codehash, dochash common. // 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)) + at := HashRegContractAddress[2:] key := storageAddress(storageMapping(storageIdx2Addr(1), khash[:])) hash := self.backend.StorageAt(at, key) @@ -172,7 +300,7 @@ func (self *Resolver) ContentHashToUrl(chash common.Hash) (uri string, err error for len(str) > 0 { mapaddr := storageMapping(storageIdx2Addr(1), chash[:]) key := storageAddress(storageFixedArray(mapaddr, storageIdx2Addr(idx))) - hex := self.backend.StorageAt(UrlHintContractAddress, key) + hex := self.backend.StorageAt(UrlHintContractAddress[2:], key) str = string(common.Hex2Bytes(hex[2:])) l := len(str) for (l > 0) && (str[l-1] == 0) { @@ -230,3 +358,18 @@ func storageFixedArray(addr, idx []byte) []byte { func storageAddress(addr []byte) string { return common.ToHex(addr) } + +func encodeAddress(address common.Address) string { + return addressAbiPrefix + address.Hex()[2:] +} + +func encodeName(name string, index uint8) (nameHex, extra string) { + nameHexBytes := make([]byte, 64) + if len(name) > 32 { + nameHexBytes[63] = byte(index) + extra = common.Bytes2Hex([]byte(name)) + } else { + copy(nameHexBytes, []byte(name)) + } + return string(nameHexBytes), extra +} diff --git a/common/resolver/resolver_test.go b/common/resolver/resolver_test.go index 02d12592e5..958d0f97fd 100644 --- a/common/resolver/resolver_test.go +++ b/common/resolver/resolver_test.go @@ -20,22 +20,22 @@ var ( ) func NewTestBackend() *testBackend { - HashRegContractAddress = common.BigToAddress(common.Big0).Hex()[2:] - UrlHintContractAddress = common.BigToAddress(common.Big1).Hex()[2:] + HashRegContractAddress = common.BigToAddress(common.Big0).Hex() //[2:] + UrlHintContractAddress = common.BigToAddress(common.Big1).Hex() //[2:] self := &testBackend{} self.contracts = make(map[string](map[string]string)) - self.contracts[HashRegContractAddress] = make(map[string]string) + self.contracts[HashRegContractAddress[2:]] = make(map[string]string) key := storageAddress(storageMapping(storageIdx2Addr(1), codehash[:])) - self.contracts[HashRegContractAddress][key] = hash.Hex() + self.contracts[HashRegContractAddress[2:]][key] = hash.Hex() - self.contracts[UrlHintContractAddress] = make(map[string]string) + self.contracts[UrlHintContractAddress[2:]] = make(map[string]string) mapaddr := storageMapping(storageIdx2Addr(1), hash[:]) key = storageAddress(storageFixedArray(mapaddr, storageIdx2Addr(0))) - self.contracts[UrlHintContractAddress][key] = common.ToHex([]byte(url)) + self.contracts[UrlHintContractAddress[2:]][key] = common.ToHex([]byte(url)) key = storageAddress(storageFixedArray(mapaddr, storageIdx2Addr(1))) - self.contracts[UrlHintContractAddress][key] = "0x00" + self.contracts[UrlHintContractAddress[2:]][key] = "0x0" return self } @@ -52,6 +52,20 @@ func (self *testBackend) Transact(fromStr, toStr, nonceStr, valueStr, gasStr, ga return "", nil } +func (self *testBackend) Call(fromStr, toStr, valueStr, gasStr, gasPriceStr, codeStr string) (string, string, error) { + return "", "", nil +} + +func TestCreateContractsExists(t *testing.T) { + b := NewTestBackend() + res := New(b) + _, _, err := res.CreateContracts(common.BigToAddress(common.Big0)) + exp := "HashReg already exists at 0x0000000000000000000000000000000000000000" + if err == nil || err.Error() != exp { + t.Errorf("expected error '%s', got '%v'", exp, err) + } +} + func TestKeyToContentHash(t *testing.T) { b := NewTestBackend() res := New(b) @@ -71,7 +85,7 @@ func TestContentHashToUrl(t *testing.T) { res := New(b) got, err := res.ContentHashToUrl(hash) if err != nil { - t.Errorf("expected no error, got %v", err) + t.Errorf("expected error, got %v", err) } else { if got != url { t.Errorf("incorrect result, expected '%v', got '%s'", url, got)