From a8aa23eec1ecb49c337e72438b3aa9c119e6a627 Mon Sep 17 00:00:00 2001 From: orogvany Date: Thu, 4 Jul 2019 17:50:46 -0700 Subject: [PATCH 1/7] Core: Update libs to latest version Pre-release, seems good to update things to latest version. --- pom.xml | 28 +++++++++---------- .../java/org/semux/api/http/HttpHandler.java | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/pom.xml b/pom.xml index 5531819cc..43ab1e8b7 100644 --- a/pom.xml +++ b/pom.xml @@ -589,13 +589,13 @@ org.webjars swagger-ui - 3.19.4 + 3.22.2 org.glassfish.jersey.core jersey-common - 2.28 + 2.29 jakarta.ws.rs @@ -624,29 +624,29 @@ org.slf4j slf4j-api - 1.7.25 + 1.7.26 org.apache.logging.log4j log4j-api - 2.11.2 + 2.12.0 org.apache.logging.log4j log4j-core - 2.11.2 + 2.12.0 org.apache.logging.log4j log4j-slf4j-impl - 2.11.2 + 2.12.0 org.bouncycastle bcprov-jdk15on - 1.61 + 1.62 @@ -660,7 +660,7 @@ io.netty netty-all - 4.1.36.Final + 4.1.37.Final @@ -674,7 +674,7 @@ com.github.oshi oshi-core - 3.13.2 + 3.13.3 @@ -739,7 +739,7 @@ com.fasterxml.jackson.core jackson-annotations - 2.9.0 + 2.9.9 @@ -853,25 +853,25 @@ org.glassfish.jersey.core jersey-client - 2.28 + 2.29 test org.glassfish.jersey.ext jersey-proxy-client - 2.28 + 2.29 test org.glassfish.jersey.inject jersey-hk2 - 2.28 + 2.29 test org.glassfish.jersey.media jersey-media-json-jackson - 2.28 + 2.29 test diff --git a/src/main/java/org/semux/api/http/HttpHandler.java b/src/main/java/org/semux/api/http/HttpHandler.java index 0b595da40..0a905d56f 100644 --- a/src/main/java/org/semux/api/http/HttpHandler.java +++ b/src/main/java/org/semux/api/http/HttpHandler.java @@ -183,7 +183,7 @@ protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) { } else if (STATIC_FILE_PATTERN.matcher(path).matches()) { if (path.startsWith("/swagger-ui/")) { lastContentFuture = writeStaticFile(ctx, - "/META-INF/resources/webjars/swagger-ui/3.19.4" + path.substring(11)); + "/META-INF/resources/webjars/swagger-ui/3.22.2" + path.substring(11)); } else { lastContentFuture = writeStaticFile(ctx, "/org/semux/api" + path); } From f0d9b23181308c3d4bf0aa74662d16b9b657c90b Mon Sep 17 00:00:00 2001 From: orogvany Date: Thu, 4 Jul 2019 18:38:43 -0700 Subject: [PATCH 2/7] API: Add call-local --- .../java/org/semux/api/v2/SemuxApiImpl.java | 59 ++++++++++--------- .../org/semux/api/swagger/v2.3.0.json | 50 ++++++++++++++++ 2 files changed, 82 insertions(+), 27 deletions(-) diff --git a/src/main/java/org/semux/api/v2/SemuxApiImpl.java b/src/main/java/org/semux/api/v2/SemuxApiImpl.java index af14b7961..1bb3077fb 100644 --- a/src/main/java/org/semux/api/v2/SemuxApiImpl.java +++ b/src/main/java/org/semux/api/v2/SemuxApiImpl.java @@ -20,7 +20,6 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; -import javax.validation.constraints.NotNull; import javax.ws.rs.core.Response; import org.apache.commons.validator.routines.DomainValidator; @@ -560,8 +559,7 @@ public Response getTransactionLimits(String type) { } @Override - public Response getTransactionResult( - @NotNull @javax.validation.constraints.Pattern(regexp = "^(0x)?[0-9a-fA-F]{64}$") String hash) { + public Response getTransactionResult(String hash) { GetTransactionResultResponse resp = new GetTransactionResultResponse(); if (!isSet(hash)) { @@ -774,36 +772,43 @@ public Response create(String from, String data, String gasPrice, String gas, St public Response call(String from, String to, String value, String gasPrice, String gas, String nonce, String data, Boolean local) { if (Boolean.TRUE.equals(local)) { - Transaction tx = getTransaction(TransactionType.CALL, from, to, value, "0", nonce, data, - gasPrice, gas); - - SemuxTransaction transaction = new SemuxTransaction(tx); - SemuxBlock block = new SemuxBlock(kernel.getBlockchain().getLatestBlock().getHeader(), - kernel.getConfig().spec().maxBlockGasLimit()); - Repository repository = new SemuxRepository(kernel.getBlockchain().getAccountState(), - kernel.getBlockchain().getDelegateState()); - ProgramInvokeFactory invokeFactory = new ProgramInvokeFactoryImpl(); - BlockStore blockStore = new SemuxBlockStore(kernel.getBlockchain()); - long gasUsedInBlock = 0l; - - org.ethereum.vm.client.TransactionExecutor executor = new org.ethereum.vm.client.TransactionExecutor( - transaction, block, repository, blockStore, - kernel.getConfig().spec().vmSpec(), invokeFactory, gasUsedInBlock, true); - TransactionReceipt receipt = executor.run(); - - DoTransactionResponse resp = new DoTransactionResponse(); - resp.setResult(Hex.encode0x(receipt.getReturnData())); - if (!receipt.isSuccess()) { - return badRequest("Error calling method"); - } else { - return success(resp); - } + return callLocal(to, data); } else { return doTransaction(TransactionType.CALL, from, to, value, "0", nonce, data, gasPrice, gas); } } + @Override + public Response callLocal(String to, String data) { + TransactionBuilder transactionBuilder = new TransactionBuilder(kernel) + .withType("CALL") + .withTo(to) + .withData(data); + + SemuxTransaction transaction = new SemuxTransaction(transactionBuilder.buildUnsigned()); + SemuxBlock block = new SemuxBlock(kernel.getBlockchain().getLatestBlock().getHeader(), + kernel.getConfig().spec().maxBlockGasLimit()); + Repository repository = new SemuxRepository(kernel.getBlockchain().getAccountState(), + kernel.getBlockchain().getDelegateState()); + ProgramInvokeFactory invokeFactory = new ProgramInvokeFactoryImpl(); + BlockStore blockStore = new SemuxBlockStore(kernel.getBlockchain()); + long gasUsedInBlock = 0l; + + org.ethereum.vm.client.TransactionExecutor executor = new org.ethereum.vm.client.TransactionExecutor( + transaction, block, repository, blockStore, + kernel.getConfig().spec().vmSpec(), invokeFactory, gasUsedInBlock, true); + TransactionReceipt receipt = executor.run(); + + DoTransactionResponse resp = new DoTransactionResponse(); + resp.setResult(Hex.encode0x(receipt.getReturnData())); + if (!receipt.isSuccess()) { + return badRequest("Error calling method"); + } else { + return success(resp); + } + } + @Override public Response unvote(String from, String to, String value, String fee, String nonce) { return doTransaction(TransactionType.UNVOTE, from, to, value, fee, nonce, null); diff --git a/src/main/resources/org/semux/api/swagger/v2.3.0.json b/src/main/resources/org/semux/api/swagger/v2.3.0.json index ab2909a23..0aec4c889 100644 --- a/src/main/resources/org/semux/api/swagger/v2.3.0.json +++ b/src/main/resources/org/semux/api/swagger/v2.3.0.json @@ -2434,6 +2434,56 @@ ] } }, + "/transaction/call-local": { + "post": { + "tags": [ + "semux" + ], + "summary": "Call a local contract.", + "description": "Call a VM local contract.", + "operationId": "call-local", + "produces": [ + "application/json" + ], + "parameters": [ + { + "name": "to", + "in": "query", + "description": "Recipient's address (the contract address)", + "required": true, + "type": "string", + "pattern": "^(0x)?[0-9a-fA-F]{40}$" + }, + { + "name": "data", + "in": "query", + "description": "Transaction data encoded in hexadecimal string", + "required": false, + "type": "string", + "pattern": "^(0x)?[0-9a-fA-F]+$" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "$ref": "#/definitions/DoTransactionResponse" + } + }, + "400": { + "description": "bad request", + "schema": { + "$ref": "#/definitions/ApiHandlerResponse" + } + } + }, + "security": [ + { + "basicAuth": [] + } + ] + } + }, "/transaction/unvote": { "post": { "tags": [ From 6c45e9eb43d203e754cf5d9c2ce9f007d5076dcc Mon Sep 17 00:00:00 2001 From: orogvany Date: Thu, 4 Jul 2019 20:53:15 -0700 Subject: [PATCH 3/7] Trivial: formatting --- src/main/java/org/semux/api/v2/SemuxApiImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/semux/api/v2/SemuxApiImpl.java b/src/main/java/org/semux/api/v2/SemuxApiImpl.java index 1bb3077fb..097e85c7f 100644 --- a/src/main/java/org/semux/api/v2/SemuxApiImpl.java +++ b/src/main/java/org/semux/api/v2/SemuxApiImpl.java @@ -772,7 +772,7 @@ public Response create(String from, String data, String gasPrice, String gas, St public Response call(String from, String to, String value, String gasPrice, String gas, String nonce, String data, Boolean local) { if (Boolean.TRUE.equals(local)) { - return callLocal(to, data); + return callLocal(to, data); } else { return doTransaction(TransactionType.CALL, from, to, value, "0", nonce, data, gasPrice, gas); From da70051a8f5aa82e13c198ac386fe69a5d882a01 Mon Sep 17 00:00:00 2001 From: orogvany Date: Fri, 5 Jul 2019 00:20:46 -0700 Subject: [PATCH 4/7] VM: fix NPEs with calling local --- src/main/java/org/semux/api/v2/SemuxApiImpl.java | 1 + .../java/org/semux/vm/client/SemuxPrecompiledContracts.java | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/src/main/java/org/semux/api/v2/SemuxApiImpl.java b/src/main/java/org/semux/api/v2/SemuxApiImpl.java index 097e85c7f..d600fb34b 100644 --- a/src/main/java/org/semux/api/v2/SemuxApiImpl.java +++ b/src/main/java/org/semux/api/v2/SemuxApiImpl.java @@ -784,6 +784,7 @@ public Response callLocal(String to, String data) { TransactionBuilder transactionBuilder = new TransactionBuilder(kernel) .withType("CALL") .withTo(to) + .withNonce("0") .withData(data); SemuxTransaction transaction = new SemuxTransaction(transactionBuilder.buildUnsigned()); diff --git a/src/main/java/org/semux/vm/client/SemuxPrecompiledContracts.java b/src/main/java/org/semux/vm/client/SemuxPrecompiledContracts.java index ba1d7b2a8..112badb8e 100644 --- a/src/main/java/org/semux/vm/client/SemuxPrecompiledContracts.java +++ b/src/main/java/org/semux/vm/client/SemuxPrecompiledContracts.java @@ -64,6 +64,9 @@ public Pair execute(PrecompiledContractContext context) { AccountState as = semuxTrack.getAccountState(); DelegateState ds = semuxTrack.getDelegateState(); byte[] from = context.getCaller(); + if (from == null) { + return failure; + } byte[] to = Arrays.copyOfRange(context.getData(), 12, 32); Amount value = weiToAmount(new BigInteger(1, Arrays.copyOfRange(context.getData(), 32, 64))); @@ -97,6 +100,9 @@ public Pair execute(PrecompiledContractContext context) { AccountState as = semuxTrack.getAccountState(); DelegateState ds = semuxTrack.getDelegateState(); byte[] from = context.getCaller(); + if (from == null) { + return failure; + } byte[] to = Arrays.copyOfRange(context.getData(), 12, 32); Amount value = weiToAmount(new BigInteger(1, Arrays.copyOfRange(context.getData(), 32, 64))); From 8d08153b3b8cb0cd753d85e7b0de50e5569924bb Mon Sep 17 00:00:00 2001 From: semux Date: Sun, 7 Jul 2019 17:45:04 +0100 Subject: [PATCH 5/7] Tools: bump dependency version --- pom.xml | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/pom.xml b/pom.xml index cef3c1d53..3eeeceb4c 100644 --- a/pom.xml +++ b/pom.xml @@ -572,10 +572,20 @@ com.github.semuxproject evm - de88e39aa01be079c555f268953725076b151d85 + 0147112b476106f1c329684a3e6ab6f72f6c5603 + + io.swagger + swagger-annotations + 1.5.22 + + + org.webjars + swagger-ui + 3.22.2 + javax.ws.rs javax.ws.rs-api @@ -586,16 +596,15 @@ validation-api 2.0.1.Final - - io.swagger - swagger-annotations - 1.5.22 + javax.xml.bind + jaxb-api + 2.3.0 - org.webjars - swagger-ui - 3.22.2 + javax.activation + activation + 1.1.1 @@ -611,21 +620,13 @@ jakarta.annotation jakarta.annotation-api + + com.sun.activation + jakarta.activation + - - - javax.xml.bind - jaxb-api - 2.3.0 - - - javax.activation - activation - 1.1.1 - - org.slf4j From 7c834039569d2d35349404b01467cd9b7263015e Mon Sep 17 00:00:00 2001 From: semux Date: Sun, 7 Jul 2019 22:44:00 +0100 Subject: [PATCH 6/7] API: add descriptions and refine interfaces --- .../semux/api/util/TransactionBuilder.java | 243 ++++----- .../java/org/semux/api/v2/SemuxApiImpl.java | 205 ++++---- .../java/org/semux/api/v2/TypeFactory.java | 11 +- src/main/java/org/semux/core/Transaction.java | 1 - .../org/semux/api/swagger/v2.3.0.json | 460 +++++++++++++----- .../api/util/TransactionBuilderTest.java | 5 +- .../java/org/semux/api/v2/SemuxApiTest.java | 50 +- 7 files changed, 599 insertions(+), 376 deletions(-) diff --git a/src/main/java/org/semux/api/util/TransactionBuilder.java b/src/main/java/org/semux/api/util/TransactionBuilder.java index d8d0afb41..16b055f90 100644 --- a/src/main/java/org/semux/api/util/TransactionBuilder.java +++ b/src/main/java/org/semux/api/util/TransactionBuilder.java @@ -17,7 +17,6 @@ import org.semux.crypto.Hex; import org.semux.crypto.Key; import org.semux.util.Bytes; -import org.semux.util.TimeUtil; /** * This is a builder class for building transactions required by Semux API with @@ -80,77 +79,59 @@ public TransactionBuilder(Kernel kernel) { } public TransactionBuilder withType(TransactionType type) { - if (type == null) { - throw new IllegalArgumentException("Parameter `type` is required"); + if (type != null) { + this.type = type; } - - this.type = type; return this; } public TransactionBuilder withType(String type) { - if (type == null) { - throw new IllegalArgumentException("Parameter `type` is required"); + if (type != null) { + this.type = TransactionType.valueOf(type); + if (this.type == null) { + throw new IllegalArgumentException("Parameter `type` is invalid"); + } } - - this.type = TransactionType.valueOf(type); return this; } public TransactionBuilder withNetwork(String network) { - if (network == null) { - throw new IllegalArgumentException("Parameter `network` is required"); + if (network != null) { + this.network = Network.valueOf(network); + if (this.network == null) { + throw new IllegalArgumentException("Parameter `network` is invalid"); + } } - - this.network = Network.valueOf(network); return this; } public TransactionBuilder withFrom(String from) { - if (from == null) { - throw new IllegalArgumentException("Parameter `from` is required"); - } - - try { - account = kernel.getWallet().getAccount(Hex.decode0x(from)); - } catch (CryptoException e) { - throw new IllegalArgumentException("Parameter `from` is not a valid hexadecimal string"); - } + if (from != null) { + try { + this.account = kernel.getWallet().getAccount(Hex.decode0x(from)); + } catch (CryptoException e) { + throw new IllegalArgumentException("Parameter `from` is not a valid hexadecimal string"); + } - if (account == null) { - throw new IllegalArgumentException( - String.format("The provided address %s doesn't belong to the wallet", from)); + if (account == null) { + throw new IllegalArgumentException( + String.format("The provided address %s doesn't belong to the wallet", from)); + } } - return this; } public TransactionBuilder withTo(String to) { - if (type == TransactionType.DELEGATE) { - if (to != null && !to.isEmpty()) { - throw new IllegalArgumentException("Parameter `to` is not needed for DELEGATE transaction"); - } - return this; // ignore the provided parameter - } - if (type == TransactionType.CREATE) { - if (to != null && !to.isEmpty()) { - throw new IllegalArgumentException("Parameter `to` is not needed for CREATE transaction"); + if (to != null) { + try { + this.to = Hex.decode0x(to); + } catch (CryptoException e) { + throw new IllegalArgumentException("Parameter `to` is not a valid hexadecimal string"); } - return this; // ignore the provided parameter - } - - if (to == null) { - throw new IllegalArgumentException("Parameter `to` is required"); - } - - try { - this.to = Hex.decode0x(to); - } catch (CryptoException e) { - throw new IllegalArgumentException("Parameter `to` is not a valid hexadecimal string"); - } - if (this.to.length != Key.ADDRESS_LEN) { - throw new IllegalArgumentException("Parameter `to` is not a valid address"); + if (this.to.length != Key.ADDRESS_LEN) { + throw new IllegalArgumentException("Parameter `to` is not a valid address"); + } } return this; @@ -169,105 +150,149 @@ public TransactionBuilder withValue(String value) { return this; } - public TransactionBuilder withFee(String fee, boolean optional) { - if (optional && (fee == null || fee.isEmpty())) { - this.fee = kernel.getConfig().spec().minTransactionFee(); - return this; - } - - try { - this.fee = NANO_SEM.of(Long.parseLong(fee)); - } catch (NumberFormatException e) { - throw new IllegalArgumentException("Parameter `fee` is not a valid number"); + public TransactionBuilder withFee(String fee) { + if (fee != null) { + try { + this.fee = NANO_SEM.of(Long.parseLong(fee)); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Parameter `fee` is not a valid number"); + } } return this; } public TransactionBuilder withNonce(String nonce) { - try { - this.nonce = Long.parseLong(nonce); - } catch (NumberFormatException e) { - throw new IllegalArgumentException("Parameter 'nonce' is not a valid number"); + if (nonce != null) { + try { + this.nonce = Long.parseLong(nonce); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Parameter 'nonce' is not a valid number"); + } } return this; } public TransactionBuilder withTimestamp(String timestamp) { - try { - this.timestamp = timestamp != null && !timestamp.isEmpty() ? Long.parseLong(timestamp) : null; - } catch (NumberFormatException e) { - throw new IllegalArgumentException("Parameter 'timestamp' is not a valid number"); + if (timestamp != null) { + try { + this.timestamp = Long.parseLong(timestamp); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Parameter 'timestamp' is not a valid number"); + } } return this; } public TransactionBuilder withData(String data) { - try { - this.data = (data == null) ? Bytes.EMPTY_BYTES : Hex.decode0x(data); - } catch (CryptoException e) { - throw new IllegalArgumentException("Parameter `data` is not a valid hexadecimal string"); + if (data != null) { + try { + this.data = Hex.decode0x(data); + } catch (CryptoException e) { + throw new IllegalArgumentException("Parameter `data` is not a valid hexadecimal string"); + } } + return this; + } + public TransactionBuilder withGas(String gasLimit) { + if (gasLimit != null) { + try { + this.gas = Long.parseLong(gasLimit); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Parameter `gas` is not a valid number"); + } + } return this; } public TransactionBuilder withGasPrice(String gasPrice) { - if (gasPrice == null) { - throw new IllegalArgumentException("Parameter `gasPrice` is required"); + if (gasPrice != null) { + try { + this.gasPrice = NANO_SEM.of(Long.parseLong(gasPrice)); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Parameter `gasPrice` is not a valid number"); + } } + return this; + } + + public Transaction buildUnsigned() { + Network network = (this.network != null) ? this.network : kernel.getConfig().network(); - try { - this.gasPrice = NANO_SEM.of(Long.parseLong(gasPrice)); - } catch (NumberFormatException e) { - throw new IllegalArgumentException("Parameter `gasPrice` is not a valid number"); + TransactionType type = this.type; + if (type == null) { + throw new IllegalArgumentException("Parameter `type` is required"); } - return this; - } + Key account = this.account; + if (account == null) { + account = kernel.getCoinbase(); + } - public TransactionBuilder withGas(String gasLimit) { - if (gasLimit == null) { - throw new IllegalArgumentException("Parameter `gas` is required"); + byte[] to = this.to; + if (to == null) { + if (type == TransactionType.DELEGATE || type == TransactionType.CREATE) { + to = Bytes.EMPTY_ADDRESS; + } else { + throw new IllegalArgumentException("Parameter `to` is required"); + } } - try { - this.gas = Long.parseLong(gasLimit); - } catch (NumberFormatException e) { - throw new IllegalArgumentException("Parameter `gas` is not a valid number"); + Amount value = this.value; + if (value == null) { + if (type == TransactionType.DELEGATE) { + value = kernel.getConfig().spec().minDelegateBurnAmount(); + } else if (type == TransactionType.CREATE || type == TransactionType.CALL) { + value = Amount.ZERO; + } else { + throw new IllegalArgumentException("Parameter `value` is required"); + } } - return this; - } + Amount fee = this.fee; + if (fee == null) { + if (type == TransactionType.CALL || type == TransactionType.CREATE) { + fee = Amount.ZERO; + } else { + fee = kernel.getConfig().spec().minTransactionFee(); + } + } - public Transaction buildUnsigned() { - if (type == TransactionType.DELEGATE || type == TransactionType.CREATE) { - to = Bytes.EMPTY_ADDRESS; + Long nonce = this.nonce; + if (nonce == null) { + nonce = kernel.getPendingManager().getNonce(account.toAddress()); } - if (type == TransactionType.DELEGATE) { - value = kernel.getConfig().spec().minDelegateBurnAmount(); + Long timestamp = this.timestamp; + if (timestamp == null) { + timestamp = System.currentTimeMillis(); } - // mandatory because no default value can be provided - if (type == null) { - throw new IllegalArgumentException("Parameter `type` is required"); + byte[] data = this.data; + if (data == null) { + data = Bytes.EMPTY_BYTES; } - if (to == null) { - throw new IllegalArgumentException("Parameter `to` is required"); + + Long gas = this.gas; + if (gas == null) { + if (type != TransactionType.CALL && type != TransactionType.CREATE) { + gasPrice = Amount.ZERO; + } else { + throw new IllegalArgumentException("Parameter `gas` is required"); + } + } + + Amount gasPrice = this.gasPrice; + if (gasPrice == null) { + if (type != TransactionType.CALL && type != TransactionType.CREATE) { + gasPrice = Amount.ZERO; + } else { + throw new IllegalArgumentException("Parameter `gasPrice` is required"); + } } - return new Transaction( - network != null ? network : kernel.getConfig().network(), - type, - to, - value != null ? value : Amount.ZERO, - fee != null ? fee : Amount.ZERO, - nonce != null ? nonce : kernel.getPendingManager().getNonce(account.toAddress()), - timestamp != null ? timestamp : TimeUtil.currentTimeMillis(), - data != null ? data : Bytes.EMPTY_BYTES, - gas, - gasPrice != null ? gasPrice : Amount.ZERO); + return new Transaction(network, type, to, value, fee, nonce, timestamp, data, gas, gasPrice); } public Transaction buildSigned() { diff --git a/src/main/java/org/semux/api/v2/SemuxApiImpl.java b/src/main/java/org/semux/api/v2/SemuxApiImpl.java index d600fb34b..b5c14b1b5 100644 --- a/src/main/java/org/semux/api/v2/SemuxApiImpl.java +++ b/src/main/java/org/semux/api/v2/SemuxApiImpl.java @@ -33,10 +33,12 @@ import org.semux.api.util.TransactionBuilder; import org.semux.api.v2.model.AddNodeResponse; import org.semux.api.v2.model.ApiHandlerResponse; +import org.semux.api.v2.model.CallResponse; import org.semux.api.v2.model.ComposeRawTransactionResponse; import org.semux.api.v2.model.CreateAccountResponse; import org.semux.api.v2.model.DeleteAccountResponse; import org.semux.api.v2.model.DoTransactionResponse; +import org.semux.api.v2.model.EstimateGasResponse; import org.semux.api.v2.model.GetAccountPendingTransactionsResponse; import org.semux.api.v2.model.GetAccountResponse; import org.semux.api.v2.model.GetAccountTransactionsResponse; @@ -83,11 +85,15 @@ import org.semux.vm.client.SemuxBlockStore; import org.semux.vm.client.SemuxRepository; import org.semux.vm.client.SemuxTransaction; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import net.i2p.crypto.eddsa.EdDSAPublicKey; public final class SemuxApiImpl implements SemuxApi { + private static final Logger logger = LoggerFactory.getLogger(SemuxApiImpl.class); + private static final Charset CHARSET = UTF_8; private final Kernel kernel; @@ -145,8 +151,8 @@ public Response addToWhitelist(String ip) { } @Override - public Response composeRawTransaction(String network, String type, String fee, String nonce, String to, - String value, String timestamp, String data, String gasPrice, String gas) { + public Response composeRawTransaction(String network, String type, String to, String value, String fee, + String nonce, String timestamp, String data, String gas, String gasPrice) { ComposeRawTransactionResponse resp = new ComposeRawTransactionResponse(); @@ -156,17 +162,12 @@ public Response composeRawTransaction(String network, String type, String fee, S .withType(type) .withTo(to) .withValue(value) - .withFee(fee, true) + .withFee(fee) .withNonce(nonce) .withTimestamp(timestamp) - .withData(data); - if (gasPrice != null) { - transactionBuilder.withGasPrice(gasPrice); - } - if (gas != null) { - transactionBuilder.withGas(gas); - } - + .withData(data) + .withGas(gas) + .withGasPrice(gasPrice); Transaction transaction = transactionBuilder.buildUnsigned(); resp.setResult(Hex.encode0x(transaction.getEncoded())); @@ -266,69 +267,19 @@ public Response deleteAccount(String address) { } } - @Override - public Response getAccountTransactions(String address, String from, String to) { - GetAccountTransactionsResponse resp = new GetAccountTransactionsResponse(); + private Response getTransactions(boolean pending, String address, String from, String to) { byte[] addressBytes; int fromInt; int toInt; - if (!isSet(address)) { - return badRequest("Parameter `address` is required"); - } - if (!isSet(from)) { - return badRequest("Parameter `from` is required"); - } - if (!isSet(to)) { - return badRequest("Parameter `to` is required"); - } - try { addressBytes = Hex.decode0x(address); } catch (CryptoException ex) { return badRequest("Parameter `address` is not a valid hexadecimal string"); } - try { - fromInt = Integer.parseInt(from); - } catch (NumberFormatException ex) { - return badRequest("Parameter `from` is not a valid integer"); - } - - try { - toInt = Integer.parseInt(to); - } catch (NumberFormatException ex) { - return badRequest("Parameter `to` is not a valid integer"); - } - - resp.setResult(kernel.getBlockchain().getTransactions(addressBytes, fromInt, toInt).parallelStream() - .map(tx -> TypeFactory.transactionType(tx)) - .collect(Collectors.toList())); - - return success(resp); - } - - @Override - public Response getAccountPendingTransactions(String address, String from, String to) { - GetAccountPendingTransactionsResponse resp = new GetAccountPendingTransactionsResponse(); - byte[] addressBytes; - int fromInt; - int toInt; - - if (!isSet(address)) { - return badRequest("Parameter `address` is required"); - } - if (!isSet(from)) { - return badRequest("Parameter `from` is required"); - } - if (!isSet(to)) { - return badRequest("Parameter `to` is required"); - } - - try { - addressBytes = Hex.decode0x(address); - } catch (CryptoException ex) { - return badRequest("Parameter `address` is not a valid hexadecimal string"); + if (addressBytes.length != 20) { + return badRequest("Parameter `address` length is invalid"); } try { @@ -347,16 +298,35 @@ public Response getAccountPendingTransactions(String address, String from, Strin return badRequest("Parameter `to` must be greater than `from`"); } - resp.setResult(kernel.getPendingManager() - .getPendingTransactions().parallelStream() - .map(pendingTransaction -> pendingTransaction.transaction) - .filter(tx -> Arrays.equals(tx.getFrom(), addressBytes) || Arrays.equals(tx.getTo(), addressBytes)) - .skip(fromInt) - .limit(toInt - fromInt) - .map(TypeFactory::transactionType) - .collect(Collectors.toList())); + if (pending) { + GetAccountPendingTransactionsResponse resp = new GetAccountPendingTransactionsResponse(); + resp.setResult(kernel.getPendingManager() + .getPendingTransactions().parallelStream() + .map(pendingTransaction -> pendingTransaction.transaction) + .filter(tx -> Arrays.equals(tx.getFrom(), addressBytes) || Arrays.equals(tx.getTo(), addressBytes)) + .skip(fromInt) + .limit(toInt - fromInt) + .map(TypeFactory::transactionType) + .collect(Collectors.toList())); + return success(resp); + } else { + GetAccountTransactionsResponse resp = new GetAccountTransactionsResponse(); + resp.setResult(kernel.getBlockchain().getTransactions(addressBytes, fromInt, toInt).parallelStream() + .map(tx -> TypeFactory.transactionType(tx)) + .collect(Collectors.toList())); - return success(resp); + return success(resp); + } + } + + @Override + public Response getAccountTransactions(String address, String from, String to) { + return getTransactions(false, address, from, to); + } + + @Override + public Response getAccountPendingTransactions(String address, String from, String to) { + return getTransactions(true, address, from, to); } @Override @@ -551,10 +521,7 @@ public Response getTransactionLimits(String type) { return success(resp); } catch (NullPointerException | IllegalArgumentException e) { - return badRequest(String.format("Invalid transaction type. (must be one of %s)", - Arrays.stream(TransactionType.values()) - .map(TransactionType::toString) - .collect(Collectors.joining(",")))); + return badRequest(String.format("Invalid transaction type")); } } @@ -763,30 +730,32 @@ public Response transfer(String from, String to, String value, String fee, Strin } @Override - public Response create(String from, String data, String gasPrice, String gas, String nonce, String value) { - return doTransaction(TransactionType.CREATE, from, null, value, "0", nonce, data, gasPrice, - gas); + public Response create(String from, String data, String gas, String gasPrice, String value, String nonce) { + return doTransaction(TransactionType.CREATE, from, null, value, null, nonce, data, gas, gasPrice); } @Override - public Response call(String from, String to, String value, String gasPrice, String gas, - String nonce, String data, Boolean local) { - if (Boolean.TRUE.equals(local)) { - return callLocal(to, data); - } else { - return doTransaction(TransactionType.CALL, from, to, value, "0", nonce, data, gasPrice, - gas); - } + public Response call(String from, String to, String gas, String gasPrice, String value, String nonce, String data) { + return doTransaction(TransactionType.CALL, from, to, value, "0", nonce, data, gas, gasPrice); } - @Override - public Response callLocal(String to, String data) { + private TransactionReceipt executeTransactionLocally(String to, String from, String value, String nonce, + String data, String gas, String gasPrice) { TransactionBuilder transactionBuilder = new TransactionBuilder(kernel) .withType("CALL") .withTo(to) - .withNonce("0") + .withFrom(from) + .withValue(value) + .withNonce(nonce) .withData(data); + if (gas == null) { + transactionBuilder.withGas(Long.toString(kernel.getConfig().spec().maxBlockGasLimit())); + } + if (gasPrice == null) { + transactionBuilder.withGasPrice("1"); + } + SemuxTransaction transaction = new SemuxTransaction(transactionBuilder.buildUnsigned()); SemuxBlock block = new SemuxBlock(kernel.getBlockchain().getLatestBlock().getHeader(), kernel.getConfig().spec().maxBlockGasLimit()); @@ -801,11 +770,31 @@ public Response callLocal(String to, String data) { kernel.getConfig().spec().vmSpec(), invokeFactory, gasUsedInBlock, true); TransactionReceipt receipt = executor.run(); - DoTransactionResponse resp = new DoTransactionResponse(); - resp.setResult(Hex.encode0x(receipt.getReturnData())); - if (!receipt.isSuccess()) { - return badRequest("Error calling method"); + return receipt; + } + + @Override + public Response localCall(String to, String from, String value, String nonce, String data, String gas, + String gasPrice) { + TransactionReceipt receipt = executeTransactionLocally(to, from, value, nonce, data, gas, gasPrice); + CallResponse resp = new CallResponse(); + if (receipt == null || !receipt.isSuccess()) { + return badRequest("Failed to call"); } else { + resp.setResult(Hex.encode0x(receipt.getReturnData())); + return success(resp); + } + } + + @Override + public Response estimateGas(String to, String from, String value, String nonce, String data, String gas, + String gasPrice) { + TransactionReceipt receipt = executeTransactionLocally(to, from, value, nonce, data, gas, gasPrice); + EstimateGasResponse resp = new EstimateGasResponse(); + if (receipt == null || !receipt.isSuccess()) { + return badRequest("Failed to estimate the gas usage"); + } else { + resp.setResult(Long.toString(receipt.getGasUsed())); return success(resp); } } @@ -850,7 +839,7 @@ public Response verifyMessage(String address, String message, String signature) isValidSignature = false; } - resp.setValidSignature(isValidSignature); + resp.setValid(isValidSignature); return success(resp); } @@ -904,6 +893,7 @@ private Response badRequest(String message) { resp.setSuccess(false); resp.setMessage(message); + logger.info("Bad request: {}", message); return Response.status(BAD_REQUEST).entity(resp).build(); } @@ -943,22 +933,17 @@ private Response doTransaction(TransactionType type, String from, String to, Str } private Transaction getTransaction(TransactionType type, String from, String to, String value, String fee, - String nonce, String data, String gasPrice, String gas) { + String nonce, String data, String gas, String gasPrice) { TransactionBuilder transactionBuilder = new TransactionBuilder(kernel) .withType(type) .withFrom(from) .withTo(to) - .withFee(fee, true) .withValue(value) - .withData(data); - - if (type == TransactionType.CREATE || type == TransactionType.CALL) { - transactionBuilder.withGasPrice(gasPrice).withGas(gas); - } - - if (nonce != null) { - transactionBuilder.withNonce(nonce); - } + .withFee(fee) + .withNonce(nonce) + .withData(data) + .withGas(gas) + .withGasPrice(gasPrice); Transaction tx = transactionBuilder.buildSigned(); @@ -977,11 +962,11 @@ private Transaction getTransaction(TransactionType type, String from, String to, * @param data * @return */ - private Response doTransaction(TransactionType type, String from, String to, String value, String fee, String nonce, - String data, String gasPrice, String gas) { + private Response doTransaction(TransactionType type, String from, String to, String value, String fee, + String nonce, String data, String gas, String gasPrice) { DoTransactionResponse resp = new DoTransactionResponse(); try { - Transaction tx = getTransaction(type, from, to, value, fee, nonce, data, gasPrice, gas); + Transaction tx = getTransaction(type, from, to, value, fee, nonce, data, gas, gasPrice); PendingManager.ProcessingResult result = kernel.getPendingManager().addTransactionSync(tx); if (result.error != null) { diff --git a/src/main/java/org/semux/api/v2/TypeFactory.java b/src/main/java/org/semux/api/v2/TypeFactory.java index 8a0f2e011..b8694d55c 100644 --- a/src/main/java/org/semux/api/v2/TypeFactory.java +++ b/src/main/java/org/semux/api/v2/TypeFactory.java @@ -6,6 +6,8 @@ */ package org.semux.api.v2; +import static org.semux.core.TransactionType.CALL; +import static org.semux.core.TransactionType.CREATE; import static org.semux.core.TransactionType.DELEGATE; import java.util.Arrays; @@ -134,7 +136,9 @@ public static TransactionLimitsType transactionLimitsType(Kernel kernel, org.semux.core.TransactionType transactionType) { return new TransactionLimitsType() .maxTransactionDataSize(kernel.getConfig().spec().maxTransactionDataSize(transactionType)) - .minTransactionFee(encodeAmount(kernel.getConfig().spec().minTransactionFee())) + .minTransactionFee(encodeAmount( + transactionType.equals(CREATE) || transactionType.equals(CALL) ? Amount.ZERO + : kernel.getConfig().spec().minTransactionFee())) .minDelegateBurnAmount(encodeAmount( transactionType.equals(DELEGATE) ? kernel.getConfig().spec().minDelegateBurnAmount() : null)); } @@ -156,9 +160,6 @@ public static TransactionType transactionType(Transaction tx) { } public static TransactionResultType transactionResultType(Transaction tx, TransactionResult result, long number) { - // gas price is in nano sem, not wei - Amount fee = tx.isVMTransaction() ? Amount.mul(result.getGasPrice(), result.getGasUsed()) : tx.getFee(); - return new TransactionResultType() .blockNumber(Long.toString(number)) .code(result.getCode().name()) @@ -166,7 +167,7 @@ public static TransactionResultType transactionResultType(Transaction tx, Transa .gas(Long.toString(result.getGas())) .gasUsed(String.valueOf(result.getGasUsed())) .gasPrice(encodeAmount(result.getGasPrice())) - .fee(encodeAmount(fee)) + .fee(encodeAmount(tx.getFee())) .code(result.getCode().name()) .internalTransactions(result.getInternalTransactions().stream() .map(TypeFactory::internalTransactionType).collect(Collectors.toList())) diff --git a/src/main/java/org/semux/core/Transaction.java b/src/main/java/org/semux/core/Transaction.java index 788f1208a..974aec28a 100644 --- a/src/main/java/org/semux/core/Transaction.java +++ b/src/main/java/org/semux/core/Transaction.java @@ -18,7 +18,6 @@ import org.semux.crypto.Hex; import org.semux.crypto.Key; import org.semux.crypto.Key.Signature; -import org.semux.util.Bytes; import org.semux.util.SimpleDecoder; import org.semux.util.SimpleEncoder; diff --git a/src/main/resources/org/semux/api/swagger/v2.3.0.json b/src/main/resources/org/semux/api/swagger/v2.3.0.json index 0aec4c889..e8f1e1084 100644 --- a/src/main/resources/org/semux/api/swagger/v2.3.0.json +++ b/src/main/resources/org/semux/api/swagger/v2.3.0.json @@ -12,9 +12,11 @@ ], "properties": { "success": { + "description": "Whether this operation was processed successfully", "type": "boolean" }, "message": { + "description": "Success/error message", "type": "string" } } @@ -31,29 +33,35 @@ "type": "object", "properties": { "address": { + "description": "The address of this account", "type": "string", "pattern": "^(0x)?[0-9a-fA-F]{40}$" }, "available": { + "description": "The available balance of this account", "type": "string", "format": "int64", "pattern": "^\\d+$" }, "locked": { + "description": "The locked balance of this account", "type": "string", "format": "int64", "pattern": "^\\d+$" }, "nonce": { + "description": "The nonce of this account", "type": "string", "format": "int64", "pattern": "^\\d+$" }, "transactionCount": { + "description": "The number of transactions received/sent", "type": "integer", "format": "int32" }, "pendingTransactionCount": { + "description": "The number of pending transaction from/to this account", "type": "integer", "format": "int32" } @@ -71,49 +79,59 @@ "type": "object", "properties": { "hash": { + "description": "The block hash", "type": "string", "pattern": "^(0x)?[0-9a-fA-F]{64}$" }, "number": { + "description": "The block number", "type": "string", "format": "int64", "pattern": "^\\d+$" }, "view": { + "description": "The view number. # of additional BFT rounds to generated this block", "type": "integer", "format": "int32" }, "coinbase": { + "description": "The block producer's address", "type": "string", "pattern": "^(0x)?[0-9a-fA-F]{40}$" }, "parentHash": { + "description": "The hash of the parent block", "type": "string", "pattern": "^(0x)?[0-9a-fA-F]{64}$" }, "timestamp": { - "description": "Block timestamp in milliseconds specified by the block forger. There can be a time drift up to 30 seconds.", + "description": "Block timestamp in milliseconds specified by the block producer.", "type": "string", "format": "int64", "pattern": "^\\d+$" }, "transactionsRoot": { + "description": "The Merkle root hash of the transactions", "type": "string", "pattern": "^(0x)?[0-9a-fA-F]{64}$" }, "resultsRoot": { + "description": "The Merkle root hash of the results", "type": "string", "pattern": "^(0x)?[0-9a-fA-F]{64}$" }, "stateRoot": { + "description": "The state root hash. Not enabled yet!", "type": "string", "pattern": "^(0x)?[0-9a-fA-F]{64}$" }, "data": { + "description": "The extra data of this block", "type": "string", "pattern": "^(0x)?[0-9a-fA-F]*$" }, "transactions": { + "description": "A list of transaction in the block", "type": "array", "items": { "$ref": "#/definitions/TransactionType" @@ -130,6 +148,7 @@ { "properties": { "result": { + "description": "The address of the newly created account", "type": "string", "pattern": "^(0x)?[0-9a-fA-F]{40}$" } @@ -141,7 +160,7 @@ "type": "object", "properties": { "address": { - "description": "Delegate SEM address", + "description": "Delegate address", "type": "string", "pattern": "^(0x)?[0-9a-fA-F]{40}$" }, @@ -162,7 +181,7 @@ "pattern": "^\\d+$" }, "blocksForged": { - "description": "Total forged blocks including primary rounds & backup rounds", + "description": "The number of blocks produced by this delegate", "type": "string", "format": "int64", "pattern": "^\\d+$" @@ -194,6 +213,7 @@ { "properties": { "result": { + "description": "The transaction hash", "type": "string", "pattern": "^(0x)?[0-9a-fA-F]{64}$" } @@ -218,9 +238,6 @@ }, "DeleteAccountResponse": { "type": "object", - "required": [ - "success" - ], "allOf": [ { "$ref": "#/definitions/ApiHandlerResponse" @@ -285,11 +302,10 @@ "type": "object", "properties": { "delegate": { - "type": "object", "$ref": "#/definitions/DelegateType" }, "votes": { - "description": "Votes from the account", + "description": "Total votes from this account to the delegate", "type": "string", "format": "int64", "pattern": "^\\d+$" @@ -383,6 +399,7 @@ { "properties": { "result": { + "description": "The number of the latest block", "type": "string", "format": "int64", "pattern": "^\\d+$" @@ -541,6 +558,7 @@ { "properties": { "result": { + "description": "Total votes in nanoSEM", "type": "string", "format": "int64", "pattern": "^\\d+$" @@ -577,6 +595,7 @@ "type": "object", "properties": { "network": { + "description": "The connected network", "type": "string", "enum": [ "MAINNET", @@ -585,32 +604,39 @@ ] }, "capabilities": { + "description": "The features supported", "type": "array", "items": { "type": "string" } }, "clientId": { + "description": "The client identifier string", "type": "string" }, "coinbase": { + "description": "The address used for establishing connections to the network", "type": "string", "pattern": "^(0x)?[0-9a-fA-F]{40}$" }, "latestBlockNumber": { + "description": "The number of the last block", "type": "string", "format": "int64", "pattern": "^\\d+$" }, "latestBlockHash": { + "description": "The hash of the last block", "type": "string", "pattern": "^(0x)?[0-9a-fA-F]{64}$" }, "activePeers": { + "description": "The number of actively connected peers", "type": "integer", "format": "int32" }, "pendingTransactions": { + "description": "The number of transactions in pending pool", "type": "integer", "format": "int32" } @@ -628,8 +654,8 @@ { "properties": { "result": { - "type": "array", "description": "A list of account addresses", + "type": "array", "items": { "description": "Account address", "type": "string", @@ -644,34 +670,42 @@ "type": "object", "properties": { "ip": { + "description": "The IP address", "type": "string", "pattern": "^(\\d{1,3}\\.){3}\\d{1,3}$" }, "port": { + "description": "The port number", "type": "integer", "format": "int32" }, "networkVersion": { + "description": "The network version", "type": "integer", "format": "int32" }, "clientId": { + "description": "The client the peer is using", "type": "string" }, "peerId": { + "description": "The id of the peer", "type": "string" }, "latestBlockNumber": { + "description": "The latest block number of the peer", "type": "string", "format": "int64", "pattern": "^\\d+$" }, "latency": { + "description": "Latency between this node and the peer", "type": "string", "format": "int64", "pattern": "^\\d+$" }, "capabilities": { + "description": "The features supported by the peer", "type": "array", "items": { "type": "string" @@ -725,10 +759,12 @@ "type": "object", "properties": { "hash": { + "description": "The transaction hash", "type": "string", "pattern": "^(0x)?[0-9a-fA-F]{64}$" }, "type": { + "description": "The transaction type", "type": "string", "enum": [ "COINBASE", @@ -757,18 +793,19 @@ "pattern": "^\\d+$" }, "fee": { - "description": "Transaction fee in nano SEM", + "description": "Transaction fee in nano SEM. For CREATE/CALL, this field is zero; use gas instead", "type": "string", "format": "int64", "pattern": "^\\d+$" }, "nonce": { + "description": "The nonce of the sender", "type": "string", "format": "int64", "pattern": "^\\d+$" }, "timestamp": { - "description": "Transaction timestamp in milliseconds specified by the transaction creator. There can be a time drift up to 2 hours.", + "description": "Transaction timestamp in milliseconds specified by the sender. There can be a time drift up to 2 hours.", "type": "string", "format": "int64", "pattern": "^\\d+$" @@ -784,6 +821,7 @@ "type": "object", "properties": { "blockNumber": { + "description": "The number of block that included the transaction", "type": "string", "format": "int64", "pattern": "^\\d+$" @@ -793,6 +831,7 @@ "type": "string" }, "logs": { + "description": "Logs produced when executing this transaction", "type": "array", "items": { "$ref": "#/definitions/LogInfoType" @@ -804,30 +843,31 @@ "pattern": "^(0x)?[0-9a-fA-F]*$" }, "gas": { - "description": "Return the gas limit", + "description": "The gas limit set by the sender", "type": "string", "format": "int64", "pattern": "^\\d+$" }, "gasPrice": { - "description": "Return the gas Price", + "description": "The gas Price set by the sender", "type": "string", "format": "int64", "pattern": "^\\d+$" }, "gasUsed": { - "description": "Return the gas consumed", + "description": "The gas consumed. For non-VM transactions, this field is zero", "type": "string", "format": "int64", "pattern": "^\\d+$" }, "fee": { - "description": "Return the transaction fee in nano SEM. For VM transaction, gasPrice * gasUsed", + "description": "The transaction fee in nano SEM. For VM transactions, this field is zero", "type": "string", "format": "int64", "pattern": "^\\d+$" }, "internalTransactions": { + "description": "Internal transactions generated when executing this transaction", "type": "array", "items": { "$ref": "#/definitions/InternalTransactionType" @@ -869,30 +909,31 @@ "pattern": "^(0x)?[0-9a-fA-F]{40}$" }, "nonce": { + "description": "The sender's nonce", "type": "string", "format": "int64", "pattern": "^\\d+$" }, "gas": { - "description": "Return the gas limit", + "description": "The gas limit", "type": "string", "format": "int64", "pattern": "^\\d+$" }, "gasPrice": { - "description": "Return the gas Price", + "description": "The gas Price", "type": "string", "format": "int64", "pattern": "^\\d+$" }, "value": { - "description": "Transaction value in nano SEM", + "description": "The value being passed, in nano SEM", "type": "string", "format": "int64", "pattern": "^\\d+$" }, "data": { - "description": "Transaction data encoded in hexadecimal string", + "description": "The data being passed, in hexadecimal string", "type": "string", "pattern": "^(0x)?[0-9a-fA-F]*$" } @@ -902,17 +943,17 @@ "type": "object", "properties": { "address": { - "description": "Address encoded in hexadecimal string", + "description": "Contract address", "type": "string", "pattern": "^(0x)?[0-9a-fA-F]{64}$" }, "data": { - "description": "Log info data encoded in hexadecimal string", + "description": "Log data encoded in hexadecimal string", "type": "string", "pattern": "^(0x)?[0-9a-fA-F]*$" }, "topics": { - "description": "Topics encoded in hexadecimal string", + "description": "Log topics encoded in hexadecimal string", "type": "array", "items": { "type": "string" @@ -931,7 +972,7 @@ }, { "properties": { - "validSignature": { + "valid": { "description": "Whether the signature is valid", "type": "boolean" } @@ -978,12 +1019,8 @@ "$ref": "#/definitions/ApiHandlerResponse" }, { - "required": [ - "result" - ], "properties": { "result": { - "type": "object", "$ref": "#/definitions/SyncingProgressType" } } @@ -1019,11 +1056,52 @@ "pattern": "^\\d+$" } } + }, + "CallResponse": { + "type": "object", + "required": [ + "success" + ], + "allOf": [ + { + "$ref": "#/definitions/ApiHandlerResponse" + }, + { + "properties": { + "result": { + "description": "The return data in hex", + "type": "string", + "pattern": "^(0x)?[0-9a-fA-F]*$" + } + } + } + ] + }, + "EstimateGasResponse": { + "type": "object", + "required": [ + "success" + ], + "allOf": [ + { + "$ref": "#/definitions/ApiHandlerResponse" + }, + { + "properties": { + "result": { + "description": "The estimated gas usage", + "type": "string", + "format": "int64", + "pattern": "^\\d+$" + } + } + } + ] } }, "info": { "description": "Semux is an experimental high-performance blockchain platform that powers decentralized application.", - "version": "2.2.0", + "version": "2.3.0", "title": "Semux API", "contact": { "name": "Semux Foundation", @@ -2176,7 +2254,7 @@ { "name": "from", "in": "query", - "description": "Sender's address. The address must exist in the wallet.data of this Semux node.", + "description": "Sender's address. The account must exist in the wallet of this node.", "required": true, "type": "string", "pattern": "^(0x)?[0-9a-fA-F]{40}$" @@ -2192,7 +2270,7 @@ { "name": "value", "in": "query", - "description": "Amount of SEM to transfer in nano SEM", + "description": "Amount of value to transfer in nano SEM", "required": true, "type": "string", "format": "int64", @@ -2267,18 +2345,18 @@ "pattern": "^(0x)?[0-9a-fA-F]{40}$" }, { - "name": "nonce", + "name": "value", "in": "query", - "description": "Transaction nonce, default to sender's nonce if omitted", + "description": "Amount of SEM to transfer in nano SEM", "required": false, "type": "string", "format": "int64", "pattern": "^\\d+$" }, { - "name": "value", + "name": "nonce", "in": "query", - "description": "Amount of SEM to transfer in nano SEM", + "description": "Transaction nonce, default to sender's nonce if omitted", "required": false, "type": "string", "format": "int64", @@ -2293,18 +2371,18 @@ "pattern": "^(0x)?[0-9a-fA-F]+$" }, { - "name": "gasPrice", + "name": "gas", "in": "query", - "description": "The gas price", + "description": "The gas limit for the call", "required": true, "type": "string", "format": "int64", "pattern": "^\\d+$" }, { - "name": "gas", + "name": "gasPrice", "in": "query", - "description": "The gas limit for the call", + "description": "The gas price", "required": true, "type": "string", "format": "int64", @@ -2363,8 +2441,8 @@ { "name": "value", "in": "query", - "description": "Amount of SEM to transfer in nano SEM", - "required": true, + "description": "Amount of value to transfer in nano SEM", + "required": false, "type": "string", "format": "int64", "pattern": "^\\d+$" @@ -2387,30 +2465,22 @@ "pattern": "^(0x)?[0-9a-fA-F]+$" }, { - "name": "gasPrice", + "name": "gas", "in": "query", - "description": "The gas price", + "description": "The gas limit for the call", "required": true, "type": "string", "format": "int64", "pattern": "^\\d+$" }, { - "name": "gas", + "name": "gasPrice", "in": "query", - "description": "The gas limit for the call", + "description": "The gas price in nano SEM", "required": true, "type": "string", "format": "int64", "pattern": "^\\d+$" - }, - { - "name": "local", - "in": "query", - "description": "Specifies whether this is a local (free) call, or a transaction", - "required": false, - "default": false, - "type": "boolean" } ], "responses": { @@ -2434,33 +2504,60 @@ ] } }, - "/transaction/call-local": { + "/transaction/unvote": { "post": { "tags": [ "semux" ], - "summary": "Call a local contract.", - "description": "Call a VM local contract.", - "operationId": "call-local", + "summary": "Unvote", + "description": "Unvotes for a delegate.", + "operationId": "unvote", "produces": [ "application/json" ], "parameters": [ + { + "name": "from", + "in": "query", + "description": "Voter's address. The address must exist in the wallet.data of this Semux node.", + "required": true, + "type": "string", + "pattern": "^(0x)?[0-9a-fA-F]{40}$" + }, { "name": "to", "in": "query", - "description": "Recipient's address (the contract address)", + "description": "Delegate address", "required": true, "type": "string", "pattern": "^(0x)?[0-9a-fA-F]{40}$" }, { - "name": "data", + "name": "value", "in": "query", - "description": "Transaction data encoded in hexadecimal string", + "description": "Number of votes in nano SEM", + "required": true, + "type": "string", + "format": "int64", + "pattern": "^\\d+$" + }, + { + "name": "fee", + "in": "query", + "description": "Transaction fee in nano SEM, default to minimum fee if omitted", "required": false, "type": "string", - "pattern": "^(0x)?[0-9a-fA-F]+$" + "format": "int64", + "pattern": "^\\d+$" + }, + { + "name": "nonce", + "in": "query", + "description": "Transaction nonce, default to sender's nonce if omitted", + "required": false, + "type": "string", + "format": "int64", + "pattern": "^\\d+$" } ], "responses": { @@ -2484,14 +2581,14 @@ ] } }, - "/transaction/unvote": { + "/transaction/vote": { "post": { "tags": [ "semux" ], - "summary": "Unvote", - "description": "Unvotes for a delegate.", - "operationId": "unvote", + "summary": "Vote", + "description": "Votes for a delegate.", + "operationId": "vote", "produces": [ "application/json" ], @@ -2561,14 +2658,14 @@ ] } }, - "/transaction/vote": { + "/transaction/delegate": { "post": { "tags": [ "semux" ], - "summary": "Vote", - "description": "Votes for a delegate.", - "operationId": "vote", + "summary": "Register delegate", + "description": "Registers as a delegate", + "operationId": "registerDelegate", "produces": [ "application/json" ], @@ -2576,28 +2673,11 @@ { "name": "from", "in": "query", - "description": "Voter's address. The address must exist in the wallet.data of this Semux node.", - "required": true, - "type": "string", - "pattern": "^(0x)?[0-9a-fA-F]{40}$" - }, - { - "name": "to", - "in": "query", - "description": "Delegate address", + "description": "Registering address", "required": true, "type": "string", "pattern": "^(0x)?[0-9a-fA-F]{40}$" }, - { - "name": "value", - "in": "query", - "description": "Number of votes in nano SEM", - "required": true, - "type": "string", - "format": "int64", - "pattern": "^\\d+$" - }, { "name": "fee", "in": "query", @@ -2615,6 +2695,14 @@ "type": "string", "format": "int64", "pattern": "^\\d+$" + }, + { + "name": "data", + "in": "query", + "description": "Delegate name in hexadecimal encoded UTF-8 string, 16 bytes of data at maximum", + "required": true, + "type": "string", + "pattern": "^(0x)?[0-9a-fA-F]+$" } ], "responses": { @@ -2638,38 +2726,80 @@ ] } }, - "/transaction/delegate": { + "/transaction/raw": { "post": { "tags": [ "semux" ], - "summary": "Register delegate", - "description": "Registers as a delegate", - "operationId": "registerDelegate", + "summary": "Broadcast a raw transaction", + "description": "Broadcasts a raw transaction to the network.", + "operationId": "broadcastRawTransaction", "produces": [ "application/json" ], "parameters": [ { - "name": "from", + "name": "raw", "in": "query", - "description": "Registering address", + "description": "Raw transaction encoded in hexadecimal string.", "required": true, "type": "string", + "pattern": "^(0x)?[0-9a-fA-F]+$" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "$ref": "#/definitions/DoTransactionResponse" + } + }, + "400": { + "description": "bad request", + "schema": { + "$ref": "#/definitions/ApiHandlerResponse" + } + } + }, + "security": [ + { + "basicAuth": [] + } + ] + } + }, + "/call": { + "get": { + "tags": [ + "semux" + ], + "summary": "Make a local call", + "description": "Executes a new message call immediately without creating a transaction on the block chain.", + "operationId": "localCall", + "produces": [ + "application/json" + ], + "parameters": [ + { + "name": "from", + "in": "query", + "description": "Sender's address. The address must exist in the wallet.data of this Semux node.", + "required": false, + "type": "string", "pattern": "^(0x)?[0-9a-fA-F]{40}$" }, { - "name": "data", + "name": "to", "in": "query", - "description": "Delegate name in hexadecimal encoded UTF-8 string, 16 bytes of data at maximum", + "description": "Recipient's address (the contract address)", "required": true, "type": "string", - "pattern": "^(0x)?[0-9a-fA-F]+$" + "pattern": "^(0x)?[0-9a-fA-F]{40}$" }, { - "name": "fee", + "name": "value", "in": "query", - "description": "Transaction fee in nano SEM, default to minimum fee if omitted", + "description": "Amount of value to transfer in nano SEM", "required": false, "type": "string", "format": "int64", @@ -2683,13 +2813,39 @@ "type": "string", "format": "int64", "pattern": "^\\d+$" + }, + { + "name": "data", + "in": "query", + "description": "Transaction data encoded in hexadecimal string", + "required": false, + "type": "string", + "pattern": "^(0x)?[0-9a-fA-F]+$" + }, + { + "name": "gas", + "in": "query", + "description": "The gas limit for the call", + "required": false, + "type": "string", + "format": "int64", + "pattern": "^\\d+$" + }, + { + "name": "gasPrice", + "in": "query", + "description": "The gas price in nanoSEM", + "required": false, + "type": "string", + "format": "int64", + "pattern": "^\\d+$" } ], "responses": { "200": { "description": "successful operation", "schema": { - "$ref": "#/definitions/DoTransactionResponse" + "$ref": "#/definitions/CallResponse" } }, "400": { @@ -2706,32 +2862,84 @@ ] } }, - "/transaction/raw": { - "post": { + "/estimate-gas": { + "get": { "tags": [ "semux" ], - "summary": "Broadcast a raw transaction", - "description": "Broadcasts a raw transaction to the network.", - "operationId": "broadcastRawTransaction", + "summary": "Estimate gas", + "description": "Estimate the gas usage of a transaction.", + "operationId": "estimateGas", "produces": [ "application/json" ], "parameters": [ { - "name": "raw", + "name": "from", "in": "query", - "description": "Raw transaction encoded in hexadecimal string.", + "description": "Sender's address. The address must exist in the wallet.data of this Semux node.", + "required": false, + "type": "string", + "pattern": "^(0x)?[0-9a-fA-F]{40}$" + }, + { + "name": "to", + "in": "query", + "description": "Recipient's address (the contract address)", "required": true, "type": "string", + "pattern": "^(0x)?[0-9a-fA-F]{40}$" + }, + { + "name": "value", + "in": "query", + "description": "Amount of value to transfer in nano SEM", + "required": false, + "type": "string", + "format": "int64", + "pattern": "^\\d+$" + }, + { + "name": "nonce", + "in": "query", + "description": "Transaction nonce, default to sender's nonce if omitted", + "required": false, + "type": "string", + "format": "int64", + "pattern": "^\\d+$" + }, + { + "name": "data", + "in": "query", + "description": "Transaction data encoded in hexadecimal string", + "required": false, + "type": "string", "pattern": "^(0x)?[0-9a-fA-F]+$" + }, + { + "name": "gas", + "in": "query", + "description": "The gas limit for the call", + "required": false, + "type": "string", + "format": "int64", + "pattern": "^\\d+$" + }, + { + "name": "gasPrice", + "in": "query", + "description": "The gas price in SEM", + "required": false, + "type": "string", + "format": "int64", + "pattern": "^\\d+$" } ], "responses": { "200": { "description": "successful operation", "schema": { - "$ref": "#/definitions/DoTransactionResponse" + "$ref": "#/definitions/EstimateGasResponse" } }, "400": { @@ -2788,35 +2996,35 @@ ] }, { - "name": "fee", + "name": "to", "in": "query", - "description": "Transaction fee in nano", + "description": "Recipient's address", "required": true, "type": "string", - "format": "int64", - "pattern": "^\\d+$" + "pattern": "^(0x)?[0-9a-fA-F]{40}$" }, { - "name": "nonce", + "name": "value", "in": "query", - "description": "Transaction nonce", + "description": "Amount of value to transfer in nano SEM", "required": true, "type": "string", "format": "int64", "pattern": "^\\d+$" }, { - "name": "to", + "name": "fee", "in": "query", - "description": "Recipient's address", + "description": "Transaction fee in nano SEM, default to minimum fee if omitted", "required": false, "type": "string", - "pattern": "^(0x)?[0-9a-fA-F]{40}$" + "format": "int64", + "pattern": "^\\d+$" }, { - "name": "value", + "name": "nonce", "in": "query", - "description": "Transaction value in nano SEM", + "description": "Transaction nonce, default to sender's nonce if omitted", "required": false, "type": "string", "format": "int64", @@ -2841,18 +3049,18 @@ "pattern": "^(0x)?[0-9a-fA-F]+$" }, { - "name": "gasPrice", - "in": "query", - "description": "The gas price", - "required": false, - "type": "string", - "format": "int64", - "pattern": "^\\d+$" + "name": "gas", + "in": "query", + "description": "The gas limit for the call", + "required": false, + "type": "string", + "format": "int64", + "pattern": "^\\d+$" }, { - "name": "gas", + "name": "gasPrice", "in": "query", - "description": "The gas limit for the call", + "description": "The gas price", "required": false, "type": "string", "format": "int64", diff --git a/src/test/java/org/semux/api/util/TransactionBuilderTest.java b/src/test/java/org/semux/api/util/TransactionBuilderTest.java index 90ec08ead..5ad76cba3 100644 --- a/src/test/java/org/semux/api/util/TransactionBuilderTest.java +++ b/src/test/java/org/semux/api/util/TransactionBuilderTest.java @@ -10,6 +10,7 @@ import static org.mockito.Mockito.RETURNS_DEEP_STUBS; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.semux.core.Amount.Unit.NANO_SEM; import static org.semux.core.Amount.Unit.SEM; import org.junit.Rule; @@ -39,9 +40,9 @@ public void testDelegateWithWrongValue() { .withType(TransactionType.DELEGATE) .withValue("6") .withNonce("7") - .withFee("8", false) + .withFee("8") .buildUnsigned(); - assertEquals(SEM.of(5), tx.getValue()); + assertEquals(NANO_SEM.of(6), tx.getValue()); assertEquals(7L, tx.getNonce()); } } diff --git a/src/test/java/org/semux/api/v2/SemuxApiTest.java b/src/test/java/org/semux/api/v2/SemuxApiTest.java index 52c4b3abf..55936ddd1 100644 --- a/src/test/java/org/semux/api/v2/SemuxApiTest.java +++ b/src/test/java/org/semux/api/v2/SemuxApiTest.java @@ -45,7 +45,9 @@ import static org.semux.TestUtils.createTransaction; import static org.semux.core.Amount.Unit.NANO_SEM; import static org.semux.core.Amount.Unit.SEM; +import static org.semux.core.TransactionType.CALL; import static org.semux.core.TransactionType.COINBASE; +import static org.semux.core.TransactionType.CREATE; import static org.semux.core.TransactionType.TRANSFER; import static org.semux.core.TransactionType.UNVOTE; import static org.semux.core.TransactionType.VOTE; @@ -485,7 +487,9 @@ public void getTransactionLimitsTest() { assertTrue(response.isSuccess()); assertEquals(config.spec().maxTransactionDataSize(type), response.getResult().getMaxTransactionDataSize().intValue()); - assertEquals(config.spec().minTransactionFee().getNano(), + assertEquals( + type.equals(CALL) || type.equals(CREATE) ? Amount.ZERO.getNano() + : config.spec().minTransactionFee().getNano(), Long.parseLong(response.getResult().getMinTransactionFee())); if (type.equals(org.semux.core.TransactionType.DELEGATE)) { @@ -611,17 +615,17 @@ public void signMessageTest() { String signature = response.getResult(); VerifyMessageResponse verifyMessageResponse = api.verifyMessage(address, message, signature); assertTrue(verifyMessageResponse.isSuccess()); - assertTrue(verifyMessageResponse.isValidSignature()); + assertTrue(verifyMessageResponse.isValid()); // verify no messing with fromaddress verifyMessageResponse = api.verifyMessage(addressOther, message, signature); assertTrue(verifyMessageResponse.isSuccess()); - assertFalse(verifyMessageResponse.isValidSignature()); + assertFalse(verifyMessageResponse.isValid()); // verify no messing with message verifyMessageResponse = api.verifyMessage(address, message + "other", signature); assertTrue(verifyMessageResponse.isSuccess()); - assertFalse(verifyMessageResponse.isValidSignature()); + assertFalse(verifyMessageResponse.isValid()); } @Test @@ -755,10 +759,10 @@ public void composeRawTransactionTransferTest() { ComposeRawTransactionResponse resp = api.composeRawTransaction( network, type, - fee, - nonce, to, value, + fee, + nonce, timestamp, data, null, @@ -774,8 +778,8 @@ public void composeRawTransactionTransferTest() { public void composeRawTransactionDelegateTest() { String network = "TESTNET"; String type = "DELEGATE"; - String to = ""; - String value = "0"; + String to = null; + String value = null; String fee = String.valueOf(config.spec().minTransactionFee().getNano()); String nonce = "123"; String timestamp = "1523028482000"; @@ -784,10 +788,10 @@ public void composeRawTransactionDelegateTest() { ComposeRawTransactionResponse resp = api.composeRawTransaction( network, type, - fee, - nonce, to, value, + fee, + nonce, timestamp, data, null, @@ -813,10 +817,10 @@ public void composeRawTransactionVoteTest() { ComposeRawTransactionResponse resp = api.composeRawTransaction( network, type, - fee, - nonce, to, value, + fee, + nonce, timestamp, data, null, @@ -833,7 +837,7 @@ public void composeRawTransactionCreateTest() { String network = "TESTNET"; String type = "CREATE"; String to = null; - String value = null; // should be able to set value here + String value = "0"; String fee = null; String nonce = "123"; String timestamp = "1523028482000"; @@ -844,18 +848,18 @@ public void composeRawTransactionCreateTest() { ComposeRawTransactionResponse resp = api.composeRawTransaction( network, type, - fee, - nonce, to, value, + fee, + nonce, timestamp, data, - gasPrice, - gas); + null, + null); assertTrue(resp.isSuccess()); assertEquals( - "0x0105140000000000000000000000000000000000000000000000000000000000000000004c4b40000000000000007b000001629b9257d00673656d75783100000000000186a00000000000000001", + "0x010514000000000000000000000000000000000000000000000000000000000000000000000000000000000000007b000001629b9257d00673656d75783100000000000000000000000000000000", resp.getResult()); } @@ -875,18 +879,18 @@ public void composeRawTransactionCallTest() { ComposeRawTransactionResponse resp = api.composeRawTransaction( network, type, - fee, - nonce, to, value, + fee, + nonce, timestamp, data, - gasPrice, - gas); + gas, + gasPrice); assertTrue(resp.isSuccess()); assertEquals( - "0x010614db7cadb25fdcdd546fb0268524107582c3f8999c000000000000006400000000004c4b40000000000000007b000001629b9257d00673656d75783100000000000186a00000000000000001", + "0x010614db7cadb25fdcdd546fb0268524107582c3f8999c00000000000000640000000000000000000000000000007b000001629b9257d00673656d75783100000000000186a00000000000000001", resp.getResult()); } From 9e86337a7839e2c922195702c46d3341a9e25bde Mon Sep 17 00:00:00 2001 From: semux Date: Sun, 7 Jul 2019 22:59:10 +0100 Subject: [PATCH 7/7] Net: change capability set, compatibility breaking --- src/main/java/org/semux/api/v2/SemuxApiImpl.java | 2 +- src/main/java/org/semux/config/AbstractConfig.java | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/main/java/org/semux/api/v2/SemuxApiImpl.java b/src/main/java/org/semux/api/v2/SemuxApiImpl.java index b5c14b1b5..f5505457e 100644 --- a/src/main/java/org/semux/api/v2/SemuxApiImpl.java +++ b/src/main/java/org/semux/api/v2/SemuxApiImpl.java @@ -278,7 +278,7 @@ private Response getTransactions(boolean pending, String address, String from, S return badRequest("Parameter `address` is not a valid hexadecimal string"); } - if (addressBytes.length != 20) { + if (addressBytes.length != Key.ADDRESS_LEN) { return badRequest("Parameter `address` length is invalid"); } diff --git a/src/main/java/org/semux/config/AbstractConfig.java b/src/main/java/org/semux/config/AbstractConfig.java index a052a7afe..4af450fe2 100644 --- a/src/main/java/org/semux/config/AbstractConfig.java +++ b/src/main/java/org/semux/config/AbstractConfig.java @@ -315,11 +315,6 @@ public String getClientId() { @Override public CapabilityTreeSet getClientCapabilities() { - // TODO: replace with SEMUX eventually - if (this.network == Network.TESTNET) { - return CapabilityTreeSet.of(Capability.SEM); - } - return CapabilityTreeSet.of(Capability.SEMUX); }