/* application.getRemoteWallet() * Copyright 2011-2012 the original author or authors. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package piuk.blockchain.android.service; import java.math.BigInteger; import java.net.URISyntaxException; import java.util.List; import java.util.Map; import org.json.simple.JSONValue; import org.spongycastle.util.encoders.Hex; import piuk.blockchain.android.EventListeners; import piuk.blockchain.android.MyBlock; import piuk.blockchain.android.MyRemoteWallet; import piuk.blockchain.android.MyTransaction; import piuk.blockchain.android.MyTransactionConfidence; import piuk.blockchain.android.MyTransactionInput; import piuk.blockchain.android.MyTransactionOutput; import piuk.blockchain.android.Constants; import piuk.blockchain.android.WalletApplication; import com.google.bitcoin.core.Sha256Hash; import com.google.bitcoin.core.TransactionInput; import com.google.bitcoin.core.TransactionOutput; import de.tavendo.autobahn.WebSocketConnection; public class WebSocketHandler { final static String URL = "ws://ws."+Constants.BLOCKCHAIN_DOMAIN+"/inv"; int nfailures = 0; static WalletApplication application; boolean isRunning = true; long lastConnectAttempt = 0; private final WebSocketConnection mConnection = new WebSocketConnection(); public int getBestChainHeight() { return getChainHead().getHeight(); } public MyBlock getChainHead() { if (application.getRemoteWallet() == null) return null; return application.getRemoteWallet().getLatestBlock(); } final private EventListeners.EventListener walletEventListener = new EventListeners.EventListener() { @Override public String getDescription() { return "Websocket Listener"; } @Override public void onWalletDidChange() { try { if (isRunning) { start(); } else if (isConnected()) { // Disconnect and reconnect // To resubscribe subscribe(); } } catch (Exception e) { e.printStackTrace(); } } }; public WebSocketHandler(WalletApplication application) { this.application = application; } public void send(String message) { try { if (mConnection.isConnected()) mConnection.sendTextMessage(message); } catch (Exception e) { e.printStackTrace(); } } public synchronized void subscribe() { if (application.getRemoteWallet() == null) return; System.out.println("Websocket subscribe"); send("{\"op\":\"blocks_sub\"}"); send("{\"op\":\"wallet_sub\",\"guid\":\""+ application.getRemoteWallet().getGUID() + "\"}"); String[] active = application.getRemoteWallet().getActiveAddresses(); for (String address : active) { send("{\"op\":\"addr_sub\", \"addr\":\""+ address + "\"}"); } } public boolean isConnected() { return mConnection != null && mConnection.isConnected(); } public void stop() { this.isRunning = false; if (mConnection.isConnected()) mConnection.disconnect(); EventListeners.removeEventListener(walletEventListener); } public void connect() throws URISyntaxException, InterruptedException { final WebSocketHandler handler = this; if (application.getRemoteWallet() == null) return; try { mConnection.connect(URL, new de.tavendo.autobahn.WebSocketHandler() { @Override public void onOpen() { handler.subscribe(); handler.nfailures = 0; } @Override public void onTextMessage(String message) { if (application.getRemoteWallet() == null) return; MyRemoteWallet wallet = application.getRemoteWallet(); try { Map<String, Object> top = (Map<String, Object>) JSONValue.parse(message); if (top == null) return; String op = (String) top.get("op"); if (op.equals("block")) { Map<String, Object> x = (Map<String, Object>) top.get("x"); if (x == null) return; Sha256Hash hash = new Sha256Hash(Hex.decode((String) x .get("hash"))); int blockIndex = ((Number) x.get("blockIndex")).intValue(); int blockHeight = ((Number) x.get("height")).intValue(); long time = ((Number) x.get("time")).longValue(); MyBlock block = new MyBlock(); block.height = blockHeight; block.hash = hash; block.blockIndex = blockIndex; block.time = time; if (application.getRemoteWallet() != null) { application.getRemoteWallet().setLatestBlock(block); } List<MyTransaction> transactions = wallet.getMyTransactions(); List<Number> txIndexes = (List<Number>) x.get("txIndexes"); for (Number txIndex : txIndexes) { for (MyTransaction tx : transactions) { MyTransactionConfidence confidence = (MyTransactionConfidence) tx .getConfidence(); if (tx.txIndex == txIndex.intValue() && confidence.height != blockHeight) { confidence.height = blockHeight; //confidence.runListeners(); } } } } else if (op.equals("utx")) { Map<String, Object> x = (Map<String, Object>) top.get("x"); MyTransaction tx = MyTransaction.fromJSONDict(x); if (wallet.prependTransaction(tx)) { BigInteger result = BigInteger.ZERO; for (TransactionInput input : tx.getInputs()) { // if the input is from me subtract the value MyTransactionInput myinput = (MyTransactionInput) input; if (wallet.isAddressMine(input.getFromAddress() .toString())) { result = result.subtract(myinput.value); wallet.setFinal_balance(wallet.getFinal_balance() .subtract(myinput.value)); wallet.setTotal_sent(wallet.getTotal_sent() .add(myinput.value)); } } for (TransactionOutput output : tx.getOutputs()) { // if the input is from me subtract the value MyTransactionOutput myoutput = (MyTransactionOutput) output; if (wallet.isAddressMine(myoutput.getToAddress() .toString())) { result = result.add(myoutput.getValue()); wallet.setFinal_balance(wallet.getFinal_balance().add(myoutput .getValue())); wallet.setTotal_received(wallet.getTotal_sent().add(myoutput .getValue())); } } tx.result = result; if (result.compareTo(BigInteger.ZERO) >= 0) { EventListeners.invokeOnCoinsReceived(tx, result.longValue()); } else { EventListeners.invokeOnCoinsSent(tx, result.longValue()); } } } else if (op.equals("on_change")) { String newChecksum = (String) top.get("checksum"); String oldChecksum = wallet.getChecksum(); System.out.println("On change " + newChecksum + " " + oldChecksum); if (!newChecksum.equals(oldChecksum)) { try { application.checkIfWalletHasUpdatedAndFetchTransactions(application.getRemoteWallet().getTemporyPassword()); } catch (Exception e) { e.printStackTrace(); } } } } catch (Exception e) { e.printStackTrace(); } } @Override public void onClose(int code, String reason) { ++handler.nfailures; } }); } catch (Exception e) { e.printStackTrace(); ++handler.nfailures; } lastConnectAttempt = System.currentTimeMillis(); System.out.println("WebSocket connect()"); EventListeners.addEventListener(walletEventListener); } public void start() { if (lastConnectAttempt > System.currentTimeMillis()-30000) return; this.isRunning = true; try { stop(); connect(); } catch (URISyntaxException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } }