Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.google.protobuf.ByteString;
import io.netty.util.internal.StringUtil;
import java.io.IOException;
import java.security.InvalidParameterException;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
Expand Down Expand Up @@ -60,16 +61,17 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response)

wallet.estimateEnergy(build.build(), trxCap,
trxExtBuilder, retBuilder, estimateEnergyBuilder);
} catch (InvalidParameterException e) {
Util.writeError(response, Return.response_code.OTHER_ERROR.name(), e.getMessage());
return;
} catch (ContractValidateException e) {
retBuilder.setResult(false).setCode(Return.response_code.CONTRACT_VALIDATE_ERROR)
.setMessage(ByteString.copyFromUtf8(e.getMessage()));
String message = e.getMessage() != null ? e.getMessage() : "contract validate error";
Util.writeError(response, Return.response_code.CONTRACT_VALIDATE_ERROR.name(), message);
return;
} catch (Exception e) {
String errString = null;
if (e.getMessage() != null) {
errString = e.getMessage().replaceAll("[\"]", "\'");
}
retBuilder.setResult(false).setCode(Return.response_code.OTHER_ERROR)
.setMessage(ByteString.copyFromUtf8(e.getClass() + " : " + errString));
logger.warn("internal error", e);
Util.writeError(response, Return.response_code.OTHER_ERROR.name(), Util.INTERNAL_ERROR_MSG);
return;
}
estimateEnergyBuilder.setResult(retBuilder);
response.getWriter().println(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.google.protobuf.ByteString;
import io.netty.util.internal.StringUtil;
import java.io.IOException;
import java.security.InvalidParameterException;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
Expand Down Expand Up @@ -67,16 +68,17 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response)
trx = Util.setTransactionExtraData(jsonObject, trx, visible);
trxExtBuilder.setTransaction(trx);
retBuilder.setResult(true).setCode(response_code.SUCCESS);
} catch (InvalidParameterException e) {
Util.writeError(response, response_code.OTHER_ERROR.name(), e.getMessage());
return;
} catch (ContractValidateException e) {
retBuilder.setResult(false).setCode(response_code.CONTRACT_VALIDATE_ERROR)
.setMessage(ByteString.copyFromUtf8(e.getMessage()));
String message = e.getMessage() != null ? e.getMessage() : "contract validate error";
Util.writeError(response, response_code.CONTRACT_VALIDATE_ERROR.name(), message);
return;
} catch (Exception e) {
String errString = null;
if (e.getMessage() != null) {
errString = e.getMessage().replaceAll("[\"]", "\'");
}
retBuilder.setResult(false).setCode(response_code.OTHER_ERROR)
.setMessage(ByteString.copyFromUtf8(e.getClass() + " : " + errString));
logger.warn("internal error", e);
Util.writeError(response, response_code.OTHER_ERROR.name(), Util.INTERNAL_ERROR_MSG);
return;
}
trxExtBuilder.setResult(retBuilder);
response.getWriter().println(Util.printTransactionExtention(trxExtBuilder.build(), visible));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ private void validateParameter(String contract) {
if (StringUtil.isNullOrEmpty(jsonObject.getString(Util.CONTRACT_ADDRESS))) {
throw new InvalidParameterException(Util.CONTRACT_ADDRESS + " isn't set.");
}
Util.validateAddressesAndHex(jsonObject, Util.getVisiblePost(contract));
}

