package controller;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;
import java.util.logging.Logger;
import org.mapdb.Fun.Tuple2;
import api.ApiService;
import qora.BlockChain;
import qora.BlockGenerator;
import qora.Synchronizer;
import qora.TransactionCreator;
import qora.account.Account;
import qora.account.PrivateKeyAccount;
import qora.assets.Asset;
import qora.assets.Order;
import qora.assets.Trade;
import qora.block.Block;
import qora.crypto.Ed25519;
import qora.naming.Name;
import qora.naming.NameSale;
import qora.payment.Payment;
import qora.transaction.Transaction;
import qora.voting.Poll;
import qora.voting.PollOption;
import qora.wallet.Wallet;
import settings.Settings;
import utils.ObserverMessage;
import utils.Pair;
import database.DBSet;
import database.SortableList;
import network.Network;
import network.Peer;
import network.message.BlockMessage;
import network.message.GetBlockMessage;
import network.message.GetSignaturesMessage;
import network.message.Message;
import network.message.MessageFactory;
import network.message.TransactionMessage;
import network.message.VersionMessage;
public class Controller extends Observable {
public static final int STATUS_NO_CONNECTIONS = 0;
public static final int STATUS_SYNCHRONIZING = 1;
public static final int STATUS_OKE = 2;
private int status;
private Network network;
private ApiService rpcService;
private BlockChain blockChain;
private BlockGenerator blockGenerator;
private Wallet wallet;
private Synchronizer synchronizer;
private TransactionCreator transactionCreator;
private Map<Peer, Integer> peerHeight;
private static Controller instance;
public Map<Peer, Integer> getPeerHeights()
{
return peerHeight;
}
public static Controller getInstance()
{
if(instance == null)
{
instance = new Controller();
}
return instance;
}
public int getStatus()
{
return this.status;
}
public void start(boolean disableRpc) throws Exception
{
//CHECK NETWORK PORT AVAILABLE
if(!Network.isPortAvailable(Network.PORT))
{
throw new Exception("Network port " + Network.PORT + " already in use!");
}
//CHECK RPC PORT AVAILABLE
if(!disableRpc)
{
if(!Network.isPortAvailable(Settings.getInstance().getRpcPort()))
{
throw new Exception("Rpc port " + Settings.getInstance().getRpcPort() + " already in use!");
}
}
//LOAD NATIVE LIBRARIES
if(!Ed25519.load())
{
throw new Exception("Failed to load native libraries!");
}
this.peerHeight = new LinkedHashMap<Peer, Integer>(); //LINKED TO PRESERVE ORDER WHEN SYNCHRONIZING (PRIORITIZE SYNCHRONIZING FROM LONGEST CONNECTION ALIVE)
this.status = STATUS_NO_CONNECTIONS;
this.transactionCreator = new TransactionCreator();
//OPENING DATABASES
DBSet.getInstance();
if(DBSet.getInstance().getBlockMap().isProcessing())
{
throw new Exception("The application was not closed correctly!");
}
//CREATE SYNCHRONIZOR
this.synchronizer = new Synchronizer();
//CREATE BLOCKCHAIN
this.blockChain = new BlockChain();
//CREATE BLOCKGENERATOR
this.blockGenerator = new BlockGenerator();
if(!disableRpc)
{
this.rpcService = new ApiService();
this.rpcService.start();
}
//CREATE WALLET
this.wallet = new Wallet();
//START BLOCKGENERATOR
this.blockGenerator.start();
//CREATE NETWORK
this.network = new Network();
//CLOSE ON UNEXPECTED SHUTDOWN
Runtime.getRuntime().addShutdownHook(new Thread()
{
@Override
public void run()
{
stopAll();
}
});
//REGISTER DATABASE OBSERVER
this.addObserver(DBSet.getInstance().getTransactionMap());
this.addObserver(DBSet.getInstance());
}
@Override
public void addObserver(Observer o)
{
//ADD OBSERVER TO SYNCHRONIZER
//this.synchronizer.addObserver(o);
DBSet.getInstance().getBlockMap().addObserver(o);
//ADD OBSERVER TO BLOCKGENERATOR
//this.blockGenerator.addObserver(o);
DBSet.getInstance().getTransactionMap().addObserver(o);
//ADD OBSERVER TO NAMESALES
DBSet.getInstance().getNameExchangeMap().addObserver(o);
//ADD OBSERVER TO POLLS
DBSet.getInstance().getPollMap().addObserver(o);
//ADD OBSERVER TO ASSETS
DBSet.getInstance().getAssetMap().addObserver(o);
//ADD OBSERVER TO ORDERS
DBSet.getInstance().getOrderMap().addObserver(o);
//ADD OBSERVER TO TRADES
DBSet.getInstance().getTradeMap().addObserver(o);
//ADD OBSERVER TO BALANCES
DBSet.getInstance().getBalanceMap().addObserver(o);
//ADD OBSERVER TO CONTROLLER
super.addObserver(o);
o.update(this, new ObserverMessage(ObserverMessage.NETWORK_STATUS, this.status));
}
@Override
public void deleteObserver(Observer o)
{
DBSet.getInstance().getBlockMap().deleteObserver(o);
super.deleteObserver(o);
}
public void deleteWalletObserver(Observer o)
{
this.wallet.deleteObserver(o);
}
private boolean isStopping = false;
public void stopAll()
{
//PREVENT MULTIPLE CALLS
if(!this.isStopping)
{
this.isStopping = true;
//STOP MESSAGE PROCESSOR
Logger.getGlobal().info("Stopping message processor");
this.network.stop();
//STOP BLOCK PROCESSOR
Logger.getGlobal().info("Stopping block processor");
this.synchronizer.stop();
//CLOSE DATABABASE
Logger.getGlobal().info("Closing database");
DBSet.getInstance().close();
//CLOSE WALLET
Logger.getGlobal().info("Closing wallet");
this.wallet.close();
//FORCE CLOSE
System.exit(0);
}
}
//NETWORK
public List<Peer> getActivePeers()
{
//GET ACTIVE PEERS
return this.network.getActiveConnections();
}
public void onConnect(Peer peer) {
//GET HEIGHT
int height = this.blockChain.getHeight();
//SEND VERSION MESSAGE
peer.sendMessage(MessageFactory.getInstance().createVersionMessage(height));
if(this.status == STATUS_NO_CONNECTIONS)
{
//UPDATE STATUS
this.status = STATUS_OKE;
//NOTIFY
this.setChanged();
this.notifyObservers(new ObserverMessage(ObserverMessage.NETWORK_STATUS, this.status));
}
}
public void onDisconnect(Peer peer)
{
synchronized(this.peerHeight)
{
this.peerHeight.remove(peer);
if(this.peerHeight.size() == 0)
{
//UPDATE STATUS
this.status = STATUS_NO_CONNECTIONS;
//NOTIFY
this.setChanged();
this.notifyObservers(new ObserverMessage(ObserverMessage.NETWORK_STATUS, this.status));
}
}
}
public void onError(Peer peer)
{
this.onDisconnect(peer);
}
//SYNCHRONIZED DO NOT PROCESSS MESSAGES SIMULTANEOUSLY
public void onMessage(Message message)
{
Message response;
Block block;
synchronized(this)
{
switch(message.getType())
{
case Message.PING_TYPE:
//CREATE PING
response = MessageFactory.getInstance().createPingMessage();
//SET ID
response.setId(message.getId());
//SEND BACK TO SENDER
message.getSender().sendMessage(response);
break;
case Message.VERSION_TYPE:
VersionMessage versionMessage = (VersionMessage) message;
//ADD TO LIST
synchronized(this.peerHeight)
{
this.peerHeight.put(versionMessage.getSender(), versionMessage.getHeight());
}
break;
case Message.GET_SIGNATURES_TYPE:
GetSignaturesMessage getHeadersMessage = (GetSignaturesMessage) message;
//ASK SIGNATURES FROM BLOCKCHAIN
List<byte[]> headers = this.blockChain.getSignatures(getHeadersMessage.getParent());
//CREATE RESPONSE WITH SAME ID
response = MessageFactory.getInstance().createHeadersMessage(headers);
response.setId(message.getId());
//SEND RESPONSE BACK WITH SAME ID
message.getSender().sendMessage(response);
break;
case Message.GET_BLOCK_TYPE:
GetBlockMessage getBlockMessage = (GetBlockMessage) message;
//ASK BLOCK FROM BLOCKCHAIN
block = this.blockChain.getBlock(getBlockMessage.getSignature());
//CREATE RESPONSE WITH SAME ID
response = MessageFactory.getInstance().createBlockMessage(block);
response.setId(message.getId());
//SEND RESPONSE BACK WITH SAME ID
message.getSender().sendMessage(response);
break;
case Message.BLOCK_TYPE:
BlockMessage blockMessage = (BlockMessage) message;
//ASK BLOCK FROM BLOCKCHAIN
block = blockMessage.getBlock();
//CHECK IF VALID
if(this.blockChain.isNewBlockValid(block))
{
Logger.getGlobal().info("received new valid block");
//PROCESS
this.synchronizer.process(block);
//BROADCAST
List<Peer> excludes = new ArrayList<Peer>();
excludes.add(message.getSender());
this.network.broadcast(message, excludes);
//UPDATE ALL PEER HEIGHTS TO OUR HEIGHT
/*synchronized(this.peerHeight)
{
for(Peer peer: this.peerHeight.keySet())
{
this.peerHeight.put(peer, this.blockChain.getHeight());
}
}*/
}
else
{
synchronized(this.peerHeight)
{
//UPDATE SENDER HEIGHT + 1
this.peerHeight.put(message.getSender(), blockMessage.getHeight());
}
}
break;
case Message.TRANSACTION_TYPE:
TransactionMessage transactionMessage = (TransactionMessage) message;
//GET TRANSACTION
Transaction transaction = transactionMessage.getTransaction();
//CHECK IF SIGNATURE IS VALID OR GENESIS TRANSACTION
if(!transaction.isSignatureValid() || transaction.getType() == Transaction.GENESIS_TRANSACTION)
{
//DISHONEST PEER
this.network.onError(message.getSender());
return;
}
//CHECK IF TRANSACTION HAS MINIMUM FEE AND MINIMUM FEE PER BYTE
if(transaction.hasMinimumFee() && transaction.hasMinimumFeePerByte())
{
//ADD TO UNCONFIRMED TRANSACTIONS
this.blockGenerator.addUnconfirmedTransaction(transaction);
//NOTIFY OBSERVERS
//this.setChanged();
//this.notifyObservers(new ObserverMessage(ObserverMessage.LIST_TRANSACTION_TYPE, DatabaseSet.getInstance().getTransactionsDatabase().getTransactions()));
this.setChanged();
this.notifyObservers(new ObserverMessage(ObserverMessage.ADD_TRANSACTION_TYPE, transaction));
//BROADCAST
List<Peer> excludes = new ArrayList<Peer>();
excludes.add(message.getSender());
this.network.broadcast(message, excludes);
}
break;
}
}
}
public void addActivePeersObserver(Observer o)
{
this.network.addObserver(o);
}
public void removeActivePeersObserver(Observer o)
{
this.network.deleteObserver(o);
}
private void broadcastBlock(Block newBlock) {
//CREATE MESSAGE
Message message = MessageFactory.getInstance().createBlockMessage(newBlock);
//BROADCAST MESSAGE
List<Peer> excludes = new ArrayList<Peer>();
this.network.broadcast(message, excludes);
}
private void broadcastTransaction(Transaction transaction) {
//CREATE MESSAGE
Message message = MessageFactory.getInstance().createTransactionMessage(transaction);
//BROADCAST MESSAGE
List<Peer> excludes = new ArrayList<Peer>();
this.network.broadcast(message, excludes);
}
//SYNCHRONIZE
public boolean isUpToDate()
{
if(this.peerHeight.size() == 0)
{
return true;
}
int maxPeerHeight = this.getMaxPeerHeight();
int chainHeight = this.blockChain.getHeight();
return maxPeerHeight <= chainHeight;
}
public void update()
{
//UPDATE STATUS
this.status = STATUS_SYNCHRONIZING;
//NOTIFY
this.setChanged();
this.notifyObservers(new ObserverMessage(ObserverMessage.NETWORK_STATUS, this.status));
Peer peer = null;
try
{
//WHILE NOT UPTODATE
while(!this.isUpToDate())
{
//START UPDATE FROM HIGHEST HEIGHT PEER
peer = this.getMaxHeightPeer();
//SYNCHRONIZE FROM PEER
this.synchronizer.synchronize(peer);
}
}
catch (Exception e)
{
e.printStackTrace();
if(peer != null)
{
//DISHONEST PEER
this.network.onError(peer);
}
}
if(this.peerHeight.size() == 0)
{
//UPDATE STATUS
this.status = STATUS_NO_CONNECTIONS;
//NOTIFY
this.setChanged();
this.notifyObservers(new ObserverMessage(ObserverMessage.NETWORK_STATUS, this.status));
}
else
{
//UPDATE STATUS
this.status = STATUS_OKE;
//NOTIFY
this.setChanged();
this.notifyObservers(new ObserverMessage(ObserverMessage.NETWORK_STATUS, this.status));
}
}
private Peer getMaxHeightPeer()
{
Peer highestPeer = null;
int height = 0;
try
{
synchronized(this.peerHeight)
{
for(Peer peer: this.peerHeight.keySet())
{
if(peer == null)
{
highestPeer = peer;
}
else
{
//IF HEIGHT IS BIGGER
if(height < this.peerHeight.get(peer))
{
highestPeer = peer;
height = this.peerHeight.get(peer);
}
//IF HEIGHT IS SAME
if(height == this.peerHeight.get(peer))
{
//CHECK IF PING OF PEER IS BETTER
if(peer.getPing() < highestPeer.getPing())
{
highestPeer = peer;
}
}
}
}
}
}
catch(Exception e)
{
//PEER REMOVED WHILE ITERATING
}
return highestPeer;
}
private int getMaxPeerHeight()
{
int height = 0;
try
{
synchronized(this.peerHeight)
{
for(Peer peer: this.peerHeight.keySet())
{
if(height < this.peerHeight.get(peer))
{
height = this.peerHeight.get(peer);
}
}
}
}
catch(Exception e)
{
//PEER REMOVED WHILE ITERATING
}
return height;
}
//WALLET
public boolean doesWalletExists()
{
//CHECK IF WALLET EXISTS
return this.wallet.exists();
}
public boolean createWallet(byte[] seed, String password, int amount)
{
//IF NEW WALLET CREADED
return this.wallet.create(seed, password, amount, false);
}
public boolean recoverWallet(byte[] seed, String password, int amount)
{
return this.wallet.create(seed, password, amount, true);
}
public List<Account> getAccounts() {
return this.wallet.getAccounts();
}
public List<PrivateKeyAccount> getPrivateKeyAccounts() {
return this.wallet.getprivateKeyAccounts();
}
public String generateNewAccount()
{
return this.wallet.generateNewAccount();
}
public PrivateKeyAccount getPrivateKeyAccountByAddress(String address)
{
return this.wallet.getPrivateKeyAccount(address);
}
public Account getAccountByAddress(String address)
{
return this.wallet.getAccount(address);
}
public BigDecimal getUnconfirmedBalance(String address)
{
return this.wallet.getUnconfirmedBalance(address);
}
public void addWalletListener(Observer o)
{
this.wallet.addObserver(o);
}
public String importAccountSeed(byte[] accountSeed)
{
return this.wallet.importAccountSeed(accountSeed);
}
public byte[] exportAccountSeed(String address)
{
return this.wallet.exportAccountSeed(address);
}
public byte[] exportSeed()
{
return this.wallet.exportSeed();
}
public boolean deleteAccount(PrivateKeyAccount account)
{
return this.wallet.deleteAccount(account);
}
public void synchronizeWallet()
{
this.wallet.synchronize();
}
public boolean isWalletUnlocked()
{
return this.wallet.isUnlocked();
}
public boolean lockWallet()
{
return this.wallet.lock();
}
public boolean unlockWallet(String password)
{
return this.wallet.unlock(password);
}
public List<Pair<Account, Transaction>> getLastTransactions(int limit)
{
return this.wallet.getLastTransactions(limit);
}
public Transaction getTransaction(byte[] signature) {
//CHECK IF IN BLOCK
Block block = DBSet.getInstance().getTransactionParentMap().getParent(signature);
if(block != null)
{
return block.getTransaction(signature);
}
//CHECK IF IN TRANSACTION DATABASE
return DBSet.getInstance().getTransactionMap().get(signature);
}
public List<Transaction> getLastTransactions(Account account, int limit)
{
return this.wallet.getLastTransactions(account, limit);
}
public List<Pair<Account, Block>> getLastBlocks()
{
return this.wallet.getLastBlocks();
}
public List<Block> getLastBlocks(Account account)
{
return this.wallet.getLastBlocks(account);
}
public List<Pair<Account, Name>> getNames()
{
return this.wallet.getNames();
}
public List<Name> getNames(Account account)
{
return this.wallet.getNames(account);
}
public List<Pair<Account, NameSale>> getNameSales()
{
return this.wallet.getNameSales();
}
public List<NameSale> getNameSales(Account account)
{
return this.wallet.getNameSales(account);
}
public List<NameSale> getAllNameSales()
{
return DBSet.getInstance().getNameExchangeMap().getNameSales();
}
public List<Pair<Account, Poll>> getPolls()
{
return this.wallet.getPolls();
}
public List<Poll> getPolls(Account account)
{
return this.wallet.getPolls(account);
}
public void addAssetFavorite(Asset asset)
{
this.wallet.addAssetFavorite(asset);
}
public void removeAssetFavorite(Asset asset)
{
this.wallet.removeAssetFavorite(asset);
}
public boolean isAssetFavorite(Asset asset)
{
return this.wallet.isAssetFavorite(asset);
}
public Collection<Poll> getAllPolls()
{
return DBSet.getInstance().getPollMap().getValues();
}
public void onDatabaseCommit()
{
this.wallet.commit();
}
//BLOCKCHAIN
public int getHeight()
{
return this.blockChain.getHeight();
}
public Block getLastBlock()
{
return this.blockChain.getLastBlock();
}
public Block getBlock(byte[] header)
{
return this.blockChain.getBlock(header);
}
public Pair<Block, List<Transaction>> scanTransactions(Block block, int blockLimit, int transactionLimit, int type, int service, Account account) {
return this.blockChain.scanTransactions(block, blockLimit, transactionLimit, type, service, account);
}
public long getNextBlockGeneratingBalance()
{
return BlockGenerator.getNextBlockGeneratingBalance(DBSet.getInstance(), DBSet.getInstance().getBlockMap().getLastBlock());
}
public long getNextBlockGeneratingBalance(Block parent)
{
return BlockGenerator.getNextBlockGeneratingBalance(DBSet.getInstance(), parent);
}
//FORGE
public void newBlockGenerated(Block newBlock) {
//ADD TO BLOCKCHAIN
this.synchronizer.process(newBlock);
//BROADCAST
this.broadcastBlock(newBlock);
}
public List<Transaction> getUnconfirmedTransactions()
{
return this.blockGenerator.getUnconfirmedTransactions();
}
//BALANCES
public SortableList<Tuple2<String, Long>, BigDecimal> getBalances(long key)
{
return DBSet.getInstance().getBalanceMap().getBalancesSortableList(key);
}
public SortableList<Tuple2<String, Long>, BigDecimal> getBalances(Account account)
{
return DBSet.getInstance().getBalanceMap().getBalancesSortableList(account);
}
//NAMES
public Name getName(String nameName)
{
return DBSet.getInstance().getNameMap().get(nameName);
}
public NameSale getNameSale(String nameName)
{
return DBSet.getInstance().getNameExchangeMap().getNameSale(nameName);
}
//POLLS
public Poll getPoll(String name)
{
return DBSet.getInstance().getPollMap().get(name);
}
//ASSETS
public Asset getQoraAsset()
{
return DBSet.getInstance().getAssetMap().get(0l);
}
public Asset getAsset(long key)
{
return DBSet.getInstance().getAssetMap().get(key);
}
public SortableList<BigInteger, Order> getOrders(Asset have, Asset want)
{
return DBSet.getInstance().getOrderMap().getOrdersSortableList(have.getKey(), want.getKey());
}
public SortableList<Tuple2<BigInteger, BigInteger>, Trade> getTrades(Asset have, Asset want)
{
return DBSet.getInstance().getTradeMap().getTradesSortableList(have.getKey(), want.getKey());
}
public SortableList<Tuple2<BigInteger, BigInteger>, Trade> getTrades(Order order)
{
return DBSet.getInstance().getTradeMap().getTrades(order);
}
//TRANSACTIONS
public void onTransactionCreate(Transaction transaction)
{
//ADD TO UNCONFIRMED TRANSACTIONS
this.blockGenerator.addUnconfirmedTransaction(transaction);
//NOTIFY OBSERVERS
this.setChanged();
this.notifyObservers(new ObserverMessage(ObserverMessage.LIST_TRANSACTION_TYPE, DBSet.getInstance().getTransactionMap().getValues()));
this.setChanged();
this.notifyObservers(new ObserverMessage(ObserverMessage.ADD_TRANSACTION_TYPE, transaction));
//BROADCAST
this.broadcastTransaction(transaction);
}
public Pair<Transaction, Integer> sendPayment(PrivateKeyAccount sender, Account recipient, BigDecimal amount, BigDecimal fee)
{
//CREATE ONLY ONE TRANSACTION AT A TIME
synchronized(this.transactionCreator)
{
return this.transactionCreator.createPayment(sender, recipient, amount, fee);
}
}
public Pair<Transaction, Integer> registerName(PrivateKeyAccount registrant, Account owner, String name, String value, BigDecimal fee)
{
//CREATE ONLY ONE TRANSACTION AT A TIME
synchronized(this.transactionCreator)
{
return this.transactionCreator.createNameRegistration(registrant, new Name(owner, name, value), fee);
}
}
public Pair<Transaction, Integer> updateName(PrivateKeyAccount owner, Account newOwner, String name, String value, BigDecimal fee)
{
//CREATE ONLY ONE TRANSACTION AT A TIME
synchronized(this.transactionCreator)
{
return this.transactionCreator.createNameUpdate(owner, new Name(newOwner, name, value), fee);
}
}
public Pair<Transaction, Integer> sellName(PrivateKeyAccount owner, String name, BigDecimal amount, BigDecimal fee)
{
//CREATE ONLY ONE TRANSACTION AT A TIME
synchronized(this.transactionCreator)
{
return this.transactionCreator.createNameSale(owner, new NameSale(name, amount), fee);
}
}
public Pair<Transaction, Integer> cancelSellName(PrivateKeyAccount owner, NameSale nameSale, BigDecimal fee)
{
//CREATE ONLY ONE TRANSACTION AT A TIME
synchronized(this.transactionCreator)
{
return this.transactionCreator.createCancelNameSale(owner, nameSale, fee);
}
}
public Pair<Transaction, Integer> BuyName(PrivateKeyAccount buyer, NameSale nameSale, BigDecimal fee)
{
//CREATE ONLY ONE TRANSACTION AT A TIME
synchronized(this.transactionCreator)
{
return this.transactionCreator.createNamePurchase(buyer, nameSale, fee);
}
}
public Pair<Transaction, Integer> createPoll(PrivateKeyAccount creator, String name, String description, List<String> options, BigDecimal fee)
{
//CREATE ONLY ONE TRANSACTION AT A TIME
synchronized(this.transactionCreator)
{
//CREATE POLL OPTIONS
List<PollOption> pollOptions = new ArrayList<PollOption>();
for(String option: options)
{
pollOptions.add(new PollOption(option));
}
//CREATE POLL
Poll poll = new Poll(creator, name, description, pollOptions);
return this.transactionCreator.createPollCreation(creator, poll, fee);
}
}
public Pair<Transaction, Integer> createPollVote(PrivateKeyAccount creator, Poll poll, PollOption option, BigDecimal fee)
{
//CREATE ONLY ONE TRANSACTION AT A TIME
synchronized(this.transactionCreator)
{
//GET OPTION INDEX
int optionIndex = poll.getOptions().indexOf(option);
return this.transactionCreator.createPollVote(creator, poll.getName(), optionIndex, fee);
}
}
public Pair<Transaction, Integer> createArbitraryTransaction(PrivateKeyAccount creator, int service, byte[] data, BigDecimal fee)
{
//CREATE ONLY ONE TRANSACTION AT A TIME
synchronized(this.transactionCreator)
{
return this.transactionCreator.createArbitraryTransaction(creator, service, data, fee);
}
}
public Pair<Transaction, Integer> issueAsset(PrivateKeyAccount creator, String name, String description, long quantity, boolean divisible, BigDecimal fee)
{
//CREATE ONLY ONE TRANSACTION AT A TIME
synchronized(this.transactionCreator)
{
return this.transactionCreator.createIssueAssetTransaction(creator, name, description, quantity, divisible, fee);
}
}
public Pair<Transaction, Integer> createOrder(PrivateKeyAccount creator, Asset have, Asset want, BigDecimal amount, BigDecimal price, BigDecimal fee)
{
//CREATE ONLY ONE TRANSACTION AT A TIME
synchronized(this.transactionCreator)
{
return this.transactionCreator.createOrderTransaction(creator, have, want, amount, price, fee);
}
}
public Pair<Transaction, Integer> cancelOrder(PrivateKeyAccount creator, Order order, BigDecimal fee)
{
//CREATE ONLY ONE TRANSACTION AT A TIME
synchronized(this.transactionCreator)
{
return this.transactionCreator.createCancelOrderTransaction(creator, order, fee);
}
}
public Pair<Transaction, Integer> transferAsset(PrivateKeyAccount sender, Account recipient, Asset asset, BigDecimal amount, BigDecimal fee)
{
//CREATE ONLY ONE TRANSACTION AT A TIME
synchronized(this.transactionCreator)
{
return this.transactionCreator.createAssetTransfer(sender, recipient, asset, amount, fee);
}
}
public Pair<Transaction, Integer> sendMultiPayment(PrivateKeyAccount sender, List<Payment> payments, BigDecimal fee)
{
//CREATE ONLY ONE TRANSACTION AT A TIME
synchronized(this.transactionCreator)
{
return this.transactionCreator.sendMultiPayment(sender, payments, fee);
}
}
}