dbRoots = Maps.newConcurrentMap();
public static String getDbEngineFromConfig(final Config config) {
+ if (Arch.isArm64()) {
+ // if is arm64 but config is leveldb, should throw exception?
+ logger.warn("Arm64 architecture detected, using RocksDB as db engine, ignore config.");
+ return ROCKS_DB_ENGINE;
+ }
return config.hasPath(DB_ENGINE_CONFIG_KEY)
? config.getString(DB_ENGINE_CONFIG_KEY) : DEFAULT_DB_ENGINE;
}
diff --git a/common/src/main/java/org/tron/core/exception/MaintenanceUnavailableException.java b/common/src/main/java/org/tron/core/exception/MaintenanceUnavailableException.java
new file mode 100644
index 00000000000..1911f84a616
--- /dev/null
+++ b/common/src/main/java/org/tron/core/exception/MaintenanceUnavailableException.java
@@ -0,0 +1,20 @@
+package org.tron.core.exception;
+
+/**
+ * Maintenance unavailable exception - thrown when service is in maintenance state
+ * Please try again later
+ */
+public class MaintenanceUnavailableException extends TronException {
+
+ public MaintenanceUnavailableException() {
+ super();
+ }
+
+ public MaintenanceUnavailableException(String message) {
+ super(message);
+ }
+
+ public MaintenanceUnavailableException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
\ No newline at end of file
diff --git a/common/src/main/java/org/tron/core/exception/P2pException.java b/common/src/main/java/org/tron/core/exception/P2pException.java
index 00d82e9fbf7..eae830627c2 100644
--- a/common/src/main/java/org/tron/core/exception/P2pException.java
+++ b/common/src/main/java/org/tron/core/exception/P2pException.java
@@ -52,6 +52,7 @@ public enum TypeEnum {
PROTOBUF_ERROR(14, "protobuf inconsistent"),
BLOCK_SIGN_ERROR(15, "block sign error"),
BLOCK_MERKLE_ERROR(16, "block merkle error"),
+ RATE_LIMIT_EXCEEDED(17, "rate limit exceeded"),
DEFAULT(100, "default exception");
diff --git a/common/src/main/java/org/tron/core/exception/TronError.java b/common/src/main/java/org/tron/core/exception/TronError.java
index 9d11d249476..f407c6dfe3c 100644
--- a/common/src/main/java/org/tron/core/exception/TronError.java
+++ b/common/src/main/java/org/tron/core/exception/TronError.java
@@ -47,7 +47,9 @@ public enum ErrCode {
LOG_LOAD(1),
WITNESS_INIT(1),
RATE_LIMITER_INIT(1),
- SOLID_NODE_INIT(0);
+ SOLID_NODE_INIT(0),
+ PARAMETER_INIT(1),
+ JDK_VERSION(1);
private final int code;
diff --git a/common/src/main/java/org/tron/core/exception/jsonrpc/JsonRpcExceedLimitException.java b/common/src/main/java/org/tron/core/exception/jsonrpc/JsonRpcExceedLimitException.java
new file mode 100644
index 00000000000..9e6f59d4636
--- /dev/null
+++ b/common/src/main/java/org/tron/core/exception/jsonrpc/JsonRpcExceedLimitException.java
@@ -0,0 +1,16 @@
+package org.tron.core.exception.jsonrpc;
+
+public class JsonRpcExceedLimitException extends JsonRpcException {
+
+ public JsonRpcExceedLimitException() {
+ super();
+ }
+
+ public JsonRpcExceedLimitException(String message) {
+ super(message);
+ }
+
+ public JsonRpcExceedLimitException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
\ No newline at end of file
diff --git a/common/src/main/java/org/tron/core/exception/jsonrpc/JsonRpcException.java b/common/src/main/java/org/tron/core/exception/jsonrpc/JsonRpcException.java
new file mode 100644
index 00000000000..7ed42d101d5
--- /dev/null
+++ b/common/src/main/java/org/tron/core/exception/jsonrpc/JsonRpcException.java
@@ -0,0 +1,32 @@
+package org.tron.core.exception.jsonrpc;
+
+import lombok.Getter;
+import org.tron.core.exception.TronException;
+
+@Getter
+public class JsonRpcException extends TronException {
+ private Object data = null;
+
+ public JsonRpcException() {
+ super();
+ report();
+ }
+
+ public JsonRpcException(String message, Object data) {
+ super(message);
+ this.data = data;
+ report();
+ }
+
+ public JsonRpcException(String message) {
+ super(message);
+ report();
+ }
+
+ public JsonRpcException(String message, Throwable cause) {
+ super(message, cause);
+ report();
+ }
+
+
+}
diff --git a/common/src/main/java/org/tron/core/exception/JsonRpcInternalException.java b/common/src/main/java/org/tron/core/exception/jsonrpc/JsonRpcInternalException.java
similarity index 53%
rename from common/src/main/java/org/tron/core/exception/JsonRpcInternalException.java
rename to common/src/main/java/org/tron/core/exception/jsonrpc/JsonRpcInternalException.java
index 12310e67747..904449866ae 100644
--- a/common/src/main/java/org/tron/core/exception/JsonRpcInternalException.java
+++ b/common/src/main/java/org/tron/core/exception/jsonrpc/JsonRpcInternalException.java
@@ -1,6 +1,6 @@
-package org.tron.core.exception;
+package org.tron.core.exception.jsonrpc;
-public class JsonRpcInternalException extends TronException {
+public class JsonRpcInternalException extends JsonRpcException {
public JsonRpcInternalException() {
super();
@@ -13,4 +13,8 @@ public JsonRpcInternalException(String message) {
public JsonRpcInternalException(String message, Throwable cause) {
super(message, cause);
}
+
+ public JsonRpcInternalException(String message, Object data) {
+ super(message, data);
+ }
}
\ No newline at end of file
diff --git a/common/src/main/java/org/tron/core/exception/JsonRpcInvalidParamsException.java b/common/src/main/java/org/tron/core/exception/jsonrpc/JsonRpcInvalidParamsException.java
similarity index 68%
rename from common/src/main/java/org/tron/core/exception/JsonRpcInvalidParamsException.java
rename to common/src/main/java/org/tron/core/exception/jsonrpc/JsonRpcInvalidParamsException.java
index adf205a309a..55ee15521e1 100644
--- a/common/src/main/java/org/tron/core/exception/JsonRpcInvalidParamsException.java
+++ b/common/src/main/java/org/tron/core/exception/jsonrpc/JsonRpcInvalidParamsException.java
@@ -1,6 +1,6 @@
-package org.tron.core.exception;
+package org.tron.core.exception.jsonrpc;
-public class JsonRpcInvalidParamsException extends TronException {
+public class JsonRpcInvalidParamsException extends JsonRpcException {
public JsonRpcInvalidParamsException() {
super();
diff --git a/common/src/main/java/org/tron/core/exception/JsonRpcInvalidRequestException.java b/common/src/main/java/org/tron/core/exception/jsonrpc/JsonRpcInvalidRequestException.java
similarity index 69%
rename from common/src/main/java/org/tron/core/exception/JsonRpcInvalidRequestException.java
rename to common/src/main/java/org/tron/core/exception/jsonrpc/JsonRpcInvalidRequestException.java
index 2689bff0cd2..32bd11a3ed9 100644
--- a/common/src/main/java/org/tron/core/exception/JsonRpcInvalidRequestException.java
+++ b/common/src/main/java/org/tron/core/exception/jsonrpc/JsonRpcInvalidRequestException.java
@@ -1,6 +1,6 @@
-package org.tron.core.exception;
+package org.tron.core.exception.jsonrpc;
-public class JsonRpcInvalidRequestException extends TronException {
+public class JsonRpcInvalidRequestException extends JsonRpcException {
public JsonRpcInvalidRequestException() {
super();
diff --git a/common/src/main/java/org/tron/core/exception/JsonRpcMethodNotFoundException.java b/common/src/main/java/org/tron/core/exception/jsonrpc/JsonRpcMethodNotFoundException.java
similarity index 68%
rename from common/src/main/java/org/tron/core/exception/JsonRpcMethodNotFoundException.java
rename to common/src/main/java/org/tron/core/exception/jsonrpc/JsonRpcMethodNotFoundException.java
index d8e18168d9d..406a51f45bd 100644
--- a/common/src/main/java/org/tron/core/exception/JsonRpcMethodNotFoundException.java
+++ b/common/src/main/java/org/tron/core/exception/jsonrpc/JsonRpcMethodNotFoundException.java
@@ -1,6 +1,6 @@
-package org.tron.core.exception;
+package org.tron.core.exception.jsonrpc;
-public class JsonRpcMethodNotFoundException extends TronException {
+public class JsonRpcMethodNotFoundException extends JsonRpcException {
public JsonRpcMethodNotFoundException() {
super();
diff --git a/common/src/main/java/org/tron/core/exception/JsonRpcTooManyResultException.java b/common/src/main/java/org/tron/core/exception/jsonrpc/JsonRpcTooManyResultException.java
similarity index 69%
rename from common/src/main/java/org/tron/core/exception/JsonRpcTooManyResultException.java
rename to common/src/main/java/org/tron/core/exception/jsonrpc/JsonRpcTooManyResultException.java
index e8e183d49c1..03bc089b1c7 100644
--- a/common/src/main/java/org/tron/core/exception/JsonRpcTooManyResultException.java
+++ b/common/src/main/java/org/tron/core/exception/jsonrpc/JsonRpcTooManyResultException.java
@@ -1,6 +1,6 @@
-package org.tron.core.exception;
+package org.tron.core.exception.jsonrpc;
-public class JsonRpcTooManyResultException extends TronException {
+public class JsonRpcTooManyResultException extends JsonRpcException {
public JsonRpcTooManyResultException() {
super();
diff --git a/common/src/main/java/org/tron/core/vm/config/VMConfig.java b/common/src/main/java/org/tron/core/vm/config/VMConfig.java
index 2bdbeb78b92..578827b2f8c 100644
--- a/common/src/main/java/org/tron/core/vm/config/VMConfig.java
+++ b/common/src/main/java/org/tron/core/vm/config/VMConfig.java
@@ -59,6 +59,8 @@ public class VMConfig {
private static boolean ALLOW_TVM_BLOB = false;
+ private static boolean ALLOW_TVM_SELFDESTRUCT_RESTRICTION = false;
+
private VMConfig() {
}
@@ -166,6 +168,10 @@ public static void initAllowTvmBlob(long allow) {
ALLOW_TVM_BLOB = allow == 1;
}
+ public static void initAllowTvmSelfdestructRestriction(long allow) {
+ ALLOW_TVM_SELFDESTRUCT_RESTRICTION = allow == 1;
+ }
+
public static boolean getEnergyLimitHardFork() {
return CommonParameter.ENERGY_LIMIT_HARD_FORK;
}
@@ -261,4 +267,8 @@ public static boolean disableJavaLangMath() {
public static boolean allowTvmBlob() {
return ALLOW_TVM_BLOB;
}
+
+ public static boolean allowTvmSelfdestructRestriction() {
+ return ALLOW_TVM_SELFDESTRUCT_RESTRICTION;
+ }
}
diff --git a/crypto/src/main/java/org/tron/common/crypto/ECKey.java b/crypto/src/main/java/org/tron/common/crypto/ECKey.java
index 5fe8b9ef359..0ead87cdc08 100644
--- a/crypto/src/main/java/org/tron/common/crypto/ECKey.java
+++ b/crypto/src/main/java/org/tron/common/crypto/ECKey.java
@@ -31,67 +31,41 @@
import java.security.interfaces.ECPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
-import java.util.Objects;
import javax.annotation.Nullable;
+import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
-import org.bouncycastle.asn1.sec.SECNamedCurves;
-import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.asn1.x9.X9IntegerConverter;
import org.bouncycastle.crypto.digests.SHA256Digest;
-import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
+import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.signers.ECDSASigner;
import org.bouncycastle.crypto.signers.HMacDSAKCalculator;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
-import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.spec.ECPrivateKeySpec;
import org.bouncycastle.math.ec.ECAlgorithms;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.encoders.Base64;
import org.bouncycastle.util.encoders.Hex;
+import org.tron.common.crypto.curveparams.CurveParams;
+import org.tron.common.crypto.curveparams.Secp256k1Params;
import org.tron.common.crypto.jce.ECKeyFactory;
import org.tron.common.crypto.jce.ECKeyPairGenerator;
import org.tron.common.crypto.jce.TronCastleProvider;
import org.tron.common.utils.BIUtil;
+import org.tron.common.utils.ByteArray;
import org.tron.common.utils.ByteUtil;
@Slf4j(topic = "crypto")
public class ECKey implements Serializable, SignInterface {
- /**
- * The parameters of the secp256k1 curve.
- */
- public static final ECDomainParameters CURVE;
- public static final ECParameterSpec CURVE_SPEC;
-
- /**
- * Equal to CURVE.getN().shiftRight(1), used for canonicalising the S value of a signature. ECDSA
- * signatures are mutable in the sense that for a given (R, S) pair, then both (R, S) and (R, N -
- * S mod N) are valid signatures. Canonical signatures are those where 1 <= S <= N/2
- *
- * See https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki
- * #Low_S_values_in_signatures
- */
+ @Getter
+ private final CurveParams curveParams;
- public static final BigInteger HALF_CURVE_ORDER;
- private static final BigInteger SECP256K1N =
- new BigInteger("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", 16);
- private static final SecureRandom secureRandom;
+ private static final SecureRandom secureRandom = new SecureRandom();
private static final long serialVersionUID = -728224901792295832L;
- static {
- // All clients must agree on the curve to use by agreement.
- X9ECParameters params = SECNamedCurves.getByName("secp256k1");
- CURVE = new ECDomainParameters(params.getCurve(), params.getG(),
- params.getN(), params.getH());
- CURVE_SPEC = new ECParameterSpec(params.getCurve(), params.getG(),
- params.getN(), params.getH());
- HALF_CURVE_ORDER = params.getN().shiftRight(1);
- secureRandom = new SecureRandom();
- }
-
protected final ECPoint pub;
// The two parts of the key. If "priv" is set, "pub" can always be
// calculated. If "pub" is set but not "priv", we
@@ -116,7 +90,11 @@ public class ECKey implements Serializable, SignInterface {
*
BouncyCastle will be used as the Java Security Provider
*/
public ECKey() {
- this(secureRandom);
+ this(secureRandom, Secp256k1Params.getInstance());
+ }
+
+ public ECKey(CurveParams curveParams) {
+ this(secureRandom, curveParams);
}
/**
@@ -125,9 +103,15 @@ public ECKey() {
*
All private key operations will use the provider.
*/
public ECKey(Provider provider, SecureRandom secureRandom) {
+ this(provider, secureRandom, Secp256k1Params.getInstance());
+ }
+
+ public ECKey(Provider provider, SecureRandom secureRandom, CurveParams curveParams) {
this.provider = provider;
+ this.curveParams = curveParams;
- final KeyPairGenerator keyPairGen = ECKeyPairGenerator.getInstance(provider, secureRandom);
+ final KeyPairGenerator keyPairGen = ECKeyPairGenerator.getInstance(provider, secureRandom,
+ curveParams);
final KeyPair keyPair = keyPairGen.generateKeyPair();
this.privKey = keyPair.getPrivate();
@@ -136,7 +120,7 @@ public ECKey(Provider provider, SecureRandom secureRandom) {
if (pubKey instanceof BCECPublicKey) {
pub = ((BCECPublicKey) pubKey).getQ();
} else if (pubKey instanceof ECPublicKey) {
- pub = extractPublicKey((ECPublicKey) pubKey);
+ pub = extractPublicKey((ECPublicKey) pubKey, curveParams);
} else {
throw new AssertionError(
"Expected Provider " + provider.getName()
@@ -152,7 +136,11 @@ public ECKey(Provider provider, SecureRandom secureRandom) {
* @param secureRandom -
*/
public ECKey(SecureRandom secureRandom) {
- this(TronCastleProvider.getInstance(), secureRandom);
+ this(secureRandom, Secp256k1Params.getInstance());
+ }
+
+ public ECKey(SecureRandom secureRandom, CurveParams curveParams) {
+ this(TronCastleProvider.getInstance(), secureRandom, curveParams);
}
/**
@@ -162,19 +150,31 @@ public ECKey(SecureRandom secureRandom) {
*/
public ECKey(byte[] key, boolean isPrivateKey) {
+ this(key, isPrivateKey, Secp256k1Params.getInstance());
+ }
+
+ public ECKey(byte[] key, boolean isPrivateKey, CurveParams curveParams) {
+ this.provider = TronCastleProvider.getInstance();
+ this.curveParams = curveParams;
+
if (isPrivateKey) {
BigInteger pk = new BigInteger(1, key);
- this.privKey = privateKeyFromBigInteger(pk);
- this.pub = CURVE.getG().multiply(pk);
+ this.privKey = privateKeyFromBigInteger(pk, curveParams);
+ this.pub = curveParams.getCurve().getG().multiply(pk);
} else {
this.privKey = null;
- this.pub = CURVE.getCurve().decodePoint(key);
+ this.pub = curveParams.getCurve().getCurve().decodePoint(key);
}
- this.provider = TronCastleProvider.getInstance();
}
public ECKey(Provider provider, @Nullable PrivateKey privKey, ECPoint pub) {
+ this(provider, privKey, pub, Secp256k1Params.getInstance());
+ }
+
+ public ECKey(Provider provider, @Nullable PrivateKey privKey, ECPoint pub,
+ CurveParams curveParams) {
this.provider = provider;
+ this.curveParams = curveParams;
if (privKey == null || isECPrivateKey(privKey)) {
this.privKey = privKey;
@@ -199,23 +199,29 @@ public ECKey(Provider provider, @Nullable PrivateKey privKey, ECPoint pub) {
* Security Provider
*/
public ECKey(@Nullable BigInteger priv, ECPoint pub) {
+ this(priv, pub, Secp256k1Params.getInstance());
+ }
+
+ public ECKey(@Nullable BigInteger priv, ECPoint pub, CurveParams curveParams) {
this(
TronCastleProvider.getInstance(),
- privateKeyFromBigInteger(priv),
- pub
+ privateKeyFromBigInteger(priv, curveParams),
+ pub,
+ curveParams
);
}
/* Convert a Java JCE ECPublicKey into a BouncyCastle ECPoint
*/
- private static ECPoint extractPublicKey(final ECPublicKey ecPublicKey) {
+ private static ECPoint extractPublicKey(final ECPublicKey ecPublicKey, CurveParams curveParams) {
final java.security.spec.ECPoint publicPointW = ecPublicKey.getW();
final BigInteger xCoord = publicPointW.getAffineX();
final BigInteger yCoord = publicPointW.getAffineY();
- return CURVE.getCurve().createPoint(xCoord, yCoord);
+ return curveParams.getCurve().getCurve().createPoint(xCoord, yCoord);
}
+
/* Test if a generic private key is an EC private key
*
* it is not sufficient to check that privKey is a subtype of ECPrivateKey
@@ -229,7 +235,7 @@ private static boolean isECPrivateKey(PrivateKey privKey) {
/* Convert a BigInteger into a PrivateKey object
*/
- private static PrivateKey privateKeyFromBigInteger(BigInteger priv) {
+ private static PrivateKey privateKeyFromBigInteger(BigInteger priv, CurveParams curveParams) {
if (priv == null) {
return null;
} else {
@@ -237,7 +243,7 @@ private static PrivateKey privateKeyFromBigInteger(BigInteger priv) {
return ECKeyFactory
.getInstance(TronCastleProvider.getInstance())
.generatePrivate(new ECPrivateKeySpec(priv,
- CURVE_SPEC));
+ curveParams.getCurveSpec()));
} catch (InvalidKeySpecException ex) {
throw new AssertionError("Assumed correct key spec statically");
}
@@ -253,7 +259,11 @@ private static PrivateKey privateKeyFromBigInteger(BigInteger priv) {
* @deprecated per-point compression property will be removed in Bouncy Castle
*/
public static ECPoint compressPoint(ECPoint uncompressed) {
- return CURVE.getCurve().decodePoint(uncompressed.getEncoded(true));
+ return compressPoint(uncompressed, Secp256k1Params.getInstance());
+ }
+
+ public static ECPoint compressPoint(ECPoint uncompressed, CurveParams curveParams) {
+ return curveParams.getCurve().getCurve().decodePoint(uncompressed.getEncoded(true));
}
/**
@@ -265,7 +275,11 @@ public static ECPoint compressPoint(ECPoint uncompressed) {
* @deprecated per-point compression property will be removed in Bouncy Castle
*/
public static ECPoint decompressPoint(ECPoint compressed) {
- return CURVE.getCurve().decodePoint(compressed.getEncoded(false));
+ return decompressPoint(compressed, Secp256k1Params.getInstance());
+ }
+
+ public static ECPoint decompressPoint(ECPoint compressed, CurveParams curveParams) {
+ return curveParams.getCurve().getCurve().decodePoint(compressed.getEncoded(false));
}
/**
@@ -275,7 +289,11 @@ public static ECPoint decompressPoint(ECPoint compressed) {
* @return -
*/
public static ECKey fromPrivate(BigInteger privKey) {
- return new ECKey(privKey, CURVE.getG().multiply(privKey));
+ return fromPrivate(privKey, Secp256k1Params.getInstance());
+ }
+
+ public static ECKey fromPrivate(BigInteger privKey, CurveParams curveParams) {
+ return new ECKey(privKey, curveParams.getCurve().getG().multiply(privKey), curveParams);
}
/**
@@ -285,52 +303,14 @@ public static ECKey fromPrivate(BigInteger privKey) {
* @return -
*/
public static ECKey fromPrivate(byte[] privKeyBytes) {
- if (Objects.isNull(privKeyBytes)) {
- return null;
- }
- return fromPrivate(new BigInteger(1, privKeyBytes));
- }
-
- /**
- * Creates an ECKey that simply trusts the caller to ensure that point is really the result of
- * multiplying the generator point by the private key. This is used to speed things up when you
- * know you have the right values already. The compression state of pub will be preserved.
- *
- * @param priv -
- * @param pub -
- * @return -
- */
- public static ECKey fromPrivateAndPrecalculatedPublic(BigInteger priv,
- ECPoint pub) {
- return new ECKey(priv, pub);
- }
-
- /**
- * Creates an ECKey that simply trusts the caller to ensure that point is really the result of
- * multiplying the generator point by the private key. This is used to speed things up when you
- * know you have the right values already. The compression state of the point will be preserved.
- *
- * @param priv -
- * @param pub -
- * @return -
- */
- public static ECKey fromPrivateAndPrecalculatedPublic(byte[] priv, byte[]
- pub) {
- check(priv != null, "Private key must not be null");
- check(pub != null, "Public key must not be null");
- return new ECKey(new BigInteger(1, priv), CURVE.getCurve()
- .decodePoint(pub));
+ return fromPrivate(privKeyBytes, Secp256k1Params.getInstance());
}
- /**
- * Creates an ECKey that cannot be used for signing, only verifying signatures, from the given
- * point. The compression state of pub will be preserved.
- *
- * @param pub -
- * @return -
- */
- public static ECKey fromPublicOnly(ECPoint pub) {
- return new ECKey(null, pub);
+ public static ECKey fromPrivate(byte[] privKeyBytes, CurveParams curveParams) {
+ if (ByteArray.isEmpty(privKeyBytes)) {
+ return null;
+ }
+ return fromPrivate(new BigInteger(1, privKeyBytes), curveParams);
}
/**
@@ -341,7 +321,11 @@ public static ECKey fromPublicOnly(ECPoint pub) {
* @return -
*/
public static ECKey fromPublicOnly(byte[] pub) {
- return new ECKey(null, CURVE.getCurve().decodePoint(pub));
+ return fromPublicOnly(pub, Secp256k1Params.getInstance());
+ }
+
+ public static ECKey fromPublicOnly(byte[] pub, CurveParams curveParams) {
+ return new ECKey(null, curveParams.getCurve().getCurve().decodePoint(pub));
}
/**
@@ -354,7 +338,12 @@ public static ECKey fromPublicOnly(byte[] pub) {
*/
public static byte[] publicKeyFromPrivate(BigInteger privKey, boolean
compressed) {
- ECPoint point = CURVE.getG().multiply(privKey);
+ return publicKeyFromPrivate(privKey, compressed, Secp256k1Params.getInstance());
+ }
+
+ public static byte[] publicKeyFromPrivate(BigInteger privKey, boolean
+ compressed, CurveParams curveParams) {
+ ECPoint point = curveParams.getCurve().getG().multiply(privKey);
return point.getEncoded(compressed);
}
@@ -384,7 +373,7 @@ public static ECKey fromNodeId(byte[] nodeId) {
}
public static byte[] signatureToKeyBytes(byte[] messageHash, String
- signatureBase64) throws SignatureException {
+ signatureBase64, CurveParams curveParams) throws SignatureException {
byte[] signatureEncoded;
try {
signatureEncoded = Base64.decode(signatureBase64);
@@ -404,11 +393,12 @@ public static byte[] signatureToKeyBytes(byte[] messageHash, String
ECDSASignature.fromComponents(
Arrays.copyOfRange(signatureEncoded, 1, 33),
Arrays.copyOfRange(signatureEncoded, 33, 65),
- (byte) (signatureEncoded[0] & 0xFF)));
+ (byte) (signatureEncoded[0] & 0xFF)),
+ curveParams);
}
public static byte[] signatureToKeyBytes(byte[] messageHash,
- ECDSASignature sig) throws SignatureException {
+ ECDSASignature sig, CurveParams curveParams) throws SignatureException {
check(messageHash.length == 32, "messageHash argument has length " +
messageHash.length);
int header = sig.v;
@@ -423,8 +413,7 @@ public static byte[] signatureToKeyBytes(byte[] messageHash,
header -= 4;
}
int recId = header - 27;
- byte[] key = ECKey.recoverPubBytesFromSignature(recId, sig,
- messageHash);
+ byte[] key = ECKey.recoverPubBytesFromSignature(recId, sig, messageHash, curveParams);
if (key == null) {
throw new SignatureException("Could not recover public key from " +
"signature");
@@ -441,8 +430,12 @@ public static byte[] signatureToKeyBytes(byte[] messageHash,
*/
public static byte[] signatureToAddress(byte[] messageHash, String
signatureBase64) throws SignatureException {
- return Hash.computeAddress(signatureToKeyBytes(messageHash,
- signatureBase64));
+ return signatureToAddress(messageHash, signatureBase64, Secp256k1Params.getInstance());
+ }
+
+ public static byte[] signatureToAddress(byte[] messageHash, String
+ signatureBase64, CurveParams curveParams) throws SignatureException {
+ return Hash.computeAddress(signatureToKeyBytes(messageHash, signatureBase64, curveParams));
}
/**
@@ -455,7 +448,13 @@ public static byte[] signatureToAddress(byte[] messageHash, String
public static byte[] signatureToAddress(byte[] messageHash,
ECDSASignature sig) throws
SignatureException {
- return Hash.computeAddress(signatureToKeyBytes(messageHash, sig));
+ return signatureToAddress(messageHash, sig, Secp256k1Params.getInstance());
+ }
+
+ public static byte[] signatureToAddress(byte[] messageHash,
+ ECDSASignature sig, CurveParams curveParams) throws
+ SignatureException {
+ return Hash.computeAddress(signatureToKeyBytes(messageHash, sig, curveParams));
}
/**
@@ -467,9 +466,13 @@ public static byte[] signatureToAddress(byte[] messageHash,
*/
public static ECKey signatureToKey(byte[] messageHash, String
signatureBase64) throws SignatureException {
- final byte[] keyBytes = signatureToKeyBytes(messageHash,
- signatureBase64);
- return ECKey.fromPublicOnly(keyBytes);
+ return signatureToKey(messageHash, signatureBase64, Secp256k1Params.getInstance());
+ }
+
+ public static ECKey signatureToKey(byte[] messageHash, String
+ signatureBase64, CurveParams curveParams) throws SignatureException {
+ final byte[] keyBytes = signatureToKeyBytes(messageHash, signatureBase64, curveParams);
+ return ECKey.fromPublicOnly(keyBytes, curveParams);
}
/**
@@ -516,6 +519,12 @@ public static boolean isPubKeyCanonical(byte[] pubkey) {
@Nullable
public static byte[] recoverPubBytesFromSignature(int recId,
ECDSASignature sig, byte[] messageHash) {
+ return recoverPubBytesFromSignature(recId, sig, messageHash, Secp256k1Params.getInstance());
+ }
+
+ @Nullable
+ public static byte[] recoverPubBytesFromSignature(int recId,
+ ECDSASignature sig, byte[] messageHash, CurveParams curveParams) {
check(recId >= 0, "recId must be positive");
check(sig.r.signum() >= 0, "r must be positive");
check(sig.s.signum() >= 0, "s must be positive");
@@ -523,7 +532,7 @@ public static byte[] recoverPubBytesFromSignature(int recId,
// 1.0 For j from 0 to h (h == recId here and the loop is outside
// this function)
// 1.1 Let x = r + jn
- BigInteger n = CURVE.getN(); // Curve order.
+ BigInteger n = curveParams.getCurve().getN(); // Curve order.
BigInteger i = BigInteger.valueOf((long) recId / 2);
BigInteger x = sig.r.add(i.multiply(n));
// 1.2. Convert the integer x to an octet string X of length mlen
@@ -538,7 +547,7 @@ public static byte[] recoverPubBytesFromSignature(int recId,
//
// More concisely, what these points mean is to use X as a compressed
// public key.
- ECCurve.Fp curve = (ECCurve.Fp) CURVE.getCurve();
+ ECCurve.Fp curve = (ECCurve.Fp) curveParams.getCurve().getCurve();
BigInteger prime = curve.getQ(); // Bouncy Castle is not consistent
// about the letter it uses for the prime.
if (x.compareTo(prime) >= 0) {
@@ -549,7 +558,7 @@ public static byte[] recoverPubBytesFromSignature(int recId,
// Compressed allKeys require you to know an extra bit of data about the
// y-coord as there are two possibilities.
// So it's encoded in the recId.
- ECPoint R = decompressKey(x, (recId & 1) == 1);
+ ECPoint R = decompressKey(x, (recId & 1) == 1, curveParams);
// 1.4. If nR != point at infinity, then do another iteration of
// Step 1 (callers responsibility).
if (!R.multiply(n).isInfinity()) {
@@ -579,7 +588,7 @@ public static byte[] recoverPubBytesFromSignature(int recId,
BigInteger rInv = sig.r.modInverse(n);
BigInteger srInv = rInv.multiply(sig.s).mod(n);
BigInteger eInvrInv = rInv.multiply(eInv).mod(n);
- ECPoint.Fp q = (ECPoint.Fp) ECAlgorithms.sumOfTwoMultiplies(CURVE
+ ECPoint.Fp q = (ECPoint.Fp) ECAlgorithms.sumOfTwoMultiplies(curveParams.getCurve()
.getG(), eInvrInv, R, srInv);
return q.getEncoded(/* compressed */ false);
}
@@ -625,15 +634,15 @@ public static ECKey recoverFromSignature(int recId, ECDSASignature sig,
*
* @param xBN -
* @param yBit -
+ * @param curveParams -
* @return -
*/
-
- private static ECPoint decompressKey(BigInteger xBN, boolean yBit) {
+ private static ECPoint decompressKey(BigInteger xBN, boolean yBit, CurveParams curveParams) {
X9IntegerConverter x9 = new X9IntegerConverter();
- byte[] compEnc = x9.integerToBytes(xBN, 1 + x9.getByteLength(CURVE
+ byte[] compEnc = x9.integerToBytes(xBN, 1 + x9.getByteLength(curveParams.getCurve()
.getCurve()));
compEnc[0] = (byte) (yBit ? 0x03 : 0x02);
- return CURVE.getCurve().decodePoint(compEnc);
+ return curveParams.getCurve().getCurve().decodePoint(compEnc);
}
private static void check(boolean test, String message) {
@@ -679,6 +688,7 @@ public String signHash(byte[] hash) {
return sign(hash).toBase64();
}
+
public byte[] Base64toBytes(String signature) {
byte[] signData = Base64.decode(signature);
byte first = (byte) (signData[0] - 27);
@@ -776,14 +786,13 @@ public ECDSASignature doSign(byte[] input) {
throw new MissingPrivateKeyException();
}
if (privKey instanceof BCECPrivateKey) {
- ECDSASigner signer = new ECDSASigner(new HMacDSAKCalculator(new
- SHA256Digest()));
+ ECDSASigner signer = new ECDSASigner(new HMacDSAKCalculator(new SHA256Digest()));
ECPrivateKeyParameters privKeyParams = new ECPrivateKeyParameters
- (((BCECPrivateKey) privKey).getD(), CURVE);
+ (((BCECPrivateKey) privKey).getD(), this.curveParams.getCurve());
signer.init(true, privKeyParams);
BigInteger[] components = signer.generateSignature(input);
return new ECDSASignature(components[0], components[1])
- .toCanonicalised();
+ .toCanonicalised(this.curveParams);
} else {
throw new RuntimeException("ECKey signing error");
}
@@ -803,7 +812,7 @@ public ECDSASignature sign(byte[] messageHash) {
int recId = -1;
byte[] thisKey = this.pub.getEncoded(/* compressed */ false);
for (int i = 0; i < 4; i++) {
- byte[] k = ECKey.recoverPubBytesFromSignature(i, sig, messageHash);
+ byte[] k = ECKey.recoverPubBytesFromSignature(i, sig, messageHash, this.curveParams);
if (k != null && Arrays.equals(k, thisKey)) {
recId = i;
break;
@@ -817,6 +826,37 @@ public ECDSASignature sign(byte[] messageHash) {
return sig;
}
+ public static boolean verify(CurveParams curveParams, byte[] hash, ECDSASignature sig, byte[]
+ pubKey) {
+ check(hash != null && hash.length == 32, "Hash must be 32 bytes");
+ check(sig.r.signum() > 0, "s must be positive");
+ check(sig.r.compareTo(curveParams.getN()) < 0, "r is too large");
+ check(sig.s.signum() > 0, "s must be positive");
+ check(sig.s.compareTo(curveParams.getN()) < 0, "s is too large");
+ check(pubKey != null && pubKey.length > 0, "Pubkey must be non-zero bytes");
+
+ ECPoint pubPoint;
+ try {
+ pubPoint = curveParams.getCurve().getCurve().decodePoint(pubKey);
+ } catch (Exception e) {
+ logger.warn("Invalid pubkey bytes", e);
+ return false;
+ }
+ if (pubPoint.isInfinity()) {
+ return false;
+ }
+
+ final ECDSASigner signer = new ECDSASigner();
+ final ECPublicKeyParameters params = new ECPublicKeyParameters(pubPoint,
+ curveParams.getCurve());
+ signer.init(false, params);
+ try {
+ return signer.verifySignature(hash, sig.r, sig.s);
+ } catch (Exception e) {
+ logger.warn("Could not verify signature", e);
+ return false;
+ }
+ }
/**
* Returns true if this pubkey is canonical, i.e. the correct length taking into account
@@ -921,6 +961,11 @@ public static ECDSASignature fromComponents(byte[] r, byte[] s, byte
public static boolean validateComponents(BigInteger r, BigInteger s,
byte v) {
+ return validateComponents(r, s, v, Secp256k1Params.getInstance());
+ }
+
+ public static boolean validateComponents(BigInteger r, BigInteger s,
+ byte v, CurveParams curveParams) {
if (v != 27 && v != 28) {
return false;
@@ -933,10 +978,10 @@ public static boolean validateComponents(BigInteger r, BigInteger s,
return false;
}
- if (!BIUtil.isLessThan(r, SECP256K1N)) {
+ if (!BIUtil.isLessThan(r, curveParams.getN())) {
return false;
}
- return BIUtil.isLessThan(s, SECP256K1N);
+ return BIUtil.isLessThan(s, curveParams.getN());
}
@@ -944,8 +989,8 @@ public boolean validateComponents() {
return validateComponents(r, s, v);
}
- public ECDSASignature toCanonicalised() {
- if (s.compareTo(HALF_CURVE_ORDER) > 0) {
+ public ECDSASignature toCanonicalised(CurveParams curveParams) {
+ if (s.compareTo(curveParams.getHalfCurveOrder()) > 0) {
// The order of the curve is the number of valid points that
// exist on that curve. If S is in the upper
// half of the number of valid points, then bring it back to
@@ -955,7 +1000,7 @@ public ECDSASignature toCanonicalised() {
// are valid solutions.
// 10 - 8 == 2, giving us always the latter solution,
// which is canonical.
- return new ECDSASignature(r, CURVE.getN().subtract(s));
+ return new ECDSASignature(r, curveParams.getN().subtract(s));
} else {
return this;
}
diff --git a/crypto/src/main/java/org/tron/common/crypto/Rsv.java b/crypto/src/main/java/org/tron/common/crypto/Rsv.java
new file mode 100644
index 00000000000..15c8498e836
--- /dev/null
+++ b/crypto/src/main/java/org/tron/common/crypto/Rsv.java
@@ -0,0 +1,26 @@
+package org.tron.common.crypto;
+
+
+import java.util.Arrays;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@Getter
+@AllArgsConstructor
+public class Rsv {
+
+ private final byte[] r;
+ private final byte[] s;
+ private final byte v;
+
+
+ public static Rsv fromSignature(byte[] sign) {
+ byte[] r = Arrays.copyOfRange(sign, 0, 32);
+ byte[] s = Arrays.copyOfRange(sign, 32, 64);
+ byte v = sign[64];
+ if (v < 27) {
+ v += 27; //revId -> v
+ }
+ return new Rsv(r, s, v);
+ }
+}
diff --git a/crypto/src/main/java/org/tron/common/crypto/curveparams/CurveParams.java b/crypto/src/main/java/org/tron/common/crypto/curveparams/CurveParams.java
new file mode 100644
index 00000000000..87fda31966d
--- /dev/null
+++ b/crypto/src/main/java/org/tron/common/crypto/curveparams/CurveParams.java
@@ -0,0 +1,27 @@
+package org.tron.common.crypto.curveparams;
+
+import java.math.BigInteger;
+import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.jce.spec.ECParameterSpec;
+
+public interface CurveParams {
+
+ String getCurveName();
+
+ ECDomainParameters getCurve();
+
+ ECParameterSpec getCurveSpec();
+
+ BigInteger getN();
+
+ /**
+ * Equal to CURVE.getN().shiftRight(1), used for canonicalising the S value of a signature. ECDSA
+ * signatures are mutable in the sense that for a given (R, S) pair, then both (R, S) and (R, N -
+ * S mod N) are valid signatures. Canonical signatures are those where 1 <= S <= N/2
+ *
+ *
See https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki
+ * #Low_S_values_in_signatures
+ */
+ BigInteger getHalfCurveOrder();
+}
+
diff --git a/crypto/src/main/java/org/tron/common/crypto/curveparams/Secp256k1Params.java b/crypto/src/main/java/org/tron/common/crypto/curveparams/Secp256k1Params.java
new file mode 100644
index 00000000000..54391bb0b1d
--- /dev/null
+++ b/crypto/src/main/java/org/tron/common/crypto/curveparams/Secp256k1Params.java
@@ -0,0 +1,65 @@
+package org.tron.common.crypto.curveparams;
+
+import java.math.BigInteger;
+import org.bouncycastle.asn1.sec.SECNamedCurves;
+import org.bouncycastle.asn1.x9.X9ECParameters;
+import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.jce.spec.ECParameterSpec;
+
+public class Secp256k1Params implements CurveParams {
+
+ private static final String CURVE_NAME = "secp256k1";
+ public static final ECDomainParameters CURVE;
+ public static final ECParameterSpec CURVE_SPEC;
+ public static final BigInteger HALF_CURVE_ORDER;
+
+ private static final Secp256k1Params INSTANCE = new Secp256k1Params();
+
+ static {
+ try {
+ X9ECParameters params = SECNamedCurves.getByName(CURVE_NAME);
+ if (params == null) {
+ throw new IllegalStateException("Failed to get secp256k1 parameters from SECNamedCurves");
+ }
+ CURVE = new ECDomainParameters(params.getCurve(), params.getG(),
+ params.getN(), params.getH());
+ CURVE_SPEC = new ECParameterSpec(params.getCurve(), params.getG(),
+ params.getN(), params.getH());
+ HALF_CURVE_ORDER = params.getN().shiftRight(1);
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to initialize secp256k1 curve parameters", e);
+ }
+ }
+
+ private Secp256k1Params() {
+ }
+
+ public static Secp256k1Params getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public String getCurveName() {
+ return CURVE_NAME;
+ }
+
+ @Override
+ public ECDomainParameters getCurve() {
+ return CURVE;
+ }
+
+ @Override
+ public ECParameterSpec getCurveSpec() {
+ return CURVE_SPEC;
+ }
+
+ @Override
+ public BigInteger getN() {
+ return CURVE.getN();
+ }
+
+ @Override
+ public BigInteger getHalfCurveOrder() {
+ return HALF_CURVE_ORDER;
+ }
+}
diff --git a/crypto/src/main/java/org/tron/common/crypto/curveparams/Secp256r1Params.java b/crypto/src/main/java/org/tron/common/crypto/curveparams/Secp256r1Params.java
new file mode 100644
index 00000000000..2b120e9acb0
--- /dev/null
+++ b/crypto/src/main/java/org/tron/common/crypto/curveparams/Secp256r1Params.java
@@ -0,0 +1,65 @@
+package org.tron.common.crypto.curveparams;
+
+import java.math.BigInteger;
+import org.bouncycastle.asn1.sec.SECNamedCurves;
+import org.bouncycastle.asn1.x9.X9ECParameters;
+import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.jce.spec.ECParameterSpec;
+
+public class Secp256r1Params implements CurveParams {
+
+ private static final String CURVE_NAME = "secp256r1";
+ public static final ECDomainParameters CURVE;
+ public static final ECParameterSpec CURVE_SPEC;
+ public static final BigInteger HALF_CURVE_ORDER;
+
+ private static final Secp256r1Params INSTANCE = new Secp256r1Params();
+
+ static {
+ try {
+ X9ECParameters params = SECNamedCurves.getByName(CURVE_NAME);
+ if (params == null) {
+ throw new IllegalStateException("Failed to get secp256r1 parameters from SECNamedCurves");
+ }
+ CURVE = new ECDomainParameters(params.getCurve(), params.getG(),
+ params.getN(), params.getH());
+ CURVE_SPEC = new ECParameterSpec(params.getCurve(), params.getG(),
+ params.getN(), params.getH());
+ HALF_CURVE_ORDER = params.getN().shiftRight(1);
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to initialize secp256r1 curve parameters", e);
+ }
+ }
+
+ private Secp256r1Params() {
+ }
+
+ public static Secp256r1Params getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public String getCurveName() {
+ return CURVE_NAME;
+ }
+
+ @Override
+ public ECDomainParameters getCurve() {
+ return CURVE;
+ }
+
+ @Override
+ public ECParameterSpec getCurveSpec() {
+ return CURVE_SPEC;
+ }
+
+ @Override
+ public BigInteger getN() {
+ return CURVE.getN();
+ }
+
+ @Override
+ public BigInteger getHalfCurveOrder() {
+ return HALF_CURVE_ORDER;
+ }
+}
diff --git a/crypto/src/main/java/org/tron/common/crypto/jce/ECKeyPairGenerator.java b/crypto/src/main/java/org/tron/common/crypto/jce/ECKeyPairGenerator.java
index 61a5b90faec..a949ecc4195 100644
--- a/crypto/src/main/java/org/tron/common/crypto/jce/ECKeyPairGenerator.java
+++ b/crypto/src/main/java/org/tron/common/crypto/jce/ECKeyPairGenerator.java
@@ -19,18 +19,16 @@
package org.tron.common.crypto.jce;
import java.security.InvalidAlgorithmParameterException;
-import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Provider;
import java.security.SecureRandom;
-import java.security.spec.ECGenParameterSpec;
+import org.tron.common.crypto.curveparams.CurveParams;
public final class ECKeyPairGenerator {
public static final String ALGORITHM = "EC";
- public static final String CURVE_NAME = "secp256k1";
private static final String algorithmAssertionMsg =
"Assumed JRE supports EC key pair generation";
@@ -38,22 +36,16 @@ public final class ECKeyPairGenerator {
private static final String keySpecAssertionMsg =
"Assumed correct key spec statically";
- private static final ECGenParameterSpec SECP256K1_CURVE
- = new ECGenParameterSpec(CURVE_NAME);
-
private ECKeyPairGenerator() {
}
- public static KeyPair generateKeyPair() {
- return Holder.INSTANCE.generateKeyPair();
- }
-
public static KeyPairGenerator getInstance(final String provider, final
- SecureRandom random) throws NoSuchProviderException {
+ SecureRandom random, CurveParams curveParams) throws NoSuchProviderException {
try {
final KeyPairGenerator gen = KeyPairGenerator.getInstance
(ALGORITHM, provider);
- gen.initialize(SECP256K1_CURVE, random);
+
+ gen.initialize(curveParams.getCurveSpec(), random);
return gen;
} catch (NoSuchAlgorithmException ex) {
throw new AssertionError(algorithmAssertionMsg, ex);
@@ -63,11 +55,11 @@ public static KeyPairGenerator getInstance(final String provider, final
}
public static KeyPairGenerator getInstance(final Provider provider, final
- SecureRandom random) {
+ SecureRandom random, CurveParams curveParams) {
try {
final KeyPairGenerator gen = KeyPairGenerator.getInstance
(ALGORITHM, provider);
- gen.initialize(SECP256K1_CURVE, random);
+ gen.initialize(curveParams.getCurveSpec(), random);
return gen;
} catch (NoSuchAlgorithmException ex) {
throw new AssertionError(algorithmAssertionMsg, ex);
@@ -75,20 +67,4 @@ public static KeyPairGenerator getInstance(final Provider provider, final
throw new AssertionError(keySpecAssertionMsg, ex);
}
}
-
- private static class Holder {
-
- private static final KeyPairGenerator INSTANCE;
-
- static {
- try {
- INSTANCE = KeyPairGenerator.getInstance(ALGORITHM);
- INSTANCE.initialize(SECP256K1_CURVE);
- } catch (NoSuchAlgorithmException ex) {
- throw new AssertionError(algorithmAssertionMsg, ex);
- } catch (InvalidAlgorithmParameterException ex) {
- throw new AssertionError(keySpecAssertionMsg, ex);
- }
- }
- }
}
diff --git a/crypto/src/main/java/org/tron/common/crypto/sm2/SM2.java b/crypto/src/main/java/org/tron/common/crypto/sm2/SM2.java
index c6aebba385a..b1d349efad3 100644
--- a/crypto/src/main/java/org/tron/common/crypto/sm2/SM2.java
+++ b/crypto/src/main/java/org/tron/common/crypto/sm2/SM2.java
@@ -41,6 +41,7 @@
import org.tron.common.crypto.SignatureInterface;
import org.tron.common.crypto.jce.ECKeyFactory;
import org.tron.common.crypto.jce.TronCastleProvider;
+import org.tron.common.utils.ByteArray;
import org.tron.common.utils.ByteUtil;
/**
@@ -247,7 +248,7 @@ public static SM2 fromPrivate(BigInteger privKey) {
* @return -
*/
public static SM2 fromPrivate(byte[] privKeyBytes) {
- if (Objects.isNull(privKeyBytes)) {
+ if (ByteArray.isEmpty(privKeyBytes)) {
return null;
}
return fromPrivate(new BigInteger(1, privKeyBytes));
diff --git a/crypto/src/main/java/org/tron/common/crypto/zksnark/Fp12.java b/crypto/src/main/java/org/tron/common/crypto/zksnark/Fp12.java
index 26ea708fbe4..f144656b0ba 100644
--- a/crypto/src/main/java/org/tron/common/crypto/zksnark/Fp12.java
+++ b/crypto/src/main/java/org/tron/common/crypto/zksnark/Fp12.java
@@ -356,7 +356,7 @@ public boolean equals(Object o) {
@Override
public int hashCode() {
- return new Integer(a.hashCode() + b.hashCode()).hashCode();
+ return a.hashCode() + b.hashCode();
}
@Override
diff --git a/crypto/src/main/java/org/tron/common/crypto/zksnark/Fp2.java b/crypto/src/main/java/org/tron/common/crypto/zksnark/Fp2.java
index ba2a1ceb477..162a5b13b30 100644
--- a/crypto/src/main/java/org/tron/common/crypto/zksnark/Fp2.java
+++ b/crypto/src/main/java/org/tron/common/crypto/zksnark/Fp2.java
@@ -164,7 +164,7 @@ public boolean equals(Object o) {
@Override
public int hashCode() {
- return new Integer(a.hashCode() + b.hashCode()).hashCode();
+ return a.hashCode() + b.hashCode();
}
Fp2 frobeniusMap(int power) {
diff --git a/crypto/src/main/java/org/tron/common/crypto/zksnark/Fp6.java b/crypto/src/main/java/org/tron/common/crypto/zksnark/Fp6.java
index 0636cc334f1..fb863bb6b34 100644
--- a/crypto/src/main/java/org/tron/common/crypto/zksnark/Fp6.java
+++ b/crypto/src/main/java/org/tron/common/crypto/zksnark/Fp6.java
@@ -246,6 +246,6 @@ public boolean equals(Object o) {
@Override
public int hashCode() {
- return new Integer(a.hashCode() + b.hashCode() + c.hashCode()).hashCode();
+ return a.hashCode() + b.hashCode() + c.hashCode();
}
}
diff --git a/docker/arm64/Dockerfile b/docker/arm64/Dockerfile
new file mode 100644
index 00000000000..6435faf7ead
--- /dev/null
+++ b/docker/arm64/Dockerfile
@@ -0,0 +1,33 @@
+FROM arm64v8/eclipse-temurin:17
+
+ENV TMP_DIR="/tron-build"
+ENV BASE_DIR="/java-tron"
+
+RUN set -o errexit -o nounset \
+ && apt-get update \
+ && apt-get -y install git p7zip-full wget libtcmalloc-minimal4 \
+ && echo "git clone" \
+ && mkdir -p $TMP_DIR \
+ && cd $TMP_DIR \
+ && git clone https://github.com/tronprotocol/java-tron.git \
+ && cd java-tron \
+ && git checkout master \
+ && ./gradlew clean build -x test -x check --no-daemon \
+ && cd build/distributions \
+ && 7za x -y java-tron-1.0.0.zip \
+ && mv java-tron-1.0.0 $BASE_DIR \
+ && rm -rf $TMP_DIR \
+ && rm -rf ~/.gradle \
+ && apt-get clean \
+ && rm -rf /var/lib/apt/lists/*
+
+ENV LD_PRELOAD="/usr/lib/aarch64-linux-gnu/libtcmalloc_minimal.so.4"
+ENV TCMALLOC_RELEASE_RATE=10
+
+RUN wget -P $BASE_DIR/config https://raw.githubusercontent.com/tronprotocol/tron-deployment/master/main_net_config.conf
+
+COPY docker-entrypoint.sh $BASE_DIR/bin
+
+WORKDIR $BASE_DIR
+
+ENTRYPOINT ["./bin/docker-entrypoint.sh"]
\ No newline at end of file
diff --git a/framework/build.gradle b/framework/build.gradle
index 0f04685f2d8..59d070e066d 100644
--- a/framework/build.gradle
+++ b/framework/build.gradle
@@ -38,15 +38,11 @@ dependencies {
//local libraries
implementation fileTree(dir: 'libs', include: '*.jar')
// end local libraries
- testImplementation group: 'org.hamcrest', name: 'hamcrest-junit', version: '1.0.0.1'
-
- implementation group: 'com.google.inject', name: 'guice', version: '4.1.0'
implementation group: 'io.dropwizard.metrics', name: 'metrics-core', version: '3.1.2'
implementation group: 'com.github.davidb', name: 'metrics-influxdb', version: '0.8.2'
- implementation group: 'com.carrotsearch', name: 'java-sizeof', version: '0.0.5'
// http
- implementation 'org.eclipse.jetty:jetty-server:9.4.53.v20231009'
- implementation 'org.eclipse.jetty:jetty-servlet:9.4.53.v20231009'
+ implementation 'org.eclipse.jetty:jetty-server:9.4.57.v20241219'
+ implementation 'org.eclipse.jetty:jetty-servlet:9.4.57.v20241219'
implementation 'com.alibaba:fastjson:1.2.83'
// end http
@@ -56,14 +52,11 @@ dependencies {
// https://mvnrepository.com/artifact/javax.portlet/portlet-api
compileOnly group: 'javax.portlet', name: 'portlet-api', version: '3.0.1'
- implementation "io.vavr:vavr:0.9.2"
implementation (group: 'org.pf4j', name: 'pf4j', version: '3.10.0') {
exclude group: "org.slf4j", module: "slf4j-api"
}
- testImplementation group: 'org.springframework', name: 'spring-test', version: '5.2.0.RELEASE'
- testImplementation group: 'org.springframework', name: 'spring-web', version: '5.2.0.RELEASE'
-
+ testImplementation group: 'org.springframework', name: 'spring-test', version: "${springVersion}"
implementation group: 'org.zeromq', name: 'jeromq', version: '0.5.3'
api project(":chainbase")
api project(":protocol")
@@ -123,6 +116,17 @@ test {
destinationFile = file("$buildDir/jacoco/jacocoTest.exec")
classDumpDir = file("$buildDir/jacoco/classpathdumps")
}
+ if (rootProject.archInfo.isArm64) {
+ exclude { element ->
+ element.file.name.toLowerCase().contains('leveldb')
+ }
+ filter {
+ excludeTestsMatching '*.*leveldb*'
+ excludeTestsMatching '*.*Leveldb*'
+ excludeTestsMatching '*.*LevelDB*'
+ excludeTestsMatching '*.*LevelDb*'
+ }
+ }
if (isWindows()) {
exclude '**/ShieldedTransferActuatorTest.class'
exclude '**/BackupDbUtilTest.class'
@@ -134,6 +138,7 @@ test {
}
maxHeapSize = "1024m"
doFirst {
+ // Restart the JVM after every 100 tests to avoid memory leaks and ensure test isolation
forkEvery = 100
jvmArgs "-XX:MetaspaceSize=128m","-XX:MaxMetaspaceSize=256m", "-XX:+UseG1GC"
}
@@ -158,7 +163,7 @@ def binaryRelease(taskName, jarName, mainClass) {
// explicit_dependency
dependsOn (project(':actuator').jar, project(':consensus').jar, project(':chainbase').jar,
- project(':crypto').jar, project(':common').jar, project(':protocol').jar)
+ project(':crypto').jar, project(':common').jar, project(':protocol').jar, project(':platform').jar)
from {
configurations.runtimeClasspath.collect {
@@ -204,8 +209,7 @@ def createScript(project, mainClass, name) {
}
}
}
-
-applicationDistribution.from("../gradle/java-tron.vmoptions") {
+applicationDistribution.from(rootProject.archInfo.VMOptions) {
into "bin"
}
//distZip {
@@ -219,35 +223,12 @@ startScripts.enabled = false
run.enabled = false
tasks.distTar.enabled = false
-createScript(project, 'org.tron.program.SolidityNode', 'SolidityNode')
createScript(project, 'org.tron.program.FullNode', 'FullNode')
-createScript(project, 'org.tron.program.KeystoreFactory', 'KeystoreFactory')
-createScript(project, 'org.tron.program.DBConvert', 'DBConvert')
-
def releaseBinary = hasProperty('binaryRelease') ? getProperty('binaryRelease') : 'true'
-def skipSolidity = hasProperty('skipSolidity') ? true : false
-def skipKeystore = hasProperty('skipKeystore') ? true : false
-def skipConvert = hasProperty('skipConvert') ? true : false
-def skipAll = hasProperty('skipAll') ? true : false
if (releaseBinary == 'true') {
artifacts {
archives(binaryRelease('buildFullNodeJar', 'FullNode', 'org.tron.program.FullNode'))
}
- if (!skipAll) {
- if (!skipSolidity) {
- artifacts {
- archives(binaryRelease('buildSolidityNodeJar', 'SolidityNode', 'org.tron.program.SolidityNode'))}
- }
- if (!skipKeystore) {
- artifacts {
- archives(binaryRelease('buildKeystoreFactoryJar', 'KeystoreFactory', 'org.tron.program.KeystoreFactory'))}
- }
- if (!skipConvert) {
- artifacts {
- archives(binaryRelease('buildDBConvertJar', 'DBConvert', 'org.tron.program.DBConvert'))}
- }
- }
-
}
task copyToParent(type: Copy) {
diff --git a/framework/src/main/java/org/tron/common/application/RpcService.java b/framework/src/main/java/org/tron/common/application/RpcService.java
index 2d118806e2c..c398b71ae41 100644
--- a/framework/src/main/java/org/tron/common/application/RpcService.java
+++ b/framework/src/main/java/org/tron/common/application/RpcService.java
@@ -19,6 +19,7 @@
import io.grpc.netty.NettyServerBuilder;
import io.grpc.protobuf.services.ProtoReflectionService;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@@ -34,6 +35,7 @@
public abstract class RpcService extends AbstractService {
private Server apiServer;
+ private ExecutorService executorService;
protected String executorName;
@Autowired
@@ -58,7 +60,24 @@ public void innerStart() throws Exception {
@Override
public void innerStop() throws Exception {
if (this.apiServer != null) {
- this.apiServer.shutdown().awaitTermination(5, TimeUnit.SECONDS);
+ this.apiServer.shutdown();
+ try {
+ if (!this.apiServer.awaitTermination(5, TimeUnit.SECONDS)) {
+ logger.warn("gRPC server did not shutdown gracefully, forcing shutdown");
+ this.apiServer.shutdownNow();
+ }
+ } catch (InterruptedException e) {
+ logger.warn("Interrupted while waiting for gRPC server shutdown", e);
+ this.apiServer.shutdownNow();
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ // Close executor
+ if (this.executorService != null) {
+ ExecutorServiceManager.shutdownAndAwaitTermination(
+ this.executorService, this.executorName);
+ this.executorService = null;
}
}
@@ -76,9 +95,9 @@ protected NettyServerBuilder initServerBuilder() {
NettyServerBuilder serverBuilder = NettyServerBuilder.forPort(this.port);
CommonParameter parameter = Args.getInstance();
if (parameter.getRpcThreadNum() > 0) {
- serverBuilder = serverBuilder
- .executor(ExecutorServiceManager.newFixedThreadPool(
- this.executorName, parameter.getRpcThreadNum()));
+ this.executorService = ExecutorServiceManager.newFixedThreadPool(
+ this.executorName, parameter.getRpcThreadNum());
+ serverBuilder = serverBuilder.executor(this.executorService);
}
// Set configs from config.conf or default value
serverBuilder
@@ -88,6 +107,10 @@ protected NettyServerBuilder initServerBuilder() {
.maxConnectionAge(parameter.getMaxConnectionAgeInMillis(), TimeUnit.MILLISECONDS)
.maxInboundMessageSize(parameter.getMaxMessageSize())
.maxHeaderListSize(parameter.getMaxHeaderListSize());
+ if (parameter.getRpcMaxRstStream() > 0 && parameter.getRpcSecondsPerWindow() > 0) {
+ serverBuilder.maxRstFramesPerWindow(
+ parameter.getRpcMaxRstStream(), parameter.getRpcSecondsPerWindow());
+ }
if (parameter.isRpcReflectionServiceEnable()) {
serverBuilder.addService(ProtoReflectionService.newInstance());
diff --git a/framework/src/main/java/org/tron/common/application/TronApplicationContext.java b/framework/src/main/java/org/tron/common/application/TronApplicationContext.java
index 64edec77c9c..6c934528f4f 100644
--- a/framework/src/main/java/org/tron/common/application/TronApplicationContext.java
+++ b/framework/src/main/java/org/tron/common/application/TronApplicationContext.java
@@ -13,8 +13,10 @@ public TronApplicationContext(DefaultListableBeanFactory beanFactory) {
super(beanFactory);
}
+ //only used for testcase
public TronApplicationContext(Class>... annotatedClasses) {
super(annotatedClasses);
+ this.registerShutdownHook();
}
public TronApplicationContext(String... basePackages) {
diff --git a/framework/src/main/java/org/tron/common/backup/BackupManager.java b/framework/src/main/java/org/tron/common/backup/BackupManager.java
index 0c4a3e60dfd..a8812a62bb4 100644
--- a/framework/src/main/java/org/tron/common/backup/BackupManager.java
+++ b/framework/src/main/java/org/tron/common/backup/BackupManager.java
@@ -126,7 +126,7 @@ public void handleEvent(UdpEvent udpEvent) {
logger.warn("Receive keep alive message from {} is not my member", sender.getHostString());
return;
}
-
+ logger.info("Receive keep alive message from {}", sender);
lastKeepAliveTime = System.currentTimeMillis();
KeepAliveMessage keepAliveMessage = (KeepAliveMessage) msg;
diff --git a/framework/src/main/java/org/tron/common/logsfilter/EventPluginLoader.java b/framework/src/main/java/org/tron/common/logsfilter/EventPluginLoader.java
index 0a7a5ac3a76..7061b2e9d57 100644
--- a/framework/src/main/java/org/tron/common/logsfilter/EventPluginLoader.java
+++ b/framework/src/main/java/org/tron/common/logsfilter/EventPluginLoader.java
@@ -9,7 +9,6 @@
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
-
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.util.encoders.Hex;
@@ -26,7 +25,6 @@
import org.tron.common.logsfilter.trigger.SolidityTrigger;
import org.tron.common.logsfilter.trigger.TransactionLogTrigger;
import org.tron.common.logsfilter.trigger.Trigger;
-import org.tron.common.utils.JsonUtil;
@Slf4j
public class EventPluginLoader {
@@ -140,7 +138,7 @@ public static boolean matchFilter(ContractTrigger trigger) {
private static boolean filterContractAddress(ContractTrigger trigger, List addressList) {
addressList = addressList.stream().filter(item ->
- org.apache.commons.lang3.StringUtils.isNotEmpty(item))
+ org.apache.commons.lang3.StringUtils.isNotEmpty(item))
.collect(Collectors.toList());
if (Objects.isNull(addressList) || addressList.isEmpty()) {
return true;
@@ -173,7 +171,7 @@ private static boolean filterContractTopicList(ContractTrigger trigger, List(((ContractEventTrigger) trigger).getTopicMap().values());
} else if (trigger != null) {
hset = trigger.getLogInfo().getClonedTopics()
- .stream().map(Hex::toHexString).collect(Collectors.toSet());
+ .stream().map(Hex::toHexString).collect(Collectors.toSet());
}
for (String top : topList) {
@@ -547,6 +545,10 @@ public boolean isBusy() {
return false;
}
int queueSize = 0;
+ if (eventListeners == null || eventListeners.isEmpty()) {
+ // only occurs in mock test. TODO fix test
+ return false;
+ }
for (IPluginEventListener listener : eventListeners) {
try {
queueSize += listener.getPendingSize();
diff --git a/framework/src/main/java/org/tron/core/Wallet.java b/framework/src/main/java/org/tron/core/Wallet.java
index 8dfb18331ff..8c86f2f66ac 100755
--- a/framework/src/main/java/org/tron/core/Wallet.java
+++ b/framework/src/main/java/org/tron/core/Wallet.java
@@ -30,6 +30,7 @@
import static org.tron.core.config.Parameter.DatabaseConstants.EXCHANGE_COUNT_LIMIT_MAX;
import static org.tron.core.config.Parameter.DatabaseConstants.MARKET_COUNT_LIMIT_MAX;
import static org.tron.core.config.Parameter.DatabaseConstants.PROPOSAL_COUNT_LIMIT_MAX;
+import static org.tron.core.config.Parameter.DatabaseConstants.WITNESS_COUNT_LIMIT_MAX;
import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.parseEnergyFee;
import static org.tron.core.services.jsonrpc.TronJsonRpcImpl.EARLIEST_STR;
import static org.tron.core.services.jsonrpc.TronJsonRpcImpl.FINALIZED_STR;
@@ -44,6 +45,7 @@
import com.google.common.collect.DiscreteDomain;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
import com.google.common.collect.Range;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
@@ -59,6 +61,7 @@
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
+import java.util.stream.Collectors;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
@@ -161,6 +164,7 @@
import org.tron.core.capsule.TransactionInfoCapsule;
import org.tron.core.capsule.TransactionResultCapsule;
import org.tron.core.capsule.TransactionRetCapsule;
+import org.tron.core.capsule.VotesCapsule;
import org.tron.core.capsule.WitnessCapsule;
import org.tron.core.capsule.utils.MarketUtils;
import org.tron.core.config.args.Args;
@@ -170,6 +174,7 @@
import org.tron.core.db.Manager;
import org.tron.core.db.TransactionContext;
import org.tron.core.db2.core.Chainbase;
+import org.tron.core.db2.core.Chainbase.Cursor;
import org.tron.core.exception.AccountResourceInsufficientException;
import org.tron.core.exception.BadItemException;
import org.tron.core.exception.ContractExeException;
@@ -177,7 +182,7 @@
import org.tron.core.exception.DupTransactionException;
import org.tron.core.exception.HeaderNotFound;
import org.tron.core.exception.ItemNotFoundException;
-import org.tron.core.exception.JsonRpcInvalidParamsException;
+import org.tron.core.exception.MaintenanceUnavailableException;
import org.tron.core.exception.NonUniqueObjectException;
import org.tron.core.exception.PermissionException;
import org.tron.core.exception.SignatureFormatException;
@@ -188,6 +193,7 @@
import org.tron.core.exception.VMIllegalException;
import org.tron.core.exception.ValidateSignatureException;
import org.tron.core.exception.ZksnarkException;
+import org.tron.core.exception.jsonrpc.JsonRpcInvalidParamsException;
import org.tron.core.net.TronNetDelegate;
import org.tron.core.net.TronNetService;
import org.tron.core.net.message.adv.TransactionMessage;
@@ -201,6 +207,8 @@
import org.tron.core.store.MarketPairPriceToOrderStore;
import org.tron.core.store.MarketPairToPriceStore;
import org.tron.core.store.StoreFactory;
+import org.tron.core.store.VotesStore;
+import org.tron.core.store.WitnessStore;
import org.tron.core.utils.TransactionUtil;
import org.tron.core.vm.program.Program;
import org.tron.core.zen.ShieldedTRC20ParametersBuilder;
@@ -764,6 +772,110 @@ public WitnessList getWitnessList() {
return builder.build();
}
+ public WitnessList getPaginatedNowWitnessList(long offset, long limit) throws
+ MaintenanceUnavailableException {
+ if (limit <= 0 || offset < 0) {
+ return null;
+ }
+ if (limit > WITNESS_COUNT_LIMIT_MAX) {
+ limit = WITNESS_COUNT_LIMIT_MAX;
+ }
+
+ /*
+ In the maintenance period, the VoteStores will be cleared.
+ To avoid the race condition of VoteStores deleted but Witness vote counts not updated,
+ return retry error.
+ Only apply to requests that rely on the latest block,
+ which means the normal fullnode requests with HEAD cursor.
+ */
+ boolean isMaintenance = chainBaseManager.getDynamicPropertiesStore().getStateFlag() == 1;
+ if (isMaintenance && !Args.getInstance().isSolidityNode() && getCursor() == Cursor.HEAD) {
+ String message =
+ "Service temporarily unavailable during maintenance period. Please try again later.";
+ throw new MaintenanceUnavailableException(message);
+ }
+ // It contains the final vote count at the end of the last epoch.
+ List witnessCapsuleList = chainBaseManager.getWitnessStore().getAllWitnesses();
+ if (offset >= witnessCapsuleList.size()) {
+ return null;
+ }
+
+ VotesStore votesStore = chainBaseManager.getVotesStore();
+ // Count the vote changes for each witness in the current epoch, it is maybe negative.
+ Map countWitness = countVote(votesStore);
+
+ // Iterate through the witness list to apply vote changes and calculate the real-time vote count
+ witnessCapsuleList.forEach(witnessCapsule -> {
+ long voteCount = countWitness.getOrDefault(witnessCapsule.getAddress(), 0L);
+ witnessCapsule.setVoteCount(witnessCapsule.getVoteCount() + voteCount);
+ });
+
+ // Use the same sorting logic as in the Maintenance period
+ WitnessStore.sortWitnesses(witnessCapsuleList,
+ chainBaseManager.getDynamicPropertiesStore().allowWitnessSortOptimization());
+
+ List sortedWitnessList = witnessCapsuleList.stream()
+ .skip(offset)
+ .limit(limit)
+ .collect(Collectors.toList());
+
+ WitnessList.Builder builder = WitnessList.newBuilder();
+ sortedWitnessList.forEach(witnessCapsule ->
+ builder.addWitnesses(witnessCapsule.getInstance()));
+
+ return builder.build();
+ }
+
+ /**
+ * Counts vote changes for witnesses in the current epoch.
+ *
+ * Vote count changes are tracked as follows:
+ * - Negative values for votes removed from previous witness in the last epoch
+ * - Positive values for votes added to new witness in the current epoch
+ *
+ * Example:
+ * an Account X had 100 votes for witness W1 in the previous epoch.
+ * In the current epoch, X changes votes to:
+ * - W2: 60 votes
+ * - W3: 80 votes
+ *
+ * Resulting vote changes:
+ * - W1: -100 (votes removed)
+ * - W2: +60 (new votes)
+ * - W3: +80 (new votes)
+ */
+ private Map countVote(VotesStore votesStore) {
+ // Initialize a result map to store vote changes for each witness
+ Map countWitness = Maps.newHashMap();
+
+ // VotesStore is a key-value store, where the key is the address of the voter
+ Iterator> dbIterator = votesStore.iterator();
+
+ while (dbIterator.hasNext()) {
+ Entry next = dbIterator.next();
+ VotesCapsule votes = next.getValue();
+
+ /**
+ * VotesCapsule contains two lists:
+ * - Old votes: Last votes from the previous epoch, updated in maintenance period
+ * - New votes: Latest votes in current epoch, updated after each vote transaction
+ */
+ votes.getOldVotes().forEach(vote -> {
+ ByteString voteAddress = vote.getVoteAddress();
+ long voteCount = vote.getVoteCount();
+ countWitness.put(voteAddress,
+ countWitness.getOrDefault(voteAddress, 0L) - voteCount);
+ });
+ votes.getNewVotes().forEach(vote -> {
+ ByteString voteAddress = vote.getVoteAddress();
+ long voteCount = vote.getVoteCount();
+ countWitness.put(voteAddress,
+ countWitness.getOrDefault(voteAddress, 0L) + voteCount);
+ });
+ }
+ return countWitness;
+ }
+
public ProposalList getProposalList() {
ProposalList.Builder builder = ProposalList.newBuilder();
List proposalCapsuleList =
@@ -1387,6 +1499,16 @@ public Protocol.ChainParameters getChainParameters() {
.setValue(dbManager.getDynamicPropertiesStore().getAllowTvmBlob())
.build());
+ builder.addChainParameter(Protocol.ChainParameters.ChainParameter.newBuilder()
+ .setKey("getAllowTvmSelfdestructRestriction")
+ .setValue(dbManager.getDynamicPropertiesStore().getAllowTvmSelfdestructRestriction())
+ .build());
+
+ builder.addChainParameter(Protocol.ChainParameters.ChainParameter.newBuilder()
+ .setKey("getProposalExpireTime")
+ .setValue(dbManager.getDynamicPropertiesStore().getProposalExpireTime())
+ .build());
+
return builder.build();
}
@@ -1806,12 +1928,8 @@ public Exchange getExchangeById(ByteString exchangeId) {
return null;
}
- private boolean getFullNodeAllowShieldedTransaction() {
- return Args.getInstance().isFullNodeAllowShieldedTransactionArgs();
- }
-
- private void checkFullNodeAllowShieldedTransaction() throws ZksnarkException {
- if (!getFullNodeAllowShieldedTransaction()) {
+ private void checkAllowShieldedTransactionApi() throws ZksnarkException {
+ if (!Args.getInstance().isAllowShieldedTransactionApi()) {
throw new ZksnarkException(SHIELDED_ID_NOT_ALLOWED);
}
}
@@ -1830,10 +1948,7 @@ public BytesMessage getNullifier(ByteString id) {
}
private long getBlockNumber(OutputPoint outPoint)
- throws BadItemException, ZksnarkException {
- if (!getFullNodeAllowShieldedTransaction()) {
- throw new ZksnarkException(SHIELDED_ID_NOT_ALLOWED);
- }
+ throws BadItemException {
ByteString txId = outPoint.getHash();
long blockNum = chainBaseManager.getTransactionStore().getBlockNumber(txId.toByteArray());
@@ -1848,9 +1963,6 @@ private long getBlockNumber(OutputPoint outPoint)
private IncrementalMerkleVoucherContainer createWitness(OutputPoint outPoint, Long blockNumber)
throws ItemNotFoundException, BadItemException,
InvalidProtocolBufferException, ZksnarkException {
- if (!getFullNodeAllowShieldedTransaction()) {
- throw new ZksnarkException(SHIELDED_ID_NOT_ALLOWED);
- }
ByteString txId = outPoint.getHash();
//Get the tree in blockNum-1 position
@@ -1946,9 +2058,6 @@ private IncrementalMerkleVoucherContainer createWitness(OutputPoint outPoint, Lo
private void updateWitnesses(List witnessList, long large,
int synBlockNum) throws ItemNotFoundException, BadItemException,
InvalidProtocolBufferException, ZksnarkException {
- if (!getFullNodeAllowShieldedTransaction()) {
- throw new ZksnarkException(SHIELDED_ID_NOT_ALLOWED);
- }
long start = large;
long end = large + synBlockNum - 1;
@@ -2022,10 +2131,7 @@ private void updateLowWitness(IncrementalMerkleVoucherContainer witness, long bl
}
}
- private void validateInput(OutputPointInfo request) throws BadItemException, ZksnarkException {
- if (!getFullNodeAllowShieldedTransaction()) {
- throw new ZksnarkException(SHIELDED_ID_NOT_ALLOWED);
- }
+ private void validateInput(OutputPointInfo request) throws BadItemException {
if (request.getBlockNum() < 0 || request.getBlockNum() > 1000) {
throw new BadItemException("request.BlockNum must be specified with range in [0, 1000]");
}
@@ -2051,7 +2157,7 @@ private void validateInput(OutputPointInfo request) throws BadItemException, Zks
public IncrementalMerkleVoucherInfo getMerkleTreeVoucherInfo(OutputPointInfo request)
throws ItemNotFoundException, BadItemException,
InvalidProtocolBufferException, ZksnarkException {
- checkFullNodeAllowShieldedTransaction();
+ checkAllowShieldedTransactionApi();
validateInput(request);
IncrementalMerkleVoucherInfo.Builder result = IncrementalMerkleVoucherInfo.newBuilder();
@@ -2097,9 +2203,7 @@ public IncrementalMerkleVoucherInfo getMerkleTreeVoucherInfo(OutputPointInfo req
}
public IncrementalMerkleTree getMerkleTreeOfBlock(long blockNum) throws ZksnarkException {
- if (!getFullNodeAllowShieldedTransaction()) {
- throw new ZksnarkException(SHIELDED_ID_NOT_ALLOWED);
- }
+ checkAllowShieldedTransactionApi();
if (blockNum < 0) {
return null;
}
@@ -2166,7 +2270,7 @@ public ReceiveNote createReceiveNoteRandom(long value) throws ZksnarkException,
public TransactionCapsule createShieldedTransaction(PrivateParameters request)
throws ContractValidateException, RuntimeException, ZksnarkException, BadItemException {
- checkFullNodeAllowShieldedTransaction();
+ checkAllowShieldedTransactionApi();
ZenTransactionBuilder builder = new ZenTransactionBuilder(this);
@@ -2268,7 +2372,7 @@ public TransactionCapsule createShieldedTransaction(PrivateParameters request)
public TransactionCapsule createShieldedTransactionWithoutSpendAuthSig(
PrivateParametersWithoutAsk request)
throws ContractValidateException, ZksnarkException, BadItemException {
- checkFullNodeAllowShieldedTransaction();
+ checkAllowShieldedTransactionApi();
ZenTransactionBuilder builder = new ZenTransactionBuilder(this);
@@ -2385,7 +2489,7 @@ private void shieldedOutput(List shieldedReceives,
public ShieldedAddressInfo getNewShieldedAddress() throws BadItemException, ZksnarkException {
- checkFullNodeAllowShieldedTransaction();
+ checkAllowShieldedTransactionApi();
ShieldedAddressInfo.Builder addressInfo = ShieldedAddressInfo.newBuilder();
@@ -2418,7 +2522,7 @@ public ShieldedAddressInfo getNewShieldedAddress() throws BadItemException, Zksn
}
public BytesMessage getSpendingKey() throws ZksnarkException {
- checkFullNodeAllowShieldedTransaction();
+ checkAllowShieldedTransactionApi();
byte[] sk = SpendingKey.random().getValue();
return BytesMessage.newBuilder().setValue(ByteString.copyFrom(sk)).build();
@@ -2426,7 +2530,7 @@ public BytesMessage getSpendingKey() throws ZksnarkException {
public ExpandedSpendingKeyMessage getExpandedSpendingKey(ByteString spendingKey)
throws BadItemException, ZksnarkException {
- checkFullNodeAllowShieldedTransaction();
+ checkAllowShieldedTransactionApi();
if (Objects.isNull(spendingKey)) {
throw new BadItemException("spendingKey is null");
@@ -2452,7 +2556,7 @@ public ExpandedSpendingKeyMessage getExpandedSpendingKey(ByteString spendingKey)
public BytesMessage getAkFromAsk(ByteString ask) throws
BadItemException, ZksnarkException {
- checkFullNodeAllowShieldedTransaction();
+ checkAllowShieldedTransactionApi();
if (Objects.isNull(ask)) {
throw new BadItemException("ask is null");
@@ -2468,7 +2572,7 @@ public BytesMessage getAkFromAsk(ByteString ask) throws
public BytesMessage getNkFromNsk(ByteString nsk) throws
BadItemException, ZksnarkException {
- checkFullNodeAllowShieldedTransaction();
+ checkAllowShieldedTransactionApi();
if (Objects.isNull(nsk)) {
throw new BadItemException("nsk is null");
@@ -2484,7 +2588,7 @@ public BytesMessage getNkFromNsk(ByteString nsk) throws
public IncomingViewingKeyMessage getIncomingViewingKey(byte[] ak, byte[] nk)
throws ZksnarkException {
- checkFullNodeAllowShieldedTransaction();
+ checkAllowShieldedTransactionApi();
byte[] ivk = new byte[32]; // the incoming viewing key
JLibrustzcash.librustzcashCrhIvk(new CrhIvkParams(ak, nk, ivk));
@@ -2495,7 +2599,7 @@ public IncomingViewingKeyMessage getIncomingViewingKey(byte[] ak, byte[] nk)
}
public DiversifierMessage getDiversifier() throws ZksnarkException {
- checkFullNodeAllowShieldedTransaction();
+ checkAllowShieldedTransactionApi();
byte[] d;
while (true) {
@@ -2511,7 +2615,7 @@ public DiversifierMessage getDiversifier() throws ZksnarkException {
}
public BytesMessage getRcm() throws ZksnarkException {
- checkFullNodeAllowShieldedTransaction();
+ checkAllowShieldedTransactionApi();
byte[] rcm = Note.generateR();
return BytesMessage.newBuilder().setValue(ByteString.copyFrom(rcm)).build();
@@ -2519,7 +2623,7 @@ public BytesMessage getRcm() throws ZksnarkException {
public PaymentAddressMessage getPaymentAddress(IncomingViewingKey ivk,
DiversifierT d) throws BadItemException, ZksnarkException {
- checkFullNodeAllowShieldedTransaction();
+ checkAllowShieldedTransactionApi();
if (!JLibrustzcash.librustzcashCheckDiversifier(d.getData())) {
throw new BadItemException("d is not valid");
@@ -2543,7 +2647,7 @@ public PaymentAddressMessage getPaymentAddress(IncomingViewingKey ivk,
public SpendResult isSpend(NoteParameters noteParameters) throws
ZksnarkException, InvalidProtocolBufferException, BadItemException, ItemNotFoundException {
- checkFullNodeAllowShieldedTransaction();
+ checkAllowShieldedTransactionApi();
GrpcAPI.Note note = noteParameters.getNote();
byte[] ak = noteParameters.getAk().toByteArray();
@@ -2599,7 +2703,7 @@ public SpendResult isSpend(NoteParameters noteParameters) throws
public BytesMessage createSpendAuthSig(SpendAuthSigParameters spendAuthSigParameters)
throws ZksnarkException {
- checkFullNodeAllowShieldedTransaction();
+ checkAllowShieldedTransactionApi();
byte[] result = new byte[64];
SpendSigParams spendSigParams = new SpendSigParams(
@@ -2613,7 +2717,7 @@ public BytesMessage createSpendAuthSig(SpendAuthSigParameters spendAuthSigParame
}
public BytesMessage createShieldNullifier(NfParameters nfParameters) throws ZksnarkException {
- checkFullNodeAllowShieldedTransaction();
+ checkAllowShieldedTransactionApi();
byte[] ak = nfParameters.getAk().toByteArray();
byte[] nk = nfParameters.getNk().toByteArray();
@@ -2649,7 +2753,7 @@ public BytesMessage createShieldNullifier(NfParameters nfParameters) throws Zksn
public BytesMessage getShieldTransactionHash(Transaction transaction)
throws ContractValidateException, ZksnarkException {
- checkFullNodeAllowShieldedTransaction();
+ checkAllowShieldedTransactionApi();
List contract = transaction.getRawData().getContractList();
if (contract == null || contract.isEmpty()) {
@@ -2671,37 +2775,7 @@ public BytesMessage getShieldTransactionHash(Transaction transaction)
}
public TransactionInfoList getTransactionInfoByBlockNum(long blockNum) {
- TransactionInfoList.Builder transactionInfoList = TransactionInfoList.newBuilder();
-
- try {
- TransactionRetCapsule result = dbManager.getTransactionRetStore()
- .getTransactionInfoByBlockNum(ByteArray.fromLong(blockNum));
-
- if (!Objects.isNull(result) && !Objects.isNull(result.getInstance())) {
- result.getInstance().getTransactioninfoList().forEach(
- transactionInfo -> transactionInfoList.addTransactionInfo(transactionInfo)
- );
- } else {
- Block block = chainBaseManager.getBlockByNum(blockNum).getInstance();
-
- if (block != null) {
- List listTransaction = block.getTransactionsList();
- for (Transaction transaction : listTransaction) {
- TransactionInfoCapsule transactionInfoCapsule = dbManager.getTransactionHistoryStore()
- .get(Sha256Hash.hash(CommonParameter.getInstance()
- .isECKeyCryptoEngine(), transaction.getRawData().toByteArray()));
-
- if (transactionInfoCapsule != null) {
- transactionInfoList.addTransactionInfo(transactionInfoCapsule.getInstance());
- }
- }
- }
- }
- } catch (BadItemException | ItemNotFoundException e) {
- logger.warn(e.getMessage());
- }
-
- return transactionInfoList.build();
+ return dbManager.getTransactionInfoByBlockNum(blockNum);
}
public NodeList listNodes() {
@@ -3354,7 +3428,7 @@ private GrpcAPI.DecryptNotes queryNoteByIvk(long startNum, long endNum, byte[] i
*/
public GrpcAPI.DecryptNotes scanNoteByIvk(long startNum, long endNum,
byte[] ivk) throws BadItemException, ZksnarkException {
- checkFullNodeAllowShieldedTransaction();
+ checkAllowShieldedTransactionApi();
return queryNoteByIvk(startNum, endNum, ivk);
}
@@ -3365,7 +3439,7 @@ public GrpcAPI.DecryptNotes scanNoteByIvk(long startNum, long endNum,
public GrpcAPI.DecryptNotesMarked scanAndMarkNoteByIvk(long startNum, long endNum,
byte[] ivk, byte[] ak, byte[] nk) throws BadItemException, ZksnarkException,
InvalidProtocolBufferException, ItemNotFoundException {
- checkFullNodeAllowShieldedTransaction();
+ checkAllowShieldedTransactionApi();
GrpcAPI.DecryptNotes srcNotes = queryNoteByIvk(startNum, endNum, ivk);
GrpcAPI.DecryptNotesMarked.Builder builder = GrpcAPI.DecryptNotesMarked.newBuilder();
@@ -3398,7 +3472,7 @@ public GrpcAPI.DecryptNotesMarked scanAndMarkNoteByIvk(long startNum, long endNu
*/
public GrpcAPI.DecryptNotes scanNoteByOvk(long startNum, long endNum,
byte[] ovk) throws BadItemException, ZksnarkException {
- checkFullNodeAllowShieldedTransaction();
+ checkAllowShieldedTransactionApi();
GrpcAPI.DecryptNotes.Builder builder = GrpcAPI.DecryptNotes.newBuilder();
if (!(startNum >= 0 && endNum > startNum && endNum - startNum <= 1000)) {
@@ -3538,7 +3612,7 @@ private void buildShieldedTRC20Output(ShieldedTRC20ParametersBuilder builder,
public ShieldedTRC20Parameters createShieldedContractParameters(
PrivateShieldedTRC20Parameters request)
throws ContractValidateException, ZksnarkException, ContractExeException {
- checkFullNodeAllowShieldedTransaction();
+ checkAllowShieldedTransactionApi();
ShieldedTRC20ParametersBuilder builder = new ShieldedTRC20ParametersBuilder();
@@ -3675,7 +3749,7 @@ private void buildShieldedTRC20InputWithAK(
public ShieldedTRC20Parameters createShieldedContractParametersWithoutAsk(
PrivateShieldedTRC20ParametersWithoutAsk request)
throws ZksnarkException, ContractValidateException, ContractExeException {
- checkFullNodeAllowShieldedTransaction();
+ checkAllowShieldedTransactionApi();
ShieldedTRC20ParametersBuilder builder = new ShieldedTRC20ParametersBuilder();
byte[] shieldedTRC20ContractAddress = request.getShieldedTRC20ContractAddress().toByteArray();
@@ -3971,7 +4045,7 @@ public DecryptNotesTRC20 scanShieldedTRC20NotesByIvk(
long startNum, long endNum, byte[] shieldedTRC20ContractAddress,
byte[] ivk, byte[] ak, byte[] nk, ProtocolStringList topicsList)
throws BadItemException, ZksnarkException, ContractExeException {
- checkFullNodeAllowShieldedTransaction();
+ checkAllowShieldedTransactionApi();
return queryTRC20NoteByIvk(startNum, endNum,
shieldedTRC20ContractAddress, ivk, ak, nk, topicsList);
@@ -4050,7 +4124,7 @@ private Optional getNoteTxFromLogListByOvk(
public DecryptNotesTRC20 scanShieldedTRC20NotesByOvk(long startNum, long endNum,
byte[] ovk, byte[] shieldedTRC20ContractAddress, ProtocolStringList topicsList)
throws ZksnarkException, BadItemException {
- checkFullNodeAllowShieldedTransaction();
+ checkAllowShieldedTransactionApi();
DecryptNotesTRC20.Builder builder = DecryptNotesTRC20.newBuilder();
if (!(startNum >= 0 && endNum > startNum && endNum - startNum <= 1000)) {
@@ -4113,7 +4187,7 @@ private byte[] getShieldedTRC20Nullifier(GrpcAPI.Note note, long pos, byte[] ak,
public GrpcAPI.NullifierResult isShieldedTRC20ContractNoteSpent(NfTRC20Parameters request) throws
ZksnarkException, ContractExeException {
- checkFullNodeAllowShieldedTransaction();
+ checkAllowShieldedTransactionApi();
return GrpcAPI.NullifierResult.newBuilder()
.setIsSpent(isShieldedTRC20NoteSpent(request.getNote(),
@@ -4236,7 +4310,7 @@ public byte[] getShieldedContractScalingFactor(byte[] contractAddress)
public BytesMessage getTriggerInputForShieldedTRC20Contract(
ShieldedTRC20TriggerContractParameters request)
throws ZksnarkException, ContractValidateException {
- checkFullNodeAllowShieldedTransaction();
+ checkAllowShieldedTransactionApi();
ShieldedTRC20Parameters shieldedTRC20Parameters = request.getShieldedTRC20Parameters();
List spendAuthoritySignature = request.getSpendAuthoritySignatureList();
diff --git a/framework/src/main/java/org/tron/core/config/args/Args.java b/framework/src/main/java/org/tron/core/config/args/Args.java
index 277b3e7bc79..46695986c1f 100644
--- a/framework/src/main/java/org/tron/core/config/args/Args.java
+++ b/framework/src/main/java/org/tron/core/config/args/Args.java
@@ -1,13 +1,18 @@
package org.tron.core.config.args;
import static java.lang.System.exit;
+import static org.fusesource.jansi.Ansi.ansi;
import static org.tron.common.math.Maths.max;
import static org.tron.common.math.Maths.min;
import static org.tron.core.Constant.ADD_PRE_FIX_BYTE_MAINNET;
+import static org.tron.core.Constant.DEFAULT_PROPOSAL_EXPIRE_TIME;
import static org.tron.core.Constant.DYNAMIC_ENERGY_INCREASE_FACTOR_RANGE;
import static org.tron.core.Constant.DYNAMIC_ENERGY_MAX_FACTOR_RANGE;
+import static org.tron.core.Constant.MAX_PROPOSAL_EXPIRE_TIME;
+import static org.tron.core.Constant.MIN_PROPOSAL_EXPIRE_TIME;
import static org.tron.core.config.Parameter.ChainConstant.BLOCK_PRODUCE_TIMEOUT_PERCENT;
import static org.tron.core.config.Parameter.ChainConstant.MAX_ACTIVE_WITNESS_NUM;
+import static org.tron.core.exception.TronError.ErrCode.PARAMETER_INIT;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.ParameterDescription;
@@ -42,14 +47,15 @@
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
+import org.fusesource.jansi.AnsiConsole;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
+import org.tron.common.arch.Arch;
import org.tron.common.args.Account;
import org.tron.common.args.GenesisBlock;
import org.tron.common.args.Witness;
import org.tron.common.config.DbBackupConfig;
import org.tron.common.cron.CronExpression;
-import org.tron.common.crypto.SignInterface;
import org.tron.common.logsfilter.EventPluginConfig;
import org.tron.common.logsfilter.FilterQuery;
import org.tron.common.logsfilter.TriggerConfig;
@@ -58,7 +64,6 @@
import org.tron.common.parameter.CommonParameter;
import org.tron.common.parameter.RateLimiterInitialization;
import org.tron.common.setting.RocksDbSettings;
-import org.tron.common.utils.ByteArray;
import org.tron.common.utils.Commons;
import org.tron.common.utils.LocalWitnesses;
import org.tron.core.Constant;
@@ -66,11 +71,8 @@
import org.tron.core.config.Configuration;
import org.tron.core.config.Parameter.NetConstants;
import org.tron.core.config.Parameter.NodeConstant;
-import org.tron.core.exception.CipherException;
import org.tron.core.exception.TronError;
import org.tron.core.store.AccountStore;
-import org.tron.keystore.Credentials;
-import org.tron.keystore.WalletUtils;
import org.tron.p2p.P2pConfig;
import org.tron.p2p.dns.update.DnsType;
import org.tron.p2p.dns.update.PublishConfig;
@@ -169,6 +171,7 @@ public static void clearParam() {
PARAMETER.tcpNettyWorkThreadNum = 0;
PARAMETER.udpNettyWorkThreadNum = 0;
PARAMETER.solidityNode = false;
+ PARAMETER.keystoreFactory = false;
PARAMETER.trustNodeAddr = "";
PARAMETER.walletExtensionApi = false;
PARAMETER.estimateEnergy = false;
@@ -186,7 +189,7 @@ public static void clearParam() {
PARAMETER.maxHttpConnectNumber = 50;
PARAMETER.allowMultiSign = 0;
PARAMETER.trxExpirationTimeInMilliseconds = 0;
- PARAMETER.fullNodeAllowShieldedTransactionArgs = true;
+ PARAMETER.allowShieldedTransactionApi = true;
PARAMETER.zenTokenId = "000000";
PARAMETER.allowProtoFilterNum = 0;
PARAMETER.allowAccountStateRoot = 0;
@@ -204,6 +207,7 @@ public static void clearParam() {
PARAMETER.jsonRpcHttpPBFTNodeEnable = false;
PARAMETER.jsonRpcMaxBlockRange = 5000;
PARAMETER.jsonRpcMaxSubTopics = 1000;
+ PARAMETER.jsonRpcMaxBlockFilterNum = 50000;
PARAMETER.nodeMetricsEnable = false;
PARAMETER.metricsStorageEnable = false;
PARAMETER.metricsPrometheusEnable = false;
@@ -235,6 +239,9 @@ public static void clearParam() {
PARAMETER.rateLimiterGlobalQps = 50000;
PARAMETER.rateLimiterGlobalIpQps = 10000;
PARAMETER.rateLimiterGlobalApiQps = 1000;
+ PARAMETER.rateLimiterSyncBlockChain = 3.0;
+ PARAMETER.rateLimiterFetchInvData = 3.0;
+ PARAMETER.rateLimiterDisconnect = 1.0;
PARAMETER.p2pDisable = false;
PARAMETER.dynamicConfigEnable = false;
PARAMETER.dynamicConfigCheckInterval = 600;
@@ -247,6 +254,8 @@ public static void clearParam() {
PARAMETER.consensusLogicOptimization = 0;
PARAMETER.allowTvmCancun = 0;
PARAMETER.allowTvmBlob = 0;
+ PARAMETER.rpcMaxRstStream = 0;
+ PARAMETER.rpcSecondsPerWindow = 0;
}
/**
@@ -342,7 +351,7 @@ private static String getCommitIdAbbrev() {
private static Map getOptionGroup() {
String[] tronOption = new String[] {"version", "help", "shellConfFileName", "logbackPath",
- "eventSubscribe"};
+ "eventSubscribe", "solidityNode", "keystoreFactory"};
String[] dbOption = new String[] {"outputDirectory"};
String[] witnessOption = new String[] {"witness", "privateKey"};
String[] vmOption = new String[] {"debug"};
@@ -369,6 +378,17 @@ private static Map getOptionGroup() {
* set parameters.
*/
public static void setParam(final String[] args, final String confFileName) {
+ try {
+ Arch.throwIfUnsupportedJavaVersion();
+ } catch (UnsupportedOperationException e) {
+ AnsiConsole.systemInstall();
+ // To avoid confusion caused by silent execution when using -h or -v flags,
+ // errors are explicitly logged to the console in this context.
+ // Console output is not required for errors in other scenarios.
+ System.out.println(ansi().fgRed().a(e.getMessage()).reset());
+ AnsiConsole.systemUninstall();
+ throw new TronError(e, TronError.ErrCode.JDK_VERSION);
+ }
JCommander.newBuilder().addObject(PARAMETER).build().parse(args);
if (PARAMETER.version) {
printVersion();
@@ -403,61 +423,7 @@ public static void setParam(final Config config) {
PARAMETER.cryptoEngine = config.hasPath(Constant.CRYPTO_ENGINE) ? config
.getString(Constant.CRYPTO_ENGINE) : Constant.ECKey_ENGINE;
- if (StringUtils.isNoneBlank(PARAMETER.privateKey)) {
- localWitnesses = (new LocalWitnesses(PARAMETER.privateKey));
- if (StringUtils.isNoneBlank(PARAMETER.witnessAddress)) {
- byte[] bytes = Commons.decodeFromBase58Check(PARAMETER.witnessAddress);
- if (bytes != null) {
- localWitnesses.setWitnessAccountAddress(bytes);
- logger.debug("Got localWitnessAccountAddress from cmd");
- } else {
- PARAMETER.witnessAddress = "";
- logger.warn(IGNORE_WRONG_WITNESS_ADDRESS_FORMAT);
- }
- }
- localWitnesses.initWitnessAccountAddress(PARAMETER.isECKeyCryptoEngine());
- logger.debug("Got privateKey from cmd");
- } else if (config.hasPath(Constant.LOCAL_WITNESS)) {
- localWitnesses = new LocalWitnesses();
- List localwitness = config.getStringList(Constant.LOCAL_WITNESS);
- localWitnesses.setPrivateKeys(localwitness);
- witnessAddressCheck(config);
- localWitnesses.initWitnessAccountAddress(PARAMETER.isECKeyCryptoEngine());
- logger.debug("Got privateKey from config.conf");
- } else if (config.hasPath(Constant.LOCAL_WITNESS_KEYSTORE)) {
- localWitnesses = new LocalWitnesses();
- List privateKeys = new ArrayList();
- if (PARAMETER.isWitness()) {
- List localwitness = config.getStringList(Constant.LOCAL_WITNESS_KEYSTORE);
- if (localwitness.size() > 0) {
- String fileName = System.getProperty("user.dir") + "/" + localwitness.get(0);
- String password;
- if (StringUtils.isEmpty(PARAMETER.password)) {
- System.out.println("Please input your password.");
- password = WalletUtils.inputPassword();
- } else {
- password = PARAMETER.password;
- PARAMETER.password = null;
- }
-
- try {
- Credentials credentials = WalletUtils
- .loadCredentials(password, new File(fileName));
- SignInterface sign = credentials.getSignInterface();
- String prikey = ByteArray.toHexString(sign.getPrivateKey());
- privateKeys.add(prikey);
- } catch (IOException | CipherException e) {
- logger.error("Witness node start failed!");
- throw new TronError(e, TronError.ErrCode.WITNESS_KEYSTORE_LOAD);
- }
- }
- }
- localWitnesses.setPrivateKeys(privateKeys);
- witnessAddressCheck(config);
- localWitnesses.initWitnessAccountAddress(PARAMETER.isECKeyCryptoEngine());
- logger.debug("Got privateKey from keystore");
- }
-
+ localWitnesses = new WitnessInitializer(config).initLocalWitnesses();
if (PARAMETER.isWitness()
&& CollectionUtils.isEmpty(localWitnesses.getPrivateKeys())) {
throw new TronError("This is a witness node, but localWitnesses is null",
@@ -526,6 +492,11 @@ public static void setParam(final Config config) {
config.getInt(Constant.NODE_JSONRPC_MAX_SUB_TOPICS);
}
+ if (config.hasPath(Constant.NODE_JSONRPC_MAX_BLOCK_FILTER_NUM)) {
+ PARAMETER.jsonRpcMaxBlockFilterNum =
+ config.getInt(Constant.NODE_JSONRPC_MAX_BLOCK_FILTER_NUM);
+ }
+
if (config.hasPath(Constant.VM_MIN_TIME_RATIO)) {
PARAMETER.minTimeRatio = config.getDouble(Constant.VM_MIN_TIME_RATIO);
}
@@ -771,6 +742,12 @@ public static void setParam(final Config config) {
PARAMETER.flowControlWindow = config.hasPath(Constant.NODE_RPC_FLOW_CONTROL_WINDOW)
? config.getInt(Constant.NODE_RPC_FLOW_CONTROL_WINDOW)
: NettyServerBuilder.DEFAULT_FLOW_CONTROL_WINDOW;
+ if (config.hasPath(Constant.NODE_RPC_MAX_RST_STREAM)) {
+ PARAMETER.rpcMaxRstStream = config.getInt(Constant.NODE_RPC_MAX_RST_STREAM);
+ }
+ if (config.hasPath(Constant.NODE_RPC_SECONDS_PER_WINDOW)) {
+ PARAMETER.rpcSecondsPerWindow = config.getInt(Constant.NODE_RPC_SECONDS_PER_WINDOW);
+ }
PARAMETER.maxConnectionIdleInMillis =
config.hasPath(Constant.NODE_RPC_MAX_CONNECTION_IDLE_IN_MILLIS)
@@ -815,9 +792,7 @@ public static void setParam(final Config config) {
config.hasPath(Constant.BLOCK_MAINTENANCE_TIME_INTERVAL) ? config
.getInt(Constant.BLOCK_MAINTENANCE_TIME_INTERVAL) : 21600000L;
- PARAMETER.proposalExpireTime =
- config.hasPath(Constant.BLOCK_PROPOSAL_EXPIRE_TIME) ? config
- .getInt(Constant.BLOCK_PROPOSAL_EXPIRE_TIME) : 259200000L;
+ PARAMETER.proposalExpireTime = getProposalExpirationTime(config);
PARAMETER.checkFrozenTime =
config.hasPath(Constant.BLOCK_CHECK_FROZEN_TIME) ? config
@@ -991,9 +966,18 @@ public static void setParam(final Config config) {
PARAMETER.eventFilter =
config.hasPath(Constant.EVENT_SUBSCRIBE_FILTER) ? getEventFilter(config) : null;
- PARAMETER.fullNodeAllowShieldedTransactionArgs =
- !config.hasPath(Constant.NODE_FULLNODE_ALLOW_SHIELDED_TRANSACTION)
- || config.getBoolean(Constant.NODE_FULLNODE_ALLOW_SHIELDED_TRANSACTION);
+ if (config.hasPath(Constant.ALLOW_SHIELDED_TRANSACTION_API)) {
+ PARAMETER.allowShieldedTransactionApi =
+ config.getBoolean(Constant.ALLOW_SHIELDED_TRANSACTION_API);
+ } else if (config.hasPath(Constant.NODE_FULLNODE_ALLOW_SHIELDED_TRANSACTION)) {
+ // for compatibility with previous configuration
+ PARAMETER.allowShieldedTransactionApi =
+ config.getBoolean(Constant.NODE_FULLNODE_ALLOW_SHIELDED_TRANSACTION);
+ logger.warn("Configuring [node.fullNodeAllowShieldedTransaction] will be deprecated. "
+ + "Please use [node.allowShieldedTransactionApi] instead.");
+ } else {
+ PARAMETER.allowShieldedTransactionApi = true;
+ }
PARAMETER.zenTokenId = config.hasPath(Constant.NODE_ZEN_TOKENID)
? config.getString(Constant.NODE_ZEN_TOKENID) : "000000";
@@ -1030,10 +1014,6 @@ public static void setParam(final Config config) {
config.hasPath(Constant.NODE_SHIELDED_TRANS_IN_PENDING_MAX_COUNTS) ? config
.getInt(Constant.NODE_SHIELDED_TRANS_IN_PENDING_MAX_COUNTS) : 10;
- if (PARAMETER.isWitness()) {
- PARAMETER.fullNodeAllowShieldedTransactionArgs = true;
- }
-
PARAMETER.rateLimiterGlobalQps =
config.hasPath(Constant.RATE_LIMITER_GLOBAL_QPS) ? config
.getInt(Constant.RATE_LIMITER_GLOBAL_QPS) : 50000;
@@ -1048,6 +1028,18 @@ public static void setParam(final Config config) {
PARAMETER.rateLimiterInitialization = getRateLimiterFromConfig(config);
+ PARAMETER.rateLimiterSyncBlockChain =
+ config.hasPath(Constant.RATE_LIMITER_P2P_SYNC_BLOCK_CHAIN) ? config
+ .getDouble(Constant.RATE_LIMITER_P2P_SYNC_BLOCK_CHAIN) : 3.0;
+
+ PARAMETER.rateLimiterFetchInvData =
+ config.hasPath(Constant.RATE_LIMITER_P2P_FETCH_INV_DATA) ? config
+ .getDouble(Constant.RATE_LIMITER_P2P_FETCH_INV_DATA) : 3.0;
+
+ PARAMETER.rateLimiterDisconnect =
+ config.hasPath(Constant.RATE_LIMITER_P2P_DISCONNECT) ? config
+ .getDouble(Constant.RATE_LIMITER_P2P_DISCONNECT) : 1.0;
+
PARAMETER.changedDelegation =
config.hasPath(Constant.COMMITTEE_CHANGED_DELEGATION) ? config
.getInt(Constant.COMMITTEE_CHANGED_DELEGATION) : 0;
@@ -1300,6 +1292,25 @@ public static void setParam(final Config config) {
logConfig();
}
+ private static long getProposalExpirationTime(final Config config) {
+ if (config.hasPath(Constant.COMMITTEE_PROPOSAL_EXPIRE_TIME)) {
+ throw new TronError("It is not allowed to configure committee.proposalExpireTime in "
+ + "config.conf, please set the value in block.proposalExpireTime.", PARAMETER_INIT);
+ }
+ if (config.hasPath(Constant.BLOCK_PROPOSAL_EXPIRE_TIME)) {
+ long proposalExpireTime = config.getLong(Constant.BLOCK_PROPOSAL_EXPIRE_TIME);
+ if (proposalExpireTime <= MIN_PROPOSAL_EXPIRE_TIME
+ || proposalExpireTime >= MAX_PROPOSAL_EXPIRE_TIME) {
+ throw new TronError("The value[block.proposalExpireTime] is only allowed to "
+ + "be greater than " + MIN_PROPOSAL_EXPIRE_TIME + " and less than "
+ + MAX_PROPOSAL_EXPIRE_TIME + "!", PARAMETER_INIT);
+ }
+ return proposalExpireTime;
+ } else {
+ return DEFAULT_PROPOSAL_EXPIRE_TIME;
+ }
+ }
+
private static List getWitnessesFromConfig(final com.typesafe.config.Config config) {
return config.getObjectList(Constant.GENESIS_BLOCK_WITNESSES).stream()
.map(Args::createWitness)
@@ -1622,7 +1633,7 @@ private static FilterQuery getEventFilter(final com.typesafe.config.Config confi
try {
fromBlockLong = FilterQuery.parseFromBlockNumber(fromBlock);
} catch (Exception e) {
- logger.error("{}", e);
+ logger.error("invalid filter: fromBlockNumber: {}", fromBlock, e);
return null;
}
filter.setFromBlock(fromBlockLong);
@@ -1631,7 +1642,7 @@ private static FilterQuery getEventFilter(final com.typesafe.config.Config confi
try {
toBlockLong = FilterQuery.parseToBlockNumber(toBlock);
} catch (Exception e) {
- logger.error("{}", e);
+ logger.error("invalid filter: toBlockNumber: {}", toBlock, e);
return null;
}
filter.setToBlock(toBlockLong);
@@ -1684,11 +1695,13 @@ private static void initRocksDbSettings(Config config) {
.getLong(prefix + "targetFileSizeBase") : 64;
int targetFileSizeMultiplier = config.hasPath(prefix + "targetFileSizeMultiplier") ? config
.getInt(prefix + "targetFileSizeMultiplier") : 1;
+ int maxOpenFiles = config.hasPath(prefix + "maxOpenFiles")
+ ? config.getInt(prefix + "maxOpenFiles") : 5000;
PARAMETER.rocksDBCustomSettings = RocksDbSettings
.initCustomSettings(levelNumber, compactThreads, blocksize, maxBytesForLevelBase,
maxBytesForLevelMultiplier, level0FileNumCompactionTrigger,
- targetFileSizeBase, targetFileSizeMultiplier);
+ targetFileSizeBase, targetFileSizeMultiplier, maxOpenFiles);
RocksDbSettings.loggingSettings();
}
@@ -1725,6 +1738,8 @@ private static void initBackupProperty(Config config) {
public static void logConfig() {
CommonParameter parameter = CommonParameter.getInstance();
logger.info("\n");
+ logger.info("************************ System info ************************");
+ logger.info("{}", Arch.withAll());
logger.info("************************ Net config ************************");
logger.info("P2P version: {}", parameter.getNodeP2pVersion());
logger.info("LAN IP: {}", parameter.getNodeLanIp());
@@ -1769,23 +1784,6 @@ public static void logConfig() {
logger.info("\n");
}
- public static void setFullNodeAllowShieldedTransaction(boolean fullNodeAllowShieldedTransaction) {
- PARAMETER.fullNodeAllowShieldedTransactionArgs = fullNodeAllowShieldedTransaction;
- }
-
- private static void witnessAddressCheck(Config config) {
- if (config.hasPath(Constant.LOCAL_WITNESS_ACCOUNT_ADDRESS)) {
- byte[] bytes = Commons
- .decodeFromBase58Check(config.getString(Constant.LOCAL_WITNESS_ACCOUNT_ADDRESS));
- if (bytes != null) {
- localWitnesses.setWitnessAccountAddress(bytes);
- logger.debug("Got localWitnessAccountAddress from config.conf");
- } else {
- logger.warn(IGNORE_WRONG_WITNESS_ADDRESS_FORMAT);
- }
- }
- }
-
/**
* get output directory.
*/
diff --git a/framework/src/main/java/org/tron/core/config/args/WitnessInitializer.java b/framework/src/main/java/org/tron/core/config/args/WitnessInitializer.java
new file mode 100644
index 00000000000..2ea3a449ef4
--- /dev/null
+++ b/framework/src/main/java/org/tron/core/config/args/WitnessInitializer.java
@@ -0,0 +1,149 @@
+package org.tron.core.config.args;
+
+import com.typesafe.config.Config;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.tron.common.crypto.SignInterface;
+import org.tron.common.utils.ByteArray;
+import org.tron.common.utils.Commons;
+import org.tron.common.utils.LocalWitnesses;
+import org.tron.core.Constant;
+import org.tron.core.exception.CipherException;
+import org.tron.core.exception.TronError;
+import org.tron.keystore.Credentials;
+import org.tron.keystore.WalletUtils;
+
+@Slf4j
+public class WitnessInitializer {
+
+ private final Config config;
+
+ private LocalWitnesses localWitnesses;
+
+ public WitnessInitializer(Config config) {
+ this.config = config;
+ this.localWitnesses = new LocalWitnesses();
+ }
+
+ public LocalWitnesses initLocalWitnesses() {
+ if (!Args.PARAMETER.isWitness()) {
+ return localWitnesses;
+ }
+
+ if (tryInitFromCommandLine()) {
+ return localWitnesses;
+ }
+
+ if (tryInitFromConfig()) {
+ return localWitnesses;
+ }
+
+ tryInitFromKeystore();
+
+ return localWitnesses;
+ }
+
+ private boolean tryInitFromCommandLine() {
+ if (StringUtils.isBlank(Args.PARAMETER.privateKey)) {
+ return false;
+ }
+
+ byte[] witnessAddress = null;
+ this.localWitnesses = new LocalWitnesses(Args.PARAMETER.privateKey);
+ if (StringUtils.isNotEmpty(Args.PARAMETER.witnessAddress)) {
+ witnessAddress = Commons.decodeFromBase58Check(Args.PARAMETER.witnessAddress);
+ if (witnessAddress == null) {
+ throw new TronError("LocalWitnessAccountAddress format from cmd is incorrect",
+ TronError.ErrCode.WITNESS_INIT);
+ }
+ logger.debug("Got localWitnessAccountAddress from cmd");
+ }
+
+ this.localWitnesses.initWitnessAccountAddress(witnessAddress,
+ Args.PARAMETER.isECKeyCryptoEngine());
+ logger.debug("Got privateKey from cmd");
+ return true;
+ }
+
+ private boolean tryInitFromConfig() {
+ if (!config.hasPath(Constant.LOCAL_WITNESS) || config.getStringList(Constant.LOCAL_WITNESS)
+ .isEmpty()) {
+ return false;
+ }
+
+ List localWitness = config.getStringList(Constant.LOCAL_WITNESS);
+ this.localWitnesses.setPrivateKeys(localWitness);
+ logger.debug("Got privateKey from config.conf");
+ byte[] witnessAddress = getWitnessAddress();
+ this.localWitnesses.initWitnessAccountAddress(witnessAddress,
+ Args.PARAMETER.isECKeyCryptoEngine());
+ return true;
+ }
+
+ private void tryInitFromKeystore() {
+ if (!config.hasPath(Constant.LOCAL_WITNESS_KEYSTORE)
+ || config.getStringList(Constant.LOCAL_WITNESS_KEYSTORE).isEmpty()) {
+ return;
+ }
+
+ List localWitness = config.getStringList(Constant.LOCAL_WITNESS_KEYSTORE);
+ if (localWitness.size() > 1) {
+ logger.warn(
+ "Multiple keystores detected. Only the first keystore will be used as witness, all "
+ + "others will be ignored.");
+ }
+
+ List privateKeys = new ArrayList<>();
+ String fileName = System.getProperty("user.dir") + "/" + localWitness.get(0);
+ String password;
+ if (StringUtils.isEmpty(Args.PARAMETER.password)) {
+ System.out.println("Please input your password.");
+ password = WalletUtils.inputPassword();
+ } else {
+ password = Args.PARAMETER.password;
+ Args.PARAMETER.password = null;
+ }
+
+ try {
+ Credentials credentials = WalletUtils
+ .loadCredentials(password, new File(fileName));
+ SignInterface sign = credentials.getSignInterface();
+ String prikey = ByteArray.toHexString(sign.getPrivateKey());
+ privateKeys.add(prikey);
+ } catch (IOException | CipherException e) {
+ logger.error("Witness node start failed!");
+ throw new TronError(e, TronError.ErrCode.WITNESS_KEYSTORE_LOAD);
+ }
+
+ this.localWitnesses.setPrivateKeys(privateKeys);
+ byte[] witnessAddress = getWitnessAddress();
+ this.localWitnesses.initWitnessAccountAddress(witnessAddress,
+ Args.PARAMETER.isECKeyCryptoEngine());
+ logger.debug("Got privateKey from keystore");
+ }
+
+ private byte[] getWitnessAddress() {
+ if (!config.hasPath(Constant.LOCAL_WITNESS_ACCOUNT_ADDRESS)) {
+ return null;
+ }
+
+ if (localWitnesses.getPrivateKeys().size() != 1) {
+ throw new TronError(
+ "LocalWitnessAccountAddress can only be set when there is only one private key",
+ TronError.ErrCode.WITNESS_INIT);
+ }
+ byte[] witnessAddress = Commons
+ .decodeFromBase58Check(config.getString(Constant.LOCAL_WITNESS_ACCOUNT_ADDRESS));
+ if (witnessAddress != null) {
+ logger.debug("Got localWitnessAccountAddress from config.conf");
+ } else {
+ throw new TronError("LocalWitnessAccountAddress format from config is incorrect",
+ TronError.ErrCode.WITNESS_INIT);
+ }
+ return witnessAddress;
+ }
+}
diff --git a/framework/src/main/java/org/tron/core/consensus/ConsensusService.java b/framework/src/main/java/org/tron/core/consensus/ConsensusService.java
index ce1f1f1cf08..ef8f30ef498 100644
--- a/framework/src/main/java/org/tron/core/consensus/ConsensusService.java
+++ b/framework/src/main/java/org/tron/core/consensus/ConsensusService.java
@@ -61,17 +61,18 @@ public void start() {
logger.info("Add witness: {}, size: {}",
Hex.toHexString(privateKeyAddress), miners.size());
}
- } else {
+ } else if (privateKeys.size() == 1) {
byte[] privateKey =
fromHexString(Args.getLocalWitnesses().getPrivateKey());
byte[] privateKeyAddress = SignUtils.fromPrivate(privateKey,
Args.getInstance().isECKeyCryptoEngine()).getAddress();
- byte[] witnessAddress = Args.getLocalWitnesses().getWitnessAccountAddress(
- Args.getInstance().isECKeyCryptoEngine());
+ byte[] witnessAddress = Args.getLocalWitnesses().getWitnessAccountAddress();
WitnessCapsule witnessCapsule = witnessStore.get(witnessAddress);
if (null == witnessCapsule) {
logger.warn("Witness {} is not in witnessStore.", Hex.toHexString(witnessAddress));
}
+ // In multi-signature mode, the address derived from the private key may differ from
+ // witnessAddress.
Miner miner = param.new Miner(privateKey, ByteString.copyFrom(privateKeyAddress),
ByteString.copyFrom(witnessAddress));
miners.add(miner);
diff --git a/framework/src/main/java/org/tron/core/consensus/ProposalService.java b/framework/src/main/java/org/tron/core/consensus/ProposalService.java
index b25f0d6fa8d..51d53f6a59e 100644
--- a/framework/src/main/java/org/tron/core/consensus/ProposalService.java
+++ b/framework/src/main/java/org/tron/core/consensus/ProposalService.java
@@ -384,6 +384,14 @@ public static boolean process(Manager manager, ProposalCapsule proposalCapsule)
manager.getDynamicPropertiesStore().saveAllowTvmBlob(entry.getValue());
break;
}
+ case ALLOW_TVM_SELFDESTRUCT_RESTRICTION: {
+ manager.getDynamicPropertiesStore().saveAllowTvmSelfdestructRestriction(entry.getValue());
+ break;
+ }
+ case PROPOSAL_EXPIRE_TIME: {
+ manager.getDynamicPropertiesStore().saveProposalExpireTime(entry.getValue());
+ break;
+ }
default:
find = false;
break;
diff --git a/framework/src/main/java/org/tron/core/db/Manager.java b/framework/src/main/java/org/tron/core/db/Manager.java
index 1eecc103874..586c3d83857 100644
--- a/framework/src/main/java/org/tron/core/db/Manager.java
+++ b/framework/src/main/java/org/tron/core/db/Manager.java
@@ -48,6 +48,7 @@
import org.bouncycastle.util.encoders.Hex;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
+import org.tron.api.GrpcAPI;
import org.tron.api.GrpcAPI.TransactionInfoList;
import org.tron.common.args.GenesisBlock;
import org.tron.common.bloom.Bloom;
@@ -163,6 +164,7 @@
import org.tron.core.store.WitnessScheduleStore;
import org.tron.core.store.WitnessStore;
import org.tron.core.utils.TransactionRegister;
+import org.tron.protos.Protocol;
import org.tron.protos.Protocol.AccountType;
import org.tron.protos.Protocol.Permission;
import org.tron.protos.Protocol.Transaction;
@@ -868,9 +870,9 @@ public boolean pushTransaction(final TransactionCapsule trx)
TooBigTransactionException, TransactionExpirationException,
ReceiptCheckErrException, VMIllegalException, TooBigTransactionResultException {
- if (isShieldedTransaction(trx.getInstance()) && !Args.getInstance()
- .isFullNodeAllowShieldedTransactionArgs()) {
- return true;
+ if (isShieldedTransaction(trx.getInstance()) && !chainBaseManager.getDynamicPropertiesStore()
+ .supportShieldedTransaction()) {
+ return false;
}
pushTransactionQueue.add(trx);
@@ -1857,12 +1859,10 @@ private void processBlock(BlockCapsule block, List txs)
chainBaseManager.getBalanceTraceStore().resetCurrentBlockTrace();
- if (CommonParameter.getInstance().isJsonRpcFilterEnabled()) {
- Bloom blockBloom = chainBaseManager.getSectionBloomStore()
- .initBlockSection(transactionRetCapsule);
- chainBaseManager.getSectionBloomStore().write(block.getNum());
- block.setBloom(blockBloom);
- }
+ Bloom blockBloom = chainBaseManager.getSectionBloomStore()
+ .initBlockSection(transactionRetCapsule);
+ chainBaseManager.getSectionBloomStore().write(block.getNum());
+ block.setBloom(blockBloom);
}
private void payReward(BlockCapsule block) {
@@ -2159,25 +2159,7 @@ private void processTransactionTrigger(BlockCapsule newBlock) {
// need to set eth compatible data from transactionInfoList
if (EventPluginLoader.getInstance().isTransactionLogTriggerEthCompatible()
&& newBlock.getNum() != 0) {
- TransactionInfoList transactionInfoList = TransactionInfoList.newBuilder().build();
- TransactionInfoList.Builder transactionInfoListBuilder = TransactionInfoList.newBuilder();
-
- try {
- TransactionRetCapsule result = chainBaseManager.getTransactionRetStore()
- .getTransactionInfoByBlockNum(ByteArray.fromLong(newBlock.getNum()));
-
- if (!Objects.isNull(result) && !Objects.isNull(result.getInstance())) {
- result.getInstance().getTransactioninfoList().forEach(
- transactionInfoListBuilder::addTransactionInfo
- );
-
- transactionInfoList = transactionInfoListBuilder.build();
- }
- } catch (BadItemException e) {
- logger.error("PostBlockTrigger getTransactionInfoList blockNum = {}, error is {}.",
- newBlock.getNum(), e.getMessage());
- }
-
+ TransactionInfoList transactionInfoList = getTransactionInfoByBlockNum(newBlock.getNum());
if (transactionCapsuleList.size() == transactionInfoList.getTransactionInfoCount()) {
long cumulativeEnergyUsed = 0;
long cumulativeLogCount = 0;
@@ -2235,21 +2217,8 @@ private void postLogsFilter(final BlockCapsule blockCapsule, boolean solidified,
boolean removed) {
if (!blockCapsule.getTransactions().isEmpty()) {
long blockNumber = blockCapsule.getNum();
- List transactionInfoList = new ArrayList<>();
-
- try {
- TransactionRetCapsule result = chainBaseManager.getTransactionRetStore()
- .getTransactionInfoByBlockNum(ByteArray.fromLong(blockNumber));
-
- if (!Objects.isNull(result) && !Objects.isNull(result.getInstance())) {
- transactionInfoList.addAll(result.getInstance().getTransactioninfoList());
- }
- } catch (BadItemException e) {
- logger.error("ProcessLogsFilter getTransactionInfoList blockNum = {}, error is {}.",
- blockNumber, e.getMessage());
- return;
- }
-
+ List transactionInfoList
+ = getTransactionInfoByBlockNum(blockNumber).getTransactionInfoList();
LogsFilterCapsule logsFilterCapsule = new LogsFilterCapsule(blockNumber,
blockCapsule.getBlockId().toString(), blockCapsule.getBloom(), transactionInfoList,
solidified, removed);
@@ -2490,6 +2459,40 @@ private boolean isBlockWaitingLock() {
return blockWaitLock.get() > NO_BLOCK_WAITING_LOCK;
}
+ public TransactionInfoList getTransactionInfoByBlockNum(long blockNum) {
+ TransactionInfoList.Builder transactionInfoList = TransactionInfoList.newBuilder();
+
+ try {
+ TransactionRetCapsule result = getTransactionRetStore()
+ .getTransactionInfoByBlockNum(ByteArray.fromLong(blockNum));
+
+ if (!Objects.isNull(result) && !Objects.isNull(result.getInstance())) {
+ result.getInstance().getTransactioninfoList().forEach(
+ transactionInfo -> transactionInfoList.addTransactionInfo(transactionInfo)
+ );
+ } else {
+ Protocol.Block block = chainBaseManager.getBlockByNum(blockNum).getInstance();
+
+ if (block != null) {
+ List listTransaction = block.getTransactionsList();
+ for (Transaction transaction : listTransaction) {
+ TransactionInfoCapsule transactionInfoCapsule = getTransactionHistoryStore()
+ .get(Sha256Hash.hash(CommonParameter.getInstance()
+ .isECKeyCryptoEngine(), transaction.getRawData().toByteArray()));
+
+ if (transactionInfoCapsule != null) {
+ transactionInfoList.addTransactionInfo(transactionInfoCapsule.getInstance());
+ }
+ }
+ }
+ }
+ } catch (BadItemException | ItemNotFoundException e) {
+ logger.warn(e.getMessage());
+ }
+
+ return transactionInfoList.build();
+ }
+
public void close() {
stopRePushThread();
stopRePushTriggerThread();
diff --git a/framework/src/main/java/org/tron/core/metrics/node/NodeMetricManager.java b/framework/src/main/java/org/tron/core/metrics/node/NodeMetricManager.java
index a0aba129648..9a5ecb33213 100644
--- a/framework/src/main/java/org/tron/core/metrics/node/NodeMetricManager.java
+++ b/framework/src/main/java/org/tron/core/metrics/node/NodeMetricManager.java
@@ -4,7 +4,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.tron.common.backup.BackupManager;
-import org.tron.common.parameter.CommonParameter;
+import org.tron.common.utils.ByteArray;
import org.tron.core.ChainBaseManager;
import org.tron.core.config.args.Args;
import org.tron.program.Version;
@@ -36,8 +36,9 @@ private void setNodeInfo(NodeInfo nodeInfo) {
nodeInfo.setIp(Args.getInstance().getNodeExternalIp());
- ByteString witnessAddress = ByteString.copyFrom(Args.getLocalWitnesses()
- .getWitnessAccountAddress(CommonParameter.getInstance().isECKeyCryptoEngine()));
+ byte[] witnessAccountAddress = Args.getLocalWitnesses().getWitnessAccountAddress();
+ ByteString witnessAddress = !ByteArray.isEmpty(witnessAccountAddress) ? ByteString
+ .copyFrom(witnessAccountAddress) : null;
if (chainBaseManager.getWitnessScheduleStore().getActiveWitnesses().contains(witnessAddress)) {
nodeInfo.setNodeType(1);
} else {
diff --git a/framework/src/main/java/org/tron/core/net/P2pEventHandlerImpl.java b/framework/src/main/java/org/tron/core/net/P2pEventHandlerImpl.java
index 795c90b4edd..9cfa5058e8c 100644
--- a/framework/src/main/java/org/tron/core/net/P2pEventHandlerImpl.java
+++ b/framework/src/main/java/org/tron/core/net/P2pEventHandlerImpl.java
@@ -178,9 +178,11 @@ private void processMessage(PeerConnection peer, byte[] data) {
handshakeService.processHelloMessage(peer, (HelloMessage) msg);
break;
case P2P_DISCONNECT:
- peer.getChannel().close();
- peer.getNodeStatistics()
- .nodeDisconnectedRemote(((DisconnectMessage)msg).getReason());
+ if (peer.getP2pRateLimiter().tryAcquire(type.asByte())) {
+ peer.getChannel().close();
+ peer.getNodeStatistics()
+ .nodeDisconnectedRemote(((DisconnectMessage)msg).getReason());
+ }
break;
case SYNC_BLOCK_CHAIN:
syncBlockChainMsgHandler.processMessage(peer, msg);
@@ -253,12 +255,14 @@ private void processException(PeerConnection peer, TronMessage msg, Exception ex
code = Protocol.ReasonCode.BAD_TX;
break;
case BAD_BLOCK:
+ case BLOCK_SIGN_ERROR:
code = Protocol.ReasonCode.BAD_BLOCK;
break;
case NO_SUCH_MESSAGE:
code = Protocol.ReasonCode.NO_SUCH_MESSAGE;
break;
case BAD_MESSAGE:
+ case RATE_LIMIT_EXCEEDED:
code = Protocol.ReasonCode.BAD_PROTOCOL;
break;
case SYNC_FAILED:
diff --git a/framework/src/main/java/org/tron/core/net/P2pRateLimiter.java b/framework/src/main/java/org/tron/core/net/P2pRateLimiter.java
new file mode 100644
index 00000000000..9b36e1e5df3
--- /dev/null
+++ b/framework/src/main/java/org/tron/core/net/P2pRateLimiter.java
@@ -0,0 +1,32 @@
+package org.tron.core.net;
+
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.util.concurrent.RateLimiter;
+
+public class P2pRateLimiter {
+ private final Cache rateLimiters = CacheBuilder.newBuilder()
+ .maximumSize(32).build();
+
+ public void register(Byte type, double rate) {
+ RateLimiter rateLimiter = RateLimiter.create(Double.POSITIVE_INFINITY);
+ rateLimiter.setRate(rate);
+ rateLimiters.put(type, rateLimiter);
+ }
+
+ public void acquire(Byte type) {
+ RateLimiter rateLimiter = rateLimiters.getIfPresent(type);
+ if (rateLimiter == null) {
+ return;
+ }
+ rateLimiter.acquire();
+ }
+
+ public boolean tryAcquire(Byte type) {
+ RateLimiter rateLimiter = rateLimiters.getIfPresent(type);
+ if (rateLimiter == null) {
+ return true;
+ }
+ return rateLimiter.tryAcquire();
+ }
+}
diff --git a/framework/src/main/java/org/tron/core/net/message/handshake/HelloMessage.java b/framework/src/main/java/org/tron/core/net/message/handshake/HelloMessage.java
index 867ced5dbff..68123c93db6 100755
--- a/framework/src/main/java/org/tron/core/net/message/handshake/HelloMessage.java
+++ b/framework/src/main/java/org/tron/core/net/message/handshake/HelloMessage.java
@@ -5,6 +5,7 @@
import org.apache.commons.lang3.StringUtils;
import org.tron.common.utils.ByteArray;
import org.tron.common.utils.DecodeUtil;
+import org.tron.common.utils.Sha256Hash;
import org.tron.common.utils.StringUtil;
import org.tron.core.ChainBaseManager;
import org.tron.core.capsule.BlockCapsule;
@@ -156,17 +157,17 @@ public Protocol.HelloMessage getInstance() {
public boolean valid() {
byte[] genesisBlockByte = this.helloMessage.getGenesisBlockId().getHash().toByteArray();
- if (genesisBlockByte.length == 0) {
+ if (genesisBlockByte.length != Sha256Hash.LENGTH) {
return false;
}
byte[] solidBlockId = this.helloMessage.getSolidBlockId().getHash().toByteArray();
- if (solidBlockId.length == 0) {
+ if (solidBlockId.length != Sha256Hash.LENGTH) {
return false;
}
byte[] headBlockId = this.helloMessage.getHeadBlockId().getHash().toByteArray();
- if (headBlockId.length == 0) {
+ if (headBlockId.length != Sha256Hash.LENGTH) {
return false;
}
diff --git a/framework/src/main/java/org/tron/core/net/messagehandler/FetchInvDataMsgHandler.java b/framework/src/main/java/org/tron/core/net/messagehandler/FetchInvDataMsgHandler.java
index 5415ea435e3..ecb7853ce6f 100644
--- a/framework/src/main/java/org/tron/core/net/messagehandler/FetchInvDataMsgHandler.java
+++ b/framework/src/main/java/org/tron/core/net/messagehandler/FetchInvDataMsgHandler.java
@@ -38,7 +38,7 @@
public class FetchInvDataMsgHandler implements TronMsgHandler {
private volatile Cache epochCache = CacheBuilder.newBuilder().initialCapacity(100)
- .maximumSize(1000).expireAfterWrite(1, TimeUnit.HOURS).build();
+ .maximumSize(1000).expireAfterWrite(1, TimeUnit.HOURS).build();
private static final int MAX_SIZE = 1_000_000;
@Autowired
@@ -55,7 +55,9 @@ public void processMessage(PeerConnection peer, TronMessage msg) throws P2pExcep
FetchInvDataMessage fetchInvDataMsg = (FetchInvDataMessage) msg;
- check(peer, fetchInvDataMsg);
+ boolean isAdv = isAdvInv(peer, fetchInvDataMsg);
+
+ check(peer, fetchInvDataMsg, isAdv);
InventoryType type = fetchInvDataMsg.getInventoryType();
List transactions = Lists.newArrayList();
@@ -64,6 +66,15 @@ public void processMessage(PeerConnection peer, TronMessage msg) throws P2pExcep
for (Sha256Hash hash : fetchInvDataMsg.getHashList()) {
Item item = new Item(hash, type);
+ /* Cache the Inventory sent to the peer.
+ Once a FetchInvData message is received from the peer, remove this Inventory from the cache.
+ If the same FetchInvData request is received from the peer again and it is
+ no longer in the cache, then reject the request.
+ * */
+ if (isAdv) {
+ peer.getAdvInvSpread().invalidate(item);
+ }
+
Message message = advService.getMessage(item);
if (message == null) {
try {
@@ -84,7 +95,7 @@ public void processMessage(PeerConnection peer, TronMessage msg) throws P2pExcep
} else {
transactions.add(((TransactionMessage) message).getTransactionCapsule().getInstance());
size += ((TransactionMessage) message).getTransactionCapsule().getInstance()
- .getSerializedSize();
+ .getSerializedSize();
if (size > MAX_SIZE) {
peer.sendMessage(new TransactionsMessage(transactions));
transactions = Lists.newArrayList();
@@ -104,16 +115,16 @@ private void sendPbftCommitMessage(PeerConnection peer, BlockCapsule blockCapsul
}
long epoch = 0;
PbftSignCapsule pbftSignCapsule = tronNetDelegate
- .getBlockPbftCommitData(blockCapsule.getNum());
+ .getBlockPbftCommitData(blockCapsule.getNum());
long maintenanceTimeInterval = consensusDelegate.getDynamicPropertiesStore()
- .getMaintenanceTimeInterval();
+ .getMaintenanceTimeInterval();
if (pbftSignCapsule != null) {
Raw raw = Raw.parseFrom(pbftSignCapsule.getPbftCommitResult().getData());
epoch = raw.getEpoch();
peer.sendMessage(new PbftCommitMessage(pbftSignCapsule));
} else {
- epoch =
- (blockCapsule.getTimeStamp() / maintenanceTimeInterval + 1) * maintenanceTimeInterval;
+ epoch = (blockCapsule.getTimeStamp() / maintenanceTimeInterval + 1)
+ * maintenanceTimeInterval;
}
if (epochCache.getIfPresent(epoch) == null) {
PbftSignCapsule srl = tronNetDelegate.getSRLPbftCommitData(epoch);
@@ -127,7 +138,21 @@ private void sendPbftCommitMessage(PeerConnection peer, BlockCapsule blockCapsul
}
}
- private void check(PeerConnection peer, FetchInvDataMessage fetchInvDataMsg) throws P2pException {
+ public boolean isAdvInv(PeerConnection peer, FetchInvDataMessage msg) {
+ MessageTypes type = msg.getInvMessageType();
+ if (type == MessageTypes.TRX) {
+ return true;
+ }
+ for (Sha256Hash hash : msg.getHashList()) {
+ if (peer.getAdvInvSpread().getIfPresent(new Item(hash, InventoryType.BLOCK)) == null) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private void check(PeerConnection peer, FetchInvDataMessage fetchInvDataMsg,
+ boolean isAdv) throws P2pException {
MessageTypes type = fetchInvDataMsg.getInvMessageType();
if (type == MessageTypes.TRX) {
@@ -144,38 +169,38 @@ private void check(PeerConnection peer, FetchInvDataMessage fetchInvDataMsg) thr
+ "maxCount: {}, fetchCount: {}, peer: {}",
maxCount, fetchCount, peer.getInetAddress());
}
- } else {
- boolean isAdv = true;
+ }
+
+ if (!isAdv) {
+ if (!peer.isNeedSyncFromUs()) {
+ throw new P2pException(TypeEnum.BAD_MESSAGE, "no need sync");
+ }
+ if (!peer.getP2pRateLimiter().tryAcquire(fetchInvDataMsg.getType().asByte())) {
+ throw new P2pException(TypeEnum.RATE_LIMIT_EXCEEDED, fetchInvDataMsg.getType()
+ + " message exceeds the rate limit");
+ }
+ if (fetchInvDataMsg.getHashList().size() > NetConstants.MAX_BLOCK_FETCH_PER_PEER) {
+ throw new P2pException(TypeEnum.BAD_MESSAGE, "fetch too many blocks, size:"
+ + fetchInvDataMsg.getHashList().size());
+ }
for (Sha256Hash hash : fetchInvDataMsg.getHashList()) {
- if (peer.getAdvInvSpread().getIfPresent(new Item(hash, InventoryType.BLOCK)) == null) {
- isAdv = false;
- break;
+ long blockNum = new BlockId(hash).getNum();
+ long minBlockNum =
+ peer.getLastSyncBlockId().getNum() - 2 * NetConstants.SYNC_FETCH_BATCH_NUM;
+ if (blockNum < minBlockNum) {
+ throw new P2pException(TypeEnum.BAD_MESSAGE,
+ "minBlockNum: " + minBlockNum + ", blockNum: " + blockNum);
}
- }
- if (!isAdv) {
- if (!peer.isNeedSyncFromUs()) {
- throw new P2pException(TypeEnum.BAD_MESSAGE, "no need sync");
+ if (blockNum > peer.getLastSyncBlockId().getNum()) {
+ throw new P2pException(TypeEnum.BAD_MESSAGE,
+ "maxBlockNum: " + peer.getLastSyncBlockId().getNum() + ", blockNum: " + blockNum);
}
- for (Sha256Hash hash : fetchInvDataMsg.getHashList()) {
- long blockNum = new BlockId(hash).getNum();
- long minBlockNum =
- peer.getLastSyncBlockId().getNum() - 2 * NetConstants.SYNC_FETCH_BATCH_NUM;
- if (blockNum < minBlockNum) {
- throw new P2pException(TypeEnum.BAD_MESSAGE,
- "minBlockNum: " + minBlockNum + ", blockNum: " + blockNum);
- }
- if (blockNum > peer.getLastSyncBlockId().getNum()) {
- throw new P2pException(TypeEnum.BAD_MESSAGE,
- "maxBlockNum: " + peer.getLastSyncBlockId().getNum() + ", blockNum: " + blockNum);
- }
- if (peer.getSyncBlockIdCache().getIfPresent(hash) != null) {
- throw new P2pException(TypeEnum.BAD_MESSAGE,
- new BlockId(hash).getString() + " is exist");
- }
- peer.getSyncBlockIdCache().put(hash, System.currentTimeMillis());
+ if (peer.getSyncBlockIdCache().getIfPresent(hash) != null) {
+ throw new P2pException(TypeEnum.BAD_MESSAGE,
+ new BlockId(hash).getString() + " is exist");
}
+ peer.getSyncBlockIdCache().put(hash, System.currentTimeMillis());
}
}
}
-
-}
+}
\ No newline at end of file
diff --git a/framework/src/main/java/org/tron/core/net/messagehandler/SyncBlockChainMsgHandler.java b/framework/src/main/java/org/tron/core/net/messagehandler/SyncBlockChainMsgHandler.java
index 55446593bd0..71d268b22bc 100644
--- a/framework/src/main/java/org/tron/core/net/messagehandler/SyncBlockChainMsgHandler.java
+++ b/framework/src/main/java/org/tron/core/net/messagehandler/SyncBlockChainMsgHandler.java
@@ -58,6 +58,14 @@ public void processMessage(PeerConnection peer, TronMessage msg) throws P2pExcep
}
private boolean check(PeerConnection peer, SyncBlockChainMessage msg) throws P2pException {
+ if (peer.getRemainNum() > 0
+ && !peer.getP2pRateLimiter().tryAcquire(msg.getType().asByte())) {
+ // Discard messages that exceed the rate limit
+ logger.warn("{} message from peer {} exceeds the rate limit",
+ msg.getType(), peer.getInetSocketAddress());
+ return false;
+ }
+
List blockIds = msg.getBlockIds();
if (CollectionUtils.isEmpty(blockIds)) {
throw new P2pException(TypeEnum.BAD_MESSAGE, "SyncBlockChain blockIds is empty");
diff --git a/framework/src/main/java/org/tron/core/net/peer/PeerConnection.java b/framework/src/main/java/org/tron/core/net/peer/PeerConnection.java
index 2e08e105bed..253502bc3a1 100644
--- a/framework/src/main/java/org/tron/core/net/peer/PeerConnection.java
+++ b/framework/src/main/java/org/tron/core/net/peer/PeerConnection.java
@@ -1,5 +1,9 @@
package org.tron.core.net.peer;
+import static org.tron.core.net.message.MessageTypes.FETCH_INV_DATA;
+import static org.tron.core.net.message.MessageTypes.P2P_DISCONNECT;
+import static org.tron.core.net.message.MessageTypes.SYNC_BLOCK_CHAIN;
+
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.protobuf.ByteString;
@@ -32,6 +36,7 @@
import org.tron.core.config.args.Args;
import org.tron.core.metrics.MetricsKey;
import org.tron.core.metrics.MetricsUtil;
+import org.tron.core.net.P2pRateLimiter;
import org.tron.core.net.TronNetDelegate;
import org.tron.core.net.message.adv.InventoryMessage;
import org.tron.core.net.message.adv.TransactionsMessage;
@@ -85,7 +90,7 @@ public class PeerConnection {
@Getter
@Setter
- private TronState tronState = TronState.INIT;
+ private volatile TronState tronState = TronState.INIT;
@Autowired
private TronNetDelegate tronNetDelegate;
@@ -123,15 +128,15 @@ public class PeerConnection {
private Map- advInvRequest = new ConcurrentHashMap<>();
@Setter
- private BlockId fastForwardBlock;
+ private volatile BlockId fastForwardBlock;
@Getter
- private BlockId blockBothHave = new BlockId();
+ private volatile BlockId blockBothHave = new BlockId();
@Getter
private volatile long blockBothHaveUpdateTime = System.currentTimeMillis();
@Setter
@Getter
- private BlockId lastSyncBlockId;
+ private volatile BlockId lastSyncBlockId;
@Setter
@Getter
private volatile long remainNum;
@@ -146,7 +151,7 @@ public class PeerConnection {
private Map syncBlockRequested = new ConcurrentHashMap<>();
@Setter
@Getter
- private Pair, Long> syncChainRequested = null;
+ private volatile Pair, Long> syncChainRequested = null;
@Setter
@Getter
private Set syncBlockInProcess = new HashSet<>();
@@ -156,6 +161,8 @@ public class PeerConnection {
@Setter
@Getter
private volatile boolean needSyncFromUs = true;
+ @Getter
+ private P2pRateLimiter p2pRateLimiter = new P2pRateLimiter();
public void setChannel(Channel channel) {
this.channel = channel;
@@ -164,6 +171,12 @@ public void setChannel(Channel channel) {
}
this.nodeStatistics = TronStatsManager.getNodeStatistics(channel.getInetAddress());
lastInteractiveTime = System.currentTimeMillis();
+ p2pRateLimiter.register(SYNC_BLOCK_CHAIN.asByte(),
+ Args.getInstance().getRateLimiterSyncBlockChain());
+ p2pRateLimiter.register(FETCH_INV_DATA.asByte(),
+ Args.getInstance().getRateLimiterFetchInvData());
+ p2pRateLimiter.register(P2P_DISCONNECT.asByte(),
+ Args.getInstance().getRateLimiterDisconnect());
}
public void setBlockBothHave(BlockId blockId) {
diff --git a/framework/src/main/java/org/tron/core/net/peer/PeerStatusCheck.java b/framework/src/main/java/org/tron/core/net/peer/PeerStatusCheck.java
index 6ccbf6427a7..04eac202484 100644
--- a/framework/src/main/java/org/tron/core/net/peer/PeerStatusCheck.java
+++ b/framework/src/main/java/org/tron/core/net/peer/PeerStatusCheck.java
@@ -42,6 +42,10 @@ public void statusCheck() {
long now = System.currentTimeMillis();
+ if (tronNetDelegate == null) {
+ // only occurs in mock test. TODO fix test
+ return;
+ }
tronNetDelegate.getActivePeer().forEach(peer -> {
boolean isDisconnected = false;
diff --git a/framework/src/main/java/org/tron/core/net/service/handshake/HandshakeService.java b/framework/src/main/java/org/tron/core/net/service/handshake/HandshakeService.java
index 6cd117c83dd..070a9f56406 100644
--- a/framework/src/main/java/org/tron/core/net/service/handshake/HandshakeService.java
+++ b/framework/src/main/java/org/tron/core/net/service/handshake/HandshakeService.java
@@ -6,6 +6,7 @@
import org.springframework.stereotype.Component;
import org.tron.common.utils.ByteArray;
import org.tron.core.ChainBaseManager;
+import org.tron.core.ChainBaseManager.NodeType;
import org.tron.core.config.args.Args;
import org.tron.core.net.TronNetService;
import org.tron.core.net.message.handshake.HelloMessage;
@@ -57,7 +58,7 @@ public void processHelloMessage(PeerConnection peer, HelloMessage msg) {
msg.getInstance().getAddress().toByteArray().length,
msg.getInstance().getSignature().toByteArray().length,
msg.getInstance().getCodeVersion().toByteArray().length);
- peer.disconnect(ReasonCode.UNEXPECTED_IDENTITY);
+ peer.disconnect(ReasonCode.INCOMPATIBLE_PROTOCOL);
return;
}
@@ -96,12 +97,17 @@ public void processHelloMessage(PeerConnection peer, HelloMessage msg) {
}
if (chainBaseManager.getSolidBlockId().getNum() >= msg.getSolidBlockId().getNum()
- && !chainBaseManager.containBlockInMainChain(msg.getSolidBlockId())) {
- logger.info("Peer {} different solid block, peer->{}, me->{}",
- peer.getInetSocketAddress(),
- msg.getSolidBlockId().getString(),
- chainBaseManager.getSolidBlockId().getString());
- peer.disconnect(ReasonCode.FORKED);
+ && !chainBaseManager.containBlockInMainChain(msg.getSolidBlockId())) {
+ if (chainBaseManager.getLowestBlockNum() <= msg.getSolidBlockId().getNum()) {
+ logger.info("Peer {} different solid block, fork with me, peer->{}, me->{}",
+ peer.getInetSocketAddress(),
+ msg.getSolidBlockId().getString(),
+ chainBaseManager.getSolidBlockId().getString());
+ peer.disconnect(ReasonCode.FORKED);
+ } else {
+ logger.info("Peer {} solid block is below than my lowest", peer.getInetSocketAddress());
+ peer.disconnect(ReasonCode.LIGHT_NODE_SYNC_FAIL);
+ }
return;
}
diff --git a/framework/src/main/java/org/tron/core/net/service/relay/RelayService.java b/framework/src/main/java/org/tron/core/net/service/relay/RelayService.java
index 161e918336b..61ae6326e9f 100644
--- a/framework/src/main/java/org/tron/core/net/service/relay/RelayService.java
+++ b/framework/src/main/java/org/tron/core/net/service/relay/RelayService.java
@@ -66,11 +66,11 @@ public class RelayService {
private List fastForwardNodes = parameter.getFastForwardNodes();
- private ByteString witnessAddress = ByteString
- .copyFrom(Args.getLocalWitnesses().getWitnessAccountAddress(CommonParameter.getInstance()
- .isECKeyCryptoEngine()));
+ private final int keySize = Args.getLocalWitnesses().getPrivateKeys().size();
- private int keySize = Args.getLocalWitnesses().getPrivateKeys().size();
+ private final ByteString witnessAddress =
+ Args.getLocalWitnesses().getWitnessAccountAddress() != null ? ByteString
+ .copyFrom(Args.getLocalWitnesses().getWitnessAccountAddress()) : null;
private int maxFastForwardNum = Args.getInstance().getMaxFastForwardNum();
diff --git a/framework/src/main/java/org/tron/core/net/service/sync/SyncService.java b/framework/src/main/java/org/tron/core/net/service/sync/SyncService.java
index e387329c467..75349bd4c19 100644
--- a/framework/src/main/java/org/tron/core/net/service/sync/SyncService.java
+++ b/framework/src/main/java/org/tron/core/net/service/sync/SyncService.java
@@ -124,7 +124,11 @@ public void syncNext(PeerConnection peer) {
peer.setSyncChainRequested(new Pair<>(chainSummary, System.currentTimeMillis()));
peer.sendMessage(new SyncBlockChainMessage(chainSummary));
} catch (Exception e) {
- logger.error("Peer {} sync failed, reason: {}", peer.getInetAddress(), e);
+ if (e instanceof P2pException) {
+ logger.warn("Peer {} sync failed, reason: {}", peer.getInetAddress(), e.getMessage());
+ } else {
+ logger.error("Peer {} sync failed.", peer.getInetAddress(), e);
+ }
peer.disconnect(ReasonCode.SYNC_FAIL);
}
}
@@ -159,9 +163,8 @@ private void invalid(BlockId blockId, PeerConnection peerConnection) {
}
private LinkedList getBlockChainSummary(PeerConnection peer) throws P2pException {
-
- BlockId beginBlockId = peer.getBlockBothHave();
List blockIds = new ArrayList<>(peer.getSyncBlockToFetch());
+ BlockId beginBlockId = peer.getBlockBothHave();
List forkList = new LinkedList<>();
LinkedList summary = new LinkedList<>();
long syncBeginNumber = tronNetDelegate.getSyncBeginNumber();
@@ -323,9 +326,9 @@ private void processSyncBlock(BlockCapsule block, PeerConnection peerConnection)
for (PeerConnection peer : tronNetDelegate.getActivePeer()) {
BlockId bid = peer.getSyncBlockToFetch().peek();
if (blockId.equals(bid)) {
+ peer.setBlockBothHave(blockId);
peer.getSyncBlockToFetch().remove(bid);
if (flag) {
- peer.setBlockBothHave(blockId);
if (peer.getSyncBlockToFetch().isEmpty() && peer.isFetchAble()) {
syncNext(peer);
}
diff --git a/framework/src/main/java/org/tron/core/services/RpcApiService.java b/framework/src/main/java/org/tron/core/services/RpcApiService.java
index 8f9c6b15bb7..63e7ba03fc7 100755
--- a/framework/src/main/java/org/tron/core/services/RpcApiService.java
+++ b/framework/src/main/java/org/tron/core/services/RpcApiService.java
@@ -89,6 +89,7 @@
import org.tron.core.exception.ContractExeException;
import org.tron.core.exception.ContractValidateException;
import org.tron.core.exception.ItemNotFoundException;
+import org.tron.core.exception.MaintenanceUnavailableException;
import org.tron.core.exception.NonUniqueObjectException;
import org.tron.core.exception.StoreException;
import org.tron.core.exception.VMIllegalException;
@@ -291,20 +292,6 @@ private StatusRuntimeException getRunTimeException(Exception e) {
}
}
- private void checkSupportShieldedTransaction() throws ZksnarkException {
- String msg = "Not support Shielded Transaction, need to be opened by the committee";
- if (!dbManager.getDynamicPropertiesStore().supportShieldedTransaction()) {
- throw new ZksnarkException(msg);
- }
- }
-
- private void checkSupportShieldedTRC20Transaction() throws ZksnarkException {
- String msg = "Not support Shielded TRC20 Transaction, need to be opened by the committee";
- if (!dbManager.getDynamicPropertiesStore().supportShieldedTRC20Transaction()) {
- throw new ZksnarkException(msg);
- }
- }
-
/**
* DatabaseApi.
*/
@@ -396,6 +383,18 @@ public void listWitnesses(EmptyMessage request, StreamObserver resp
responseObserver.onCompleted();
}
+ @Override
+ public void getPaginatedNowWitnessList(PaginatedMessage request,
+ StreamObserver responseObserver) {
+ try {
+ responseObserver.onNext(
+ wallet.getPaginatedNowWitnessList(request.getOffset(), request.getLimit()));
+ } catch (MaintenanceUnavailableException e) {
+ responseObserver.onError(getRunTimeException(e));
+ }
+ responseObserver.onCompleted();
+ }
+
@Override
public void getAssetIssueList(EmptyMessage request,
StreamObserver responseObserver) {
@@ -651,8 +650,6 @@ public void getMerkleTreeVoucherInfo(OutputPointInfo request,
StreamObserver responseObserver) {
try {
- checkSupportShieldedTransaction();
-
IncrementalMerkleVoucherInfo witnessInfo = wallet
.getMerkleTreeVoucherInfo(request);
responseObserver.onNext(witnessInfo);
@@ -669,8 +666,6 @@ public void scanNoteByIvk(GrpcAPI.IvkDecryptParameters request,
long endNum = request.getEndBlockIndex();
try {
- checkSupportShieldedTransaction();
-
DecryptNotes decryptNotes = wallet
.scanNoteByIvk(startNum, endNum, request.getIvk().toByteArray());
responseObserver.onNext(decryptNotes);
@@ -687,8 +682,6 @@ public void scanAndMarkNoteByIvk(GrpcAPI.IvkDecryptAndMarkParameters request,
long endNum = request.getEndBlockIndex();
try {
- checkSupportShieldedTransaction();
-
DecryptNotesMarked decryptNotes = wallet.scanAndMarkNoteByIvk(startNum, endNum,
request.getIvk().toByteArray(),
request.getAk().toByteArray(),
@@ -707,8 +700,6 @@ public void scanNoteByOvk(GrpcAPI.OvkDecryptParameters request,
long startNum = request.getStartBlockIndex();
long endNum = request.getEndBlockIndex();
try {
- checkSupportShieldedTransaction();
-
DecryptNotes decryptNotes = wallet
.scanNoteByOvk(startNum, endNum, request.getOvk().toByteArray());
responseObserver.onNext(decryptNotes);
@@ -721,8 +712,6 @@ public void scanNoteByOvk(GrpcAPI.OvkDecryptParameters request,
@Override
public void isSpend(NoteParameters request, StreamObserver responseObserver) {
try {
- checkSupportShieldedTransaction();
-
responseObserver.onNext(wallet.isSpend(request));
} catch (Exception e) {
responseObserver.onError(getRunTimeException(e));
@@ -742,7 +731,6 @@ public void scanShieldedTRC20NotesByIvk(IvkDecryptTRC20Parameters request,
ProtocolStringList topicsList = request.getEventsList();
try {
- checkSupportShieldedTRC20Transaction();
responseObserver.onNext(
wallet.scanShieldedTRC20NotesByIvk(startNum, endNum, contractAddress, ivk, ak, nk,
topicsList));
@@ -762,7 +750,6 @@ public void scanShieldedTRC20NotesByOvk(OvkDecryptTRC20Parameters request,
byte[] ovk = request.getOvk().toByteArray();
ProtocolStringList topicList = request.getEventsList();
try {
- checkSupportShieldedTRC20Transaction();
responseObserver
.onNext(wallet
.scanShieldedTRC20NotesByOvk(startNum, endNum, ovk, contractAddress, topicList));
@@ -776,7 +763,6 @@ public void scanShieldedTRC20NotesByOvk(OvkDecryptTRC20Parameters request,
public void isShieldedTRC20ContractNoteSpent(NfTRC20Parameters request,
StreamObserver responseObserver) {
try {
- checkSupportShieldedTRC20Transaction();
responseObserver.onNext(wallet.isShieldedTRC20ContractNoteSpent(request));
} catch (Exception e) {
responseObserver.onError(getRunTimeException(e));
@@ -1872,6 +1858,18 @@ public void listWitnesses(EmptyMessage request,
responseObserver.onCompleted();
}
+ @Override
+ public void getPaginatedNowWitnessList(PaginatedMessage request,
+ StreamObserver responseObserver) {
+ try {
+ responseObserver.onNext(
+ wallet.getPaginatedNowWitnessList(request.getOffset(), request.getLimit()));
+ } catch (MaintenanceUnavailableException e) {
+ responseObserver.onError(getRunTimeException(e));
+ }
+ responseObserver.onCompleted();
+ }
+
@Override
public void listProposals(EmptyMessage request,
StreamObserver responseObserver) {
@@ -2066,8 +2064,6 @@ public void getMerkleTreeVoucherInfo(OutputPointInfo request,
StreamObserver responseObserver) {
try {
- checkSupportShieldedTransaction();
-
IncrementalMerkleVoucherInfo witnessInfo = wallet
.getMerkleTreeVoucherInfo(request);
responseObserver.onNext(witnessInfo);
@@ -2087,8 +2083,6 @@ public void createShieldedTransaction(PrivateParameters request,
Return.Builder retBuilder = Return.newBuilder();
try {
- checkSupportShieldedTransaction();
-
TransactionCapsule trx = wallet.createShieldedTransaction(request);
trxExtBuilder.setTransaction(trx.getInstance());
trxExtBuilder.setTxid(trx.getTransactionId().getByteString());
@@ -2118,8 +2112,6 @@ public void createShieldedTransactionWithoutSpendAuthSig(PrivateParametersWithou
Return.Builder retBuilder = Return.newBuilder();
try {
- checkSupportShieldedTransaction();
-
TransactionCapsule trx = wallet.createShieldedTransactionWithoutSpendAuthSig(request);
trxExtBuilder.setTransaction(trx.getInstance());
trxExtBuilder.setTxid(trx.getTransactionId().getByteString());
@@ -2147,8 +2139,6 @@ public void getNewShieldedAddress(EmptyMessage request,
StreamObserver responseObserver) {
try {
- checkSupportShieldedTRC20Transaction();
-
responseObserver.onNext(wallet.getNewShieldedAddress());
} catch (Exception e) {
responseObserver.onError(getRunTimeException(e));
@@ -2161,8 +2151,6 @@ public void getNewShieldedAddress(EmptyMessage request,
public void getSpendingKey(EmptyMessage request,
StreamObserver responseObserver) {
try {
- checkSupportShieldedTRC20Transaction();
-
responseObserver.onNext(wallet.getSpendingKey());
} catch (Exception e) {
responseObserver.onError(getRunTimeException(e));
@@ -2175,8 +2163,6 @@ public void getSpendingKey(EmptyMessage request,
public void getRcm(EmptyMessage request,
StreamObserver responseObserver) {
try {
- checkSupportShieldedTRC20Transaction();
-
responseObserver.onNext(wallet.getRcm());
} catch (Exception e) {
responseObserver.onError(getRunTimeException(e));
@@ -2191,8 +2177,6 @@ public void getExpandedSpendingKey(BytesMessage request,
ByteString spendingKey = request.getValue();
try {
- checkSupportShieldedTRC20Transaction();
-
ExpandedSpendingKeyMessage response = wallet.getExpandedSpendingKey(spendingKey);
responseObserver.onNext(response);
} catch (BadItemException | ZksnarkException e) {
@@ -2208,8 +2192,6 @@ public void getAkFromAsk(BytesMessage request, StreamObserver resp
ByteString ak = request.getValue();
try {
- checkSupportShieldedTRC20Transaction();
-
responseObserver.onNext(wallet.getAkFromAsk(ak));
} catch (BadItemException | ZksnarkException e) {
responseObserver.onError(getRunTimeException(e));
@@ -2224,8 +2206,6 @@ public void getNkFromNsk(BytesMessage request, StreamObserver resp
ByteString nk = request.getValue();
try {
- checkSupportShieldedTRC20Transaction();
-
responseObserver.onNext(wallet.getNkFromNsk(nk));
} catch (BadItemException | ZksnarkException e) {
responseObserver.onError(getRunTimeException(e));
@@ -2242,8 +2222,6 @@ public void getIncomingViewingKey(ViewingKeyMessage request,
ByteString nk = request.getNk();
try {
- checkSupportShieldedTRC20Transaction();
-
responseObserver.onNext(wallet.getIncomingViewingKey(ak.toByteArray(), nk.toByteArray()));
} catch (ZksnarkException e) {
responseObserver.onError(getRunTimeException(e));
@@ -2257,8 +2235,6 @@ public void getIncomingViewingKey(ViewingKeyMessage request,
public void getDiversifier(EmptyMessage request,
StreamObserver responseObserver) {
try {
- checkSupportShieldedTRC20Transaction();
-
DiversifierMessage d = wallet.getDiversifier();
responseObserver.onNext(d);
} catch (ZksnarkException e) {
@@ -2276,8 +2252,6 @@ public void getZenPaymentAddress(IncomingViewingKeyDiversifierMessage request,
DiversifierMessage d = request.getD();
try {
- checkSupportShieldedTRC20Transaction();
-
PaymentAddressMessage saplingPaymentAddressMessage =
wallet.getPaymentAddress(new IncomingViewingKey(ivk.getIvk().toByteArray()),
new DiversifierT(d.getD().toByteArray()));
@@ -2298,8 +2272,6 @@ public void scanNoteByIvk(GrpcAPI.IvkDecryptParameters request,
long endNum = request.getEndBlockIndex();
try {
- checkSupportShieldedTransaction();
-
DecryptNotes decryptNotes = wallet
.scanNoteByIvk(startNum, endNum, request.getIvk().toByteArray());
responseObserver.onNext(decryptNotes);
@@ -2318,8 +2290,6 @@ public void scanAndMarkNoteByIvk(GrpcAPI.IvkDecryptAndMarkParameters request,
long endNum = request.getEndBlockIndex();
try {
- checkSupportShieldedTransaction();
-
DecryptNotesMarked decryptNotes = wallet.scanAndMarkNoteByIvk(startNum, endNum,
request.getIvk().toByteArray(),
request.getAk().toByteArray(),
@@ -2340,8 +2310,6 @@ public void scanNoteByOvk(GrpcAPI.OvkDecryptParameters request,
long endNum = request.getEndBlockIndex();
try {
- checkSupportShieldedTransaction();
-
DecryptNotes decryptNotes = wallet
.scanNoteByOvk(startNum, endNum, request.getOvk().toByteArray());
responseObserver.onNext(decryptNotes);
@@ -2355,8 +2323,6 @@ public void scanNoteByOvk(GrpcAPI.OvkDecryptParameters request,
@Override
public void isSpend(NoteParameters request, StreamObserver responseObserver) {
try {
- checkSupportShieldedTransaction();
-
responseObserver.onNext(wallet.isSpend(request));
} catch (Exception e) {
responseObserver.onError(getRunTimeException(e));
@@ -2369,8 +2335,6 @@ public void isSpend(NoteParameters request, StreamObserver response
public void createShieldNullifier(GrpcAPI.NfParameters request,
StreamObserver responseObserver) {
try {
- checkSupportShieldedTransaction();
-
BytesMessage nf = wallet
.createShieldNullifier(request);
responseObserver.onNext(nf);
@@ -2385,8 +2349,6 @@ public void createShieldNullifier(GrpcAPI.NfParameters request,
public void createSpendAuthSig(SpendAuthSigParameters request,
StreamObserver responseObserver) {
try {
- checkSupportShieldedTRC20Transaction();
-
BytesMessage spendAuthSig = wallet.createSpendAuthSig(request);
responseObserver.onNext(spendAuthSig);
} catch (Exception e) {
@@ -2400,8 +2362,6 @@ public void createSpendAuthSig(SpendAuthSigParameters request,
public void getShieldTransactionHash(Transaction request,
StreamObserver responseObserver) {
try {
- checkSupportShieldedTransaction();
-
BytesMessage transactionHash = wallet.getShieldTransactionHash(request);
responseObserver.onNext(transactionHash);
} catch (Exception e) {
@@ -2416,8 +2376,6 @@ public void createShieldedContractParameters(
PrivateShieldedTRC20Parameters request,
StreamObserver responseObserver) {
try {
- checkSupportShieldedTRC20Transaction();
-
ShieldedTRC20Parameters shieldedTRC20Parameters = wallet
.createShieldedContractParameters(request);
responseObserver.onNext(shieldedTRC20Parameters);
@@ -2438,8 +2396,6 @@ public void createShieldedContractParametersWithoutAsk(
PrivateShieldedTRC20ParametersWithoutAsk request,
StreamObserver responseObserver) {
try {
- checkSupportShieldedTRC20Transaction();
-
ShieldedTRC20Parameters shieldedTRC20Parameters = wallet
.createShieldedContractParametersWithoutAsk(request);
responseObserver.onNext(shieldedTRC20Parameters);
@@ -2459,8 +2415,6 @@ public void scanShieldedTRC20NotesByIvk(
long startNum = request.getStartBlockIndex();
long endNum = request.getEndBlockIndex();
try {
- checkSupportShieldedTRC20Transaction();
-
DecryptNotesTRC20 decryptNotes = wallet.scanShieldedTRC20NotesByIvk(startNum, endNum,
request.getShieldedTRC20ContractAddress().toByteArray(),
request.getIvk().toByteArray(),
@@ -2487,8 +2441,6 @@ public void scanShieldedTRC20NotesByOvk(
long startNum = request.getStartBlockIndex();
long endNum = request.getEndBlockIndex();
try {
- checkSupportShieldedTRC20Transaction();
-
DecryptNotesTRC20 decryptNotes = wallet.scanShieldedTRC20NotesByOvk(startNum, endNum,
request.getOvk().toByteArray(),
request.getShieldedTRC20ContractAddress().toByteArray(),
@@ -2506,8 +2458,6 @@ public void scanShieldedTRC20NotesByOvk(
public void isShieldedTRC20ContractNoteSpent(NfTRC20Parameters request,
StreamObserver responseObserver) {
try {
- checkSupportShieldedTRC20Transaction();
-
GrpcAPI.NullifierResult nf = wallet
.isShieldedTRC20ContractNoteSpent(request);
responseObserver.onNext(nf);
@@ -2523,8 +2473,6 @@ public void getTriggerInputForShieldedTRC20Contract(
ShieldedTRC20TriggerContractParameters request,
StreamObserver responseObserver) {
try {
- checkSupportShieldedTRC20Transaction();
-
responseObserver.onNext(wallet.getTriggerInputForShieldedTRC20Contract(request));
} catch (Exception e) {
responseObserver.onError(e);
diff --git a/framework/src/main/java/org/tron/core/services/event/BlockEventGet.java b/framework/src/main/java/org/tron/core/services/event/BlockEventGet.java
index bf668a3e0b6..122a61222c3 100644
--- a/framework/src/main/java/org/tron/core/services/event/BlockEventGet.java
+++ b/framework/src/main/java/org/tron/core/services/event/BlockEventGet.java
@@ -57,10 +57,16 @@ public BlockEvent getBlockEvent(long blockNum) throws Exception {
BlockCapsule block = manager.getChainBaseManager().getBlockByNum(blockNum);
block.getTransactions().forEach(t -> t.setBlockNum(block.getNum()));
long solidNum = manager.getDynamicPropertiesStore().getLatestSolidifiedBlockNum();
+ long headNum = manager.getHeadBlockNum();
+ // solve the single SR concurrency problem
+ if (solidNum >= headNum && headNum > 0) {
+ solidNum = headNum - 1;
+ }
BlockEvent blockEvent = new BlockEvent();
blockEvent.setBlockId(block.getBlockId());
blockEvent.setParentId(block.getParentBlockId());
blockEvent.setSolidId(manager.getChainBaseManager().getBlockIdByNum(solidNum));
+
if (instance.isBlockLogTriggerEnable()) {
blockEvent.setBlockLogTriggerCapsule(getBlockLogTrigger(block, solidNum));
}
@@ -88,18 +94,13 @@ public BlockEvent getBlockEvent(long blockNum) throws Exception {
}
public SmartContractTrigger getContractTrigger(BlockCapsule block, long solidNum) {
- TransactionRetCapsule result;
- try {
- result = manager.getChainBaseManager().getTransactionRetStore()
- .getTransactionInfoByBlockNum(ByteArray.fromLong(block.getNum()));
- } catch (BadItemException e) {
- throw new RuntimeException(e);
- }
+
+ GrpcAPI.TransactionInfoList list = manager.getTransactionInfoByBlockNum(block.getNum());
SmartContractTrigger contractTrigger = new SmartContractTrigger();
for (int i = 0; i < block.getTransactions().size(); i++) {
Protocol.Transaction tx = block.getInstance().getTransactions(i);
- Protocol.TransactionInfo txInfo = result.getInstance().getTransactioninfo(i);
+ Protocol.TransactionInfo txInfo = list.getTransactionInfo(i);
List triggers = parseLogs(tx, txInfo);
for (ContractTrigger trigger : triggers) {
@@ -328,22 +329,10 @@ public List getTransactionLogTrigger(BlockCapsule
if (!EventPluginLoader.getInstance().isTransactionLogTriggerEthCompatible()) {
return getTransactionTriggers(block, solidNum);
}
+
+ GrpcAPI.TransactionInfoList transactionInfoList
+ = manager.getTransactionInfoByBlockNum(block.getNum());
List transactionCapsuleList = block.getTransactions();
- GrpcAPI.TransactionInfoList transactionInfoList = GrpcAPI
- .TransactionInfoList.newBuilder().build();
- GrpcAPI.TransactionInfoList.Builder transactionInfoListBuilder = GrpcAPI
- .TransactionInfoList.newBuilder();
- try {
- TransactionRetCapsule result = manager.getChainBaseManager().getTransactionRetStore()
- .getTransactionInfoByBlockNum(ByteArray.fromLong(block.getNum()));
- if (!Objects.isNull(result) && !Objects.isNull(result.getInstance())) {
- result.getInstance().getTransactioninfoList()
- .forEach(transactionInfoListBuilder::addTransactionInfo);
- transactionInfoList = transactionInfoListBuilder.build();
- }
- } catch (BadItemException e) {
- logger.error("Get TransactionInfo failed, blockNum {}, {}.", block.getNum(), e.getMessage());
- }
if (transactionCapsuleList.size() != transactionInfoList.getTransactionInfoCount()) {
logger.error("Get TransactionInfo size not eq, blockNum {}, {}, {}",
block.getNum(), transactionCapsuleList.size(),
@@ -384,22 +373,8 @@ public List getTransactionTriggers(BlockCapsule bl
return list;
}
- GrpcAPI.TransactionInfoList transactionInfoList = GrpcAPI
- .TransactionInfoList.newBuilder().build();
- GrpcAPI.TransactionInfoList.Builder transactionInfoListBuilder = GrpcAPI
- .TransactionInfoList.newBuilder();
- try {
- TransactionRetCapsule result = manager.getChainBaseManager().getTransactionRetStore()
- .getTransactionInfoByBlockNum(ByteArray.fromLong(block.getNum()));
- if (!Objects.isNull(result) && !Objects.isNull(result.getInstance())) {
- result.getInstance().getTransactioninfoList()
- .forEach(transactionInfoListBuilder::addTransactionInfo);
- transactionInfoList = transactionInfoListBuilder.build();
- }
- } catch (Exception e) {
- logger.warn("Get TransactionInfo failed, blockNum {}, {}.", block.getNum(), e.getMessage());
- }
-
+ GrpcAPI.TransactionInfoList transactionInfoList
+ = manager.getTransactionInfoByBlockNum(block.getNum());
if (block.getTransactions().size() != transactionInfoList.getTransactionInfoCount()) {
for (TransactionCapsule t : block.getTransactions()) {
TransactionLogTriggerCapsule triggerCapsule = new TransactionLogTriggerCapsule(t, block);
diff --git a/framework/src/main/java/org/tron/core/services/event/HistoryEventService.java b/framework/src/main/java/org/tron/core/services/event/HistoryEventService.java
index 8f79ee47a3c..2eccb9fa2a9 100644
--- a/framework/src/main/java/org/tron/core/services/event/HistoryEventService.java
+++ b/framework/src/main/java/org/tron/core/services/event/HistoryEventService.java
@@ -29,6 +29,8 @@ public class HistoryEventService {
@Autowired
private Manager manager;
+ private volatile boolean isClosed = false;
+
private volatile Thread thread;
public void init() {
@@ -44,8 +46,15 @@ public void init() {
}
public void close() {
+ isClosed = true;
if (thread != null) {
- thread.interrupt();
+ try {
+ thread.interrupt();
+ thread.join(1000);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ logger.warn("Wait close timeout, {}", e.getMessage());
+ }
}
logger.info("History event service close.");
}
@@ -54,7 +63,10 @@ private void syncEvent() {
try {
long tmp = instance.getStartSyncBlockNum();
long endNum = manager.getDynamicPropertiesStore().getLatestSolidifiedBlockNum();
- while (tmp <= endNum) {
+ while (tmp < endNum) {
+ if (thread.isInterrupted() || isClosed) {
+ throw new InterruptedException();
+ }
if (instance.isUseNativeQueue()) {
Thread.sleep(20);
} else if (instance.isBusy()) {
@@ -67,7 +79,8 @@ private void syncEvent() {
tmp++;
endNum = manager.getDynamicPropertiesStore().getLatestSolidifiedBlockNum();
}
- initEventService(manager.getChainBaseManager().getBlockIdByNum(endNum));
+ long startNum = endNum == 0 ? 0 : endNum - 1;
+ initEventService(manager.getChainBaseManager().getBlockIdByNum(startNum));
} catch (InterruptedException e1) {
logger.warn("History event service interrupted.");
Thread.currentThread().interrupt();
diff --git a/framework/src/main/java/org/tron/core/services/http/FullNodeHttpApiService.java b/framework/src/main/java/org/tron/core/services/http/FullNodeHttpApiService.java
index 76785218096..3ad4ace62fc 100644
--- a/framework/src/main/java/org/tron/core/services/http/FullNodeHttpApiService.java
+++ b/framework/src/main/java/org/tron/core/services/http/FullNodeHttpApiService.java
@@ -86,6 +86,8 @@ public class FullNodeHttpApiService extends HttpService {
@Autowired
private ListWitnessesServlet listWitnessesServlet;
@Autowired
+ private GetPaginatedNowWitnessListServlet getPaginatedNowWitnessListServlet;
+ @Autowired
private GetAssetIssueListServlet getAssetIssueListServlet;
@Autowired
private GetPaginatedAssetIssueListServlet getPaginatedAssetIssueListServlet;
@@ -342,7 +344,11 @@ protected void addServlet(ServletContextHandler context) {
context.addServlet(
new ServletHolder(getTransactionCountByBlockNumServlet),
"/wallet/gettransactioncountbyblocknum");
+ // Get the list of witnesses info with contains vote counts for last epoch/maintenance
context.addServlet(new ServletHolder(listWitnessesServlet), "/wallet/listwitnesses");
+ // Get the paged list of witnesses info with realtime vote counts
+ context.addServlet(new ServletHolder(getPaginatedNowWitnessListServlet),
+ "/wallet/getpaginatednowwitnesslist");
context.addServlet(new ServletHolder(getAssetIssueListServlet), "/wallet/getassetissuelist");
context.addServlet(
new ServletHolder(getPaginatedAssetIssueListServlet),
diff --git a/framework/src/main/java/org/tron/core/services/http/GetPaginatedNowWitnessListServlet.java b/framework/src/main/java/org/tron/core/services/http/GetPaginatedNowWitnessListServlet.java
new file mode 100644
index 00000000000..e53ab6610ec
--- /dev/null
+++ b/framework/src/main/java/org/tron/core/services/http/GetPaginatedNowWitnessListServlet.java
@@ -0,0 +1,52 @@
+package org.tron.core.services.http;
+
+import java.io.IOException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.tron.api.GrpcAPI;
+import org.tron.core.Wallet;
+import org.tron.core.exception.MaintenanceUnavailableException;
+
+// Get the paged list of witnesses info with realtime vote counts
+@Component
+@Slf4j(topic = "API")
+public class GetPaginatedNowWitnessListServlet extends RateLimiterServlet {
+
+ @Autowired
+ private Wallet wallet;
+
+ protected void doGet(HttpServletRequest request, HttpServletResponse response) {
+ try {
+ boolean visible = Util.getVisible(request);
+ long offset = Long.parseLong(request.getParameter("offset"));
+ long limit = Long.parseLong(request.getParameter("limit"));
+ fillResponse(offset, limit, visible, response);
+ } catch (Exception e) {
+ Util.processError(e, response);
+ }
+ }
+
+ protected void doPost(HttpServletRequest request, HttpServletResponse response) {
+ try {
+ PostParams params = PostParams.getPostParams(request);
+ GrpcAPI.PaginatedMessage.Builder build = GrpcAPI.PaginatedMessage.newBuilder();
+ JsonFormat.merge(params.getParams(), build, params.isVisible());
+ fillResponse(build.getOffset(), build.getLimit(), params.isVisible(), response);
+ } catch (Exception e) {
+ Util.processError(e, response);
+ }
+ }
+
+ private void fillResponse(long offset, long limit, boolean visible, HttpServletResponse response)
+ throws IOException, MaintenanceUnavailableException {
+ GrpcAPI.WitnessList reply = wallet.getPaginatedNowWitnessList(offset, limit);
+ if (reply != null) {
+ response.getWriter().println(JsonFormat.printToString(reply, visible));
+ } else {
+ response.getWriter().println("{}");
+ }
+ }
+}
diff --git a/framework/src/main/java/org/tron/core/services/http/GetProposalByIdServlet.java b/framework/src/main/java/org/tron/core/services/http/GetProposalByIdServlet.java
index a903a5b4920..9d7805d4f98 100644
--- a/framework/src/main/java/org/tron/core/services/http/GetProposalByIdServlet.java
+++ b/framework/src/main/java/org/tron/core/services/http/GetProposalByIdServlet.java
@@ -26,7 +26,7 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) {
try {
boolean visible = Util.getVisible(request);
String input = request.getParameter("id");
- long id = new Long(input);
+ long id = Long.parseLong(input);
fillResponse(ByteString.copyFrom(ByteArray.fromLong(id)), visible, response);
} catch (Exception e) {
Util.processError(e, response);
diff --git a/framework/src/main/java/org/tron/core/services/http/solidity/SolidityNodeHttpApiService.java b/framework/src/main/java/org/tron/core/services/http/solidity/SolidityNodeHttpApiService.java
index ea08d2d42cf..359adfc2b39 100644
--- a/framework/src/main/java/org/tron/core/services/http/solidity/SolidityNodeHttpApiService.java
+++ b/framework/src/main/java/org/tron/core/services/http/solidity/SolidityNodeHttpApiService.java
@@ -44,6 +44,7 @@
import org.tron.core.services.http.GetNodeInfoServlet;
import org.tron.core.services.http.GetNowBlockServlet;
import org.tron.core.services.http.GetPaginatedAssetIssueListServlet;
+import org.tron.core.services.http.GetPaginatedNowWitnessListServlet;
import org.tron.core.services.http.GetRewardServlet;
import org.tron.core.services.http.GetTransactionCountByBlockNumServlet;
import org.tron.core.services.http.GetTransactionInfoByBlockNumServlet;
@@ -92,6 +93,8 @@ public class SolidityNodeHttpApiService extends HttpService {
@Autowired
private ListWitnessesServlet listWitnessesServlet;
@Autowired
+ private GetPaginatedNowWitnessListServlet getPaginatedNowWitnessListServlet;
+ @Autowired
private GetAssetIssueListServlet getAssetIssueListServlet;
@Autowired
private GetPaginatedAssetIssueListServlet getPaginatedAssetIssueListServlet;
@@ -174,6 +177,8 @@ protected void addServlet(ServletContextHandler context) {
// same as FullNode
context.addServlet(new ServletHolder(getAccountServlet), "/walletsolidity/getaccount");
context.addServlet(new ServletHolder(listWitnessesServlet), "/walletsolidity/listwitnesses");
+ context.addServlet(new ServletHolder(getPaginatedNowWitnessListServlet),
+ "/walletsolidity/getpaginatednowwitnesslist");
context.addServlet(new ServletHolder(getAssetIssueListServlet),
"/walletsolidity/getassetissuelist");
context.addServlet(new ServletHolder(getPaginatedAssetIssueListServlet),
diff --git a/framework/src/main/java/org/tron/core/services/interfaceOnSolidity/RpcApiServiceOnSolidity.java b/framework/src/main/java/org/tron/core/services/interfaceOnSolidity/RpcApiServiceOnSolidity.java
index aa566f56042..315d70df8d6 100755
--- a/framework/src/main/java/org/tron/core/services/interfaceOnSolidity/RpcApiServiceOnSolidity.java
+++ b/framework/src/main/java/org/tron/core/services/interfaceOnSolidity/RpcApiServiceOnSolidity.java
@@ -162,6 +162,13 @@ public void listWitnesses(EmptyMessage request, StreamObserver resp
() -> rpcApiService.getWalletSolidityApi().listWitnesses(request, responseObserver));
}
+ public void getPaginatedNowWitnessList(PaginatedMessage request,
+ StreamObserver responseObserver) {
+ walletOnSolidity.futureGet(
+ () -> rpcApiService.getWalletSolidityApi()
+ .getPaginatedNowWitnessList(request, responseObserver));
+ }
+
@Override
public void getAssetIssueById(BytesMessage request,
StreamObserver responseObserver) {
diff --git a/framework/src/main/java/org/tron/core/services/interfaceOnSolidity/http/GetPaginatedNowWitnessListOnSolidityServlet.java b/framework/src/main/java/org/tron/core/services/interfaceOnSolidity/http/GetPaginatedNowWitnessListOnSolidityServlet.java
new file mode 100644
index 00000000000..4578393ec76
--- /dev/null
+++ b/framework/src/main/java/org/tron/core/services/interfaceOnSolidity/http/GetPaginatedNowWitnessListOnSolidityServlet.java
@@ -0,0 +1,24 @@
+package org.tron.core.services.interfaceOnSolidity.http;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.tron.core.services.http.GetPaginatedNowWitnessListServlet;
+import org.tron.core.services.interfaceOnSolidity.WalletOnSolidity;
+
+@Component
+@Slf4j(topic = "API")
+public class GetPaginatedNowWitnessListOnSolidityServlet extends GetPaginatedNowWitnessListServlet {
+ @Autowired
+ private WalletOnSolidity walletOnSolidity;
+
+ protected void doGet(HttpServletRequest request, HttpServletResponse response) {
+ walletOnSolidity.futureGet(() -> super.doGet(request, response));
+ }
+
+ protected void doPost(HttpServletRequest request, HttpServletResponse response) {
+ walletOnSolidity.futureGet(() -> super.doPost(request, response));
+ }
+}
diff --git a/framework/src/main/java/org/tron/core/services/interfaceOnSolidity/http/solidity/HttpApiOnSolidityService.java b/framework/src/main/java/org/tron/core/services/interfaceOnSolidity/http/solidity/HttpApiOnSolidityService.java
index b1d940ce2cd..f69597959f8 100644
--- a/framework/src/main/java/org/tron/core/services/interfaceOnSolidity/http/solidity/HttpApiOnSolidityService.java
+++ b/framework/src/main/java/org/tron/core/services/interfaceOnSolidity/http/solidity/HttpApiOnSolidityService.java
@@ -3,8 +3,6 @@
import java.util.EnumSet;
import javax.servlet.DispatcherType;
import lombok.extern.slf4j.Slf4j;
-import org.eclipse.jetty.server.ConnectionLimit;
-import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
@@ -46,6 +44,7 @@
import org.tron.core.services.interfaceOnSolidity.http.GetNodeInfoOnSolidityServlet;
import org.tron.core.services.interfaceOnSolidity.http.GetNowBlockOnSolidityServlet;
import org.tron.core.services.interfaceOnSolidity.http.GetPaginatedAssetIssueListOnSolidityServlet;
+import org.tron.core.services.interfaceOnSolidity.http.GetPaginatedNowWitnessListOnSolidityServlet;
import org.tron.core.services.interfaceOnSolidity.http.GetRewardOnSolidityServlet;
import org.tron.core.services.interfaceOnSolidity.http.GetTransactionCountByBlockNumOnSolidityServlet;
import org.tron.core.services.interfaceOnSolidity.http.GetTransactionInfoByBlockNumOnSolidityServlet;
@@ -60,7 +59,6 @@
import org.tron.core.services.interfaceOnSolidity.http.ScanShieldedTRC20NotesByOvkOnSolidityServlet;
import org.tron.core.services.interfaceOnSolidity.http.TriggerConstantContractOnSolidityServlet;
-
@Slf4j(topic = "API")
public class HttpApiOnSolidityService extends HttpService {
@@ -74,6 +72,8 @@ public class HttpApiOnSolidityService extends HttpService {
@Autowired
private ListWitnessesOnSolidityServlet listWitnessesOnSolidityServlet;
@Autowired
+ private GetPaginatedNowWitnessListOnSolidityServlet getPaginatedNowWitnessListOnSolidityServlet;
+ @Autowired
private GetAssetIssueListOnSolidityServlet getAssetIssueListOnSolidityServlet;
@Autowired
private GetPaginatedAssetIssueListOnSolidityServlet getPaginatedAssetIssueListOnSolidityServlet;
@@ -189,6 +189,8 @@ protected void addServlet(ServletContextHandler context) {
context.addServlet(new ServletHolder(accountOnSolidityServlet), "/walletsolidity/getaccount");
context.addServlet(new ServletHolder(listWitnessesOnSolidityServlet),
"/walletsolidity/listwitnesses");
+ context.addServlet(new ServletHolder(getPaginatedNowWitnessListOnSolidityServlet),
+ "/walletsolidity/getpaginatednowwitnesslist");
context.addServlet(new ServletHolder(getAssetIssueListOnSolidityServlet),
"/walletsolidity/getassetissuelist");
context.addServlet(new ServletHolder(getPaginatedAssetIssueListOnSolidityServlet),
diff --git a/framework/src/main/java/org/tron/core/services/jsonrpc/JsonRpcApiUtil.java b/framework/src/main/java/org/tron/core/services/jsonrpc/JsonRpcApiUtil.java
index 955ba55060f..4a60f14b534 100644
--- a/framework/src/main/java/org/tron/core/services/jsonrpc/JsonRpcApiUtil.java
+++ b/framework/src/main/java/org/tron/core/services/jsonrpc/JsonRpcApiUtil.java
@@ -26,7 +26,7 @@
import org.tron.common.utils.Sha256Hash;
import org.tron.common.utils.StringUtil;
import org.tron.core.Wallet;
-import org.tron.core.exception.JsonRpcInvalidParamsException;
+import org.tron.core.exception.jsonrpc.JsonRpcInvalidParamsException;
import org.tron.protos.Protocol.Block;
import org.tron.protos.Protocol.Transaction;
import org.tron.protos.Protocol.Transaction.Contract.ContractType;
diff --git a/framework/src/main/java/org/tron/core/services/jsonrpc/JsonRpcErrorResolver.java b/framework/src/main/java/org/tron/core/services/jsonrpc/JsonRpcErrorResolver.java
new file mode 100644
index 00000000000..b92b3cf1af6
--- /dev/null
+++ b/framework/src/main/java/org/tron/core/services/jsonrpc/JsonRpcErrorResolver.java
@@ -0,0 +1,81 @@
+package org.tron.core.services.jsonrpc;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.googlecode.jsonrpc4j.ErrorData;
+import com.googlecode.jsonrpc4j.ErrorResolver;
+import com.googlecode.jsonrpc4j.JsonRpcError;
+import com.googlecode.jsonrpc4j.JsonRpcErrors;
+import com.googlecode.jsonrpc4j.ReflectionUtil;
+import java.lang.reflect.Method;
+import java.util.List;
+import org.tron.core.exception.jsonrpc.JsonRpcException;
+
+/**
+ * {@link ErrorResolver} that uses annotations.
+ */
+public enum JsonRpcErrorResolver implements ErrorResolver {
+ INSTANCE;
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public JsonError resolveError(
+ Throwable thrownException, Method method, List arguments) {
+ JsonRpcError resolver = getResolverForException(thrownException, method);
+ if (notFoundResolver(resolver)) {
+ return null;
+ }
+
+ String message = hasErrorMessage(resolver) ? resolver.message() : thrownException.getMessage();
+
+ // data priority: exception > annotation > default ErrorData
+ Object data = null;
+ if (thrownException instanceof JsonRpcException) {
+ JsonRpcException jsonRpcException = (JsonRpcException) thrownException;
+ data = jsonRpcException.getData();
+ }
+
+ if (data == null) {
+ data = hasErrorData(resolver)
+ ? resolver.data()
+ : new ErrorData(resolver.exception().getName(), message);
+ }
+
+ return new JsonError(resolver.code(), message, data);
+ }
+
+ private JsonRpcError getResolverForException(Throwable thrownException, Method method) {
+ JsonRpcErrors errors = ReflectionUtil.getAnnotation(method, JsonRpcErrors.class);
+ if (hasAnnotations(errors)) {
+ for (JsonRpcError errorDefined : errors.value()) {
+ if (isExceptionInstanceOfError(thrownException, errorDefined)) {
+ return errorDefined;
+ }
+ }
+ }
+ return null;
+ }
+
+ private boolean notFoundResolver(JsonRpcError resolver) {
+ return resolver == null;
+ }
+
+ private boolean hasErrorMessage(JsonRpcError em) {
+ // noinspection ConstantConditions
+ return em.message() != null && !em.message().trim().isEmpty();
+ }
+
+ private boolean hasErrorData(JsonRpcError em) {
+ // noinspection ConstantConditions
+ return em.data() != null && !em.data().trim().isEmpty();
+ }
+
+ private boolean hasAnnotations(JsonRpcErrors errors) {
+ return errors != null;
+ }
+
+ private boolean isExceptionInstanceOfError(Throwable target, JsonRpcError em) {
+ return em.exception().isInstance(target);
+ }
+}
\ No newline at end of file
diff --git a/framework/src/main/java/org/tron/core/services/jsonrpc/JsonRpcServlet.java b/framework/src/main/java/org/tron/core/services/jsonrpc/JsonRpcServlet.java
index 878b71d86b5..104a0e9e470 100644
--- a/framework/src/main/java/org/tron/core/services/jsonrpc/JsonRpcServlet.java
+++ b/framework/src/main/java/org/tron/core/services/jsonrpc/JsonRpcServlet.java
@@ -43,6 +43,7 @@ public void init(ServletConfig config) throws ServletException {
true);
rpcServer = new JsonRpcServer(compositeService);
+ rpcServer.setErrorResolver(JsonRpcErrorResolver.INSTANCE);
HttpStatusCodeProvider httpStatusCodeProvider = new HttpStatusCodeProvider() {
@Override
diff --git a/framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpc.java b/framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpc.java
index 52a3a2380d1..115df6ef9da 100644
--- a/framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpc.java
+++ b/framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpc.java
@@ -7,6 +7,7 @@
import com.googlecode.jsonrpc4j.JsonRpcMethod;
import java.io.IOException;
import java.util.List;
+import java.util.Objects;
import java.util.concurrent.ExecutionException;
import lombok.AllArgsConstructor;
import lombok.Getter;
@@ -18,17 +19,21 @@
import org.tron.common.utils.ByteArray;
import org.tron.core.exception.BadItemException;
import org.tron.core.exception.ItemNotFoundException;
-import org.tron.core.exception.JsonRpcInternalException;
-import org.tron.core.exception.JsonRpcInvalidParamsException;
-import org.tron.core.exception.JsonRpcInvalidRequestException;
-import org.tron.core.exception.JsonRpcMethodNotFoundException;
-import org.tron.core.exception.JsonRpcTooManyResultException;
+import org.tron.core.exception.jsonrpc.JsonRpcExceedLimitException;
+import org.tron.core.exception.jsonrpc.JsonRpcInternalException;
+import org.tron.core.exception.jsonrpc.JsonRpcInvalidParamsException;
+import org.tron.core.exception.jsonrpc.JsonRpcInvalidRequestException;
+import org.tron.core.exception.jsonrpc.JsonRpcMethodNotFoundException;
+import org.tron.core.exception.jsonrpc.JsonRpcTooManyResultException;
import org.tron.core.services.jsonrpc.types.BlockResult;
import org.tron.core.services.jsonrpc.types.BuildArguments;
import org.tron.core.services.jsonrpc.types.CallArguments;
import org.tron.core.services.jsonrpc.types.TransactionReceipt;
import org.tron.core.services.jsonrpc.types.TransactionResult;
+/**
+ * Error code refers to https://www.quicknode.com/docs/ethereum/error-references
+ */
@Component
public interface TronJsonRpc {
@@ -146,6 +151,14 @@ TransactionResult getTransactionByBlockNumberAndIndex(String blockNumOrTag, Stri
})
TransactionReceipt getTransactionReceipt(String txid) throws JsonRpcInvalidParamsException;
+ @JsonRpcMethod("eth_getBlockReceipts")
+ @JsonRpcErrors({
+ @JsonRpcError(exception = JsonRpcInvalidParamsException.class, code = -32602, data = "{}"),
+ @JsonRpcError(exception = JsonRpcInternalException.class, code = -32000, data = "{}")
+ })
+ List getBlockReceipts(String blockNumOrHashOrTag)
+ throws JsonRpcInvalidParamsException, JsonRpcInternalException;
+
@JsonRpcMethod("eth_call")
@JsonRpcErrors({
@JsonRpcError(exception = JsonRpcInvalidRequestException.class, code = -32600, data = "{}"),
@@ -284,9 +297,10 @@ String newFilter(FilterRequest fr) throws JsonRpcInvalidParamsException,
@JsonRpcMethod("eth_newBlockFilter")
@JsonRpcErrors({
+ @JsonRpcError(exception = JsonRpcExceedLimitException.class, code = -32005, data = "{}"),
@JsonRpcError(exception = JsonRpcMethodNotFoundException.class, code = -32601, data = "{}"),
})
- String newBlockFilter() throws JsonRpcMethodNotFoundException;
+ String newBlockFilter() throws JsonRpcExceedLimitException, JsonRpcMethodNotFoundException;
@JsonRpcMethod("eth_uninstallFilter")
@JsonRpcErrors({
@@ -464,5 +478,35 @@ public LogFilterElement(String blockHash, Long blockNum, String txId, Integer tx
}
this.removed = removed;
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || this.getClass() != o.getClass()) {
+ return false;
+ }
+ LogFilterElement item = (LogFilterElement) o;
+ if (!Objects.equals(blockHash, item.blockHash)) {
+ return false;
+ }
+ if (!Objects.equals(transactionHash, item.transactionHash)) {
+ return false;
+ }
+ if (!Objects.equals(transactionIndex, item.transactionIndex)) {
+ return false;
+ }
+ if (!Objects.equals(logIndex, item.logIndex)) {
+ return false;
+ }
+ return removed == item.removed;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(blockHash, transactionHash, transactionIndex, logIndex, removed);
+ }
+
}
}
diff --git a/framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpcImpl.java b/framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpcImpl.java
index eb432432a1c..de939bdfff4 100644
--- a/framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpcImpl.java
+++ b/framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpcImpl.java
@@ -11,10 +11,13 @@
import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.triggerCallContract;
import com.alibaba.fastjson.JSON;
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
import com.google.protobuf.ByteString;
import com.google.protobuf.GeneratedMessageV3;
import java.io.Closeable;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
@@ -24,11 +27,10 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
-import java.util.regex.Matcher;
+import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.util.encoders.Hex;
import org.springframework.beans.factory.annotation.Autowired;
@@ -38,6 +40,7 @@
import org.tron.api.GrpcAPI.Return;
import org.tron.api.GrpcAPI.Return.response_code;
import org.tron.api.GrpcAPI.TransactionExtention;
+import org.tron.api.GrpcAPI.TransactionInfoList;
import org.tron.common.crypto.Hash;
import org.tron.common.es.ExecutorServiceManager;
import org.tron.common.logsfilter.ContractEventParser;
@@ -50,6 +53,7 @@
import org.tron.core.Wallet;
import org.tron.core.capsule.BlockCapsule;
import org.tron.core.capsule.TransactionCapsule;
+import org.tron.core.config.args.Args;
import org.tron.core.db.Manager;
import org.tron.core.db2.core.Chainbase;
import org.tron.core.exception.BadItemException;
@@ -57,12 +61,13 @@
import org.tron.core.exception.ContractValidateException;
import org.tron.core.exception.HeaderNotFound;
import org.tron.core.exception.ItemNotFoundException;
-import org.tron.core.exception.JsonRpcInternalException;
-import org.tron.core.exception.JsonRpcInvalidParamsException;
-import org.tron.core.exception.JsonRpcInvalidRequestException;
-import org.tron.core.exception.JsonRpcMethodNotFoundException;
-import org.tron.core.exception.JsonRpcTooManyResultException;
import org.tron.core.exception.VMIllegalException;
+import org.tron.core.exception.jsonrpc.JsonRpcExceedLimitException;
+import org.tron.core.exception.jsonrpc.JsonRpcInternalException;
+import org.tron.core.exception.jsonrpc.JsonRpcInvalidParamsException;
+import org.tron.core.exception.jsonrpc.JsonRpcInvalidRequestException;
+import org.tron.core.exception.jsonrpc.JsonRpcMethodNotFoundException;
+import org.tron.core.exception.jsonrpc.JsonRpcTooManyResultException;
import org.tron.core.services.NodeInfoService;
import org.tron.core.services.http.JsonFormat;
import org.tron.core.services.http.Util;
@@ -76,12 +81,14 @@
import org.tron.core.services.jsonrpc.types.BuildArguments;
import org.tron.core.services.jsonrpc.types.CallArguments;
import org.tron.core.services.jsonrpc.types.TransactionReceipt;
+import org.tron.core.services.jsonrpc.types.TransactionReceipt.TransactionContext;
import org.tron.core.services.jsonrpc.types.TransactionResult;
import org.tron.core.store.StorageRowStore;
import org.tron.core.vm.program.Storage;
import org.tron.program.Version;
import org.tron.protos.Protocol.Account;
import org.tron.protos.Protocol.Block;
+import org.tron.protos.Protocol.ResourceReceipt;
import org.tron.protos.Protocol.Transaction;
import org.tron.protos.Protocol.Transaction.Contract.ContractType;
import org.tron.protos.Protocol.Transaction.Result.code;
@@ -94,6 +101,7 @@
import org.tron.protos.contract.SmartContractOuterClass.SmartContractDataWrapper;
import org.tron.protos.contract.SmartContractOuterClass.TriggerSmartContract;
+
@Slf4j(topic = "API")
@Component
public class TronJsonRpcImpl implements TronJsonRpc, Closeable {
@@ -106,6 +114,17 @@ public enum RequestSource {
private static final String FILTER_NOT_FOUND = "filter not found";
public static final int EXPIRE_SECONDS = 5 * 60;
+ private static final int maxBlockFilterNum = Args.getInstance().getJsonRpcMaxBlockFilterNum();
+ private static final Cache logElementCache =
+ CacheBuilder.newBuilder()
+ .maximumSize(300_000L) // 300s * tps(1000) * 1 log/tx ≈ 300_000
+ .expireAfterWrite(EXPIRE_SECONDS, TimeUnit.SECONDS)
+ .recordStats().build(); //LRU cache
+ private static final Cache blockHashCache =
+ CacheBuilder.newBuilder()
+ .maximumSize(60_000L) // 300s * 200 block/s when syncing
+ .expireAfterWrite(EXPIRE_SECONDS, TimeUnit.SECONDS)
+ .recordStats().build(); //LRU cache
/**
* for log filter in Full Json-RPC
*/
@@ -177,16 +196,31 @@ public static void handleBLockFilter(BlockFilterCapsule blockFilterCapsule) {
it = getBlockFilter2ResultFull().entrySet().iterator();
}
+ if (!it.hasNext()) {
+ return;
+ }
+ final String originalBlockHash = ByteArray.toJsonHex(blockFilterCapsule.getBlockHash());
+ String cachedBlockHash;
+ try {
+ // compare with hashcode() first, then with equals(). If not exist, put it.
+ cachedBlockHash = blockHashCache.get(originalBlockHash, () -> originalBlockHash);
+ } catch (ExecutionException e) {
+ logger.error("Getting/loading blockHash from cache failed", e); // never happen
+ cachedBlockHash = originalBlockHash;
+ }
while (it.hasNext()) {
Entry entry = it.next();
if (entry.getValue().isExpire()) {
it.remove();
continue;
}
- entry.getValue().getResult().add(ByteArray.toJsonHex(blockFilterCapsule.getBlockHash()));
+ entry.getValue().getResult().add(cachedBlockHash);
}
}
+ /**
+ * append LogsFilterCapsule's LogFilterElement list to each filter if matched
+ */
public static void handleLogsFilter(LogsFilterCapsule logsFilterCapsule) {
Iterator> it;
@@ -222,23 +256,28 @@ public static void handleLogsFilter(LogsFilterCapsule logsFilterCapsule) {
LogMatch.matchBlock(logFilter, logsFilterCapsule.getBlockNumber(),
logsFilterCapsule.getBlockHash(), logsFilterCapsule.getTxInfoList(),
logsFilterCapsule.isRemoved());
- if (CollectionUtils.isNotEmpty(elements)) {
- logFilterAndResult.getResult().addAll(elements);
+
+ for (LogFilterElement element : elements) {
+ LogFilterElement cachedElement;
+ try {
+ // compare with hashcode() first, then with equals(). If not exist, put it.
+ cachedElement = logElementCache.get(element, () -> element);
+ } catch (ExecutionException e) {
+ logger.error("Getting/loading LogFilterElement from cache fails", e); // never happen
+ cachedElement = element;
+ }
+ logFilterAndResult.getResult().add(cachedElement);
}
}
}
@Override
public String web3ClientVersion() {
- Pattern shortVersion = Pattern.compile("(\\d\\.\\d).*");
- Matcher matcher = shortVersion.matcher(System.getProperty("java.version"));
- matcher.matches();
-
return String.join("/", Arrays.asList(
"TRON",
"v" + Version.getVersion(),
System.getProperty("os.name"),
- "Java" + matcher.group(1)));
+ "Java" + System.getProperty("java.specification.version")));
}
@Override
@@ -473,7 +512,6 @@ private String call(byte[] ownerAddressByte, byte[] contractAddressByte, long va
}
result = ByteArray.toJsonHex(listBytes);
} else {
- logger.error("trigger contract failed.");
String errMsg = retBuilder.getMessage().toStringUtf8();
byte[] resData = trxExtBuilder.getConstantResult(0).toByteArray();
if (resData.length > 4 && Hex.toHexString(resData).startsWith(ERROR_SELECTOR)) {
@@ -483,7 +521,12 @@ private String call(byte[] ownerAddressByte, byte[] contractAddressByte, long va
errMsg += ": " + msg;
}
- throw new JsonRpcInternalException(errMsg);
+ if (resData.length > 0) {
+ throw new JsonRpcInternalException(errMsg, ByteArray.toJsonHex(resData));
+ } else {
+ throw new JsonRpcInternalException(errMsg);
+ }
+
}
return result;
@@ -644,7 +687,12 @@ public String estimateGas(CallArguments args) throws JsonRpcInvalidRequestExcept
errMsg += ": " + msg;
}
- throw new JsonRpcInternalException(errMsg);
+ if (data.length > 0) {
+ throw new JsonRpcInternalException(errMsg, ByteArray.toJsonHex(data));
+ } else {
+ throw new JsonRpcInternalException(errMsg);
+ }
+
} else {
if (supportEstimateEnergy) {
@@ -763,6 +811,13 @@ public TransactionResult getTransactionByBlockNumberAndIndex(String blockNumOrTa
return getTransactionByBlockAndIndex(block, index);
}
+ /**
+ * Get a transaction receipt by transaction hash
+ *
+ * @param txId the transaction hash in hex format (with or without 0x prefix)
+ * @return TransactionReceipt object for the specified transaction, or null if not found
+ * @throws JsonRpcInvalidParamsException if the transaction hash format is invalid
+ */
@Override
public TransactionReceipt getTransactionReceipt(String txId)
throws JsonRpcInvalidParamsException {
@@ -777,7 +832,126 @@ public TransactionReceipt getTransactionReceipt(String txId)
return null;
}
- return new TransactionReceipt(block, transactionInfo, wallet);
+ BlockCapsule blockCapsule = new BlockCapsule(block);
+ long blockNum = blockCapsule.getNum();
+ TransactionInfoList transactionInfoList = wallet.getTransactionInfoByBlockNum(blockNum);
+ long energyFee = wallet.getEnergyFee(blockCapsule.getTimeStamp());
+
+ // Find transaction context
+ TransactionReceipt.TransactionContext context
+ = findTransactionContext(transactionInfoList,
+ transactionInfo.getId());
+
+ if (context == null) {
+ return null; // Transaction not found in block
+ }
+
+ return new TransactionReceipt(blockCapsule, transactionInfo, context, energyFee);
+ }
+
+ /**
+ * Finds transaction context for a specific transaction ID within the block
+ * Calculates cumulative gas and log count up to the target transaction
+ * @param infoList the transactionInfo list for the block
+ * @param txId the transaction ID
+ * @return TransactionContext containing index and cumulative values, or null if not found
+ */
+ private TransactionContext findTransactionContext(TransactionInfoList infoList,
+ ByteString txId) {
+
+ long cumulativeGas = 0;
+ long cumulativeLogCount = 0;
+
+ for (int index = 0; index < infoList.getTransactionInfoCount(); index++) {
+ TransactionInfo info = infoList.getTransactionInfo(index);
+ ResourceReceipt resourceReceipt = info.getReceipt();
+
+ if (info.getId().equals(txId)) {
+ return new TransactionContext(index, cumulativeGas, cumulativeLogCount);
+ } else {
+ cumulativeGas += resourceReceipt.getEnergyUsageTotal();
+ cumulativeLogCount += info.getLogCount();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get all transaction receipts for a specific block
+ * @param blockNumOrHashOrTag blockNumber or blockHash or tag,
+ * tag includes: latest, earliest, pending, finalized
+ * @return List of TransactionReceipt objects for all transactions in the block,
+ * null if block not found
+ * @throws JsonRpcInvalidParamsException if the parameter format is invalid
+ * @throws JsonRpcInternalException if there's an internal error
+ */
+ @Override
+ public List getBlockReceipts(String blockNumOrHashOrTag)
+ throws JsonRpcInvalidParamsException, JsonRpcInternalException {
+
+ Block block = null;
+
+ if (Pattern.matches(HASH_REGEX, blockNumOrHashOrTag)) {
+ block = getBlockByJsonHash(blockNumOrHashOrTag);
+ } else {
+ block = wallet.getByJsonBlockId(blockNumOrHashOrTag);
+ }
+
+ // block receipts not available: block is genesis, not produced yet, or pruned in light node
+ if (block == null || block.getBlockHeader().getRawData().getNumber() == 0) {
+ return null;
+ }
+
+ BlockCapsule blockCapsule = new BlockCapsule(block);
+ long blockNum = blockCapsule.getNum();
+ TransactionInfoList transactionInfoList = wallet.getTransactionInfoByBlockNum(blockNum);
+
+ // energy price at the block timestamp
+ long energyFee = wallet.getEnergyFee(blockCapsule.getTimeStamp());
+
+ // Validate transaction list size consistency
+ int transactionSizeInBlock = blockCapsule.getTransactions().size();
+ if (transactionSizeInBlock != transactionInfoList.getTransactionInfoCount()) {
+ throw new JsonRpcInternalException(
+ String.format("TransactionList size mismatch: "
+ + "block has %d transactions, but transactionInfoList has %d",
+ transactionSizeInBlock, transactionInfoList.getTransactionInfoCount()));
+ }
+
+ return getTransactionReceiptsFromBlock(blockCapsule, transactionInfoList, energyFee);
+ }
+
+ /**
+ * Get all TransactionReceipts from a block
+ * This method processes all transactions in the block
+ * and creates receipts with cumulative gas calculations
+ * @param blockCapsule the block containing transactions
+ * @param transactionInfoList the transaction info list for the block
+ * @param energyFee the energy price at the block timestamp
+ * @return List of TransactionReceipt objects for all transactions in the block
+ */
+ private List getTransactionReceiptsFromBlock(BlockCapsule blockCapsule,
+ TransactionInfoList transactionInfoList, long energyFee) {
+
+ List receipts = new ArrayList<>();
+ long cumulativeGas = 0;
+ long cumulativeLogCount = 0;
+
+ for (int index = 0; index < transactionInfoList.getTransactionInfoCount(); index++) {
+ TransactionInfo info = transactionInfoList.getTransactionInfo(index);
+ ResourceReceipt resourceReceipt = info.getReceipt();
+
+ TransactionReceipt.TransactionContext context = new TransactionContext(
+ index, cumulativeGas, cumulativeLogCount);
+
+ // Use the constructor with pre-calculated context
+ TransactionReceipt receipt = new TransactionReceipt(blockCapsule, info, context, energyFee);
+ receipts.add(receipt);
+
+ cumulativeGas += resourceReceipt.getEnergyUsageTotal();
+ cumulativeLogCount += info.getLogCount();
+ }
+ return receipts;
}
@Override
@@ -1256,7 +1430,8 @@ public String newFilter(FilterRequest fr) throws JsonRpcInvalidParamsException,
}
@Override
- public String newBlockFilter() throws JsonRpcMethodNotFoundException {
+ public String newBlockFilter() throws JsonRpcMethodNotFoundException,
+ JsonRpcExceedLimitException {
disableInPBFT("eth_newBlockFilter");
Map blockFilter2Result;
@@ -1265,6 +1440,10 @@ public String newBlockFilter() throws JsonRpcMethodNotFoundException {
} else {
blockFilter2Result = blockFilter2ResultSolidity;
}
+ if (blockFilter2Result.size() >= maxBlockFilterNum) {
+ throw new JsonRpcExceedLimitException(
+ "exceed max block filters: " + maxBlockFilterNum + ", try again later");
+ }
BlockFilterAndResult filterAndResult = new BlockFilterAndResult();
String filterID = generateFilterId();
@@ -1394,6 +1573,8 @@ public static Object[] getFilterResult(String filterId, Map>> bitSetList = new ArrayList<>();
-
+ // 1. Collect all unique bitIndexes
+ Set uniqueBitIndexes = new HashSet<>();
for (int[] index : bitIndexes) {
- List> futureList = new ArrayList<>();
- for (final int bitIndex : index) { //must be 3
- Future bitSetFuture =
- sectionExecutor.submit(() -> sectionBloomStore.get(section, bitIndex));
- futureList.add(bitSetFuture);
+ for (int bitIndex : index) { //normally 3, but could be less due to hash collisions
+ uniqueBitIndexes.add(bitIndex);
+ }
+ }
+
+ // 2. Submit concurrent requests for all unique bitIndexes
+ Map> bitIndexResults = new HashMap<>();
+ for (int bitIndex : uniqueBitIndexes) {
+ Future future
+ = sectionExecutor.submit(() -> sectionBloomStore.get(section, bitIndex));
+ bitIndexResults.put(bitIndex, future);
+ }
+
+ // 3. Wait for all results and cache them
+ Map resultCache = new HashMap<>();
+ for (Map.Entry> entry : bitIndexResults.entrySet()) {
+ BitSet result = entry.getValue().get();
+ if (result != null) {
+ resultCache.put(entry.getKey(), result);
}
- bitSetList.add(futureList);
}
- BitSet bitSet = new BitSet(SectionBloomStore.BLOCK_PER_SECTION);
- for (List> futureList : bitSetList) {
- // initial a BitSet with all 1
- BitSet subBitSet = new BitSet(SectionBloomStore.BLOCK_PER_SECTION);
- subBitSet.set(0, SectionBloomStore.BLOCK_PER_SECTION);
+ // 4. Process valid groups with reused BitSet objects
+ BitSet finalResult = new BitSet(SectionBloomStore.BLOCK_PER_SECTION);
+ BitSet tempBitSet = new BitSet(SectionBloomStore.BLOCK_PER_SECTION);
+
+ for (int[] index : bitIndexes) {
+
+ // init tempBitSet with all 1
+ tempBitSet.set(0, SectionBloomStore.BLOCK_PER_SECTION);
+
// and condition in second dimension
- for (Future future : futureList) {
- BitSet one = future.get();
- if (one == null) { //match nothing
- subBitSet.clear();
+ for (int bitIndex : index) {
+ BitSet cached = resultCache.get(bitIndex);
+ if (cached == null) { //match nothing
+ tempBitSet.clear();
break;
}
// "and" condition in second dimension
- subBitSet.and(one);
+ tempBitSet.and(cached);
+ if (tempBitSet.isEmpty()) {
+ break;
+ }
}
+
// "or" condition in first dimension
- bitSet.or(subBitSet);
+ if (!tempBitSet.isEmpty()) {
+ finalResult.or(tempBitSet);
+ }
}
- return bitSet;
+
+ return finalResult;
}
/**
diff --git a/framework/src/main/java/org/tron/core/services/jsonrpc/filters/LogFilter.java b/framework/src/main/java/org/tron/core/services/jsonrpc/filters/LogFilter.java
index ce315e506d2..42bc123d4bc 100644
--- a/framework/src/main/java/org/tron/core/services/jsonrpc/filters/LogFilter.java
+++ b/framework/src/main/java/org/tron/core/services/jsonrpc/filters/LogFilter.java
@@ -14,7 +14,7 @@
import org.tron.common.crypto.Hash;
import org.tron.common.runtime.vm.DataWord;
import org.tron.core.config.args.Args;
-import org.tron.core.exception.JsonRpcInvalidParamsException;
+import org.tron.core.exception.jsonrpc.JsonRpcInvalidParamsException;
import org.tron.core.services.jsonrpc.TronJsonRpc.FilterRequest;
import org.tron.protos.Protocol.TransactionInfo.Log;
diff --git a/framework/src/main/java/org/tron/core/services/jsonrpc/filters/LogFilterAndResult.java b/framework/src/main/java/org/tron/core/services/jsonrpc/filters/LogFilterAndResult.java
index 3b893aec4cf..57739819d1e 100644
--- a/framework/src/main/java/org/tron/core/services/jsonrpc/filters/LogFilterAndResult.java
+++ b/framework/src/main/java/org/tron/core/services/jsonrpc/filters/LogFilterAndResult.java
@@ -5,7 +5,7 @@
import java.util.concurrent.LinkedBlockingQueue;
import lombok.Getter;
import org.tron.core.Wallet;
-import org.tron.core.exception.JsonRpcInvalidParamsException;
+import org.tron.core.exception.jsonrpc.JsonRpcInvalidParamsException;
import org.tron.core.services.jsonrpc.TronJsonRpc.FilterRequest;
import org.tron.core.services.jsonrpc.TronJsonRpc.LogFilterElement;
diff --git a/framework/src/main/java/org/tron/core/services/jsonrpc/filters/LogFilterWrapper.java b/framework/src/main/java/org/tron/core/services/jsonrpc/filters/LogFilterWrapper.java
index c0cd1ff12df..97a012b7f9a 100644
--- a/framework/src/main/java/org/tron/core/services/jsonrpc/filters/LogFilterWrapper.java
+++ b/framework/src/main/java/org/tron/core/services/jsonrpc/filters/LogFilterWrapper.java
@@ -8,7 +8,7 @@
import org.tron.common.utils.ByteArray;
import org.tron.core.Wallet;
import org.tron.core.config.args.Args;
-import org.tron.core.exception.JsonRpcInvalidParamsException;
+import org.tron.core.exception.jsonrpc.JsonRpcInvalidParamsException;
import org.tron.core.services.jsonrpc.JsonRpcApiUtil;
import org.tron.core.services.jsonrpc.TronJsonRpc.FilterRequest;
import org.tron.protos.Protocol.Block;
diff --git a/framework/src/main/java/org/tron/core/services/jsonrpc/filters/LogMatch.java b/framework/src/main/java/org/tron/core/services/jsonrpc/filters/LogMatch.java
index d96aa07f9a4..cf958d1e2cb 100644
--- a/framework/src/main/java/org/tron/core/services/jsonrpc/filters/LogMatch.java
+++ b/framework/src/main/java/org/tron/core/services/jsonrpc/filters/LogMatch.java
@@ -10,7 +10,7 @@
import org.tron.core.db.Manager;
import org.tron.core.exception.BadItemException;
import org.tron.core.exception.ItemNotFoundException;
-import org.tron.core.exception.JsonRpcTooManyResultException;
+import org.tron.core.exception.jsonrpc.JsonRpcTooManyResultException;
import org.tron.core.services.jsonrpc.TronJsonRpc.LogFilterElement;
import org.tron.protos.Protocol.TransactionInfo;
import org.tron.protos.Protocol.TransactionInfo.Log;
@@ -83,17 +83,13 @@ public LogFilterElement[] matchBlockOneByOne()
List logFilterElementList = new ArrayList<>();
for (long blockNum : blockNumList) {
- TransactionRetCapsule transactionRetCapsule =
- manager.getTransactionRetStore()
- .getTransactionInfoByBlockNum(ByteArray.fromLong(blockNum));
- if (transactionRetCapsule == null) {
- //if query condition (address and topics) is empty, we will traversal every block,
- //include empty block
+ List transactionInfoList =
+ manager.getTransactionInfoByBlockNum(blockNum).getTransactionInfoList();
+ //if query condition (address and topics) is empty, we will traversal every block,
+ //include empty block
+ if (transactionInfoList.isEmpty()) {
continue;
}
- TransactionRet transactionRet = transactionRetCapsule.getInstance();
- List transactionInfoList = transactionRet.getTransactioninfoList();
-
String blockHash = manager.getChainBaseManager().getBlockIdByNum(blockNum).toString();
List matchedLog = matchBlock(logFilterWrapper.getLogFilter(), blockNum,
blockHash, transactionInfoList, false);
diff --git a/framework/src/main/java/org/tron/core/services/jsonrpc/types/BuildArguments.java b/framework/src/main/java/org/tron/core/services/jsonrpc/types/BuildArguments.java
index 223e807e622..490219a13d9 100644
--- a/framework/src/main/java/org/tron/core/services/jsonrpc/types/BuildArguments.java
+++ b/framework/src/main/java/org/tron/core/services/jsonrpc/types/BuildArguments.java
@@ -14,8 +14,8 @@
import org.apache.commons.lang3.StringUtils;
import org.tron.api.GrpcAPI.BytesMessage;
import org.tron.core.Wallet;
-import org.tron.core.exception.JsonRpcInvalidParamsException;
-import org.tron.core.exception.JsonRpcInvalidRequestException;
+import org.tron.core.exception.jsonrpc.JsonRpcInvalidParamsException;
+import org.tron.core.exception.jsonrpc.JsonRpcInvalidRequestException;
import org.tron.protos.Protocol.Transaction.Contract.ContractType;
import org.tron.protos.contract.SmartContractOuterClass.SmartContract;
diff --git a/framework/src/main/java/org/tron/core/services/jsonrpc/types/CallArguments.java b/framework/src/main/java/org/tron/core/services/jsonrpc/types/CallArguments.java
index 1485448c4b6..70edd1ad94f 100644
--- a/framework/src/main/java/org/tron/core/services/jsonrpc/types/CallArguments.java
+++ b/framework/src/main/java/org/tron/core/services/jsonrpc/types/CallArguments.java
@@ -13,8 +13,8 @@
import org.apache.commons.lang3.StringUtils;
import org.tron.api.GrpcAPI.BytesMessage;
import org.tron.core.Wallet;
-import org.tron.core.exception.JsonRpcInvalidParamsException;
-import org.tron.core.exception.JsonRpcInvalidRequestException;
+import org.tron.core.exception.jsonrpc.JsonRpcInvalidParamsException;
+import org.tron.core.exception.jsonrpc.JsonRpcInvalidRequestException;
import org.tron.protos.Protocol.Transaction.Contract.ContractType;
import org.tron.protos.contract.SmartContractOuterClass.SmartContract;
diff --git a/framework/src/main/java/org/tron/core/services/jsonrpc/types/TransactionReceipt.java b/framework/src/main/java/org/tron/core/services/jsonrpc/types/TransactionReceipt.java
index 81b7c763cca..fd57ec0d9ad 100644
--- a/framework/src/main/java/org/tron/core/services/jsonrpc/types/TransactionReceipt.java
+++ b/framework/src/main/java/org/tron/core/services/jsonrpc/types/TransactionReceipt.java
@@ -9,179 +9,151 @@
import java.util.List;
import lombok.Getter;
import lombok.Setter;
-import org.tron.api.GrpcAPI.TransactionInfoList;
import org.tron.common.utils.ByteArray;
-import org.tron.core.Wallet;
import org.tron.core.capsule.BlockCapsule;
import org.tron.core.capsule.TransactionCapsule;
import org.tron.protos.Protocol;
-import org.tron.protos.Protocol.ResourceReceipt;
import org.tron.protos.Protocol.Transaction.Contract;
import org.tron.protos.Protocol.Transaction.Contract.ContractType;
import org.tron.protos.Protocol.TransactionInfo;
+@Getter
+@Setter
@JsonPropertyOrder(alphabetic = true)
public class TransactionReceipt {
-
@JsonPropertyOrder(alphabetic = true)
+ @Getter
+ @Setter
public static class TransactionLog {
- @Getter
- @Setter
private String logIndex;
- @Getter
- @Setter
private String blockHash;
- @Getter
- @Setter
private String blockNumber;
- @Getter
- @Setter
private String transactionIndex;
- @Getter
- @Setter
private String transactionHash;
- @Getter
- @Setter
private String address;
- @Getter
- @Setter
private String data;
- @Getter
- @Setter
private String[] topics;
- @Getter
- @Setter
private boolean removed = false;
- public TransactionLog() {
- }
+ public TransactionLog() {}
}
- @Getter
- @Setter
private String blockHash;
- @Getter
- @Setter
private String blockNumber;
- @Getter
- @Setter
private String transactionIndex;
- @Getter
- @Setter
private String transactionHash;
- @Getter
- @Setter
private String from;
- @Getter
- @Setter
private String to;
- @Getter
- @Setter
private String cumulativeGasUsed;
- @Getter
- @Setter
private String effectiveGasPrice;
- @Getter
- @Setter
private String gasUsed;
- @Getter
- @Setter
private String contractAddress;
- @Getter
- @Setter
private TransactionLog[] logs;
- @Getter
- @Setter
- private String logsBloom;
- @JsonInclude(JsonInclude.Include.NON_NULL)
- public String root; // 32 bytes of post-transaction stateroot (pre Byzantium)
- @JsonInclude(JsonInclude.Include.NON_NULL)
- public String status; // either 1 (success) or 0 (failure) (post Byzantium)
+ private String logsBloom = ByteArray.toJsonHex(new byte[256]); // default no value;
- @Getter
- @Setter
- private String type = "0x0";
-
- public TransactionReceipt(Protocol.Block block, TransactionInfo txInfo, Wallet wallet) {
- BlockCapsule blockCapsule = new BlockCapsule(block);
- String txid = ByteArray.toHexString(txInfo.getId().toByteArray());
- long blockNum = blockCapsule.getNum();
-
- Protocol.Transaction transaction = null;
- long cumulativeGas = 0;
- long cumulativeLogCount = 0;
-
- TransactionInfoList infoList = wallet.getTransactionInfoByBlockNum(blockNum);
- for (int index = 0; index < infoList.getTransactionInfoCount(); index++) {
- TransactionInfo info = infoList.getTransactionInfo(index);
- ResourceReceipt resourceReceipt = info.getReceipt();
-
- long energyUsage = resourceReceipt.getEnergyUsageTotal();
- cumulativeGas += energyUsage;
-
- if (ByteArray.toHexString(info.getId().toByteArray()).equals(txid)) {
- transactionIndex = ByteArray.toJsonHex(index);
- cumulativeGasUsed = ByteArray.toJsonHex(cumulativeGas);
- gasUsed = ByteArray.toJsonHex(energyUsage);
- status = resourceReceipt.getResultValue() <= 1 ? "0x1" : "0x0";
-
- transaction = block.getTransactions(index);
- break;
- } else {
- cumulativeLogCount += info.getLogCount();
- }
- }
-
- blockHash = ByteArray.toJsonHex(blockCapsule.getBlockId().getBytes());
- blockNumber = ByteArray.toJsonHex(blockCapsule.getNum());
- transactionHash = ByteArray.toJsonHex(txInfo.getId().toByteArray());
- effectiveGasPrice = ByteArray.toJsonHex(wallet.getEnergyFee(blockCapsule.getTimeStamp()));
-
- from = null;
- to = null;
- contractAddress = null;
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ private String root = null; // 32 bytes of post-transaction stateroot (pre Byzantium)
- if (transaction != null && !transaction.getRawData().getContractList().isEmpty()) {
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ private String status; // either 1 (success) or 0 (failure) (post Byzantium)
+
+ private String type = "0x0"; // legacy transaction, set 0 in java-tron
+
+ /**
+ * Constructor for creating a TransactionReceipt
+ *
+ * @param blockCapsule the block containing the transaction
+ * @param txInfo the transaction info containing execution details
+ * @param context the pre-calculated transaction context
+ * @param energyFee the energy price at the block timestamp
+ */
+ public TransactionReceipt(
+ BlockCapsule blockCapsule,
+ TransactionInfo txInfo,
+ TransactionContext context,
+ long energyFee) {
+ // Set basic fields
+ this.blockHash = ByteArray.toJsonHex(blockCapsule.getBlockId().getBytes());
+ this.blockNumber = ByteArray.toJsonHex(blockCapsule.getNum());
+ this.transactionHash = ByteArray.toJsonHex(txInfo.getId().toByteArray());
+ this.transactionIndex = ByteArray.toJsonHex(context.index);
+ // Compute cumulative gas until this transaction
+ this.cumulativeGasUsed =
+ ByteArray.toJsonHex(context.cumulativeGas + txInfo.getReceipt().getEnergyUsageTotal());
+ this.gasUsed = ByteArray.toJsonHex(txInfo.getReceipt().getEnergyUsageTotal());
+ this.status = txInfo.getReceipt().getResultValue() <= 1 ? "0x1" : "0x0";
+ this.effectiveGasPrice = ByteArray.toJsonHex(energyFee);
+
+ // Set contract fields
+ this.from = null;
+ this.to = null;
+ this.contractAddress = null;
+
+ TransactionCapsule txCapsule = blockCapsule.getTransactions().get(context.index);
+ Protocol.Transaction transaction = txCapsule.getInstance();
+ if (!transaction.getRawData().getContractList().isEmpty()) {
Contract contract = transaction.getRawData().getContract(0);
byte[] fromByte = TransactionCapsule.getOwner(contract);
byte[] toByte = getToAddress(transaction);
- from = ByteArray.toJsonHexAddress(fromByte);
- to = ByteArray.toJsonHexAddress(toByte);
+ this.from = ByteArray.toJsonHexAddress(fromByte);
+ this.to = ByteArray.toJsonHexAddress(toByte);
if (contract.getType() == ContractType.CreateSmartContract) {
- contractAddress = ByteArray.toJsonHexAddress(txInfo.getContractAddress().toByteArray());
+ this.contractAddress =
+ ByteArray.toJsonHexAddress(txInfo.getContractAddress().toByteArray());
}
}
- // logs
+ // Set logs
List logList = new ArrayList<>();
- for (int index = 0; index < txInfo.getLogCount(); index++) {
- TransactionInfo.Log log = txInfo.getLogList().get(index);
-
- TransactionReceipt.TransactionLog transactionLog = new TransactionReceipt.TransactionLog();
- // index is the index in the block
- transactionLog.logIndex = ByteArray.toJsonHex(index + cumulativeLogCount);
- transactionLog.transactionHash = transactionHash;
- transactionLog.transactionIndex = transactionIndex;
- transactionLog.blockHash = blockHash;
- transactionLog.blockNumber = blockNumber;
+ for (int logIndex = 0; logIndex < txInfo.getLogCount(); logIndex++) {
+ TransactionInfo.Log log = txInfo.getLogList().get(logIndex);
+ TransactionLog transactionLog = new TransactionLog();
+ transactionLog.setLogIndex(ByteArray.toJsonHex(logIndex + context.cumulativeLogCount));
+ transactionLog.setTransactionHash(this.transactionHash);
+ transactionLog.setTransactionIndex(this.transactionIndex);
+ transactionLog.setBlockHash(this.blockHash);
+ transactionLog.setBlockNumber(this.blockNumber);
+
byte[] addressByte = convertToTronAddress(log.getAddress().toByteArray());
- transactionLog.address = ByteArray.toJsonHexAddress(addressByte);
- transactionLog.data = ByteArray.toJsonHex(log.getData().toByteArray());
+ transactionLog.setAddress(ByteArray.toJsonHexAddress(addressByte));
+ transactionLog.setData(ByteArray.toJsonHex(log.getData().toByteArray()));
String[] topics = new String[log.getTopicsCount()];
for (int i = 0; i < log.getTopicsCount(); i++) {
topics[i] = ByteArray.toJsonHex(log.getTopics(i).toByteArray());
}
- transactionLog.topics = topics;
+ transactionLog.setTopics(topics);
logList.add(transactionLog);
}
+ this.logs = logList.toArray(new TransactionLog[0]);
+
+ }
- logs = logList.toArray(new TransactionReceipt.TransactionLog[logList.size()]);
- logsBloom = ByteArray.toJsonHex(new byte[256]); // no value
- root = null;
+ /**
+ * Context class to hold transaction creation parameters Contains index and cumulative values
+ * needed for receipt creation
+ */
+ @Getter
+ public static class TransactionContext {
+ private final int index;
+ private final long cumulativeGas;
+ private final long cumulativeLogCount;
+
+ /**
+ * Creates a transaction context with the given parameters
+ *
+ * @param index the transaction index within the block
+ * @param cumulativeGas the cumulative gas used up to this transaction
+ * @param cumulativeLogCount the cumulative log count up to this transaction
+ */
+ public TransactionContext(int index, long cumulativeGas, long cumulativeLogCount) {
+ this.index = index;
+ this.cumulativeGas = cumulativeGas;
+ this.cumulativeLogCount = cumulativeLogCount;
+ }
}
-}
\ No newline at end of file
+}
diff --git a/framework/src/main/java/org/tron/core/services/jsonrpc/types/TransactionResult.java b/framework/src/main/java/org/tron/core/services/jsonrpc/types/TransactionResult.java
index 389c58505cd..57650355d46 100644
--- a/framework/src/main/java/org/tron/core/services/jsonrpc/types/TransactionResult.java
+++ b/framework/src/main/java/org/tron/core/services/jsonrpc/types/TransactionResult.java
@@ -5,9 +5,9 @@
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.google.protobuf.ByteString;
-import java.util.Arrays;
import lombok.Getter;
import lombok.ToString;
+import org.tron.common.crypto.Rsv;
import org.tron.common.utils.ByteArray;
import org.tron.core.Wallet;
import org.tron.core.capsule.BlockCapsule;
@@ -65,16 +65,10 @@ private void parseSignature(Transaction tx) {
}
ByteString signature = tx.getSignature(0); // r[32] + s[32] + v[1]
- byte[] signData = signature.toByteArray();
- byte[] rByte = Arrays.copyOfRange(signData, 0, 32);
- byte[] sByte = Arrays.copyOfRange(signData, 32, 64);
- byte vByte = signData[64];
- if (vByte < 27) {
- vByte += 27;
- }
- v = ByteArray.toJsonHex(vByte);
- r = ByteArray.toJsonHex(rByte);
- s = ByteArray.toJsonHex(sByte);
+ Rsv rsv = Rsv.fromSignature(signature.toByteArray());
+ r = ByteArray.toJsonHex(rsv.getR());
+ s = ByteArray.toJsonHex(rsv.getS());
+ v = ByteArray.toJsonHex(rsv.getV());
}
private String parseInput(Transaction tx) {
diff --git a/framework/src/main/java/org/tron/core/zen/ZksnarkInitService.java b/framework/src/main/java/org/tron/core/zen/ZksnarkInitService.java
index a1f812426f7..dfc4b428836 100644
--- a/framework/src/main/java/org/tron/core/zen/ZksnarkInitService.java
+++ b/framework/src/main/java/org/tron/core/zen/ZksnarkInitService.java
@@ -3,6 +3,7 @@
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
+import java.util.concurrent.atomic.AtomicBoolean;
import javax.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
@@ -16,34 +17,43 @@
@Component
public class ZksnarkInitService {
+ private static final AtomicBoolean initialized = new AtomicBoolean(false);
+
@PostConstruct
private void init() {
librustzcashInitZksnarkParams();
}
public static void librustzcashInitZksnarkParams() {
- logger.info("init zk param begin");
-
- if (!JLibrustzcash.isOpenZen()) {
- logger.info("zen switch is off, zen will not start.");
+ if (initialized.get()) {
+ logger.info("zk param already initialized");
return;
}
- String spendPath = getParamsFile("sapling-spend.params");
- String spendHash = "25fd9a0d1c1be0526c14662947ae95b758fe9f3d7fb7f55e9b4437830dcc6215a7ce3ea465"
- + "914b157715b7a4d681389ea4aa84438190e185d5e4c93574d3a19a";
+ synchronized (ZksnarkInitService.class) {
+ if (initialized.get()) {
+ logger.info("zk param already initialized");
+ return;
+ }
+ logger.info("init zk param begin");
- String outputPath = getParamsFile("sapling-output.params");
- String outputHash = "a1cb23b93256adce5bce2cb09cefbc96a1d16572675ceb691e9a3626ec15b5b546926ff1c"
- + "536cfe3a9df07d796b32fdfc3e5d99d65567257bf286cd2858d71a6";
+ String spendPath = getParamsFile("sapling-spend.params");
+ String spendHash = "25fd9a0d1c1be0526c14662947ae95b758fe9f3d7fb7f55e9b4437830dcc6215a7ce3ea"
+ + "465914b157715b7a4d681389ea4aa84438190e185d5e4c93574d3a19a";
- try {
- JLibrustzcash.librustzcashInitZksnarkParams(
- new LibrustzcashParam.InitZksnarkParams(spendPath, spendHash, outputPath, outputHash));
- } catch (ZksnarkException e) {
- throw new TronError(e, TronError.ErrCode.ZCASH_INIT);
+ String outputPath = getParamsFile("sapling-output.params");
+ String outputHash = "a1cb23b93256adce5bce2cb09cefbc96a1d16572675ceb691e9a3626ec15b5b546926f"
+ + "f1c536cfe3a9df07d796b32fdfc3e5d99d65567257bf286cd2858d71a6";
+
+ try {
+ JLibrustzcash.librustzcashInitZksnarkParams(
+ new LibrustzcashParam.InitZksnarkParams(spendPath, spendHash, outputPath, outputHash));
+ } catch (ZksnarkException e) {
+ throw new TronError(e, TronError.ErrCode.ZCASH_INIT);
+ }
+ initialized.set(true);
+ logger.info("init zk param done");
}
- logger.info("init zk param done");
}
private static String getParamsFile(String fileName) {
@@ -61,4 +71,4 @@ private static String getParamsFile(String fileName) {
}
return fileOut.getAbsolutePath();
}
-}
\ No newline at end of file
+}
diff --git a/framework/src/main/java/org/tron/program/DBConvert.java b/framework/src/main/java/org/tron/program/DBConvert.java
deleted file mode 100644
index 7b9d63544dc..00000000000
--- a/framework/src/main/java/org/tron/program/DBConvert.java
+++ /dev/null
@@ -1,413 +0,0 @@
-package org.tron.program;
-
-import static org.fusesource.leveldbjni.JniDBFactory.factory;
-import static org.tron.common.math.Maths.max;
-
-import java.io.File;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-import java.util.stream.Collectors;
-import lombok.extern.slf4j.Slf4j;
-import org.fusesource.leveldbjni.JniDBFactory;
-import org.iq80.leveldb.CompressionType;
-import org.iq80.leveldb.DB;
-import org.iq80.leveldb.DBIterator;
-import org.rocksdb.BlockBasedTableConfig;
-import org.rocksdb.BloomFilter;
-import org.rocksdb.ComparatorOptions;
-import org.rocksdb.Options;
-import org.rocksdb.RocksDB;
-import org.rocksdb.RocksDBException;
-import org.rocksdb.RocksIterator;
-import org.rocksdb.Status;
-import org.tron.common.utils.FileUtil;
-import org.tron.common.utils.MarketOrderPriceComparatorForLevelDB;
-import org.tron.common.utils.MarketOrderPriceComparatorForRockDB;
-import org.tron.common.utils.PropUtil;
-
-@Slf4j
-public class DBConvert implements Callable {
-
- static {
- RocksDB.loadLibrary();
- }
-
- private final String srcDir;
- private final String dstDir;
- private final String dbName;
- private final Path srcDbPath;
- private final Path dstDbPath;
-
- private long srcDbKeyCount = 0L;
- private long dstDbKeyCount = 0L;
- private long srcDbKeySum = 0L;
- private long dstDbKeySum = 0L;
- private long srcDbValueSum = 0L;
- private long dstDbValueSum = 0L;
- private final long startTime;
- private static final int CPUS = Runtime.getRuntime().availableProcessors();
- private static final int BATCH = 256;
- private static final String CHECKPOINT_V2_DIR_NAME = "checkpoint";
-
-
- @Override
- public Boolean call() throws Exception {
- return doConvert();
- }
-
- public DBConvert(String src, String dst, String name) {
- this.srcDir = src;
- this.dstDir = dst;
- this.dbName = name;
- this.srcDbPath = Paths.get(this.srcDir, name);
- this.dstDbPath = Paths.get(this.dstDir, name);
- this.startTime = System.currentTimeMillis();
- }
-
- public static org.iq80.leveldb.Options newDefaultLevelDbOptions() {
- org.iq80.leveldb.Options dbOptions = new org.iq80.leveldb.Options();
- dbOptions.createIfMissing(true);
- dbOptions.paranoidChecks(true);
- dbOptions.verifyChecksums(true);
- dbOptions.compressionType(CompressionType.SNAPPY);
- dbOptions.blockSize(4 * 1024);
- dbOptions.writeBufferSize(10 * 1024 * 1024);
- dbOptions.cacheSize(10 * 1024 * 1024L);
- dbOptions.maxOpenFiles(1000);
- return dbOptions;
- }
-
- public static void main(String[] args) {
- int code = run(args);
- logger.info("exit code {}.", code);
- System.out.printf("exit code %d.\n", code);
- System.exit(code);
- }
-
- public static int run(String[] args) {
- String dbSrc;
- String dbDst;
- if (args.length < 2) {
- dbSrc = "output-directory/database";
- dbDst = "output-directory-dst/database";
- } else {
- dbSrc = args[0];
- dbDst = args[1];
- }
- File dbDirectory = new File(dbSrc);
- if (!dbDirectory.exists()) {
- logger.info(" {} does not exist.", dbSrc);
- return 404;
- }
- List files = Arrays.stream(Objects.requireNonNull(dbDirectory.listFiles()))
- .filter(File::isDirectory)
- .filter(e -> !CHECKPOINT_V2_DIR_NAME.equals(e.getName()))
- .collect(Collectors.toList());
-
- // add checkpoint v2 convert
- File cpV2Dir = new File(Paths.get(dbSrc, CHECKPOINT_V2_DIR_NAME).toString());
- List cpList = null;
- if (cpV2Dir.exists()) {
- cpList = Arrays.stream(Objects.requireNonNull(cpV2Dir.listFiles()))
- .filter(File::isDirectory)
- .collect(Collectors.toList());
- }
-
- if (files.isEmpty()) {
- logger.info("{} does not contain any database.", dbSrc);
- return 0;
- }
- final long time = System.currentTimeMillis();
- final List> res = new ArrayList<>();
-
- final ThreadPoolExecutor esDb = new ThreadPoolExecutor(
- CPUS, 16 * CPUS, 1, TimeUnit.MINUTES,
- new ArrayBlockingQueue<>(CPUS, true), Executors.defaultThreadFactory(),
- new ThreadPoolExecutor.CallerRunsPolicy());
-
- esDb.allowCoreThreadTimeOut(true);
-
- files.forEach(f -> res.add(esDb.submit(new DBConvert(dbSrc, dbDst, f.getName()))));
- // convert v2
- if (cpList != null) {
- cpList.forEach(f -> res.add(esDb.submit(
- new DBConvert(dbSrc + "/" + CHECKPOINT_V2_DIR_NAME,
- dbDst + "/" + CHECKPOINT_V2_DIR_NAME, f.getName()))));
- }
-
- int fails = res.size();
-
- for (Future re : res) {
- try {
- if (re.get()) {
- fails--;
- }
- } catch (InterruptedException e) {
- logger.error("{}", e);
- Thread.currentThread().interrupt();
- } catch (ExecutionException e) {
- logger.error("{}", e);
- }
- }
-
- esDb.shutdown();
- logger.info("database convert use {} seconds total.",
- (System.currentTimeMillis() - time) / 1000);
- if (fails > 0) {
- logger.error("failed!!!!!!!!!!!!!!!!!!!!!!!! size:{}", fails);
- }
- return fails;
- }
-
- public DB newLevelDb(Path db) throws Exception {
- DB database;
- File file = db.toFile();
- org.iq80.leveldb.Options dbOptions = newDefaultLevelDbOptions();
- if ("market_pair_price_to_order".equalsIgnoreCase(this.dbName)) {
- dbOptions.comparator(new MarketOrderPriceComparatorForLevelDB());
- }
- database = factory.open(file, dbOptions);
- return database;
- }
-
- private Options newDefaultRocksDbOptions() {
- Options options = new Options();
- options.setCreateIfMissing(true);
- options.setIncreaseParallelism(1);
- options.setNumLevels(7);
- options.setMaxOpenFiles(5000);
- options.setTargetFileSizeBase(64 * 1024 * 1024);
- options.setTargetFileSizeMultiplier(1);
- options.setMaxBytesForLevelBase(512 * 1024 * 1024);
- options.setMaxBackgroundCompactions(max(1, Runtime.getRuntime().availableProcessors(), true));
- options.setLevel0FileNumCompactionTrigger(4);
- options.setLevelCompactionDynamicLevelBytes(true);
- if ("market_pair_price_to_order".equalsIgnoreCase(this.dbName)) {
- options.setComparator(new MarketOrderPriceComparatorForRockDB(new ComparatorOptions()));
- }
- final BlockBasedTableConfig tableCfg;
- options.setTableFormatConfig(tableCfg = new BlockBasedTableConfig());
- tableCfg.setBlockSize(64 * 1024);
- tableCfg.setBlockCacheSize(32 * 1024 * 1024);
- tableCfg.setCacheIndexAndFilterBlocks(true);
- tableCfg.setPinL0FilterAndIndexBlocksInCache(true);
- tableCfg.setFilter(new BloomFilter(10, false));
- options.prepareForBulkLoad();
- return options;
- }
-
- public RocksDB newRocksDb(Path db) {
- RocksDB database = null;
- try (Options options = newDefaultRocksDbOptions()) {
- database = RocksDB.open(options, db.toString());
- } catch (Exception e) {
- logger.error("{}", e);
- }
- return database;
- }
-
- private void batchInsert(RocksDB rocks, List keys, List values)
- throws Exception {
- try (org.rocksdb.WriteBatch batch = new org.rocksdb.WriteBatch()) {
- for (int i = 0; i < keys.size(); i++) {
- byte[] k = keys.get(i);
- byte[] v = values.get(i);
- batch.put(k, v);
- }
- write(rocks, batch);
- }
- keys.clear();
- values.clear();
- }
-
- /**
- * https://github.com/facebook/rocksdb/issues/6625
- * @param rocks db
- * @param batch write batch
- * @throws Exception RocksDBException
- */
- private void write(RocksDB rocks, org.rocksdb.WriteBatch batch) throws Exception {
- try {
- rocks.write(new org.rocksdb.WriteOptions(), batch);
- } catch (RocksDBException e) {
- // retry
- if (maybeRetry(e)) {
- TimeUnit.MILLISECONDS.sleep(1);
- write(rocks, batch);
- } else {
- throw e;
- }
- }
- }
-
- private boolean maybeRetry(RocksDBException e) {
- boolean retry = false;
- if (e.getStatus() != null) {
- retry = e.getStatus().getCode() == Status.Code.TryAgain
- || e.getStatus().getCode() == Status.Code.Busy
- || e.getStatus().getCode() == Status.Code.Incomplete;
- }
- return retry || (e.getMessage() != null && ("Write stall".equalsIgnoreCase(e.getMessage())
- || ("Incomplete").equalsIgnoreCase(e.getMessage())));
- }
-
- /**
- * https://github.com/facebook/rocksdb/wiki/RocksDB-FAQ .
- * What's the fastest way to load data into RocksDB?
- * @param level leveldb
- * @param rocks rocksdb
- * @return if ok
- */
- public boolean convertLevelToRocksBatchIterator(DB level, RocksDB rocks) {
- // convert
- List keys = new ArrayList<>(BATCH);
- List values = new ArrayList<>(BATCH);
- try (DBIterator levelIterator = level.iterator(
- new org.iq80.leveldb.ReadOptions().fillCache(false))) {
-
- JniDBFactory.pushMemoryPool(1024 * 1024);
- levelIterator.seekToFirst();
-
- while (levelIterator.hasNext()) {
- Map.Entry entry = levelIterator.next();
- byte[] key = entry.getKey();
- byte[] value = entry.getValue();
- srcDbKeyCount++;
- srcDbKeySum = byteArrayToIntWithOne(srcDbKeySum, key);
- srcDbValueSum = byteArrayToIntWithOne(srcDbValueSum, value);
- keys.add(key);
- values.add(value);
- if (keys.size() >= BATCH) {
- try {
- batchInsert(rocks, keys, values);
- } catch (Exception e) {
- logger.error("{}", e);
- return false;
- }
- }
- }
-
- if (!keys.isEmpty()) {
- try {
- batchInsert(rocks, keys, values);
- } catch (Exception e) {
- logger.error("{}", e);
- return false;
- }
- }
- // check
- check(rocks);
- } catch (Exception e) {
- logger.error("{}", e);
- return false;
- } finally {
- try {
- level.close();
- rocks.close();
- JniDBFactory.popMemoryPool();
- } catch (Exception e1) {
- logger.error("{}", e1);
- }
- }
- return dstDbKeyCount == srcDbKeyCount && dstDbKeySum == srcDbKeySum
- && dstDbValueSum == srcDbValueSum;
- }
-
- private void check(RocksDB rocks) throws RocksDBException {
- logger.info("check database {} start", this.dbName);
- // manually call CompactRange()
- logger.info("compact database {} start", this.dbName);
- rocks.compactRange();
- logger.info("compact database {} end", this.dbName);
- // check
- try (org.rocksdb.ReadOptions r = new org.rocksdb.ReadOptions().setFillCache(false);
- RocksIterator rocksIterator = rocks.newIterator(r)) {
- for (rocksIterator.seekToFirst(); rocksIterator.isValid(); rocksIterator.next()) {
- byte[] key = rocksIterator.key();
- byte[] value = rocksIterator.value();
- dstDbKeyCount++;
- dstDbKeySum = byteArrayToIntWithOne(dstDbKeySum, key);
- dstDbValueSum = byteArrayToIntWithOne(dstDbValueSum, value);
- }
- }
- logger.info("check database {} end", this.dbName);
- }
-
- public boolean createEngine(String dir) {
- String enginePath = dir + File.separator + "engine.properties";
-
- if (!FileUtil.createFileIfNotExists(enginePath)) {
- return false;
- }
-
- return PropUtil.writeProperty(enginePath, "ENGINE", "ROCKSDB");
- }
-
- public boolean checkDone(String dir) {
- String enginePath = dir + File.separator + "engine.properties";
- return FileUtil.isExists(enginePath);
-
- }
-
- public boolean doConvert() throws Exception {
-
- if (checkDone(this.dstDbPath.toString())) {
- logger.info(" {} is done, skip it.", this.dbName);
- return true;
- }
-
- File levelDbFile = srcDbPath.toFile();
- if (!levelDbFile.exists()) {
- logger.info(" {} does not exist.", srcDbPath.toString());
- return false;
- }
-
- DB level = newLevelDb(srcDbPath);
-
- if (this.dstDbPath.toFile().exists()) {
- logger.info(" {} begin to clear exist database directory", this.dbName);
- FileUtil.deleteDir(this.dstDbPath.toFile());
- logger.info(" {} clear exist database directory done.", this.dbName);
- }
-
- FileUtil.createDirIfNotExists(dstDir);
- RocksDB rocks = newRocksDb(dstDbPath);
-
- logger.info("Convert database {} start", this.dbName);
- boolean result = convertLevelToRocksBatchIterator(level, rocks)
- && createEngine(dstDbPath.toString());
- long etime = System.currentTimeMillis();
-
- if (result) {
- logger.info("Convert database {} successful end with {} key-value {} minutes",
- this.dbName, this.srcDbKeyCount, (etime - this.startTime) / 1000.0 / 60);
- } else {
- logger.info("Convert database {} failure", this.dbName);
- if (this.dstDbPath.toFile().exists()) {
- logger.info(" {} begin to clear exist database directory", this.dbName);
- FileUtil.deleteDir(this.dstDbPath.toFile());
- logger.info(" {} clear exist database directory done.", this.dbName);
- }
- }
- return result;
- }
-
- public long byteArrayToIntWithOne(long sum, byte[] b) {
- for (byte oneByte : b) {
- sum += oneByte;
- }
- return sum;
- }
-}
\ No newline at end of file
diff --git a/framework/src/main/java/org/tron/program/FullNode.java b/framework/src/main/java/org/tron/program/FullNode.java
index bd275de544c..9f2f497a579 100644
--- a/framework/src/main/java/org/tron/program/FullNode.java
+++ b/framework/src/main/java/org/tron/program/FullNode.java
@@ -21,12 +21,20 @@ public class FullNode {
*/
public static void main(String[] args) {
ExitManager.initExceptionHandler();
- logger.info("Full node running.");
Args.setParam(args, Constant.TESTNET_CONF);
CommonParameter parameter = Args.getInstance();
LogService.load(parameter.getLogbackPath());
+ if (parameter.isSolidityNode()) {
+ SolidityNode.start();
+ return;
+ }
+ if (parameter.isKeystoreFactory()) {
+ KeystoreFactory.start();
+ return;
+ }
+ logger.info("Full node running.");
if (Args.getInstance().isDebug()) {
logger.info("in debug mode, it won't check energy time");
} else {
diff --git a/framework/src/main/java/org/tron/program/KeystoreFactory.java b/framework/src/main/java/org/tron/program/KeystoreFactory.java
index bfd2df22856..8199d7e9076 100755
--- a/framework/src/main/java/org/tron/program/KeystoreFactory.java
+++ b/framework/src/main/java/org/tron/program/KeystoreFactory.java
@@ -1,6 +1,5 @@
package org.tron.program;
-import com.beust.jcommander.JCommander;
import java.io.File;
import java.io.IOException;
import java.util.Scanner;
@@ -11,8 +10,6 @@
import org.tron.common.parameter.CommonParameter;
import org.tron.common.utils.ByteArray;
import org.tron.common.utils.Utils;
-import org.tron.core.Constant;
-import org.tron.core.config.args.Args;
import org.tron.core.exception.CipherException;
import org.tron.keystore.Credentials;
import org.tron.keystore.WalletUtils;
@@ -22,15 +19,8 @@ public class KeystoreFactory {
private static final String FilePath = "Wallet";
- public static void main(String[] args) {
- Args.setParam(args, Constant.TESTNET_CONF);
+ public static void start() {
KeystoreFactory cli = new KeystoreFactory();
-
- JCommander.newBuilder()
- .addObject(cli)
- .build()
- .parse(args);
-
cli.run();
}
diff --git a/framework/src/main/java/org/tron/program/SolidityNode.java b/framework/src/main/java/org/tron/program/SolidityNode.java
index b774ab03aaa..3367141e2a5 100644
--- a/framework/src/main/java/org/tron/program/SolidityNode.java
+++ b/framework/src/main/java/org/tron/program/SolidityNode.java
@@ -5,21 +5,17 @@
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.atomic.AtomicLong;
import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.lang3.BooleanUtils;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.util.ObjectUtils;
import org.tron.common.application.Application;
import org.tron.common.application.ApplicationFactory;
import org.tron.common.application.TronApplicationContext;
import org.tron.common.client.DatabaseGrpcClient;
-import org.tron.common.exit.ExitManager;
import org.tron.common.parameter.CommonParameter;
import org.tron.common.prometheus.Metrics;
import org.tron.core.ChainBaseManager;
-import org.tron.core.Constant;
import org.tron.core.capsule.BlockCapsule;
import org.tron.core.config.DefaultConfig;
-import org.tron.core.config.args.Args;
import org.tron.core.db.Manager;
import org.tron.core.exception.TronError;
import org.tron.protos.Protocol.Block;
@@ -55,25 +51,12 @@ public SolidityNode(Manager dbManager) {
/**
* Start the SolidityNode.
*/
- public static void main(String[] args) {
- ExitManager.initExceptionHandler();
+ public static void start() {
logger.info("Solidity node is running.");
- Args.setParam(args, Constant.TESTNET_CONF);
CommonParameter parameter = CommonParameter.getInstance();
-
- logger.info("index switch is {}",
- BooleanUtils.toStringOnOff(BooleanUtils
- .toBoolean(parameter.getStorage().getIndexSwitch())));
-
if (ObjectUtils.isEmpty(parameter.getTrustNodeAddr())) {
- logger.error("Trust node is not set.");
- return;
- }
- parameter.setSolidityNode(true);
-
- if (parameter.isHelp()) {
- logger.info("Here is the help message.");
- return;
+ throw new TronError(new IllegalArgumentException("Trust node is not set."),
+ TronError.ErrCode.SOLID_NODE_INIT);
}
// init metrics first
Metrics.init();
@@ -88,11 +71,11 @@ public static void main(String[] args) {
context.registerShutdownHook();
appT.startup();
SolidityNode node = new SolidityNode(appT.getDbManager());
- node.start();
+ node.run();
appT.blockUntilShutdown();
}
- private void start() {
+ private void run() {
try {
new Thread(this::getBlock).start();
new Thread(this::processBlock).start();
diff --git a/framework/src/main/java/org/tron/program/Version.java b/framework/src/main/java/org/tron/program/Version.java
index 4e9528ee50e..bd19cc8e971 100644
--- a/framework/src/main/java/org/tron/program/Version.java
+++ b/framework/src/main/java/org/tron/program/Version.java
@@ -4,7 +4,7 @@ public class Version {
public static final String VERSION_NAME = "GreatVoyage-v4.7.7-243-gb3555dd655";
public static final String VERSION_CODE = "18631";
- private static final String VERSION = "4.8.0";
+ private static final String VERSION = "4.8.1";
public static String getVersion() {
return VERSION;
diff --git a/framework/src/main/resources/config-localtest.conf b/framework/src/main/resources/config-localtest.conf
index 405a0f92b2d..bdd5ea14d3e 100644
--- a/framework/src/main/resources/config-localtest.conf
+++ b/framework/src/main/resources/config-localtest.conf
@@ -163,6 +163,7 @@ node {
# httpPBFTPort = 8565
# maxBlockRange = 5000
# maxSubTopics = 1000
+ # maxBlockFilterNum = 30000
}
}
diff --git a/framework/src/main/resources/config.conf b/framework/src/main/resources/config.conf
index d434d9c7203..54f229e4e25 100644
--- a/framework/src/main/resources/config.conf
+++ b/framework/src/main/resources/config.conf
@@ -1,90 +1,80 @@
net {
+ # Type can be 'mainnet' or 'testnet', refers to address type.
+ # Hex address of 'mainnet' begin with 0x41, and 'testnet' begin with 0xa0.
+ # Note: 'testnet' is not related to TRON network Nile, Shasta or private net
type = mainnet
- # type = testnet
}
storage {
# Directory for storing persistent data
- db.engine = "LEVELDB",
+ db.engine = "LEVELDB", // deprecated for arm, because arm only support "ROCKSDB".
db.sync = false,
db.directory = "database",
- index.directory = "index",
- transHistory.switch = "on",
- # You can custom these 14 databases' configs:
-
- # account, account-index, asset-issue, block, block-index,
- # block_KDB, peers, properties, recent-block, trans,
- # utxo, votes, witness, witness_schedule.
- # Otherwise, db configs will remain default and data will be stored in
- # the path of "output-directory" or which is set by "-d" ("--output-directory").
+ # Whether to write transaction result in transactionRetStore
+ transHistory.switch = "on",
- # setting can impove leveldb performance .... start
+ # setting can improve leveldb performance .... start, deprecated for arm
# node: if this will increase process fds,you may be check your ulimit if 'too many open files' error occurs
# see https://github.com/tronprotocol/tips/blob/master/tip-343.md for detail
- # if you find block sync has lower performance,you can try this settings
- #default = {
+ # if you find block sync has lower performance, you can try this settings
+ # default = {
# maxOpenFiles = 100
- #}
- #defaultM = {
+ # }
+ # defaultM = {
# maxOpenFiles = 500
- #}
- #defaultL = {
+ # }
+ # defaultL = {
# maxOpenFiles = 1000
- #}
- # setting can impove leveldb performance .... end
+ # }
+ # setting can improve leveldb performance .... end, deprecated for arm
- # Attention: name is a required field that must be set !!!
+ # You can customize the configuration for each database. Otherwise, the database settings will use
+ # their defaults, and data will be stored in the "output-directory" or in the directory specified
+ # by the "-d" or "--output-directory" option. Attention: name is a required field that must be set!
+ # In this configuration, the name and path properties take effect for both LevelDB and RocksDB storage engines,
+ # while the additional properties (such as createIfMissing, paranoidChecks, compressionType, etc.) only take effect when using LevelDB.
properties = [
- // {
- // name = "account",
- // path = "storage_directory_test",
- // createIfMissing = true,
- // paranoidChecks = true,
- // verifyChecksums = true,
- // compressionType = 1, // compressed with snappy
- // blockSize = 4096, // 4 KB = 4 * 1024 B
- // writeBufferSize = 10485760, // 10 MB = 10 * 1024 * 1024 B
- // cacheSize = 10485760, // 10 MB = 10 * 1024 * 1024 B
- // maxOpenFiles = 100
- // },
- // {
- // name = "account-index",
- // path = "storage_directory_test",
- // createIfMissing = true,
- // paranoidChecks = true,
- // verifyChecksums = true,
- // compressionType = 1, // compressed with snappy
- // blockSize = 4096, // 4 KB = 4 * 1024 B
- // writeBufferSize = 10485760, // 10 MB = 10 * 1024 * 1024 B
- // cacheSize = 10485760, // 10 MB = 10 * 1024 * 1024 B
- // maxOpenFiles = 100
- // },
+ # {
+ # name = "account",
+ # path = "storage_directory_test",
+ # createIfMissing = true, // deprecated for arm start
+ # paranoidChecks = true,
+ # verifyChecksums = true,
+ # compressionType = 1, // compressed with snappy
+ # blockSize = 4096, // 4 KB = 4 * 1024 B
+ # writeBufferSize = 10485760, // 10 MB = 10 * 1024 * 1024 B
+ # cacheSize = 10485760, // 10 MB = 10 * 1024 * 1024 B
+ # maxOpenFiles = 100 // deprecated for arm end
+ # },
+ # {
+ # name = "account-index",
+ # path = "storage_directory_test",
+ # createIfMissing = true,
+ # paranoidChecks = true,
+ # verifyChecksums = true,
+ # compressionType = 1, // compressed with snappy
+ # blockSize = 4096, // 4 KB = 4 * 1024 B
+ # writeBufferSize = 10485760, // 10 MB = 10 * 1024 * 1024 B
+ # cacheSize = 10485760, // 10 MB = 10 * 1024 * 1024 B
+ # maxOpenFiles = 100
+ # },
]
needToUpdateAsset = true
- //dbsettings is needed when using rocksdb as the storage implement (db.engine="ROCKSDB").
- //we'd strongly recommend that do not modify it unless you know every item's meaning clearly.
+ # dbsettings is needed when using rocksdb as the storage implement (db.engine="ROCKSDB").
+ # we'd strongly recommend that do not modify it unless you know every item's meaning clearly.
dbSettings = {
levelNumber = 7
- //compactThreads = 32
+ # compactThreads = 32
blocksize = 64 // n * KB
maxBytesForLevelBase = 256 // n * MB
maxBytesForLevelMultiplier = 10
level0FileNumCompactionTrigger = 4
targetFileSizeBase = 256 // n * MB
targetFileSizeMultiplier = 1
- }
-
- //backup settings when using rocks db as the storage implement (db.engine="ROCKSDB").
- //if you want to use the backup plugin, please confirm set the db.engine="ROCKSDB" above.
- backup = {
- enable = false // indicate whether enable the backup plugin
- propPath = "prop.properties" // record which bak directory is valid
- bak1path = "bak1/database" // you must set two backup directories to prevent application halt unexpected(e.g. kill -9).
- bak2path = "bak2/database"
- frequency = 10000 // indicate backup db once every 10000 blocks processed.
+ maxOpenFiles = 5000
}
balance.history.lookup = false
@@ -95,13 +85,16 @@ storage {
# the estimated number of block transactions (default 1000, min 100, max 10000).
# so the total number of cached transactions is 65536 * txCache.estimatedTransactions
# txCache.estimatedTransactions = 1000
- # if true, transaction cache initialization will be faster. default false
- # txCache.initOptimization = true
- # data root setting, for check data, currently, only reward-vi is used.
+ # if true, transaction cache initialization will be faster. Default: false
+ txCache.initOptimization = true
+
+ # The number of blocks flushed to db in each batch during node syncing. Default: 1
+ # snapshot.maxFlushCount = 1
+ # data root setting, for check data, currently, only reward-vi is used.
# merkleRoot = {
- # reward-vi = 9debcb9924055500aaae98cdee10501c5c39d4daa75800a996f4bdda73dbccd8 // main-net, Sha256Hash, hexString
+ # reward-vi = 9debcb9924055500aaae98cdee10501c5c39d4daa75800a996f4bdda73dbccd8 // main-net, Sha256Hash, hexString
# }
}
@@ -119,13 +112,13 @@ node.discovery = {
#}
node.backup {
+ # udp listen port, each member should have the same configuration
+ port = 10001
+
# my priority, each member should use different priority
priority = 8
- # udp listen port, each member should have the save configuration
- port = 10001
-
- # time interval to send keepAlive message, each member should have the save configuration
+ # time interval to send keepAlive message, each member should have the same configuration
keepAliveInterval = 3000
# peer's ip list, can't contain mine
@@ -135,18 +128,27 @@ node.backup {
]
}
+# Specify the algorithm for generating a public key from private key. To avoid forks, please do not modify it
crypto {
engine = "eckey"
}
-# prometheus metrics start
-# node.metrics = {
-# prometheus{
-# enable=true
-# port="9527"
-# }
-# }
-# prometheus metrics end
+node.metrics = {
+ # prometheus metrics
+ prometheus {
+ enable = false
+ port = 9527
+ }
+
+ # influxdb metrics
+ storageEnable = false # Whether write metrics data into InfluxDb. Default: false.
+ influxdb {
+ ip = ""
+ port = 8086
+ database = ""
+ metricsReportInterval = 10
+ }
+}
node {
# trust node for solidity node
@@ -161,17 +163,16 @@ node {
connection.timeout = 2
fetchBlock.timeout = 200
+ # syncFetchBatchNum = 2000
- tcpNettyWorkThreadNum = 0
-
- udpNettyWorkThreadNum = 1
+ # Number of validate sign thread, default availableProcessors
+ # validateSignThreadNum = 16
maxConnections = 30
+
minConnections = 8
- minActiveConnections = 3
- # Number of validate sign thread, default availableProcessors / 2
- # validateSignThreadNum = 16
+ minActiveConnections = 3
maxConnectionsWithSameIp = 2
@@ -179,11 +180,23 @@ node {
minParticipationRate = 15
+ # allowShieldedTransactionApi = true
+
+ # openPrintLog = true
+
+ # If set to true, SR packs transactions into a block in descending order of fee; otherwise, it packs
+ # them based on their receive timestamp. Default: false
+ # openTransactionSort = false
+
+ # The threshold for the number of broadcast transactions received from each peer every second,
+ # transactions exceeding this threshold will be discarded
+ # maxTps = 1000
+
isOpenFullTcpDisconnect = false
inactiveThreshold = 600 //seconds
p2p {
- version = 11111 # 11111: mainnet; 20180622: testnet
+ version = 11111 # Mainnet:11111; Nile:201910292; Shasta:1
}
active = [
@@ -214,19 +227,6 @@ node {
PBFTPort = 8092
}
- # use your ipv6 address for node discovery and tcp connection, default false
- enableIpv6 = false
-
- # if your node's highest block num is below than all your pees', try to acquire new connection. default false
- effectiveCheckEnable = false
-
- dns {
- # dns urls to get nodes, url format tree://{pubkey}@{domain}, default empty
- treeUrls = [
- #"tree://AKMQMNAJJBL73LXWPXDI4I5ZWWIZ4AWO34DWQ636QOBBXNFXH3LQS@main.trondisco.net",
- ]
- }
-
rpc {
enable = true
port = 50051
@@ -234,6 +234,7 @@ node {
solidityPort = 50061
PBFTEnable = true
PBFTPort = 50071
+
# Number of gRPC thread, default availableProcessors / 2
# thread = 16
@@ -255,17 +256,23 @@ node {
# The maximum size of header list allowed to be received, default 8192
# maxHeaderListSize =
+ # The number of RST_STREAM frames allowed to be sent per connection per period for grpc, by default there is no limit.
+ # maxRstStream =
+
+ # The number of seconds per period for grpc
+ # secondsPerWindow =
+
# Transactions can only be broadcast if the number of effective connections is reached.
minEffectiveConnection = 1
- # The switch of the reflection service, effective for all gRPC services
- # reflectionService = true
+ # The switch of the reflection service, effective for all gRPC services, used for grpcurl tool. Default: false
+ reflectionService = false
}
# number of solidity thread in the FullNode.
# If accessing solidity rpc and http interface timeout, could increase the number of threads,
# The default value is the number of cpu cores of the machine.
- #solidity.threads = 8
+ # solidity.threads = 8
# Limits the maximum percentage (default 75%) of producing block interval
# to provide sufficient time to perform other operations e.g. broadcast block
@@ -274,52 +281,120 @@ node {
# Limits the maximum number (default 700) of transaction from network layer
# netMaxTrxPerSecond = 700
- # open the history query APIs(http&GRPC) when node is a lite fullNode,
- # like {getBlockByNum, getBlockByID, getTransactionByID...}.
- # default: false.
+ # Whether to enable the node detection function. Default: false
+ # nodeDetectEnable = false
+
+ # use your ipv6 address for node discovery and tcp connection. Default: false
+ # enableIpv6 = false
+
+ # if your node's highest block num is below than all your pees', try to acquire new connection. Default: false
+ # effectiveCheckEnable = false
+
+ # Dynamic loading configuration function, disabled by default
+ dynamicConfig = {
+ # enable = false
+ # checkInterval = 600 // Check interval of Configuration file's change, default is 600 seconds
+ }
+
+ # Whether to continue broadcast transactions after at least maxUnsolidifiedBlocks are not solidified. Default: false
+ # unsolidifiedBlockCheck = false
+ # maxUnsolidifiedBlocks = 54
+
+ dns {
+ # dns urls to get nodes, url format tree://{pubkey}@{domain}, default empty
+ treeUrls = [
+ #"tree://AKMQMNAJJBL73LXWPXDI4I5ZWWIZ4AWO34DWQ636QOBBXNFXH3LQS@main.trondisco.net",
+ ]
+
+ # enable or disable dns publish. Default: false
+ # publish = false
+
+ # dns domain to publish nodes, required if publish is true
+ # dnsDomain = "nodes1.example.org"
+
+ # dns private key used to publish, required if publish is true, hex string of length 64
+ # dnsPrivate = "b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291"
+
+ # known dns urls to publish if publish is true, url format tree://{pubkey}@{domain}, default empty
+ # knownUrls = [
+ #"tree://APFGGTFOBVE2ZNAB3CSMNNX6RRK3ODIRLP2AA5U4YFAA6MSYZUYTQ@nodes2.example.org",
+ # ]
+
+ # staticNodes = [
+ # static nodes to published on dns
+ # Sample entries:
+ # "ip:port",
+ # "ip:port"
+ # ]
+
+ # merge several nodes into a leaf of tree, should be 1~5
+ # maxMergeSize = 5
+
+ # only nodes change percent is bigger then the threshold, we update data on dns
+ # changeThreshold = 0.1
+
+ # dns server to publish, required if publish is true, only aws or aliyun is support
+ # serverType = "aws"
+
+ # access key id of aws or aliyun api, required if publish is true, string
+ # accessKeyId = "your-key-id"
+
+ # access key secret of aws or aliyun api, required if publish is true, string
+ # accessKeySecret = "your-key-secret"
+
+ # if publish is true and serverType is aliyun, it's endpoint of aws dns server, string
+ # aliyunDnsEndpoint = "alidns.aliyuncs.com"
+
+ # if publish is true and serverType is aws, it's region of aws api, such as "eu-south-1", string
+ # awsRegion = "us-east-1"
+
+ # if publish is true and server-type is aws, it's host zone id of aws's domain, string
+ # awsHostZoneId = "your-host-zone-id"
+ }
+
+ # open the history query APIs(http&GRPC) when node is a lite FullNode,
+ # like {getBlockByNum, getBlockByID, getTransactionByID...}. Default: false.
# note: above APIs may return null even if blocks and transactions actually are on the blockchain
- # when opening on a lite fullnode. only open it if the consequences being clearly known
+ # when opening on a lite FullNode. only open it if the consequences being clearly known
# openHistoryQueryWhenLiteFN = false
jsonrpc {
- # Note: If you turn on jsonrpc and run it for a while and then turn it off, you will not
- # be able to get the data from eth_getLogs for that period of time.
-
- # httpFullNodeEnable = true
+ # Note: Before release_4.8.1, if you turn on jsonrpc and run it for a while and then turn it off,
+ # you will not be able to get the data from eth_getLogs for that period of time. Default: false
+ # httpFullNodeEnable = false
# httpFullNodePort = 8545
- # httpSolidityEnable = true
+ # httpSolidityEnable = false
# httpSolidityPort = 8555
- # httpPBFTEnable = true
+ # httpPBFTEnable = false
# httpPBFTPort = 8565
# The maximum blocks range to retrieve logs for eth_getLogs, default value is 5000,
# should be > 0, otherwise means no limit.
- # maxBlockRange = 5000
+ maxBlockRange = 5000
# The maximum number of allowed topics within a topic criteria, default value is 1000,
# should be > 0, otherwise means no limit.
- # maxSubTopics = 1000
+ maxSubTopics = 1000
+ # Allowed maximum number for blockFilter
+ maxBlockFilterNum = 50000
}
- # Disabled api list, it will work for http, rpc and pbft, both fullnode and soliditynode,
- # but not jsonrpc.
- # Sample: The setting is case insensitive, GetNowBlock2 is equal to getnowblock2
- #
- # disabledApi = [
- # "getaccount",
- # "getnowblock2"
- # ]
+ # Disabled api list, it will work for http, rpc and pbft, both FullNode and SolidityNode,
+ # but not jsonrpc. The setting is case insensitive, GetNowBlock2 is equal to getnowblock2
+ disabledApi = [
+ # "getaccount",
+ # "getnowblock2"
+ ]
}
## rate limiter config
rate.limiter = {
- # Every api could be set a specific rate limit strategy. Three strategy are supported:GlobalPreemptibleAdapter、IPQPSRateLimiterAdapte、QpsRateLimiterAdapter
- # GlobalPreemptibleAdapter: permit is the number of preemptible resource, every client must apply one resourse
- # before do the request and release the resource after got the reponse automaticlly. permit should be a Integer.
+ # Every api could only set a specific rate limit strategy. Three blocking strategy are supported:
+ # GlobalPreemptibleAdapter: The number of preemptible resource or maximum concurrent requests globally.
# QpsRateLimiterAdapter: qps is the average request count in one second supported by the server, it could be a Double or a Integer.
# IPQPSRateLimiterAdapter: similar to the QpsRateLimiterAdapter, qps could be a Double or a Integer.
- # If do not set, the "default strategy" is set.The "default startegy" is based on QpsRateLimiterAdapter, the qps is set as 10000.
+ # If not set, QpsRateLimiterAdapter with qps=1000 is the default strategy.
#
# Sample entries:
#
@@ -363,9 +438,20 @@ rate.limiter = {
# },
]
+ p2p = {
+ # syncBlockChain = 3.0
+ # fetchInvData = 3.0
+ # disconnect = 1.0
+ }
+
+ # global qps, default 50000
+ global.qps = 50000
+ # IP-based global qps, default 10000
+ global.ip.qps = 10000
}
+
seed.node = {
# List of the seed nodes
# Seed nodes are stable full nodes
@@ -375,18 +461,18 @@ seed.node = {
# "ip:port"
# ]
ip.list = [
- "54.236.37.243:18888",
- "52.53.189.99:18888",
- "18.196.99.16:18888",
- "34.253.187.192:18888",
- "52.56.56.149:18888",
- "35.180.51.163:18888",
- "54.252.224.209:18888",
- "18.228.15.36:18888",
- "52.15.93.92:18888",
- "34.220.77.106:18888",
- "13.127.47.162:18888",
- "13.124.62.58:18888",
+ "3.225.171.164:18888",
+ "52.8.46.215:18888",
+ "3.79.71.167:18888",
+ "108.128.110.16:18888",
+ "18.133.82.227:18888",
+ "35.180.81.133:18888",
+ "13.210.151.5:18888",
+ "18.231.27.82:18888",
+ "3.12.212.122:18888",
+ "52.24.128.7:18888",
+ "15.207.144.3:18888",
+ "3.39.38.55:18888",
"54.151.226.240:18888",
"35.174.93.198:18888",
"18.210.241.149:18888",
@@ -404,7 +490,9 @@ seed.node = {
"54.82.161.39:18888",
"54.179.207.68:18888",
"18.142.82.44:18888",
- "18.163.230.203:18888"
+ "18.163.230.203:18888",
+ # "[2a05:d014:1f2f:2600:1b15:921:d60b:4c60]:18888", // use this if support ipv6
+ # "[2600:1f18:7260:f400:8947:ebf3:78a0:282b]:18888", // use this if support ipv6
]
}
@@ -569,34 +657,33 @@ genesis.block = {
}
]
- timestamp = "0" #2017-8-26 12:00:00
+ timestamp = "0" # Genesis block timestamp, milli seconds
parentHash = "0xe58f33f9baf9305dc6f82b9f1934ea8f0ade2defb951258d50167028c780351f"
}
-// Optional.The default is empty.
-// It is used when the witness account has set the witnessPermission.
-// When it is not empty, the localWitnessAccountAddress represents the address of the witness account,
-// and the localwitness is configured with the private key of the witnessPermissionAddress in the witness account.
-// When it is empty,the localwitness is configured with the private key of the witness account.
-
-//localWitnessAccountAddress =
+# Optional. The default is empty. It is used when the witness account has set the witnessPermission.
+# When it is not empty, the localWitnessAccountAddress represents the address of the witness account,
+# and the localwitness is configured with the private key of the witnessPermissionAddress in the witness account.
+# When it is empty,the localwitness is configured with the private key of the witness account.
+# localWitnessAccountAddress =
localwitness = [
]
-#localwitnesskeystore = [
+# localwitnesskeystore = [
# "localwitnesskeystore.json"
-#]
+# ]
block = {
needSyncCheck = true
- maintenanceTimeInterval = 21600000
- proposalExpireTime = 259200000 // 3 day: 259200000(ms)
+ maintenanceTimeInterval = 21600000 // 6 hours: 21600000(ms)
+ proposalExpireTime = 259200000 // default value: 3 days: 259200000(ms), Note: this value is controlled by committee proposal
+ # checkFrozenTime = 1 // for test only
}
-# Transaction reference block, default is "solid", configure to "head" may accur TaPos error
-# trx.reference.block = "solid" // head;solid;
+# Transaction reference block, default is "solid", configure to "head" may cause TaPos error
+trx.reference.block = "solid" // "head" or "solid"
# This property sets the number of milliseconds after the creation of the transaction that is expired, default value is 60000.
# trx.expiration.timeInMilliseconds = 60000
@@ -607,26 +694,73 @@ vm = {
minTimeRatio = 0.0
maxTimeRatio = 5.0
saveInternalTx = false
+ # lruCacheSize = 500
+ # vmTrace = false
- # Indicates whether the node stores featured internal transactions, such as freeze, vote and so on
+ # Indicates whether the node stores featured internal transactions, such as freeze, vote and so on. Default: false.
# saveFeaturedInternalTx = false
- # Indicates whether the node stores the details of the internal transactions generated by the CANCELALLUNFREEZEV2 opcode, such as bandwidth/energy/tronpower cancel amount.
+ # Indicates whether the node stores the details of the internal transactions generated by the CANCELALLUNFREEZEV2 opcode,
+ # such as bandwidth/energy/tronpower cancel amount. Default: false.
# saveCancelAllUnfreezeV2Details = false
# In rare cases, transactions that will be within the specified maximum execution time (default 10(ms)) are re-executed and packaged
# longRunningTime = 10
- # Indicates whether the node support estimate energy API.
+ # Indicates whether the node support estimate energy API. Default: false.
# estimateEnergy = false
- # Indicates the max retry time for executing transaction in estimating energy.
+ # Indicates the max retry time for executing transaction in estimating energy. Default 3.
# estimateEnergyMaxRetry = 3
}
+# These parameters are designed for private chain testing only and cannot be freely switched on or off in production systems.
committee = {
allowCreationOfContracts = 0 //mainnet:0 (reset by committee),test:1
allowAdaptiveEnergy = 0 //mainnet:0 (reset by committee),test:1
+ # allowCreationOfContracts = 0
+ # allowMultiSign = 0
+ # allowAdaptiveEnergy = 0
+ # allowDelegateResource = 0
+ # allowSameTokenName = 0
+ # allowTvmTransferTrc10 = 0
+ # allowTvmConstantinople = 0
+ # allowTvmSolidity059 = 0
+ # forbidTransferToContract = 0
+ # allowShieldedTRC20Transaction = 0
+ # allowTvmIstanbul = 0
+ # allowMarketTransaction = 0
+ # allowProtoFilterNum = 0
+ # allowAccountStateRoot = 0
+ # changedDelegation = 0
+ # allowPBFT = 0
+ # pBFTExpireNum = 0
+ # allowTransactionFeePool = 0
+ # allowBlackHoleOptimization = 0
+ # allowNewResourceModel = 0
+ # allowReceiptsMerkleRoot = 0
+ # allowTvmFreeze = 0
+ # allowTvmVote = 0
+ # unfreezeDelayDays = 0
+ # allowTvmLondon = 0
+ # allowTvmCompatibleEvm = 0
+ # allowNewRewardAlgorithm = 0
+ # allowAccountAssetOptimization = 0
+ # allowAssetOptimization = 0
+ # allowNewReward = 0
+ # memoFee = 0
+ # allowDelegateOptimization = 0
+ # allowDynamicEnergy = 0
+ # dynamicEnergyThreshold = 0
+ # dynamicEnergyMaxFactor = 0
+ # allowTvmShangHai = 0
+ # allowOldRewardOpt = 0
+ # allowEnergyAdjustment = 0
+ # allowStrictMath = 0
+ # allowTvmCancun = 0
+ # allowTvmBlob = 0
+ # consensusLogicOptimization = 0
+ # allowOptimizedReturnValueOfChainId = 0
}
event.subscribe = {
@@ -635,24 +769,35 @@ event.subscribe = {
bindport = 5555 // bind port
sendqueuelength = 1000 //max length of send queue
}
+ version = 0
+ # Specify the starting block number to sync historical events. This is only applicable when version = 1.
+ # After performing a full event sync, set this value to 0 or a negative number.
+ # startSyncBlockNum = 1
path = "" // absolute path of plugin
server = "" // target server address to receive event triggers
- dbconfig = "" // dbname|username|password
- contractParse = true,
+ # dbname|username|password, if you want to create indexes for collections when the collections
+ # are not exist, you can add version and set it to 2, as dbname|username|password|version
+ # if you use version 2 and one collection not exists, it will create index automaticaly;
+ # if you use version 2 and one collection exists, it will not create index, you must create index manually;
+ dbconfig = ""
+ contractParse = true
topics = [
{
triggerName = "block" // block trigger, the value can't be modified
enable = false
topic = "block" // plugin topic, the value could be modified
+ solidified = false // if set true, just need solidified block. Default: false
},
{
triggerName = "transaction"
enable = false
topic = "transaction"
+ solidified = false
+ ethCompatible = false // if set true, add transactionIndex, cumulativeEnergyUsed, preCumulativeLogCount, logList, energyUnitPrice. Default: false
},
{
- triggerName = "contractevent"
+ triggerName = "contractevent" // contractevent represents contractlog data decoded by the ABI.
enable = false
topic = "contractevent"
},
@@ -660,10 +805,11 @@ event.subscribe = {
triggerName = "contractlog"
enable = false
topic = "contractlog"
+ redundancy = false // if set true, contractevent will also be regarded as contractlog
},
{
- triggerName = "solidity" // solidity block event trigger, the value can't be modified
- enable = true // the default value is true
+ triggerName = "solidity" // solidity block trigger(just include solidity block number and timestamp), the value can't be modified
+ enable = true // Default: true
topic = "solidity"
},
{
@@ -675,6 +821,7 @@ event.subscribe = {
triggerName = "soliditylog"
enable = false
topic = "soliditylog"
+ redundancy = false // if set true, solidityevent will also be regarded as soliditylog
}
]
@@ -689,5 +836,4 @@ event.subscribe = {
"" // contract topic you want to subscribe, if it's set to "", you will receive contract logs/events with any contract topic.
]
}
-
}
diff --git a/framework/src/test/java/org/springframework/http/InvalidMediaTypeException.java b/framework/src/test/java/org/springframework/http/InvalidMediaTypeException.java
new file mode 100644
index 00000000000..3b1e41f1756
--- /dev/null
+++ b/framework/src/test/java/org/springframework/http/InvalidMediaTypeException.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2002-2018 the original author or authors.
+ *
+ * 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
+ *
+ * https://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 org.springframework.http;
+
+import org.springframework.util.InvalidMimeTypeException;
+
+/**
+ * Exception thrown from {@link MediaType#parseMediaType(String)} in case of
+ * encountering an invalid media type specification String.
+ *
+ * @author Juergen Hoeller
+ * @since 3.2.2
+ */
+@SuppressWarnings("serial")
+public class InvalidMediaTypeException extends IllegalArgumentException {
+
+ private final String mediaType;
+
+
+ /**
+ * Create a new InvalidMediaTypeException for the given media type.
+ *
+ * @param mediaType the offending media type
+ * @param message a detail message indicating the invalid part
+ */
+ public InvalidMediaTypeException(String mediaType, String message) {
+ super("Invalid media type \"" + mediaType + "\": " + message);
+ this.mediaType = mediaType;
+ }
+
+ /**
+ * Constructor that allows wrapping {@link InvalidMimeTypeException}.
+ */
+ InvalidMediaTypeException(InvalidMimeTypeException ex) {
+ super(ex.getMessage(), ex);
+ this.mediaType = ex.getMimeType();
+ }
+
+
+ /**
+ * Return the offending media type.
+ */
+ public String getMediaType() {
+ return this.mediaType;
+ }
+
+}
\ No newline at end of file
diff --git a/framework/src/test/java/org/springframework/http/MediaType.java b/framework/src/test/java/org/springframework/http/MediaType.java
new file mode 100644
index 00000000000..84962970235
--- /dev/null
+++ b/framework/src/test/java/org/springframework/http/MediaType.java
@@ -0,0 +1,841 @@
+/*
+ * Copyright 2002-2022 the original author or authors.
+ *
+ * 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
+ *
+ * https://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 org.springframework.http;
+
+import java.io.Serializable;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.InvalidMimeTypeException;
+import org.springframework.util.MimeType;
+import org.springframework.util.MimeTypeUtils;
+import org.springframework.util.StringUtils;
+
+/**
+ * A subclass of {@link MimeType} that adds support for quality parameters
+ * as defined in the HTTP specification.
+ *
+ * @author Arjen Poutsma
+ * @author Juergen Hoeller
+ * @author Rossen Stoyanchev
+ * @author Sebastien Deleuze
+ * @author Kazuki Shimizu
+ * @author Sam Brannen
+ * @see
+ * HTTP 1.1: Semantics and Content, section 3.1.1.1
+ * @since 3.0
+ */
+public class MediaType extends MimeType implements Serializable {
+
+ /**
+ * Public constant media type that includes all media ranges (i.e. "*/*").
+ */
+ public static final MediaType ALL;
+ /**
+ * A String equivalent of {@link MediaType#ALL}.
+ */
+ public static final String ALL_VALUE = "*/*";
+ /**
+ * Public constant media type for {@code application/atom+xml}.
+ */
+ public static final MediaType APPLICATION_ATOM_XML;
+ /**
+ * A String equivalent of {@link MediaType#APPLICATION_ATOM_XML}.
+ */
+ public static final String APPLICATION_ATOM_XML_VALUE = "application/atom+xml";
+ /**
+ * Public constant media type for {@code application/cbor}.
+ *
+ * @since 5.2
+ */
+ public static final MediaType APPLICATION_CBOR;
+ /**
+ * A String equivalent of {@link MediaType#APPLICATION_CBOR}.
+ *
+ * @since 5.2
+ */
+ public static final String APPLICATION_CBOR_VALUE = "application/cbor";
+ /**
+ * Public constant media type for {@code application/x-www-form-urlencoded}.
+ */
+ public static final MediaType APPLICATION_FORM_URLENCODED;
+ /**
+ * A String equivalent of {@link MediaType#APPLICATION_FORM_URLENCODED}.
+ */
+ public static final String APPLICATION_FORM_URLENCODED_VALUE =
+ "application/x-www-form-urlencoded";
+ /**
+ * Public constant media type for {@code application/graphql+json}.
+ *
+ * @see GraphQL over HTTP spec
+ * @since 5.3.19
+ */
+ public static final MediaType APPLICATION_GRAPHQL;
+ /**
+ * A String equivalent of {@link MediaType#APPLICATION_GRAPHQL}.
+ *
+ * @since 5.3.19
+ */
+ public static final String APPLICATION_GRAPHQL_VALUE = "application/graphql+json";
+ /**
+ * Public constant media type for {@code application/json}.
+ */
+ public static final MediaType APPLICATION_JSON;
+ /**
+ * A String equivalent of {@link MediaType#APPLICATION_JSON}.
+ *
+ * @see #APPLICATION_JSON_UTF8_VALUE
+ */
+ public static final String APPLICATION_JSON_VALUE = "application/json";
+ /**
+ * Public constant media type for {@code application/json;charset=UTF-8}.
+ *
+ * @deprecated as of 5.2 in favor of {@link #APPLICATION_JSON}
+ * since major browsers like Chrome
+ *
+ * now comply with the specification and interpret correctly UTF-8 special
+ * characters without requiring a {@code charset=UTF-8} parameter.
+ */
+ @Deprecated
+ public static final MediaType APPLICATION_JSON_UTF8;
+ /**
+ * A String equivalent of {@link MediaType#APPLICATION_JSON_UTF8}.
+ *
+ * @deprecated as of 5.2 in favor of {@link #APPLICATION_JSON_VALUE}
+ * since major browsers like Chrome
+ *
+ * now comply with the specification and interpret correctly UTF-8 special
+ * characters without requiring a {@code charset=UTF-8} parameter.
+ */
+ @Deprecated
+ public static final String APPLICATION_JSON_UTF8_VALUE = "application/json;charset=UTF-8";
+ /**
+ * Public constant media type for {@code application/octet-stream}.
+ */
+ public static final MediaType APPLICATION_OCTET_STREAM;
+ /**
+ * A String equivalent of {@link MediaType#APPLICATION_OCTET_STREAM}.
+ */
+ public static final String APPLICATION_OCTET_STREAM_VALUE = "application/octet-stream";
+ /**
+ * Public constant media type for {@code application/pdf}.
+ *
+ * @since 4.3
+ */
+ public static final MediaType APPLICATION_PDF;
+ /**
+ * A String equivalent of {@link MediaType#APPLICATION_PDF}.
+ *
+ * @since 4.3
+ */
+ public static final String APPLICATION_PDF_VALUE = "application/pdf";
+ /**
+ * Public constant media type for {@code application/problem+json}.
+ *
+ * @see
+ * Problem Details for HTTP APIs, 6.1. application/problem+json
+ * @since 5.0
+ */
+ public static final MediaType APPLICATION_PROBLEM_JSON;
+ /**
+ * A String equivalent of {@link MediaType#APPLICATION_PROBLEM_JSON}.
+ *
+ * @since 5.0
+ */
+ public static final String APPLICATION_PROBLEM_JSON_VALUE = "application/problem+json";
+ /**
+ * Public constant media type for {@code application/problem+json}.
+ *
+ * @see
+ * Problem Details for HTTP APIs, 6.1. application/problem+json
+ * @since 5.0
+ * @deprecated as of 5.2 in favor of {@link #APPLICATION_PROBLEM_JSON}
+ * since major browsers like Chrome
+ *
+ * now comply with the specification and interpret correctly UTF-8 special
+ * characters without requiring a {@code charset=UTF-8} parameter.
+ */
+ @Deprecated
+ public static final MediaType APPLICATION_PROBLEM_JSON_UTF8;
+ /**
+ * A String equivalent of {@link MediaType#APPLICATION_PROBLEM_JSON_UTF8}.
+ *
+ * @since 5.0
+ * @deprecated as of 5.2 in favor of {@link #APPLICATION_PROBLEM_JSON_VALUE}
+ * since major browsers like Chrome
+ *
+ * now comply with the specification and interpret correctly UTF-8 special
+ * characters without requiring a {@code charset=UTF-8} parameter.
+ */
+ @Deprecated
+ public static final String APPLICATION_PROBLEM_JSON_UTF8_VALUE =
+ "application/problem+json;charset=UTF-8";
+ /**
+ * Public constant media type for {@code application/problem+xml}.
+ *
+ * @see
+ * Problem Details for HTTP APIs, 6.2. application/problem+xml
+ * @since 5.0
+ */
+ public static final MediaType APPLICATION_PROBLEM_XML;
+ /**
+ * A String equivalent of {@link MediaType#APPLICATION_PROBLEM_XML}.
+ *
+ * @since 5.0
+ */
+ public static final String APPLICATION_PROBLEM_XML_VALUE = "application/problem+xml";
+ /**
+ * Public constant media type for {@code application/rss+xml}.
+ *
+ * @since 4.3.6
+ */
+ public static final MediaType APPLICATION_RSS_XML;
+ /**
+ * A String equivalent of {@link MediaType#APPLICATION_RSS_XML}.
+ *
+ * @since 4.3.6
+ */
+ public static final String APPLICATION_RSS_XML_VALUE = "application/rss+xml";
+ /**
+ * Public constant media type for {@code application/x-ndjson}.
+ *
+ * @since 5.3
+ */
+ public static final MediaType APPLICATION_NDJSON;
+ /**
+ * A String equivalent of {@link MediaType#APPLICATION_NDJSON}.
+ *
+ * @since 5.3
+ */
+ public static final String APPLICATION_NDJSON_VALUE = "application/x-ndjson";
+ /**
+ * Public constant media type for {@code application/stream+json}.
+ *
+ * @since 5.0
+ * @deprecated as of 5.3, see notice on {@link #APPLICATION_STREAM_JSON_VALUE}.
+ */
+ @Deprecated
+ public static final MediaType APPLICATION_STREAM_JSON;
+ /**
+ * A String equivalent of {@link MediaType#APPLICATION_STREAM_JSON}.
+ *
+ * @since 5.0
+ * @deprecated as of 5.3 since it originates from the W3C Activity Streams
+ * specification which has a more specific purpose and has been since
+ * replaced with a different mime type. Use {@link #APPLICATION_NDJSON} as
+ * a replacement or any other line-delimited JSON format (e.g. JSON Lines,
+ * JSON Text Sequences).
+ */
+ @Deprecated
+ public static final String APPLICATION_STREAM_JSON_VALUE = "application/stream+json";
+ /**
+ * Public constant media type for {@code application/xhtml+xml}.
+ */
+ public static final MediaType APPLICATION_XHTML_XML;
+ /**
+ * A String equivalent of {@link MediaType#APPLICATION_XHTML_XML}.
+ */
+ public static final String APPLICATION_XHTML_XML_VALUE = "application/xhtml+xml";
+ /**
+ * Public constant media type for {@code application/xml}.
+ */
+ public static final MediaType APPLICATION_XML;
+ /**
+ * A String equivalent of {@link MediaType#APPLICATION_XML}.
+ */
+ public static final String APPLICATION_XML_VALUE = "application/xml";
+ /**
+ * Public constant media type for {@code image/gif}.
+ */
+ public static final MediaType IMAGE_GIF;
+ /**
+ * A String equivalent of {@link MediaType#IMAGE_GIF}.
+ */
+ public static final String IMAGE_GIF_VALUE = "image/gif";
+ /**
+ * Public constant media type for {@code image/jpeg}.
+ */
+ public static final MediaType IMAGE_JPEG;
+ /**
+ * A String equivalent of {@link MediaType#IMAGE_JPEG}.
+ */
+ public static final String IMAGE_JPEG_VALUE = "image/jpeg";
+ /**
+ * Public constant media type for {@code image/png}.
+ */
+ public static final MediaType IMAGE_PNG;
+ /**
+ * A String equivalent of {@link MediaType#IMAGE_PNG}.
+ */
+ public static final String IMAGE_PNG_VALUE = "image/png";
+ /**
+ * Public constant media type for {@code multipart/form-data}.
+ */
+ public static final MediaType MULTIPART_FORM_DATA;
+ /**
+ * A String equivalent of {@link MediaType#MULTIPART_FORM_DATA}.
+ */
+ public static final String MULTIPART_FORM_DATA_VALUE = "multipart/form-data";
+ /**
+ * Public constant media type for {@code multipart/mixed}.
+ *
+ * @since 5.2
+ */
+ public static final MediaType MULTIPART_MIXED;
+ /**
+ * A String equivalent of {@link MediaType#MULTIPART_MIXED}.
+ *
+ * @since 5.2
+ */
+ public static final String MULTIPART_MIXED_VALUE = "multipart/mixed";
+ /**
+ * Public constant media type for {@code multipart/related}.
+ *
+ * @since 5.2.5
+ */
+ public static final MediaType MULTIPART_RELATED;
+ /**
+ * A String equivalent of {@link MediaType#MULTIPART_RELATED}.
+ *
+ * @since 5.2.5
+ */
+ public static final String MULTIPART_RELATED_VALUE = "multipart/related";
+ /**
+ * Public constant media type for {@code text/event-stream}.
+ *
+ * @see Server-Sent Events W3C recommendation
+ * @since 4.3.6
+ */
+ public static final MediaType TEXT_EVENT_STREAM;
+ /**
+ * A String equivalent of {@link MediaType#TEXT_EVENT_STREAM}.
+ *
+ * @since 4.3.6
+ */
+ public static final String TEXT_EVENT_STREAM_VALUE = "text/event-stream";
+ /**
+ * Public constant media type for {@code text/html}.
+ */
+ public static final MediaType TEXT_HTML;
+ /**
+ * A String equivalent of {@link MediaType#TEXT_HTML}.
+ */
+ public static final String TEXT_HTML_VALUE = "text/html";
+ /**
+ * Public constant media type for {@code text/markdown}.
+ *
+ * @since 4.3
+ */
+ public static final MediaType TEXT_MARKDOWN;
+ /**
+ * A String equivalent of {@link MediaType#TEXT_MARKDOWN}.
+ *
+ * @since 4.3
+ */
+ public static final String TEXT_MARKDOWN_VALUE = "text/markdown";
+ /**
+ * Public constant media type for {@code text/plain}.
+ */
+ public static final MediaType TEXT_PLAIN;
+ /**
+ * A String equivalent of {@link MediaType#TEXT_PLAIN}.
+ */
+ public static final String TEXT_PLAIN_VALUE = "text/plain";
+ /**
+ * Public constant media type for {@code text/xml}.
+ */
+ public static final MediaType TEXT_XML;
+ /**
+ * A String equivalent of {@link MediaType#TEXT_XML}.
+ */
+ public static final String TEXT_XML_VALUE = "text/xml";
+ private static final long serialVersionUID = 2069937152339670231L;
+ private static final String PARAM_QUALITY_FACTOR = "q";
+ /**
+ * Comparator used by {@link #sortByQualityValue(List)}.
+ */
+ public static final Comparator QUALITY_VALUE_COMPARATOR = (mediaType1, mediaType2) -> {
+ double quality1 = mediaType1.getQualityValue();
+ double quality2 = mediaType2.getQualityValue();
+ int qualityComparison = Double.compare(quality2, quality1);
+ if (qualityComparison != 0) {
+ return qualityComparison; // audio/*;q=0.7 < audio/*;q=0.3
+ } else if (mediaType1.isWildcardType() && !mediaType2.isWildcardType()) { // */* < audio/*
+ return 1;
+ } else if (mediaType2.isWildcardType() && !mediaType1.isWildcardType()) { // audio/* > */*
+ return -1;
+ } else if (!mediaType1.getType().equals(mediaType2.getType())) { // audio/basic == text/html
+ return 0;
+ } else { // mediaType1.getType().equals(mediaType2.getType())
+ if (mediaType1.isWildcardSubtype() && !mediaType2.isWildcardSubtype()) {
+ // audio/* < audio/basic
+ return 1;
+ } else if (mediaType2.isWildcardSubtype() && !mediaType1.isWildcardSubtype()) {
+ // audio/basic > audio/*
+ return -1;
+ } else if (!mediaType1.getSubtype().equals(mediaType2.getSubtype())) {
+ // audio/basic == audio/wave
+ return 0;
+ } else {
+ int paramsSize1 = mediaType1.getParameters().size();
+ int paramsSize2 = mediaType2.getParameters().size();
+ return Integer.compare(paramsSize2, paramsSize1); // audio/basic;level=1 < audio/basic
+ }
+ }
+ };
+ /**
+ * Comparator used by {@link #sortBySpecificity(List)}.
+ */
+ public static final Comparator SPECIFICITY_COMPARATOR =
+ new SpecificityComparator() {
+
+ @Override
+ protected int compareParameters(MediaType mediaType1, MediaType mediaType2) {
+ double quality1 = mediaType1.getQualityValue();
+ double quality2 = mediaType2.getQualityValue();
+ int qualityComparison = Double.compare(quality2, quality1);
+ if (qualityComparison != 0) {
+ return qualityComparison; // audio/*;q=0.7 < audio/*;q=0.3
+ }
+ return super.compareParameters(mediaType1, mediaType2);
+ }
+ };
+
+ static {
+ // Not using "valueOf' to avoid static init cost
+ ALL = new MediaType("*", "*");
+ APPLICATION_ATOM_XML = new MediaType("application", "atom+xml");
+ APPLICATION_CBOR = new MediaType("application", "cbor");
+ APPLICATION_FORM_URLENCODED = new MediaType("application", "x-www-form-urlencoded");
+ APPLICATION_GRAPHQL = new MediaType("application", "graphql+json");
+ APPLICATION_JSON = new MediaType("application", "json");
+ APPLICATION_JSON_UTF8 = new MediaType("application", "json", StandardCharsets.UTF_8);
+ APPLICATION_NDJSON = new MediaType("application", "x-ndjson");
+ APPLICATION_OCTET_STREAM = new MediaType("application", "octet-stream");
+ APPLICATION_PDF = new MediaType("application", "pdf");
+ APPLICATION_PROBLEM_JSON = new MediaType("application", "problem+json");
+ APPLICATION_PROBLEM_JSON_UTF8 = new MediaType("application", "problem+json",
+ StandardCharsets.UTF_8);
+ APPLICATION_PROBLEM_XML = new MediaType("application", "problem+xml");
+ APPLICATION_RSS_XML = new MediaType("application", "rss+xml");
+ APPLICATION_STREAM_JSON = new MediaType("application", "stream+json");
+ APPLICATION_XHTML_XML = new MediaType("application", "xhtml+xml");
+ APPLICATION_XML = new MediaType("application", "xml");
+ IMAGE_GIF = new MediaType("image", "gif");
+ IMAGE_JPEG = new MediaType("image", "jpeg");
+ IMAGE_PNG = new MediaType("image", "png");
+ MULTIPART_FORM_DATA = new MediaType("multipart", "form-data");
+ MULTIPART_MIXED = new MediaType("multipart", "mixed");
+ MULTIPART_RELATED = new MediaType("multipart", "related");
+ TEXT_EVENT_STREAM = new MediaType("text", "event-stream");
+ TEXT_HTML = new MediaType("text", "html");
+ TEXT_MARKDOWN = new MediaType("text", "markdown");
+ TEXT_PLAIN = new MediaType("text", "plain");
+ TEXT_XML = new MediaType("text", "xml");
+ }
+
+ /**
+ * Create a new {@code MediaType} for the given primary type.
+ *
The {@linkplain #getSubtype() subtype} is set to "*", parameters empty.
+ *
+ * @param type the primary type
+ * @throws IllegalArgumentException if any of the parameters contain illegal characters
+ */
+ public MediaType(String type) {
+ super(type);
+ }
+
+ /**
+ * Create a new {@code MediaType} for the given primary type and subtype.
+ *
The parameters are empty.
+ *
+ * @param type the primary type
+ * @param subtype the subtype
+ * @throws IllegalArgumentException if any of the parameters contain illegal characters
+ */
+ public MediaType(String type, String subtype) {
+ super(type, subtype, Collections.emptyMap());
+ }
+
+ /**
+ * Create a new {@code MediaType} for the given type, subtype, and character set.
+ *
+ * @param type the primary type
+ * @param subtype the subtype
+ * @param charset the character set
+ * @throws IllegalArgumentException if any of the parameters contain illegal characters
+ */
+ public MediaType(String type, String subtype, Charset charset) {
+ super(type, subtype, charset);
+ }
+
+ /**
+ * Create a new {@code MediaType} for the given type, subtype, and quality value.
+ *
+ * @param type the primary type
+ * @param subtype the subtype
+ * @param qualityValue the quality value
+ * @throws IllegalArgumentException if any of the parameters contain illegal characters
+ */
+ public MediaType(String type, String subtype, double qualityValue) {
+ this(type, subtype, Collections.singletonMap(PARAM_QUALITY_FACTOR,
+ Double.toString(qualityValue)));
+ }
+
+ /**
+ * Copy-constructor that copies the type, subtype and parameters of the given
+ * {@code MediaType}, and allows to set the specified character set.
+ *
+ * @param other the other media type
+ * @param charset the character set
+ * @throws IllegalArgumentException if any of the parameters contain illegal characters
+ * @since 4.3
+ */
+ public MediaType(MediaType other, Charset charset) {
+ super(other, charset);
+ }
+
+ /**
+ * Copy-constructor that copies the type and subtype of the given {@code MediaType},
+ * and allows for different parameters.
+ *
+ * @param other the other media type
+ * @param parameters the parameters, may be {@code null}
+ * @throws IllegalArgumentException if any of the parameters contain illegal characters
+ */
+ public MediaType(MediaType other, @Nullable Map parameters) {
+ super(other.getType(), other.getSubtype(), parameters);
+ }
+
+
+ /**
+ * Create a new {@code MediaType} for the given type, subtype, and parameters.
+ *
+ * @param type the primary type
+ * @param subtype the subtype
+ * @param parameters the parameters, may be {@code null}
+ * @throws IllegalArgumentException if any of the parameters contain illegal characters
+ */
+ public MediaType(String type, String subtype, @Nullable Map parameters) {
+ super(type, subtype, parameters);
+ }
+
+ /**
+ * Create a new {@code MediaType} for the given {@link MimeType}.
+ * The type, subtype and parameters information is copied and {@code MediaType}-specific
+ * checks on parameters are performed.
+ *
+ * @param mimeType the MIME type
+ * @throws IllegalArgumentException if any of the parameters contain illegal characters
+ * @since 5.3
+ */
+ public MediaType(MimeType mimeType) {
+ super(mimeType);
+ getParameters().forEach(this::checkParameters);
+ }
+
+ /**
+ * Parse the given String value into a {@code MediaType} object,
+ * with this method name following the 'valueOf' naming convention
+ * (as supported by {@link org.springframework.core.convert.ConversionService}.
+ *
+ * @param value the string to parse
+ * @throws InvalidMediaTypeException if the media type value cannot be parsed
+ * @see #parseMediaType(String)
+ */
+ public static MediaType valueOf(String value) {
+ return parseMediaType(value);
+ }
+
+ /**
+ * Parse the given String into a single {@code MediaType}.
+ *
+ * @param mediaType the string to parse
+ * @return the media type
+ * @throws InvalidMediaTypeException if the media type value cannot be parsed
+ */
+ public static MediaType parseMediaType(String mediaType) {
+ MimeType type;
+ try {
+ type = MimeTypeUtils.parseMimeType(mediaType);
+ } catch (InvalidMimeTypeException ex) {
+ throw new InvalidMediaTypeException(ex);
+ }
+ try {
+ return new MediaType(type);
+ } catch (IllegalArgumentException ex) {
+ throw new InvalidMediaTypeException(mediaType, ex.getMessage());
+ }
+ }
+
+ /**
+ * Parse the comma-separated string into a list of {@code MediaType} objects.
+ * This method can be used to parse an Accept or Content-Type header.
+ *
+ * @param mediaTypes the string to parse
+ * @return the list of media types
+ * @throws InvalidMediaTypeException if the media type value cannot be parsed
+ */
+ public static List parseMediaTypes(@Nullable String mediaTypes) {
+ if (!StringUtils.hasLength(mediaTypes)) {
+ return Collections.emptyList();
+ }
+ // Avoid using java.util.stream.Stream in hot paths
+ List tokenizedTypes = MimeTypeUtils.tokenize(mediaTypes);
+ List result = new ArrayList<>(tokenizedTypes.size());
+ for (String type : tokenizedTypes) {
+ if (StringUtils.hasText(type)) {
+ result.add(parseMediaType(type));
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Parse the given list of (potentially) comma-separated strings into a
+ * list of {@code MediaType} objects.
+ * This method can be used to parse an Accept or Content-Type header.
+ *
+ * @param mediaTypes the string to parse
+ * @return the list of media types
+ * @throws InvalidMediaTypeException if the media type value cannot be parsed
+ * @since 4.3.2
+ */
+ public static List parseMediaTypes(@Nullable List mediaTypes) {
+ if (CollectionUtils.isEmpty(mediaTypes)) {
+ return Collections.emptyList();
+ } else if (mediaTypes.size() == 1) {
+ return parseMediaTypes(mediaTypes.get(0));
+ } else {
+ List result = new ArrayList<>(8);
+ for (String mediaType : mediaTypes) {
+ result.addAll(parseMediaTypes(mediaType));
+ }
+ return result;
+ }
+ }
+
+ /**
+ * Re-create the given mime types as media types.
+ *
+ * @since 5.0
+ */
+ public static List asMediaTypes(List mimeTypes) {
+ List mediaTypes = new ArrayList<>(mimeTypes.size());
+ for (MimeType mimeType : mimeTypes) {
+ mediaTypes.add(MediaType.asMediaType(mimeType));
+ }
+ return mediaTypes;
+ }
+
+ /**
+ * Re-create the given mime type as a media type.
+ *
+ * @since 5.0
+ */
+ public static MediaType asMediaType(MimeType mimeType) {
+ if (mimeType instanceof MediaType) {
+ return (MediaType) mimeType;
+ }
+ return new MediaType(mimeType.getType(), mimeType.getSubtype(), mimeType.getParameters());
+ }
+
+ /**
+ * Return a string representation of the given list of {@code MediaType} objects.
+ * This method can be used to for an {@code Accept} or {@code Content-Type} header.
+ *
+ * @param mediaTypes the media types to create a string representation for
+ * @return the string representation
+ */
+ public static String toString(Collection mediaTypes) {
+ return MimeTypeUtils.toString(mediaTypes);
+ }
+
+ /**
+ * Sorts the given list of {@code MediaType} objects by specificity.
+ * Given two media types:
+ *
+ * - if either media type has a {@linkplain #isWildcardType() wildcard type},
+ * then the media type without the wildcard is ordered before the other.
+ * - if the two media types have different {@linkplain #getType() types},
+ * then they are considered equal and remain their current order.
+ * - if either media type has a {@linkplain #isWildcardSubtype() wildcard subtype},
+ * then the media type without the wildcard is sorted before the other.
+ * - if the two media types have different {@linkplain #getSubtype() subtypes},
+ * then they are considered equal and remain their current order.
+ * - if the two media types have different {@linkplain #getQualityValue() quality value},
+ * then the media type with the highest quality value is ordered before the other.
+ * - if the two media types have a different amount of
+ * {@linkplain #getParameter(String) parameters}, then the
+ * media type with the most parameters is ordered before the other.
+ *
+ * For example:
+ *
audio/basic < audio/* < */*
+ * audio/* < audio/*;q=0.7; audio/*;q=0.3
+ * audio/basic;level=1 < audio/basic
+ * audio/basic == text/html
+ * audio/basic == audio/wave
+ *
+ * @param mediaTypes the list of media types to be sorted
+ * @see HTTP 1.1: Semantics
+ * and Content, section 5.3.2
+ */
+ public static void sortBySpecificity(List mediaTypes) {
+ Assert.notNull(mediaTypes, "'mediaTypes' must not be null");
+ if (mediaTypes.size() > 1) {
+ mediaTypes.sort(SPECIFICITY_COMPARATOR);
+ }
+ }
+
+ /**
+ * Sorts the given list of {@code MediaType} objects by quality value.
+ * Given two media types:
+ *
+ * - if the two media types have different {@linkplain #getQualityValue() quality value},
+ * then the media type with the highest quality value is ordered before the other.
+ * - if either media type has a {@linkplain #isWildcardType() wildcard type},
+ * then the media type without the wildcard is ordered before the other.
+ * - if the two media types have different {@linkplain #getType() types},
+ * then they are considered equal and remain their current order.
+ * - if either media type has a {@linkplain #isWildcardSubtype() wildcard subtype},
+ * then the media type without the wildcard is sorted before the other.
+ * - if the two media types have different {@linkplain #getSubtype() subtypes},
+ * then they are considered equal and remain their current order.
+ * - if the two media types have a different amount of
+ * {@linkplain #getParameter(String) parameters}, then the
+ * media type with the most parameters is ordered before the other.
+ *
+ *
+ * @param mediaTypes the list of media types to be sorted
+ * @see #getQualityValue()
+ */
+ public static void sortByQualityValue(List mediaTypes) {
+ Assert.notNull(mediaTypes, "'mediaTypes' must not be null");
+ if (mediaTypes.size() > 1) {
+ mediaTypes.sort(QUALITY_VALUE_COMPARATOR);
+ }
+ }
+
+ /**
+ * Sorts the given list of {@code MediaType} objects by specificity as the
+ * primary criteria and quality value the secondary.
+ *
+ * @see MediaType#sortBySpecificity(List)
+ * @see MediaType#sortByQualityValue(List)
+ */
+ public static void sortBySpecificityAndQuality(List mediaTypes) {
+ Assert.notNull(mediaTypes, "'mediaTypes' must not be null");
+ if (mediaTypes.size() > 1) {
+ mediaTypes.sort(
+ MediaType.SPECIFICITY_COMPARATOR.thenComparing(MediaType.QUALITY_VALUE_COMPARATOR));
+ }
+ }
+
+ @Override
+ protected void checkParameters(String parameter, String value) {
+ super.checkParameters(parameter, value);
+ if (PARAM_QUALITY_FACTOR.equals(parameter)) {
+ String unquotedValue = unquote(value);
+ double d = Double.parseDouble(unquotedValue);
+ Assert.isTrue(d >= 0D && d <= 1D,
+ () -> "Invalid quality value \"" + unquotedValue + "\": should be between 0.0 and 1.0");
+ }
+ }
+
+ /**
+ * Return the quality factor, as indicated by a {@code q} parameter, if any.
+ * Defaults to {@code 1.0}.
+ *
+ * @return the quality factor as double value
+ */
+ public double getQualityValue() {
+ String qualityFactor = getParameter(PARAM_QUALITY_FACTOR);
+ return (qualityFactor != null ? Double.parseDouble(unquote(qualityFactor)) : 1D);
+ }
+
+ /**
+ * Indicate whether this {@code MediaType} includes the given media type.
+ * For instance, {@code text/*} includes {@code text/plain} and {@code text/html},
+ * and {@code application/*+xml} includes {@code application/soap+xml}, etc.
+ * This method is not symmetric.
+ *
Simply calls {@link MimeType#includes(MimeType)} but declared with a
+ * {@code MediaType} parameter for binary backwards compatibility.
+ *
+ * @param other the reference media type with which to compare
+ * @return {@code true} if this media type includes the given media type;
+ * {@code false} otherwise
+ */
+ public boolean includes(@Nullable MediaType other) {
+ return super.includes(other);
+ }
+
+ /**
+ * Indicate whether this {@code MediaType} is compatible with the given media type.
+ *
For instance, {@code text/*} is compatible with {@code text/plain},
+ * {@code text/html}, and vice versa. In effect, this method is similar to
+ * {@link #includes}, except that it is symmetric.
+ *
Simply calls {@link MimeType#isCompatibleWith(MimeType)} but declared with a
+ * {@code MediaType} parameter for binary backwards compatibility.
+ *
+ * @param other the reference media type with which to compare
+ * @return {@code true} if this media type is compatible with the given media type;
+ * {@code false} otherwise
+ */
+ public boolean isCompatibleWith(@Nullable MediaType other) {
+ return super.isCompatibleWith(other);
+ }
+
+ /**
+ * Return a replica of this instance with the quality value of the given {@code MediaType}.
+ *
+ * @return the same instance if the given MediaType doesn't have a quality value,
+ * or a new one otherwise
+ */
+ public MediaType copyQualityValue(MediaType mediaType) {
+ if (!mediaType.getParameters().containsKey(PARAM_QUALITY_FACTOR)) {
+ return this;
+ }
+ Map params = new LinkedHashMap<>(getParameters());
+ params.put(PARAM_QUALITY_FACTOR, mediaType.getParameters().get(PARAM_QUALITY_FACTOR));
+ return new MediaType(this, params);
+ }
+
+ /**
+ * Return a replica of this instance with its quality value removed.
+ *
+ * @return the same instance if the media type doesn't contain a quality value,
+ * or a new one otherwise
+ */
+ public MediaType removeQualityValue() {
+ if (!getParameters().containsKey(PARAM_QUALITY_FACTOR)) {
+ return this;
+ }
+ Map params = new LinkedHashMap<>(getParameters());
+ params.remove(PARAM_QUALITY_FACTOR);
+ return new MediaType(this, params);
+ }
+
+}
\ No newline at end of file
diff --git a/framework/src/test/java/org/tron/common/BaseTest.java b/framework/src/test/java/org/tron/common/BaseTest.java
index 552808b842c..dd4400e10f2 100644
--- a/framework/src/test/java/org/tron/common/BaseTest.java
+++ b/framework/src/test/java/org/tron/common/BaseTest.java
@@ -25,6 +25,8 @@
import org.tron.core.config.args.Args;
import org.tron.core.db.Manager;
import org.tron.core.exception.BalanceInsufficientException;
+import org.tron.core.net.peer.PeerConnection;
+import org.tron.core.net.peer.PeerManager;
import org.tron.core.store.AccountStore;
import org.tron.protos.Protocol;
@@ -68,6 +70,12 @@ public static void destroy() {
Args.clearParam();
}
+ public void closePeer() {
+ for (PeerConnection p : PeerManager.getPeers()) {
+ PeerManager.remove(p.getChannel());
+ }
+ }
+
public Protocol.Block getSignedBlock(ByteString witness, long time, byte[] privateKey) {
long blockTime = System.currentTimeMillis() / 3000 * 3000;
if (time != 0) {
diff --git a/framework/src/test/java/org/tron/common/EntityTest.java b/framework/src/test/java/org/tron/common/EntityTest.java
index 483475a453b..bbdc8631225 100644
--- a/framework/src/test/java/org/tron/common/EntityTest.java
+++ b/framework/src/test/java/org/tron/common/EntityTest.java
@@ -5,13 +5,16 @@
import static org.junit.Assert.assertTrue;
import com.google.common.collect.Lists;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import org.apache.commons.collections4.CollectionUtils;
import org.junit.Before;
import org.junit.Test;
import org.tron.common.entity.NodeInfo;
import org.tron.common.entity.NodeInfo.MachineInfo;
import org.tron.common.entity.NodeInfo.MachineInfo.DeadLockThreadInfo;
+import org.tron.common.entity.PeerInfo;
public class EntityTest {
@@ -54,6 +57,9 @@ public void testDeadLockThreadInfo() {
@Test
public void testNodeInfo() {
+ List peerInfoList = new ArrayList<>();
+ peerInfoList.add(getDefaultPeerInfo());
+
NodeInfo nodeInfo = new NodeInfo();
nodeInfo.setTotalFlow(1L);
nodeInfo.setCheatWitnessInfoMap(new HashMap<>());
@@ -62,6 +68,39 @@ public void testNodeInfo() {
nodeInfo.setMachineInfo(machineInfo);
nodeInfo.setBlock("block");
nodeInfo.setSolidityBlock("solidityBlock");
+ nodeInfo.setPeerList(peerInfoList);
nodeInfo.transferToProtoEntity();
}
+
+ private PeerInfo getDefaultPeerInfo() {
+ PeerInfo peerInfo = new PeerInfo();
+ peerInfo.setAvgLatency(peerInfo.getAvgLatency());
+ peerInfo.setBlockInPorcSize(peerInfo.getBlockInPorcSize());
+ peerInfo.setConnectTime(peerInfo.getConnectTime());
+ peerInfo.setDisconnectTimes(peerInfo.getDisconnectTimes());
+ peerInfo.setHeadBlockTimeWeBothHave(peerInfo.getHeadBlockTimeWeBothHave());
+ peerInfo.setHeadBlockWeBothHave(peerInfo.getHeadBlockWeBothHave());
+ peerInfo.setHost("host");
+ peerInfo.setInFlow(peerInfo.getInFlow());
+ peerInfo.setLastBlockUpdateTime(peerInfo.getLastBlockUpdateTime());
+ peerInfo.setLastSyncBlock("last");
+ peerInfo.setLocalDisconnectReason("localDisconnectReason");
+ peerInfo.setNodeCount(peerInfo.getNodeCount());
+ peerInfo.setNodeId("nodeId");
+ peerInfo.setHeadBlockWeBothHave("headBlockWeBothHave");
+ peerInfo.setRemainNum(peerInfo.getRemainNum());
+ peerInfo.setRemoteDisconnectReason("remoteDisconnectReason");
+ peerInfo.setScore(peerInfo.getScore());
+ peerInfo.setPort(peerInfo.getPort());
+ peerInfo.setSyncFlag(peerInfo.isSyncFlag());
+ peerInfo.setNeedSyncFromPeer(peerInfo.isNeedSyncFromPeer());
+ peerInfo.setNeedSyncFromUs(peerInfo.isNeedSyncFromUs());
+ peerInfo.setSyncToFetchSize(peerInfo.getSyncToFetchSize());
+ peerInfo.setSyncToFetchSizePeekNum(peerInfo.getSyncToFetchSizePeekNum());
+ peerInfo.setSyncBlockRequestedSize(peerInfo.getSyncBlockRequestedSize());
+ peerInfo.setUnFetchSynNum(peerInfo.getUnFetchSynNum());
+ peerInfo.setActive(peerInfo.isActive());
+
+ return peerInfo;
+ }
}
diff --git a/framework/src/test/java/org/tron/common/ParameterTest.java b/framework/src/test/java/org/tron/common/ParameterTest.java
index b16be405f61..2f65189ac1c 100644
--- a/framework/src/test/java/org/tron/common/ParameterTest.java
+++ b/framework/src/test/java/org/tron/common/ParameterTest.java
@@ -129,6 +129,12 @@ public void testCommonParameter() {
assertEquals(10, parameter.getMaxConcurrentCallsPerConnection());
parameter.setFlowControlWindow(20);
assertEquals(20, parameter.getFlowControlWindow());
+ assertEquals(0, parameter.getRpcMaxRstStream());
+ parameter.setRpcMaxRstStream(10);
+ assertEquals(10, parameter.getRpcMaxRstStream());
+ assertEquals(0, parameter.getRpcSecondsPerWindow());
+ parameter.setRpcSecondsPerWindow(5);
+ assertEquals(5, parameter.getRpcSecondsPerWindow());
parameter.setMaxConnectionIdleInMillis(1000);
assertEquals(1000, parameter.getMaxConnectionIdleInMillis());
parameter.setBlockProducedTimeOut(500);
diff --git a/framework/src/test/java/org/tron/common/backup/BackupServerTest.java b/framework/src/test/java/org/tron/common/backup/BackupServerTest.java
index c40aca7e17a..18e264eead2 100644
--- a/framework/src/test/java/org/tron/common/backup/BackupServerTest.java
+++ b/framework/src/test/java/org/tron/common/backup/BackupServerTest.java
@@ -10,6 +10,7 @@
import org.junit.rules.Timeout;
import org.tron.common.backup.socket.BackupServer;
import org.tron.common.parameter.CommonParameter;
+import org.tron.common.utils.PublicMethod;
import org.tron.core.Constant;
import org.tron.core.config.args.Args;
@@ -26,7 +27,7 @@ public class BackupServerTest {
@Before
public void setUp() throws Exception {
Args.setParam(new String[]{"-d", temporaryFolder.newFolder().toString()}, Constant.TEST_CONF);
- CommonParameter.getInstance().setBackupPort(80);
+ CommonParameter.getInstance().setBackupPort(PublicMethod.chooseRandomPort());
List members = new ArrayList<>();
members.add("127.0.0.2");
CommonParameter.getInstance().setBackupMembers(members);
diff --git a/framework/src/test/java/org/tron/common/crypto/ECKeyR1Test.java b/framework/src/test/java/org/tron/common/crypto/ECKeyR1Test.java
new file mode 100644
index 00000000000..23f4163889c
--- /dev/null
+++ b/framework/src/test/java/org/tron/common/crypto/ECKeyR1Test.java
@@ -0,0 +1,261 @@
+package org.tron.common.crypto;
+
+import java.math.BigInteger;
+import java.security.SecureRandom;
+import java.security.SignatureException;
+import lombok.extern.slf4j.Slf4j;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.encoders.Base64;
+import org.junit.Assert;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.tron.common.crypto.ECKey.ECDSASignature;
+import org.tron.common.crypto.curveparams.Secp256r1Params;
+import org.tron.common.utils.ByteArray;
+import org.tron.common.utils.PublicMethod;
+import org.tron.common.utils.Sha256Hash;
+
+@Slf4j
+public class ECKeyR1Test {
+
+ @Test
+ public void testCurveParams() {
+ BigInteger p = new BigInteger(
+ "ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", 16);
+ BigInteger a = new BigInteger(
+ "ffffffff00000001000000000000000000000000fffffffffffffffffffffffc", 16);
+ BigInteger b = new BigInteger(
+ "5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", 16);
+ BigInteger gx = new BigInteger(
+ "6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", 16);
+ BigInteger gy = new BigInteger(
+ "4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5", 16);
+ BigInteger n = new BigInteger(
+ "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", 16);
+ BigInteger h = new BigInteger(
+ "1", 16);
+ Secp256r1Params params = Secp256r1Params.getInstance();
+ Assert.assertTrue(
+ params.getCurve().getCurve().getField().getCharacteristic().compareTo(p) == 0);
+ Assert.assertTrue(params.getCurve().getCurve().getA().toBigInteger().compareTo(a) == 0);
+ Assert.assertTrue(params.getCurve().getCurve().getB().toBigInteger().compareTo(b) == 0);
+ Assert.assertTrue(params.getCurve().getG().getRawXCoord().toBigInteger().compareTo(gx) == 0);
+ Assert.assertTrue(params.getCurve().getG().getRawYCoord().toBigInteger().compareTo(gy) == 0);
+ Assert.assertTrue(params.getCurve().getN().compareTo(n) == 0);
+ Assert.assertTrue(params.getCurve().getH().compareTo(h) == 0);
+ }
+
+ @Test
+ public void testSignAndVerify() {
+ ECKey key = new ECKey(Secp256r1Params.getInstance());
+ Assert.assertTrue(key.getPrivKey().compareTo(Secp256r1Params.getInstance().getN()) < 0);
+ byte[] message = "Raw Transaction".getBytes();
+ byte[] hash = Sha256Hash.hash(true, message);
+ ECDSASignature signature = key.doSign(hash);
+ Assert.assertTrue(
+ ECKey.verify(Secp256r1Params.getInstance(), hash, signature, key.getPubKey()));
+ }
+
+ @Test
+ public void testAddress() throws SignatureException {
+ ECKey key = new ECKey(Secp256r1Params.getInstance());
+
+ byte[] message = "Raw Transaction".getBytes();
+ byte[] hash = Sha256Hash.hash(true, message);
+ ECDSASignature signature = key.sign(hash);
+ byte[] address = ECKey.signatureToAddress(hash, signature.toBase64(),
+ Secp256r1Params.getInstance());
+ Assert.assertArrayEquals(key.getAddress(), address);
+ }
+
+ @Test
+ public void testSignatureMalleability() throws SignatureException {
+ ECKey key = new ECKey(Secp256r1Params.getInstance());
+ byte[] randomBytes = new byte[128];
+ SecureRandom secureRandom = new SecureRandom();
+ secureRandom.nextBytes(randomBytes);
+ byte[] msgHash = Sha256Hash.hash(true, randomBytes);
+ ECDSASignature signature = key.sign(msgHash);
+
+ byte[] pubKeyBytes = ECKey.signatureToKeyBytes(msgHash, signature.toBase64(),
+ Secp256r1Params.getInstance());
+ Assert.assertArrayEquals(pubKeyBytes, key.getPubKey());
+
+ Assert.assertTrue(
+ signature.s.compareTo(Secp256r1Params.getInstance().getHalfCurveOrder()) <= 0);
+ BigInteger flipS = Secp256r1Params.getInstance().getCurve().getN().subtract(signature.s);
+ Assert.assertTrue(flipS.compareTo(Secp256r1Params.getInstance().getHalfCurveOrder()) > 0);
+
+ byte[] flipPubKeyBytes = ECKey.signatureToKeyBytes(msgHash,
+ ECDSASignature.fromComponents(signature.r.toByteArray(), flipS.toByteArray(), signature.v)
+ .toBase64(), Secp256r1Params.getInstance());
+ Assert.assertNotEquals(ByteArray.toHexString(flipPubKeyBytes),
+ ByteArray.toHexString(key.getPubKey()));
+
+ byte flipV;
+ if (signature.v <= 28) {
+ flipV = signature.v == 27 ? (byte) 28 : (byte) 27;
+ } else {
+ flipV = signature.v == 29 ? (byte) 30 : (byte) 29;
+ }
+
+ flipPubKeyBytes = ECKey.signatureToKeyBytes(msgHash,
+ ECDSASignature.fromComponents(signature.r.toByteArray(), flipS.toByteArray(), flipV)
+ .toBase64(), Secp256r1Params.getInstance());
+ Assert.assertArrayEquals(flipPubKeyBytes, key.getPubKey());
+ }
+
+ @Test
+ public void testInvalidSignature() throws SignatureException {
+ byte[] randomBytes = new byte[128];
+ SecureRandom secureRandom = new SecureRandom();
+ secureRandom.nextBytes(randomBytes);
+ byte[] msgHash = Sha256Hash.hash(true, randomBytes);
+
+ String privString = PublicMethod.getRandomPrivateKey();
+ SignInterface ecKey = ECKey.fromPrivate(ByteArray.fromHexString(privString),
+ Secp256r1Params.getInstance());
+ String signature = ((ECKey) ecKey).signHash(msgHash);
+
+ byte[] address = ECKey.signatureToAddress(msgHash, signature, Secp256r1Params.getInstance());
+ Assert.assertArrayEquals(address, ecKey.getAddress());
+
+ ECDSASignature ecdsaSignature;
+ byte[] signatureEncoded = Base64.decode(signature);
+ byte[] r = new byte[32];
+ byte[] s = new byte[32];
+ System.arraycopy(signatureEncoded, 1, r, 0, 32);
+ System.arraycopy(signatureEncoded, 33, s, 0, 32);
+ byte v = signatureEncoded[0];
+
+ for (int i = 0; i < 32; i++) {
+ byte[] r2 = Arrays.copyOf(r, r.length);
+ r2[i] = (byte) (r2[i] + 1);
+ ecdsaSignature = ECDSASignature.fromComponents(r2, s, v);
+ try {
+ byte[] ecdsaAddress = ECKey.signatureToAddress(msgHash, ecdsaSignature,
+ Secp256r1Params.getInstance());
+ Assert.assertNotEquals(ByteArray.toHexString(ecdsaAddress), ByteArray.toHexString(address));
+ } catch (SignatureException e) {
+ Assert.assertEquals(e.getMessage(), "Could not parse pub key");
+ } catch (IllegalArgumentException e) {
+ Assert.assertEquals(e.getMessage(), "Invalid point compression");
+ }
+ }
+
+ for (int i = 0; i < 32; i++) {
+ byte[] s2 = Arrays.copyOf(s, s.length);
+ s2[i] = (byte) (s2[i] + 1);
+ ecdsaSignature = ECDSASignature.fromComponents(r, s2, v);
+
+ byte[] ecdsaAddress = ECKey.signatureToAddress(msgHash, ecdsaSignature,
+ Secp256r1Params.getInstance());
+ Assert.assertNotEquals(ByteArray.toHexString(ecdsaAddress), ByteArray.toHexString(address));
+ }
+
+ for (int i = 27; i < 60; i++) {
+ byte v2 = (byte) (i);
+ if (v2 == v || v2 == v + 4) {
+ continue;
+ }
+ ecdsaSignature = ECDSASignature.fromComponents(r, s, v2);
+ try {
+ byte[] ecdsaAddress = ECKey.signatureToAddress(msgHash, ecdsaSignature,
+ Secp256r1Params.getInstance());
+ Assert.assertNotEquals(ByteArray.toHexString(ecdsaAddress), ByteArray.toHexString(address));
+ } catch (Exception e) {
+ Assert.assertTrue(e instanceof SignatureException);
+ }
+ }
+
+ ecdsaSignature = ECDSASignature.fromComponents(r, s, v);
+ byte[] ecdsaAddress = ECKey.signatureToAddress(msgHash, ecdsaSignature,
+ Secp256r1Params.getInstance());
+ Assert.assertArrayEquals(ecdsaAddress, address);
+ }
+
+ @Ignore
+ @Test
+ public void ecKeySignBench() throws SignatureException {
+ byte[] randomBytes = new byte[128];
+ SecureRandom secureRandom = new SecureRandom();
+ secureRandom.nextBytes(randomBytes);
+ String privString = PublicMethod.getRandomPrivateKey();
+ byte[] msgHash = Sha256Hash.hash(true, randomBytes);
+
+ ECKey ecKeyK1 = ECKey.fromPrivate(ByteArray.fromHexString(privString));
+ int iterations = 10000;
+ // warm up
+ for (int i = 0; i < iterations; i++) {
+ ecKeyK1.signHash(msgHash);
+ }
+ long startTime = System.nanoTime();
+ for (int i = 0; i < iterations; i++) {
+ ecKeyK1.signHash(msgHash);
+ }
+ long endTime = System.nanoTime();
+ long durationNs = endTime - startTime;
+ long nsPerIteration = durationNs / iterations;
+ logger.info("ECKeyK1 sign cost: " + nsPerIteration + "ns per call");
+
+ ECKey ecKeyR1 = ECKey.fromPrivate(ByteArray.fromHexString(privString),
+ Secp256r1Params.getInstance());
+ // warm up
+ for (int i = 0; i < iterations; i++) {
+ ecKeyR1.signHash(msgHash);
+ }
+ startTime = System.nanoTime();
+ for (int i = 0; i < iterations; i++) {
+ ecKeyR1.signHash(msgHash);
+ }
+ endTime = System.nanoTime();
+ durationNs = endTime - startTime;
+ long nsPerIterationV2 = durationNs / iterations;
+ logger.info("ECKeyR1 sign cost: " + nsPerIterationV2 + "ns per call");
+ logger.info("ECKeyK1/ECKeyR1: " + (double) nsPerIteration / nsPerIterationV2);
+ }
+
+ @Ignore
+ @Test
+ public void ecKeyVerifyBench() throws SignatureException {
+ byte[] randomBytes = new byte[128];
+ SecureRandom secureRandom = new SecureRandom();
+ secureRandom.nextBytes(randomBytes);
+ String privString = PublicMethod.getRandomPrivateKey();
+ byte[] msgHash = Sha256Hash.hash(true, randomBytes);
+
+ ECKey ecKeyK1 = ECKey.fromPrivate(ByteArray.fromHexString(privString));
+ String signature = ecKeyK1.signHash(msgHash);
+ int iterations = 10000;
+ // warm up
+ for (int i = 0; i < iterations; i++) {
+ ECKey.signatureToAddress(msgHash, signature);
+ }
+ long startTime = System.nanoTime();
+ for (int i = 0; i < iterations; i++) {
+ ECKey.signatureToAddress(msgHash, signature);
+ }
+ long endTime = System.nanoTime();
+ long durationNs = endTime - startTime;
+ long nsPerIteration = durationNs / iterations;
+ logger.info("ECKeyK1 verify cost: " + nsPerIteration + "ns per call");
+
+ ECKey ecKeyR1 = ECKey.fromPrivate(ByteArray.fromHexString(privString),
+ Secp256r1Params.getInstance());
+ String signatureV2 = ecKeyR1.signHash(msgHash);
+
+ // warm up
+ for (int i = 0; i < iterations; i++) {
+ ECKey.signatureToAddress(msgHash, signatureV2, Secp256r1Params.getInstance());
+ }
+ startTime = System.nanoTime();
+ for (int i = 0; i < iterations; i++) {
+ ECKey.signatureToAddress(msgHash, signatureV2, Secp256r1Params.getInstance());
+ }
+ endTime = System.nanoTime();
+ durationNs = endTime - startTime;
+ long nsPerIterationV2 = durationNs / iterations;
+ logger.info("ECKeyR1 verify cost: " + nsPerIterationV2 + "ns per call");
+ logger.info("ECKeyK1/ECKeyR1: " + (double) nsPerIteration / nsPerIterationV2);
+ }
+}
diff --git a/framework/src/test/java/org/tron/common/crypto/ECKeyTest.java b/framework/src/test/java/org/tron/common/crypto/ECKeyTest.java
index 4e7d45ee8d7..3ae7457f5e9 100644
--- a/framework/src/test/java/org/tron/common/crypto/ECKeyTest.java
+++ b/framework/src/test/java/org/tron/common/crypto/ECKeyTest.java
@@ -5,6 +5,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.tron.common.utils.client.utils.AbiUtil.generateOccupationConstantPrivateKey;
@@ -16,8 +17,13 @@
import java.util.Arrays;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.util.encoders.Hex;
+import org.junit.Assert;
import org.junit.Test;
import org.tron.common.crypto.ECKey.ECDSASignature;
+import org.tron.common.crypto.curveparams.Secp256k1Params;
+import org.tron.common.crypto.curveparams.Secp256r1Params;
+import org.tron.common.utils.ByteArray;
+import org.tron.common.utils.Sha256Hash;
import org.tron.core.Wallet;
/**
@@ -67,6 +73,11 @@ public void testFromPrivateKey() {
assertTrue(key.isPubKeyCanonical());
assertTrue(key.hasPrivKey());
assertArrayEquals(pubKey, key.getPubKey());
+
+ key = ECKey.fromPrivate((byte[]) null);
+ assertNull(key);
+ key = ECKey.fromPrivate(new byte[0]);
+ assertNull(key);
}
@Test(expected = IllegalArgumentException.class)
@@ -222,4 +233,35 @@ public void testNodeId() {
assertEquals(key, ECKey.fromNodeId(key.getNodeId()));
}
+
+ @Test
+ public void testSignature() throws SignatureException {
+ ECKey ecKey = new ECKey();
+ String msg = "transaction raw data";
+ byte[] hash = Sha256Hash.hash(true, msg.getBytes());
+ String sig = ecKey.signHash(hash);
+ byte[] address = SignUtils.signatureToAddress(hash, sig, true);
+ Assert.assertArrayEquals(ecKey.getAddress(),address);
+ }
+
+ @Test
+ public void testLargeHashSignature() throws SignatureException {
+ ECKey ecKey = new ECKey();
+ byte[] hash = Secp256k1Params.getInstance().getN().add(BigInteger.TEN).toByteArray();
+ Assert.assertTrue(hash.length == 33);
+ byte[] hash32 = ByteArray.subArray(hash, 1, 33); // remove leading 0x00
+ String sig = ecKey.signHash(hash32);
+ byte[] address = SignUtils.signatureToAddress(hash32, sig, true);
+ Assert.assertArrayEquals(ecKey.getAddress(),address);
+ }
+
+ @Test
+ public void testSignAndVerify() {
+ ECKey key = new ECKey();
+ byte[] message = "Raw Transaction".getBytes();
+ byte[] hash = Sha256Hash.hash(true, message);
+ ECDSASignature signature = key.doSign(hash);
+ Assert.assertTrue(
+ ECKey.verify(Secp256k1Params.getInstance(), hash, signature, key.getPubKey()));
+ }
}
diff --git a/framework/src/test/java/org/tron/common/crypto/SM2KeyTest.java b/framework/src/test/java/org/tron/common/crypto/SM2KeyTest.java
index b84026d2085..9b7cc175654 100644
--- a/framework/src/test/java/org/tron/common/crypto/SM2KeyTest.java
+++ b/framework/src/test/java/org/tron/common/crypto/SM2KeyTest.java
@@ -4,6 +4,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.tron.common.utils.client.utils.AbiUtil.generateOccupationConstantPrivateKey;
@@ -15,9 +16,13 @@
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.util.encoders.Hex;
+import org.junit.Assert;
import org.junit.Test;
+import org.tron.common.crypto.curveparams.Secp256k1Params;
import org.tron.common.crypto.sm2.SM2;
import org.tron.common.crypto.sm2.SM2Signer;
+import org.tron.common.utils.ByteArray;
+import org.tron.common.utils.Sha256Hash;
import org.tron.core.Wallet;
/**
@@ -64,6 +69,11 @@ public void testFromPrivateKey() {
assertTrue(key.isPubKeyCanonical());
assertTrue(key.hasPrivKey());
assertArrayEquals(pubKey, key.getPubKey());
+
+ key = SM2.fromPrivate((byte[]) null);
+ assertNull(key);
+ key = SM2.fromPrivate(new byte[0]);
+ assertNull(key);
}
@Test(expected = IllegalArgumentException.class)
@@ -275,4 +285,25 @@ public void testSM3() {
assertEquals("b524f552cd82b8b028476e005c377fb19a87e6fc682d48bb5d42e3d9b9effe76",
Hex.toHexString(eHash));
}
+
+ @Test
+ public void testSignature() throws SignatureException {
+ SM2 sm2 = new SM2();
+ String msg = "transaction raw data";
+ byte[] hash = Sha256Hash.hash(false, msg.getBytes());
+ String sig = sm2.signHash(hash);
+ byte[] address = SignUtils.signatureToAddress(hash, sig, false);
+ Assert.assertArrayEquals(sm2.getAddress(),address);
+ }
+
+ @Test
+ public void testLargeHashSignature() throws SignatureException {
+ SM2 sm2 = new SM2();
+ byte[] hash = Secp256k1Params.getInstance().getN().add(BigInteger.TEN).toByteArray();
+ Assert.assertTrue(hash.length == 33);
+ byte[] hash32 = ByteArray.subArray(hash, 1, 33); // remove leading 0x00
+ String sig = sm2.signHash(hash32);
+ byte[] address = SignUtils.signatureToAddress(hash32, sig, false);
+ Assert.assertArrayEquals(sm2.getAddress(),address);
+ }
}
diff --git a/framework/src/test/java/org/tron/common/logsfilter/NativeMessageQueueTest.java b/framework/src/test/java/org/tron/common/logsfilter/NativeMessageQueueTest.java
index e6bb407bb53..d356e43d66c 100644
--- a/framework/src/test/java/org/tron/common/logsfilter/NativeMessageQueueTest.java
+++ b/framework/src/test/java/org/tron/common/logsfilter/NativeMessageQueueTest.java
@@ -55,18 +55,19 @@ public void publishTrigger() {
public void startSubscribeThread() {
Thread thread = new Thread(() -> {
- ZContext context = new ZContext();
- ZMQ.Socket subscriber = context.createSocket(SocketType.SUB);
+ try (ZContext context = new ZContext()) {
+ ZMQ.Socket subscriber = context.createSocket(SocketType.SUB);
- Assert.assertEquals(true, subscriber.connect(String.format("tcp://localhost:%d", bindPort)));
- Assert.assertEquals(true, subscriber.subscribe(topic));
+ Assert.assertTrue(subscriber.connect(String.format("tcp://localhost:%d", bindPort)));
+ Assert.assertTrue(subscriber.subscribe(topic));
- while (!Thread.currentThread().isInterrupted()) {
- byte[] message = subscriber.recv();
- String triggerMsg = new String(message);
-
- Assert.assertEquals(true, triggerMsg.contains(dataToSend) || triggerMsg.contains(topic));
+ while (!Thread.currentThread().isInterrupted()) {
+ byte[] message = subscriber.recv();
+ String triggerMsg = new String(message);
+ Assert.assertTrue(triggerMsg.contains(dataToSend) || triggerMsg.contains(topic));
+ }
+ // ZMQ.Socket will be automatically closed when ZContext is closed
}
});
thread.start();
diff --git a/framework/src/test/java/org/tron/common/runtime/vm/BatchValidateSignContractTest.java b/framework/src/test/java/org/tron/common/runtime/vm/BatchValidateSignContractTest.java
index fc60d8c0648..c18eb396546 100644
--- a/framework/src/test/java/org/tron/common/runtime/vm/BatchValidateSignContractTest.java
+++ b/framework/src/test/java/org/tron/common/runtime/vm/BatchValidateSignContractTest.java
@@ -15,6 +15,7 @@
import org.tron.core.db.TransactionTrace;
import org.tron.core.vm.PrecompiledContracts;
import org.tron.core.vm.PrecompiledContracts.BatchValidateSign;
+import org.tron.core.vm.config.VMConfig;
@Slf4j
@@ -74,6 +75,13 @@ public void staticCallTest() {
ret = validateMultiSign(hash, signatures, addresses);
Assert.assertEquals(ret.getValue().length, 32);
Assert.assertArrayEquals(ret.getValue(), new byte[32]);
+
+ //after optimized
+ VMConfig.initAllowTvmSelfdestructRestriction(1);
+ ret = validateMultiSign(hash, signatures, addresses);
+ Assert.assertEquals(ret.getValue().length, 32);
+ Assert.assertArrayEquals(ret.getValue(), new byte[32]);
+ VMConfig.initAllowTvmSelfdestructRestriction(0);
System.gc(); // force triggering full gc to avoid timeout for next test
}
diff --git a/framework/src/test/java/org/tron/common/runtime/vm/Create2Test.java b/framework/src/test/java/org/tron/common/runtime/vm/Create2Test.java
index f400b3215ee..6fa2801c51f 100644
--- a/framework/src/test/java/org/tron/common/runtime/vm/Create2Test.java
+++ b/framework/src/test/java/org/tron/common/runtime/vm/Create2Test.java
@@ -19,9 +19,9 @@
import org.tron.core.Wallet;
import org.tron.core.exception.ContractExeException;
import org.tron.core.exception.ContractValidateException;
-import org.tron.core.exception.JsonRpcInvalidParamsException;
import org.tron.core.exception.ReceiptCheckErrException;
import org.tron.core.exception.VMIllegalException;
+import org.tron.core.exception.jsonrpc.JsonRpcInvalidParamsException;
import org.tron.core.services.NodeInfoService;
import org.tron.core.services.jsonrpc.TronJsonRpcImpl;
import org.tron.core.vm.config.ConfigLoader;
diff --git a/framework/src/test/java/org/tron/common/runtime/vm/OperationsTest.java b/framework/src/test/java/org/tron/common/runtime/vm/OperationsTest.java
index 3315005b7d2..d6bbdddc854 100644
--- a/framework/src/test/java/org/tron/common/runtime/vm/OperationsTest.java
+++ b/framework/src/test/java/org/tron/common/runtime/vm/OperationsTest.java
@@ -1,6 +1,8 @@
package org.tron.common.runtime.vm;
import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.tron.core.config.Parameter.ChainConstant.FROZEN_PERIOD;
import java.util.List;
import java.util.Random;
@@ -13,25 +15,33 @@
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
+import org.mockito.Mockito;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.tron.common.BaseTest;
import org.tron.common.parameter.CommonParameter;
import org.tron.common.runtime.InternalTransaction;
import org.tron.common.utils.DecodeUtil;
import org.tron.core.Constant;
+import org.tron.core.Wallet;
+import org.tron.core.capsule.AccountCapsule;
import org.tron.core.config.args.Args;
import org.tron.core.exception.ContractValidateException;
import org.tron.core.store.StoreFactory;
import org.tron.core.vm.EnergyCost;
import org.tron.core.vm.JumpTable;
+import org.tron.core.vm.MessageCall;
import org.tron.core.vm.Op;
import org.tron.core.vm.Operation;
+import org.tron.core.vm.OperationActions;
import org.tron.core.vm.OperationRegistry;
+import org.tron.core.vm.PrecompiledContracts;
import org.tron.core.vm.VM;
import org.tron.core.vm.config.ConfigLoader;
import org.tron.core.vm.config.VMConfig;
import org.tron.core.vm.program.Program;
import org.tron.core.vm.program.invoke.ProgramInvokeMockImpl;
+import org.tron.core.vm.repository.Repository;
import org.tron.protos.Protocol;
@Slf4j
@@ -40,6 +50,8 @@ public class OperationsTest extends BaseTest {
private ProgramInvokeMockImpl invoke;
private Program program;
private final JumpTable jumpTable = OperationRegistry.getTable();
+ @Autowired
+ private Wallet wallet;
@BeforeClass
public static void init() {
@@ -749,6 +761,30 @@ public void testPushDupSwapAndLogOperations() throws ContractValidateException {
Assert.assertEquals(2158, program.getResult().getEnergyUsed());
}
+ @Test
+ public void testCallOperations() throws ContractValidateException {
+ invoke = new ProgramInvokeMockImpl();
+ Protocol.Transaction trx = Protocol.Transaction.getDefaultInstance();
+ InternalTransaction interTrx =
+ new InternalTransaction(trx, InternalTransaction.TrxType.TRX_UNKNOWN_TYPE);
+
+ byte prePrefixByte = DecodeUtil.addressPreFixByte;
+ DecodeUtil.addressPreFixByte = Constant.ADD_PRE_FIX_BYTE_MAINNET;
+ VMConfig.initAllowTvmSelfdestructRestriction(1);
+
+ program = new Program(new byte[0], new byte[0], invoke, interTrx);
+ MessageCall messageCall = new MessageCall(
+ Op.CALL, new DataWord(10000),
+ DataWord.ZERO(), DataWord.ZERO(),
+ DataWord.ZERO(), DataWord.ZERO(),
+ DataWord.ZERO(), DataWord.ZERO(),
+ DataWord.ZERO(), false);
+ program.callToPrecompiledAddress(messageCall, new PrecompiledContracts.ECRecover());
+
+ DecodeUtil.addressPreFixByte = prePrefixByte;
+ VMConfig.initAllowTvmSelfdestructRestriction(0);
+ }
+
@Test
public void testOtherOperations() throws ContractValidateException {
invoke = new ProgramInvokeMockImpl();
@@ -886,6 +922,12 @@ public void testSuicideCost() throws ContractValidateException {
Assert.assertEquals(25000, EnergyCost.getSuicideCost2(program));
invoke.getDeposit().createAccount(receiver2, Protocol.AccountType.Normal);
Assert.assertEquals(0, EnergyCost.getSuicideCost2(program));
+
+ byte[] receiver3 = generateRandomAddress();
+ program.stackPush(new DataWord(receiver3));
+ Assert.assertEquals(30000, EnergyCost.getSuicideCost3(program));
+ invoke.getDeposit().createAccount(receiver3, Protocol.AccountType.Normal);
+ Assert.assertEquals(5000, EnergyCost.getSuicideCost3(program));
}
@Test
@@ -911,6 +953,85 @@ public void testSuicideAction() throws ContractValidateException {
VMConfig.initAllowEnergyAdjustment(0);
}
+ @Test
+ public void testCanSuicide2() throws ContractValidateException {
+ VMConfig.initAllowTvmFreeze(1);
+ VMConfig.initAllowTvmFreezeV2(1);
+
+ byte[] contractAddr = Hex.decode("41471fd3ad3e9eeadeec4608b92d16ce6b500704cc");
+ invoke = new ProgramInvokeMockImpl(StoreFactory.getInstance(), new byte[0], contractAddr);
+
+ program = new Program(null, null, invoke,
+ new InternalTransaction(
+ Protocol.Transaction.getDefaultInstance(),
+ InternalTransaction.TrxType.TRX_UNKNOWN_TYPE));
+ program.getContractState().createAccount(
+ program.getContextAddress(), Protocol.AccountType.Contract);
+ Assert.assertTrue(program.canSuicide2());
+
+ long nowInMs =
+ program.getContractState().getDynamicPropertiesStore().getLatestBlockHeaderTimestamp();
+ long expireTime = nowInMs + FROZEN_PERIOD;
+ AccountCapsule owner = program.getContractState().getAccount(program.getContextAddress());
+ owner.setFrozenForEnergy(1000000, expireTime);
+ program.getContractState().updateAccount(program.getContextAddress(), owner);
+ Assert.assertFalse(program.canSuicide2());
+
+ VMConfig.initAllowTvmFreeze(0);
+ VMConfig.initAllowTvmFreezeV2(0);
+ }
+
+ @Test
+ public void testSuicideAction2() throws ContractValidateException {
+ byte[] contractAddr = Hex.decode("41471fd3ad3e9eeadeec4608b92d16ce6b500704cc");
+ invoke = new ProgramInvokeMockImpl(StoreFactory.getInstance(), new byte[0], contractAddr);
+ Assert.assertTrue(invoke.getDeposit().isNewContract(contractAddr));
+
+ program = new Program(null, null, invoke,
+ new InternalTransaction(
+ Protocol.Transaction.getDefaultInstance(),
+ InternalTransaction.TrxType.TRX_UNKNOWN_TYPE));
+
+ VMConfig.initAllowEnergyAdjustment(1);
+ VMConfig.initAllowTvmSelfdestructRestriction(1);
+ VMConfig.initAllowTvmFreeze(1);
+ VMConfig.initAllowTvmFreezeV2(1);
+ VMConfig.initAllowTvmCompatibleEvm(1);
+ VMConfig.initAllowTvmVote(1);
+ byte prePrefixByte = DecodeUtil.addressPreFixByte;
+ DecodeUtil.addressPreFixByte = Constant.ADD_PRE_FIX_BYTE_MAINNET;
+
+ program.stackPush(new DataWord(
+ dbManager.getAccountStore().getBlackhole().getAddress().toByteArray()));
+ OperationActions.suicideAction2(program);
+
+ Assert.assertEquals(1, program.getResult().getDeleteAccounts().size());
+
+
+ invoke = new ProgramInvokeMockImpl(StoreFactory.getInstance(), new byte[0], contractAddr);
+ program = new Program(null, null, invoke,
+ new InternalTransaction(
+ Protocol.Transaction.getDefaultInstance(),
+ InternalTransaction.TrxType.TRX_UNKNOWN_TYPE));
+ Program spyProgram = Mockito.spy(program);
+ Repository realContractState = program.getContractState();
+ Repository spyContractState = Mockito.spy(realContractState);
+ Mockito.when(spyContractState.isNewContract(any(byte[].class))).thenReturn(false);
+ Mockito.when(spyProgram.getContractState()).thenReturn(spyContractState);
+ spyProgram.suicide2(new DataWord(
+ dbManager.getAccountStore().getBlackhole().getAddress().toByteArray()));
+
+ Assert.assertEquals(0, spyProgram.getResult().getDeleteAccounts().size());
+
+ DecodeUtil.addressPreFixByte = prePrefixByte;
+ VMConfig.initAllowEnergyAdjustment(0);
+ VMConfig.initAllowTvmSelfdestructRestriction(0);
+ VMConfig.initAllowTvmFreeze(0);
+ VMConfig.initAllowTvmFreezeV2(0);
+ VMConfig.initAllowTvmCompatibleEvm(0);
+ VMConfig.initAllowTvmVote(0);
+ }
+
@Test
public void testVoteWitnessCost() throws ContractValidateException {
// Build stack environment, the stack from top to bottom is 0x00, 0x80, 0x00, 0x80
diff --git a/framework/src/test/java/org/tron/common/runtime/vm/Secp256R1ContractTest.java b/framework/src/test/java/org/tron/common/runtime/vm/Secp256R1ContractTest.java
new file mode 100644
index 00000000000..c4731413a69
--- /dev/null
+++ b/framework/src/test/java/org/tron/common/runtime/vm/Secp256R1ContractTest.java
@@ -0,0 +1,97 @@
+package org.tron.common.runtime.vm;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.tron.core.db.TransactionTrace.convertToTronAddress;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.google.common.base.Charsets;
+import com.google.common.io.Files;
+import java.io.File;
+import java.util.List;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.tuple.Pair;
+import org.bouncycastle.util.encoders.Hex;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.tron.common.BaseTest;
+import org.tron.common.runtime.ProgramResult;
+import org.tron.core.Constant;
+import org.tron.core.Wallet;
+import org.tron.core.config.args.Args;
+import org.tron.core.store.StoreFactory;
+import org.tron.core.vm.PrecompiledContracts;
+import org.tron.core.vm.PrecompiledContracts.PrecompiledContract;
+import org.tron.core.vm.repository.RepositoryImpl;
+
+@Slf4j
+public class Secp256R1ContractTest extends BaseTest {
+
+ private static final DataWord secp256R1Addr = new DataWord(
+ "0000000000000000000000000000000000000000000000000000000000000100");
+
+ private static final String OWNER_ADDRESS;
+
+ static {
+ Args.setParam(new String[]{"--output-directory", dbPath(), "--debug"}, Constant.TEST_CONF);
+ OWNER_ADDRESS = Wallet.getAddressPreFixString() + "abd4b9367799eaa3197fecb144eb71de1e049abc";
+ }
+
+ @Test
+ public void secp256R1Test() throws Exception {
+ PrecompiledContract secp256R1 = createPrecompiledContract(secp256R1Addr, OWNER_ADDRESS);
+ JSONArray testCases = readJsonFile("secp256r1-test-vectors.json");
+ for (int i = 0; i < testCases.size(); i++) {
+ JSONObject testCase = testCases.getJSONObject(i);
+ String name = testCase.getString("Name");
+ byte[] input = Hex.decode(testCase.getString("Input"));
+ byte[] expected = Hex.decode(testCase.getString("Expected"));
+ Pair result = secp256R1.execute(input);
+ if (result.getLeft()) {
+ assertArrayEquals(String.format("Secp256R1 test: %s failed", name), expected,
+ result.getRight());
+ }
+ }
+ }
+
+ @Ignore
+ @Test
+ public void secp256R1Bench() throws Exception {
+ PrecompiledContract secp256R1 = createPrecompiledContract(secp256R1Addr, OWNER_ADDRESS);
+ JSONObject testCase = readJsonFile("secp256r1-test-vectors.json").getJSONObject(0);
+ byte[] input = Hex.decode(testCase.getString("Input"));
+ bench(secp256R1, input, 10000);
+ }
+
+ private PrecompiledContract createPrecompiledContract(DataWord addr, String ownerAddress) {
+ PrecompiledContract contract = PrecompiledContracts.getContractForAddress(addr);
+ contract.setCallerAddress(convertToTronAddress(Hex.decode(ownerAddress)));
+ contract.setRepository(RepositoryImpl.createRoot(StoreFactory.getInstance()));
+ ProgramResult programResult = new ProgramResult();
+ contract.setResult(programResult);
+ return contract;
+ }
+
+ private JSONArray readJsonFile(String fileName) throws Exception {
+ String file1 = Secp256R1ContractTest.class.getClassLoader()
+ .getResource("json" + File.separator + fileName).getFile();
+ List readLines = Files.readLines(new File(file1),
+ Charsets.UTF_8);
+
+ return JSONArray
+ .parseArray(readLines.stream().reduce((s, s2) -> s + s2).get());
+ }
+
+ private static void bench(PrecompiledContract contract, byte[] input, int itersCount) {
+ int MATH_WARMUP = 1000;
+ for (int i = 0; i < MATH_WARMUP; i++) {
+ contract.execute(input);
+ }
+ long start = System.nanoTime();
+ for (int i = 0; i < itersCount; i++) {
+ contract.execute(input);
+ }
+ long end = System.nanoTime();
+ logger.info("{} cost {} ns", contract.getClass().getSimpleName(), (end - start) / itersCount);
+ }
+}
diff --git a/framework/src/test/java/org/tron/common/runtime/vm/ValidateMultiSignContractTest.java b/framework/src/test/java/org/tron/common/runtime/vm/ValidateMultiSignContractTest.java
index a688f5f9a29..894022fcea1 100644
--- a/framework/src/test/java/org/tron/common/runtime/vm/ValidateMultiSignContractTest.java
+++ b/framework/src/test/java/org/tron/common/runtime/vm/ValidateMultiSignContractTest.java
@@ -25,6 +25,7 @@
import org.tron.core.config.args.Args;
import org.tron.core.store.StoreFactory;
import org.tron.core.vm.PrecompiledContracts.ValidateMultiSign;
+import org.tron.core.vm.config.VMConfig;
import org.tron.core.vm.repository.Repository;
import org.tron.core.vm.repository.RepositoryImpl;
import org.tron.protos.Protocol;
@@ -123,6 +124,13 @@ public void testDifferentCase() {
validateMultiSign(StringUtil.encode58Check(key.getAddress()), permissionId, data, signs)
.getValue(), DataWord.ONE().getData());
+ //after optimized
+ VMConfig.initAllowTvmSelfdestructRestriction(1);
+ Assert.assertArrayEquals(
+ validateMultiSign(StringUtil.encode58Check(key.getAddress()), permissionId, data, signs)
+ .getValue(), DataWord.ONE().getData());
+ VMConfig.initAllowTvmSelfdestructRestriction(0);
+
//weight not enough
signs = new ArrayList<>();
signs.add(Hex.toHexString(key1.sign(toSign).toByteArray()));
diff --git a/framework/src/test/java/org/tron/common/storage/CheckOrInitEngineTest.java b/framework/src/test/java/org/tron/common/storage/CheckOrInitEngineTest.java
new file mode 100644
index 00000000000..90aac10c0b6
--- /dev/null
+++ b/framework/src/test/java/org/tron/common/storage/CheckOrInitEngineTest.java
@@ -0,0 +1,263 @@
+package org.tron.common.storage;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mockStatic;
+import static org.tron.core.db.common.DbSourceInter.ENGINE_FILE;
+import static org.tron.core.db.common.DbSourceInter.ENGINE_KEY;
+import static org.tron.core.db.common.DbSourceInter.LEVELDB;
+import static org.tron.core.db.common.DbSourceInter.ROCKSDB;
+import static org.tron.core.db.common.DbSourceInter.checkOrInitEngine;
+
+import com.google.common.base.Strings;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Paths;
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+import org.tron.common.utils.FileUtil;
+import org.tron.common.utils.PropUtil;
+import org.tron.core.exception.TronError;
+
+
+public class CheckOrInitEngineTest {
+
+ @Rule
+ public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+ private static final String ACCOUNT = "account";
+
+ @After
+ public void clearMocks() {
+ Mockito.clearAllCaches();
+ }
+
+ @Test
+ public void testLevelDbDetectedWhenExpectingRocksDb() throws IOException {
+ try (MockedStatic fileUtil = mockStatic(FileUtil.class);
+ MockedStatic propUtil = mockStatic(PropUtil.class);
+ MockedStatic strings = mockStatic(Strings.class)) {
+
+ String dir = temporaryFolder.newFolder(ACCOUNT).toString();
+ File currentFile = new File(dir, "CURRENT");
+ assertTrue(currentFile.createNewFile());
+ TronError.ErrCode errCode = TronError.ErrCode.ROCKSDB_INIT;
+ TronError exception = assertThrows(TronError.class, () ->
+ checkOrInitEngine(ROCKSDB, dir, errCode));
+ assertEquals("Cannot open LEVELDB database with ROCKSDB engine.",
+ exception.getMessage());
+ assertEquals(errCode, exception.getErrCode());
+ }
+ }
+
+ @Test
+ public void testCannotCreateDir() {
+ try (MockedStatic fileUtil = mockStatic(FileUtil.class)) {
+ String dir = "/invalid/path/that/cannot/be/created";
+
+ fileUtil.when(() -> FileUtil.createDirIfNotExists(dir)).thenReturn(false);
+ TronError.ErrCode errCode = TronError.ErrCode.LEVELDB_INIT;
+ TronError exception = assertThrows(TronError.class, () ->
+ checkOrInitEngine(LEVELDB, dir, errCode));
+ assertEquals("Cannot create dir: " + dir + ".", exception.getMessage());
+ assertEquals(errCode, exception.getErrCode());
+ }
+ }
+
+ @Test
+ public void testCannotCreateEngineFile() throws IOException {
+ try (MockedStatic fileUtil = mockStatic(FileUtil.class)) {
+ String dir = temporaryFolder.newFolder().toString();
+ String engineFile = Paths.get(dir, ENGINE_FILE).toString();
+ fileUtil.when(() -> FileUtil.createDirIfNotExists(dir)).thenReturn(true);
+ fileUtil.when(() -> FileUtil.createFileIfNotExists(engineFile)).thenReturn(false);
+ TronError.ErrCode errCode = TronError.ErrCode.ROCKSDB_INIT;
+ TronError exception = assertThrows(TronError.class, () ->
+ checkOrInitEngine(ROCKSDB, dir, errCode));
+
+ assertEquals("Cannot create file: " + engineFile + ".", exception.getMessage());
+ assertEquals(errCode, exception.getErrCode());
+ }
+ }
+
+ @Test
+ public void testCannotWritePropertyFile() throws IOException {
+ try (MockedStatic fileUtil = mockStatic(FileUtil.class);
+ MockedStatic propUtil = mockStatic(PropUtil.class);
+ MockedStatic strings = mockStatic(Strings.class)) {
+
+ String dir = temporaryFolder.newFolder().toString();
+ String engineFile = Paths.get(dir, ENGINE_FILE).toString();
+
+ fileUtil.when(() -> FileUtil.createDirIfNotExists(dir)).thenReturn(true);
+ fileUtil.when(() -> FileUtil.createFileIfNotExists(engineFile)).thenReturn(true);
+
+ propUtil.when(() -> PropUtil.readProperty(engineFile, ENGINE_KEY)).thenReturn(null);
+ strings.when(() -> Strings.isNullOrEmpty(null)).thenReturn(true);
+
+ propUtil.when(() -> PropUtil.writeProperty(engineFile, ENGINE_KEY, ROCKSDB))
+ .thenReturn(false);
+
+ TronError.ErrCode errCode = TronError.ErrCode.LEVELDB_INIT;
+
+ TronError exception = assertThrows(TronError.class, () ->
+ checkOrInitEngine(ROCKSDB, dir, errCode));
+
+ assertEquals("Cannot write file: " + engineFile + ".", exception.getMessage());
+ assertEquals(errCode, exception.getErrCode());
+ }
+
+ }
+
+ @Test
+ public void testEngineMismatch() throws IOException {
+ try (MockedStatic fileUtil = mockStatic(FileUtil.class);
+ MockedStatic propUtil = mockStatic(PropUtil.class);
+ MockedStatic strings = mockStatic(Strings.class)) {
+
+ String dir = temporaryFolder.newFolder(ACCOUNT).toString();
+ String engineFile = Paths.get(dir, ENGINE_FILE).toString();
+
+ fileUtil.when(() -> FileUtil.createDirIfNotExists(dir)).thenReturn(true);
+ fileUtil.when(() -> FileUtil.createFileIfNotExists(engineFile)).thenReturn(true);
+
+ propUtil.when(() -> PropUtil.readProperty(engineFile, ENGINE_KEY)).thenReturn(LEVELDB);
+ strings.when(() -> Strings.isNullOrEmpty(LEVELDB)).thenReturn(false);
+
+ TronError.ErrCode errCode = TronError.ErrCode.ROCKSDB_INIT;
+
+ TronError exception = assertThrows(TronError.class, () ->
+ checkOrInitEngine(ROCKSDB, dir, errCode));
+
+ assertEquals("Cannot open LEVELDB database with ROCKSDB engine.",
+ exception.getMessage());
+ assertEquals(errCode, exception.getErrCode());
+ }
+ }
+
+ @Test
+ public void testSuccessfulFirstTimeInit() throws IOException {
+ try (MockedStatic fileUtil = mockStatic(FileUtil.class);
+ MockedStatic propUtil = mockStatic(PropUtil.class);
+ MockedStatic strings = mockStatic(Strings.class)) {
+
+ String dir = temporaryFolder.newFolder(ACCOUNT).toString();
+ String engineFile = Paths.get(dir, ENGINE_FILE).toString();
+
+ fileUtil.when(() -> FileUtil.createDirIfNotExists(dir)).thenReturn(true);
+ fileUtil.when(() -> FileUtil.createFileIfNotExists(engineFile)).thenReturn(true);
+
+ propUtil.when(() -> PropUtil.readProperty(engineFile, ENGINE_KEY))
+ .thenReturn(null)
+ .thenReturn(LEVELDB);
+ strings.when(() -> Strings.isNullOrEmpty(null)).thenReturn(true);
+
+ propUtil.when(() -> PropUtil.writeProperty(engineFile, ENGINE_KEY, LEVELDB))
+ .thenReturn(true);
+
+ TronError.ErrCode errCode = TronError.ErrCode.LEVELDB_INIT;
+ checkOrInitEngine(LEVELDB, dir, errCode);
+ }
+ }
+
+ @Test
+ public void testSuccessfulExistingEngine() throws IOException {
+ try (MockedStatic fileUtil = mockStatic(FileUtil.class);
+ MockedStatic propUtil = mockStatic(PropUtil.class);
+ MockedStatic strings = mockStatic(Strings.class)) {
+
+ String dir = temporaryFolder.newFolder(ACCOUNT).toString();
+ String engineFile = Paths.get(dir, ENGINE_FILE).toString();
+
+ fileUtil.when(() -> FileUtil.createDirIfNotExists(dir)).thenReturn(true);
+ fileUtil.when(() -> FileUtil.createFileIfNotExists(engineFile)).thenReturn(true);
+ propUtil.when(() -> PropUtil.readProperty(engineFile, ENGINE_KEY)).thenReturn(ROCKSDB);
+ strings.when(() -> Strings.isNullOrEmpty(ROCKSDB)).thenReturn(false);
+
+ TronError.ErrCode errCode = TronError.ErrCode.ROCKSDB_INIT;
+ checkOrInitEngine(ROCKSDB, dir, errCode);
+ }
+ }
+
+ @Test
+ /**
+ * 000003.log CURRENT LOCK MANIFEST-000002
+ */
+ public void testCurrentFileExistsWithNoEngineFile() throws IOException {
+ try (MockedStatic fileUtil = mockStatic(FileUtil.class);
+ MockedStatic propUtil = mockStatic(PropUtil.class);
+ MockedStatic strings = mockStatic(Strings.class)) {
+
+ String dir = temporaryFolder.newFolder(ACCOUNT).toString();
+ String engineFile = Paths.get(dir, ENGINE_FILE).toString();
+ File currentFile = new File(dir, "CURRENT");
+ assertTrue(currentFile.createNewFile());
+
+ fileUtil.when(() -> FileUtil.createDirIfNotExists(dir)).thenReturn(true);
+ fileUtil.when(() -> FileUtil.createFileIfNotExists(engineFile)).thenReturn(true);
+ propUtil.when(() -> PropUtil.readProperty(engineFile, ENGINE_KEY)).thenReturn(LEVELDB);
+ strings.when(() -> Strings.isNullOrEmpty(LEVELDB)).thenReturn(false);
+
+ TronError.ErrCode errCode = TronError.ErrCode.LEVELDB_INIT;
+
+ checkOrInitEngine(LEVELDB, dir, errCode);
+ }
+ }
+
+ @Test
+ /**
+ * 000003.log CURRENT LOCK MANIFEST-000002 engine.properties(RocksDB)
+ */
+ public void testCurrentFileExistsEngineFileExists() throws IOException {
+ try (MockedStatic fileUtil = mockStatic(FileUtil.class);
+ MockedStatic propUtil = mockStatic(PropUtil.class);
+ MockedStatic strings = mockStatic(Strings.class)) {
+
+ String dir = temporaryFolder.newFolder(ACCOUNT).toString();
+ String engineFile = Paths.get(dir, ENGINE_FILE).toString();
+
+ File currentFile = new File(dir, "CURRENT");
+ File engineFileObj = new File(engineFile);
+ assertTrue(currentFile.createNewFile());
+ assertTrue(engineFileObj.createNewFile());
+
+
+ fileUtil.when(() -> FileUtil.createDirIfNotExists(dir)).thenReturn(true);
+ fileUtil.when(() -> FileUtil.createFileIfNotExists(engineFile)).thenReturn(true);
+
+ propUtil.when(() -> PropUtil.readProperty(engineFile, ENGINE_KEY)).thenReturn(ROCKSDB);
+ strings.when(() -> Strings.isNullOrEmpty(ROCKSDB)).thenReturn(false);
+
+ TronError.ErrCode errCode = TronError.ErrCode.ROCKSDB_INIT;
+ checkOrInitEngine(ROCKSDB, dir, errCode);
+ }
+ }
+
+ @Test
+ public void testEmptyStringEngine() throws IOException {
+ try (MockedStatic