Skip to content

Commit

Permalink
issue #133 - refactoring + added transfer helpers
Browse files Browse the repository at this point in the history
  • Loading branch information
tonowie committed Nov 26, 2019
1 parent 1a88f48 commit fdb4fff
Show file tree
Hide file tree
Showing 7 changed files with 249 additions and 176 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,27 @@
* Use of this source code is governed by the Apache 2.0
* license that can be found in the LICENSE file.
*/
package io.proximax.sdk.utils;
package io.proximax.sdk.helpers;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import io.proximax.sdk.BlockchainApi;
import io.proximax.sdk.BlockchainRepository;
import io.proximax.sdk.ListenerRepository;
import io.proximax.sdk.TransactionRepository;
import io.proximax.sdk.model.account.Account;
import io.proximax.sdk.model.account.PublicAccount;
import io.proximax.sdk.model.blockchain.NetworkType;
import io.proximax.sdk.model.mosaic.NetworkHarvestMosaic;
import io.proximax.sdk.model.transaction.AccountLinkTransaction;
import io.proximax.sdk.model.transaction.AggregateTransaction;
import io.proximax.sdk.model.transaction.LockFundsTransaction;
import io.proximax.sdk.model.transaction.Message;
import io.proximax.sdk.model.transaction.ModifyMultisigAccountTransaction;
import io.proximax.sdk.model.transaction.TransferTransaction;

