Skip to content

Commit

Permalink
implementation of ENS Reverse name resolution
Browse files Browse the repository at this point in the history
  • Loading branch information
yuriymyronovych committed Feb 14, 2018
1 parent fdf8348 commit a79c944
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 16 deletions.
75 changes: 63 additions & 12 deletions core/src/main/java/org/web3j/ens/EnsResolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
public class EnsResolver {

static final long DEFAULT_SYNC_THRESHOLD = 1000 * 60 * 3;
static final String REVERSE_NAME_SUFFIX = ".addr.reverse";

private final Web3j web3j;
private final TransactionManager transactionManager;
Expand All @@ -42,45 +43,95 @@ public long getSyncThreshold() {
return syncThreshold;
}

public String resolve(String contractId) {
if (isValidEnsName(contractId)) {
/**
* Provides an access to a valid public resolver in order to access extra API methods like "text", "content" etc..
* @param ensName our user input ENS name
* @return PublicResolver
*/
public PublicResolver obtainPublicResolver(String ensName) {
if (isValidEnsName(ensName)) {
try {
if (!isSynced()) {
throw new EnsResolutionException("Node is not currently synced");
} else {
return lookupAddress(contractId);
return lookupResolver(ensName);
}
} catch (Exception e) {
throw new EnsResolutionException("Unable to determine sync status of node", e);
}

} else {
throw new EnsResolutionException("EnsName is invalid: " + ensName);
}
}

public String resolve(String contractId) {
if (isValidEnsName(contractId)) {
PublicResolver resolver = obtainPublicResolver(contractId);

byte[] nameHash = NameHash.nameHashAsBytes(contractId);
String contractAddress = null;
try {
contractAddress = resolver.addr(nameHash).send();
} catch (Exception e) {
throw new RuntimeException("Unable to execute Ethereum request", e);
}

if (!WalletUtils.isValidAddress(contractAddress)) {
throw new RuntimeException("Unable to resolve address for name: " + contractId);
} else {
return contractAddress;
}
} else {
return contractId;
}
}

String lookupAddress(String ensName) throws Exception {
/**
* Reverse name resolution as documented in the
* <a href="https://docs.ens.domains/en/latest/userguide.html#reverse-name-resolution">specification</a>.
* @param address an ethereum address, example: "0x314159265dd8dbb310642f98f50c066173c1259b"
* @return a EnsName registered for provided address
*/
public String reverseResolve(String address) {
if (WalletUtils.isValidAddress(address)) {
String reverseName = Numeric.cleanHexPrefix(address) + REVERSE_NAME_SUFFIX;
PublicResolver resolver = obtainPublicResolver(reverseName);

byte[] nameHash = NameHash.nameHashAsBytes(reverseName);
String name = null;
try {
name = resolver.name(nameHash).send();
} catch (Exception e) {
throw new RuntimeException("Unable to execute Ethereum request", e);
}

if (!isValidEnsName(name)) {
throw new RuntimeException("Unable to resolve name for address: " + address);
} else {
return name;
}
} else {
throw new EnsResolutionException("Address is invalid: " + address);
}
}

PublicResolver lookupResolver(String ensName) throws Exception {
NetVersion netVersion = web3j.netVersion().send();
String registryContract = Contracts.resolveRegistryContract(netVersion.getNetVersion());

ENS ensRegistry = ENS.load(
registryContract, web3j, transactionManager,
ManagedTransaction.GAS_PRICE, org.web3j.tx.Contract.GAS_LIMIT);

byte[] nameHash = Numeric.hexStringToByteArray(NameHash.nameHash(ensName));
byte[] nameHash = NameHash.nameHashAsBytes(ensName);

String resolverAddress = ensRegistry.resolver(nameHash).send();
PublicResolver resolver = PublicResolver.load(
resolverAddress, web3j, transactionManager,
ManagedTransaction.GAS_PRICE, org.web3j.tx.Contract.GAS_LIMIT);

String contractAddress = resolver.addr(nameHash).send();

if (!WalletUtils.isValidAddress(contractAddress)) {
throw new RuntimeException("Unable to resolve address for name: " + ensName);
} else {
return contractAddress;
}
return resolver;
}

boolean isSynced() throws Exception {
Expand Down
4 changes: 4 additions & 0 deletions core/src/main/java/org/web3j/ens/NameHash.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ public class NameHash {

private static final byte[] EMPTY = new byte[32];

public static byte[] nameHashAsBytes(String ensName) {
return Numeric.hexStringToByteArray(nameHash(ensName));
}

public static String nameHash(String ensName) {
String normalisedEnsName = normalise(ensName);
return Numeric.toHexString(nameHash(normalisedEnsName.split("\\.")));
Expand Down
48 changes: 44 additions & 4 deletions core/src/test/java/org/web3j/ens/EnsResolverTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,18 @@
import org.junit.Before;
import org.junit.Test;

import org.web3j.abi.TypeDecoder;
import org.web3j.abi.TypeEncoder;
import org.web3j.abi.datatypes.Utf8String;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.Web3jService;
import org.web3j.protocol.core.JsonRpc2_0Web3j;
import org.web3j.protocol.core.Request;
import org.web3j.protocol.core.methods.response.EthBlock;
import org.web3j.protocol.core.methods.response.EthCall;
import org.web3j.protocol.core.methods.response.EthSyncing;
import org.web3j.protocol.core.methods.response.NetVersion;
import org.web3j.protocol.http.HttpService;
import org.web3j.tx.ChainId;
import org.web3j.utils.Numeric;

Expand Down Expand Up @@ -41,7 +46,10 @@ public void setUp() {
}

@Test
public void testLookupAddress() throws Exception {
public void testResolve() throws Exception {
configureSyncing(false);
configureLatestBlock(System.currentTimeMillis() / 1000); // block timestamp is in seconds

NetVersion netVersion = new NetVersion();
netVersion.setResult(Byte.toString(ChainId.MAINNET));

Expand All @@ -63,10 +71,42 @@ public void testLookupAddress() throws Exception {
when(web3jService.send(any(Request.class), eq(EthCall.class)))
.thenReturn(contractAddressResponse);

assertThat(ensResolver.lookupAddress("web3j.eth"),
assertThat(ensResolver.resolve("web3j.eth"),
is("0x19e03255f667bdfd50a32722df860b1eeaf4d635"));
}

@Test
public void testReverseResolve() throws Exception {
configureSyncing(false);
configureLatestBlock(System.currentTimeMillis() / 1000); // block timestamp is in seconds

NetVersion netVersion = new NetVersion();
netVersion.setResult(Byte.toString(ChainId.MAINNET));

String resolverAddress =
"0x0000000000000000000000004c641fb9bad9b60ef180c31f56051ce826d21a9a";
String contractName =
"0x0000000000000000000000000000000000000000000000000000000000000020"
+ TypeEncoder.encode(new Utf8String("web3j.eth"));
System.err.println(contractName);

EthCall resolverAddressResponse = new EthCall();
resolverAddressResponse.setResult(resolverAddress);

EthCall contractNameResponse = new EthCall();
contractNameResponse.setResult(contractName);

when(web3jService.send(any(Request.class), eq(NetVersion.class)))
.thenReturn(netVersion);
when(web3jService.send(any(Request.class), eq(EthCall.class)))
.thenReturn(resolverAddressResponse);
when(web3jService.send(any(Request.class), eq(EthCall.class)))
.thenReturn(contractNameResponse);

assertThat(ensResolver.reverseResolve("0x19e03255f667bdfd50a32722df860b1eeaf4d635"),
is("web3j.eth"));
}

@Test
public void testIsSyncedSyncing() throws Exception {
configureSyncing(true);
Expand All @@ -90,10 +130,10 @@ public void testIsSyncedBelowThreshold() throws Exception {
assertFalse(ensResolver.isSynced());
}

private void configureSyncing(boolean isSynced) throws IOException {
private void configureSyncing(boolean isSyncing) throws IOException {
EthSyncing ethSyncing = new EthSyncing();
EthSyncing.Result result = new EthSyncing.Result();
result.setSyncing(isSynced);
result.setSyncing(isSyncing);
ethSyncing.setResult(result);

when(web3jService.send(any(Request.class), eq(EthSyncing.class)))
Expand Down

0 comments on commit a79c944

Please sign in to comment.