From 031ff4ecf8249431619938fdc9cf526ba2afc7a0 Mon Sep 17 00:00:00 2001 From: Oleg Zhymolokhov Date: Wed, 7 Nov 2018 15:59:25 +0200 Subject: [PATCH] web3j-warmup: Some refactoring. Added extended functionality for smart-contract managing. Added web3j client's initial warm-up. Fixed event subscription. --- .../walletservice/config/ConfigValues.java | 31 ++++++++++++++ .../walletservice/config/EthereumConfig.java | 38 ++++------------- .../walletservice/config/Web3jWarmUp.java | 41 ++++++++++++++++++ .../controller/ContractController.java | 25 ++++++++++- .../controller/EthereumController.java | 6 ++- .../dto/DeployedContractsAddressesDto.java | 13 ------ .../dto/NynContractAddressesDto.java | 15 +++++++ .../provider/ContractProvider.java | 42 +++++++++++-------- .../walletservice/service/Web3JService.java | 24 +++++------ .../service/listener/EventListener.java | 10 ++++- .../service/listener/MintEventHandler.java | 40 ++++++++++++++++++ .../listener/TransferEventHandler.java | 17 ++++++-- src/main/resources/application.yml | 6 +-- 13 files changed, 224 insertions(+), 84 deletions(-) create mode 100644 src/main/java/com/nynja/walletservice/config/Web3jWarmUp.java delete mode 100644 src/main/java/com/nynja/walletservice/dto/DeployedContractsAddressesDto.java create mode 100644 src/main/java/com/nynja/walletservice/dto/NynContractAddressesDto.java create mode 100644 src/main/java/com/nynja/walletservice/service/listener/MintEventHandler.java diff --git a/src/main/java/com/nynja/walletservice/config/ConfigValues.java b/src/main/java/com/nynja/walletservice/config/ConfigValues.java index 4be3e16..4eebc77 100644 --- a/src/main/java/com/nynja/walletservice/config/ConfigValues.java +++ b/src/main/java/com/nynja/walletservice/config/ConfigValues.java @@ -4,10 +4,41 @@ import lombok.Getter; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; +import java.math.BigInteger; + @Configuration @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; + + @Value("${web3j.attempts}") + private Integer attempts; + + @Value("${web3j.interval}") + private Integer interval; + @Value("${wallet-discovery.search-job-pool-size}") private int poolSize; + + public BigInteger gasPrice() { + return BigInteger.valueOf(gasPrice); + } + + public BigInteger gasLimit() { + return BigInteger.valueOf(gasLimit); + } } diff --git a/src/main/java/com/nynja/walletservice/config/EthereumConfig.java b/src/main/java/com/nynja/walletservice/config/EthereumConfig.java index 62e58b8..8a14aa9 100644 --- a/src/main/java/com/nynja/walletservice/config/EthereumConfig.java +++ b/src/main/java/com/nynja/walletservice/config/EthereumConfig.java @@ -3,7 +3,6 @@ package com.nynja.walletservice.config; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import okhttp3.OkHttpClient; -import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -14,53 +13,32 @@ import org.web3j.protocol.Web3j; import org.web3j.protocol.http.HttpService; import org.web3j.utils.Async; -import java.math.BigInteger; - @Configuration @Getter @Slf4j public class EthereumConfig implements ApplicationListener { - @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 ConfigValues conf; private Credentials adminCredentials; + public EthereumConfig(ConfigValues conf) { + this.conf = conf; + } + @Override public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) { - this.adminCredentials = Credentials.create(adminPrivateKey); + this.adminCredentials = Credentials.create(conf.getAdminPrivateKey()); } @Bean @Scope("singleton") public Web3j web3j() { - log.info("BUILDING Web3j INSTANCE FOR URL: {}", clientUrl); + log.info("BUILDING Web3j INSTANCE FOR URL: {}", conf.getClientUrl()); return Web3j.build(new HttpService( - clientUrl, + conf.getClientUrl(), new OkHttpClient.Builder().build(), false ), 1000L, Async.defaultExecutorService()); } - - public BigInteger gasPrice() { - return BigInteger.valueOf(gasPrice); - } - - public BigInteger gasLimit() { - return BigInteger.valueOf(gasLimit); - } - } diff --git a/src/main/java/com/nynja/walletservice/config/Web3jWarmUp.java b/src/main/java/com/nynja/walletservice/config/Web3jWarmUp.java new file mode 100644 index 0000000..a109a64 --- /dev/null +++ b/src/main/java/com/nynja/walletservice/config/Web3jWarmUp.java @@ -0,0 +1,41 @@ +package com.nynja.walletservice.config; + +import com.nynja.walletservice.provider.Web3jProvider; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationListener; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.event.ContextRefreshedEvent; +import org.web3j.protocol.core.DefaultBlockParameterName; +import org.web3j.protocol.core.methods.response.EthGetBalance; + +import java.math.BigInteger; + +@Configuration +@Slf4j +public class Web3jWarmUp implements ApplicationListener { + + private Web3jProvider web3jProvider; + private ConfigValues configValues; + + @Autowired + public Web3jWarmUp(Web3jProvider web3jProvider, ConfigValues configValues) { + this.web3jProvider = web3jProvider; + this.configValues = configValues; + } + + @Override + public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) { + log.info("Warming up the Web3j client"); + web3jProvider.get().ethGasPrice().sendAsync(); + web3jProvider.get().ethGetBalance(configValues.getAdminAddress(), DefaultBlockParameterName.LATEST) + .sendAsync() + .thenApplyAsync(EthGetBalance::getBalance) + .thenApplyAsync(BigInteger::toString) + .thenAcceptAsync(log::info) + .exceptionally(ex -> { + log.error(ex.getMessage(), ex); + return null; + }); + } +} diff --git a/src/main/java/com/nynja/walletservice/controller/ContractController.java b/src/main/java/com/nynja/walletservice/controller/ContractController.java index beac4ed..8382a69 100644 --- a/src/main/java/com/nynja/walletservice/controller/ContractController.java +++ b/src/main/java/com/nynja/walletservice/controller/ContractController.java @@ -1,5 +1,7 @@ package com.nynja.walletservice.controller; +import com.nynja.walletservice.dto.NynContractAddressesDto; +import com.nynja.walletservice.provider.ContractProvider; import com.nynja.walletservice.service.TokenService; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiResponse; @@ -7,12 +9,14 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; 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.controller.ControllerHelper.handleAndRespondAsync; /** * @author Oleg Zhymolokhov (oleg.zhimolokhov@dataart.com) @@ -23,10 +27,12 @@ import static com.nynja.walletservice.constant.Constants.RestApi.RETRIEVED; public class ContractController { private final TokenService tokenService; + private final ContractProvider contractProvider; @Autowired - public ContractController(TokenService tokenService) { + public ContractController(TokenService tokenService, ContractProvider contractProvider) { this.tokenService = tokenService; + this.contractProvider = contractProvider; } @ApiOperation(value = "Endpoint for NynjaCoin contract deployment into Blockchain network", httpMethod = "GET") @@ -35,4 +41,21 @@ public class ContractController { public CompletableFuture> deployNynjaCoin() { return tokenService.deployTokenContract().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() { + return handleAndRespondAsync(() -> NynContractAddressesDto.builder() + .runtimeAddress(contractProvider.getNynjaAddress()) + .dbAddress(contractProvider.getDBContractAddress()) + .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)); + } } diff --git a/src/main/java/com/nynja/walletservice/controller/EthereumController.java b/src/main/java/com/nynja/walletservice/controller/EthereumController.java index 7b2ba7f..9f077a9 100644 --- a/src/main/java/com/nynja/walletservice/controller/EthereumController.java +++ b/src/main/java/com/nynja/walletservice/controller/EthereumController.java @@ -10,9 +10,11 @@ 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.protocol.core.methods.response.EthGetBalance; import org.web3j.utils.Convert; import java.math.BigDecimal; +import java.math.BigInteger; import java.util.concurrent.CompletableFuture; import static com.nynja.walletservice.constant.Constants.DataTypes.LONG; @@ -54,7 +56,9 @@ public class EthereumController { @GetMapping(API_VERSION + "/address-balance") public CompletableFuture> getBalanceForAddress(@NotBlank(message = ADDRESS_VALIDATION_MESSAGE) @RequestParam String address) { return web3JService.getBalance(address) - .thenApplyAsync(ethGetBalance -> ResponseEntity.ok(ethGetBalance.getBalance().toString())); + .thenApplyAsync(EthGetBalance::getBalance) + .thenApplyAsync(BigInteger::toString) + .thenApplyAsync(ResponseEntity::ok); } @ApiOperation(value = "Send ether to an ethereum account from alloc account", httpMethod = "GET") diff --git a/src/main/java/com/nynja/walletservice/dto/DeployedContractsAddressesDto.java b/src/main/java/com/nynja/walletservice/dto/DeployedContractsAddressesDto.java deleted file mode 100644 index b2e50b1..0000000 --- a/src/main/java/com/nynja/walletservice/dto/DeployedContractsAddressesDto.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.nynja.walletservice.dto; - -import lombok.Data; - -/** - * @author Oleg Zhymolokhov (oleg.zhimolokhov@dataart.com) - */ - -@Data -public class DeployedContractsAddressesDto { - private String nynjaCoinAddress; - private String multiChargeAddress; -} diff --git a/src/main/java/com/nynja/walletservice/dto/NynContractAddressesDto.java b/src/main/java/com/nynja/walletservice/dto/NynContractAddressesDto.java new file mode 100644 index 0000000..9d4b059 --- /dev/null +++ b/src/main/java/com/nynja/walletservice/dto/NynContractAddressesDto.java @@ -0,0 +1,15 @@ +package com.nynja.walletservice.dto; + +import lombok.Builder; +import lombok.Data; + +/** + * @author Oleg Zhymolokhov (oleg.zhimolokhov@dataart.com) + */ + +@Data +@Builder +public class NynContractAddressesDto { + private String runtimeAddress; + private String dbAddress; +} diff --git a/src/main/java/com/nynja/walletservice/provider/ContractProvider.java b/src/main/java/com/nynja/walletservice/provider/ContractProvider.java index d380498..2a4efd6 100644 --- a/src/main/java/com/nynja/walletservice/provider/ContractProvider.java +++ b/src/main/java/com/nynja/walletservice/provider/ContractProvider.java @@ -1,9 +1,10 @@ package com.nynja.walletservice.provider; -import com.nynja.walletservice.config.EthereumConfig; +import com.nynja.walletservice.config.ConfigValues; import com.nynja.walletservice.model.ContractData; import com.nynja.walletservice.repository.ContractDataRepository; import com.nynja.walletservice.wrapper.NynjaCoin; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -24,37 +25,32 @@ import java.util.concurrent.CompletableFuture; @Slf4j public class ContractProvider { - @Value("${web3j.attempts}") - private Integer attempts; - - @Value("${web3j.interval}") - private Integer interval; - @Value("${ethereum.contract.nynja-coin-address}") + @Getter private String nynjaAddress; - private final EthereumConfig ethereumConfig; + private final ConfigValues conf; private final Web3jProvider web3jProvider; private final ContractDataRepository contractRepository; @Autowired - public ContractProvider(EthereumConfig ethereumConfig, Web3jProvider web3jProvider, ContractDataRepository contractRepository) { - this.ethereumConfig = ethereumConfig; + public ContractProvider(ConfigValues conf, Web3jProvider web3jProvider, ContractDataRepository contractRepository) { + this.conf = conf; this.web3jProvider = web3jProvider; this.contractRepository = contractRepository; } private RawTransactionManager getTrManager(Credentials credentials) { - return new RawTransactionManager(web3jProvider.get(), credentials, attempts, interval); + return new RawTransactionManager(web3jProvider.get(), credentials, conf.getAttempts(), conf.getInterval()); } public CompletableFuture deployNynjaCoinContract() { try { return NynjaCoin.deploy( web3jProvider.get(), - getTrManager(Credentials.create(ethereumConfig.getAdminPrivateKey())), - ethereumConfig.gasPrice(), - ethereumConfig.gasLimit() + getTrManager(Credentials.create(conf.getAdminPrivateKey())), + conf.gasPrice(), + conf.gasLimit() ).sendAsync() .thenApplyAsync(Contract::getContractAddress) .thenApplyAsync(this::persistContractAddress); @@ -73,7 +69,7 @@ public class ContractProvider { } public NynjaCoin getNynjaCoin() { - return getNynjaCoin(Credentials.create(ethereumConfig.getAdminPrivateKey())); + return getNynjaCoin(Credentials.create(conf.getAdminPrivateKey())); } public NynjaCoin getNynjaCoin(Credentials credentials) { @@ -95,8 +91,20 @@ public class ContractProvider { address, web3j, getTrManager(credentials), - ethereumConfig.gasPrice(), - ethereumConfig.gasLimit() + conf.gasPrice(), + conf.gasLimit() ); } + + public String getDBContractAddress() { + return contractRepository.findOne("1L").getAddress(); + } + + 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")); + } } diff --git a/src/main/java/com/nynja/walletservice/service/Web3JService.java b/src/main/java/com/nynja/walletservice/service/Web3JService.java index e74b9bf..df2819c 100644 --- a/src/main/java/com/nynja/walletservice/service/Web3JService.java +++ b/src/main/java/com/nynja/walletservice/service/Web3JService.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.EstimateGasRequestDto; import com.nynja.walletservice.dto.EstimateGasResponseDto; import com.nynja.walletservice.dto.TransactionResponseDto; @@ -9,7 +9,6 @@ import com.nynja.walletservice.exception.TransactionNotFoundException; import com.nynja.walletservice.provider.Web3jProvider; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.web3j.crypto.Credentials; import org.web3j.protocol.Web3j; @@ -34,19 +33,13 @@ import static org.web3j.protocol.core.DefaultBlockParameterName.PENDING; @Slf4j public class Web3JService { - @Value("${web3j.attempts}") - private Integer attempts; - - @Value("${web3j.interval}") - private Integer interval; - private final Web3jProvider web3jProvider; - private final EthereumConfig ethConfig; + private final ConfigValues conf; @Autowired - public Web3JService(Web3jProvider web3jProvider, EthereumConfig ethConfig) { + public Web3JService(Web3jProvider web3jProvider, ConfigValues conf) { this.web3jProvider = web3jProvider; - this.ethConfig = ethConfig; + this.conf = conf; } public String getClientVersion() { @@ -62,8 +55,8 @@ public class Web3JService { public CompletableFuture> sendEtherAsync(Credentials credentials, String toAddress, BigDecimal value, Convert.Unit currency) { - return new Transfer(web3j(), new RawTransactionManager(web3j(), credentials, attempts, interval)) - .sendFunds(toAddress, value, currency, ethConfig.gasPrice(), ethConfig.gasLimit()) + return new Transfer(web3j(), new RawTransactionManager(web3j(), credentials, conf.getAttempts(), conf.getInterval())) + .sendFunds(toAddress, value, currency, conf.gasPrice(), conf.gasLimit()) .sendAsync() .thenApplyAsync(tr -> { log.info(ETH_TRANSFERRED, toAddress, value, currency); @@ -123,6 +116,11 @@ public class Web3JService { )); } + public CompletableFuture getCurrentBlockNumber() { + return web3j().ethBlockNumber().sendAsync() + .thenApplyAsync(EthBlockNumber::getBlockNumber); + } + private Web3j web3j() { return web3jProvider.get(); } 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 755c47b..0b6082c 100644 --- a/src/main/java/com/nynja/walletservice/service/listener/EventListener.java +++ b/src/main/java/com/nynja/walletservice/service/listener/EventListener.java @@ -2,20 +2,26 @@ package com.nynja.walletservice.service.listener; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.stereotype.Component; import java.util.List; @Component @Slf4j -public class EventListener { +public class EventListener implements ApplicationListener { private final List eventHandlers; @Autowired public EventListener(List eventHandlers) { this.eventHandlers = eventHandlers; - //subscribe(); + } + + @Override + public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) { + subscribe(); } public void subscribe() { diff --git a/src/main/java/com/nynja/walletservice/service/listener/MintEventHandler.java b/src/main/java/com/nynja/walletservice/service/listener/MintEventHandler.java new file mode 100644 index 0000000..1763476 --- /dev/null +++ b/src/main/java/com/nynja/walletservice/service/listener/MintEventHandler.java @@ -0,0 +1,40 @@ +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.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.web3j.protocol.core.DefaultBlockParameter; +import org.web3j.protocol.core.DefaultBlockParameterName; + +/** + * @author Oleg Zhymolokhov (oleg.zhimolokhov@dataart.com) + */ + +@Component +@Slf4j +public class MintEventHandler extends EventHandler { + + @Autowired + private ContractProvider provider; + @Autowired + private Web3JService web3JService; + + @Override + public void subscribe() { + web3JService.getCurrentBlockNumber() + .thenAcceptAsync(blockNumber -> + provider.getNynjaCoin().mintEventObservable( + DefaultBlockParameter.valueOf(blockNumber), + DefaultBlockParameterName.LATEST + ).subscribe(this, ex -> log.error(ex.getMessage(), ex)) + ); + } + + @Override + public void call(NynjaCoin.MintEventResponse event) { + log.info("Mint for address {} has been successful. Amount: {}", event.to, event.amount); + } +} 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 86b1318..c630722 100644 --- a/src/main/java/com/nynja/walletservice/service/listener/TransferEventHandler.java +++ b/src/main/java/com/nynja/walletservice/service/listener/TransferEventHandler.java @@ -7,6 +7,7 @@ import com.nynja.walletservice.dto.messagebroker.MessageBrokerResponse; import com.nynja.walletservice.dto.messagebroker.TokenBalanceChangeDto; import com.nynja.walletservice.provider.ContractProvider; import com.nynja.walletservice.service.TokenService; +import com.nynja.walletservice.service.Web3JService; import com.nynja.walletservice.wrapper.NynjaCoin; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -14,6 +15,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; +import org.web3j.protocol.core.DefaultBlockParameter; import org.web3j.protocol.core.DefaultBlockParameterName; import java.util.Base64; @@ -29,6 +31,8 @@ public class TransferEventHandler extends EventHandler + provider.getNynjaCoin().transferEventObservable( + DefaultBlockParameter.valueOf(blockNumber), + DefaultBlockParameterName.LATEST + ).subscribe(this, ex -> log.error(ex.getMessage(), ex)) + ); } @Override @@ -52,6 +59,8 @@ public class TransferEventHandler extends EventHandler sendMessageToMessageBroker(response, balance)); } + + private void sendMessageToMessageBroker(NynjaCoin.TransferEventResponse response, long balance) { RestTemplate template = new RestTemplate(); ResponseEntity mbResponse = template.postForEntity( diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index ab05688..420b7e2 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -19,10 +19,10 @@ web3j: interval: 1000 ethereum: client: - url: http://35.198.77.116:8545 + url: http://35.242.240.78:8545 admin: - address: "0x57309004546c33b47a7ac7c9d07f20fb2bc4a240" - private-key: "960d1cad3ad7c42251f79c31e15636915f953cbfff1770cc4483efa44bf4ffc9" + address: "0xB9BbCB84B654316D8E23383434c4EC66A97038ED" + private-key: "3e3a8b9c6e92808a1c683aa9458f90cfbcf5c19bed87e443b82fc7814a134796" contract: # First deploy a contract and then place/replace it's address into this property nynja-coin-address: "0xF36c7AdAd65c39A4848F3c85001F67f31d00d207" -- GitLab