protected void doPost(HttpServletRequest request, HttpServletResponse response)
Expand Down Expand Up @@ -85,16 +86,17 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response)
trx = Util.setTransactionPermissionId(jsonObject, trx);
trxExtBuilder.setTransaction(trx);
retBuilder.setResult(true).setCode(response_code.SUCCESS);
} catch (InvalidParameterException e) {
Util.writeError(response, response_code.OTHER_ERROR.name(), e.getMessage());
return;
} catch (ContractValidateException e) {
retBuilder.setResult(false).setCode(response_code.CONTRACT_VALIDATE_ERROR)
.setMessage(ByteString.copyFromUtf8(e.getMessage()));
String message = e.getMessage() != null ? e.getMessage() : "contract validate error";
Util.writeError(response, response_code.CONTRACT_VALIDATE_ERROR.name(), message);
return;
} catch (Exception e) {
String errString = null;
if (e.getMessage() != null) {
errString = e.getMessage().replaceAll("[\"]", "\'");
}
retBuilder.setResult(false).setCode(response_code.OTHER_ERROR)
.setMessage(ByteString.copyFromUtf8(e.getClass() + " : " + errString));
logger.warn("internal error", e);
Util.writeError(response, response_code.OTHER_ERROR.name(), Util.INTERNAL_ERROR_MSG);
return;
}
trxExtBuilder.setResult(retBuilder);
response.getWriter().println(Util.printTransactionExtention(trxExtBuilder.build(), visible));
Expand Down
79 changes: 79 additions & 0 deletions framework/src/main/java/org/tron/core/services/http/Util.java
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ public class Util {
public static final String FUNCTION_PARAMETER = "parameter";
public static final String CALL_DATA = "data";

public static final String INVALID_ADDRESS_BASE58CHECK =
"invalid address: base58check failed";
public static final String INVALID_HEX_LENGTH = "invalid hex: length must be even";
public static final String INTERNAL_ERROR_MSG = "internal error";

public static boolean hasMeaningfulEvents(ProtocolStringList events) {
return events.stream().anyMatch(s -> !s.isEmpty());
}
Expand Down Expand Up @@ -661,6 +666,80 @@ public static void validateParameter(String contract) throws InvalidParameterExc
throw new InvalidParameterException("While trying to deploy, "
+ FUNCTION_SELECTOR + " and " + CALL_DATA + " can not be both set.");
}
boolean visible = false;
if (jsonObject.containsKey(VISIBLE)) {
visible = Boolean.parseBoolean(jsonObject.getString(VISIBLE));
}
validateAddressesAndHex(jsonObject, visible);
}

/**
* Validate address encoding and hex payload length for TVM HTTP APIs.
* Base58Check addresses are checked when {@code visible} is true; hex
* fields must have even length.
*/
public static void validateAddressesAndHex(JSONObject jsonObject, boolean visible) {
validateAddressValue(jsonObject.getString(OWNER_ADDRESS), visible);
validateAddressValue(jsonObject.getString(CONTRACT_ADDRESS), visible);
validateHexString(jsonObject.getString(FUNCTION_PARAMETER));
validateHexString(jsonObject.getString(CALL_DATA));
}

/**
* Validate a single address. Empty values are ignored so required-field
* checks can remain the caller's responsibility.
*/
public static void validateAddressValue(String address, boolean visible) {
if (StringUtils.isEmpty(address)) {
return;
}
if (visible) {
byte[] decoded;
try {
decoded = decodeFromBase58Check(address);
} catch (IllegalArgumentException e) {
throw new InvalidParameterException(INVALID_ADDRESS_BASE58CHECK);
}
if (decoded == null) {
throw new InvalidParameterException(INVALID_ADDRESS_BASE58CHECK);
}
} else if ((address.length() & 1) != 0) {
throw new InvalidParameterException(INVALID_HEX_LENGTH);
}
}

/**
* Reject odd-length hex so {@code ByteArray.fromHexString} cannot NPE.
*/
public static void validateHexString(String hex) {
if (StringUtils.isEmpty(hex)) {
return;
}
String value = hex;
if (value.length() >= 2
&& (value.startsWith("0x") || value.startsWith("0X"))) {
value = value.substring(2);
}
if ((value.length() & 1) != 0) {
throw new InvalidParameterException(INVALID_HEX_LENGTH);
}
}

/**
* Write a sanitized HTTP API error. Parameter errors pass a stable
* client-facing message; internal errors must use {@link #INTERNAL_ERROR_MSG}
* and never include {@code e.getClass()} or {@code e.getMessage()}.
*/
public static void writeError(HttpServletResponse response, String code, String message)
throws IOException {
String safeMessage = message == null ? INTERNAL_ERROR_MSG : message;
JSONObject result = new JSONObject();
result.put("result", false);
result.put("code", code);
result.put("message", safeMessage);
JSONObject json = new JSONObject();
json.put("result", result);
response.getWriter().println(json.toJSONString());
}

public static String getJsonString(String str) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package org.tron.core.services.http;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import org.junit.Test;
import org.springframework.mock.web.MockHttpServletResponse;
import org.tron.common.crypto.ECKey;
import org.tron.common.utils.ByteArray;

public class EstimateEnergyServletTest extends BaseHttpTest {

private static final String ISSUE_OWNER = "TKgD8Qnx9Zw3RNjdiU2i5y2Swa2y4QvG6v";
private static final String USDT = "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t";
private static final String ODD_HEX =
"0000000000000000000000418a8e8b8c8d8e8f9a9b9c9d9e9f0a1b2c3d4e5f6";

private EstimateEnergyServlet servlet;

@Override
protected void setUpMocks() throws Exception {
servlet = new EstimateEnergyServlet();
injectWallet(servlet);
}

@Test
public void testInvalidBase58CheckOwnerReturnsStableError() throws Exception {
String body = "{\"owner_address\":\"" + ISSUE_OWNER + "\","
+ "\"contract_address\":\"" + USDT + "\","
+ "\"function_selector\":\"isBlackListed(address)\","
+ "\"parameter\":\"00\","
+ "\"visible\":true}";
MockHttpServletResponse response = newResponse();
servlet.doPost(postRequest(body), response);
assertEquals(200, response.getStatus());
String content = response.getContentAsString();
assertTrue(content.contains(Util.INVALID_ADDRESS_BASE58CHECK));
assertFalse(content.contains("NullPointerException"));
assertFalse(content.contains("java.lang"));
verify(wallet, never()).estimateEnergy(any(), any(), any(), any(), any());
}

@Test
public void testOddLengthHexParameterReturnsStableError() throws Exception {
String owner = ByteArray.toHexString(new ECKey().getAddress());
String contract = ByteArray.toHexString(new ECKey().getAddress());
String body = "{\"owner_address\":\"" + owner + "\","
+ "\"contract_address\":\"" + contract + "\","
+ "\"function_selector\":\"isBlackListed(address)\","
+ "\"parameter\":\"" + ODD_HEX + "\"}";
MockHttpServletResponse response = newResponse();
servlet.doPost(postRequest(body), response);
assertEquals(200, response.getStatus());
String content = response.getContentAsString();
assertTrue(content.contains(Util.INVALID_HEX_LENGTH));
assertFalse(content.contains("NullPointerException"));
verify(wallet, never()).estimateEnergy(any(), any(), any(), any(), any());
}

@Test
public void testInternalErrorDoesNotLeakExceptionDetails() throws Exception {
String owner = ByteArray.toHexString(new ECKey().getAddress());
String contract = ByteArray.toHexString(new ECKey().getAddress());
String body = "{\"owner_address\":\"" + owner + "\","
+ "\"contract_address\":\"" + contract + "\","
+ "\"data\":\"00\"}";
when(wallet.createTransactionCapsule(any(), any()))
.thenThrow(new NullPointerException("secret internals"));
MockHttpServletResponse response = newResponse();
servlet.doPost(postRequest(body), response);
assertEquals(200, response.getStatus());
String content = response.getContentAsString();
assertTrue(content.contains(Util.INTERNAL_ERROR_MSG));
assertFalse(content.contains("NullPointerException"));
assertFalse(content.contains("secret internals"));
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package org.tron.core.services.http;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

Expand All @@ -13,6 +16,13 @@

public class TriggerConstantContractServletTest extends BaseHttpTest {

private static final String ISSUE_OWNER = "TKgD8Qnx9Zw3RNjdiU2i5y2Swa2y4QvG6v";
private static final String USDT = "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t";
private static final String ODD_HEX =
"0000000000000000000000418a8e8b8c8d8e8f9a9b9c9d9e9f0a1b2c3d4e5f6";
private static final String EVEN_HEX =
"0000000000000000000000418a8e8b8c8d8e8f9a9b9c9d9e9f0a1b2c3d4e5f60";

private TriggerConstantContractServlet servlet;

@Override
Expand Down Expand Up @@ -46,4 +56,85 @@ public void testManyFlatFieldsDoesNotOverflowStack() throws Exception {
assertEquals(200, response.getStatus());
verify(wallet).triggerConstantContract(any(), any(), any(), any());
}

@Test
public void testInvalidBase58CheckOwnerReturnsStableError() throws Exception {
String body = "{\"owner_address\":\"" + ISSUE_OWNER + "\","
+ "\"contract_address\":\"" + USDT + "\","
+ "\"function_selector\":\"isBlackListed(address)\","
+ "\"parameter\":\"" + EVEN_HEX + "\","
+ "\"visible\":true}";

MockHttpServletResponse response = newResponse();
servlet.doPost(postRequest(body), response);

assertEquals(200, response.getStatus());
String content = response.getContentAsString();
assertTrue(content.contains(Util.INVALID_ADDRESS_BASE58CHECK));
assertFalse(content.contains("NullPointerException"));
assertFalse(content.contains("java.lang"));
verify(wallet, never()).triggerConstantContract(any(), any(), any(), any());
}

@Test
public void testOddLengthHexParameterReturnsStableError() throws Exception {
String owner = ByteArray.toHexString(new ECKey().getAddress());
String contract = ByteArray.toHexString(new ECKey().getAddress());
String body = "{\"owner_address\":\"" + owner + "\","
+ "\"contract_address\":\"" + contract + "\","
+ "\"function_selector\":\"isBlackListed(address)\","
+ "\"parameter\":\"" + ODD_HEX + "\"}";

MockHttpServletResponse response = newResponse();
servlet.doPost(postRequest(body), response);

assertEquals(200, response.getStatus());
String content = response.getContentAsString();
assertTrue(content.contains(Util.INVALID_HEX_LENGTH));
assertFalse(content.contains("NullPointerException"));
assertFalse(content.contains("java.lang"));
verify(wallet, never()).triggerConstantContract(any(), any(), any(), any());
}

@Test
public void testIssuePayloadReturnsParameterErrorNotNpe() throws Exception {
String body = "{\"owner_address\":\"" + ISSUE_OWNER + "\","
+ "\"contract_address\":\"" + USDT + "\","
+ "\"function_selector\":\"isBlackListed(address)\","
+ "\"parameter\":\"" + ODD_HEX + "\","
+ "\"visible\":true}";

MockHttpServletResponse response = newResponse();
servlet.doPost(postRequest(body), response);

assertEquals(200, response.getStatus());
String content = response.getContentAsString();
assertTrue(content.contains(Util.INVALID_ADDRESS_BASE58CHECK)
|| content.contains(Util.INVALID_HEX_LENGTH));
assertFalse(content.contains("NullPointerException"));
assertFalse(content.contains("java.lang"));
verify(wallet, never()).createTransactionCapsule(any(), any());
}

@Test
public void testInternalErrorDoesNotLeakExceptionDetails() throws Exception {
String owner = ByteArray.toHexString(new ECKey().getAddress());
String contract = ByteArray.toHexString(new ECKey().getAddress());
String body = "{\"owner_address\":\"" + owner + "\","
+ "\"contract_address\":\"" + contract + "\","
+ "\"data\":\"00\"}";

when(wallet.createTransactionCapsule(any(), any()))
.thenThrow(new NullPointerException("secret internals"));

MockHttpServletResponse response = newResponse();
servlet.doPost(postRequest(body), response);

assertEquals(200, response.getStatus());
String content = response.getContentAsString();
assertTrue(content.contains(Util.INTERNAL_ERROR_MSG));
assertFalse(content.contains("NullPointerException"));
assertFalse(content.contains("secret internals"));
assertFalse(content.contains("java.lang"));
}
}
Loading
Loading