Implemented the send service of bitcoin likes account with the new architecture

This commit is contained in:
hvarona 2018-10-19 00:13:42 -04:00
parent 6b37db9279
commit b3442a511e
7 changed files with 211 additions and 71 deletions

View file

@ -25,6 +25,12 @@ public interface BitcoinAddressDao {
@Query("SELECT * FROM bitcoin_address ba WHERE ba.address = :address")
BitcoinAddress getdadress(String address);
@Query("SELECT * FROM bitcoin_address ba WHERE ba.address_index = :index and ba.is_change = 'true'")
BitcoinAddress getChangeByIndex(long index);
@Query("SELECT MAX(ba.address_index) FROM bitcoin_address ba WHERE ba.account_id = :accountId and ba.is_change = 'true' ")
long getLastChangeAddress(long accountId);
@Insert(onConflict = OnConflictStrategy.REPLACE)
public long[] insertBitcoinAddresses(BitcoinAddress... addresses);
}

View file

@ -24,6 +24,15 @@ public interface BitcoinTransactionDao {
@Query("SELECT * FROM bitcoin_transaction bt WHERE bt.tx_id = :txid")
List<BitcoinTransaction> getTransactionsByTxid(String txid);
@Query("SELECT * FROM bitcoin_transaction bt WHERE bt.crypto_coin_transaction_id = :idCryptoCoinTransaction")
BitcoinTransaction getBitcoinTransactionByCryptoCoinTransaction(long idCryptoCoinTransaction);
@Query("SELECT * FROM bitcoin_transaction_gt_io bt WHERE bt.bitcoin_transaction_id= :idBitcoinTransaction")
List<BitcoinTransactionGTxIO> getGtxIOByTransaction(long idBitcoinTransaction);
@Query("SELECT * FROM bitcoin_transaction_gt_io bt WHERE bt.address= :address")
List<BitcoinTransactionGTxIO> getGtxIOByAddress(String address);
@Insert(onConflict = OnConflictStrategy.REPLACE)
public long[] insertBitcoinTransaction(BitcoinTransaction... transactions);

View file

@ -1,5 +1,7 @@
package cy.agorise.crystalwallet.enums;
import org.bitcoinj.core.NetworkParameters;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
@ -11,21 +13,25 @@ import java.util.List;
*/
public enum CryptoCoin implements Serializable {
BITCOIN(CryptoNet.BITCOIN,"BTC",8),
BITCOIN_TEST(CryptoNet.BITCOIN_TEST,"BTC",8),
LITECOIN(CryptoNet.LITECOIN,"LTC",8),
DASH(CryptoNet.DASH,"DASH",8),
DOGECOIN(CryptoNet.DOGECOIN,"DOGE",8),
BITSHARES(CryptoNet.BITSHARES,"BTS",5);
BITCOIN(CryptoNet.BITCOIN,"BTC",8,0,NetworkParameters.fromID(NetworkParameters.ID_MAINNET)),
BITCOIN_TEST(CryptoNet.BITCOIN_TEST,"BTC",8,1,NetworkParameters.fromID(NetworkParameters.ID_TESTNET)),
LITECOIN(CryptoNet.LITECOIN,"LTC",8,2,null),
DASH(CryptoNet.DASH,"DASH",8,5,null),
DOGECOIN(CryptoNet.DOGECOIN,"DOGE",8,3,null),
BITSHARES(CryptoNet.BITSHARES,"BTS",5,0,null);
protected CryptoNet cryptoNet;
protected String label;
protected int precision;
protected int coinNumber;
protected NetworkParameters parameters;
CryptoCoin(CryptoNet cryptoNet, String label, int precision){
CryptoCoin(CryptoNet cryptoNet, String label, int precision, int coinNumber, NetworkParameters parameters){
this.cryptoNet = cryptoNet;
this.label = label;
this.precision = precision;
this.coinNumber = coinNumber;
this.parameters = parameters;
}
@ -38,6 +44,14 @@ public enum CryptoCoin implements Serializable {
public int getPrecision(){
return this.precision;
}
public NetworkParameters getParameters() {
return parameters;
}
public int getCoinNumber() {
return coinNumber;
}
public static List<CryptoCoin> getByCryptoNet(CryptoNet cryptoNet){
List<CryptoCoin> result = new ArrayList<CryptoCoin>();

View file

@ -1,13 +1,16 @@
package cy.agorise.crystalwallet.manager;
import android.accounts.Account;
import android.content.Context;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionOutPoint;
import org.bitcoinj.crypto.ChildNumber;
import org.bitcoinj.crypto.DeterministicKey;
import org.bitcoinj.crypto.HDKeyDerivation;
import org.bitcoinj.script.Script;
@ -23,11 +26,16 @@ import cy.agorise.crystalwallet.apigenerator.InsightApiGenerator;
import cy.agorise.crystalwallet.apigenerator.insightapi.models.Txi;
import cy.agorise.crystalwallet.apigenerator.insightapi.models.Vin;
import cy.agorise.crystalwallet.apigenerator.insightapi.models.Vout;
import cy.agorise.crystalwallet.application.CrystalApplication;
import cy.agorise.crystalwallet.dao.CrystalDatabase;
import cy.agorise.crystalwallet.enums.CryptoCoin;
import cy.agorise.crystalwallet.models.AccountSeed;
import cy.agorise.crystalwallet.models.BitcoinAddress;
import cy.agorise.crystalwallet.models.BitcoinTransaction;
import cy.agorise.crystalwallet.models.BitcoinTransactionGTxIO;
import cy.agorise.crystalwallet.models.CryptoCoinBalance;
import cy.agorise.crystalwallet.models.CryptoCoinTransaction;
import cy.agorise.crystalwallet.models.CryptoCurrency;
import cy.agorise.crystalwallet.models.CryptoNetAccount;
import cy.agorise.crystalwallet.models.GTxIO;
import cy.agorise.crystalwallet.models.GeneralCoinAddress;
@ -102,6 +110,7 @@ public class GeneralAccountManager implements CryptoAccountManager, CryptoNetInf
if (!ccTransaction.isConfirmed() && btTransaction.getConfirmations() >= cryptoCoin.getCryptoNet().getConfirmationsNeeded()) {
ccTransaction.setConfirmed(true);
db.transactionDao().insertTransaction(ccTransaction);
updateBalance(ccTransaction,(ccTransaction.getInput()?1:-1)*ccTransaction.getAmount(),db);
}
db.bitcoinTransactionDao().insertBitcoinTransaction(btTransaction);
@ -140,8 +149,26 @@ public class GeneralAccountManager implements CryptoAccountManager, CryptoNetInf
input.setOriginalTxId(vin.txid);
input.setScriptHex(vin.scriptSig.hex);
BitcoinAddress address = db.bitcoinAddressDao().getdadress(addr);
if(address != null){
if(ccTransaction.getAccountId() < 0){
ccTransaction.setAccountId(address.getAccountId());
ccTransaction.setFrom(addr);
ccTransaction.setInput(false);
}
if(ccTransaction.getAccountId()== address.getAccountId()){
amount -= vin.value;
}
}
if(ccTransaction.getFrom() == null || ccTransaction.getFrom().isEmpty()){
ccTransaction.setFrom(addr);
}
gtxios.add(input);
}
for (Vout vout : txi.vout) {
@ -155,90 +182,118 @@ public class GeneralAccountManager implements CryptoAccountManager, CryptoNetInf
output.setOutput(false);
output.setAmount((long) (vout.value * Math.pow(10, cryptoCoin.getPrecision())));
output.setScriptHex(vout.scriptPubKey.hex);
output.setOriginalTxId(txi.txid);
gtxios.add(output);
/*for (GeneralCoinAddress address : this.mAddresses) {
if (address.getAddressString(this.mAccount.getNetworkParam()).equals(addr)) {
output.setAddress(address);
tempAccount = address.getAccount();
if (!address.hasTransactionInput(output, this.mAccount.getNetworkParam())) {
address.getTransactionInput().add(output);
BitcoinAddress address = db.bitcoinAddressDao().getdadress(addr);
if(address != null){
if(ccTransaction.getAccountId() < 0){
ccTransaction.setAccountId(address.getAccountId());
ccTransaction.setInput(true);
ccTransaction.setTo(addr);
}
if(ccTransaction.getAccountId()== address.getAccountId()){
amount += vout.value;
}
}else{
//TOOD multiple send address
if(ccTransaction.getTo() == null || ccTransaction.getTo().isEmpty()){
ccTransaction.setTo(addr);
}
changed = true;
}
}*/
}
}
ccTransaction.setAmount(amount);
CryptoCurrency currency = db.cryptoCurrencyDao().getByNameAndCryptoNet(this.cryptoCoin.name(), this.cryptoCoin.getCryptoNet().name());
if (currency == null) {
currency = new CryptoCurrency();
currency.setCryptoNet(this.cryptoCoin.getCryptoNet());
currency.setName(this.cryptoCoin.name());
currency.setPrecision(this.cryptoCoin.getPrecision());
long idCurrency = db.cryptoCurrencyDao().insertCryptoCurrency(currency)[0];
currency.setId(idCurrency);
}
ccTransaction.setIdCurrency((int)currency.getId());
long ccId = db.transactionDao().insertTransaction(ccTransaction)[0];
btTransaction.setCryptoCoinTransactionId(ccId);
long btId = db.bitcoinTransactionDao().insertBitcoinTransaction(btTransaction)[0];
for(BitcoinTransactionGTxIO gtxio : gtxios){
gtxio.setBitcoinTransactionId(btId);
db.bitcoinTransactionDao().insertBitcoinTransactionGTxIO(gtxio);
}
//TODO database
/*SCWallDatabase db = new SCWallDatabase(this.mContext);
long idTransaction = db.getGeneralTransactionId(transaction);
if (idTransaction == -1) {
db.putGeneralTransaction(transaction);
} else {
transaction.setId(idTransaction);
db.updateGeneralTransaction(transaction);
}*/
/*if (tempAccount != null && transaction.getConfirm() < this.mAccount.getCryptoNet().getConfirmationsNeeded()) {
InsightApiGenerator.followTransaction();
new GetTransactionData(transaction.getTxid(), tempAccount, this.serverUrl, this.mContext, true).start();
}
for (GeneralCoinAddress address : this.mAddresses) {
if (address.updateTransaction(transaction)) {
break;
if(ccTransaction.isConfirmed()) {
updateBalance(ccTransaction,amount,db);
}
}*/
}
}
private void updateBalance(CryptoCoinTransaction ccTransaction, long amount, CrystalDatabase db){
CryptoCurrency currency = db.cryptoCurrencyDao().getByNameAndCryptoNet(this.cryptoCoin.name(), this.cryptoCoin.getCryptoNet().name());
if (currency == null) {
currency = new CryptoCurrency();
currency.setCryptoNet(this.cryptoCoin.getCryptoNet());
currency.setName(this.cryptoCoin.name());
currency.setPrecision(this.cryptoCoin.getPrecision());
long idCurrency = db.cryptoCurrencyDao().insertCryptoCurrency(currency)[0];
currency.setId(idCurrency);
}
CryptoCoinBalance balance = db.cryptoCoinBalanceDao().getBalanceFromAccount(ccTransaction.getAccountId(), currency.getId());
if (balance == null) {
balance = new CryptoCoinBalance();
balance.setAccountId(ccTransaction.getAccountId());
balance.setCryptoCurrencyId(currency.getId());
long idBalance = db.cryptoCoinBalanceDao().insertCryptoCoinBalance(balance)[0];
balance.setId(idBalance);
}
balance.setBalance(balance.getBalance()+amount);
db.cryptoCoinBalanceDao().insertCryptoCoinBalance(balance);
}
public void send(final GeneralAccountSendRequest request){
//TODO check server connection
//TODO validate to address
InsightApiGenerator.getEstimateFee(request.getAccount().getCryptoCoin(),new ApiRequest(1, new ApiRequestListener() {
InsightApiGenerator.getEstimateFee(this.cryptoCoin,new ApiRequest(1, new ApiRequestListener() {
@Override
public void success(Object answer, int idPetition) {
Transaction tx = new Transaction(request.getAccount().getNetworkParam());
Transaction tx = new Transaction(cryptoCoin.getParameters());
long currentAmount = 0;
long fee = -1;
long feeRate = (Long) answer;
fee = 226 * feeRate;
List<GeneralCoinAddress> addresses = request.getAccount().getAddresses();
List<GTxIO> utxos = new ArrayList();
for(GeneralCoinAddress address : addresses){
List<GTxIO> addrUtxos = address.getUTXos();
for(GTxIO addrUtxo : addrUtxos){
utxos.add(addrUtxo);
currentAmount += addrUtxo.getAmount();
if(currentAmount >= request.getAmount()+ fee){
break;
}
}
if(currentAmount >= request.getAmount() + fee){
break;
}
}
CrystalDatabase db = CrystalDatabase.getAppDatabase(request.getContext());
db.bitcoinTransactionDao();
List<BitcoinTransactionGTxIO> utxos = getUtxos(request.getAccount().getId(),db);
if(currentAmount< request.getAmount() + fee){
request.setStatus(GeneralAccountSendRequest.StatusCode.NO_BALANCE);
return;
}
AccountSeed seed = db.accountSeedDao().findById(request.getAccount().getSeedId());
DeterministicKey purposeKey = HDKeyDerivation.deriveChildKey((DeterministicKey) seed.getPrivateKey(),
new ChildNumber(44, true));
DeterministicKey coinKey = HDKeyDerivation.deriveChildKey(purposeKey,
new ChildNumber(cryptoCoin.getCoinNumber(), true));
DeterministicKey accountKey = HDKeyDerivation.deriveChildKey(coinKey,
new ChildNumber(request.getAccount().getAccountIndex(), true));
DeterministicKey externalKey = HDKeyDerivation.deriveChildKey(accountKey,
new ChildNumber(0, false));
DeterministicKey changeKey = HDKeyDerivation.deriveChildKey(accountKey,
new ChildNumber(1, false));
//String to an address
Address toAddr = Address.fromBase58(request.getAccount().getNetworkParam(), request.getToAccount());
Address toAddr = Address.fromBase58(cryptoCoin.getParameters(), request.getToAccount());
tx.addOutput(Coin.valueOf(request.getAmount()), toAddr);
if(request.getMemo()!= null && !request.getMemo().isEmpty()){
/*if(request.getMemo()!= null && !request.getMemo().isEmpty()){
String memo = request.getMemo();
if(request.getMemo().length()>40){
memo = memo.substring(0,40);
@ -249,30 +304,50 @@ public class GeneralAccountManager implements CryptoAccountManager, CryptoNetInf
System.arraycopy(memo.getBytes(),0,scriptByte,2,memo.length());
Script memoScript = new Script(scriptByte);
tx.addOutput(Coin.valueOf(0),memoScript);
}
}*/
//Change address
long remain = currentAmount - request.getAmount() - fee;
if( remain > 0 ) {
Address changeAddr = Address.fromBase58(request.getAccount().getNetworkParam(), request.getAccount().getNextChangeAddress());
long index = db.bitcoinAddressDao().getLastChangeAddress(request.getAccount().getId());
BitcoinAddress btAddress = db.bitcoinAddressDao().getChangeByIndex(index);
Address changeAddr;
if(btAddress != null && db.bitcoinTransactionDao().getGtxIOByAddress(btAddress.getAddress()).size()<=0){
changeAddr = Address.fromBase58(cryptoCoin.getParameters(), btAddress.getAddress());
}else{
if(btAddress == null){
index = 0;
}else{
index++;
}
btAddress = new BitcoinAddress();
btAddress.setIndex(index);
btAddress.setAccountId(request.getAccount().getId());
btAddress.setChange(true);
btAddress.setAddress(HDKeyDerivation.deriveChildKey(changeKey, new ChildNumber((int) btAddress.getIndex(), false)).toAddress(cryptoCoin.getParameters()).toString());
db.bitcoinAddressDao().insertBitcoinAddresses(btAddress);
changeAddr = Address.fromBase58(cryptoCoin.getParameters(), btAddress.getAddress());
}
tx.addOutput(Coin.valueOf(remain), changeAddr);
}
for(GTxIO utxo: utxos) {
Sha256Hash txHash = Sha256Hash.wrap(utxo.getTransaction().getTxid());
for(BitcoinTransactionGTxIO utxo: utxos) {
Sha256Hash txHash = Sha256Hash.wrap(utxo.getOriginalTxId());
Script script = new Script(Util.hexToBytes(utxo.getScriptHex()));
TransactionOutPoint outPoint = new TransactionOutPoint(request.getAccount().getNetworkParam(), utxo.getIndex(), txHash);
if(utxo.getAddress().getKey().isPubKeyOnly()){
if(utxo.getAddress().isIsChange()){
utxo.getAddress().setKey(HDKeyDerivation.deriveChildKey(request.getAccount().getChangeKey(), new ChildNumber(utxo.getAddress().getIndex(), false)));
}else{
utxo.getAddress().setKey(HDKeyDerivation.deriveChildKey(request.getAccount().getExternalKey(), new ChildNumber(utxo.getAddress().getIndex(), false)));
}
TransactionOutPoint outPoint = new TransactionOutPoint(cryptoCoin.getParameters(), utxo.getIndex(), txHash);
BitcoinAddress btAddress = db.bitcoinAddressDao().getdadress(utxo.getAddress());
ECKey addrKey;
if(btAddress.isChange()){
addrKey = HDKeyDerivation.deriveChildKey(changeKey, new ChildNumber((int) btAddress.getIndex(), false));
}else{
addrKey = HDKeyDerivation.deriveChildKey(changeKey, new ChildNumber((int) btAddress.getIndex(), true));
}
tx.addSignedInput(outPoint, script, utxo.getAddress().getKey(), Transaction.SigHash.ALL, true);
tx.addSignedInput(outPoint, script, addrKey, Transaction.SigHash.ALL, true);
}
InsightApiGenerator.broadcastTransaction(request.getAccount().getCryptoCoin(),Util.bytesToHex(tx.bitcoinSerialize()),new ApiRequest(1, new ApiRequestListener() {
InsightApiGenerator.broadcastTransaction(cryptoCoin,Util.bytesToHex(tx.bitcoinSerialize()),new ApiRequest(1, new ApiRequestListener() {
@Override
public void success(Object answer, int idPetition) {
request.setStatus(GeneralAccountSendRequest.StatusCode.SUCCEEDED);
@ -292,4 +367,37 @@ public class GeneralAccountManager implements CryptoAccountManager, CryptoNetInf
}
}));
}
private List<BitcoinTransactionGTxIO> getUtxos(long accountId, CrystalDatabase db){
List<BitcoinTransactionGTxIO> answer = new ArrayList<>();
List<BitcoinTransactionGTxIO> bTGTxI = new ArrayList<>();
List<BitcoinTransactionGTxIO> bTGTxO = new ArrayList<>();
List<CryptoCoinTransaction> ccTransactions = db.transactionDao().getByIdAccount(accountId);
for(CryptoCoinTransaction ccTransaction : ccTransactions) {
List<BitcoinTransactionGTxIO> gtxios = db.bitcoinTransactionDao().getGtxIOByTransaction(ccTransaction.getId());
for(BitcoinTransactionGTxIO gtxio : gtxios){
if(db.bitcoinAddressDao().addressExists(gtxio.getAddress())){
if(gtxio.isOutput()){
bTGTxO.add(gtxio);
}else{
bTGTxI.add(gtxio);
}
}
}
}
for(BitcoinTransactionGTxIO gtxi : bTGTxI){
boolean find = false;
for(BitcoinTransactionGTxIO gtxo : bTGTxO){
if(gtxo.getOriginalTxId().equals(gtxi.getOriginalTxId())){
find = true;
break;
}
}
if(!find){
answer.add(gtxi);
}
}
return answer;
}
}

View file

@ -55,6 +55,9 @@ public class BitcoinAddress {
this.address = address;
}
public BitcoinAddress() {
}
public long getAccountId() {
return accountId;
}

View file

@ -66,7 +66,7 @@ public class CryptoCoinTransaction {
* The id of the account assoiciated, this is used for the foreign key definition
*/
@ColumnInfo(name="account_id")
protected long accountId;
protected long accountId = -1;
/**
* The amount of asset is moved in this transaction
*/

View file

@ -3,7 +3,7 @@ package cy.agorise.crystalwallet.requestmanagers;
import android.content.Context;
import cy.agorise.crystalwallet.enums.CryptoCoin;
import cy.agorise.crystalwallet.models.GeneralCoinAccount;
import cy.agorise.crystalwallet.models.CryptoNetAccount;
public class GeneralAccountSendRequest extends CryptoNetInfoRequest {
/**
@ -23,7 +23,7 @@ public class GeneralAccountSendRequest extends CryptoNetInfoRequest {
// The app context
private Context mContext;
//The soruce Account
private GeneralCoinAccount mAccount;
private CryptoNetAccount mAccount;
// The destination account address
private String mToAccount;
// The amount of the transaction
@ -33,7 +33,7 @@ public class GeneralAccountSendRequest extends CryptoNetInfoRequest {
// The state of this request
private StatusCode status = StatusCode.NOT_STARTED;
public GeneralAccountSendRequest(CryptoCoin coin, Context context, GeneralCoinAccount account, String toAccount, long amount, String memo) {
public GeneralAccountSendRequest(CryptoCoin coin, Context context, CryptoNetAccount account, String toAccount, long amount, String memo) {
super(coin);
this.mContext = context;
this.mAccount = account;
@ -42,7 +42,7 @@ public class GeneralAccountSendRequest extends CryptoNetInfoRequest {
this.mMemo = memo;
}
public GeneralAccountSendRequest(CryptoCoin coin, Context context, GeneralCoinAccount account, String toAccount, long amount) {
public GeneralAccountSendRequest(CryptoCoin coin, Context context, CryptoNetAccount account, String toAccount, long amount) {
this(coin,context,account,toAccount,amount,null);
}
@ -51,7 +51,7 @@ public class GeneralAccountSendRequest extends CryptoNetInfoRequest {
return mContext;
}
public GeneralCoinAccount getAccount() {
public CryptoNetAccount getAccount() {
return mAccount;
}