feat: Add ens service (#2467)
This commit is contained in:
parent
b244188702
commit
12c727df25
3
go.mod
3
go.mod
|
@ -22,6 +22,7 @@ require (
|
||||||
github.com/golang/protobuf v1.5.2
|
github.com/golang/protobuf v1.5.2
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.3.0
|
||||||
github.com/imdario/mergo v0.3.12
|
github.com/imdario/mergo v0.3.12
|
||||||
|
github.com/ipfs/go-cid v0.0.7
|
||||||
github.com/ipfs/go-ds-sql v0.2.0
|
github.com/ipfs/go-ds-sql v0.2.0
|
||||||
github.com/ipfs/go-log v1.0.5
|
github.com/ipfs/go-log v1.0.5
|
||||||
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a
|
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a
|
||||||
|
@ -38,6 +39,7 @@ require (
|
||||||
github.com/mattn/go-colorable v0.1.4 // indirect
|
github.com/mattn/go-colorable v0.1.4 // indirect
|
||||||
github.com/multiformats/go-multiaddr v0.4.0
|
github.com/multiformats/go-multiaddr v0.4.0
|
||||||
github.com/multiformats/go-multibase v0.0.3
|
github.com/multiformats/go-multibase v0.0.3
|
||||||
|
github.com/multiformats/go-multihash v0.0.15
|
||||||
github.com/multiformats/go-varint v0.0.6
|
github.com/multiformats/go-varint v0.0.6
|
||||||
github.com/mutecomm/go-sqlcipher v0.0.0-20190227152316-55dbde17881f
|
github.com/mutecomm/go-sqlcipher v0.0.0-20190227152316-55dbde17881f
|
||||||
github.com/nfnt/resize v0.0.0-00010101000000-000000000000
|
github.com/nfnt/resize v0.0.0-00010101000000-000000000000
|
||||||
|
@ -62,6 +64,7 @@ require (
|
||||||
github.com/tsenart/tb v0.0.0-20181025101425-0d2499c8b6e9
|
github.com/tsenart/tb v0.0.0-20181025101425-0d2499c8b6e9
|
||||||
github.com/vacp2p/mvds v0.0.24-0.20201124060106-26d8e94130d8
|
github.com/vacp2p/mvds v0.0.24-0.20201124060106-26d8e94130d8
|
||||||
github.com/wealdtech/go-ens/v3 v3.5.0
|
github.com/wealdtech/go-ens/v3 v3.5.0
|
||||||
|
github.com/wealdtech/go-multicodec v1.4.0
|
||||||
github.com/xeipuuv/gojsonschema v1.2.0
|
github.com/xeipuuv/gojsonschema v1.2.0
|
||||||
go.uber.org/zap v1.19.0
|
go.uber.org/zap v1.19.0
|
||||||
golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e
|
golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e
|
||||||
|
|
|
@ -32,6 +32,7 @@ import (
|
||||||
accountssvc "github.com/status-im/status-go/services/accounts"
|
accountssvc "github.com/status-im/status-go/services/accounts"
|
||||||
appmetricsservice "github.com/status-im/status-go/services/appmetrics"
|
appmetricsservice "github.com/status-im/status-go/services/appmetrics"
|
||||||
"github.com/status-im/status-go/services/browsers"
|
"github.com/status-im/status-go/services/browsers"
|
||||||
|
"github.com/status-im/status-go/services/ens"
|
||||||
localnotifications "github.com/status-im/status-go/services/local-notifications"
|
localnotifications "github.com/status-im/status-go/services/local-notifications"
|
||||||
"github.com/status-im/status-go/services/mailservers"
|
"github.com/status-im/status-go/services/mailservers"
|
||||||
"github.com/status-im/status-go/services/peer"
|
"github.com/status-im/status-go/services/peer"
|
||||||
|
@ -105,6 +106,7 @@ type StatusNode struct {
|
||||||
wakuExtSrvc *wakuext.Service
|
wakuExtSrvc *wakuext.Service
|
||||||
wakuV2Srvc *wakuv2.Waku
|
wakuV2Srvc *wakuv2.Waku
|
||||||
wakuV2ExtSrvc *wakuv2ext.Service
|
wakuV2ExtSrvc *wakuv2ext.Service
|
||||||
|
ensSrvc *ens.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
// New makes new instance of StatusNode.
|
// New makes new instance of StatusNode.
|
||||||
|
@ -411,6 +413,7 @@ func (n *StatusNode) stop() error {
|
||||||
n.wakuExtSrvc = nil
|
n.wakuExtSrvc = nil
|
||||||
n.wakuV2Srvc = nil
|
n.wakuV2Srvc = nil
|
||||||
n.wakuV2ExtSrvc = nil
|
n.wakuV2ExtSrvc = nil
|
||||||
|
n.ensSrvc = nil
|
||||||
n.publicMethods = make(map[string]bool)
|
n.publicMethods = make(map[string]bool)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -24,6 +24,7 @@ import (
|
||||||
accountssvc "github.com/status-im/status-go/services/accounts"
|
accountssvc "github.com/status-im/status-go/services/accounts"
|
||||||
appmetricsservice "github.com/status-im/status-go/services/appmetrics"
|
appmetricsservice "github.com/status-im/status-go/services/appmetrics"
|
||||||
"github.com/status-im/status-go/services/browsers"
|
"github.com/status-im/status-go/services/browsers"
|
||||||
|
"github.com/status-im/status-go/services/ens"
|
||||||
"github.com/status-im/status-go/services/ext"
|
"github.com/status-im/status-go/services/ext"
|
||||||
localnotifications "github.com/status-im/status-go/services/local-notifications"
|
localnotifications "github.com/status-im/status-go/services/local-notifications"
|
||||||
"github.com/status-im/status-go/services/mailservers"
|
"github.com/status-im/status-go/services/mailservers"
|
||||||
|
@ -65,6 +66,7 @@ func (b *StatusNode) initServices(config *params.NodeConfig) error {
|
||||||
services = appendIf(config.EnableNTPSync, services, b.timeSource())
|
services = appendIf(config.EnableNTPSync, services, b.timeSource())
|
||||||
services = appendIf(b.appDB != nil && b.multiaccountsDB != nil, services, b.accountsService(accountsFeed))
|
services = appendIf(b.appDB != nil && b.multiaccountsDB != nil, services, b.accountsService(accountsFeed))
|
||||||
services = appendIf(config.BrowsersConfig.Enabled, services, b.browsersService())
|
services = appendIf(config.BrowsersConfig.Enabled, services, b.browsersService())
|
||||||
|
services = appendIf(config.ENSConfig.Enabled, services, b.ensService())
|
||||||
services = appendIf(config.PermissionsConfig.Enabled, services, b.permissionsService())
|
services = appendIf(config.PermissionsConfig.Enabled, services, b.permissionsService())
|
||||||
services = appendIf(config.MailserversConfig.Enabled, services, b.mailserversService())
|
services = appendIf(config.MailserversConfig.Enabled, services, b.mailserversService())
|
||||||
if config.WakuConfig.Enabled {
|
if config.WakuConfig.Enabled {
|
||||||
|
@ -351,6 +353,13 @@ func (b *StatusNode) browsersService() *browsers.Service {
|
||||||
return b.browsersSrvc
|
return b.browsersSrvc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *StatusNode) ensService() *ens.Service {
|
||||||
|
if b.ensSrvc == nil {
|
||||||
|
b.ensSrvc = ens.NewService(b.rpcClient)
|
||||||
|
}
|
||||||
|
return b.ensSrvc
|
||||||
|
}
|
||||||
|
|
||||||
func (b *StatusNode) permissionsService() *permissions.Service {
|
func (b *StatusNode) permissionsService() *permissions.Service {
|
||||||
if b.permissionsSrvc == nil {
|
if b.permissionsSrvc == nil {
|
||||||
b.permissionsSrvc = permissions.NewService(permissions.NewDB(b.appDB))
|
b.permissionsSrvc = permissions.NewService(permissions.NewDB(b.appDB))
|
||||||
|
|
|
@ -502,6 +502,9 @@ type NodeConfig struct {
|
||||||
// BrowsersConfig extra configuration for browsers.Service.
|
// BrowsersConfig extra configuration for browsers.Service.
|
||||||
BrowsersConfig BrowsersConfig
|
BrowsersConfig BrowsersConfig
|
||||||
|
|
||||||
|
// ENSConfig extra configuration for ens.Service.
|
||||||
|
ENSConfig ENSConfig `json:"EnsConfig" validate:"structonly"`
|
||||||
|
|
||||||
// PermissionsConfig extra configuration for permissions.Service.
|
// PermissionsConfig extra configuration for permissions.Service.
|
||||||
PermissionsConfig PermissionsConfig
|
PermissionsConfig PermissionsConfig
|
||||||
|
|
||||||
|
@ -542,6 +545,11 @@ type BrowsersConfig struct {
|
||||||
Enabled bool
|
Enabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ENSConfig extra configuration for ens.Service.
|
||||||
|
type ENSConfig struct {
|
||||||
|
Enabled bool
|
||||||
|
}
|
||||||
|
|
||||||
// PermissionsConfig extra configuration for permissions.Service.
|
// PermissionsConfig extra configuration for permissions.Service.
|
||||||
type PermissionsConfig struct {
|
type PermissionsConfig struct {
|
||||||
Enabled bool
|
Enabled bool
|
||||||
|
|
|
@ -0,0 +1,321 @@
|
||||||
|
package ens
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/ipfs/go-cid"
|
||||||
|
"github.com/multiformats/go-multibase"
|
||||||
|
"github.com/multiformats/go-multihash"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/wealdtech/go-multicodec"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/status-im/status-go/rpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewAPI(rpcClient *rpc.Client) *API {
|
||||||
|
return &API{
|
||||||
|
contractMaker: &contractMaker{
|
||||||
|
rpcClient: rpcClient,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type uri struct {
|
||||||
|
Scheme string
|
||||||
|
Host string
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
type publicKey struct {
|
||||||
|
X [32]byte
|
||||||
|
Y [32]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type API struct {
|
||||||
|
contractMaker *contractMaker
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) Resolver(ctx context.Context, chainID uint64, username string) (*common.Address, error) {
|
||||||
|
err := validateENSUsername(username)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
registry, err := api.contractMaker.newRegistry(chainID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
callOpts := &bind.CallOpts{Context: ctx, Pending: false}
|
||||||
|
resolver, err := registry.Resolver(callOpts, nameHash(username))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &resolver, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) OwnerOf(ctx context.Context, chainID uint64, username string) (*common.Address, error) {
|
||||||
|
err := validateENSUsername(username)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
registry, err := api.contractMaker.newRegistry(chainID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
callOpts := &bind.CallOpts{Context: ctx, Pending: false}
|
||||||
|
owner, err := registry.Owner(callOpts, nameHash(username))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &owner, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) ContentHash(ctx context.Context, chainID uint64, username string) ([]byte, error) {
|
||||||
|
err := validateENSUsername(username)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resolverAddress, err := api.Resolver(ctx, chainID, username)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resolver, err := api.contractMaker.newPublicResolver(chainID, resolverAddress)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
callOpts := &bind.CallOpts{Context: ctx, Pending: false}
|
||||||
|
contentHash, err := resolver.Contenthash(callOpts, nameHash(username))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return contentHash, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) PublicKeyOf(ctx context.Context, chainID uint64, username string) (*publicKey, error) {
|
||||||
|
err := validateENSUsername(username)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resolverAddress, err := api.Resolver(ctx, chainID, username)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resolver, err := api.contractMaker.newPublicResolver(chainID, resolverAddress)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
callOpts := &bind.CallOpts{Context: ctx, Pending: false}
|
||||||
|
pubKey, err := resolver.Pubkey(callOpts, nameHash(username))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &publicKey{pubKey.X, pubKey.Y}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) AddressOf(ctx context.Context, chainID uint64, username string) (*common.Address, error) {
|
||||||
|
err := validateENSUsername(username)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resolverAddress, err := api.Resolver(ctx, chainID, username)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resolver, err := api.contractMaker.newPublicResolver(chainID, resolverAddress)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
callOpts := &bind.CallOpts{Context: ctx, Pending: false}
|
||||||
|
addr, err := resolver.Addr(callOpts, nameHash(username))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &addr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) ExpireAt(ctx context.Context, chainID uint64, username string) (*big.Int, error) {
|
||||||
|
err := validateENSUsername(username)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
registrar, err := api.contractMaker.newUsernameRegistrar(chainID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
callOpts := &bind.CallOpts{Context: ctx, Pending: false}
|
||||||
|
expTime, err := registrar.GetExpirationTime(callOpts, nameHash(username))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return expTime, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) Price(ctx context.Context, chainID uint64) (*big.Int, error) {
|
||||||
|
registrar, err := api.contractMaker.newUsernameRegistrar(chainID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
callOpts := &bind.CallOpts{Context: ctx, Pending: false}
|
||||||
|
price, err := registrar.GetPrice(callOpts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return price, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: implement once the send tx as been refactored
|
||||||
|
// func (api *API) Release(ctx context.Context, chainID uint64, from string, gasPrice *big.Int, gasLimit uint64, password string, username string) (string, error) {
|
||||||
|
// err := validateENSUsername(username)
|
||||||
|
// if err != nil {
|
||||||
|
// return "", err
|
||||||
|
// }
|
||||||
|
|
||||||
|
// registrar, err := api.contractMaker.newUsernameRegistrar(chainID)
|
||||||
|
// if err != nil {
|
||||||
|
// return "", err
|
||||||
|
// }
|
||||||
|
|
||||||
|
// txOpts := &bind.TransactOpts{
|
||||||
|
// From: common.HexToAddress(from),
|
||||||
|
// Signer: func(addr common.Address, tx *types.Transaction) (*types.Transaction, error) {
|
||||||
|
// // return types.SignTx(tx, types.NewLondonSigner(chainID), selectedAccount.AccountKey.PrivateKey)
|
||||||
|
// return nil, nil
|
||||||
|
// },
|
||||||
|
// GasPrice: gasPrice,
|
||||||
|
// GasLimit: gasLimit,
|
||||||
|
// }
|
||||||
|
// tx, err := registrar.Release(txOpts, nameHash(username))
|
||||||
|
// if err != nil {
|
||||||
|
// return "", err
|
||||||
|
// }
|
||||||
|
// return tx.Hash().String(), nil
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (api *API) Register(ctx context.Context, chainID uint64, from string, gasPrice *big.Int, gasLimit uint64, password string, username string, x [32]byte, y [32]byte) (string, error) {
|
||||||
|
// err := validateENSUsername(username)
|
||||||
|
// if err != nil {
|
||||||
|
// return "", err
|
||||||
|
// }
|
||||||
|
|
||||||
|
// registrar, err := api.contractMaker.newUsernameRegistrar(chainID)
|
||||||
|
// if err != nil {
|
||||||
|
// return "", err
|
||||||
|
// }
|
||||||
|
|
||||||
|
// txOpts := &bind.TransactOpts{
|
||||||
|
// From: common.HexToAddress(from),
|
||||||
|
// Signer: func(addr common.Address, tx *types.Transaction) (*types.Transaction, error) {
|
||||||
|
// // return types.SignTx(tx, types.NewLondonSigner(chainID), selectedAccount.AccountKey.PrivateKey)
|
||||||
|
// return nil, nil
|
||||||
|
// },
|
||||||
|
// GasPrice: gasPrice,
|
||||||
|
// GasLimit: gasLimit,
|
||||||
|
// }
|
||||||
|
// tx, err := registrar.Register(
|
||||||
|
// txOpts,
|
||||||
|
// nameHash(username),
|
||||||
|
// common.HexToAddress(from),
|
||||||
|
// x,
|
||||||
|
// y,
|
||||||
|
// )
|
||||||
|
// if err != nil {
|
||||||
|
// return "", err
|
||||||
|
// }
|
||||||
|
// return tx.Hash().String(), nil
|
||||||
|
// }
|
||||||
|
|
||||||
|
func (api *API) ResourceURL(ctx context.Context, chainID uint64, username string) (*uri, error) {
|
||||||
|
scheme := "https"
|
||||||
|
contentHash, err := api.ContentHash(ctx, chainID, username)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(contentHash) == 0 {
|
||||||
|
return &uri{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
data, codec, err := multicodec.RemoveCodec(contentHash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
codecName, err := multicodec.Name(codec)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch codecName {
|
||||||
|
case "ipfs-ns":
|
||||||
|
thisCID, err := cid.Parse(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to parse CID")
|
||||||
|
}
|
||||||
|
str, err := thisCID.StringOfBase(multibase.Base32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to obtain base36 representation")
|
||||||
|
}
|
||||||
|
host := str + ".ipfs.cf-ipfs.com"
|
||||||
|
return &uri{scheme, host, ""}, nil
|
||||||
|
case "ipns-ns":
|
||||||
|
id, offset := binary.Uvarint(data)
|
||||||
|
if id == 0 {
|
||||||
|
return nil, fmt.Errorf("unknown CID")
|
||||||
|
}
|
||||||
|
|
||||||
|
data, _, err := multicodec.RemoveCodec(data[offset:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
decodedMHash, err := multihash.Decode(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &uri{scheme, string(decodedMHash.Digest), ""}, nil
|
||||||
|
case "swarm-ns":
|
||||||
|
id, offset := binary.Uvarint(data)
|
||||||
|
if id == 0 {
|
||||||
|
return nil, fmt.Errorf("unknown CID")
|
||||||
|
}
|
||||||
|
data, _, err := multicodec.RemoveCodec(data[offset:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
decodedMHash, err := multihash.Decode(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
path := "/bzz:/" + hex.EncodeToString(decodedMHash.Digest) + "/"
|
||||||
|
return &uri{scheme, "swarm-gateways.net", path}, nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unknown codec name %s", codecName)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,148 @@
|
||||||
|
package ens
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
gethrpc "github.com/ethereum/go-ethereum/rpc"
|
||||||
|
"github.com/status-im/status-go/appdatabase"
|
||||||
|
"github.com/status-im/status-go/params"
|
||||||
|
statusRPC "github.com/status-im/status-go/rpc"
|
||||||
|
"github.com/status-im/status-go/t/utils"
|
||||||
|
"github.com/status-im/status-go/transactions/fake"
|
||||||
|
)
|
||||||
|
|
||||||
|
func createDB(t *testing.T) (*sql.DB, func()) {
|
||||||
|
tmpfile, err := ioutil.TempFile("", "service-ens-tests-")
|
||||||
|
require.NoError(t, err)
|
||||||
|
db, err := appdatabase.InitializeDB(tmpfile.Name(), "service-ens-tests")
|
||||||
|
require.NoError(t, err)
|
||||||
|
return db, func() {
|
||||||
|
require.NoError(t, db.Close())
|
||||||
|
require.NoError(t, os.Remove(tmpfile.Name()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupTestAPI(t *testing.T) (*API, func()) {
|
||||||
|
db, cancel := createDB(t)
|
||||||
|
|
||||||
|
keyStoreDir, err := ioutil.TempDir(os.TempDir(), "accounts")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Creating a dummy status node to simulate what it's done in get_status_node.go
|
||||||
|
upstreamConfig := params.UpstreamRPCConfig{
|
||||||
|
URL: "https://mainnet.infura.io/v3/800c641949d64d768a5070a1b0511938",
|
||||||
|
Enabled: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
txServiceMockCtrl := gomock.NewController(t)
|
||||||
|
server, _ := fake.NewTestServer(txServiceMockCtrl)
|
||||||
|
client := gethrpc.DialInProc(server)
|
||||||
|
|
||||||
|
_ = client
|
||||||
|
|
||||||
|
rpcClient, err := statusRPC.NewClient(nil, 1, upstreamConfig, nil, db)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// import account keys
|
||||||
|
utils.Init()
|
||||||
|
require.NoError(t, utils.ImportTestAccount(keyStoreDir, utils.GetAccount1PKFile()))
|
||||||
|
|
||||||
|
return NewAPI(rpcClient), cancel
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolver(t *testing.T) {
|
||||||
|
api, cancel := setupTestAPI(t)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
r, err := api.Resolver(context.Background(), 1, "rramos.eth")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41", r.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOwnerOf(t *testing.T) {
|
||||||
|
api, cancel := setupTestAPI(t)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
r, err := api.OwnerOf(context.Background(), 1, "rramos.eth")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "0x7d28Ab6948F3Db2F95A43742265D382a4888c120", r.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContentHash(t *testing.T) {
|
||||||
|
api, cancel := setupTestAPI(t)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
r, err := api.ContentHash(context.Background(), 1, "simpledapp.eth")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, []byte{0xe3, 0x1, 0x1, 0x70, 0x12, 0x20, 0x79, 0x5c, 0x1e, 0xa0, 0xce, 0xaf, 0x4c, 0xee, 0xdc, 0x98, 0x96, 0xf1, 0x4b, 0x73, 0xbb, 0x30, 0xe9, 0x78, 0xe4, 0x85, 0x5e, 0xe2, 0x21, 0xb9, 0xa5, 0x7f, 0x5a, 0x93, 0x42, 0x68, 0x28, 0xe}, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPublicKeyOf(t *testing.T) {
|
||||||
|
api, cancel := setupTestAPI(t)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
pubKey, err := api.PublicKeyOf(context.Background(), 1, "rramos.eth")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(
|
||||||
|
t,
|
||||||
|
[32]byte{226, 93, 166, 153, 78, 162, 220, 74, 199, 7, 39, 224, 126, 202, 21, 58, 233, 43, 247, 96, 157, 183, 190, 251, 126, 189, 206, 170, 211, 72, 244, 252},
|
||||||
|
pubKey.X,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddressOf(t *testing.T) {
|
||||||
|
api, cancel := setupTestAPI(t)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
r, err := api.AddressOf(context.Background(), 1, "rramos.eth")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "0x7d28Ab6948F3Db2F95A43742265D382a4888c120", r.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExpireAt(t *testing.T) {
|
||||||
|
api, cancel := setupTestAPI(t)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
r, err := api.ExpireAt(context.Background(), 1, "rramos.eth")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "0", r.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrice(t *testing.T) {
|
||||||
|
api, cancel := setupTestAPI(t)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
r, err := api.Price(context.Background(), 1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "10000000000000000000", r.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResourceURL(t *testing.T) {
|
||||||
|
api, cancel := setupTestAPI(t)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
uri, err := api.ResourceURL(context.Background(), 1, "simpledapp.eth")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "https", uri.Scheme)
|
||||||
|
require.Equal(t, "bafybeidzlqpkbtvpjtxnzgew6ffxhozq5f4ojbk64iq3tjl7lkjue2biby.ipfs.cf-ipfs.com", uri.Host)
|
||||||
|
require.Equal(t, "", uri.Path)
|
||||||
|
|
||||||
|
uri, err = api.ResourceURL(context.Background(), 1, "swarm.eth")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "https", uri.Scheme)
|
||||||
|
require.Equal(t, "swarm-gateways.net", uri.Host)
|
||||||
|
require.Equal(t, "/bzz:/b00909fbabe78f57fda93218323db5721ce256fda442ce02b46813404c6d8958/", uri.Path)
|
||||||
|
|
||||||
|
uri, err = api.ResourceURL(context.Background(), 1, "noahzinsmeister.eth")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "https", uri.Scheme)
|
||||||
|
require.Equal(t, "noahzinsmeister.com", uri.Host)
|
||||||
|
require.Equal(t, "", uri.Path)
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
package ens
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/status-im/status-go/rpc"
|
||||||
|
"github.com/status-im/status-go/services/ens/registrar"
|
||||||
|
"github.com/status-im/status-go/services/ens/resolver"
|
||||||
|
)
|
||||||
|
|
||||||
|
var errorNotAvailableOnChainID = errors.New("not available for chainID")
|
||||||
|
|
||||||
|
var resolversByChainID = map[uint64]common.Address{
|
||||||
|
1: common.HexToAddress("0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e"), // mainnet
|
||||||
|
}
|
||||||
|
|
||||||
|
var usernameRegistrarsByChainID = map[uint64]common.Address{
|
||||||
|
1: common.HexToAddress("0xDB5ac1a559b02E12F29fC0eC0e37Be8E046DEF49"), // mainnet
|
||||||
|
3: common.HexToAddress("0xdaae165beb8c06e0b7613168138ebba774aff071"), // ropsten
|
||||||
|
}
|
||||||
|
|
||||||
|
type contractMaker struct {
|
||||||
|
rpcClient *rpc.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *contractMaker) newRegistry(chainID uint64) (*resolver.ENSRegistryWithFallback, error) {
|
||||||
|
if _, ok := resolversByChainID[chainID]; !ok {
|
||||||
|
return nil, errorNotAvailableOnChainID
|
||||||
|
}
|
||||||
|
|
||||||
|
backend, err := c.rpcClient.EthClient(chainID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolver.NewENSRegistryWithFallback(
|
||||||
|
resolversByChainID[chainID],
|
||||||
|
backend,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *contractMaker) newPublicResolver(chainID uint64, resolverAddress *common.Address) (*resolver.PublicResolver, error) {
|
||||||
|
backend, err := c.rpcClient.EthClient(chainID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolver.NewPublicResolver(*resolverAddress, backend)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *contractMaker) newUsernameRegistrar(chainID uint64) (*registrar.UsernameRegistrar, error) {
|
||||||
|
if _, ok := usernameRegistrarsByChainID[chainID]; !ok {
|
||||||
|
return nil, errorNotAvailableOnChainID
|
||||||
|
}
|
||||||
|
|
||||||
|
backend, err := c.rpcClient.EthClient(chainID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return registrar.NewUsernameRegistrar(
|
||||||
|
usernameRegistrarsByChainID[chainID],
|
||||||
|
backend,
|
||||||
|
)
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,3 @@
|
||||||
|
package registrar
|
||||||
|
|
||||||
|
//go:generate abigen -sol Registrar.sol -pkg registrar -out registrar.go
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,3 @@
|
||||||
|
package resolver
|
||||||
|
|
||||||
|
//go:generate abigen -sol ENS.sol -pkg resolver -out resolver.go
|
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,43 @@
|
||||||
|
package ens
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
|
ethRpc "github.com/ethereum/go-ethereum/rpc"
|
||||||
|
"github.com/status-im/status-go/rpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewService initializes service instance.
|
||||||
|
func NewService(rpcClient *rpc.Client) *Service {
|
||||||
|
return &Service{rpcClient}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Service is a browsers service.
|
||||||
|
type Service struct {
|
||||||
|
rpcClient *rpc.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start a service.
|
||||||
|
func (s *Service) Start() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop a service.
|
||||||
|
func (s *Service) Stop() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// APIs returns list of available RPC APIs.
|
||||||
|
func (s *Service) APIs() []ethRpc.API {
|
||||||
|
return []ethRpc.API{
|
||||||
|
{
|
||||||
|
Namespace: "ens",
|
||||||
|
Version: "0.1.0",
|
||||||
|
Service: NewAPI(s.rpcClient),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Protocols returns list of p2p protocols.
|
||||||
|
func (s *Service) Protocols() []p2p.Protocol {
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package ens
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
)
|
||||||
|
|
||||||
|
func nameHash(name string) common.Hash {
|
||||||
|
node := common.Hash{}
|
||||||
|
|
||||||
|
if len(name) > 0 {
|
||||||
|
labels := strings.Split(name, ".")
|
||||||
|
|
||||||
|
for i := len(labels) - 1; i >= 0; i-- {
|
||||||
|
labelSha := crypto.Keccak256Hash([]byte(labels[i]))
|
||||||
|
node = crypto.Keccak256Hash(node.Bytes(), labelSha.Bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateENSUsername(username string) error {
|
||||||
|
if !strings.HasSuffix(username, ".eth") {
|
||||||
|
return fmt.Errorf("username must end with .eth")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in New Issue