From c007439b73d2a8537f41d226580d0957b5c0b847 Mon Sep 17 00:00:00 2001 From: Oleg Zhymolokhov Date: Thu, 22 Nov 2018 15:48:33 +0200 Subject: [PATCH 1/6] multiple-networks-update: Intermediate commit. Added functionality for multiple configurable network properties, multiple web3j instances. Added functionality to query EtherScan depending on the chainId. --- .../blockexplorer/EtherScanConsumer.java | 19 ++-- .../walletservice/config/ConfigValues.java | 38 ++++--- .../walletservice/config/EthereumConfig.java | 44 -------- .../walletservice/config/Web3jWarmUp.java | 4 +- .../walletservice/constant/Constants.java | 11 ++ .../controller/ContractController.java | 23 ++-- .../controller/EthereumController.java | 32 ++++-- .../controller/TransactionController.java | 15 ++- .../walletservice/dto/AccountScanDto.java | 4 + .../dto/EstimateGasRequestDto.java | 1 + .../dto/SignedTransactionDto.java | 2 + .../walletservice/dto/WalletImportDto.java | 5 + .../walletservice/model/ContractData.java | 1 + .../walletservice/model/FundedAddress.java | 1 + .../model/network/NetworkProperties.java | 17 +++ .../provider/ContractProvider.java | 102 +++++++++--------- .../walletservice/provider/Web3jProvider.java | 49 ++++++++- .../repository/ContractDataRepository.java | 4 + .../repository/FundedAddressRepository.java | 2 +- .../walletservice/service/TokenService.java | 86 ++++++++------- .../walletservice/service/Web3JService.java | 53 +++++---- .../service/operation/EthOperation.java | 2 +- .../service/operation/TokenOperation.java | 4 +- src/main/resources/application.yml | 29 ++--- 24 files changed, 324 insertions(+), 224 deletions(-) delete mode 100644 src/main/java/com/nynja/walletservice/config/EthereumConfig.java create mode 100644 src/main/java/com/nynja/walletservice/model/network/NetworkProperties.java diff --git a/src/main/java/com/nynja/walletservice/blockexplorer/EtherScanConsumer.java b/src/main/java/com/nynja/walletservice/blockexplorer/EtherScanConsumer.java index b030db3..dd93ee9 100644 --- a/src/main/java/com/nynja/walletservice/blockexplorer/EtherScanConsumer.java +++ b/src/main/java/com/nynja/walletservice/blockexplorer/EtherScanConsumer.java @@ -2,9 +2,10 @@ package com.nynja.walletservice.blockexplorer; import com.nynja.walletservice.blockexplorer.dto.Response; import com.nynja.walletservice.blockexplorer.dto.TxListItemDto; +import com.nynja.walletservice.config.ConfigValues; import com.nynja.walletservice.constant.enums.Sort; +import com.nynja.walletservice.model.network.NetworkProperties; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; @@ -18,13 +19,12 @@ import static org.apache.commons.lang3.StringUtils.isBlank; @Slf4j public class EtherScanConsumer { - @Value("${etherscan.url}") - private String etherScanUrl; + private ConfigValues conf; - @Value("${etherscan.apikey}") - private String apikey; + public EtherScanConsumer(ConfigValues conf) { + this.conf = conf; + } - //TODO Use ChainId to select etherscan urls public Response getTxHistory(String address, String chainId, String contractAddress, String page, String offset, Sort sort) { if (isBlank(contractAddress)) { return getTransactionsByAddress("txlist", address, chainId, page, offset, sort); @@ -51,15 +51,16 @@ public class EtherScanConsumer { return makeGetRequest(prepareBuilder(action, chainId, address, page, offset, sort)); } - //TODO Add differentiation between networks private UriComponentsBuilder prepareBuilder(String action, String chainId, String address, String page, String offset, Sort sort) { - UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(etherScanUrl) + NetworkProperties props = conf.getNetworks().get(chainId); + + UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(props.getBlockExplorerUrl()) .queryParam("module", "account") .queryParam("action", action) .queryParam("address", address) .queryParam("startblock", "0") .queryParam("endblock", "99999999999") - .queryParam("apikey", apikey) + .queryParam("apikey", props.getBlockExplorerApiKey()) .queryParam("sort", sort != null ? sort.getSort() : Sort.ASC.getSort()); if (!isBlank(page)) { diff --git a/src/main/java/com/nynja/walletservice/config/ConfigValues.java b/src/main/java/com/nynja/walletservice/config/ConfigValues.java index 072cc96..b0172b0 100644 --- a/src/main/java/com/nynja/walletservice/config/ConfigValues.java +++ b/src/main/java/com/nynja/walletservice/config/ConfigValues.java @@ -1,29 +1,29 @@ package com.nynja.walletservice.config; +import com.nynja.walletservice.model.network.NetworkProperties; import lombok.Getter; +import lombok.Setter; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Configuration; import java.math.BigInteger; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import static com.nynja.walletservice.constant.Constants.LogMessages.CHAIN_ID_NOT_FOUND; +import static com.nynja.walletservice.constant.Constants.newExceptionWithMessage; + +@ConfigurationProperties(prefix = "ethereum") +@EnableConfigurationProperties @Configuration +@Setter @Getter public class ConfigValues { - @Value("${ethereum.client.url}") - private String clientUrl; - - @Value("${ethereum.admin.address}") - private String adminAddress; - - @Value("${ethereum.admin.private-key}") - private String adminPrivateKey; - - @Value("${ethereum.contract.gas-price}") - private Long gasPrice; - - @Value("${ethereum.contract.gas-limit}") - private Long gasLimit; + private Map networks = new HashMap<>(); @Value("${web3j.attempts}") private Integer attempts; @@ -37,11 +37,9 @@ public class ConfigValues { @Value("${ethereum.event.margin}") private BigInteger blockSubscriptionMargin; - public BigInteger gasPrice() { - return BigInteger.valueOf(gasPrice); - } - - public BigInteger gasLimit() { - return BigInteger.valueOf(gasLimit); + public NetworkProperties getNetworkProps(String chainId) { + return Optional.ofNullable(networks.get(chainId)) + .orElseThrow(newExceptionWithMessage(CHAIN_ID_NOT_FOUND, chainId)); } } + diff --git a/src/main/java/com/nynja/walletservice/config/EthereumConfig.java b/src/main/java/com/nynja/walletservice/config/EthereumConfig.java deleted file mode 100644 index 8a14aa9..0000000 --- a/src/main/java/com/nynja/walletservice/config/EthereumConfig.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.nynja.walletservice.config; - -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import okhttp3.OkHttpClient; -import org.springframework.context.ApplicationListener; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Scope; -import org.springframework.context.event.ContextRefreshedEvent; -import org.web3j.crypto.Credentials; -import org.web3j.protocol.Web3j; -import org.web3j.protocol.http.HttpService; -import org.web3j.utils.Async; - -@Configuration -@Getter -@Slf4j -public class EthereumConfig implements ApplicationListener { - - private ConfigValues conf; - private Credentials adminCredentials; - - public EthereumConfig(ConfigValues conf) { - this.conf = conf; - } - - @Override - public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) { - this.adminCredentials = Credentials.create(conf.getAdminPrivateKey()); - } - - @Bean - @Scope("singleton") - public Web3j web3j() { - log.info("BUILDING Web3j INSTANCE FOR URL: {}", conf.getClientUrl()); - - return Web3j.build(new HttpService( - conf.getClientUrl(), - new OkHttpClient.Builder().build(), - false - ), 1000L, Async.defaultExecutorService()); - } -} diff --git a/src/main/java/com/nynja/walletservice/config/Web3jWarmUp.java b/src/main/java/com/nynja/walletservice/config/Web3jWarmUp.java index c43cdf4..984ddfc 100644 --- a/src/main/java/com/nynja/walletservice/config/Web3jWarmUp.java +++ b/src/main/java/com/nynja/walletservice/config/Web3jWarmUp.java @@ -25,13 +25,13 @@ public class Web3jWarmUp implements ApplicationListener { @Override public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) { - log.info("Warming up the Web3j client"); + /*log.info("Warming up the Web3j client"); web3jProvider.get().ethGasPrice().sendAsync(); web3JService.getBalance(configValues.getAdminAddress()) .thenAcceptAsync(log::info) .exceptionally(ex -> { log.error(ex.getMessage(), ex); return null; - }); + });*/ } } diff --git a/src/main/java/com/nynja/walletservice/constant/Constants.java b/src/main/java/com/nynja/walletservice/constant/Constants.java index 6313d29..aeab19f 100644 --- a/src/main/java/com/nynja/walletservice/constant/Constants.java +++ b/src/main/java/com/nynja/walletservice/constant/Constants.java @@ -1,5 +1,7 @@ package com.nynja.walletservice.constant; +import java.util.function.Supplier; + /** * @author Oleg Zhymolokhov (oleg.zhimolokhov@dataart.com) */ @@ -43,6 +45,7 @@ public interface Constants { String SIGNED_TRANSACTION_SENT = "The signed transaction has been sent. Hash: {}"; String SIGNED_TRANSACTION_FAIL = "Failed to send signed transaction. Reason: %s"; String TRANSACTION_NOT_FOUND = "Transaction with the hash %s is not found"; + String CHAIN_ID_NOT_FOUND = "Wrong chainId (%s) or the configuration for this network is missing."; } interface ValidationMessages { @@ -51,4 +54,12 @@ public interface Constants { String HASH_VALIDATION_MESSAGE = "Hash should be correct"; } + static Supplier newExceptionWithMessage(String message, Object ... args) { + return () -> new RuntimeException(String.format(message, args)); + } + + static Supplier newExceptionWithMessage(String message) { + return () -> new RuntimeException(message); + } + } diff --git a/src/main/java/com/nynja/walletservice/controller/ContractController.java b/src/main/java/com/nynja/walletservice/controller/ContractController.java index 8382a69..c231f26 100644 --- a/src/main/java/com/nynja/walletservice/controller/ContractController.java +++ b/src/main/java/com/nynja/walletservice/controller/ContractController.java @@ -5,8 +5,10 @@ import com.nynja.walletservice.provider.ContractProvider; import com.nynja.walletservice.service.TokenService; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiResponse; +import org.hibernate.validator.constraints.NotBlank; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -16,6 +18,7 @@ import java.util.concurrent.CompletableFuture; import static com.nynja.walletservice.constant.Constants.RestApi.API_VERSION; import static com.nynja.walletservice.constant.Constants.RestApi.RETRIEVED; +import static com.nynja.walletservice.constant.Constants.ValidationMessages.CHAIN_ID_VALIDATION_MESSAGE; import static com.nynja.walletservice.controller.ControllerHelper.handleAndRespondAsync; /** @@ -24,6 +27,7 @@ import static com.nynja.walletservice.controller.ControllerHelper.handleAndRespo @RestController @RequestMapping("/contract") +@Validated public class ContractController { private final TokenService tokenService; @@ -38,24 +42,31 @@ public class ContractController { @ApiOperation(value = "Endpoint for NynjaCoin contract deployment into Blockchain network", httpMethod = "GET") @ApiResponse(code = 200, message = RETRIEVED) @GetMapping(API_VERSION + "/deploy") - public CompletableFuture> deployNynjaCoin() { - return tokenService.deployTokenContract().thenApplyAsync(ResponseEntity::ok); + public CompletableFuture> deployNynjaCoin( + @NotBlank(message = CHAIN_ID_VALIDATION_MESSAGE) @RequestParam String chainId + ) { + return tokenService.deployTokenContract(chainId).thenApplyAsync(ResponseEntity::ok); } @ApiOperation(value = "Endpoint for NynjaCoin contract address obtaining", httpMethod = "GET") @ApiResponse(code = 200, message = RETRIEVED) @GetMapping(API_VERSION + "/nyn-address") - public CompletableFuture> getCurrentNynAddress() { + public CompletableFuture> getCurrentNynAddress( + @NotBlank(message = CHAIN_ID_VALIDATION_MESSAGE) @RequestParam String chainId + ) { return handleAndRespondAsync(() -> NynContractAddressesDto.builder() .runtimeAddress(contractProvider.getNynjaAddress()) - .dbAddress(contractProvider.getDBContractAddress()) + .dbAddress(contractProvider.getDBContractAddress(chainId)) .build()); } @ApiOperation(value = "Endpoint for NynjaCoin contract address updating", httpMethod = "GET") @ApiResponse(code = 200, message = RETRIEVED) @GetMapping(API_VERSION + "/update-nyn-address") - public CompletableFuture> updateNynAddress(@RequestParam String address) { - return handleAndRespondAsync(() -> contractProvider.updateContractAddress(address)); + public CompletableFuture> updateNynAddress( + @NotBlank(message = CHAIN_ID_VALIDATION_MESSAGE) @RequestParam String chainId, + @RequestParam String address + ) { + return handleAndRespondAsync(() -> contractProvider.updateContractAddress(chainId, address)); } } diff --git a/src/main/java/com/nynja/walletservice/controller/EthereumController.java b/src/main/java/com/nynja/walletservice/controller/EthereumController.java index f205f4e..3156f6c 100644 --- a/src/main/java/com/nynja/walletservice/controller/EthereumController.java +++ b/src/main/java/com/nynja/walletservice/controller/EthereumController.java @@ -1,6 +1,6 @@ package com.nynja.walletservice.controller; -import com.nynja.walletservice.config.EthereumConfig; +import com.nynja.walletservice.config.ConfigValues; import com.nynja.walletservice.dto.TransactionResponseDto; import com.nynja.walletservice.service.Web3JService; import io.swagger.annotations.*; @@ -10,6 +10,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import org.web3j.crypto.Credentials; import org.web3j.utils.Convert; import java.math.BigDecimal; @@ -20,6 +21,7 @@ import static com.nynja.walletservice.constant.Constants.DataTypes.STRING; import static com.nynja.walletservice.constant.Constants.ParamTypes.Query; import static com.nynja.walletservice.constant.Constants.RestApi.*; import static com.nynja.walletservice.constant.Constants.ValidationMessages.ADDRESS_VALIDATION_MESSAGE; +import static com.nynja.walletservice.constant.Constants.ValidationMessages.CHAIN_ID_VALIDATION_MESSAGE; /** * @author Oleg Zhymolokhov (oleg.zhimolokhov@dataart.com) @@ -32,17 +34,19 @@ import static com.nynja.walletservice.constant.Constants.ValidationMessages.ADDR public class EthereumController { private final Web3JService web3JService; - private final EthereumConfig ethConfig; + private final ConfigValues conf; @Autowired - public EthereumController(Web3JService web3JService, EthereumConfig ethConfig) { + public EthereumController(Web3JService web3JService, ConfigValues conf) { this.web3JService = web3JService; - this.ethConfig = ethConfig; + this.conf = conf; } @GetMapping(API_VERSION + "/version") - public CompletableFuture> checkClientVersion() { - return web3JService.getClientVersion() + public CompletableFuture> checkClientVersion( + @NotBlank(message = CHAIN_ID_VALIDATION_MESSAGE) @RequestParam String chainId + ) { + return web3JService.getClientVersion(chainId) .thenApplyAsync(ResponseEntity::ok); } @@ -53,8 +57,11 @@ public class EthereumController { @ApiResponse(code = 400, message = REQUIRED_PARAM_MISSING) }) @GetMapping(API_VERSION + "/address-balance") - public CompletableFuture> getBalanceForAddress(@NotBlank(message = ADDRESS_VALIDATION_MESSAGE) @RequestParam String address) { - return web3JService.getBalance(address) + public CompletableFuture> getBalanceForAddress( + @NotBlank(message = CHAIN_ID_VALIDATION_MESSAGE) @RequestParam String chainId, + @NotBlank(message = ADDRESS_VALIDATION_MESSAGE) @RequestParam String address + ) { + return web3JService.getBalance(chainId, address) .thenApplyAsync(ResponseEntity::ok); } @@ -68,9 +75,14 @@ public class EthereumController { @ApiResponse(code = 400, message = REQUIRED_PARAM_MISSING) }) @GetMapping(API_VERSION + "/eth-send") - public CompletableFuture>> sendEthToAddress(@NotBlank(message = ADDRESS_VALIDATION_MESSAGE) @RequestParam String address, @RequestParam long amount) { + public CompletableFuture>> sendEthToAddress( + @NotBlank(message = CHAIN_ID_VALIDATION_MESSAGE) @RequestParam String chainId, + @NotBlank(message = ADDRESS_VALIDATION_MESSAGE) @RequestParam String address, + @RequestParam BigDecimal amount + ) { + Credentials adminCredentials = Credentials.create(conf.getNetworkProps(chainId).getAdminPrivateKey()); return web3JService - .sendEtherAsync(ethConfig.getAdminCredentials(), address, BigDecimal.valueOf(amount), Convert.Unit.ETHER) + .sendEtherAsync(chainId, adminCredentials, address, amount, Convert.Unit.ETHER) .thenApplyAsync(ResponseEntity::ok); } } diff --git a/src/main/java/com/nynja/walletservice/controller/TransactionController.java b/src/main/java/com/nynja/walletservice/controller/TransactionController.java index 4e7587a..ccddb97 100644 --- a/src/main/java/com/nynja/walletservice/controller/TransactionController.java +++ b/src/main/java/com/nynja/walletservice/controller/TransactionController.java @@ -52,7 +52,10 @@ public class TransactionController { @ApiResponse(code = 200, message = RETRIEVED) }) @GetMapping(API_VERSION + "/balance-for-address") - public CompletableFuture> getBalanceByAddress(@NotBlank(message = ADDRESS_VALIDATION_MESSAGE) @RequestParam String walletAddress) { + public CompletableFuture> getBalanceByAddress( + @NotBlank(message = CHAIN_ID_VALIDATION_MESSAGE) @RequestParam String chainId, + @NotBlank(message = ADDRESS_VALIDATION_MESSAGE) @RequestParam String walletAddress + ) { return tokenService.tokenBalanceByAddress(walletAddress).thenApplyAsync(ResponseEntity::ok); } @@ -66,14 +69,20 @@ public class TransactionController { @ApiOperation(value = "Endpoint for getting transaction info by hash", httpMethod = "GET") @ApiResponse(code = 200, message = RETRIEVED) @GetMapping(API_VERSION + "/single-transaction-info") - public CompletableFuture> getSingleTransactionInfo(@NotBlank(message = HASH_VALIDATION_MESSAGE) @RequestParam String hash) { + public CompletableFuture> getSingleTransactionInfo( + @NotBlank(message = CHAIN_ID_VALIDATION_MESSAGE) @RequestParam String chainId, + @NotBlank(message = HASH_VALIDATION_MESSAGE) @RequestParam String hash + ) { return web3JService.getSingleTransactionInfo(hash).thenApplyAsync(ResponseEntity::ok); } @ApiOperation(value = "Endpoint for obtaining the latest nonce", httpMethod = "GET") @ApiResponse(code = 200, message = RETRIEVED) @GetMapping(API_VERSION + "/get-transaction-count") - public CompletableFuture> getTransactionCount(@NotBlank(message = ADDRESS_VALIDATION_MESSAGE) @RequestParam String address) { + public CompletableFuture> getTransactionCount( + @NotBlank(message = CHAIN_ID_VALIDATION_MESSAGE) @RequestParam String chainId, + @NotBlank(message = ADDRESS_VALIDATION_MESSAGE) @RequestParam String address + ) { return web3JService.getTransactionCount(address).thenApplyAsync(ResponseEntity::ok); } diff --git a/src/main/java/com/nynja/walletservice/dto/AccountScanDto.java b/src/main/java/com/nynja/walletservice/dto/AccountScanDto.java index ddf9d39..9ca81d2 100644 --- a/src/main/java/com/nynja/walletservice/dto/AccountScanDto.java +++ b/src/main/java/com/nynja/walletservice/dto/AccountScanDto.java @@ -6,6 +6,8 @@ import lombok.Data; import lombok.NoArgsConstructor; import org.hibernate.validator.constraints.NotBlank; +import static com.nynja.walletservice.constant.Constants.ValidationMessages.CHAIN_ID_VALIDATION_MESSAGE; + @Data @NoArgsConstructor @AllArgsConstructor @@ -16,4 +18,6 @@ public class AccountScanDto { private String publicKey; @NotBlank(message = "ChainCode should be present.") private String chainCode; + @NotBlank(message = CHAIN_ID_VALIDATION_MESSAGE) + private String chainId; } diff --git a/src/main/java/com/nynja/walletservice/dto/EstimateGasRequestDto.java b/src/main/java/com/nynja/walletservice/dto/EstimateGasRequestDto.java index 78cdc33..adfd253 100644 --- a/src/main/java/com/nynja/walletservice/dto/EstimateGasRequestDto.java +++ b/src/main/java/com/nynja/walletservice/dto/EstimateGasRequestDto.java @@ -15,4 +15,5 @@ public class EstimateGasRequestDto { private BigInteger gas; private BigInteger value; private String data; + private String chainId; } diff --git a/src/main/java/com/nynja/walletservice/dto/SignedTransactionDto.java b/src/main/java/com/nynja/walletservice/dto/SignedTransactionDto.java index 01537d7..9fe0a6d 100644 --- a/src/main/java/com/nynja/walletservice/dto/SignedTransactionDto.java +++ b/src/main/java/com/nynja/walletservice/dto/SignedTransactionDto.java @@ -16,4 +16,6 @@ public class SignedTransactionDto { @NotBlank private String signedTransaction; + @NotBlank + private String chainId; } diff --git a/src/main/java/com/nynja/walletservice/dto/WalletImportDto.java b/src/main/java/com/nynja/walletservice/dto/WalletImportDto.java index 58e417c..17ffbf0 100644 --- a/src/main/java/com/nynja/walletservice/dto/WalletImportDto.java +++ b/src/main/java/com/nynja/walletservice/dto/WalletImportDto.java @@ -9,6 +9,8 @@ import org.hibernate.validator.constraints.NotBlank; import javax.validation.constraints.NotNull; +import static com.nynja.walletservice.constant.Constants.ValidationMessages.CHAIN_ID_VALIDATION_MESSAGE; + @Data @NoArgsConstructor @AllArgsConstructor @@ -21,4 +23,7 @@ public class WalletImportDto { @NotNull(message = "Currency should be provided.") private Currency currency; + + @NotBlank(message = CHAIN_ID_VALIDATION_MESSAGE) + private String chainId; } diff --git a/src/main/java/com/nynja/walletservice/model/ContractData.java b/src/main/java/com/nynja/walletservice/model/ContractData.java index c4f2081..3de1160 100644 --- a/src/main/java/com/nynja/walletservice/model/ContractData.java +++ b/src/main/java/com/nynja/walletservice/model/ContractData.java @@ -12,5 +12,6 @@ import javax.persistence.Entity; @Builder public class ContractData extends MainEntity { + private String chainId; private String address; } diff --git a/src/main/java/com/nynja/walletservice/model/FundedAddress.java b/src/main/java/com/nynja/walletservice/model/FundedAddress.java index 5272637..8d8ba52 100644 --- a/src/main/java/com/nynja/walletservice/model/FundedAddress.java +++ b/src/main/java/com/nynja/walletservice/model/FundedAddress.java @@ -13,6 +13,7 @@ import javax.persistence.Entity; @AllArgsConstructor @NoArgsConstructor public class FundedAddress extends MainEntity { + private String chainId; private String address; private String amount; } diff --git a/src/main/java/com/nynja/walletservice/model/network/NetworkProperties.java b/src/main/java/com/nynja/walletservice/model/network/NetworkProperties.java new file mode 100644 index 0000000..fc104fd --- /dev/null +++ b/src/main/java/com/nynja/walletservice/model/network/NetworkProperties.java @@ -0,0 +1,17 @@ +package com.nynja.walletservice.model.network; + +import lombok.Data; + +import java.math.BigInteger; + +@Data +public class NetworkProperties { + private String url; + private String blockExplorerUrl; + private String blockExplorerApiKey; + private String adminAddress; + private String adminPrivateKey; + private String nynjaCoinAddress; + private BigInteger gasPrice; + private BigInteger gasLimit; +} diff --git a/src/main/java/com/nynja/walletservice/provider/ContractProvider.java b/src/main/java/com/nynja/walletservice/provider/ContractProvider.java index 2a4efd6..58f096e 100644 --- a/src/main/java/com/nynja/walletservice/provider/ContractProvider.java +++ b/src/main/java/com/nynja/walletservice/provider/ContractProvider.java @@ -2,12 +2,12 @@ package com.nynja.walletservice.provider; import com.nynja.walletservice.config.ConfigValues; import com.nynja.walletservice.model.ContractData; +import com.nynja.walletservice.model.network.NetworkProperties; import com.nynja.walletservice.repository.ContractDataRepository; import com.nynja.walletservice.wrapper.NynjaCoin; -import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.web3j.crypto.Credentials; import org.web3j.protocol.Web3j; @@ -17,6 +17,8 @@ import org.web3j.tx.RawTransactionManager; import java.util.Optional; import java.util.concurrent.CompletableFuture; +import static com.nynja.walletservice.constant.Constants.newExceptionWithMessage; + /** * @author Oleg Zhymolokhov (oleg.zhimolokhov@dataart.com) */ @@ -25,10 +27,6 @@ import java.util.concurrent.CompletableFuture; @Slf4j public class ContractProvider { - @Value("${ethereum.contract.nynja-coin-address}") - @Getter - private String nynjaAddress; - private final ConfigValues conf; private final Web3jProvider web3jProvider; private final ContractDataRepository contractRepository; @@ -40,48 +38,54 @@ public class ContractProvider { this.contractRepository = contractRepository; } - private RawTransactionManager getTrManager(Credentials credentials) { - return new RawTransactionManager(web3jProvider.get(), credentials, conf.getAttempts(), conf.getInterval()); + private RawTransactionManager getTrManager(Web3j web3j, Credentials credentials) { + return new RawTransactionManager(web3j, credentials, conf.getAttempts(), conf.getInterval()); } - public CompletableFuture deployNynjaCoinContract() { - try { - return NynjaCoin.deploy( - web3jProvider.get(), - getTrManager(Credentials.create(conf.getAdminPrivateKey())), - conf.gasPrice(), - conf.gasLimit() - ).sendAsync() - .thenApplyAsync(Contract::getContractAddress) - .thenApplyAsync(this::persistContractAddress); - } catch (Exception e) { - throw new RuntimeException("Failed to deploy the Nynja contract.", e); - } + public CompletableFuture deployNynjaCoinContract(String chainId) { + NetworkProperties networkProperties = conf.getNetworkProps(chainId); + + return Optional.ofNullable(web3jProvider.getInstance(chainId)) + .map(web3j -> { + try { + return NynjaCoin.deploy( + web3j, + getTrManager(web3j, Credentials.create(networkProperties.getAdminPrivateKey())), + networkProperties.getGasPrice(), + networkProperties.getGasLimit() + ).sendAsync() + .thenApplyAsync(Contract::getContractAddress) + .thenApplyAsync(address -> { + networkProperties.setNynjaCoinAddress(address); + return persistContractAddress(chainId, address); + }); + } catch (Exception e) { + throw new RuntimeException("Failed to deploy the Nynja contract.", e); + } + }).orElseThrow(newExceptionWithMessage("Wrong value or the web3j is not configured for this chainId.")); } - private String persistContractAddress(String address) { - this.nynjaAddress = address; - - ContractData data = ContractData.builder().address(address).build(); - data.setId("1L"); - - return contractRepository.save(data).getAddress(); + private String persistContractAddress(String chainId, String address) { + return contractRepository.save(ContractData.builder().chainId(chainId).address(address).build()).getAddress(); } - public NynjaCoin getNynjaCoin() { - return getNynjaCoin(Credentials.create(conf.getAdminPrivateKey())); + public NynjaCoin getNynjaCoin(String chainId) { + return getNynjaCoin(chainId, Credentials.create(conf.getNetworkProps(chainId).getAdminPrivateKey())); } - public NynjaCoin getNynjaCoin(Credentials credentials) { - return loadNynjaCoin(web3jProvider.get(), credentials); + public NynjaCoin getNynjaCoin(String chainId, Credentials credentials) { + return loadNynjaCoin(web3jProvider.getInstance(chainId), chainId, credentials); } - private NynjaCoin loadNynjaCoin(Web3j web3j, Credentials credentials) { - String address = Optional.ofNullable(contractRepository.findOne("1L")) + private NynjaCoin loadNynjaCoin(Web3j web3j, String chainId, Credentials credentials) { + NetworkProperties networkProperties = conf.getNetworkProps(chainId); + + String address = contractRepository.findByChainId(chainId) .map(ContractData::getAddress) - .orElseGet(() -> Optional.of(nynjaAddress) - .filter(propertyAddress -> !propertyAddress.equals("")) - .map(this::persistContractAddress) + .orElseGet(() -> Optional.of(networkProperties) + .map(NetworkProperties::getNynjaCoinAddress) + .filter(StringUtils::isNotBlank) + .map(contractAddress -> persistContractAddress(chainId, contractAddress)) .orElseThrow(() -> { log.error("Deploy Nynja contract first and put it's address into application.yml"); return new RuntimeException("Nynja Contract is not deployed!"); @@ -90,21 +94,23 @@ public class ContractProvider { return NynjaCoin.load( address, web3j, - getTrManager(credentials), - conf.gasPrice(), - conf.gasLimit() + getTrManager(web3j, credentials), + networkProperties.getGasPrice(), + networkProperties.getGasLimit() ); } - public String getDBContractAddress() { - return contractRepository.findOne("1L").getAddress(); + public String getDBContractAddress(String chainId) { + return contractRepository.findByChainId(chainId) + .map(ContractData::getAddress) + .orElse(null); } - public String updateContractAddress(String nynjaAddress) { - return Optional.ofNullable(contractRepository.findOne("1L")) - .map(address -> { - address.setAddress(nynjaAddress); - return contractRepository.save(address).getAddress(); - }).orElseThrow(() -> new RuntimeException("There is no contract address in the DB")); + public String updateContractAddress(String chainId, String nynjaAddress) { + return contractRepository.findByChainId(chainId) + .map(contractData -> { + contractData.setAddress(nynjaAddress); + return contractRepository.save(contractData).getAddress(); + }).orElseThrow(newExceptionWithMessage("There is no contract address in the DB")); } } diff --git a/src/main/java/com/nynja/walletservice/provider/Web3jProvider.java b/src/main/java/com/nynja/walletservice/provider/Web3jProvider.java index 4c8de43..29ff677 100644 --- a/src/main/java/com/nynja/walletservice/provider/Web3jProvider.java +++ b/src/main/java/com/nynja/walletservice/provider/Web3jProvider.java @@ -1,12 +1,53 @@ package com.nynja.walletservice.provider; -import org.springframework.beans.factory.annotation.Lookup; +import com.nynja.walletservice.config.ConfigValues; +import lombok.extern.slf4j.Slf4j; +import okhttp3.OkHttpClient; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.stereotype.Component; import org.web3j.protocol.Web3j; +import org.web3j.protocol.http.HttpService; +import org.web3j.utils.Async; + +import java.util.HashMap; +import java.util.Map; + +import static org.apache.commons.lang3.StringUtils.isBlank; @Component -public abstract class Web3jProvider { +@Slf4j +public class Web3jProvider implements ApplicationListener { + + private Map instances; + + private ConfigValues conf; + + public Web3jProvider(ConfigValues conf) { + this.conf = conf; + } + + @Override + public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) { + instances = new HashMap<>(); + conf.getNetworks().forEach((k, v) -> instances.put(k, buildInstance(v.getUrl()))); + } + + private Web3j buildInstance(String clientUrl) { + log.info("BUILDING Web3j INSTANCE FOR URL: {}", clientUrl); + + return Web3j.build(new HttpService( + clientUrl, + new OkHttpClient.Builder().build(), + false + ), 1000L, Async.defaultExecutorService()); + } - @Lookup - public abstract Web3j get(); + public Web3j getInstance(String chainId) { + if (isBlank(chainId)) { + return instances.get("4"); + } else { + return instances.get(chainId); + } + } } diff --git a/src/main/java/com/nynja/walletservice/repository/ContractDataRepository.java b/src/main/java/com/nynja/walletservice/repository/ContractDataRepository.java index 81134cc..8adf5d4 100644 --- a/src/main/java/com/nynja/walletservice/repository/ContractDataRepository.java +++ b/src/main/java/com/nynja/walletservice/repository/ContractDataRepository.java @@ -3,5 +3,9 @@ package com.nynja.walletservice.repository; import com.nynja.walletservice.model.ContractData; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; + public interface ContractDataRepository extends JpaRepository { + + Optional findByChainId(String chainId); } diff --git a/src/main/java/com/nynja/walletservice/repository/FundedAddressRepository.java b/src/main/java/com/nynja/walletservice/repository/FundedAddressRepository.java index 0ff2a78..3746f72 100644 --- a/src/main/java/com/nynja/walletservice/repository/FundedAddressRepository.java +++ b/src/main/java/com/nynja/walletservice/repository/FundedAddressRepository.java @@ -5,5 +5,5 @@ import org.springframework.data.jpa.repository.JpaRepository; public interface FundedAddressRepository extends JpaRepository { - boolean existsByAddress(String address); + boolean existsByChainIdAndAddress(String chainId, String address); } diff --git a/src/main/java/com/nynja/walletservice/service/TokenService.java b/src/main/java/com/nynja/walletservice/service/TokenService.java index 491595f..ddd880e 100644 --- a/src/main/java/com/nynja/walletservice/service/TokenService.java +++ b/src/main/java/com/nynja/walletservice/service/TokenService.java @@ -1,6 +1,6 @@ package com.nynja.walletservice.service; -import com.nynja.walletservice.config.EthereumConfig; +import com.nynja.walletservice.config.ConfigValues; import com.nynja.walletservice.dto.TransactionResponseDto; import com.nynja.walletservice.model.FundedAddress; import com.nynja.walletservice.provider.ContractProvider; @@ -9,6 +9,7 @@ import com.nynja.walletservice.service.operation.TokenOperationFactory; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.web3j.crypto.Credentials; import org.web3j.protocol.core.methods.response.TransactionReceipt; import org.web3j.utils.Convert; @@ -18,6 +19,8 @@ import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.function.Function; +import static com.nynja.walletservice.constant.Constants.LogMessages.CHAIN_ID_NOT_FOUND; +import static com.nynja.walletservice.constant.Constants.newExceptionWithMessage; import static java.lang.String.format; /** @@ -32,70 +35,75 @@ public class TokenService { private final ContractProvider contractProvider; private final TokenOperationFactory operationFactory; - private final EthereumConfig ethConfig; private final Web3JService web3JService; private final FundedAddressRepository fundedAddressRepository; + private final ConfigValues conf; @Autowired public TokenService( - ContractProvider contractProvider, TokenOperationFactory operationFactory, - EthereumConfig ethConfig, Web3JService web3JService, FundedAddressRepository fundedAddressRepository + ContractProvider contractProvider, TokenOperationFactory operationFactory, Web3JService web3JService, + FundedAddressRepository fundedAddressRepository, ConfigValues conf ) { this.contractProvider = contractProvider; this.operationFactory = operationFactory; - this.ethConfig = ethConfig; this.web3JService = web3JService; this.fundedAddressRepository = fundedAddressRepository; + this.conf = conf; } - public CompletableFuture deployTokenContract() { - return contractProvider.deployNynjaCoinContract(); + public CompletableFuture deployTokenContract(String chainId) { + return contractProvider.deployNynjaCoinContract(chainId); } - public CompletableFuture tokenBalanceByAddress(String address) { - return operationFactory.balanceOperation(ethConfig.getAdminCredentials(), address) - .execute() - .thenApplyAsync(balance -> { - if(Objects.isNull(balance)) { - throw new RuntimeException("SmartContract returned empty value"); - } else { - return balance; - } - }); + public CompletableFuture tokenBalanceByAddress(String chainId, String address) { + return conf.getNetworkProps(chainId) + .map(networkProperties -> + operationFactory.balanceOperation(Credentials.create(networkProperties.getAdminPrivateKey()), address) + .execute(chainId) + .thenApplyAsync(balance -> { + if(Objects.isNull(balance)) { + throw new RuntimeException("SmartContract returned empty value"); + } else { + return balance; + } + }) + ).orElseThrow(newExceptionWithMessage(CHAIN_ID_NOT_FOUND, chainId)); } //TODO For demo purposes. Remove after. - public CompletableFuture> handleAccountCreation(String accountAddress) { - checkIfFunded(accountAddress); - - return mint(accountAddress, AMOUNT) - .thenApplyAsync(transactionResponseDto -> { - fundedAddressRepository.save(new FundedAddress(accountAddress, transactionResponseDto.getValue())); - return transactionResponseDto; - }).thenApplyAsync(transactionResponseDto -> { - web3JService.sendEtherAsync( - ethConfig.getAdminCredentials(), - accountAddress, - new BigDecimal(500), - Convert.Unit.FINNEY - ); - - return transactionResponseDto; - }); + public CompletableFuture> handleAccountCreation(String chainId, String accountAddress) { + checkIfFunded(chainId, accountAddress); + + return conf.getNetworkProps(chainId) + .map(networkProperties -> mint(chainId, Credentials.create(networkProperties.getAdminPrivateKey()), accountAddress, AMOUNT) + .thenApplyAsync(transactionResponseDto -> { + fundedAddressRepository.save(new FundedAddress(chainId, accountAddress, transactionResponseDto.getValue())); + return transactionResponseDto; + }).thenApplyAsync(transactionResponseDto -> { + web3JService.sendEtherAsync( + Credentials.create(networkProperties.getAdminPrivateKey()), + accountAddress, + new BigDecimal(500), + Convert.Unit.FINNEY + ); + + return transactionResponseDto; + }) + ).orElseThrow(newExceptionWithMessage(CHAIN_ID_NOT_FOUND, chainId)); } - private void checkIfFunded(String address) { - if (fundedAddressRepository.existsByAddress(address)) { + private void checkIfFunded(String chainId, String address) { + if (fundedAddressRepository.existsByChainIdAndAddress(chainId, address)) { throw new RuntimeException(String.format("The Address %s has already been funded", address)); } } - public CompletableFuture> mint(String address, String amount) { - return operationFactory.mintOperation(ethConfig.getAdminCredentials(), address, amount).execute() + public CompletableFuture> mint(String chainId, Credentials adminCredentials, String address, String amount) { + return operationFactory.mintOperation(adminCredentials, address, amount).execute(chainId) .thenApplyAsync(handleTransaction(amount, format("Error during token minting. Address %s", address))); } - public static Function> handleTransaction(T value, String errorMessage) { + static Function> handleTransaction(T value, String errorMessage) { return tR -> { if (!tR.getLogs().isEmpty()) return new TransactionResponseDto<>(tR.getTransactionHash(), value); diff --git a/src/main/java/com/nynja/walletservice/service/Web3JService.java b/src/main/java/com/nynja/walletservice/service/Web3JService.java index d24f140..36b9f23 100644 --- a/src/main/java/com/nynja/walletservice/service/Web3JService.java +++ b/src/main/java/com/nynja/walletservice/service/Web3JService.java @@ -6,6 +6,7 @@ import com.nynja.walletservice.dto.EstimateGasResponseDto; import com.nynja.walletservice.dto.TransactionResponseDto; import com.nynja.walletservice.exception.TransactionFailedException; import com.nynja.walletservice.exception.TransactionNotFoundException; +import com.nynja.walletservice.model.network.NetworkProperties; import com.nynja.walletservice.provider.Web3jProvider; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -43,17 +44,21 @@ public class Web3JService { this.conf = conf; } - public CompletableFuture getClientVersion() { - return web3j().web3ClientVersion() + public CompletableFuture getClientVersion(String chainId) { + return web3j(chainId).web3ClientVersion() .sendAsync() .thenApplyAsync(this::validateResponse) .thenApplyAsync(Web3ClientVersion::getWeb3ClientVersion); } - public CompletableFuture> sendEtherAsync(Credentials credentials, String toAddress, - BigDecimal value, Convert.Unit currency) { - return new Transfer(web3j(), new RawTransactionManager(web3j(), credentials, conf.getAttempts(), conf.getInterval())) - .sendFunds(toAddress, value, currency, conf.gasPrice(), conf.gasLimit()) + public CompletableFuture> sendEtherAsync( + String chainId, Credentials credentials, String toAddress, BigDecimal value, Convert.Unit currency + ) { + final Web3j web3j = web3j(chainId); + final NetworkProperties networkProperties = conf.getNetworkProps(chainId); + + return new Transfer(web3j, new RawTransactionManager(web3j, credentials, conf.getAttempts(), conf.getInterval())) + .sendFunds(toAddress, value, currency, networkProperties.getGasPrice(), networkProperties.getGasLimit()) .sendAsync() .thenApplyAsync(tr -> { log.info(ETH_TRANSFERRED, toAddress, value, currency); @@ -63,16 +68,16 @@ public class Web3JService { }); } - public CompletableFuture getBalance(String address) { - return web3j().ethGetBalance(address, DefaultBlockParameterName.LATEST) + public CompletableFuture getBalance(String chainId, String address) { + return web3j(chainId).ethGetBalance(address, DefaultBlockParameterName.LATEST) .sendAsync() .thenApplyAsync(this::validateResponse) .thenApplyAsync(EthGetBalance::getBalance) .thenApplyAsync(BigInteger::toString); } - public CompletableFuture> sendSignedTransaction(String transaction) { - return web3j().ethSendRawTransaction(transaction) + public CompletableFuture> sendSignedTransaction(String chainId, String transaction) { + return web3j(chainId).ethSendRawTransaction(transaction) .sendAsync() .thenApplyAsync(this::validateResponse) .thenApplyAsync(ethSendTransaction -> { @@ -83,23 +88,25 @@ public class Web3JService { }); } - public CompletableFuture getSingleTransactionInfo(String hash) { - return web3j().ethGetTransactionReceipt(hash) + public CompletableFuture getSingleTransactionInfo(String chainId, String hash) { + return web3j(chainId).ethGetTransactionReceipt(hash) .sendAsync() .thenApplyAsync(this::validateResponse) .thenApplyAsync(EthGetTransactionReceipt::getTransactionReceipt) .thenApplyAsync(trOpt -> trOpt.orElseThrow(() -> new TransactionNotFoundException(hash))); } - public CompletableFuture getTransactionCount(String address) { - return web3j().ethGetTransactionCount(address, PENDING) + public CompletableFuture getTransactionCount(String chainId, String address) { + return web3j(chainId).ethGetTransactionCount(address, PENDING) .sendAsync() .thenApplyAsync(this::validateResponse) .thenApplyAsync(EthGetTransactionCount::getTransactionCount); } - public CompletableFuture estimateGasPrice(EstimateGasRequestDto dto) { - return web3j().ethEstimateGas(new org.web3j.protocol.core.methods.request.Transaction( + public CompletableFuture estimateGasPrice(String chainId, EstimateGasRequestDto dto) { + final Web3j web3j = web3j(chainId); + + return web3j.ethEstimateGas(new org.web3j.protocol.core.methods.request.Transaction( dto.getFrom(), null, null, @@ -109,7 +116,7 @@ public class Web3JService { dto.getData() )).sendAsync() .thenApplyAsync(this::validateResponse) - .thenComposeAsync(ethEstimateGas -> web3j().ethGasPrice() + .thenComposeAsync(ethEstimateGas -> web3j.ethGasPrice() .sendAsync() .thenApplyAsync(this::validateResponse) .thenApplyAsync(ethGasPrice -> EstimateGasResponseDto.builder() @@ -119,14 +126,14 @@ public class Web3JService { )); } - public CompletableFuture getCurrentBlockNumber() { - return web3j().ethBlockNumber().sendAsync() + public CompletableFuture getCurrentBlockNumber(String chainId) { + return web3j(chainId).ethBlockNumber().sendAsync() .thenApplyAsync(this::validateResponse) .thenApplyAsync(EthBlockNumber::getBlockNumber); } - public CompletableFuture getCurrentBlockWithMargin() { - return getCurrentBlockNumber() + public CompletableFuture getCurrentBlockWithMargin(String chainId) { + return getCurrentBlockNumber(chainId) .thenApplyAsync(blockNumber -> blockNumber.compareTo(conf.getBlockSubscriptionMargin()) < 0 ? blockNumber : blockNumber.subtract(conf.getBlockSubscriptionMargin()) @@ -143,7 +150,7 @@ public class Web3JService { return response; } - public Web3j web3j() { - return web3jProvider.get(); + public Web3j web3j(String chainId) { + return web3jProvider.getInstance(chainId); } } diff --git a/src/main/java/com/nynja/walletservice/service/operation/EthOperation.java b/src/main/java/com/nynja/walletservice/service/operation/EthOperation.java index 9e051ba..f37f171 100644 --- a/src/main/java/com/nynja/walletservice/service/operation/EthOperation.java +++ b/src/main/java/com/nynja/walletservice/service/operation/EthOperation.java @@ -3,5 +3,5 @@ package com.nynja.walletservice.service.operation; import java.util.concurrent.CompletableFuture; public interface EthOperation { - CompletableFuture execute(); + CompletableFuture execute(String chainId); } diff --git a/src/main/java/com/nynja/walletservice/service/operation/TokenOperation.java b/src/main/java/com/nynja/walletservice/service/operation/TokenOperation.java index 6dd4225..44233a9 100644 --- a/src/main/java/com/nynja/walletservice/service/operation/TokenOperation.java +++ b/src/main/java/com/nynja/walletservice/service/operation/TokenOperation.java @@ -19,8 +19,8 @@ public abstract class TokenOperation implements EthOperation { this.senderCredentials = senderCredentials; } - public CompletableFuture execute() { - NynjaCoin token = contractProvider.getNynjaCoin(senderCredentials); + public CompletableFuture execute(String chainId) { + NynjaCoin token = contractProvider.getNynjaCoin(chainId, senderCredentials); return execute(token); } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 011ecda..9c75a7d 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -18,13 +18,23 @@ web3j: attempts: 150 interval: 1000 ethereum: - client: - url: http://35.242.240.78:8545 - admin: - address: "0xB9BbCB84B654316D8E23383434c4EC66A97038ED" - private-key: "3e3a8b9c6e92808a1c683aa9458f90cfbcf5c19bed87e443b82fc7814a134796" - contract: - # First deploy a contract and then place/replace it's address into this property + networks: + 1: + url: http://35.242.240.78:8545 + block-explorer-url: http://api.etherscan.io/api + block-explorer-api-key: "K8J2W58EGI3EIR2FI8SYGW7FR1QKV5KDBM" + admin-address: "0xB9BbCB84B654316D8E23383434c4EC66A97038ED" + admin-private-key: "3e3a8b9c6e92808a1c683aa9458f90cfbcf5c19bed87e443b82fc7814a134796" + nynja-coin-address: "0xF36c7AdAd65c39A4848F3c85001F67f31d00d207" + gas-price: 41000000000 + gas-limit: 400000 + + 4: + url: http://35.242.240.78:8545 + block-explorer-url: http://api-rinkeby.etherscan.io/api + block-explorer-api-key: "K8J2W58EGI3EIR2FI8SYGW7FR1QKV5KDBM" + admin-address: "0xB9BbCB84B654316D8E23383434c4EC66A97038ED" + admin-private-key: "3e3a8b9c6e92808a1c683aa9458f90cfbcf5c19bed87e443b82fc7814a134796" nynja-coin-address: "0xF36c7AdAd65c39A4848F3c85001F67f31d00d207" gas-price: 41000000000 gas-limit: 400000 @@ -32,11 +42,6 @@ ethereum: margin: 20 wallet-discovery: search-job-pool-size: 2 -etherscan: - #url: http://api.etherscan.io/api - #url: http://api-ropsten.etherscan.io/api - url: http://api-rinkeby.etherscan.io/api - apikey: "K8J2W58EGI3EIR2FI8SYGW7FR1QKV5KDBM" message-broker: url: "http://dev2.ci.nynja.net/publish" topic: "/transfer-" -- GitLab From cf9e19dd4b9759706f247f247275518047061dad Mon Sep 17 00:00:00 2001 From: Oleg Zhymolokhov Date: Wed, 28 Nov 2018 15:54:45 +0200 Subject: [PATCH 2/6] multiple-networks-update: Intermediate commit. Changes to service and controller layers. --- .../controller/ContractController.java | 5 +- .../provider/ContractProvider.java | 8 +++ .../walletservice/service/TokenService.java | 52 +++++++++---------- 3 files changed, 33 insertions(+), 32 deletions(-) diff --git a/src/main/java/com/nynja/walletservice/controller/ContractController.java b/src/main/java/com/nynja/walletservice/controller/ContractController.java index c231f26..e81d57b 100644 --- a/src/main/java/com/nynja/walletservice/controller/ContractController.java +++ b/src/main/java/com/nynja/walletservice/controller/ContractController.java @@ -54,10 +54,7 @@ public class ContractController { public CompletableFuture> getCurrentNynAddress( @NotBlank(message = CHAIN_ID_VALIDATION_MESSAGE) @RequestParam String chainId ) { - return handleAndRespondAsync(() -> NynContractAddressesDto.builder() - .runtimeAddress(contractProvider.getNynjaAddress()) - .dbAddress(contractProvider.getDBContractAddress(chainId)) - .build()); + return handleAndRespondAsync(() -> contractProvider.getCurrentContractAddresses(chainId)); } @ApiOperation(value = "Endpoint for NynjaCoin contract address updating", httpMethod = "GET") diff --git a/src/main/java/com/nynja/walletservice/provider/ContractProvider.java b/src/main/java/com/nynja/walletservice/provider/ContractProvider.java index 58f096e..c646617 100644 --- a/src/main/java/com/nynja/walletservice/provider/ContractProvider.java +++ b/src/main/java/com/nynja/walletservice/provider/ContractProvider.java @@ -1,6 +1,7 @@ package com.nynja.walletservice.provider; import com.nynja.walletservice.config.ConfigValues; +import com.nynja.walletservice.dto.NynContractAddressesDto; import com.nynja.walletservice.model.ContractData; import com.nynja.walletservice.model.network.NetworkProperties; import com.nynja.walletservice.repository.ContractDataRepository; @@ -100,6 +101,13 @@ public class ContractProvider { ); } + public NynContractAddressesDto getCurrentContractAddresses(String chainId) { + return NynContractAddressesDto.builder() + .runtimeAddress(conf.getNetworkProps(chainId).getNynjaCoinAddress()) + .dbAddress(getDBContractAddress(chainId)) + .build(); + } + public String getDBContractAddress(String chainId) { return contractRepository.findByChainId(chainId) .map(ContractData::getAddress) diff --git a/src/main/java/com/nynja/walletservice/service/TokenService.java b/src/main/java/com/nynja/walletservice/service/TokenService.java index ddd880e..2a4dde6 100644 --- a/src/main/java/com/nynja/walletservice/service/TokenService.java +++ b/src/main/java/com/nynja/walletservice/service/TokenService.java @@ -3,6 +3,7 @@ package com.nynja.walletservice.service; import com.nynja.walletservice.config.ConfigValues; import com.nynja.walletservice.dto.TransactionResponseDto; import com.nynja.walletservice.model.FundedAddress; +import com.nynja.walletservice.model.network.NetworkProperties; import com.nynja.walletservice.provider.ContractProvider; import com.nynja.walletservice.repository.FundedAddressRepository; import com.nynja.walletservice.service.operation.TokenOperationFactory; @@ -19,8 +20,6 @@ import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.function.Function; -import static com.nynja.walletservice.constant.Constants.LogMessages.CHAIN_ID_NOT_FOUND; -import static com.nynja.walletservice.constant.Constants.newExceptionWithMessage; import static java.lang.String.format; /** @@ -56,40 +55,37 @@ public class TokenService { } public CompletableFuture tokenBalanceByAddress(String chainId, String address) { - return conf.getNetworkProps(chainId) - .map(networkProperties -> - operationFactory.balanceOperation(Credentials.create(networkProperties.getAdminPrivateKey()), address) - .execute(chainId) - .thenApplyAsync(balance -> { - if(Objects.isNull(balance)) { - throw new RuntimeException("SmartContract returned empty value"); - } else { - return balance; - } - }) - ).orElseThrow(newExceptionWithMessage(CHAIN_ID_NOT_FOUND, chainId)); + return operationFactory.balanceOperation(Credentials.create(conf.getNetworkProps(chainId).getAdminPrivateKey()), address) + .execute(chainId) + .thenApplyAsync(balance -> { + if(Objects.isNull(balance)) { + throw new RuntimeException("SmartContract returned empty value"); + } else { + return balance; + } + }); } //TODO For demo purposes. Remove after. public CompletableFuture> handleAccountCreation(String chainId, String accountAddress) { checkIfFunded(chainId, accountAddress); - return conf.getNetworkProps(chainId) - .map(networkProperties -> mint(chainId, Credentials.create(networkProperties.getAdminPrivateKey()), accountAddress, AMOUNT) - .thenApplyAsync(transactionResponseDto -> { - fundedAddressRepository.save(new FundedAddress(chainId, accountAddress, transactionResponseDto.getValue())); - return transactionResponseDto; - }).thenApplyAsync(transactionResponseDto -> { - web3JService.sendEtherAsync( - Credentials.create(networkProperties.getAdminPrivateKey()), - accountAddress, - new BigDecimal(500), - Convert.Unit.FINNEY - ); + NetworkProperties networkProperties = conf.getNetworkProps(chainId); + mint(chainId, Credentials.create(networkProperties.getAdminPrivateKey()), accountAddress, AMOUNT) + .thenApplyAsync(transactionResponseDto -> { + fundedAddressRepository.save(new FundedAddress(chainId, accountAddress, transactionResponseDto.getValue())); return transactionResponseDto; - }) - ).orElseThrow(newExceptionWithMessage(CHAIN_ID_NOT_FOUND, chainId)); + } + ).thenAcceptAsync(transactionResponseDto -> web3JService.sendEtherAsync( + chainId, + Credentials.create(networkProperties.getAdminPrivateKey()), + accountAddress, + new BigDecimal(500), + Convert.Unit.FINNEY + )); + + return CompletableFuture.completedFuture(new TransactionResponseDto<>("", AMOUNT)); } private void checkIfFunded(String chainId, String address) { -- GitLab From bb8ed3b98b0d7b922939f7e4e2ecab27973a00c7 Mon Sep 17 00:00:00 2001 From: Zhymolokhov Date: Tue, 4 Dec 2018 16:30:52 +0200 Subject: [PATCH 3/6] multiple-networks-update: Finished functionality for multiple chains configuration. --- .../WalletServiceApplication.java | 8 +++-- .../controller/TokenController.java | 9 +++-- .../controller/TransactionController.java | 20 +++++++---- .../model/network/NetworkProperties.java | 1 + .../service/listener/EventListener.java | 33 ++++++++++++++++-- .../service/listener/MintEventHandler.java | 8 +++-- .../listener/TransferEventHandler.java | 34 +++++++++++-------- .../walletservice/wrapper/NynjaCoin.java | 33 ++++++++++-------- src/main/resources/application.yml | 2 ++ .../controller/EthereumControllerTest.java | 15 ++++++++ 10 files changed, 117 insertions(+), 46 deletions(-) create mode 100644 src/test/java/com/nynja/walletservice/controller/EthereumControllerTest.java diff --git a/src/main/java/com/nynja/walletservice/WalletServiceApplication.java b/src/main/java/com/nynja/walletservice/WalletServiceApplication.java index 633e4f6..2416af0 100644 --- a/src/main/java/com/nynja/walletservice/WalletServiceApplication.java +++ b/src/main/java/com/nynja/walletservice/WalletServiceApplication.java @@ -1,12 +1,16 @@ package com.nynja.walletservice; +import com.nynja.walletservice.service.listener.TransferEventHandler; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.FilterType; +import org.web3j.spring.autoconfigure.Web3jAutoConfiguration; -@SpringBootApplication +@SpringBootApplication(exclude = Web3jAutoConfiguration.class) +@ComponentScan(excludeFilters=@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = TransferEventHandler.class)) public class WalletServiceApplication { - public static void main(String[] args) { SpringApplication.run(WalletServiceApplication.class, args); } diff --git a/src/main/java/com/nynja/walletservice/controller/TokenController.java b/src/main/java/com/nynja/walletservice/controller/TokenController.java index 2b1016f..1626741 100644 --- a/src/main/java/com/nynja/walletservice/controller/TokenController.java +++ b/src/main/java/com/nynja/walletservice/controller/TokenController.java @@ -3,6 +3,7 @@ package com.nynja.walletservice.controller; import com.nynja.walletservice.dto.TransactionResponseDto; import com.nynja.walletservice.service.TokenService; import io.swagger.annotations.*; +import org.hibernate.validator.constraints.NotBlank; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -14,6 +15,7 @@ import static com.nynja.walletservice.constant.Constants.ParamTypes.Query; import static com.nynja.walletservice.constant.Constants.RestApi.API_VERSION; import static com.nynja.walletservice.constant.Constants.RestApi.REQUIRED_PARAM_MISSING; import static com.nynja.walletservice.constant.Constants.RestApi.RETRIEVED; +import static com.nynja.walletservice.constant.Constants.ValidationMessages.CHAIN_ID_VALIDATION_MESSAGE; /** * @author Oleg Zhymolokhov (oleg.zhimolokhov@dataart.com) @@ -40,7 +42,10 @@ public class TokenController { @ApiResponse(code = 400, message = REQUIRED_PARAM_MISSING) }) @GetMapping(API_VERSION + "/fund-address") - public CompletableFuture>> fundAddress(@RequestParam String address) { - return tokenService.handleAccountCreation(address).thenApplyAsync(ResponseEntity::ok); + public CompletableFuture>> fundAddress( + @NotBlank(message = CHAIN_ID_VALIDATION_MESSAGE) @RequestParam String chainId, + @RequestParam String address + ) { + return tokenService.handleAccountCreation(chainId, address).thenApplyAsync(ResponseEntity::ok); } } diff --git a/src/main/java/com/nynja/walletservice/controller/TransactionController.java b/src/main/java/com/nynja/walletservice/controller/TransactionController.java index ccddb97..cc6f5bb 100644 --- a/src/main/java/com/nynja/walletservice/controller/TransactionController.java +++ b/src/main/java/com/nynja/walletservice/controller/TransactionController.java @@ -56,14 +56,17 @@ public class TransactionController { @NotBlank(message = CHAIN_ID_VALIDATION_MESSAGE) @RequestParam String chainId, @NotBlank(message = ADDRESS_VALIDATION_MESSAGE) @RequestParam String walletAddress ) { - return tokenService.tokenBalanceByAddress(walletAddress).thenApplyAsync(ResponseEntity::ok); + return tokenService.tokenBalanceByAddress(chainId, walletAddress).thenApplyAsync(ResponseEntity::ok); } @ApiOperation(value = "Endpoint for sending signed transactions", httpMethod = "POST") @ApiResponse(code = 200, message = RETRIEVED) @PostMapping(API_VERSION + "/signed-transaction-send") - public CompletableFuture>> sendSignedTransaction(@Valid @RequestBody SignedTransactionDto dto) { - return web3JService.sendSignedTransaction(dto.getSignedTransaction()).thenApplyAsync(ResponseEntity::ok); + public CompletableFuture>> sendSignedTransaction( + @NotBlank(message = CHAIN_ID_VALIDATION_MESSAGE) @RequestParam String chainId, + @Valid @RequestBody SignedTransactionDto dto + ) { + return web3JService.sendSignedTransaction(chainId, dto.getSignedTransaction()).thenApplyAsync(ResponseEntity::ok); } @ApiOperation(value = "Endpoint for getting transaction info by hash", httpMethod = "GET") @@ -73,7 +76,7 @@ public class TransactionController { @NotBlank(message = CHAIN_ID_VALIDATION_MESSAGE) @RequestParam String chainId, @NotBlank(message = HASH_VALIDATION_MESSAGE) @RequestParam String hash ) { - return web3JService.getSingleTransactionInfo(hash).thenApplyAsync(ResponseEntity::ok); + return web3JService.getSingleTransactionInfo(chainId, hash).thenApplyAsync(ResponseEntity::ok); } @ApiOperation(value = "Endpoint for obtaining the latest nonce", httpMethod = "GET") @@ -83,7 +86,7 @@ public class TransactionController { @NotBlank(message = CHAIN_ID_VALIDATION_MESSAGE) @RequestParam String chainId, @NotBlank(message = ADDRESS_VALIDATION_MESSAGE) @RequestParam String address ) { - return web3JService.getTransactionCount(address).thenApplyAsync(ResponseEntity::ok); + return web3JService.getTransactionCount(chainId, address).thenApplyAsync(ResponseEntity::ok); } @ApiOperation(value = "Endpoint for obtaining tx history by address. Internal transactions", httpMethod = "GET") @@ -113,7 +116,10 @@ public class TransactionController { @ApiOperation(value = "Endpoint for transaction gas price estimation", httpMethod = "POST") @ApiResponse(code = 200, message = RETRIEVED) @PostMapping(API_VERSION + "/estimate-gas-price") - public CompletableFuture> estimateGasPrice(@RequestBody EstimateGasRequestDto dto) { - return web3JService.estimateGasPrice(dto).thenApplyAsync(ResponseEntity::ok); + public CompletableFuture> estimateGasPrice( + @NotBlank(message = CHAIN_ID_VALIDATION_MESSAGE) @RequestParam String chainId, + @RequestBody EstimateGasRequestDto dto + ) { + return web3JService.estimateGasPrice(chainId, dto).thenApplyAsync(ResponseEntity::ok); } } diff --git a/src/main/java/com/nynja/walletservice/model/network/NetworkProperties.java b/src/main/java/com/nynja/walletservice/model/network/NetworkProperties.java index fc104fd..3db862c 100644 --- a/src/main/java/com/nynja/walletservice/model/network/NetworkProperties.java +++ b/src/main/java/com/nynja/walletservice/model/network/NetworkProperties.java @@ -12,6 +12,7 @@ public class NetworkProperties { private String adminAddress; private String adminPrivateKey; private String nynjaCoinAddress; + private boolean nynjaCoinListenEvents; private BigInteger gasPrice; private BigInteger gasLimit; } diff --git a/src/main/java/com/nynja/walletservice/service/listener/EventListener.java b/src/main/java/com/nynja/walletservice/service/listener/EventListener.java index 8fa6f90..d8e0c30 100644 --- a/src/main/java/com/nynja/walletservice/service/listener/EventListener.java +++ b/src/main/java/com/nynja/walletservice/service/listener/EventListener.java @@ -1,22 +1,49 @@ package com.nynja.walletservice.service.listener; +import com.nynja.walletservice.config.ConfigValues; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.stereotype.Component; +import javax.annotation.PostConstruct; +import java.util.ArrayList; import java.util.List; @Component @Slf4j public class EventListener implements ApplicationListener { - private final List eventHandlers; + private List eventHandlers; + + private final ConfigValues conf; + private final ApplicationContext applicationContext; @Autowired - public EventListener(List eventHandlers) { - this.eventHandlers = eventHandlers; + public EventListener(ConfigValues conf, ApplicationContext applicationContext) { + this.conf = conf; + this.applicationContext = applicationContext; + } + + @PostConstruct + public void init() { + eventHandlers = new ArrayList<>(); + + AutowireCapableBeanFactory beanFactory = applicationContext.getAutowireCapableBeanFactory(); + conf.getNetworks().entrySet().stream() + .filter(e -> e.getValue().isNynjaCoinListenEvents()) + .forEach(e -> { + log.info("Creating {} bean for chainId: {}", TransferEventHandler.class.getCanonicalName(), e.getKey()); + + TransferEventHandler transferEventHandler = beanFactory.createBean(TransferEventHandler.class); + transferEventHandler.setChainId(e.getKey()); + transferEventHandler.setNetworkProperties(e.getValue()); + + eventHandlers.add(transferEventHandler); + }); } @Override diff --git a/src/main/java/com/nynja/walletservice/service/listener/MintEventHandler.java b/src/main/java/com/nynja/walletservice/service/listener/MintEventHandler.java index 166f4c3..22a401f 100644 --- a/src/main/java/com/nynja/walletservice/service/listener/MintEventHandler.java +++ b/src/main/java/com/nynja/walletservice/service/listener/MintEventHandler.java @@ -3,6 +3,7 @@ package com.nynja.walletservice.service.listener; import com.nynja.walletservice.provider.ContractProvider; import com.nynja.walletservice.service.Web3JService; import com.nynja.walletservice.wrapper.NynjaCoin; +import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -22,11 +23,14 @@ public class MintEventHandler extends EventHandler @Autowired private Web3JService web3JService; + @Setter + private String chainId; + @Override public void subscribe() { - web3JService.getCurrentBlockWithMargin() + web3JService.getCurrentBlockWithMargin(chainId) .thenAcceptAsync(blockNumber -> - provider.getNynjaCoin().mintEventObservable( + provider.getNynjaCoin(chainId).mintEventObservable( DefaultBlockParameter.valueOf(blockNumber), DefaultBlockParameterName.LATEST ).subscribe(this, ex -> log.error(ex.getMessage(), ex)) diff --git a/src/main/java/com/nynja/walletservice/service/listener/TransferEventHandler.java b/src/main/java/com/nynja/walletservice/service/listener/TransferEventHandler.java index 71907a9..0eed9d0 100644 --- a/src/main/java/com/nynja/walletservice/service/listener/TransferEventHandler.java +++ b/src/main/java/com/nynja/walletservice/service/listener/TransferEventHandler.java @@ -5,12 +5,12 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.nynja.walletservice.dto.messagebroker.MessageBrokerRequest; import com.nynja.walletservice.dto.messagebroker.MessageBrokerResponse; import com.nynja.walletservice.dto.messagebroker.TokenBalanceChangeDto; -import com.nynja.walletservice.provider.ContractProvider; +import com.nynja.walletservice.model.network.NetworkProperties; import com.nynja.walletservice.service.TokenService; import com.nynja.walletservice.service.Web3JService; import com.nynja.walletservice.wrapper.NynjaCoin; +import lombok.Setter; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; @@ -39,13 +39,19 @@ import static org.web3j.tx.Contract.staticExtractEventParameters; @Component public class TransferEventHandler extends EventHandler { - @Autowired - private ContractProvider provider; - @Autowired private TokenService tokenService; - @Autowired private Web3JService web3JService; + public TransferEventHandler(TokenService tokenService, Web3JService web3JService) { + this.tokenService = tokenService; + this.web3JService = web3JService; + } + + @Setter + private String chainId; + @Setter + private NetworkProperties networkProperties; + @Value("${message-broker.url}") private String messageBrokerUrl; @@ -56,7 +62,9 @@ public class TransferEventHandler extends EventHandler { @Override public void subscribe() { - web3JService.getCurrentBlockWithMargin() + log.info("Trying to subscribe to NynjaCoin for chainId: {}", chainId); + + web3JService.getCurrentBlockWithMargin(chainId) .thenAcceptAsync(blockNumber -> { event = new Event("Transfer", Arrays.asList(new TypeReference
() {}, new TypeReference
() {}), @@ -65,10 +73,10 @@ public class TransferEventHandler extends EventHandler { EthFilter filter = new EthFilter( DefaultBlockParameter.valueOf(blockNumber), DefaultBlockParameterName.LATEST, - provider.getNynjaAddress() + networkProperties.getNynjaCoinAddress() ); filter.addSingleTopic(EventEncoder.encode(event)); - web3JService.web3j().ethLogObservable(filter) + web3JService.web3j(chainId).ethLogObservable(filter) .subscribe(this, ex -> log.error(ex.getMessage(), ex)); } ); @@ -81,18 +89,14 @@ public class TransferEventHandler extends EventHandler { log.info("{} Nynja-coins has been transferred from {} to {}. Transaction hash: {}", typedResponse.value, typedResponse.from, typedResponse.to, response.getTransactionHash()); - tokenService.tokenBalanceByAddress(typedResponse.to).thenAcceptAsync(balance -> + tokenService.tokenBalanceByAddress(chainId, typedResponse.to).thenAcceptAsync(balance -> sendMessageToMessageBroker(typedResponse, balance, response.getTransactionHash())); } private NynjaCoin.TransferEventResponse extractEvent(Log response) { EventValues eventValues = staticExtractEventParameters(event, response); NynjaCoin.TransferEventResponse typedResponse = new NynjaCoin.TransferEventResponse(); - typedResponse.from = (String) eventValues.getIndexedValues().get(0).getValue(); - typedResponse.to = (String) eventValues.getIndexedValues().get(1).getValue(); - typedResponse.value = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); - - return typedResponse; + return NynjaCoin.getTransferEventResponse(eventValues, typedResponse); } private void sendMessageToMessageBroker(NynjaCoin.TransferEventResponse response, BigInteger balance, String txHash) { diff --git a/src/main/java/com/nynja/walletservice/wrapper/NynjaCoin.java b/src/main/java/com/nynja/walletservice/wrapper/NynjaCoin.java index 07855d9..12c7ec7 100644 --- a/src/main/java/com/nynja/walletservice/wrapper/NynjaCoin.java +++ b/src/main/java/com/nynja/walletservice/wrapper/NynjaCoin.java @@ -181,33 +181,36 @@ public class NynjaCoin extends Contract { ArrayList responses = new ArrayList(valueList.size()); for (EventValues eventValues : valueList) { TransferEventResponse typedResponse = new TransferEventResponse(); - typedResponse.from = (String) eventValues.getIndexedValues().get(0).getValue(); - typedResponse.to = (String) eventValues.getIndexedValues().get(1).getValue(); - typedResponse.value = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); - responses.add(typedResponse); + responses.add(getTransferEventResponse(eventValues, typedResponse)); } return responses; } public Observable transferEventObservable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) { - final Event event = new Event("Transfer", + final Event event = new Event("Transfer", Arrays.>asList(new TypeReference
() {}, new TypeReference
() {}), Arrays.>asList(new TypeReference() {})); EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress()); filter.addSingleTopic(EventEncoder.encode(event)); - return web3j.ethLogObservable(filter).map(new Func1() { - @Override - public TransferEventResponse call(Log log) { - EventValues eventValues = extractEventParameters(event, log); - TransferEventResponse typedResponse = new TransferEventResponse(); - typedResponse.from = (String) eventValues.getIndexedValues().get(0).getValue(); - typedResponse.to = (String) eventValues.getIndexedValues().get(1).getValue(); - typedResponse.value = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); - return typedResponse; - } + return web3j.ethLogObservable(filter).map(log -> { + EventValues eventValues = extractEventParameters(event, log); + TransferEventResponse typedResponse = new TransferEventResponse(); + return getTransferEventResponse(eventValues, typedResponse); }); } + /** + * This was not a part of generated Contract class. + * Add to the new NynjaCoin of there are any changes to the smart contract. + */ + public static NynjaCoin.TransferEventResponse getTransferEventResponse(EventValues eventValues, NynjaCoin.TransferEventResponse typedResponse) { + typedResponse.from = (String) eventValues.getIndexedValues().get(0).getValue(); + typedResponse.to = (String) eventValues.getIndexedValues().get(1).getValue(); + typedResponse.value = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue(); + + return typedResponse; + } + public RemoteCall mintingFinished() { Function function = new Function("mintingFinished", Arrays.asList(), diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 9c75a7d..d41b90a 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -25,6 +25,7 @@ ethereum: block-explorer-api-key: "K8J2W58EGI3EIR2FI8SYGW7FR1QKV5KDBM" admin-address: "0xB9BbCB84B654316D8E23383434c4EC66A97038ED" admin-private-key: "3e3a8b9c6e92808a1c683aa9458f90cfbcf5c19bed87e443b82fc7814a134796" + nynja-coin-listen-events: false nynja-coin-address: "0xF36c7AdAd65c39A4848F3c85001F67f31d00d207" gas-price: 41000000000 gas-limit: 400000 @@ -35,6 +36,7 @@ ethereum: block-explorer-api-key: "K8J2W58EGI3EIR2FI8SYGW7FR1QKV5KDBM" admin-address: "0xB9BbCB84B654316D8E23383434c4EC66A97038ED" admin-private-key: "3e3a8b9c6e92808a1c683aa9458f90cfbcf5c19bed87e443b82fc7814a134796" + nynja-coin-listen-events: true nynja-coin-address: "0xF36c7AdAd65c39A4848F3c85001F67f31d00d207" gas-price: 41000000000 gas-limit: 400000 diff --git a/src/test/java/com/nynja/walletservice/controller/EthereumControllerTest.java b/src/test/java/com/nynja/walletservice/controller/EthereumControllerTest.java new file mode 100644 index 0000000..9793b29 --- /dev/null +++ b/src/test/java/com/nynja/walletservice/controller/EthereumControllerTest.java @@ -0,0 +1,15 @@ +package com.nynja.walletservice.controller; + +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * @author Oleg Zhymolokhov (oleg.zhimolokhov@dataart.com) + */ +@SpringBootTest +@RunWith(SpringRunner.class) +public class EthereumControllerTest { + + +} -- GitLab From 26e75af35a2f5101b951e748fd6c70812d9baa4c Mon Sep 17 00:00:00 2001 From: Zhymolokhov Date: Thu, 6 Dec 2018 16:59:56 +0200 Subject: [PATCH 4/6] multiple-networks-update: Integration tests. --- .../controller/EthereumControllerTest.java | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/src/test/java/com/nynja/walletservice/controller/EthereumControllerTest.java b/src/test/java/com/nynja/walletservice/controller/EthereumControllerTest.java index 9793b29..06612a4 100644 --- a/src/test/java/com/nynja/walletservice/controller/EthereumControllerTest.java +++ b/src/test/java/com/nynja/walletservice/controller/EthereumControllerTest.java @@ -1,8 +1,32 @@ package com.nynja.walletservice.controller; +import com.nynja.walletservice.config.ConfigValues; +import com.nynja.walletservice.dto.TransactionResponseDto; +import com.nynja.walletservice.model.network.NetworkProperties; +import com.nynja.walletservice.service.Web3JService; +import org.junit.Before; +import org.junit.Test; import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.ResponseEntity; import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; +import org.web3j.crypto.Credentials; +import org.web3j.utils.Convert; + +import java.math.BigDecimal; +import java.util.concurrent.CompletableFuture; + +import static com.nynja.walletservice.constant.Constants.RestApi.API_VERSION; +import static org.junit.Assert.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * @author Oleg Zhymolokhov (oleg.zhimolokhov@dataart.com) @@ -11,5 +35,68 @@ import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) public class EthereumControllerTest { + private static final String RINKEBY_CHAIN_ID = "4"; + private static final String TEST_ADDRESS = "0x9028B59227c5EDdA388f04cef5e43020fEd1E73a"; + private static final String TEST_ADDRESS_PK = "3f5890282cc3b4eb8350823a1d1e950f7ba036b98cf074f299e1a9d09db196ca"; + + private MockMvc mvc; + private NetworkProperties networkProperties; + + @Autowired + private WebApplicationContext context; + @Autowired + private ConfigValues conf; + @Autowired + private Web3JService web3JService; + + + @Before + public void setup() { + mvc = MockMvcBuilders.webAppContextSetup(context).build(); + networkProperties = conf.getNetworkProps(RINKEBY_CHAIN_ID); + } + + @Test + public void testEthBalanceCheck() throws Exception { + MvcResult balanceResult = mvc.perform( + get("/ethereum" + API_VERSION + "address-balance") + .param("chainId", RINKEBY_CHAIN_ID) + .param("address", networkProperties.getAdminAddress()) + ).andExpect(request().asyncStarted()).andReturn(); + + mvc.perform(asyncDispatch(balanceResult)) + .andExpect(status().isOk()) + .andExpect(balance -> assertTrue( + new BigDecimal(balance.getResponse().getContentAsString()).compareTo(BigDecimal.ZERO) > 0 + )); + } + + @Test + @SuppressWarnings("unchecked") + public void testEthSend() throws Exception { + String ethVal = "1"; + + MvcResult sendResult = mvc.perform( + get("/ethereum" + API_VERSION + "eth-send") + .param("chainId", RINKEBY_CHAIN_ID) + .param("address", TEST_ADDRESS) + .param("amount", ethVal) + ).andExpect(request().asyncStarted()).andReturn(); + + mvc.perform(asyncDispatch(sendResult)) + .andExpect(status().isOk()); + + TransactionResponseDto responseDto = ((ResponseEntity>) sendResult.getAsyncResult()).getBody(); + assertNotNull(responseDto.getHash()); + assertEquals(ethVal, responseDto.getValue()); + //returning ether + CompletableFuture.supplyAsync(() -> web3JService.sendEtherAsync( + RINKEBY_CHAIN_ID, + Credentials.create(TEST_ADDRESS, TEST_ADDRESS_PK), + networkProperties.getAdminAddress(), + new BigDecimal("1"), + Convert.Unit.ETHER + )); + } } -- GitLab From d3f8f5008d34cc05a3ff29d1f7317b98eebb1fcd Mon Sep 17 00:00:00 2001 From: Oleg Zhymolokhov Date: Thu, 6 Dec 2018 17:05:36 +0200 Subject: [PATCH 5/6] multiple-networks-update: Integration tests. --- .../controller/TransactionControllerTest.java | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 src/test/java/com/nynja/walletservice/controller/TransactionControllerTest.java diff --git a/src/test/java/com/nynja/walletservice/controller/TransactionControllerTest.java b/src/test/java/com/nynja/walletservice/controller/TransactionControllerTest.java new file mode 100644 index 0000000..c481581 --- /dev/null +++ b/src/test/java/com/nynja/walletservice/controller/TransactionControllerTest.java @@ -0,0 +1,85 @@ +package com.nynja.walletservice.controller; + + +import com.nynja.walletservice.blockexplorer.dto.Response; +import com.nynja.walletservice.blockexplorer.dto.TxListItemDto; +import com.nynja.walletservice.service.Web3JService; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import static com.nynja.walletservice.constant.Constants.RestApi.API_VERSION; +import static org.junit.Assert.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * @author Oleg Zhymolokhov (oleg.zhimolokhov@dataart.com) + */ +@SpringBootTest +@RunWith(SpringRunner.class) +public class TransactionControllerTest { + + private MockMvc mvc; + + @Autowired + private Web3JService web3JService; + @Autowired + private WebApplicationContext context; + + @Before + public void setup() { + mvc = MockMvcBuilders + .webAppContextSetup(context) + .build(); + } + + @Test + @SuppressWarnings("unchecked") + public void testTransactionHistory() throws Exception { + MvcResult result = mvc.perform( + get("/wallet-service" + API_VERSION + "tx-history") + .param("address", "0xef253203acde5813a82ce53a67b0f93adbeef74d") + .param("chainId", "4") + .param("contractAddress", "0xF36c7AdAd65c39A4848F3c85001F67f31d00d207") + .param("page", "1") + .param("offset", "10") + ).andReturn(); + + mvc.perform(asyncDispatch(result)).andExpect(status().isOk()); + + Response responseDto = ((ResponseEntity>) result.getAsyncResult()).getBody(); + assertNotNull(responseDto); + assertEquals("1", responseDto.getStatus()); + assertFalse(responseDto.getResult().isEmpty()); + } + + @Test + @SuppressWarnings("unchecked") + public void testSingleTransactionInfo() throws Exception { + MvcResult result = mvc.perform( + get("/wallet-service" + API_VERSION + "single-transaction-info") + .param("address", "0xef253203acde5813a82ce53a67b0f93adbeef74d") + .param("chainId", "4") + .param("contractAddress", "0xF36c7AdAd65c39A4848F3c85001F67f31d00d207") + .param("page", "1") + .param("offset", "10") + ).andReturn(); + + mvc.perform(asyncDispatch(result)).andExpect(status().isOk()); + + Response responseDto = ((ResponseEntity>) result.getAsyncResult()).getBody(); + assertNotNull(responseDto); + assertEquals("1", responseDto.getStatus()); + assertFalse(responseDto.getResult().isEmpty()); + } +} -- GitLab From 985fe5cd244cbc1c9c9ad7134b06813ade53911a Mon Sep 17 00:00:00 2001 From: Zhymolokhov Date: Thu, 13 Dec 2018 18:08:14 +0200 Subject: [PATCH 6/6] multiple-networks-update: Integration tests. --- .../controller/EthereumControllerTest.java | 13 +++- .../controller/TransactionControllerTest.java | 69 ++++++++++++++----- 2 files changed, 63 insertions(+), 19 deletions(-) diff --git a/src/test/java/com/nynja/walletservice/controller/EthereumControllerTest.java b/src/test/java/com/nynja/walletservice/controller/EthereumControllerTest.java index 06612a4..1f9a51f 100644 --- a/src/test/java/com/nynja/walletservice/controller/EthereumControllerTest.java +++ b/src/test/java/com/nynja/walletservice/controller/EthereumControllerTest.java @@ -35,8 +35,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @RunWith(SpringRunner.class) public class EthereumControllerTest { - private static final String RINKEBY_CHAIN_ID = "4"; - private static final String TEST_ADDRESS = "0x9028B59227c5EDdA388f04cef5e43020fEd1E73a"; + static final String RINKEBY_CHAIN_ID = "4"; + static final String TEST_ADDRESS = "0x9028B59227c5EDdA388f04cef5e43020fEd1E73a"; private static final String TEST_ADDRESS_PK = "3f5890282cc3b4eb8350823a1d1e950f7ba036b98cf074f299e1a9d09db196ca"; private MockMvc mvc; @@ -71,6 +71,15 @@ public class EthereumControllerTest { )); } + @Test + public void testEthBalanceCheckBadRequest() throws Exception { + mvc.perform( + get("/ethereum" + API_VERSION + "address-balance") + .param("chainId", RINKEBY_CHAIN_ID) + .param("address", "") + ).andExpect(status().isBadRequest()); + } + @Test @SuppressWarnings("unchecked") public void testEthSend() throws Exception { diff --git a/src/test/java/com/nynja/walletservice/controller/TransactionControllerTest.java b/src/test/java/com/nynja/walletservice/controller/TransactionControllerTest.java index c481581..d700cdf 100644 --- a/src/test/java/com/nynja/walletservice/controller/TransactionControllerTest.java +++ b/src/test/java/com/nynja/walletservice/controller/TransactionControllerTest.java @@ -1,9 +1,7 @@ package com.nynja.walletservice.controller; - import com.nynja.walletservice.blockexplorer.dto.Response; import com.nynja.walletservice.blockexplorer.dto.TxListItemDto; -import com.nynja.walletservice.service.Web3JService; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -15,8 +13,13 @@ import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; +import org.web3j.protocol.core.methods.response.TransactionReceipt; + +import java.math.BigInteger; import static com.nynja.walletservice.constant.Constants.RestApi.API_VERSION; +import static com.nynja.walletservice.controller.EthereumControllerTest.RINKEBY_CHAIN_ID; +import static com.nynja.walletservice.controller.EthereumControllerTest.TEST_ADDRESS; import static org.junit.Assert.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -31,8 +34,6 @@ public class TransactionControllerTest { private MockMvc mvc; - @Autowired - private Web3JService web3JService; @Autowired private WebApplicationContext context; @@ -49,10 +50,10 @@ public class TransactionControllerTest { MvcResult result = mvc.perform( get("/wallet-service" + API_VERSION + "tx-history") .param("address", "0xef253203acde5813a82ce53a67b0f93adbeef74d") - .param("chainId", "4") - .param("contractAddress", "0xF36c7AdAd65c39A4848F3c85001F67f31d00d207") - .param("page", "1") - .param("offset", "10") + .param("chainId", RINKEBY_CHAIN_ID) + .param("contractAddress", "0xF36c7AdAd65c39A4848F3c85001F67f31d00d207") + .param("page", "1") + .param("offset", "10") ).andReturn(); mvc.perform(asyncDispatch(result)).andExpect(status().isOk()); @@ -63,23 +64,57 @@ public class TransactionControllerTest { assertFalse(responseDto.getResult().isEmpty()); } + @Test + public void testTransactionHistoryBadRequest() throws Exception { + mvc.perform( + get("/wallet-service" + API_VERSION + "tx-history") + .param("address", "") + .param("chainId", RINKEBY_CHAIN_ID) + .param("contractAddress", "0xF36c7AdAd65c39A4848F3c85001F67f31d00d207") + .param("page", "1") + .param("offset", "10") + ).andExpect(status().isBadRequest()); + } + @Test @SuppressWarnings("unchecked") public void testSingleTransactionInfo() throws Exception { + final String testTransaction = "0x42165f05b29fab8170bdb3bd5e3b0b7beab27afe1472952b022b57ec9f2d4eb9"; + MvcResult result = mvc.perform( get("/wallet-service" + API_VERSION + "single-transaction-info") - .param("address", "0xef253203acde5813a82ce53a67b0f93adbeef74d") - .param("chainId", "4") - .param("contractAddress", "0xF36c7AdAd65c39A4848F3c85001F67f31d00d207") - .param("page", "1") - .param("offset", "10") + .param("chainId", RINKEBY_CHAIN_ID) + .param("hash", testTransaction) ).andReturn(); mvc.perform(asyncDispatch(result)).andExpect(status().isOk()); - Response responseDto = ((ResponseEntity>) result.getAsyncResult()).getBody(); - assertNotNull(responseDto); - assertEquals("1", responseDto.getStatus()); - assertFalse(responseDto.getResult().isEmpty()); + TransactionReceipt receipt = ((ResponseEntity) result.getAsyncResult()).getBody(); + assertNotNull(receipt); + assertEquals(testTransaction, receipt.getTransactionHash()); + } + + @Test + public void testSingleTransactionInfoBadRequest() throws Exception { + mvc.perform( + get("/wallet-service" + API_VERSION + "single-transaction-info") + .param("chainId", RINKEBY_CHAIN_ID) + .param("hash", "") + ).andExpect(status().isBadRequest()); + } + + @Test + @SuppressWarnings("unchecked") + public void testGetTransactionCount() throws Exception { + MvcResult result = mvc.perform( + get("/wallet-service" + API_VERSION + "get-transaction-count") + .param("chainId", RINKEBY_CHAIN_ID) + .param("address", TEST_ADDRESS) + ).andReturn(); + + mvc.perform(asyncDispatch(result)).andExpect(status().isOk()); + + BigInteger txCount = ((ResponseEntity) result.getAsyncResult()).getBody(); + assertNotNull(txCount); } } -- GitLab