/**
* TODO add proper description
* Helper for account-related tasks
*/
public class AccountHelper extends BaseHelper {

Expand All @@ -50,7 +46,7 @@ public AccountHelper(BlockchainApi api) {
*
* @return stream of random accounts
*/
public Stream<Account> accountRandom() {
public Stream<Account> randomAccount() {
final NetworkType network = api.getNetworkType();
return Stream.generate(() -> Account.generateNewAccount(network));
}
Expand All @@ -61,46 +57,35 @@ public Stream<Account> accountRandom() {
* @param count the count of items in the returned list of accounts
* @return list of accounts
*/
public List<Account> accountRandom(int count) {
public List<Account> randomAccount(int count) {
// generate specified number of random accounts
return accountRandom().limit(count).collect(Collectors.toList());
return randomAccount().limit(count).collect(Collectors.toList());
}

/**
* convert account to multisig account
* <b>BLOCKING</b> convert account to multisig account
*
* @param account
* @param cosigners
* @param minApprovals
* @param minRemovals
* @param optinBlocks
* @param confirmationTimeoutSeconds
* @param account the account that will be converted to multisignature account
* @param cosigners account cosigners
* @param minApprovals minimum approvals for transactions
* @param minRemovals minimum approvals for removal of cosigner
* @param optinBlocks number of blocks to wait for cosigner opt-in
* @param confirmationTimeoutSeconds timeout before transaction expires
*/
public void accountToMultisig(Account account, List<PublicAccount> cosigners, int minApprovals, int minRemovals,
int optinBlocks, int confirmationTimeoutSeconds) {
// prepare transaction repo
TransactionRepository transactions = api.createTransactionRepository();
// prepare listener
ListenerRepository listener = createListener();
// first prepare the actual transaction to change account to multisig account
ModifyMultisigAccountTransaction multisigChangeTrans = transact.multisigModification()
.changeToMultisig(cosigners, minApprovals, minRemovals).build();
// aggregate bonded transaction is required for cosigner opt-in so create that
AggregateTransaction aggregateTrans = transact.aggregateBonded()
.innerTransactions(multisigChangeTrans.toAggregate(account.getPublicAccount())).build();
// aggregate bonded transaction requires lock funds
LockFundsTransaction lockTrans = transact.lockFunds()
.forAggregate(BigInteger.valueOf(optinBlocks), api.sign(aggregateTrans, account)).build();
// announce lock funds and wait for confirmation
transactionConfirmed(transactions, listener, lockTrans, account, confirmationTimeoutSeconds);
// !!! wait a bit for server to get into consistent state !!!
sleepForAWhile();
// now announce the aggregate transaction
transactionBondedAdded(transactions, listener, aggregateTrans, account, confirmationTimeoutSeconds);
// aggregate bonded transaction is required for cosigner opt-in so use that
announceAsAggregateBonded(account,
optinBlocks,
confirmationTimeoutSeconds,
multisigChangeTrans.toAggregate(account.getPublicAccount()));
}

/**
* activate delegated harvesting for an account
* <b>BLOCKING</b> NOT SUPPORTED YET! activate delegated harvesting for an account
*
* @param account the account that will get delegated harvesting activated
* @param nodePublicKey node public key (use {@link BlockchainRepository#getNodeInfo()} or /node/info)
Expand All @@ -110,10 +95,6 @@ public void accountToMultisig(Account account, List<PublicAccount> cosigners, in
*/
@Deprecated
public Account activateDelegatedHarvesting(Account account, String nodePublicKey, int confirmationTimeoutSeconds) {
// prepare transaction repo
TransactionRepository transactions = api.createTransactionRepository();
// prepare listener
ListenerRepository listener = createListener();
// prepare new account that will be sent to the node
Account remoteAccount = Account.generateNewAccount(api.getNetworkType());
// link accounts
Expand All @@ -131,7 +112,7 @@ public Account activateDelegatedHarvesting(Account account, String nodePublicKey
transfer.toAggregate(account.getPublicAccount()))
.build();
// wait for confirmation
transactionConfirmed(transactions, listener, transaction, account, confirmationTimeoutSeconds);
transactionConfirmed(transaction, account, confirmationTimeoutSeconds);
// upon successful execution return the remote account
return remoteAccount;
}
Expand Down
156 changes: 156 additions & 0 deletions src/main/java/io/proximax/sdk/helpers/BaseHelper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/*
* Copyright 2019 ProximaX Limited. All rights reserved.
* Use of this source code is governed by the Apache 2.0
* license that can be found in the LICENSE file.
*/
package io.proximax.sdk.helpers;

import java.math.BigInteger;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

import io.proximax.sdk.BlockchainApi;
import io.proximax.sdk.FeeCalculationStrategy;
import io.proximax.sdk.ListenerRepository;
import io.proximax.sdk.TransactionRepository;
import io.proximax.sdk.model.account.Account;
import io.proximax.sdk.model.transaction.AggregateTransaction;
import io.proximax.sdk.model.transaction.LockFundsTransaction;
import io.proximax.sdk.model.transaction.SignedTransaction;
import io.proximax.sdk.model.transaction.Transaction;
import io.proximax.sdk.model.transaction.TransactionInfo;
import io.proximax.sdk.model.transaction.builder.TransactionBuilderFactory;

/**
* Base helper implementation with common initialization
*/
class BaseHelper {
/** milliseconds in an hour 60 minutes of 60 seconds of 1000 milliseconds */
protected static final long HOUR_MILLIS = 3_600_000;

protected final BlockchainApi api;
protected final TransactionBuilderFactory transact;
protected final TransactionRepository transactionRepo;

private ListenerRepository listener;

/**
* </p>
* create new helper instance
* </p>
* <p>
* note this might require connection to the network if network type is not known to the provided api
* </p>
*
* @param api
*/
public BaseHelper(BlockchainApi api) {
this.api = api;
this.transact = api.transact();
this.transactionRepo = api.createTransactionRepository();
// initialize defaults for the transactions - 2 hour deadline and medium fees
this.transact.setDeadlineMillis(BigInteger.valueOf(2 * HOUR_MILLIS));
this.transact.setFeeCalculationStrategy(FeeCalculationStrategy.MEDIUM);
}

/**
* <b>BLOCKING!</b> announce transaction and wait for it to be added to confirmed transactions
*
* @param transaction the transaction to announce to the network
* @param signer account used to sign the transaction
* @param confirmationTimeoutSeconds transaction confirmation timeout
* @return the transaction response
*/
public Transaction transactionConfirmed(Transaction transaction, Account signer,
int confirmationTimeoutSeconds) {
// sign the transaction
SignedTransaction signedTrans = api.sign(transaction, signer);
// announce the signed transaction
transactionRepo.announce(signedTrans).blockingFirst();
// wait for confirmation of the transaction
return getListener().confirmed(signer.getAddress()).filter(trans -> equalHashes(trans, signedTrans))
.timeout(confirmationTimeoutSeconds, TimeUnit.SECONDS).blockingFirst();
}

/**
* <b>BLOCKING!</b> announce transaction and wait for it to be added to confirmed transactions
*
* @param transaction the aggregate bonded transaction that is to be announced
* @param initiatorAccount initiator of the transaction
* @param confirmationTimeoutSeconds transaction confirmation timeout
* @return the transaction response
*/
public Transaction transactionBondedAdded(AggregateTransaction transaction, Account initiatorAccount,
int confirmationTimeoutSeconds) {
// sign the transaction
SignedTransaction signedTrans = api.sign(transaction, initiatorAccount);
// announce the signed transaction
transactionRepo.announceAggregateBonded(signedTrans).blockingFirst();
// wait for confirmation of the transaction
return getListener().aggregateBondedAdded(initiatorAccount.getAddress())
.filter(trans -> equalHashes(trans, signedTrans)).timeout(confirmationTimeoutSeconds, TimeUnit.SECONDS)
.blockingFirst();
}

/**
* <b>BLOCKING</b> announce transactions as aggregate bonded transaction. Make sure that inner transactions are converted to
* aggregate via call to {@link Transaction#toAggregate(io.proximax.sdk.model.account.PublicAccount)}
*
* @param initiatorAccount account announcing the transaction (will be used to lock funds)
* @param lockBlocks number of blocks to wait for cosigners
* @param confirmationTimeoutSeconds timeout for transaction announcements
* @param innerTransactions transactions to include in the aggregate bonded transaction
*/
public void announceAsAggregateBonded(Account initiatorAccount, int lockBlocks, int confirmationTimeoutSeconds,
Transaction... innerTransactions) {
// aggregate bonded transaction is required for cosigner opt-in so create that
AggregateTransaction aggregateTrans = transact.aggregateBonded().innerTransactions(innerTransactions).build();
// aggregate bonded transaction requires lock funds
LockFundsTransaction lockTrans = transact.lockFunds()
.forAggregate(BigInteger.valueOf(lockBlocks), api.sign(aggregateTrans, initiatorAccount)).build();
// announce lock funds and wait for confirmation
transactionConfirmed(lockTrans, initiatorAccount, confirmationTimeoutSeconds);
// !!! wait a bit for server to get into consistent state !!!
sleepForAWhile();
// now announce the aggregate transaction
transactionBondedAdded(aggregateTrans, initiatorAccount, confirmationTimeoutSeconds);
}

/**
* blocking! create and initialize listener
*
* @return listener that can immediately be used
*/
public synchronized ListenerRepository getListener() {
if (listener == null) {
ListenerRepository newListener = api.createListener();
try {
newListener.open().get();
listener = newListener;
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException("Failed to open listener", e);
}
}
return listener;
}

private static boolean equalHashes(Transaction trans, SignedTransaction signed) {
Optional<TransactionInfo> info = trans.getTransactionInfo();
if (info.isPresent()) {
return info.get().getHash().equals(Optional.of(signed.getHash()));
}
return false;
}

/**
* convenience sleep needed to work around server listener synchronization issues
*/
public void sleepForAWhile() {
try {
Thread.sleep(1000l);
} catch (InterruptedException e) {
// do nothing
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
* Use of this source code is governed by the Apache 2.0
* license that can be found in the LICENSE file.
*/
package io.proximax.sdk.utils;
package io.proximax.sdk.helpers;

import java.math.BigInteger;
import java.util.concurrent.TimeUnit;

import io.proximax.sdk.BlockchainApi;

/**
* helper class for common operations
* helper class for common blockchain operations
*/
public class BlockchainHelper extends BaseHelper {

Expand All @@ -30,10 +30,11 @@ public BlockchainHelper(BlockchainApi api) {
}

/**
* wait for specified number of blocks. This method blocks or throws timeout exception
* <b>BLOCKING</b> wait for specified number of blocks. This method blocks or throws timeout exception
*
* @param count number of blocks to wait for
* @param timeoutSeconds timeout after which the wait will be aborted
* @param timeoutSeconds timeout after which the wait will be aborted. Always make sure the timeout is much much
* larger than expected wait for given number of blocks
*
* @return height of the last block
*/
Expand Down
63 changes: 63 additions & 0 deletions src/main/java/io/proximax/sdk/helpers/TransferHelper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright 2019 ProximaX Limited. All rights reserved.
* Use of this source code is governed by the Apache 2.0
* license that can be found in the LICENSE file.
*/
package io.proximax.sdk.helpers;

import io.proximax.sdk.BlockchainApi;
import io.proximax.sdk.model.account.Account;
import io.proximax.sdk.model.account.Address;
import io.proximax.sdk.model.account.PublicAccount;
import io.proximax.sdk.model.mosaic.Mosaic;
import io.proximax.sdk.model.transaction.Message;
import io.proximax.sdk.model.transaction.TransferTransaction;

/**
* Helper for mosaic transfers
*/
public class TransferHelper extends BaseHelper {

/**
* @param api
*/
public TransferHelper(BlockchainApi api) {
super(api);
}

/**
* <b>BLOCKING</b> transfer mosaic from standard (non-multisig) account
*
* @param from the source account
* @param to target address
* @param mosaic mosaic to transfer
* @param message transfer message
* @param confirmationTimeoutSeconds seconds to wait for transaction confirmation
*/
public void transfer(Account from, Address to, Mosaic mosaic, Message message, int confirmationTimeoutSeconds) {
// prepare transfer transaction
TransferTransaction transferTx = transact.transfer().mosaics(mosaic).to(to).message(message).build();
// announce the transaction
transactionConfirmed(transferTx, from, confirmationTimeoutSeconds);
}

/**
* <b>BLOCKING</b> make transfer from multisignature account. Cosigners are expected to approve the transaction
* before lockBlocks elapsed
*
* @param from the public key for the multisig account
* @param initiator the initiator of the transaction (one of cosigners)
* @param to target account
* @param mosaic mosaic to transfer
* @param message transfer message
* @param confirmationTimeoutSeconds time to wait for transaction confirmation
* @param lockBlocks number of blocks to wait for cosigners to cosign the transaction
*/
public void transferFromMultisig(PublicAccount from, Account initiator, Address to, Mosaic mosaic, Message message,
int confirmationTimeoutSeconds, int lockBlocks) {
// prepare transfer transaction
TransferTransaction transferTx = transact.transfer().mosaics(mosaic).to(to).message(message).build();
// announce as aggregate bonded to allow cosigners to act
announceAsAggregateBonded(initiator, lockBlocks, confirmationTimeoutSeconds, transferTx.toAggregate(from));
}
}
Loading

0 comments on commit fdb4fff

Please sign in to comment.