From 72585337467651570d3010171d3dcc7fb53c6734 Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Tue, 11 Jul 2023 11:42:32 +1000 Subject: [PATCH] Encode/Decode 1559 transactions for public and private transactions --- .../pegasys/web3signer/dsl/Transactions.java | 13 ++++ .../signing/ValueTransferAcceptanceTest.java | 36 +++++++++++ .../SlashingProtectionAcceptanceTest.java | 1 + ...otectionDatabaseVersionAcceptanceTest.java | 1 + .../src/test/resources/besu/genesis.json | 1 + .../transaction/EeaPrivateTransaction.java | 3 +- .../transaction/EthTransaction.java | 9 ++- .../transaction/PrivateTransaction.java | 6 ++ .../transaction/Transaction.java | 2 + .../signing/TransactionSerializer.java | 60 +++++++++++++----- .../EeaPrivateTransactionTest.java | 61 +++++++++++++++++++ .../transaction/EthTransactionTest.java | 48 +++++++++++++++ 12 files changed, 221 insertions(+), 20 deletions(-) diff --git a/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/Transactions.java b/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/Transactions.java index 26e13546e..16dbd4197 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/Transactions.java +++ b/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/Transactions.java @@ -19,10 +19,14 @@ import tech.pegasys.web3signer.core.service.jsonrpc.response.JsonRpcErrorResponse; import tech.pegasys.web3signer.dsl.signer.SignerResponse; +import java.io.IOException; +import java.util.Optional; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.awaitility.core.ConditionTimeoutException; import org.web3j.protocol.core.methods.request.Transaction; +import org.web3j.protocol.core.methods.response.TransactionReceipt; import org.web3j.protocol.exceptions.ClientConnectionException; public class Transactions { @@ -56,4 +60,13 @@ public void awaitBlockContaining(final String hash) { throw new RuntimeException("No receipt found for hash: " + hash); } } + + public Optional getTransactionReceipt(final String hash) { + try { + return eth.getTransactionReceipt(hash); + } catch (IOException e) { + LOG.error("IOException with message: " + e.getMessage()); + throw new RuntimeException("No tx receipt found for hash: " + hash); + } + } } diff --git a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/eth1rpc/signing/ValueTransferAcceptanceTest.java b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/eth1rpc/signing/ValueTransferAcceptanceTest.java index be52efd3c..e7be6b278 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/eth1rpc/signing/ValueTransferAcceptanceTest.java +++ b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/eth1rpc/signing/ValueTransferAcceptanceTest.java @@ -15,6 +15,7 @@ import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST; import static io.netty.handler.codec.http.HttpResponseStatus.OK; import static org.assertj.core.api.Assertions.assertThat; +import static org.web3j.crypto.transaction.type.TransactionType.EIP1559; import static tech.pegasys.web3signer.core.service.jsonrpc.response.JsonRpcError.SIGNING_FROM_IS_NOT_AN_UNLOCKED_ACCOUNT; import static tech.pegasys.web3signer.core.service.jsonrpc.response.JsonRpcError.TRANSACTION_UPFRONT_COST_EXCEEDS_BALANCE; @@ -40,6 +41,8 @@ public class ValueTransferAcceptanceTest extends Eth1RpcAcceptanceTestBase { private static final String RECIPIENT = "0x1b00ba00ca00bb00aa00bc00be00ac00ca00da00"; private static final long FIFTY_TRANSACTIONS = 50; + private static final String FRONTIER = "0x0"; + private static final String EIP1559 = "0x2"; @BeforeEach public void setup() { @@ -74,6 +77,39 @@ public void valueTransfer() { final BigInteger expectedEndBalance = startBalance.add(transferAmountWei); final BigInteger actualEndBalance = besu.accounts().balance(RECIPIENT); assertThat(actualEndBalance).isEqualTo(expectedEndBalance); + + // assert tx is FRONTIER type + final var receipt = besu.transactions().getTransactionReceipt(hash).orElseThrow(); + assertThat(receipt.getType()).isEqualTo(FRONTIER); + } + + @Test + public void valueTransferEip1559() { + final BigInteger transferAmountWei = Convert.toWei("1.75", Unit.ETHER).toBigIntegerExact(); + final BigInteger startBalance = besu.accounts().balance(RECIPIENT); + final Transaction eip1559Transaction = + new Transaction( + richBenefactor().address(), + null, + null, + INTRINSIC_GAS, + RECIPIENT, + transferAmountWei, + null, + 2018L, + GAS_PRICE, + GAS_PRICE); + + final String hash = signer.transactions().submit(eip1559Transaction); + besu.transactions().awaitBlockContaining(hash); + + final BigInteger expectedEndBalance = startBalance.add(transferAmountWei); + final BigInteger actualEndBalance = besu.accounts().balance(RECIPIENT); + assertThat(actualEndBalance).isEqualTo(expectedEndBalance); + + // assert tx is EIP1559 type + final var receipt = besu.transactions().getTransactionReceipt(hash).orElseThrow(); + assertThat(receipt.getType()).isEqualTo(EIP1559); } @Test diff --git a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/slashing/SlashingProtectionAcceptanceTest.java b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/slashing/SlashingProtectionAcceptanceTest.java index 3fc2756c8..fa67dcd85 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/slashing/SlashingProtectionAcceptanceTest.java +++ b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/slashing/SlashingProtectionAcceptanceTest.java @@ -31,6 +31,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import io.restassured.response.Response; import org.apache.tuweni.bytes.Bytes32; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; diff --git a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/slashing/SlashingProtectionDatabaseVersionAcceptanceTest.java b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/slashing/SlashingProtectionDatabaseVersionAcceptanceTest.java index 55eeccb05..0575ff226 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/slashing/SlashingProtectionDatabaseVersionAcceptanceTest.java +++ b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/slashing/SlashingProtectionDatabaseVersionAcceptanceTest.java @@ -22,6 +22,7 @@ import java.nio.file.Path; import org.jdbi.v3.core.Jdbi; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; diff --git a/acceptance-tests/src/test/resources/besu/genesis.json b/acceptance-tests/src/test/resources/besu/genesis.json index 998ec47ba..df5eec9a8 100644 --- a/acceptance-tests/src/test/resources/besu/genesis.json +++ b/acceptance-tests/src/test/resources/besu/genesis.json @@ -9,6 +9,7 @@ "byzantiumBlock": 0, "constantinopleBlock": 0, "constantinopleFixBlock": 0, + "londonBlock": 0, "contractSizeLimit": 2147483647, "ethash": { "fixeddifficulty": 100 diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/sendtransaction/transaction/EeaPrivateTransaction.java b/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/sendtransaction/transaction/EeaPrivateTransaction.java index 5599dac04..1abec01af 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/sendtransaction/transaction/EeaPrivateTransaction.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/sendtransaction/transaction/EeaPrivateTransaction.java @@ -67,8 +67,7 @@ public String toString() { @Override protected RawPrivateTransaction createTransaction() { - if (transactionJsonParameters.maxPriorityFeePerGas().isPresent() - && transactionJsonParameters.maxFeePerGas().isPresent()) { + if (isEip1559()) { return RawPrivateTransaction.createTransaction( chainId, nonce, diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/sendtransaction/transaction/EthTransaction.java b/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/sendtransaction/transaction/EthTransaction.java index 1db38a772..a64d9ba44 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/sendtransaction/transaction/EthTransaction.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/sendtransaction/transaction/EthTransaction.java @@ -92,6 +92,12 @@ public JsonRpcRequestId getId() { return id; } + @Override + public boolean isEip1559() { + return transactionJsonParameters.maxPriorityFeePerGas().isPresent() + && transactionJsonParameters.maxFeePerGas().isPresent(); + } + @Override public String toString() { return MoreObjects.toStringHelper(this) @@ -104,8 +110,7 @@ public String toString() { } protected RawTransaction createTransaction() { - if (transactionJsonParameters.maxPriorityFeePerGas().isPresent() - && transactionJsonParameters.maxFeePerGas().isPresent()) { + if (isEip1559()) { return RawTransaction.createTransaction( chainId, nonce, diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/sendtransaction/transaction/PrivateTransaction.java b/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/sendtransaction/transaction/PrivateTransaction.java index c7b67ec3a..aefc8abc0 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/sendtransaction/transaction/PrivateTransaction.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/sendtransaction/transaction/PrivateTransaction.java @@ -93,6 +93,12 @@ public JsonRpcRequestId getId() { return id; } + @Override + public boolean isEip1559() { + return transactionJsonParameters.maxPriorityFeePerGas().isPresent() + && transactionJsonParameters.maxFeePerGas().isPresent(); + } + @Override public String toString() { return MoreObjects.toStringHelper(this) diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/sendtransaction/transaction/Transaction.java b/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/sendtransaction/transaction/Transaction.java index 1b5e0e96e..d3f419660 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/sendtransaction/transaction/Transaction.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/sendtransaction/transaction/Transaction.java @@ -58,4 +58,6 @@ static JsonRpcRequest jsonRpcRequest( } JsonRpcRequestId getId(); + + boolean isEip1559(); } diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/signing/TransactionSerializer.java b/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/signing/TransactionSerializer.java index ee65d4893..05f38452f 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/signing/TransactionSerializer.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/signing/TransactionSerializer.java @@ -22,6 +22,7 @@ import tech.pegasys.web3signer.signing.SecpArtifactSignature; import tech.pegasys.web3signer.signing.secp256k1.Signature; +import java.nio.ByteBuffer; import java.util.Optional; import org.apache.logging.log4j.LogManager; @@ -29,6 +30,7 @@ import org.apache.tuweni.bytes.Bytes; import org.web3j.crypto.Sign.SignatureData; import org.web3j.crypto.TransactionEncoder; +import org.web3j.crypto.transaction.type.TransactionType; import org.web3j.utils.Numeric; public class TransactionSerializer { @@ -43,14 +45,44 @@ public TransactionSerializer(final ArtifactSignerProvider signer, final long cha } public String serialize(final Transaction transaction) { - final SignatureData signatureData = - new SignatureData(longToBytes(chainId), new byte[] {}, new byte[] {}); - final byte[] bytesToSign = transaction.rlpEncode(signatureData); - Optional publicKey = signerPublicKeyFromAddress(signer, transaction.sender()); + SignatureData signatureData = null; + + if (!transaction.isEip1559()) { + signatureData = new SignatureData(longToBytes(chainId), new byte[] {}, new byte[] {}); + } + + byte[] bytesToSign = transaction.rlpEncode(signatureData); + + if (transaction.isEip1559()) { + bytesToSign = prependEip1559TransactionType(bytesToSign); + } + + final Signature signature = sign(transaction.sender(), bytesToSign); + + SignatureData web3jSignature = + new SignatureData( + signature.getV().toByteArray(), + signature.getR().toByteArray(), + signature.getS().toByteArray()); + + if (!transaction.isEip1559()) { + web3jSignature = TransactionEncoder.createEip155SignatureData(web3jSignature, chainId); + } + + byte[] serializedBytes = transaction.rlpEncode(web3jSignature); + if (transaction.isEip1559()) { + serializedBytes = prependEip1559TransactionType(serializedBytes); + } + + return Numeric.toHexString(serializedBytes); + } + + private Signature sign(final String sender, final byte[] bytesToSign) { + Optional publicKey = signerPublicKeyFromAddress(signer, sender); if (publicKey.isEmpty()) { - LOG.debug("From address ({}) does not match any available account", transaction.sender()); + LOG.debug("From address ({}) does not match any available account", sender); throw new JsonRpcException(SIGNING_FROM_IS_NOT_AN_UNLOCKED_ACCOUNT); } @@ -63,17 +95,13 @@ public String serialize(final Transaction transaction) { .get(); final Signature signature = artifactSignature.getSignatureData(); + return signature; + } - final SignatureData web3jSignature = - new SignatureData( - signature.getV().toByteArray(), - signature.getR().toByteArray(), - signature.getS().toByteArray()); - - final SignatureData eip155Signature = - TransactionEncoder.createEip155SignatureData(web3jSignature, chainId); - - final byte[] serializedBytes = transaction.rlpEncode(eip155Signature); - return Numeric.toHexString(serializedBytes); + private static byte[] prependEip1559TransactionType(byte[] bytesToSign) { + return ByteBuffer.allocate(bytesToSign.length + 1) + .put(TransactionType.EIP1559.getRlpType()) + .put(bytesToSign) + .array(); } } diff --git a/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/sendtransaction/transaction/EeaPrivateTransactionTest.java b/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/sendtransaction/transaction/EeaPrivateTransactionTest.java index 342078b42..5edf3018c 100644 --- a/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/sendtransaction/transaction/EeaPrivateTransactionTest.java +++ b/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/sendtransaction/transaction/EeaPrivateTransactionTest.java @@ -24,12 +24,17 @@ import tech.pegasys.web3signer.core.service.jsonrpc.handlers.sendtransaction.transaction.PrivateTransaction; import java.math.BigInteger; +import java.nio.ByteBuffer; import java.util.List; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.web3j.crypto.Sign.SignatureData; +import org.web3j.crypto.transaction.type.Transaction1559; +import org.web3j.crypto.transaction.type.TransactionType; import org.web3j.protocol.eea.crypto.PrivateTransactionDecoder; +import org.web3j.protocol.eea.crypto.RawPrivateTransaction; import org.web3j.protocol.eea.crypto.SignedRawPrivateTransaction; import org.web3j.utils.Base64String; import org.web3j.utils.Numeric; @@ -92,6 +97,62 @@ public void rlpEncodesTransaction() { assertThat(trimLeadingZeroes(decodedSignatureData.getS())).isEqualTo(new byte[] {3}); } + @Disabled("TODO SLD") + @Test + public void rlpEncodesEip1559Transaction() { + final EeaSendTransactionJsonParameters params = + new EeaSendTransactionJsonParameters( + "0x7577919ae5df4941180eac211965f275cdce314d", + "ZlapEsl9qDLPy/e88+/6yvCUEVIvH83y0N4A6wHuKXI=", + "restricted"); + params.receiver("0xd46e8dd67c5d32be8058bb8eb970870f07244567"); + params.gas("0x76c0"); + params.maxPriorityFeePerGas("0x9184e72a000"); + params.maxFeePerGas("0x9184e72a001"); + params.value("0x0"); + params.nonce("0x1"); + params.data( + "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"); + params.privateFor(new String[] {"GV8m0VZAccYGAAYMBuYQtKEj0XtpXeaw2APcoBmtA2w="}); + + privateTransaction = + EeaPrivateTransaction.from(1337L, params, () -> BigInteger.ZERO, new JsonRpcRequestId(1)); + + final SignatureData signatureData = null; + final byte[] rlpEncodedBytes = privateTransaction.rlpEncode(signatureData); + final String rlpString = Numeric.toHexString(prependEip1559TransactionType(rlpEncodedBytes)); + + final RawPrivateTransaction decodedTransaction = PrivateTransactionDecoder.decode(rlpString); + final Transaction1559 decoded1559Transaction = + (Transaction1559) decodedTransaction.getTransaction(); + assertThat(decoded1559Transaction.getTo()) + .isEqualTo("0xd46e8dd67c5d32be8058bb8eb970870f07244567"); + assertThat(decoded1559Transaction.getGasLimit()).isEqualTo(decodeQuantity("0x76c0")); + assertThat(decoded1559Transaction.getMaxPriorityFeePerGas()) + .isEqualTo(decodeQuantity("0x9184e72a000")); + assertThat(decoded1559Transaction.getMaxFeePerGas()).isEqualTo(decodeQuantity("0x9184e72a001")); + assertThat(decoded1559Transaction.getNonce()).isEqualTo(decodeQuantity("0x1")); + assertThat(decoded1559Transaction.getValue()).isEqualTo(decodeQuantity("0x0")); + assertThat(decoded1559Transaction.getData()) + .isEqualTo( + "d46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"); + assertThat(decodedTransaction.getRestriction()).isEqualTo(Restriction.RESTRICTED); + + final Base64String expectedDecodedPrivateFrom = params.privateFrom(); + final Base64String expectedDecodedPrivateFor = params.privateFor().get().get(0); + + assertThat(decodedTransaction.getPrivateFrom()).isEqualTo(expectedDecodedPrivateFrom); + assertThat(decodedTransaction.getPrivateFor().get().get(0)) + .isEqualTo(expectedDecodedPrivateFor); + } + + private static byte[] prependEip1559TransactionType(byte[] bytesToSign) { + return ByteBuffer.allocate(bytesToSign.length + 1) + .put(TransactionType.EIP1559.getRlpType()) + .put(bytesToSign) + .array(); + } + @Test @SuppressWarnings("unchecked") public void createsJsonEeaRequest() { diff --git a/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/sendtransaction/transaction/EthTransactionTest.java b/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/sendtransaction/transaction/EthTransactionTest.java index 0cb675be9..b37d61814 100644 --- a/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/sendtransaction/transaction/EthTransactionTest.java +++ b/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/sendtransaction/transaction/EthTransactionTest.java @@ -22,6 +22,7 @@ import tech.pegasys.web3signer.core.service.jsonrpc.handlers.sendtransaction.transaction.EthTransaction; import java.math.BigInteger; +import java.nio.ByteBuffer; import java.util.List; import org.junit.jupiter.api.BeforeEach; @@ -29,6 +30,8 @@ import org.web3j.crypto.Sign.SignatureData; import org.web3j.crypto.SignedRawTransaction; import org.web3j.crypto.TransactionDecoder; +import org.web3j.crypto.transaction.type.Transaction1559; +import org.web3j.crypto.transaction.type.TransactionType; import org.web3j.utils.Numeric; public class EthTransactionTest { @@ -78,6 +81,51 @@ public void rlpEncodesTransaction() { assertThat(trimLeadingZeroes(decodedSignatureData.getS())).isEqualTo(new byte[] {3}); } + @Test + public void rlpEncodesEip1559Transaction() { + final EthSendTransactionJsonParameters params = + new EthSendTransactionJsonParameters("0x7577919ae5df4941180eac211965f275cdce314d"); + params.receiver("0xd46e8dd67c5d32be8058bb8eb970870f07244567"); + params.gas("0x76c0"); + params.maxPriorityFeePerGas("0x9184e72a000"); + params.maxFeePerGas("0x9184e72a001"); + params.nonce("0xe04d296d2460cfb8472af2c5fd05b5a214109c25688d3704aed5484f9a7792f2"); + params.value("0x0"); + params.data( + "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"); + + ethTransaction = + new EthTransaction(1337L, params, () -> BigInteger.ZERO, new JsonRpcRequestId(1)); + + final SignatureData signatureData = null; + final byte[] rlpEncodedBytes = ethTransaction.rlpEncode(signatureData); + final String rlpString = Numeric.toHexString(prependEip1559TransactionType(rlpEncodedBytes)); + + final Transaction1559 decodedTransaction = + (Transaction1559) TransactionDecoder.decode(rlpString).getTransaction(); + assertThat(decodedTransaction.getTo()).isEqualTo("0xd46e8dd67c5d32be8058bb8eb970870f07244567"); + assertThat(decodedTransaction.getGasLimit()).isEqualTo(Numeric.decodeQuantity("0x76c0")); + assertThat(decodedTransaction.getMaxPriorityFeePerGas()) + .isEqualTo(Numeric.decodeQuantity("0x9184e72a000")); + assertThat(decodedTransaction.getMaxFeePerGas()) + .isEqualTo(Numeric.decodeQuantity("0x9184e72a001")); + assertThat(decodedTransaction.getNonce()) + .isEqualTo( + Numeric.decodeQuantity( + "0xe04d296d2460cfb8472af2c5fd05b5a214109c25688d3704aed5484f9a7792f2")); + assertThat(decodedTransaction.getValue()).isEqualTo(Numeric.decodeQuantity("0x0")); + assertThat(decodedTransaction.getData()) + .isEqualTo( + "d46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"); + } + + private static byte[] prependEip1559TransactionType(byte[] bytesToSign) { + return ByteBuffer.allocate(bytesToSign.length + 1) + .put(TransactionType.EIP1559.getRlpType()) + .put(bytesToSign) + .array(); + } + @Test @SuppressWarnings("unchecked") public void createsJsonRequest() {