diff --git a/apl-api2/pom.xml b/apl-api2/pom.xml index e06f46de64..2d5a2c10c3 100644 --- a/apl-api2/pom.xml +++ b/apl-api2/pom.xml @@ -134,7 +134,7 @@ ApiException.java,NotFoundException.java - AccountInfo,AccountInfoResp,AccountReq,AccountReqTest,AccountReqSendMoney,BlockchainInfo,BlockInfo,BlockchainState,CountResponse,CreateChildAccountResp,EmptyResponse,HealthResponse,ListResponse,QueryCountResult,QueryObject,QueryResult,TransactionInfo,TransactionInfoArrayResp,TransactionInfoResp,TxReceipt,TxRequest,PublishContractReq,CallContractMethodReq,ContractListResponse,ContractDetails,ContractDetailsResponse,ContractStateResponse,ContractMethod,CallMethodResult,TransactionArrayResp,CallViewMethodReq,ResultValueResponse,MemberSpec,PropertySpec,ArgSpec,ContractSpecResponse,CurrencyBurningTxCreationRequest,TransactionCreationRequest,TransactionCreationResponse,UnconfirmedTransactionInfo,CurrencyBurningTxParams,AddressSpec,AddressSpecResponse,TransactionVerificationResponse,LastTransactionVerificationResponse,TransactionVerification,StringListResponse,ModuleSourceResponse,ModuleListResponse,AsrSpec,AsrSpecResponse,AsrMemberSpec,ContractEventsRequest,ContractEventsResponse,ContractEventDetails + AccountInfo,AccountInfoResp,AccountReq,AccountReqTest,AccountReqSendMoney,BlockchainInfo,BlockInfo,BlockchainState,CountResponse,CreateChildAccountResp,EmptyResponse,HealthResponse,ListResponse,QueryCountResult,QueryObject,QueryResult,TransactionInfo,TransactionInfoArrayResp,TransactionInfoResp,TxReceipt,TxRequest,PublishContractReq,CallContractMethodReq,ContractListResponse,ContractDetails,ContractDetailsResponse,ContractStateResponse,ContractMethod,CallMethodResult,TransactionByteArrayResp,CallViewMethodReq,ResultValueResponse,MemberSpec,PropertySpec,ArgSpec,ContractSpecResponse,CurrencyBurningTxCreationRequest,TransactionCreationRequest,TransactionCreationResponse,UnconfirmedTransactionInfo,CurrencyBurningTxParams,AddressSpec,AddressSpecResponse,TransactionVerificationResponse,LastTransactionVerificationResponse,TransactionVerification,StringListResponse,ModuleSourceResponse,ModuleListResponse,AsrSpec,AsrSpecResponse,AsrMemberSpec,ContractEventsRequest,ContractEventsResponse,ContractEventDetails false diff --git a/apl-api2/src/main/resources/yaml/apollo-api-v2.yaml b/apl-api2/src/main/resources/yaml/apollo-api-v2.yaml index e934a14ac8..5ffe3b6f6a 100644 --- a/apl-api2/src/main/resources/yaml/apollo-api-v2.yaml +++ b/apl-api2/src/main/resources/yaml/apollo-api-v2.yaml @@ -647,7 +647,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/TransactionArrayResp' + $ref: '#/components/schemas/TransactionByteArrayResp' 400: $ref: '#/components/responses/BadRequest' 500: @@ -674,7 +674,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/TransactionArrayResp' + $ref: '#/components/schemas/TransactionByteArrayResp' 400: $ref: '#/components/responses/BadRequest' 500: @@ -711,9 +711,9 @@ paths: post: tags: - smc - summary: Returns signed transaction to call the contract method. + summary: Create, sign and return the 'Call smart-contract' transaction. description: | - Returns signed transaction to call the contract method. + Create, sign and return the 'Call smart-contract' transaction. operationId: createCallContractMethodTx requestBody: description: the contract method and method parameters @@ -728,7 +728,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/TransactionArrayResp' + $ref: '#/components/schemas/TransactionByteArrayResp' 400: $ref: '#/components/responses/BadRequest' 500: @@ -860,7 +860,7 @@ paths: example: 1591696372000 - name: transaction in: query - description: The transaction that contains contract + description: The contract transaction id required: false schema: type: string @@ -2372,7 +2372,7 @@ components: items: type: string - TransactionArrayResp: + TransactionByteArrayResp: required: - tx allOf: diff --git a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/dao/blockchain/TransactionDao.java b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/dao/blockchain/TransactionDao.java index 47f0a2b47d..e1d94d2a5a 100644 --- a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/dao/blockchain/TransactionDao.java +++ b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/dao/blockchain/TransactionDao.java @@ -1,7 +1,7 @@ package com.apollocurrency.aplwallet.apl.core.dao.blockchain; import com.apollocurrency.aplwallet.api.v2.model.TxReceipt; -import com.apollocurrency.aplwallet.apl.core.model.Sort; +import com.apollocurrency.aplwallet.apl.util.api.Sort; import com.apollocurrency.aplwallet.apl.util.db.TransactionalDataSource; import com.apollocurrency.aplwallet.apl.core.entity.appdata.ChatInfo; import com.apollocurrency.aplwallet.apl.core.entity.blockchain.TransactionEntity; diff --git a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/dao/blockchain/TransactionDaoImpl.java b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/dao/blockchain/TransactionDaoImpl.java index be436d242c..3ae9caf04a 100644 --- a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/dao/blockchain/TransactionDaoImpl.java +++ b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/dao/blockchain/TransactionDaoImpl.java @@ -29,7 +29,7 @@ import com.apollocurrency.aplwallet.apl.core.db.DatabaseManager; import com.apollocurrency.aplwallet.apl.core.entity.appdata.ChatInfo; import com.apollocurrency.aplwallet.apl.core.entity.blockchain.TransactionEntity; -import com.apollocurrency.aplwallet.apl.core.model.Sort; +import com.apollocurrency.aplwallet.apl.util.api.Sort; import com.apollocurrency.aplwallet.apl.core.model.TransactionDbInfo; import com.apollocurrency.aplwallet.apl.core.transaction.PrunableTransaction; import com.apollocurrency.aplwallet.apl.crypto.Convert; diff --git a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/dao/state/keyfactory/ComplexKey.java b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/dao/state/keyfactory/ThreeValuesKey.java similarity index 83% rename from apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/dao/state/keyfactory/ComplexKey.java rename to apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/dao/state/keyfactory/ThreeValuesKey.java index 391fb8f52e..ac08273ea7 100644 --- a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/dao/state/keyfactory/ComplexKey.java +++ b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/dao/state/keyfactory/ThreeValuesKey.java @@ -13,13 +13,13 @@ /** * @author andrew.zinchenko@gmail.com */ -public final class ComplexKey implements DbKey { +public final class ThreeValuesKey implements DbKey { private final long idA; private final String idB; private final byte[] idC; - public ComplexKey(long idA, String idB, byte[] idC) { + public ThreeValuesKey(long idA, String idB, byte[] idC) { this.idA = idA; this.idB = idB; this.idC = idC; @@ -42,7 +42,7 @@ public int setPK(PreparedStatement pstmt, int index) throws SQLException { public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - ComplexKey that = (ComplexKey) o; + ThreeValuesKey that = (ThreeValuesKey) o; return idA == that.idA && idB.equals(that.idB) && Arrays.equals(idC, that.idC); } @@ -56,7 +56,7 @@ public int hashCode() { @Override public String toString() { - return "ComplexKey{" + "idA=" + idA + ", idB=" + idB + ", idC=" + Convert.toHexString(idC) + '}'; + return "ThreeValuesKey{" + "idA=" + idA + ", idB=" + idB + ", idC=" + Convert.toHexString(idC) + '}'; } } diff --git a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/dao/state/keyfactory/ComplexKeyFactory.java b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/dao/state/keyfactory/ThreeValuesKeyFactory.java similarity index 76% rename from apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/dao/state/keyfactory/ComplexKeyFactory.java rename to apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/dao/state/keyfactory/ThreeValuesKeyFactory.java index 3882c4cee0..265b1bf35d 100644 --- a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/dao/state/keyfactory/ComplexKeyFactory.java +++ b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/dao/state/keyfactory/ThreeValuesKeyFactory.java @@ -9,13 +9,13 @@ /** * @author andrew.zinchenko@gmail.com */ -public abstract class ComplexKeyFactory extends KeyFactory { +public abstract class ThreeValuesKeyFactory extends KeyFactory { private final String idColumnA; private final String idColumnB; private final String idColumnC; - protected ComplexKeyFactory(String idColumnA, String idColumnB, String idColumnC) { + protected ThreeValuesKeyFactory(String idColumnA, String idColumnB, String idColumnC) { super(" WHERE" + " " + idColumnA + " = ? " + "AND " + idColumnB + " = ? " + @@ -32,10 +32,10 @@ protected ComplexKeyFactory(String idColumnA, String idColumnB, String idColumnC @Override public DbKey newKey(ResultSet rs) throws SQLException { - return new ComplexKey(rs.getLong(idColumnA), rs.getString(idColumnB), rs.getBytes(idColumnC)); + return new ThreeValuesKey(rs.getLong(idColumnA), rs.getString(idColumnB), rs.getBytes(idColumnC)); } public DbKey newKey(long idA, String idB, byte[] idC) { - return new ComplexKey(idA, idB, idC); + return new ThreeValuesKey(idA, idB, idC); } } diff --git a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/dao/state/smc/SmcContractEventLogTable.java b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/dao/state/smc/SmcContractEventLogTable.java index 91c8ae48ed..e3e2b046f7 100644 --- a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/dao/state/smc/SmcContractEventLogTable.java +++ b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/dao/state/smc/SmcContractEventLogTable.java @@ -6,6 +6,7 @@ import com.apollocurrency.aplwallet.api.v2.model.ContractEventDetails; import com.apollocurrency.aplwallet.apl.core.converter.db.smc.SmcContractEventLogDetailsRowMapper; import com.apollocurrency.aplwallet.apl.core.converter.db.smc.SmcContractEventLogRowMapper; +import com.apollocurrency.aplwallet.apl.core.dao.JdbcQueryExecutionHelper; import com.apollocurrency.aplwallet.apl.core.dao.state.derived.DerivedDbTable; import com.apollocurrency.aplwallet.apl.core.dao.state.keyfactory.DbKey; import com.apollocurrency.aplwallet.apl.core.db.DatabaseManager; @@ -13,6 +14,8 @@ import com.apollocurrency.aplwallet.apl.core.service.fulltext.FullTextOperationData; import com.apollocurrency.aplwallet.apl.util.annotation.DatabaseSpecificDml; import com.apollocurrency.aplwallet.apl.util.annotation.DmlMarker; +import com.apollocurrency.aplwallet.apl.util.api.PositiveRange; +import com.apollocurrency.aplwallet.apl.util.api.Sort; import com.apollocurrency.aplwallet.apl.util.db.DbUtils; import com.apollocurrency.aplwallet.apl.util.db.TransactionalDataSource; import com.apollocurrency.aplwallet.apl.util.injectable.PropertiesHolder; @@ -45,6 +48,7 @@ public class SmcContractEventLogTable extends DerivedDbTable txQueryExecutionHelper; /** * Number of blocks to keep when trimming @@ -64,6 +68,7 @@ public SmcContractEventLogTable(PropertiesHolder propertiesHolder, this.propertiesHolder = propertiesHolder; this.batchCommitSize = propertiesHolder.BATCH_COMMIT_SIZE(); trimKeep = propertiesHolder.getIntProperty("apl.smcEventLogTrimKeep", -1); + this.txQueryExecutionHelper = new JdbcQueryExecutionHelper<>(databaseManager.getDataSource(), (rs) -> detailsRowMapper.map(rs, null)); } /** @@ -202,7 +207,7 @@ public List getEntries(long eventId, byte[] signature, return entryList; } - public List getEventsByFilter(Long contract, String name, int heightFrom, int heightTo, int from, int to, String order) { + public List getEventsByFilter(Long contract, String name, PositiveRange blockRange, PositiveRange paging, Sort order) { StringBuilder sql = new StringBuilder( "SELECT el.*, " + "e.contract, e.name, e.spec " + @@ -214,39 +219,29 @@ public List getEventsByFilter(Long contract, String name, sql.append(" AND e.name = ? "); } - if (heightTo > 0) { + if (blockRange.isTopBoundarySet()) { sql.append(" AND el.height <= ? "); } sql.append("ORDER BY el.db_id ").append(order); - sql.append(DbUtils.limitsClause(from, to)); + sql.append(DbUtils.limitsClause(paging)); log.trace("Sql.query={}", sql); - try (Connection con = databaseManager.getDataSource().getConnection(); - PreparedStatement pstm = con.prepareStatement(sql.toString())) { + return txQueryExecutionHelper.executeListQuery(con -> { + PreparedStatement pstm = con.prepareStatement(sql.toString()); int i = 0; pstm.setLong(++i, contract); - pstm.setInt(++i, heightFrom); - + pstm.setInt(++i, blockRange.from()); if (name != null) { pstm.setString(++i, name); } - - if (heightTo > 0) { - pstm.setInt(++i, heightTo); + if (blockRange.to() > 0) { + pstm.setInt(++i, blockRange.to()); } - DbUtils.setLimits(++i, pstm, from, to); + DbUtils.setLimits(++i, pstm, paging); pstm.setFetchSize(50); - try (ResultSet rs = pstm.executeQuery()) { - List list = new ArrayList<>(); - while (rs.next()) { - list.add(detailsRowMapper.map(rs, null)); - } - return list; - } - } catch (SQLException e) { - throw new RuntimeException(e); - } + return pstm; + }); } } diff --git a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/dao/state/smc/SmcContractMappingTable.java b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/dao/state/smc/SmcContractMappingTable.java index bcfddeb0aa..b56589a870 100644 --- a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/dao/state/smc/SmcContractMappingTable.java +++ b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/dao/state/smc/SmcContractMappingTable.java @@ -6,7 +6,7 @@ import com.apollocurrency.aplwallet.apl.core.converter.db.smc.SmcContractMappingRowMapper; import com.apollocurrency.aplwallet.apl.core.dao.state.derived.VersionedDeletableEntityDbTable; -import com.apollocurrency.aplwallet.apl.core.dao.state.keyfactory.ComplexKeyFactory; +import com.apollocurrency.aplwallet.apl.core.dao.state.keyfactory.ThreeValuesKeyFactory; import com.apollocurrency.aplwallet.apl.core.dao.state.keyfactory.DbKey; import com.apollocurrency.aplwallet.apl.core.db.DatabaseManager; import com.apollocurrency.aplwallet.apl.core.entity.state.smc.SmcContractMappingEntity; @@ -28,7 +28,7 @@ * @author andrew.zinchenko@gmail.com */ public class SmcContractMappingTable extends VersionedDeletableEntityDbTable { - public static final ComplexKeyFactory KEY_FACTORY = new ComplexKeyFactory<>("address", "name", "entry_key") { + public static final ThreeValuesKeyFactory KEY_FACTORY = new ThreeValuesKeyFactory<>("address", "name", "entry_key") { @Override public DbKey newKey(SmcContractMappingEntity mapping) { if (mapping.getDbKey() == null) { diff --git a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/dao/state/smc/SmcContractTable.java b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/dao/state/smc/SmcContractTable.java index 81cf9c7074..6a5fae51eb 100644 --- a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/dao/state/smc/SmcContractTable.java +++ b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/dao/state/smc/SmcContractTable.java @@ -7,12 +7,14 @@ import com.apollocurrency.aplwallet.api.v2.model.ContractDetails; import com.apollocurrency.aplwallet.apl.core.converter.db.smc.SmcContractDetailsRowMapper; import com.apollocurrency.aplwallet.apl.core.converter.db.smc.SmcContractRowMapper; +import com.apollocurrency.aplwallet.apl.core.dao.JdbcQueryExecutionHelper; import com.apollocurrency.aplwallet.apl.core.dao.state.derived.EntityDbTable; import com.apollocurrency.aplwallet.apl.core.dao.state.keyfactory.DbKey; import com.apollocurrency.aplwallet.apl.core.dao.state.keyfactory.LongKeyFactory; import com.apollocurrency.aplwallet.apl.core.db.DatabaseManager; import com.apollocurrency.aplwallet.apl.core.entity.state.smc.SmcContractEntity; import com.apollocurrency.aplwallet.apl.core.service.fulltext.FullTextOperationData; +import com.apollocurrency.aplwallet.apl.core.service.state.smc.ContractQuery; import com.apollocurrency.aplwallet.apl.util.db.DbUtils; import com.apollocurrency.aplwallet.apl.util.db.TransactionalDataSource; @@ -23,7 +25,6 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; -import java.util.ArrayList; import java.util.List; /** @@ -45,13 +46,12 @@ public DbKey newKey(SmcContractEntity contract) { private static final String TABLE_NAME = "smc_contract"; private static final SmcContractRowMapper MAPPER = new SmcContractRowMapper(KEY_FACTORY); - - private final SmcContractDetailsRowMapper smcContractDetailsRowMapper; + private final JdbcQueryExecutionHelper txQueryExecutionHelper; @Inject public SmcContractTable(DatabaseManager databaseManager, Event fullTextOperationDataEvent, SmcContractDetailsRowMapper smcContractDetailsRowMapper) { super(TABLE_NAME, KEY_FACTORY, false, null, databaseManager, fullTextOperationDataEvent); - this.smcContractDetailsRowMapper = smcContractDetailsRowMapper; + this.txQueryExecutionHelper = new JdbcQueryExecutionHelper<>(databaseManager.getDataSource(), (rs) -> smcContractDetailsRowMapper.map(rs, null)); } @Override @@ -110,80 +110,21 @@ public void save(Connection con, SmcContractEntity entity) throws SQLException { } } - //TODO use a special wrapping object for query filter params with default settings and statement adjustments - public List getContractsByFilter(Long address, Long txId, Long owner, String name, String baseContract, Integer blockTimestamp, String status, int height, int from, int to) { - String namePrefix = null; - String baseContractPrefix = null; + public List getContractsByFilter(ContractQuery query) { StringBuilder sql = new StringBuilder( "SELECT sc.*, " + "ss.status as smc_status " + "FROM smc_contract sc " + - "LEFT JOIN smc_state ss on sc.address = ss.address " + - "WHERE sc.latest = true AND sc.height <= ? AND ss.latest = true "); - - if (address != null) { - sql.append(" AND sc.address = ? "); - } - if (txId != null) { - sql.append(" AND sc.transaction_id = ? "); - } - if (owner != null) { - sql.append(" AND sc.owner = ? "); - } - if (name != null && !name.isEmpty()) { - sql.append(" AND sc.name LIKE ? "); - namePrefix = name.replace("%", "\\%").replace("_", "\\_") + "%"; - } - if (baseContract != null && !baseContract.isEmpty()) { - sql.append(" AND sc.base_contract LIKE ? "); - baseContractPrefix = baseContract.replace("%", "\\%").replace("_", "\\_") + "%"; - } - if (blockTimestamp != null) { - sql.append(" AND sc.block_timestamp >= ? "); - } - if (status != null) { - sql.append(" AND ss.status = ? "); - } + "LEFT JOIN smc_state ss on sc.address = ss.address "); + sql.append(query.toWhereClause("WHERE sc.latest = true AND ss.latest = true ")); sql.append("ORDER BY sc.block_timestamp DESC, sc.db_id DESC "); - sql.append(DbUtils.limitsClause(from, to)); + sql.append(DbUtils.limitsClause(query.getPaging())); - //TODO try to use JdbcQueryExecutionHelper - try (Connection con = databaseManager.getDataSource().getConnection(); - PreparedStatement pstm = con.prepareStatement(sql.toString())) { - int i = 0; - pstm.setInt(++i, height); - if (address != null) { - pstm.setLong(++i, address); - } - if (txId != null) { - pstm.setLong(++i, txId); - } - if (owner != null) { - pstm.setLong(++i, owner); - } - if (namePrefix != null) { - pstm.setString(++i, namePrefix); - } - if (baseContractPrefix != null) { - pstm.setString(++i, baseContractPrefix); - } - if (blockTimestamp != null) { - pstm.setInt(++i, blockTimestamp); - } - if (status != null) { - pstm.setString(++i, status); - } - DbUtils.setLimits(++i, pstm, from, to); + return txQueryExecutionHelper.executeListQuery(con -> { + PreparedStatement pstm = con.prepareStatement(sql.toString()); + query.setPreparedStatementParameters(pstm); pstm.setFetchSize(50); - try (ResultSet rs = pstm.executeQuery()) { - List list = new ArrayList<>(); - while (rs.next()) { - list.add(smcContractDetailsRowMapper.map(rs, null)); - } - return list; - } - } catch (SQLException e) { - throw new RuntimeException(e); - } + return pstm; + }); } } diff --git a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/http/BlockEventSourceProcessor.java b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/http/BlockEventSourceProcessor.java index 1c67e14f73..956a8f961a 100644 --- a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/http/BlockEventSourceProcessor.java +++ b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/http/BlockEventSourceProcessor.java @@ -5,7 +5,7 @@ package com.apollocurrency.aplwallet.apl.core.http; import com.apollocurrency.aplwallet.apl.core.model.Block; -import com.apollocurrency.aplwallet.apl.core.model.Sort; +import com.apollocurrency.aplwallet.apl.util.api.Sort; import com.apollocurrency.aplwallet.apl.core.model.Transaction; import com.apollocurrency.aplwallet.apl.core.entity.state.account.Account; import com.apollocurrency.aplwallet.apl.core.entity.state.account.AccountAsset; diff --git a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/http/get/GetBlockchainTransactions.java b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/http/get/GetBlockchainTransactions.java index 66ec76ed7d..94b7d0972c 100644 --- a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/http/get/GetBlockchainTransactions.java +++ b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/http/get/GetBlockchainTransactions.java @@ -24,7 +24,7 @@ import com.apollocurrency.aplwallet.apl.core.http.AbstractAPIRequestHandler; import com.apollocurrency.aplwallet.apl.core.http.HttpParameterParserUtil; import com.apollocurrency.aplwallet.apl.core.http.JSONData; -import com.apollocurrency.aplwallet.apl.core.model.Sort; +import com.apollocurrency.aplwallet.apl.util.api.Sort; import com.apollocurrency.aplwallet.apl.core.model.Transaction; import com.apollocurrency.aplwallet.apl.util.exception.AplException; import org.json.simple.JSONArray; diff --git a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/http/get/GetPrivateBlockchainTransactions.java b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/http/get/GetPrivateBlockchainTransactions.java index 484a085b5f..099fc4acb1 100644 --- a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/http/get/GetPrivateBlockchainTransactions.java +++ b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/http/get/GetPrivateBlockchainTransactions.java @@ -5,7 +5,7 @@ package com.apollocurrency.aplwallet.apl.core.http.get; import com.apollocurrency.aplwallet.apl.core.model.Block; -import com.apollocurrency.aplwallet.apl.core.model.Sort; +import com.apollocurrency.aplwallet.apl.util.api.Sort; import com.apollocurrency.aplwallet.apl.core.model.Transaction; import com.apollocurrency.aplwallet.apl.core.http.APITag; import com.apollocurrency.aplwallet.apl.core.http.AbstractAPIRequestHandler; diff --git a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/model/Sort.java b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/model/Sort.java deleted file mode 100644 index 391a3a9e9b..0000000000 --- a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/model/Sort.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright © 2018-2021 Apollo Foundation - */ - -package com.apollocurrency.aplwallet.apl.core.model; - -/** - * @author Andrii Boiarskyi - * @see - * @since 1.0.0 - */ -public class Sort { - private final String order; - - public Sort(String order) { - this.order = order; - if (!isASC() && !isDESC()) { - throw new IllegalArgumentException("Not allowed sort value: " + order); - } - } - - public boolean isASC() { - return "ASC".equalsIgnoreCase(order); - } - - public boolean isDESC() { - return "DESC".equalsIgnoreCase(order); - } - - @Override - public String toString() { - return order; - } - - public static Sort desc() { - return new Sort("DESC"); - } - - public static Sort asc() { - return new Sort("ASC"); - } -} diff --git a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/rest/v2/converter/SmartMethodMapper.java b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/rest/v2/converter/SmartMethodMapper.java index 2d3563105e..57937e41e4 100644 --- a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/rest/v2/converter/SmartMethodMapper.java +++ b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/rest/v2/converter/SmartMethodMapper.java @@ -20,7 +20,6 @@ public class SmartMethodMapper implements Converter @Override public SmartMethod apply(ContractMethod dto) { var modelBuilder = SmartMethod.builder() - .name(dto.getFunction()) .name(dto.getFunction()) .value(BigInteger.ZERO); if (dto.getInput() != null) { diff --git a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/rest/v2/impl/SmcApiServiceImpl.java b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/rest/v2/impl/SmcApiServiceImpl.java index 1e8c2583c1..e8f1c53643 100644 --- a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/rest/v2/impl/SmcApiServiceImpl.java +++ b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/rest/v2/impl/SmcApiServiceImpl.java @@ -14,16 +14,13 @@ import com.apollocurrency.aplwallet.api.v2.model.ContractEventsRequest; import com.apollocurrency.aplwallet.api.v2.model.ContractEventsResponse; import com.apollocurrency.aplwallet.api.v2.model.ContractListResponse; -import com.apollocurrency.aplwallet.api.v2.model.ContractMethod; import com.apollocurrency.aplwallet.api.v2.model.ContractSpecResponse; import com.apollocurrency.aplwallet.api.v2.model.ContractStateResponse; -import com.apollocurrency.aplwallet.api.v2.model.MemberSpec; import com.apollocurrency.aplwallet.api.v2.model.ModuleListResponse; import com.apollocurrency.aplwallet.api.v2.model.ModuleSourceResponse; -import com.apollocurrency.aplwallet.api.v2.model.PropertySpec; import com.apollocurrency.aplwallet.api.v2.model.PublishContractReq; import com.apollocurrency.aplwallet.api.v2.model.ResultValueResponse; -import com.apollocurrency.aplwallet.api.v2.model.TransactionArrayResp; +import com.apollocurrency.aplwallet.api.v2.model.TransactionByteArrayResp; import com.apollocurrency.aplwallet.apl.core.chainid.BlockchainConfig; import com.apollocurrency.aplwallet.apl.core.config.SmcConfig; import com.apollocurrency.aplwallet.apl.core.entity.state.account.Account; @@ -33,8 +30,8 @@ import com.apollocurrency.aplwallet.apl.core.rest.v2.ResponseBuilderV2; import com.apollocurrency.aplwallet.apl.core.rest.v2.converter.CallMethodResultMapper; import com.apollocurrency.aplwallet.apl.core.rest.v2.converter.MethodSpecMapper; -import com.apollocurrency.aplwallet.apl.core.rest.v2.converter.SmartMethodMapper; import com.apollocurrency.aplwallet.apl.core.service.state.account.AccountService; +import com.apollocurrency.aplwallet.apl.core.service.state.smc.ContractQuery; import com.apollocurrency.aplwallet.apl.core.service.state.smc.ContractToolService; import com.apollocurrency.aplwallet.apl.core.service.state.smc.SmcContractRepository; import com.apollocurrency.aplwallet.apl.core.service.state.smc.SmcContractService; @@ -48,13 +45,13 @@ import com.apollocurrency.aplwallet.apl.crypto.Crypto; import com.apollocurrency.aplwallet.apl.smc.model.AplAddress; import com.apollocurrency.aplwallet.apl.smc.service.SmcContractEventService; -import com.apollocurrency.aplwallet.apl.smc.service.SmcContractTxBatchProcessor; import com.apollocurrency.aplwallet.apl.smc.service.SmcContractTxProcessor; import com.apollocurrency.aplwallet.apl.smc.service.tx.CallMethodTxValidator; -import com.apollocurrency.aplwallet.apl.smc.service.tx.CallViewMethodTxProcessor; import com.apollocurrency.aplwallet.apl.smc.service.tx.PublishContractTxValidator; import com.apollocurrency.aplwallet.apl.util.Convert2; import com.apollocurrency.aplwallet.apl.util.StringUtils; +import com.apollocurrency.aplwallet.apl.util.api.PositiveRange; +import com.apollocurrency.aplwallet.apl.util.api.Sort; import com.apollocurrency.aplwallet.apl.util.api.parameter.FirstLastIndexBeanParam; import com.apollocurrency.aplwallet.apl.util.cdi.config.Property; import com.apollocurrency.aplwallet.apl.util.exception.ApiErrors; @@ -67,18 +64,15 @@ import com.apollocurrency.smc.contract.SmartMethod; import com.apollocurrency.smc.contract.fuel.ContractFuel; import com.apollocurrency.smc.contract.vm.ExecutionLog; -import com.apollocurrency.smc.contract.vm.ResultValue; import com.apollocurrency.smc.data.expr.Term; import com.apollocurrency.smc.data.expr.TrueTerm; import com.apollocurrency.smc.data.expr.parser.TermParser; -import com.apollocurrency.smc.data.type.Address; import com.apollocurrency.smc.polyglot.SimpleVersion; import com.apollocurrency.smc.polyglot.Version; import com.apollocurrency.smc.polyglot.language.ContractSpec; import com.apollocurrency.smc.polyglot.language.SmartSource; import com.apollocurrency.smc.polyglot.language.lib.JSLibraryProvider; import com.apollocurrency.smc.util.HexUtils; -import com.google.common.base.Strings; import lombok.extern.slf4j.Slf4j; import jakarta.enterprise.context.RequestScoped; @@ -86,11 +80,8 @@ import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.SecurityContext; import java.math.BigInteger; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.Map; -import java.util.function.Function; import java.util.stream.Collectors; import static com.apollocurrency.smc.util.HexUtils.toHex; @@ -113,7 +104,6 @@ class SmcApiServiceImpl implements SmcApiService { private final SmcBlockchainIntegratorFactory integratorFactory; private final SmcConfig smcConfig; private final int maxAPIRecords; - private final SmartMethodMapper methodMapper; private final CallMethodResultMapper methodResultMapper; private final MethodSpecMapper methodSpecMapper; private final ElGamalEncryptor elGamal; @@ -130,7 +120,6 @@ public SmcApiServiceImpl(BlockchainConfig blockchainConfig, TransactionCreator transactionCreator, SmcBlockchainIntegratorFactory integratorFactory, SmcConfig smcConfig, - SmartMethodMapper methodMapper, CallMethodResultMapper methodResultMapper, MethodSpecMapper methodSpecMapper, @Property(name = "apl.maxAPIRecords", defaultValue = "100") int maxAPIRecords, @@ -145,7 +134,6 @@ public SmcApiServiceImpl(BlockchainConfig blockchainConfig, this.txBContext = TxBContext.newInstance(blockchainConfig.getChain()); this.integratorFactory = integratorFactory; this.smcConfig = smcConfig; - this.methodMapper = methodMapper; this.methodResultMapper = methodResultMapper; this.methodSpecMapper = methodSpecMapper; this.maxAPIRecords = maxAPIRecords; @@ -160,7 +148,7 @@ public Response createPublishContractTxMultisig(PublishContractReq body, Securit if (transaction == null) { return builder.build(); } - TransactionArrayResp response = new TransactionArrayResp(); + TransactionByteArrayResp response = new TransactionByteArrayResp(); Result signedTxBytes = PayloadResult.createLittleEndianByteArrayResult(); txBContext.createSerializer(transaction.getVersion()).serialize(transaction, signedTxBytes); @@ -176,7 +164,7 @@ public Response createPublishContractTx(PublishContractReq body, SecurityContext if (transaction == null) { return builder.build(); } - TransactionArrayResp response = new TransactionArrayResp(); + TransactionByteArrayResp response = new TransactionByteArrayResp(); Result signedTxBytes = PayloadResult.createLittleEndianByteArrayResult(); txBContext.createSerializer(transaction.getVersion()).serialize(transaction, signedTxBytes); @@ -325,19 +313,14 @@ private Transaction validateAndCreatePublishContractTransaction(PublishContractR masterSecret = elGamal.elGamalDecrypt(body.getSender()); var senderId = AccountService.getId(Crypto.getPublicKey(Crypto.getKeySeed(secretPhrase))); senderAccount = accountService.getAccount(senderId); - if (senderAccount == null) { - response.error(ApiErrors.INCORRECT_VALUE, "sender", body.getSender()); - return null; - } - senderAccountId = senderAccount.getId(); } else { senderAccount = getAccountByAddress(body.getSender()); - if (senderAccount == null) { - response.error(ApiErrors.INCORRECT_VALUE, "sender", body.getSender()); - return null; - } - senderAccountId = senderAccount.getId(); } + if (senderAccount == null) { + response.error(ApiErrors.INCORRECT_VALUE, "sender", body.getSender()); + return null; + } + senderAccountId = senderAccount.getId(); byte[] publicKey; if (senderAccount.getPublicKey() == null) { @@ -346,19 +329,19 @@ private Transaction validateAndCreatePublishContractTransaction(PublishContractR publicKey = senderAccount.getPublicKey().getPublicKey(); } - if (Strings.isNullOrEmpty(body.getName())) { + if (StringUtils.isBlank(body.getName())) { response.error(ApiErrors.INCORRECT_VALUE, "contract_name", body.getName()); return null; } - if (Strings.isNullOrEmpty(body.getSource())) { + if (StringUtils.isBlank(body.getSource())) { response.error(ApiErrors.INCORRECT_VALUE, "contract_source", body.getSource()); return null; } - if (Strings.isNullOrEmpty(body.getFuelPrice())) { + if (StringUtils.isBlank(body.getFuelPrice())) { response.error(ApiErrors.INCORRECT_VALUE, "fuel_price", body.getFuelPrice()); return null; } - if (Strings.isNullOrEmpty(body.getFuelLimit())) { + if (StringUtils.isBlank(body.getFuelLimit())) { response.error(ApiErrors.INCORRECT_VALUE, "fuel_limit", body.getFuelLimit()); return null; } @@ -447,7 +430,7 @@ public Response callViewMethod(CallViewMethodReq body, SecurityContext securityC } var executionLog = new ExecutionLog(); - var result = processAllViewMethods(new AplAddress(contractId), body.getMembers(), executionLog); + var result = contractService.processAllViewMethods(new AplAddress(contractId), body.getMembers(), executionLog); if (executionLog.hasError()) { return builder.detailedError(ApiErrors.CONTRACT_READ_METHOD_ERROR, executionLog.toJsonString(), executionLog.getLatestCause()).build(); @@ -466,113 +449,16 @@ public Response getSmcSpecificationByAddress(String addressStr, SecurityContext return builder.error(ApiErrors.CONTRACT_NOT_FOUND, addressStr).build(); } var address = new AplAddress(accountId); - var response = new ContractSpecResponse(); - var aplContractSpec = contractRepository.loadAsrModuleSpec(address); - var contractSpec = aplContractSpec.getContractSpec(); - var notViewMethods = contractSpec.getMembers().stream() - .filter(member -> member.getType() == ContractSpec.MemberType.FUNCTION && member.getStateMutability() != ContractSpec.StateMutability.VIEW - && (member.getVisibility() == ContractSpec.Visibility.PUBLIC - || member.getVisibility() == ContractSpec.Visibility.EXTERNAL)) - .collect(Collectors.toList()); - var viewMethods = contractSpec.getMembers().stream() - .filter(member -> member.getType() == ContractSpec.MemberType.FUNCTION && member.getStateMutability() == ContractSpec.StateMutability.VIEW - && (member.getVisibility() == ContractSpec.Visibility.PUBLIC - || member.getVisibility() == ContractSpec.Visibility.EXTERNAL)) - .collect(Collectors.toList()); - var events = contractSpec.getMembers().stream() - .filter(member -> member.getType() == ContractSpec.MemberType.EVENT) - .collect(Collectors.toList()); - - List methodsToCall = new ArrayList<>(); + var response = contractService.loadContractSpecification(address); - viewMethods.forEach(member -> { - ContractMethod m = new ContractMethod(); - m.setFunction(member.getName()); - if (member.getInputs() == null || member.getInputs().isEmpty()) { - methodsToCall.add(m); - } - }); - - var executionLog = new ExecutionLog(); - var result = processAllViewMethods(address, methodsToCall, executionLog); - if (executionLog.hasError()) { - return builder.detailedError(ApiErrors.CONTRACT_READ_METHOD_ERROR, executionLog.toJsonString(), executionLog.getLatestCause()).build(); + if (response == null) { + return builder.error(ApiErrors.CONTRACT_READ_METHOD_ERROR).build(); } - result.add(ResultValue.builder() - .method(JSLibraryProvider.CONTRACT_OVERVIEW_ITEM.getName()) - .output(List.of(address.getHex())) - .build()); - - var resultMap = toMap(result); - var overviewProperties = new ArrayList<>( - createOverview(contractSpec, resultMap) - ); - response.setOverview(overviewProperties); - - response.getMembers().addAll(methodSpecMapper.convert(viewMethods)); - matchResults(response.getMembers(), resultMap); - response.getMembers().addAll(methodSpecMapper.convert(notViewMethods)); - response.getMembers().addAll(methodSpecMapper.convert(events)); - - response.setInheritedContracts( - contractService.getInheritedAsrModules(contractSpec.getType() - , aplContractSpec.getLanguage() - , aplContractSpec.getVersion()) - ); return builder.bind(response).build(); } - private Map toMap(List result) { - return result.stream().collect(Collectors.toMap(ResultValue::getMethod, Function.identity())); - } - - private void matchResults(List methods, Map resultMap) { - methods.forEach(methodSpec -> { - if (methodSpec.getInputs() == null || methodSpec.getInputs().isEmpty()) { - var res = resultMap.getOrDefault(methodSpec.getName(), ResultValue.UNDEFINED_RESULT); - methodSpec.setValue(res.getStringResult()); - methodSpec.setSignature(res.getSignature()); - } - }); - } - - private List createOverview(ContractSpec contractSpec, Map resultMap) { - //TODO: move Overview info to ContractSpec - var properties = contractSpec.getOverview(); - - var propertySpec = new ArrayList(); - - properties.forEach(item -> { - var prop = new PropertySpec(); - prop.setName(item.getName()); - prop.setType(item.getType()); - prop.setValue(resultMap.getOrDefault(item.getName(), ResultValue.UNDEFINED_RESULT).getStringResult()); - propertySpec.add(prop); - }); - return propertySpec; - } - - private List processAllViewMethods(Address contractAddress, List members, ExecutionLog executionLog) { - SmartContract smartContract = contractRepository.loadContract( - contractAddress, - contractAddress, - new ContractFuel(contractAddress, BigInteger.ZERO, BigInteger.ONE) - ); - var methods = methodMapper.convert(members); - var context = smcConfig.asViewContext(accountService.getBlockchainHeight(), - smartContract, - integratorFactory.createReadonlyProcessor() - ); - - SmcContractTxBatchProcessor processor = new CallViewMethodTxProcessor(smartContract, methods, context); - - var rc = processor.batchProcess(); - executionLog.join(processor.getExecutionLog()); - return rc; - } - @Override public Response createCallContractMethodTx(CallContractMethodReq body, SecurityContext securityContext) throws NotFoundException { ResponseBuilderV2 builder = ResponseBuilderV2.startTiming(); @@ -580,7 +466,7 @@ public Response createCallContractMethodTx(CallContractMethodReq body, SecurityC if (transaction == null) { return builder.build(); } - TransactionArrayResp response = new TransactionArrayResp(); + TransactionByteArrayResp response = new TransactionByteArrayResp(); Result signedTxBytes = PayloadResult.createLittleEndianByteArrayResult(); txBContext.createSerializer(transaction.getVersion()).serialize(transaction, signedTxBytes); response.setTx(Convert.toHexString(signedTxBytes.array())); @@ -664,15 +550,15 @@ private Transaction validateAndCreateCallContractMethodTransaction(CallContractM publicKey = senderAccount.getPublicKey().getPublicKey(); } - if (Strings.isNullOrEmpty(body.getName())) { + if (StringUtils.isBlank(body.getName())) { response.error(ApiErrors.INCORRECT_VALUE, "method_name", body.getName()); return null; } - if (Strings.isNullOrEmpty(body.getFuelPrice())) { + if (StringUtils.isBlank(body.getFuelPrice())) { response.error(ApiErrors.INCORRECT_VALUE, "fuel_price", body.getFuelPrice()); return null; } - if (Strings.isNullOrEmpty(body.getFuelLimit())) { + if (StringUtils.isBlank(body.getFuelLimit())) { response.error(ApiErrors.INCORRECT_VALUE, "fuel_limit", body.getFuelLimit()); return null; } @@ -747,25 +633,18 @@ public Response getSmcByOwnerAccount(String accountStr, Integer firstIndex, Inte if (accountId == null) { return builder.error(ApiErrors.CONTRACTS_NOT_FOUND).build(); } - var publisher = new AplAddress(accountId); FirstLastIndexBeanParam indexBeanParam = new FirstLastIndexBeanParam(firstIndex, lastIndex); indexBeanParam.adjustIndexes(maxAPIRecords); ContractListResponse response = new ContractListResponse(); - List contracts = contractRepository.loadContractsByFilter( - null, - null, - publisher, - null, - null, - null, - null, - -1, - indexBeanParam.getFirstIndex(), - indexBeanParam.getLastIndex() - ); + var query = ContractQuery.builder() + .owner(accountId) + .height(-1) + .paging(indexBeanParam.range()) + .build(); + List contracts = contractRepository.loadContractsByFilter(query); response.setContracts(contracts); response.setContracts(contracts); @@ -788,7 +667,7 @@ public Response getSmcEvents(String address, ContractEventsRequest body, Securit } var filterStr = body.getFilter(); Term filter; - if (Strings.isNullOrEmpty(filterStr)) { + if (StringUtils.isBlank(filterStr)) { filter = new TrueTerm(); } else { try { @@ -797,41 +676,17 @@ public Response getSmcEvents(String address, ContractEventsRequest body, Securit return ResponseBuilderV2.apiError(ApiErrors.CONTRACT_EVENT_FILTER_ERROR, e.getMessage()).build(); } } - int[] blockBoundaries = boundaries(body.getFromBlock(), body.getToBlock()); - int[] paging = boundaries(body.getFrom(), body.getTo()); - String order; - if (!Strings.isNullOrEmpty(body.getOrder())) { - order = body.getOrder(); - if (!"ASC".equals(order) && !"DESC".equals(order)) { - order = "ASC"; - } - } else - order = "ASC"; - + var blockRange = new PositiveRange(body.getFromBlock(), body.getToBlock()); + var paging = new PositiveRange(body.getFrom(), body.getTo()); + var order = Sort.of(body.getOrder()); ContractEventsResponse response = new ContractEventsResponse(); - var rc = eventService.getEventsByFilter(contractId, eventName, - filter, - blockBoundaries[0], blockBoundaries[1], - paging[0], paging[1], - order - ); + var rc = eventService.getEventsByFilter(contractId, eventName, filter, blockRange, paging, order); response.setEvents(rc); return builder.bind(response).build(); } - private int[] boundaries(Integer from, Integer to) { - int[] rc = new int[]{0, -1}; - if (from != null) { - rc[0] = from; - } - if (to != null) { - rc[1] = to; - } - return rc; - } - @Override public Response getSmcByAddress(String addressStr, SecurityContext securityContext) throws NotFoundException { ResponseBuilderV2 builder = ResponseBuilderV2.startTiming(); @@ -839,10 +694,10 @@ public Response getSmcByAddress(String addressStr, SecurityContext securityConte if (contractId == null || !contractRepository.isContractExist(new AplAddress(contractId))) { return builder.error(ApiErrors.CONTRACT_NOT_FOUND, addressStr).build(); } - var address = new AplAddress(contractId); + ContractListResponse response = new ContractListResponse(); - var contracts = contractRepository.getContractDetailsByAddress(address); + var contracts = contractRepository.getContractDetailsByAddress(contractId); response.setContracts(contracts); @@ -855,16 +710,15 @@ public Response getSmcList(String addressStr, String publisherStr, String name, FirstLastIndexBeanParam indexBeanParam = new FirstLastIndexBeanParam(firstIndex, lastIndex); indexBeanParam.adjustIndexes(maxAPIRecords); - AplAddress address = null; - AplAddress publisher = null; - AplAddress transaction = null; + Long address = null; + Long publisher = null; + Long transaction = null; - ContractStatus smcStatus = null; if (status != null) { try { - smcStatus = ContractStatus.valueOf(status); + ContractStatus.valueOf(status); } catch (IllegalArgumentException e) { - return ResponseBuilderV2.apiError(ApiErrors.INCORRECT_VALUE, "status", addressStr).build(); + return ResponseBuilderV2.apiError(ApiErrors.INCORRECT_VALUE, "status", status).build(); } } @@ -873,39 +727,33 @@ public Response getSmcList(String addressStr, String publisherStr, String name, if (account == null) { return builder.error(ApiErrors.CONTRACT_NOT_FOUND, addressStr).build(); } - address = new AplAddress(account.getId()); - if (!contractRepository.isContractExist(address)) { + if (!contractRepository.isContractExist(new AplAddress(account.getId()))) { return ResponseBuilderV2.apiError(ApiErrors.CONTRACT_NOT_FOUND, addressStr).build(); } + address = account.getId(); } if (publisherStr != null) { - var publisherId = getIdByAddress(publisherStr); - if (publisherId != null) { - publisher = new AplAddress(publisherId); - } + publisher = getIdByAddress(publisherStr); } if (transactionAddr != null) { - var transactionId = getIdByAddress(transactionAddr); - if (transactionId != null) { - transaction = new AplAddress(transactionId); - } + transaction = getIdByAddress(transactionAddr); } ContractListResponse response = new ContractListResponse(); - List contracts = contractRepository.loadContractsByFilter( - address, - transaction, - publisher, - name, - baseContract, - timestamp, - smcStatus, - -1, - indexBeanParam.getFirstIndex(), - indexBeanParam.getLastIndex() - ); + var query = ContractQuery.builder() + .address(address) + .transaction(transaction) + .owner(publisher) + .name(name) + .baseContract(baseContract) + .status(status) + .height(-1) + .paging(indexBeanParam.range()) + .build(); + + List contracts = contractRepository.loadContractsByFilter(query); response.setContracts(contracts); diff --git a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/service/blockchain/Blockchain.java b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/service/blockchain/Blockchain.java index 0ba5c145c5..e054f5bef4 100644 --- a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/service/blockchain/Blockchain.java +++ b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/service/blockchain/Blockchain.java @@ -22,7 +22,7 @@ import com.apollocurrency.aplwallet.apl.core.model.Block; import com.apollocurrency.aplwallet.apl.core.model.EcBlockData; -import com.apollocurrency.aplwallet.apl.core.model.Sort; +import com.apollocurrency.aplwallet.apl.util.api.Sort; import com.apollocurrency.aplwallet.apl.core.model.Transaction; import com.apollocurrency.aplwallet.apl.core.model.TransactionDbInfo; import com.apollocurrency.aplwallet.apl.core.transaction.PrunableTransaction; diff --git a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/service/blockchain/BlockchainImpl.java b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/service/blockchain/BlockchainImpl.java index 6b8056ff55..f3ff80d9c7 100644 --- a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/service/blockchain/BlockchainImpl.java +++ b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/service/blockchain/BlockchainImpl.java @@ -34,7 +34,7 @@ import com.apollocurrency.aplwallet.apl.core.entity.state.account.PublicKey; import com.apollocurrency.aplwallet.apl.core.model.Block; import com.apollocurrency.aplwallet.apl.core.model.EcBlockData; -import com.apollocurrency.aplwallet.apl.core.model.Sort; +import com.apollocurrency.aplwallet.apl.util.api.Sort; import com.apollocurrency.aplwallet.apl.core.model.Transaction; import com.apollocurrency.aplwallet.apl.core.model.TransactionDbInfo; import com.apollocurrency.aplwallet.apl.core.service.appdata.TimeService; diff --git a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/service/blockchain/TransactionService.java b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/service/blockchain/TransactionService.java index 7f64f5b18f..40ff1a4d99 100644 --- a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/service/blockchain/TransactionService.java +++ b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/service/blockchain/TransactionService.java @@ -6,7 +6,7 @@ import com.apollocurrency.aplwallet.api.v2.model.TxReceipt; import com.apollocurrency.aplwallet.apl.core.entity.appdata.ChatInfo; -import com.apollocurrency.aplwallet.apl.core.model.Sort; +import com.apollocurrency.aplwallet.apl.util.api.Sort; import com.apollocurrency.aplwallet.apl.core.model.Transaction; import com.apollocurrency.aplwallet.apl.core.model.TransactionDbInfo; import com.apollocurrency.aplwallet.apl.core.transaction.PrunableTransaction; diff --git a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/service/blockchain/TransactionServiceImpl.java b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/service/blockchain/TransactionServiceImpl.java index 06f5c085e4..a68324de12 100644 --- a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/service/blockchain/TransactionServiceImpl.java +++ b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/service/blockchain/TransactionServiceImpl.java @@ -12,7 +12,7 @@ import com.apollocurrency.aplwallet.apl.core.db.DatabaseManager; import com.apollocurrency.aplwallet.apl.core.entity.appdata.ChatInfo; import com.apollocurrency.aplwallet.apl.core.entity.blockchain.TransactionEntity; -import com.apollocurrency.aplwallet.apl.core.model.Sort; +import com.apollocurrency.aplwallet.apl.util.api.Sort; import com.apollocurrency.aplwallet.apl.core.model.Transaction; import com.apollocurrency.aplwallet.apl.core.model.TransactionDbInfo; import com.apollocurrency.aplwallet.apl.core.service.appdata.TimeService; diff --git a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/service/state/smc/ContractQuery.java b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/service/state/smc/ContractQuery.java new file mode 100644 index 0000000000..549c6d3d82 --- /dev/null +++ b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/service/state/smc/ContractQuery.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2018-2022. Apollo Foundation. + */ + +package com.apollocurrency.aplwallet.apl.core.service.state.smc; + +import com.apollocurrency.aplwallet.apl.util.Convert2; +import com.apollocurrency.aplwallet.apl.util.StringUtils; +import com.apollocurrency.aplwallet.apl.util.api.PositiveRange; +import com.apollocurrency.aplwallet.apl.util.api.Sort; +import com.apollocurrency.aplwallet.apl.util.db.DbUtils; +import lombok.Builder; +import lombok.Data; + +import java.sql.PreparedStatement; +import java.sql.SQLException; + +/** + * @author andrew.zinchenko@gmail.com + */ +@Data +@Builder +public class ContractQuery { + Long address; + Long transaction; + Long owner; + String name; + String baseContract; + Long timestamp; + String status; + int height; + Sort order; + PositiveRange paging; + + public String toWhereClause(String wherePrefix) { + return toWhereClause(new StringBuilder(wherePrefix)).toString(); + } + + public StringBuilder toWhereClause(StringBuilder sql) { + sql.append(" AND sc.height <= ? "); + if (address != null) { + sql.append(" AND sc.address = ? "); + } + if (transaction != null) { + sql.append(" AND sc.transaction_id = ? "); + } + if (owner != null) { + sql.append(" AND sc.owner = ? "); + } + if (StringUtils.isNotBlank(name)) { + sql.append(" AND sc.name LIKE ? "); + } + if (StringUtils.isNotBlank(baseContract)) { + sql.append(" AND sc.base_contract LIKE ? "); + } + if (timestamp != null) { + sql.append(" AND sc.block_timestamp >= ? "); + } + if (status != null) { + sql.append(" AND ss.status = ? "); + } + return sql; + } + + public void setPreparedStatementParameters(PreparedStatement stmt) throws SQLException { + setPreparedStatementParameters(stmt, true); + } + + public void setPreparedStatementParameters(PreparedStatement stmt, boolean addLimits) throws SQLException { + int i = 0; + stmt.setInt(++i, height); + if (address != null) { + stmt.setLong(++i, address); + } + if (transaction != null) { + stmt.setLong(++i, transaction); + } + if (owner != null) { + stmt.setLong(++i, owner); + } + if (StringUtils.isNotBlank(name)) { + stmt.setString(++i, DbUtils.escapeLikePattern(name)); + } + if (StringUtils.isNotBlank(baseContract)) { + stmt.setString(++i, DbUtils.escapeLikePattern(baseContract)); + } + if (timestamp != null) { + stmt.setInt(++i, Convert2.toEpochTime(timestamp)); + } + if (status != null) { + stmt.setString(++i, status); + } + if (addLimits) { + DbUtils.setLimits(++i, stmt, paging); + } + } +} diff --git a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/service/state/smc/SmcContractRepository.java b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/service/state/smc/SmcContractRepository.java index 16c7dd0620..55511aee36 100644 --- a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/service/state/smc/SmcContractRepository.java +++ b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/service/state/smc/SmcContractRepository.java @@ -5,8 +5,6 @@ package com.apollocurrency.aplwallet.apl.core.service.state.smc; import com.apollocurrency.aplwallet.api.v2.model.ContractDetails; -import com.apollocurrency.aplwallet.apl.smc.model.AplContractSpec; -import com.apollocurrency.smc.contract.ContractStatus; import com.apollocurrency.smc.contract.SmartContract; import com.apollocurrency.smc.contract.fuel.Fuel; import com.apollocurrency.smc.data.type.Address; @@ -42,14 +40,6 @@ public interface SmcContractRepository { SmartContract loadContract(Address address); - /** - * Load the contract specification by given contract address - * - * @param address the contract address - * @return the contract specification by given contract address - */ - AplContractSpec loadAsrModuleSpec(Address address); - /** * Checks if contract already exists * @@ -67,16 +57,10 @@ public interface SmcContractRepository { /** * Returns the list of contracts by filter * - * @param address given address - * @param transaction given transaction - * @param owner given owner - * @param name given contract name - * @param height the blockchain height - * @param from the first index - * @param to the last index + * @param query given filter * @return the list of contracts */ - List loadContractsByFilter(Address address, Address transaction, Address owner, String name, String baseContract, Long timestamp, ContractStatus status, int height, int from, int to); + List loadContractsByFilter(ContractQuery query); /** * Returns the details information about contract given address @@ -84,6 +68,6 @@ public interface SmcContractRepository { * @param address the given contract address * @return the details information about contract */ - List getContractDetailsByAddress(Address address); + List getContractDetailsByAddress(long address); } diff --git a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/service/state/smc/SmcContractService.java b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/service/state/smc/SmcContractService.java index 5f60c1d82e..02918caaaa 100644 --- a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/service/state/smc/SmcContractService.java +++ b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/service/state/smc/SmcContractService.java @@ -4,7 +4,12 @@ package com.apollocurrency.aplwallet.apl.core.service.state.smc; +import com.apollocurrency.aplwallet.api.v2.model.ContractMethod; +import com.apollocurrency.aplwallet.api.v2.model.ContractSpecResponse; import com.apollocurrency.aplwallet.apl.smc.model.AplContractSpec; +import com.apollocurrency.smc.contract.vm.ExecutionLog; +import com.apollocurrency.smc.contract.vm.ResultValue; +import com.apollocurrency.smc.data.type.Address; import com.apollocurrency.smc.polyglot.Version; import java.util.List; @@ -24,6 +29,8 @@ public interface SmcContractService { */ List getAsrModules(String language, Version version, String type); + List processAllViewMethods(Address contractAddress, List members, ExecutionLog executionLog); + /** * Returns the list of inherited ASR modules. * @@ -44,4 +51,20 @@ public interface SmcContractService { */ AplContractSpec loadAsrModuleSpec(String asrModuleName, String language, Version version); + /** + * Load the contract specification of the specified contract address. + * + * @param contractAddress the given contract + * @return the contract specification + */ + ContractSpecResponse loadContractSpecification(Address contractAddress); + + /** + * Loads the contract specification by the given address + * or returns null if the given address doesn't correspond the smart contract + * + * @param address given contract address + * @return loaded smart contract specification or throw {@link com.apollocurrency.smc.contract.AddressNotFoundException} + */ + AplContractSpec loadAsrModuleSpec(Address address); } diff --git a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/service/state/smc/impl/SmcContractEventServiceImpl.java b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/service/state/smc/impl/SmcContractEventServiceImpl.java index 72e4496868..87222d7014 100644 --- a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/service/state/smc/impl/SmcContractEventServiceImpl.java +++ b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/service/state/smc/impl/SmcContractEventServiceImpl.java @@ -14,6 +14,8 @@ import com.apollocurrency.aplwallet.apl.smc.events.SmcEventType; import com.apollocurrency.aplwallet.apl.smc.model.AplContractEvent; import com.apollocurrency.aplwallet.apl.smc.service.SmcContractEventService; +import com.apollocurrency.aplwallet.apl.util.api.PositiveRange; +import com.apollocurrency.aplwallet.apl.util.api.Sort; import com.apollocurrency.aplwallet.apl.util.cdi.Transactional; import com.apollocurrency.smc.blockchain.crypt.HashSumProvider; import com.apollocurrency.smc.contract.vm.event.EventArguments; @@ -104,25 +106,21 @@ public void fireCdiEvent(AplContractEvent event) { } @Override - public List getEventsByFilter(Long contract, String eventName, Term filter, int fromBlock, int toBlock, int from, int to, String order) { + public List getEventsByFilter(Long contract, String eventName, Term predicate, PositiveRange range, PositiveRange paging, Sort order) { var height = blockchain.getHeight(); - if (fromBlock < 0) { - fromBlock = height; - } - if (toBlock < 0) { - toBlock = height; - } - log.trace("getEventsByFilter: height={} contract={} eventName={} fromBlock={} toBlock={} from={} to={} order={}" - , height, contract, eventName, fromBlock, toBlock, from, to, order); - var result = contractEventLogTable.getEventsByFilter(contract, eventName, fromBlock, toBlock, from, to, order); + int fromBlock = range.adjustBottomBoundary(height); + int toBlock = range.adjustTopBoundary(height); + var blockRange = new PositiveRange(fromBlock, toBlock); + log.trace("getEventsByFilter: height={} contract={} eventName={} blockRange={} paging={} order={}", height, contract, eventName, blockRange, paging, order); + var result = contractEventLogTable.getEventsByFilter(contract, eventName, blockRange, paging, order); log.trace("getEventsByFilter: resultSet.size={}", result.size()); //filter result set var deserializer = jsonMapper.deserializer(); var rc = result.stream().filter(event -> { var args = deserializer.deserialize(event.getState(), EventArguments.class); - return filter.test(args.getMap()); + return predicate.test(args.getMap()); }).collect(Collectors.toList()); - log.trace("getEventsByFilter: apply filter={} result.size={}", filter, rc.size()); + log.trace("getEventsByFilter: apply filter={} result.size={}", predicate, rc.size()); return rc; } } diff --git a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/service/state/smc/impl/SmcContractRepositoryImpl.java b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/service/state/smc/impl/SmcContractRepositoryImpl.java index 6c8df1a0f9..e38ce8408d 100644 --- a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/service/state/smc/impl/SmcContractRepositoryImpl.java +++ b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/service/state/smc/impl/SmcContractRepositoryImpl.java @@ -13,11 +13,10 @@ import com.apollocurrency.aplwallet.apl.core.entity.state.smc.SmcContractEntity; import com.apollocurrency.aplwallet.apl.core.entity.state.smc.SmcContractStateEntity; import com.apollocurrency.aplwallet.apl.core.service.blockchain.Blockchain; +import com.apollocurrency.aplwallet.apl.core.service.state.smc.ContractQuery; import com.apollocurrency.aplwallet.apl.core.service.state.smc.SmcContractRepository; -import com.apollocurrency.aplwallet.apl.core.service.state.smc.SmcContractService; import com.apollocurrency.aplwallet.apl.smc.model.AplAddress; -import com.apollocurrency.aplwallet.apl.smc.model.AplContractSpec; -import com.apollocurrency.aplwallet.apl.util.Convert2; +import com.apollocurrency.aplwallet.apl.util.api.PositiveRange; import com.apollocurrency.aplwallet.apl.util.cdi.Transactional; import com.apollocurrency.smc.contract.AddressNotFoundException; import com.apollocurrency.smc.contract.ContractSource; @@ -48,17 +47,15 @@ public class SmcContractRepositoryImpl implements SmcContractRepository { private final ContractModelToStateEntityConverter contractModelToStateConverter; protected final SmcConfig smcConfig; - private final SmcContractService contractService; @Inject - public SmcContractRepositoryImpl(Blockchain blockchain, SmcContractTable smcContractTable, SmcContractStateTable smcContractStateTable, ContractModelToEntityConverter contractModelToEntityConverter, ContractModelToStateEntityConverter contractModelToStateConverter, SmcConfig smcConfig, SmcContractService contractService) { + public SmcContractRepositoryImpl(Blockchain blockchain, SmcContractTable smcContractTable, SmcContractStateTable smcContractStateTable, ContractModelToEntityConverter contractModelToEntityConverter, ContractModelToStateEntityConverter contractModelToStateConverter, SmcConfig smcConfig) { this.blockchain = blockchain; this.smcContractTable = smcContractTable; this.smcContractStateTable = smcContractStateTable; this.contractModelToEntityConverter = contractModelToEntityConverter; this.contractModelToStateConverter = contractModelToStateConverter; this.smcConfig = smcConfig; - this.contractService = contractService; } @Override @@ -114,19 +111,6 @@ public SmartContract loadContract(Address address) { return loadContract(address, null, null, new ContractFuel(address, 0, 0)); } - /** - * Load the contract specification by the given address or null if the given address doesn't correspond the smart contract - * - * @param address given contract address - * @return loaded smart contract specification or throw {@link com.apollocurrency.smc.contract.AddressNotFoundException} - */ - @Override - @Transactional(readOnly = true) - public AplContractSpec loadAsrModuleSpec(Address address) { - SmcContractEntity smcEntity = loadContractEntity(address); - log.trace("Loaded specification for contract name={} type={}", smcEntity.getContractName(), smcEntity.getBaseContract()); - return contractService.loadAsrModuleSpec(smcEntity.getBaseContract(), smcEntity.getLanguageName(), SimpleVersion.fromString(smcEntity.getLanguageVersion())); - } @Override @Transactional(readOnly = true) @@ -168,19 +152,19 @@ public void saveSerializedContract(SmartContract contract, String serializedObje } @Override - public List loadContractsByFilter(Address address, Address transaction, Address owner, String name, String baseContract, Long timestamp, ContractStatus status, int height, int from, int to) { - Long contractId = address != null ? new AplAddress(address).getLongId() : null; - Long txId = transaction != null ? new AplAddress(transaction).getLongId() : null; - Long ownerId = owner != null ? new AplAddress(owner).getLongId() : null; - Integer blockTimestamp = timestamp == null ? null : Convert2.toEpochTime(timestamp); - List result = smcContractTable.getContractsByFilter(contractId, txId, ownerId, name, baseContract, blockTimestamp, status != null ? status.name() : null, height < 0 ? blockchain.getHeight() : height, from, to); + public List loadContractsByFilter(ContractQuery query) { + List result = smcContractTable.getContractsByFilter(query); return result; } @Override - public List getContractDetailsByAddress(Address address) { - var result = loadContractsByFilter(address, null, null, null, null, null, null, -1, 0, 1); - return result; + public List getContractDetailsByAddress(long address) { + var query = ContractQuery.builder() + .address(address) + .height(-1) + .paging(new PositiveRange(0, 1)) + .build(); + return loadContractsByFilter(query); } public static SmartContract convert(SmcContractEntity entity, SmcContractStateEntity stateEntity, Address originator, Address caller, Fuel contractFuel) { diff --git a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/service/state/smc/impl/SmcContractServiceImpl.java b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/service/state/smc/impl/SmcContractServiceImpl.java index 2abf92594a..5eb35834b0 100644 --- a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/service/state/smc/impl/SmcContractServiceImpl.java +++ b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/service/state/smc/impl/SmcContractServiceImpl.java @@ -4,20 +4,42 @@ package com.apollocurrency.aplwallet.apl.core.service.state.smc.impl; +import com.apollocurrency.aplwallet.api.v2.model.ContractMethod; +import com.apollocurrency.aplwallet.api.v2.model.ContractSpecResponse; +import com.apollocurrency.aplwallet.api.v2.model.MemberSpec; +import com.apollocurrency.aplwallet.api.v2.model.PropertySpec; import com.apollocurrency.aplwallet.apl.core.config.SmcConfig; import com.apollocurrency.aplwallet.apl.core.exception.AplCoreContractViolationException; +import com.apollocurrency.aplwallet.apl.core.rest.v2.converter.MethodSpecMapper; +import com.apollocurrency.aplwallet.apl.core.rest.v2.converter.SmartMethodMapper; +import com.apollocurrency.aplwallet.apl.core.service.state.account.AccountService; +import com.apollocurrency.aplwallet.apl.core.service.state.smc.SmcContractRepository; import com.apollocurrency.aplwallet.apl.core.service.state.smc.SmcContractService; import com.apollocurrency.aplwallet.apl.smc.model.AplContractSpec; +import com.apollocurrency.aplwallet.apl.smc.service.SmcContractTxBatchProcessor; +import com.apollocurrency.aplwallet.apl.smc.service.tx.CallViewMethodTxProcessor; +import com.apollocurrency.smc.contract.SmartContract; +import com.apollocurrency.smc.contract.fuel.ContractFuel; +import com.apollocurrency.smc.contract.vm.ExecutionLog; +import com.apollocurrency.smc.contract.vm.ResultValue; +import com.apollocurrency.smc.data.type.Address; import com.apollocurrency.smc.polyglot.Version; +import com.apollocurrency.smc.polyglot.language.ContractSpec; import com.apollocurrency.smc.polyglot.language.LanguageContext; +import com.apollocurrency.smc.polyglot.language.lib.JSLibraryProvider; import com.apollocurrency.smc.polyglot.language.lib.LibraryProvider; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import jakarta.inject.Inject; import jakarta.inject.Singleton; +import java.math.BigInteger; import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; /** * @author andrew.zinchenko@gmail.com @@ -27,12 +49,33 @@ public class SmcContractServiceImpl implements SmcContractService { protected final SmcConfig smcConfig; private final LibraryProvider libraryProvider; + private final SmcContractRepository contractRepository; + private final SmcBlockchainIntegratorFactory integratorFactory; + private final AccountService accountService; + private final SmartMethodMapper methodMapper; + private final MethodSpecMapper methodSpecMapper; + private SmcContractTxBatchProcessor processor; // field is used for unit testing + @Inject - public SmcContractServiceImpl(SmcConfig smcConfig) { + public SmcContractServiceImpl(SmcConfig smcConfig, + AccountService accountService, + SmcContractRepository contractRepository, + SmcBlockchainIntegratorFactory integratorFactory, + SmartMethodMapper methodMapper, + MethodSpecMapper methodSpecMapper, + Optional processor) { this.smcConfig = smcConfig; final LanguageContext languageContext = smcConfig.createLanguageContext(); this.libraryProvider = languageContext.getLibraryProvider(); + this.contractRepository = contractRepository; + this.integratorFactory = integratorFactory; + this.accountService = accountService; + this.methodMapper = methodMapper; + this.methodSpecMapper = methodSpecMapper; + processor.ifPresent( + value -> this.processor = value + ); } @SneakyThrows @@ -50,6 +93,124 @@ public AplContractSpec loadAsrModuleSpec(String asrModuleName, String language, .build(); } + @Override + public AplContractSpec loadAsrModuleSpec(Address address) { + var smcEntity = contractRepository.loadContract(address); + log.trace("Load specification for contract {} name={} type={}", address.getHex(), smcEntity.getName(), smcEntity.getBaseContract()); + return loadAsrModuleSpec(smcEntity.getBaseContract(), smcEntity.getLanguageName(), smcEntity.getLanguageVersion()); + } + + @Override + public ContractSpecResponse loadContractSpecification(Address contractAddress) { + var response = new ContractSpecResponse(); + var aplContractSpec = loadAsrModuleSpec(contractAddress); + var contractSpec = aplContractSpec.getContractSpec(); + var notViewMethods = contractSpec.getMembers().stream() + .filter(member -> member.getType() == ContractSpec.MemberType.FUNCTION && member.getStateMutability() != ContractSpec.StateMutability.VIEW + && (member.getVisibility() == ContractSpec.Visibility.PUBLIC + || member.getVisibility() == ContractSpec.Visibility.EXTERNAL)) + .collect(Collectors.toList()); + var viewMethods = contractSpec.getMembers().stream() + .filter(member -> member.getType() == ContractSpec.MemberType.FUNCTION && member.getStateMutability() == ContractSpec.StateMutability.VIEW + && (member.getVisibility() == ContractSpec.Visibility.PUBLIC + || member.getVisibility() == ContractSpec.Visibility.EXTERNAL)) + .collect(Collectors.toList()); + + var events = contractSpec.getMembers().stream() + .filter(member -> member.getType() == ContractSpec.MemberType.EVENT) + .collect(Collectors.toList()); + + List methodsToCall = new ArrayList<>(); + + viewMethods.forEach(member -> { + ContractMethod m = new ContractMethod(); + m.setFunction(member.getName()); + if (member.getInputs() == null || member.getInputs().isEmpty()) { + methodsToCall.add(m); + } + }); + + var executionLog = new ExecutionLog(); + var result = processAllViewMethods(contractAddress, methodsToCall, executionLog); + if (executionLog.hasError()) { + return null; + } + result.add(ResultValue.builder() + .method(JSLibraryProvider.CONTRACT_OVERVIEW_ITEM.getName()) + .output(List.of(contractAddress.getHex())) + .build()); + + var resultMap = toMap(result); + var overviewProperties = new ArrayList<>( + createOverview(contractSpec, resultMap) + ); + response.setOverview(overviewProperties); + + response.getMembers().addAll(methodSpecMapper.convert(viewMethods)); + matchResults(response.getMembers(), resultMap); + response.getMembers().addAll(methodSpecMapper.convert(notViewMethods)); + response.getMembers().addAll(methodSpecMapper.convert(events)); + + response.setInheritedContracts( + this.getInheritedAsrModules(contractSpec.getType() + , aplContractSpec.getLanguage() + , aplContractSpec.getVersion()) + ); + return null; + } + + private Map toMap(List result) { + return result.stream().collect(Collectors.toMap(ResultValue::getMethod, Function.identity())); + } + + private void matchResults(List methods, Map resultMap) { + methods.forEach(methodSpec -> { + if (methodSpec.getInputs() == null || methodSpec.getInputs().isEmpty()) { + var res = resultMap.getOrDefault(methodSpec.getName(), ResultValue.UNDEFINED_RESULT); + methodSpec.setValue(res.getStringResult()); + methodSpec.setSignature(res.getSignature()); + } + }); + } + + private List createOverview(ContractSpec contractSpec, Map resultMap) { + //TODO: move Overview info to ContractSpec + var properties = contractSpec.getOverview(); + + var propertySpec = new ArrayList(); + + properties.forEach(item -> { + var prop = new PropertySpec(); + prop.setName(item.getName()); + prop.setType(item.getType()); + prop.setValue(resultMap.getOrDefault(item.getName(), ResultValue.UNDEFINED_RESULT).getStringResult()); + propertySpec.add(prop); + }); + return propertySpec; + } + + @Override + public List processAllViewMethods(Address contractAddress, List members, ExecutionLog executionLog) { + SmartContract smartContract = contractRepository.loadContract( + contractAddress, + contractAddress, + new ContractFuel(contractAddress, BigInteger.ZERO, BigInteger.ONE) + ); + var methods = methodMapper.convert(members); + var context = smcConfig.asViewContext(accountService.getBlockchainHeight(), + smartContract, + integratorFactory.createReadonlyProcessor() + ); + + if (this.processor == null) { // used by unit test only !! + this.processor = new CallViewMethodTxProcessor(smartContract, methods, context); + } + + var rc = processor.batchProcess(); + executionLog.join(processor.getExecutionLog()); + return rc; + } + @Override public List getInheritedAsrModules(String asrModuleName, String language, Version version) { checkLibraryCompatibility(language, version); diff --git a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/transaction/types/smc/SmcCallMethodTransactionType.java b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/transaction/types/smc/SmcCallMethodTransactionType.java index 35f1f50302..8fe04464e3 100644 --- a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/transaction/types/smc/SmcCallMethodTransactionType.java +++ b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/transaction/types/smc/SmcCallMethodTransactionType.java @@ -29,6 +29,7 @@ import com.apollocurrency.aplwallet.apl.smc.service.tx.CallMethodTxProcessor; import com.apollocurrency.aplwallet.apl.smc.service.tx.CallMethodTxValidator; import com.apollocurrency.aplwallet.apl.smc.service.tx.SyntaxValidator; +import com.apollocurrency.aplwallet.apl.util.StringUtils; import com.apollocurrency.smc.contract.AddressNotFoundException; import com.apollocurrency.smc.contract.SmartContract; import com.apollocurrency.smc.contract.SmartMethod; @@ -37,7 +38,6 @@ import com.apollocurrency.smc.contract.fuel.OperationPrice; import com.apollocurrency.smc.data.type.Address; import com.apollocurrency.smc.polyglot.PolyglotException; -import com.google.common.base.Strings; import lombok.extern.slf4j.Slf4j; import org.json.simple.JSONObject; @@ -138,7 +138,7 @@ public void doStateDependentValidation(Transaction transaction) { public void executeStateIndependentValidation(Transaction transaction, AbstractSmcAttachment abstractSmcAttachment) { log.debug("SMC: doStateIndependentValidation = ... txId={}", transaction.getStringId()); SmcCallMethodAttachment attachment = (SmcCallMethodAttachment) abstractSmcAttachment; - if (Strings.isNullOrEmpty(attachment.getMethodName())) { + if (StringUtils.isBlank(attachment.getMethodName())) { throw new AplUnacceptableTransactionValidationException("Empty contract method name.", transaction); } SmartMethod smartMethod = SmartMethod.builder() diff --git a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/transaction/types/smc/SmcPublishContractTransactionType.java b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/transaction/types/smc/SmcPublishContractTransactionType.java index 480b633868..5aeccfbc6f 100644 --- a/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/transaction/types/smc/SmcPublishContractTransactionType.java +++ b/apl-core/src/main/java/com/apollocurrency/aplwallet/apl/core/transaction/types/smc/SmcPublishContractTransactionType.java @@ -34,12 +34,12 @@ import com.apollocurrency.aplwallet.apl.smc.service.SmcContractTxProcessor; import com.apollocurrency.aplwallet.apl.smc.service.tx.PublishContractTxProcessor; import com.apollocurrency.aplwallet.apl.smc.service.tx.PublishContractTxValidator; +import com.apollocurrency.aplwallet.apl.util.StringUtils; import com.apollocurrency.aplwallet.apl.util.io.PayloadResult; import com.apollocurrency.aplwallet.apl.util.io.Result; import com.apollocurrency.smc.contract.SmartContract; import com.apollocurrency.smc.data.type.Address; import com.apollocurrency.smc.polyglot.PolyglotException; -import com.google.common.base.Strings; import lombok.extern.slf4j.Slf4j; import org.json.simple.JSONObject; @@ -121,19 +121,19 @@ public void executeStateIndependentValidation(Transaction transaction, AbstractS } SmcPublishContractAttachment attachment = (SmcPublishContractAttachment) abstractSmcAttachment; - if (Strings.isNullOrEmpty(attachment.getContractName())) { + if (StringUtils.isBlank(attachment.getContractName())) { throw new AplUnacceptableTransactionValidationException("Empty contract name.", transaction); } - if (Strings.isNullOrEmpty(attachment.getBaseContract())) { + if (StringUtils.isBlank(attachment.getBaseContract())) { throw new AplUnacceptableTransactionValidationException("Empty base contract.", transaction); } - if (Strings.isNullOrEmpty(attachment.getContractSource())) { + if (StringUtils.isBlank(attachment.getContractSource())) { throw new AplUnacceptableTransactionValidationException("Empty contract source.", transaction); } - if (Strings.isNullOrEmpty(attachment.getLanguageName())) { + if (StringUtils.isBlank(attachment.getLanguageName())) { throw new AplUnacceptableTransactionValidationException("Empty contract language name.", transaction); } - if (Strings.isNullOrEmpty(attachment.getLanguageVersion())) { + if (StringUtils.isBlank(attachment.getLanguageVersion())) { throw new AplUnacceptableTransactionValidationException("Empty contract language version.", transaction); } SmartContract smartContract = contractToolService.createNewContract(transaction); diff --git a/apl-core/src/test/java/com/apollocurrency/aplwallet/apl/core/dao/blockchain/TransactionDaoTest.java b/apl-core/src/test/java/com/apollocurrency/aplwallet/apl/core/dao/blockchain/TransactionDaoTest.java index 815d8be34e..8b254cfe18 100644 --- a/apl-core/src/test/java/com/apollocurrency/aplwallet/apl/core/dao/blockchain/TransactionDaoTest.java +++ b/apl-core/src/test/java/com/apollocurrency/aplwallet/apl/core/dao/blockchain/TransactionDaoTest.java @@ -14,7 +14,7 @@ import com.apollocurrency.aplwallet.apl.core.db.DatabaseManager; import com.apollocurrency.aplwallet.apl.core.entity.appdata.ChatInfo; import com.apollocurrency.aplwallet.apl.core.entity.blockchain.TransactionEntity; -import com.apollocurrency.aplwallet.apl.core.model.Sort; +import com.apollocurrency.aplwallet.apl.util.api.Sort; import com.apollocurrency.aplwallet.apl.core.model.Transaction; import com.apollocurrency.aplwallet.apl.core.model.TransactionDbInfo; import com.apollocurrency.aplwallet.apl.core.service.appdata.TimeService; diff --git a/apl-core/src/test/java/com/apollocurrency/aplwallet/apl/core/dao/state/smc/SmcContractEventLogTableTest.java b/apl-core/src/test/java/com/apollocurrency/aplwallet/apl/core/dao/state/smc/SmcContractEventLogTableTest.java index b4ee5a4bb2..e8a31a7625 100644 --- a/apl-core/src/test/java/com/apollocurrency/aplwallet/apl/core/dao/state/smc/SmcContractEventLogTableTest.java +++ b/apl-core/src/test/java/com/apollocurrency/aplwallet/apl/core/dao/state/smc/SmcContractEventLogTableTest.java @@ -16,6 +16,8 @@ import com.apollocurrency.aplwallet.apl.extension.DbExtension; import com.apollocurrency.aplwallet.apl.extension.TemporaryFolderExtension; import com.apollocurrency.aplwallet.apl.smc.model.AplAddress; +import com.apollocurrency.aplwallet.apl.util.api.PositiveRange; +import com.apollocurrency.aplwallet.apl.util.api.Sort; import com.apollocurrency.aplwallet.apl.util.env.config.Chain; import com.apollocurrency.aplwallet.apl.util.injectable.PropertiesHolder; import com.apollocurrency.smc.data.type.Address; @@ -131,8 +133,8 @@ void getEntries() { @ParameterizedTest void getEventsByFilter(String name, Integer heightFrom, Integer heightTo, Integer num) { var actual = table.getEventsByFilter( - contractId, name, heightFrom, heightTo, 0, -1, "ASC" + contractId, name, new PositiveRange(heightFrom, heightTo), new PositiveRange(0, -1), Sort.asc() ); assertEquals(num, actual.size()); } -} \ No newline at end of file +} diff --git a/apl-core/src/test/java/com/apollocurrency/aplwallet/apl/core/dao/state/smc/SmcContractTableTest.java b/apl-core/src/test/java/com/apollocurrency/aplwallet/apl/core/dao/state/smc/SmcContractTableTest.java index 92abcac779..86e18656ad 100644 --- a/apl-core/src/test/java/com/apollocurrency/aplwallet/apl/core/dao/state/smc/SmcContractTableTest.java +++ b/apl-core/src/test/java/com/apollocurrency/aplwallet/apl/core/dao/state/smc/SmcContractTableTest.java @@ -18,6 +18,7 @@ import com.apollocurrency.aplwallet.apl.core.service.fulltext.FullTextConfigImpl; import com.apollocurrency.aplwallet.apl.core.service.state.DerivedDbTablesRegistryImpl; import com.apollocurrency.aplwallet.apl.core.service.state.DerivedTablesRegistry; +import com.apollocurrency.aplwallet.apl.core.service.state.smc.ContractQuery; import com.apollocurrency.aplwallet.apl.core.transaction.TransactionType; import com.apollocurrency.aplwallet.apl.core.transaction.TransactionTypeFactory; import com.apollocurrency.aplwallet.apl.core.transaction.messages.SmcPublishContractAttachment; @@ -26,6 +27,7 @@ import com.apollocurrency.aplwallet.apl.testutil.DbUtils; import com.apollocurrency.aplwallet.apl.testutil.EntityProducer; import com.apollocurrency.aplwallet.apl.util.Convert2; +import com.apollocurrency.aplwallet.apl.util.api.PositiveRange; import com.apollocurrency.aplwallet.apl.util.db.TransactionalDataSource; import com.apollocurrency.aplwallet.apl.util.exception.AplException; import com.apollocurrency.aplwallet.apl.util.injectable.PropertiesHolder; @@ -183,7 +185,14 @@ private List getMockContractDetailsList(Long address, Long owne when(attachment.getFuelPrice()).thenReturn(BigInteger.ONE); //WHEN - return table.getContractsByFilter(address, null, owner, name, null, null, status, height, from, to); + return table.getContractsByFilter(ContractQuery.builder() + .address(address) + .owner(owner) + .name(name) + .status(status) + .height(height) + .paging(new PositiveRange(from, to)) + .build()); } @Test diff --git a/apl-core/src/test/java/com/apollocurrency/aplwallet/apl/core/service/blockchain/BlockchainTest.java b/apl-core/src/test/java/com/apollocurrency/aplwallet/apl/core/service/blockchain/BlockchainTest.java index a766464f4a..5ac3fe2de7 100644 --- a/apl-core/src/test/java/com/apollocurrency/aplwallet/apl/core/service/blockchain/BlockchainTest.java +++ b/apl-core/src/test/java/com/apollocurrency/aplwallet/apl/core/service/blockchain/BlockchainTest.java @@ -6,7 +6,7 @@ import com.apollocurrency.aplwallet.apl.core.model.Block; import com.apollocurrency.aplwallet.apl.core.model.EcBlockData; -import com.apollocurrency.aplwallet.apl.core.model.Sort; +import com.apollocurrency.aplwallet.apl.util.api.Sort; import com.apollocurrency.aplwallet.apl.core.model.Transaction; import com.apollocurrency.aplwallet.apl.core.cache.NullCacheProducerForTests; import com.apollocurrency.aplwallet.apl.core.chainid.BlockchainConfig; diff --git a/apl-core/src/test/java/com/apollocurrency/aplwallet/apl/core/service/state/smc/SmcContractRepositoryTest.java b/apl-core/src/test/java/com/apollocurrency/aplwallet/apl/core/service/state/smc/SmcContractRepositoryTest.java index 5ba9770c70..02667da09d 100644 --- a/apl-core/src/test/java/com/apollocurrency/aplwallet/apl/core/service/state/smc/SmcContractRepositoryTest.java +++ b/apl-core/src/test/java/com/apollocurrency/aplwallet/apl/core/service/state/smc/SmcContractRepositoryTest.java @@ -18,21 +18,17 @@ import com.apollocurrency.aplwallet.apl.core.service.state.smc.impl.SmcContractToolServiceImpl; import com.apollocurrency.aplwallet.apl.core.transaction.messages.SmcPublishContractAttachment; import com.apollocurrency.aplwallet.apl.smc.model.AplAddress; -import com.apollocurrency.aplwallet.apl.smc.model.AplContractSpec; import com.apollocurrency.aplwallet.apl.util.Convert2; -import com.apollocurrency.smc.contract.AddressNotFoundException; import com.apollocurrency.smc.contract.ContractSource; import com.apollocurrency.smc.contract.ContractStatus; import com.apollocurrency.smc.contract.SmartContract; import com.apollocurrency.smc.contract.fuel.ContractFuel; import com.apollocurrency.smc.contract.fuel.Fuel; -import com.apollocurrency.smc.data.type.Address; import com.apollocurrency.smc.polyglot.SimpleVersion; import com.apollocurrency.smc.polyglot.language.LanguageContext; import com.apollocurrency.smc.polyglot.language.LanguageContextFactory; import com.apollocurrency.smc.polyglot.language.SmartSource; import com.apollocurrency.smc.polyglot.language.lib.Preprocessor; -import lombok.SneakyThrows; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -43,9 +39,7 @@ import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -103,8 +97,7 @@ void setUp() { smcContractStateTable, contractModelToEntityConverter, contractModelToStateConverter, - smcConfig, - contractService); + smcConfig); contractToolService = new SmcContractToolServiceImpl(blockchain, smcConfig); smcTxData = SmcTxData.builder() @@ -241,39 +234,4 @@ void createNewContract() { assertEquals(smartContract, newContract); } - @SneakyThrows - @Test - void loadSpecByAddress() { - //GIVEN - var address = new AplAddress(123L); - var module = mock(AplContractSpec.class); - var entity = spy(smcContractEntity); - when(entity.getBaseContract()).thenReturn("APL20"); - when(smcContractTable.get(SmcContractTable.KEY_FACTORY.newKey(address.getLongId()))).thenReturn(entity); - when(contractService.loadAsrModuleSpec(entity.getBaseContract(), - entity.getLanguageName(), - SimpleVersion.fromString(entity.getLanguageVersion()))).thenReturn(module); - //WHEN - var rc = contractRepository.loadAsrModuleSpec(address); - //THEN - assertEquals(module, rc); - } - - @Test - void loadSpecByAddressThrowException() { - //GIVEN - var address = new AplAddress(123L); - //WHEN - //THEN - assertThrows(AddressNotFoundException.class, () -> contractRepository.loadAsrModuleSpec(address)); - } - - static String convertToString(Address address) { - return Long.toUnsignedString(new AplAddress(address).getLongId()); - } - - static String convertToRS(Address address) { - return Convert2.rsAccount(new AplAddress(address).getLongId()); - } - } diff --git a/apl-core/src/test/java/com/apollocurrency/aplwallet/apl/core/service/state/smc/SmcContractServiceTest.java b/apl-core/src/test/java/com/apollocurrency/aplwallet/apl/core/service/state/smc/SmcContractServiceTest.java index 392cce1294..85c509ecaa 100644 --- a/apl-core/src/test/java/com/apollocurrency/aplwallet/apl/core/service/state/smc/SmcContractServiceTest.java +++ b/apl-core/src/test/java/com/apollocurrency/aplwallet/apl/core/service/state/smc/SmcContractServiceTest.java @@ -6,6 +6,10 @@ import com.apollocurrency.aplwallet.apl.core.config.SmcConfig; import com.apollocurrency.aplwallet.apl.core.exception.AplCoreContractViolationException; +import com.apollocurrency.aplwallet.apl.core.rest.v2.converter.MethodSpecMapper; +import com.apollocurrency.aplwallet.apl.core.rest.v2.converter.SmartMethodMapper; +import com.apollocurrency.aplwallet.apl.core.service.state.account.AccountService; +import com.apollocurrency.aplwallet.apl.core.service.state.smc.impl.SmcBlockchainIntegratorFactory; import com.apollocurrency.aplwallet.apl.core.service.state.smc.impl.SmcContractServiceImpl; import com.apollocurrency.aplwallet.apl.util.Convert2; import com.apollocurrency.smc.polyglot.SimpleVersion; @@ -20,6 +24,8 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import java.util.Optional; + import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.mock; @@ -41,13 +47,25 @@ class SmcContractServiceTest { LanguageContext languageContext; @Mock LibraryProvider libraryProvider; + @Mock + SmcContractRepository contractRepository; + @Mock + SmcBlockchainIntegratorFactory integratorFactory; + @Mock + AccountService accountService; + @Mock + SmartMethodMapper methodMapper; + @Mock + MethodSpecMapper methodSpecMapper; + SmcContractService contractService; @BeforeEach void setUp() { when(smcConfig.createLanguageContext()).thenReturn(languageContext); when(languageContext.getLibraryProvider()).thenReturn(libraryProvider); - contractService = new SmcContractServiceImpl(smcConfig); + contractService = new SmcContractServiceImpl( + smcConfig, accountService, contractRepository, integratorFactory, methodMapper, methodSpecMapper, Optional.empty()); } @SneakyThrows @@ -80,7 +98,5 @@ void loadSpecByModuleNameWithException() { //WHEN //THEN assertThrows(AplCoreContractViolationException.class, () -> contractService.loadAsrModuleSpec(moduleName, "java", version)); - } - } diff --git a/apl-core/src/test/java/com/apollocurrency/aplwallet/apl/core/service/state/smc/impl/SmcContractServiceImplTest.java b/apl-core/src/test/java/com/apollocurrency/aplwallet/apl/core/service/state/smc/impl/SmcContractServiceImplTest.java new file mode 100644 index 0000000000..caeb2da3ad --- /dev/null +++ b/apl-core/src/test/java/com/apollocurrency/aplwallet/apl/core/service/state/smc/impl/SmcContractServiceImplTest.java @@ -0,0 +1,180 @@ +package com.apollocurrency.aplwallet.apl.core.service.state.smc.impl; + +import com.apollocurrency.aplwallet.api.v2.model.ContractSpecResponse; +import com.apollocurrency.aplwallet.apl.core.config.SmcConfig; +import com.apollocurrency.aplwallet.apl.core.exception.AplCoreContractViolationException; +import com.apollocurrency.aplwallet.apl.core.rest.v2.converter.MethodSpecMapper; +import com.apollocurrency.aplwallet.apl.core.rest.v2.converter.SmartMethodMapper; +import com.apollocurrency.aplwallet.apl.core.service.state.account.AccountService; +import com.apollocurrency.aplwallet.apl.core.service.state.smc.SmcContractRepository; +import com.apollocurrency.aplwallet.apl.core.service.state.smc.SmcContractService; +import com.apollocurrency.aplwallet.apl.smc.model.AplAddress; +import com.apollocurrency.aplwallet.apl.smc.model.AplContractSpec; +import com.apollocurrency.aplwallet.apl.smc.service.SmcContractTxBatchProcessor; +import com.apollocurrency.aplwallet.apl.util.Convert2; +import com.apollocurrency.smc.contract.SmartContract; +import com.apollocurrency.smc.data.type.Address; +import com.apollocurrency.smc.polyglot.SimpleVersion; +import com.apollocurrency.smc.polyglot.Version; +import com.apollocurrency.smc.polyglot.language.ContractSpec; +import com.apollocurrency.smc.polyglot.language.LanguageContext; +import com.apollocurrency.smc.polyglot.language.lib.LibraryProvider; +import com.apollocurrency.smc.polyglot.language.lib.ModuleSource; +import lombok.SneakyThrows; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; +import java.util.Optional; + +import static com.apollocurrency.aplwallet.apl.core.app.GenesisImporter.EPOCH_BEGINNING; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class SmcContractServiceImplTest { + + @Mock + LanguageContext languageContext; + @Mock + LibraryProvider libraryProvider; + @Mock + SmcConfig smcConfig; + @Mock + AccountService accountService; + @Mock + SmcContractRepository contractRepository; + @Mock + SmcBlockchainIntegratorFactory integratorFactory; + SmartMethodMapper methodMapper = new SmartMethodMapper(); + @Mock + MethodSpecMapper methodSpecMapper; + @Mock + SmcContractTxBatchProcessor processor; + + SmcContractService contractService; + + @BeforeEach + void setUp() { + when(smcConfig.createLanguageContext()).thenReturn(languageContext); + when(languageContext.getLibraryProvider()).thenReturn(libraryProvider); + contractService = new SmcContractServiceImpl( + smcConfig, accountService, contractRepository, integratorFactory, methodMapper, methodSpecMapper, Optional.of(processor)); + } + + @SneakyThrows + @Test + void test_loadAsrModuleSpec() { + //GIVEN + SmartContract smartContract = mock(SmartContract.class); + when(smartContract.getBaseContract()).thenReturn("anyContract"); + when(smartContract.getLanguageName()).thenReturn("anyLang"); + Version version = new SimpleVersion("0", "1", "1"); + when(smartContract.getLanguageVersion()).thenReturn(version); + when(libraryProvider.isCompatible("anyLang", version)).thenReturn(true); + ContractSpec contractSpec = mock(ContractSpec.class); + when(libraryProvider.loadSpecification((anyString()))).thenReturn(contractSpec); + ModuleSource moduleSource = mock(ModuleSource.class); + when(libraryProvider.importModule((anyString()))).thenReturn(moduleSource); + Address address = new AplAddress(1L); + when(contractRepository.loadContract(address)).thenReturn(smartContract); + //WHEN + AplContractSpec aplContractSpec = contractService.loadAsrModuleSpec(address); + assertNotNull(aplContractSpec); + //THEN + verify(libraryProvider, times(1)).isCompatible("anyLang", version); + verify(libraryProvider, times(1)).importModule((anyString())); + verify(libraryProvider, times(1)).importModule((anyString())); + } + + @SneakyThrows + @Test + void test_getAsrModules() { + //GIVEN + Version version = new SimpleVersion("0", "1", "1"); + when(libraryProvider.isCompatible("js", version)).thenReturn(true).thenReturn(false); + when(libraryProvider.getAsrModules(anyString())).thenReturn(List.of("one", "two")); + //WHEN + List result = contractService.getAsrModules("js", version, "type"); + assertNotNull(result); + result = contractService.getAsrModules("js", version, "type"); + assertTrue(result.isEmpty()); + + //verify + verify(libraryProvider, times(2)).isCompatible("js", version); + verify(libraryProvider, times(1)).getAsrModules(anyString()); + } + + @SneakyThrows + @Test + void test_getInheritedAsrModulesError() { + //GIVEN + Version version = new SimpleVersion("0", "1", "1"); + when(libraryProvider.isCompatible("js", version)).thenReturn(false); + //WHEN + assertThrows(AplCoreContractViolationException.class, () -> + contractService.getInheritedAsrModules("an_module", "js", version) + ); + //verify + verify(libraryProvider, times(1)).isCompatible("js", version); + } + + @SneakyThrows + @Test + void test_loadContractSpecification() { + Convert2.init("APL", EPOCH_BEGINNING); + //GIVEN + SmartContract smartContract = mock(SmartContract.class); + when(smartContract.getBaseContract()).thenReturn("anyContract"); + when(smartContract.getLanguageName()).thenReturn("js"); + Version version = new SimpleVersion("0", "1", "1"); + when(smartContract.getLanguageVersion()).thenReturn(version); + Address address = new AplAddress(1L); + + when(libraryProvider.isCompatible("js", version)).thenReturn(true); + + ContractSpec contractSpec = mock(ContractSpec.class); + List overview = List.of(new ContractSpec.Item("address", "address")); + when(contractSpec.getOverview()).thenReturn(overview); + List members = List.of( + new ContractSpec.Member(ContractSpec.MemberType.FUNCTION, "1", + ContractSpec.Visibility.PUBLIC, + ContractSpec.StateMutability.PAYABLE, null, List.of(), "value 1"), + new ContractSpec.Member(ContractSpec.MemberType.FUNCTION, "2", + ContractSpec.Visibility.EXTERNAL, + ContractSpec.StateMutability.PAYABLE, null, List.of(), "value 2"), + new ContractSpec.Member(ContractSpec.MemberType.FUNCTION, "3", + ContractSpec.Visibility.EXTERNAL, + ContractSpec.StateMutability.VIEW, null, List.of(), "value 3"), + new ContractSpec.Member(ContractSpec.MemberType.CONSTRUCTOR, "4", + ContractSpec.Visibility.PUBLIC, + ContractSpec.StateMutability.NONPAYABLE, List.of(), null, "value 4"), + new ContractSpec.Member(ContractSpec.MemberType.EVENT, "5", + ContractSpec.Visibility.INTERNAL, + ContractSpec.StateMutability.VIEW, List.of(), List.of(), "value 5") + ); + when(contractSpec.getMembers()).thenReturn(members); + when(libraryProvider.loadSpecification((anyString()))).thenReturn(contractSpec); + + ModuleSource moduleSource = mock(ModuleSource.class); + when(libraryProvider.importModule((anyString()))).thenReturn(moduleSource); + when(contractRepository.loadContract(address)).thenReturn(smartContract); + + //WHEN + ContractSpecResponse response = contractService.loadContractSpecification(address); + assertNull(response); + //verify + verify(libraryProvider, times(2)).isCompatible("js", version); + verify(libraryProvider, times(1)).loadSpecification(anyString()); + verify(libraryProvider, times(1)).importModule(anyString()); + verify(contractRepository, times(1)).loadContract(address); + + } +} \ No newline at end of file diff --git a/apl-core/src/test/java/com/apollocurrency/aplwallet/apl/core/service/state/smc/impl/SmcContractStorageServiceImplTest.java b/apl-core/src/test/java/com/apollocurrency/aplwallet/apl/core/service/state/smc/impl/SmcContractStorageServiceImplTest.java index 1db697a969..f615c5550e 100644 --- a/apl-core/src/test/java/com/apollocurrency/aplwallet/apl/core/service/state/smc/impl/SmcContractStorageServiceImplTest.java +++ b/apl-core/src/test/java/com/apollocurrency/aplwallet/apl/core/service/state/smc/impl/SmcContractStorageServiceImplTest.java @@ -4,6 +4,7 @@ package com.apollocurrency.aplwallet.apl.core.service.state.smc.impl; +import com.apollocurrency.aplwallet.apl.core.dao.state.keyfactory.DbKey; import com.apollocurrency.aplwallet.apl.core.dao.state.smc.SmcContractMappingTable; import com.apollocurrency.aplwallet.apl.core.entity.state.smc.SmcContractMappingEntity; import com.apollocurrency.aplwallet.apl.core.service.blockchain.Blockchain; @@ -71,6 +72,28 @@ void saveEntry() { verify(smcContractMappingTable, times(1)).insert(smcContractMappingEntity); } + @Test + void updateEntry() { + //GIVEN + String json = "{}"; + String name = "mapping"; + Key key = AplAddress.valueOf("0x0102030405"); + SmcContractMappingEntity smcContractMappingEntity = SmcContractMappingEntity.builder() + .address(contractAddress.getLongId()) + .key(key.key()) + .name(name) + .serializedObject(json) + .height(blockchain.getHeight()) // new height value + .build(); + DbKey dbKey = SmcContractMappingTable.KEY_FACTORY.newKey(contractAddress.getLongId(), name, key.key()); + when(smcContractMappingTable.get(dbKey)).thenReturn(smcContractMappingEntity); + + //WHEN + smcContractStorageService.saveOrUpdateEntry(contractAddress, key, name, json); + //THEN + verify(smcContractMappingTable, times(1)).get(dbKey); + } + @Test void loadEntry() { //GIVEN diff --git a/apl-core/src/test/java/com/apollocurrency/aplwallet/apl/core/transaction/types/smc/AbstractSmcTransactionTypeApplyTest.java b/apl-core/src/test/java/com/apollocurrency/aplwallet/apl/core/transaction/types/smc/AbstractSmcTransactionTypeApplyTest.java index 873da19640..6fd2acf0d6 100644 --- a/apl-core/src/test/java/com/apollocurrency/aplwallet/apl/core/transaction/types/smc/AbstractSmcTransactionTypeApplyTest.java +++ b/apl-core/src/test/java/com/apollocurrency/aplwallet/apl/core/transaction/types/smc/AbstractSmcTransactionTypeApplyTest.java @@ -37,6 +37,8 @@ import com.apollocurrency.aplwallet.apl.core.model.smc.SmcTxData; import com.apollocurrency.aplwallet.apl.core.rest.TransactionCreator; import com.apollocurrency.aplwallet.apl.core.rest.service.ServerInfoService; +import com.apollocurrency.aplwallet.apl.core.rest.v2.converter.MethodSpecMapper; +import com.apollocurrency.aplwallet.apl.core.rest.v2.converter.SmartMethodMapper; import com.apollocurrency.aplwallet.apl.core.service.appdata.GeneratorService; import com.apollocurrency.aplwallet.apl.core.service.appdata.TimeService; import com.apollocurrency.aplwallet.apl.core.service.appdata.impl.TimeServiceImpl; @@ -160,7 +162,7 @@ abstract class AbstractSmcTransactionTypeApplyTest extends DbContainerBaseTest { TransactionValidator.class, TransactionApplier.class, SmcConfig.class, SmcBlockchainIntegratorFactory.class, SmcPostponedContractServiceImpl.class, SmcContractTable.class, SmcContractStateTable.class, SmcContractMappingTable.class, SmcContractEventTable.class, SmcContractEventLogTable.class, - ContractModelToEntityConverter.class, ContractModelToStateEntityConverter.class, + ContractModelToEntityConverter.class, ContractModelToStateEntityConverter.class, SmartMethodMapper.class, MethodSpecMapper.class, ContractEventLogModelToLogEntryConverter.class, ContractEventModelToEntityConverter.class, SmcContractRepositoryImpl.class, SmcContractServiceImpl.class, SmcContractToolServiceImpl.class, SmcContractStorageServiceImpl.class, SmcContractEventServiceImpl.class, SmcTxLogProcessor.class, SmcMappingRepositoryClassFactory.class, SmcContractEventManagerClassFactory.class, diff --git a/apl-smc/src/main/java/com/apollocurrency/aplwallet/apl/smc/service/SmcContractEventService.java b/apl-smc/src/main/java/com/apollocurrency/aplwallet/apl/smc/service/SmcContractEventService.java index 6a0ab8d462..35e1f20662 100644 --- a/apl-smc/src/main/java/com/apollocurrency/aplwallet/apl/smc/service/SmcContractEventService.java +++ b/apl-smc/src/main/java/com/apollocurrency/aplwallet/apl/smc/service/SmcContractEventService.java @@ -6,6 +6,8 @@ import com.apollocurrency.aplwallet.api.v2.model.ContractEventDetails; import com.apollocurrency.aplwallet.apl.smc.model.AplContractEvent; +import com.apollocurrency.aplwallet.apl.util.api.PositiveRange; +import com.apollocurrency.aplwallet.apl.util.api.Sort; import com.apollocurrency.smc.data.expr.Term; import com.apollocurrency.smc.data.type.Address; @@ -38,5 +40,6 @@ public interface SmcContractEventService { */ void fireCdiEvent(AplContractEvent event); - List getEventsByFilter(Long contract, String eventName, Term filter, int fromBlock, int toBlock, int from, int to, String order); + List getEventsByFilter(Long contract, String eventName, Term filter, PositiveRange blockRange, PositiveRange paging, Sort order); + } diff --git a/apl-utils/src/main/java/com/apollocurrency/aplwallet/apl/util/api/PositiveRange.java b/apl-utils/src/main/java/com/apollocurrency/aplwallet/apl/util/api/PositiveRange.java new file mode 100644 index 0000000000..63d8dc7ede --- /dev/null +++ b/apl-utils/src/main/java/com/apollocurrency/aplwallet/apl/util/api/PositiveRange.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2018-2022. Apollo Foundation. + */ + +package com.apollocurrency.aplwallet.apl.util.api; + +import java.util.Objects; + +/** + * @author andrew.zinchenko@gmail.com + */ +public class PositiveRange implements Range { + private Integer from; + private Integer to; + + public PositiveRange(Integer from, Integer to) { + this.from = Objects.requireNonNull(from); + this.to = Objects.requireNonNull(to); + } + + public static boolean isUndefined(Integer value) { + return (value == null || value < 0); + } + + /** + * Check if the top boundary is set + * + * @return false if the top value equals null or negative. + */ + @Override + public boolean isTopBoundarySet() { + return isUndefined(to()); + } + + /** + * Check if the bottom boundary is set + * + * @return false if the bottom value equals null or negative. + */ + @Override + public boolean isBottomBoundarySet() { + return isUndefined(from()); + } + + /** + * Returns the Range object with default values for unknown boundary. + * The default value for min value is 0; + * The default value for max value is Long.MAX_VALUE; + * + * @param fromStr the min value + * @param toStr the max value + * @return the Range object with minStr and maxStr boundaries + */ + public static PositiveRange of(String fromStr, String toStr) { + Integer min = null; + Integer max = null; + if (fromStr != null) { + min = Integer.parseInt(fromStr); + } + if (toStr != null) { + max = Integer.parseInt(toStr); + } + return of(min, max); + } + + public static PositiveRange of(Integer from, Integer to) { + var rc = new PositiveRange((from == null) ? 0 : from, (to == null) ? Integer.MAX_VALUE : to); + rc.validate(); + return rc; + } + + @Override + public boolean inRange(Integer value) { + return from.compareTo(value) <= 0 && to.compareTo(value) >= 0; + } + + @Override + public boolean isValid() { + var from = this.from.longValue(); + var to = this.to.longValue(); + return !(from >= 0 && from <= to); + } + + @Override + public void validate() { + if (!isValid()) { + throw new IllegalStateException("The left boundary greater then the right boundary."); + } + } + + @Override + public Integer from() { + return from; + } + + @Override + public Integer adjustBottomBoundary(Integer value) { + if (!isBottomBoundarySet()) { + from = value; + } + return from; + } + + @Override + public Integer to() { + return to; + } + + @Override + public Integer adjustTopBoundary(Integer value) { + if (!isTopBoundarySet()) { + to = value; + } + return to; + } + + @Override + public String toString() { + return "[" + from + "," + to + ']'; + } +} diff --git a/apl-utils/src/main/java/com/apollocurrency/aplwallet/apl/util/api/Range.java b/apl-utils/src/main/java/com/apollocurrency/aplwallet/apl/util/api/Range.java new file mode 100644 index 0000000000..d33f35c93d --- /dev/null +++ b/apl-utils/src/main/java/com/apollocurrency/aplwallet/apl/util/api/Range.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2018-2022. Apollo Foundation. + */ + +package com.apollocurrency.aplwallet.apl.util.api; + +/** + * @author andrew.zinchenko@gmail.com + */ +public interface Range { + + boolean isTopBoundarySet(); + + boolean isBottomBoundarySet(); + + boolean inRange(T value); + + /** + * Validate the boundary. + * + * @return true if min value less than max value and false otherwise. + */ + boolean isValid(); + + /** + * Validate the boundary. + *

