package com.yoghurt.crypto.transactions.client.ui;
import java.util.ArrayList;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.AcceptsOneWidget;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import com.googlecode.gwt.crypto.bouncycastle.util.encoders.Hex;
import com.googlecode.gwt.crypto.util.Str;
import com.yoghurt.crypto.transactions.client.place.MinePlace;
import com.yoghurt.crypto.transactions.client.place.MinePlace.MineDataType;
import com.yoghurt.crypto.transactions.client.util.AppAsyncCallback;
import com.yoghurt.crypto.transactions.client.util.MorphCallback;
import com.yoghurt.crypto.transactions.client.util.block.BlockEncodeUtil;
import com.yoghurt.crypto.transactions.client.util.block.BlockParseUtil;
import com.yoghurt.crypto.transactions.client.util.transaction.ComputeUtil;
import com.yoghurt.crypto.transactions.client.util.transaction.TransactionEncodeUtil;
import com.yoghurt.crypto.transactions.client.util.transaction.TransactionParseUtil;
import com.yoghurt.crypto.transactions.shared.domain.Block;
import com.yoghurt.crypto.transactions.shared.domain.BlockInformation;
import com.yoghurt.crypto.transactions.shared.domain.RawBlockContainer;
import com.yoghurt.crypto.transactions.shared.domain.RawTransactionContainer;
import com.yoghurt.crypto.transactions.shared.domain.Transaction;
import com.yoghurt.crypto.transactions.shared.domain.TransactionInformation;
import com.yoghurt.crypto.transactions.shared.service.BlockchainRetrievalServiceAsync;
import com.yoghurt.crypto.transactions.shared.util.ArrayUtil;
import com.yoghurt.crypto.transactions.shared.util.NumberEncodeUtil;
/**
* TODO GET RID OF HISTORICAL SIMULATION AND USE GETWORK/GETBLOCKTEMPLATE TYPE OF CONSTRUCT
*/
public class MineActivity extends LookupActivity<BlockInformation, MinePlace> implements MineView.Presenter {
private static final String SEAN_OUTPOST_HASH_160 = "01000000010000000000000000000000000000000000000000000000000000000000000000FFFFFFFF0487654321FFFFFFFF0100F90295000000001976A914DC863734A218BFE83EF770EE9D41A27F824A6E5688AC00000000";
/**
* Thirty seconds
*/
private static final int LATEST_BLOCK_POLL_DELAY = 30 * 1000;
private static final int SHORT_INITIAL_POLL_DELAY = 500;
private final MineView view;
private final Timer timer = new Timer() {
@Override
public void run() {
getLatestBlock();
}
};
private String latestBlock;
private final AsyncCallback<String> callback = new AppAsyncCallback<String>() {
@Override
public void onSuccess(final String result) {
retrieving = false;
if (result == null) {
timer.schedule(LATEST_BLOCK_POLL_DELAY);
return;
}
// I have no idea which of the endians this is and this isn't a time in which I like being in a thinking mood
final byte[] otherEndianBytes = Hex.decode(result);
ArrayUtil.reverse(otherEndianBytes);
final String otherEndianResult = Str.toString(Hex.encode(otherEndianBytes));
if (!otherEndianResult.equals(latestBlock)) {
latestBlock = result;
view.broadcastLatestBlock(otherEndianResult);
}
timer.schedule(LATEST_BLOCK_POLL_DELAY);
}
};
private boolean retrieving;
@Inject
public MineActivity(final MineView view, @Assisted final MinePlace place, final BlockchainRetrievalServiceAsync service) {
super(place, service);
this.view = view;
}
@Override
protected void doLookup(final MinePlace place, final AsyncCallback<BlockInformation> callback) {
switch (place.getType()) {
case HEIGHT:
service.getBlockInformationFromHeight(Integer.parseInt(place.getPayload()), callback);
break;
case ID:
service.getBlockInformationFromHash(place.getPayload(), callback);
break;
case LAST:
service.getBlockInformationLast(new MorphCallback<BlockInformation, BlockInformation>(callback) {
@Override
protected BlockInformation morphResult(final BlockInformation result) {
final TransactionInformation ti = new TransactionInformation();
ti.setRawHex(SEAN_OUTPOST_HASH_160);
result.setCoinbaseInformation(ti);
return result;
}
});
break;
default:
callback.onFailure(new IllegalStateException("No support lookup for type: " + place.getType().name()));
return;
}
}
@Override
public void onStop() {
timer.cancel();
view.cancel();
super.onStop();
}
@Override
protected boolean mustPerformLookup(final MinePlace place) {
return place.getType() == MineDataType.HEIGHT || place.getType() == MineDataType.LAST || place.getType() == MineDataType.ID;
}
@Override
protected BlockInformation createInfo(final MinePlace place) {
final BlockInformation blockInformation = new BlockInformation();
blockInformation.setRawBlockHeaders(place.getPayload());
return blockInformation;
}
@Override
protected void doDeferredStart(final AcceptsOneWidget panel, final BlockInformation blockInformation) {
view.setPresenter(this);
panel.setWidget(view);
Block block;
final Transaction coinbase;
try {
block = BlockParseUtil.parseBlockBytes(Hex.decode(blockInformation.getRawBlockHeaders()));
coinbase = TransactionParseUtil.parseTransactionBytes(Hex.decode(blockInformation.getCoinbaseInformation().getRawHex()));
} catch (final Throwable e) {
// TODO Throw error
return;
}
// Store this block's hash
latestBlock = Str.toString(Hex.encode(block.getBlockHash()));
final RawBlockContainer rawBlock = new RawBlockContainer();
final RawTransactionContainer rawTransaction = new RawTransactionContainer();
try {
BlockEncodeUtil.encodeBlock(block, rawBlock);
TransactionEncodeUtil.encodeTransaction(coinbase, rawTransaction);
} catch (final Throwable e) {
// TODO Throw error
return;
}
final boolean customMiningSession = isCustomMiningPlace();
// If this is a custom mining session (which includes custom coinbase), do some special stuff (mining on top of tip)
if(customMiningSession) {
// Set the previous block hash to last/current block hash
final byte[] blockHash = ComputeUtil.computeDoubleSHA256(rawBlock.values());
block.setPreviousBlockHash(blockHash);
rawBlock.setPreviousBlockHash(blockHash);
// Reset the nonce
block.setNonce(0);
rawBlock.setNonce(NumberEncodeUtil.encodeUint32(0));
// Set the merkle root to the coinbase tx only
final ArrayList<byte[]> txs = new ArrayList<byte[]>();
txs.add(rawTransaction.getBytes());
final byte[] merkleRoot = ComputeUtil.computeMerkleRoot(txs);
block.setMerkleRoot(merkleRoot);
rawBlock.setMerkleRoot(merkleRoot);
}
view.setInformation(block, rawBlock, rawTransaction, customMiningSession);
}
private boolean isCustomMiningPlace() {
return place.getType() == MineDataType.LAST || place.getType() == MineDataType.RAW;
}
@Override
protected void doDeferredError(final AcceptsOneWidget panel, final Throwable caught) {
// Not supported
}
@Override
public void startPoll() {
timer.schedule(SHORT_INITIAL_POLL_DELAY);
}
@Override
public void pausePoll() {
retrieving = false;
timer.cancel();
}
@Override
public void getLatestBlock() {
if (retrieving) {
return;
}
retrieving = true;
service.getLatestBlockHash(callback);
}
}