-
Notifications
You must be signed in to change notification settings - Fork 56
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
move domain clasess to dshackle to fix integer limitation of transact…
…ion V value
- Loading branch information
Showing
87 changed files
with
2,975 additions
and
154 deletions.
There are no files selected for viewing
186 changes: 186 additions & 0 deletions
186
src/main/java/io/emeraldpay/dshackle/upstream/ethereum/domain/Address.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
/* | ||
* Copyright (c) 2020 EmeraldPay Inc, All Rights Reserved. | ||
* Copyright (c) 2016-2017 Infinitape Inc, All Rights Reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package io.emeraldpay.dshackle.upstream.ethereum.domain; | ||
|
||
import io.emeraldpay.etherjar.hex.Hex32; | ||
import io.emeraldpay.etherjar.hex.HexData; | ||
import org.bouncycastle.jcajce.provider.digest.Keccak; | ||
import org.bouncycastle.util.encoders.Hex; | ||
|
||
import java.util.Arrays; | ||
import java.util.regex.Pattern; | ||
|
||
/** | ||
* Ethereum Wallet address | ||
*/ | ||
public class Address extends HexData { | ||
|
||
private final static byte[] HEX_BYTES = "0123456789abcdef".getBytes(); | ||
|
||
public static final int SIZE_BYTES = 20; | ||
public static final int SIZE_HEX = 2 + SIZE_BYTES * 2; | ||
|
||
private static final byte[] EMPTY_12BYTES = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; | ||
|
||
/** | ||
* Use {@link Address#empty()} | ||
*/ | ||
@Deprecated | ||
public static final Address EMPTY = Address.from("0x0000000000000000000000000000000000000000"); | ||
|
||
private static final Pattern CASE_INSENSITIVE_PATTERN = Pattern.compile("0x(?i:[0-9a-f]{40})"); | ||
private static final Pattern CASE_SENSITIVE_PATTERN = Pattern.compile("0x(?:[0-9a-f]{40}|[0-9A-F]{40})"); | ||
|
||
private Address(byte[] bytes) { | ||
super(bytes, SIZE_BYTES); | ||
} | ||
|
||
/** | ||
* Create address from the provided value | ||
* | ||
* @param value 20 byte data | ||
* @return address | ||
*/ | ||
public static Address from(HexData value) { | ||
if (value == null) { | ||
throw new IllegalArgumentException("Null input value"); | ||
} | ||
if (value.getSize() != SIZE_BYTES) { | ||
throw new IllegalArgumentException("Invalid input length: " + value.getSize() + " != " + SIZE_BYTES); | ||
} | ||
return new Address(value.getBytes()); | ||
} | ||
|
||
public static Address from(byte[] value) { | ||
if (value == null) { | ||
throw new IllegalArgumentException("Null input value"); | ||
} | ||
if (value.length != SIZE_BYTES) { | ||
throw new IllegalArgumentException("Invalid input length: " + value.length + " != " + SIZE_BYTES); | ||
} | ||
return new Address(value); | ||
} | ||
|
||
public static Address from(String value) { | ||
if (value == null) { | ||
throw new IllegalArgumentException("Null input value"); | ||
} | ||
if (value.length() != SIZE_HEX) { | ||
throw new IllegalArgumentException("Invalid input length: " + value.length() + " != " + SIZE_HEX); | ||
} | ||
return new Address(HexData.from(value).getBytes()); | ||
} | ||
|
||
public static Address empty() { | ||
return new Address(new byte[SIZE_BYTES]); | ||
} | ||
|
||
/** | ||
* Extract address from Hex32 (i.e. from method call parameters, logs, etc). The extract method verifies | ||
* that the input in fact contains only address, i.e. input has zeroes for initial non-address bytes. | ||
* | ||
* @param value a Hex 32 | ||
* @return address | ||
*/ | ||
public static Address extract(Hex32 value) { | ||
if (value == null) { | ||
throw new IllegalArgumentException("Null input value"); | ||
} | ||
byte[] bytes = value.getBytes(); | ||
byte[] empty = new byte[Hex32.SIZE_BYTES - Address.SIZE_BYTES]; | ||
System.arraycopy(bytes, 0, empty, 0, empty.length); | ||
if (!Arrays.equals(empty, EMPTY_12BYTES)) { | ||
throw new IllegalArgumentException("Hex32 has non zero prefix for an Address"); | ||
} | ||
byte[] address = new byte[Address.SIZE_BYTES]; | ||
System.arraycopy(bytes, Hex32.SIZE_BYTES - Address.SIZE_BYTES, address,0, address.length); | ||
return new Address(address); | ||
} | ||
|
||
/** | ||
* Validate address according to EIP 55. | ||
* | ||
* @param address a wallet address ('0x...') | ||
* @return {@code true} if address correct or {@code false} otherwise | ||
* @see <a href="https://github.com/ethereum/EIPs/issues/55">EIP 55</a> | ||
*/ | ||
public static boolean isValidAddress(String address) { | ||
return CASE_INSENSITIVE_PATTERN.matcher(address).matches() | ||
&& (CASE_SENSITIVE_PATTERN.matcher(address).matches() || isValidChecksum(address)); | ||
} | ||
|
||
public String toChecksumString() { | ||
Keccak.Digest256 digest256 = new Keccak.Digest256(); | ||
|
||
byte[] hex = new byte[value.length * 2]; | ||
for(int i = 0, j = 0; i < value.length; i++){ | ||
hex[j++] = HEX_BYTES[(0xF0 & value[i]) >>> 4]; | ||
hex[j++] = HEX_BYTES[0x0F & value[i]]; | ||
} | ||
digest256.update(hex); | ||
String hash = Hex.toHexString(digest256.digest()); | ||
|
||
char[] plain = toHex().toCharArray(); | ||
char[] str = new char[hex.length + 2]; | ||
if (plain.length != str.length) { | ||
throw new IllegalStateException("Hex representation has invalid length: " + plain.length + " != " + str.length); | ||
} | ||
str[0] = '0'; | ||
str[1] = 'x'; | ||
for (int i = 0; i < 40; i++) { | ||
int dg = Character.digit(hash.charAt(i), 16); | ||
if (dg > 7) { | ||
str[i + 2] = Character.toUpperCase(plain[i + 2]); | ||
} else { | ||
str[i + 2] = plain[i + 2]; | ||
} | ||
} | ||
return new String(str); | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return toChecksumString(); | ||
} | ||
|
||
/** | ||
* Checks if the given string is an address with checksum (Keccak256). | ||
* | ||
* @param address a wallet address ('0x...') | ||
* @return {@code true} if address with checksum | ||
*/ | ||
static boolean isValidChecksum(String address) { | ||
Keccak.Digest256 digest256 = new Keccak.Digest256(); | ||
|
||
digest256.update( | ||
address.substring(2).toLowerCase().getBytes()); | ||
|
||
String hash = Hex.toHexString(digest256.digest()); | ||
|
||
for (int i = 0; i < 40; i++) { | ||
char ch = address.charAt(i + 2); | ||
int dg = Character.digit(hash.charAt(i), 16); | ||
|
||
if ((dg > 7 && Character.toUpperCase(ch) != ch) | ||
|| (dg <= 7 && Character.toLowerCase(ch) != ch)) | ||
return false; | ||
} | ||
|
||
return true; | ||
} | ||
} |
54 changes: 54 additions & 0 deletions
54
src/main/java/io/emeraldpay/dshackle/upstream/ethereum/domain/BlockHash.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
/* | ||
* Copyright (c) 2020 EmeraldPay Inc, All Rights Reserved. | ||
* Copyright (c) 2016-2017 Infinitape Inc, All Rights Reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package io.emeraldpay.dshackle.upstream.ethereum.domain; | ||
|
||
import io.emeraldpay.etherjar.hex.HexData; | ||
|
||
public class BlockHash extends HexData { | ||
|
||
public static final int SIZE_BYTES = 32; | ||
public static final int SIZE_HEX = 2 + SIZE_BYTES * 2; | ||
|
||
public BlockHash(byte[] value) { | ||
super(value, SIZE_BYTES); | ||
} | ||
|
||
public static BlockHash from(byte[] value) { | ||
if (value == null) { | ||
throw new IllegalArgumentException("Null Hash"); | ||
} | ||
if (value.length != SIZE_BYTES) { | ||
throw new IllegalArgumentException("Invalid Block Hash length: " + value.length); | ||
} | ||
return new BlockHash(value); | ||
} | ||
|
||
public static BlockHash from(String value) { | ||
if (value == null) { | ||
throw new IllegalArgumentException("Null Hash"); | ||
} | ||
if (value.length() != SIZE_HEX) { | ||
throw new IllegalArgumentException("Invalid Block Hash length: " + value.length()); | ||
} | ||
return new BlockHash(HexData.from(value).getBytes()); | ||
} | ||
|
||
public static BlockHash empty() { | ||
return new BlockHash(new byte[SIZE_BYTES]); | ||
} | ||
} |
131 changes: 131 additions & 0 deletions
131
src/main/java/io/emeraldpay/dshackle/upstream/ethereum/domain/Bloom.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
package io.emeraldpay.dshackle.upstream.ethereum.domain; | ||
|
||
import io.emeraldpay.etherjar.hex.HexData; | ||
import org.bouncycastle.jcajce.provider.digest.Keccak; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
|
||
public class Bloom extends HexData { | ||
|
||
public static final int SIZE_BYTES = 256; | ||
public static final int SIZE_HEX = 2 + SIZE_BYTES * 2; | ||
|
||
public Bloom(byte[] value) { | ||
super(value, SIZE_BYTES); | ||
} | ||
|
||
public static Bloom from(HexData value) { | ||
if (value == null) { | ||
throw new IllegalArgumentException("Null input value"); | ||
} | ||
if (value.getSize() != SIZE_BYTES) { | ||
throw new IllegalArgumentException("Invalid input length: " + value.getSize() + " != " + SIZE_BYTES); | ||
} | ||
return new Bloom(value.getBytes()); | ||
} | ||
|
||
public static Bloom from(String value) { | ||
if (value == null) { | ||
throw new IllegalArgumentException("Null input value"); | ||
} | ||
if (value.length() != SIZE_HEX) { | ||
throw new IllegalArgumentException("Invalid input length: " + value.length() + " != " + SIZE_HEX); | ||
} | ||
return new Bloom(HexData.from(value).getBytes()); | ||
} | ||
|
||
public static Bloom empty() { | ||
return new Bloom(new byte[SIZE_BYTES]); | ||
} | ||
|
||
public Bloom mergeWith(Bloom another) { | ||
byte[] result = new byte[SIZE_BYTES]; | ||
for (int i = 0; i < SIZE_BYTES; i++) { | ||
result[i] = (byte)(this.value[i] | another.value[i]); | ||
} | ||
return new Bloom(result); | ||
} | ||
|
||
public static Builder newBuilder() { | ||
return new Builder(); | ||
} | ||
|
||
public static int bytePosition(int bitpos) { | ||
return bitpos >> 3; | ||
} | ||
|
||
public static int byteMask(int bytepos, int bitpos) { | ||
int inbytepos = bitpos - (bytepos << 3); | ||
return 1 << (inbytepos); | ||
} | ||
|
||
public static class Builder { | ||
private final byte[] current = new byte[SIZE_BYTES]; | ||
private final Keccak.Digest256 keccak = new Keccak.Digest256(); | ||
|
||
public Builder add(HexData value) { | ||
keccak.update(value.getBytes()); | ||
byte[] hash = keccak.digest(); | ||
|
||
for (int i = 0; i < 6; i+= 2) { | ||
int high = hash[i] & 0x7; | ||
int low = hash[i + 1] & 0xff; | ||
int bitpos = (high << 8) + low; | ||
int bytepos = bytePosition(bitpos); | ||
int bytemask = byteMask(bytepos, bitpos); | ||
current[SIZE_BYTES - bytepos - 1] |= bytemask & 0xff; | ||
} | ||
return this; | ||
} | ||
|
||
public Bloom build() { | ||
return new Bloom(current); | ||
} | ||
|
||
public Filter buildFilter() { | ||
List<Filter.Check> checks = new ArrayList<>(); | ||
for (int i = 0; i < SIZE_BYTES; i++) { | ||
if (current[i] != 0) { | ||
checks.add(new Filter.Check(i, current[i])); | ||
} | ||
} | ||
return new Filter(checks.toArray(new Filter.Check[0])); | ||
} | ||
} | ||
|
||
public static class Filter { | ||
private final Check[] checks; | ||
|
||
public Filter(Check[] checks) { | ||
this.checks = checks; | ||
} | ||
|
||
public boolean isSet(Bloom bloom) { | ||
for (Check check: checks) { | ||
int actual = bloom.value[check.pos] & check.mask; | ||
if (actual == 0) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
|
||
public static class Check { | ||
private final int pos; | ||
private final byte mask; | ||
|
||
public Check(int pos, byte mask) { | ||
if (pos >= SIZE_BYTES) { | ||
throw new IllegalArgumentException("Out of filter boundaries position: " + pos); | ||
} | ||
if (mask == 0) { | ||
throw new IllegalArgumentException("Mask cannot be empty"); | ||
} | ||
this.pos = pos; | ||
this.mask = mask; | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.