+ * Throws @{@link IllegalArgumentException} if min value greater than max value. + * + * @throws IllegalStateException if min value greater than max value. + */ + void validate(); + + /** + * Returns the min value of the range - the bottom boundary of the range + * + * @return the left boundary of the range + */ + T from(); + + /** + * Adjust top boundary to specified value. Set top boundary to value if it equals to the undefinedValue. + * + * @param value the value for undefined boundary + */ + T adjustTopBoundary(T value); + + /** + * Adjust bottom boundary to specified value. Set bottom boundary to value if it equals to the undefinedValue. + * + * @param value the value for undefined boundary + */ + T adjustBottomBoundary(T value); + + /** + * Returns the max value of the range - the top boundary of the range + * + * @return the right boundary of the range + */ + T to(); + + default T top() { + return to(); + } + + default T max() { + return to(); + } +} diff --git a/apl-utils/src/main/java/com/apollocurrency/aplwallet/apl/util/api/Sort.java b/apl-utils/src/main/java/com/apollocurrency/aplwallet/apl/util/api/Sort.java new file mode 100644 index 0000000000..5d8081a964 --- /dev/null +++ b/apl-utils/src/main/java/com/apollocurrency/aplwallet/apl/util/api/Sort.java @@ -0,0 +1,59 @@ +/* + * Copyright © 2018-2021 Apollo Foundation + */ + +package com.apollocurrency.aplwallet.apl.util.api; + +/** + * @author Andrii Boiarskyi + * @see + * @since 1.50.31 + */ +public class Sort { + public static final String ASC = "ASC"; + public static final String DESC = "DESC"; + private final String order; + + public Sort(String order) { + this.order = order; + if (!isASC() && !isDESC()) { + throw new IllegalArgumentException("Not allowed sort value: " + order); + } + } + + /** + * Returns the Sort object with default values for unknown ordering. + * The default value is ASC. + * + * @param order the given order + * @return the Sort object + */ + public static Sort of(String order) { + if (ASC.equalsIgnoreCase(order) || DESC.equalsIgnoreCase(order)) { + return new Sort(order); + } else { + return desc(); + } + } + + public boolean isASC() { + return ASC.equalsIgnoreCase(order); + } + + public boolean isDESC() { + return DESC.equalsIgnoreCase(order); + } + + @Override + public String toString() { + return order; + } + + public static Sort desc() { + return new Sort(DESC); + } + + public static Sort asc() { + return new Sort(ASC); + } +} diff --git a/apl-utils/src/main/java/com/apollocurrency/aplwallet/apl/util/api/parameter/FirstLastIndexBeanParam.java b/apl-utils/src/main/java/com/apollocurrency/aplwallet/apl/util/api/parameter/FirstLastIndexBeanParam.java index 8dab756acd..716aab0cfb 100644 --- a/apl-utils/src/main/java/com/apollocurrency/aplwallet/apl/util/api/parameter/FirstLastIndexBeanParam.java +++ b/apl-utils/src/main/java/com/apollocurrency/aplwallet/apl/util/api/parameter/FirstLastIndexBeanParam.java @@ -4,6 +4,7 @@ package com.apollocurrency.aplwallet.apl.util.api.parameter; +import com.apollocurrency.aplwallet.apl.util.api.PositiveRange; import io.swagger.v3.oas.annotations.Parameter; import lombok.AllArgsConstructor; import lombok.Getter; @@ -27,7 +28,8 @@ public class FirstLastIndexBeanParam { @PositiveOrZero private int firstIndex = 0; @Parameter(description = "A zero-based index to the last record ID to retrieve (optional).") - @QueryParam("lastIndex") @DefaultValue("-1") + @QueryParam("lastIndex") + @DefaultValue("-1") private int lastIndex = -1; public void adjustIndexes(int maxAPIrecords) { @@ -40,4 +42,7 @@ public void adjustIndexes(int maxAPIrecords) { this.lastIndex = tempLastIndex; } + public PositiveRange range() { + return new PositiveRange(firstIndex, lastIndex); + } } diff --git a/apl-utils/src/main/java/com/apollocurrency/aplwallet/apl/util/db/DbClause.java b/apl-utils/src/main/java/com/apollocurrency/aplwallet/apl/util/db/DbClause.java index 1005375758..b956017c17 100644 --- a/apl-utils/src/main/java/com/apollocurrency/aplwallet/apl/util/db/DbClause.java +++ b/apl-utils/src/main/java/com/apollocurrency/aplwallet/apl/util/db/DbClause.java @@ -127,7 +127,7 @@ public static final class LikeClause extends DbClause { public LikeClause(String columnName, String prefix) { super(" " + columnName + " LIKE ? "); - this.prefix = prefix.replace("%", "\\%").replace("_", "\\_") + '%'; + this.prefix = DbUtils.escapeLikePattern(prefix); } @Override diff --git a/apl-utils/src/main/java/com/apollocurrency/aplwallet/apl/util/db/DbUtils.java b/apl-utils/src/main/java/com/apollocurrency/aplwallet/apl/util/db/DbUtils.java index c2bed678d7..16d0378121 100644 --- a/apl-utils/src/main/java/com/apollocurrency/aplwallet/apl/util/db/DbUtils.java +++ b/apl-utils/src/main/java/com/apollocurrency/aplwallet/apl/util/db/DbUtils.java @@ -19,6 +19,7 @@ package com.apollocurrency.aplwallet.apl.util.db; +import com.apollocurrency.aplwallet.apl.util.api.PositiveRange; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.slf4j.Logger; @@ -35,6 +36,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; +import java.util.Objects; import java.util.Optional; import static org.slf4j.LoggerFactory.getLogger; @@ -234,6 +236,10 @@ public static int calculateLimit(int from, int to) { to - from + 1 : 0; } + public static String limitsClause(PositiveRange paging) { + return limitsClause(paging.from(), paging.to()); + } + public static String limitsClause(int from, int to) { int limit = calculateLimit(from, to); if (limit > 0 && from > 0) { @@ -251,6 +257,10 @@ public static String limitsClause(int from, int to) { } } + public static int setLimits(int index, PreparedStatement pstmt, PositiveRange paging) throws SQLException { + return setLimits(index, pstmt, paging.from(), paging.to()); + } + public static int setLimits(int index, PreparedStatement pstmt, int from, int to) throws SQLException { int limit = calculateLimit(from, to); if (limit > 0) { @@ -262,4 +272,9 @@ public static int setLimits(int index, PreparedStatement pstmt, int from, int to return index; } + public static String escapeLikePattern(String pattern) { + Objects.requireNonNull(pattern, "pattern"); + return pattern.replace("%", "\\%").replace("_", "\\_") + "%"; + } + }