package com.yoghurt.crypto.transactions.client.ui;
import java.util.Date;
import java.util.Map.Entry;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Widget;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.googlecode.gwt.crypto.bouncycastle.util.encoders.Hex;
import com.googlecode.gwt.crypto.util.Str;
import com.yoghurt.crypto.transactions.client.di.BitcoinPlaceRouter;
import com.yoghurt.crypto.transactions.client.util.FormatUtil;
import com.yoghurt.crypto.transactions.client.util.RepeatingExecutor;
import com.yoghurt.crypto.transactions.client.util.block.BlockEncodeUtil;
import com.yoghurt.crypto.transactions.client.util.transaction.ComputeUtil;
import com.yoghurt.crypto.transactions.client.widget.BitsTargetHexViewer;
import com.yoghurt.crypto.transactions.client.widget.BlockHexViewer;
import com.yoghurt.crypto.transactions.client.widget.BlockViewer;
import com.yoghurt.crypto.transactions.client.widget.TargettedHashHexViewer;
import com.yoghurt.crypto.transactions.client.widget.TransactionHexViewer;
import com.yoghurt.crypto.transactions.client.widget.ValueViewer;
import com.yoghurt.crypto.transactions.shared.domain.Block;
import com.yoghurt.crypto.transactions.shared.domain.RawBlockContainer;
import com.yoghurt.crypto.transactions.shared.domain.RawTransactionContainer;
import com.yoghurt.crypto.transactions.shared.domain.TransactionPartType;
import com.yoghurt.crypto.transactions.shared.util.ArrayUtil;
import com.yoghurt.crypto.transactions.shared.util.NumberEncodeUtil;
import com.yoghurt.crypto.transactions.shared.util.NumberParseUtil;
@Singleton
public class MineViewImpl extends Composite implements MineView {
private static final int MINING_SIMULATION_DELAY = 250;
interface MineViewImplUiBinder extends UiBinder<Widget, MineViewImpl> {}
private static final MineViewImplUiBinder UI_BINDER = GWT.create(MineViewImplUiBinder.class);
@UiField ValueViewer versionViewer;
@UiField(provided = true) BlockViewer previousBlockHashViewer;
@UiField ValueViewer merkleRootViewer;
@UiField ValueViewer timestampViewer;
@UiField ValueViewer bitsViewer;
@UiField ValueViewer nonceViewer;
@UiField BlockHexViewer blockHexViewer;
@UiField TargettedHashHexViewer blockHashViewer;
@UiField TransactionHexViewer coinbaseHexViewer;
@UiField BitsTargetHexViewer targetViewer;
private final ScheduledCommand defferedTimeHash = new ScheduledCommand() {
@Override
public void execute() {
synchronizeTime();
doHashCycle();
}
};
private final ScheduledCommand defferedNonceHash = new ScheduledCommand() {
@Override
public void execute() {
incrementNonce();
doHashCycle();
}
};
private final ScheduledCommand defferedNonceDecrementHash = new ScheduledCommand() {
@Override
public void execute() {
decrementNonce();
doHashCycle();
}
};
private final ScheduledCommand defferedExtraNonceHash = new ScheduledCommand() {
@Override
public void execute() {
incrementExtraNonce();
doHashCycle();
}
};
private final ScheduledCommand defferedExtraNonceDecrementHash = new ScheduledCommand() {
@Override
public void execute() {
decrementExtraNonce();
doHashCycle();
}
};
private final ScheduledCommand executeHashCommand = new ScheduledCommand() {
@Override
public void execute() {
doFullHashCycle();
}
};
private final RepeatingExecutor executor = new RepeatingExecutor(executeHashCommand);
private RawBlockContainer rawBlock;
private Presenter presenter;
private boolean custom;
private RawTransactionContainer coinbase;
@Inject
public MineViewImpl(final BitcoinPlaceRouter router) {
previousBlockHashViewer = new BlockViewer(router);
initWidget(UI_BINDER.createAndBindUi(this));
}
@Override
public void setInformation(final Block initialBlock, final RawBlockContainer rawBlock, final RawTransactionContainer coinbase, final boolean custom) {
this.rawBlock = rawBlock;
this.coinbase = coinbase;
this.custom = custom;
versionViewer.setValue(initialBlock.getVersion());
final byte[] previousBlockHashFlipped = ArrayUtil.reverseCopy(initialBlock.getPreviousBlockHash());
previousBlockHashViewer.setValue(Str.toString(Hex.encode(previousBlockHashFlipped)).toUpperCase());
merkleRootViewer.setValue(Str.toString(Hex.encode(initialBlock.getMerkleRoot())).toUpperCase());
timestampViewer.setValue(FormatUtil.formatDateTime(initialBlock.getTimestamp()));
bitsViewer.setValue(initialBlock.getBits());
nonceViewer.setValue(initialBlock.getNonce());
final RawBlockContainer viewBlock = rawBlock.copy();
// Set up viewBlock for display in hex
blockHexViewer.setValue(viewBlock.entrySet());
coinbaseHexViewer.setValue(coinbase);
targetViewer.setBits(initialBlock.getBits());
blockHashViewer.setDifficulty(initialBlock.getBits());
blockHashViewer.setHash(initialBlock.getBlockHash());
// If we need to stay fly with the latest on the hood, set up the timers
if (custom) {
startHashExecutor();
presenter.startPoll();
}
}
@Override
public void cancel() {
executor.cancel();
}
@UiHandler("cancelButton")
public void onCancelClick(final ClickEvent e) {
cancel();
if (custom) {
presenter.pausePoll();
}
}
@UiHandler("continueButton")
public void onContinueClick(final ClickEvent e) {
startHashExecutor();
if (custom) {
presenter.startPoll();
}
}
@UiHandler("singleCycleButton")
public void onSingleCycleClick(final ClickEvent e) {
doSingleCycle();
}
@UiHandler("nonceIncrementButton")
public void onNonceIncrementClick(final ClickEvent e) {
Scheduler.get().scheduleDeferred(defferedNonceHash);
}
@UiHandler("nonceDecrementButton")
public void onNonceDecrementClick(final ClickEvent e) {
Scheduler.get().scheduleDeferred(defferedNonceDecrementHash);
}
@UiHandler("extraNonceIncrementButton")
public void onExtraNonceIncrementClick(final ClickEvent e) {
Scheduler.get().scheduleDeferred(defferedExtraNonceHash);
}
@UiHandler("extraNonceDecrementButton")
public void onExtraNonceDecrementClick(final ClickEvent e) {
Scheduler.get().scheduleDeferred(defferedExtraNonceDecrementHash);
}
@UiHandler("timeSynchronizeButton")
public void onTimeSyncClick(final ClickEvent e) {
Scheduler.get().scheduleDeferred(defferedTimeHash);
}
// @UiHandler("latestBlockButton")
// public void onLatestBlockClick(final ClickEvent e) {
// presenter.getLatestBlock();
// }
@Override
public void broadcastLatestBlock(final String latestBlock) {
rawBlock.setPreviousBlockHash(Hex.decode(latestBlock));
final byte[] previousBlockHash = BlockEncodeUtil.encodePreviousBlockHash(Hex.decode(latestBlock));
previousBlockHashViewer.setValue(previousBlockHash);
}
@Override
public void setPresenter(final Presenter presenter) {
this.presenter = presenter;
}
private void doSingleCycle() {
Scheduler.get().scheduleDeferred(executeHashCommand);
}
private void startHashExecutor() {
executor.start(MINING_SIMULATION_DELAY);
}
private void incrementNonce() {
final long nonce = NumberParseUtil.parseUint32(rawBlock.getNonce()) + 1;
nonceViewer.setValue(nonce);
rawBlock.setNonce(BlockEncodeUtil.encodeNonce(nonce));
}
private void decrementNonce() {
final long nonce = NumberParseUtil.parseUint32(rawBlock.getNonce()) - 1;
nonceViewer.setValue(nonce);
rawBlock.setNonce(BlockEncodeUtil.encodeNonce(nonce));
}
private void incrementExtraNonce() {
final Entry<TransactionPartType, byte[]> find = coinbase.find(TransactionPartType.COINBASE_SCRIPT_SIG);
final byte[] value = find.getValue();
// Let's just take the last 4 bytes
final byte[] nonceBytes = new byte[4];
// Get the last four bytes
System.arraycopy(value, value.length - 4, nonceBytes, 0, 4);
// Increment the sucker
final long nonce = NumberParseUtil.parseUint32(nonceBytes) + 1;
final byte[] encodedNonceBytes = NumberEncodeUtil.encodeUint32(nonce);
// Stick the encoded result back in
System.arraycopy(encodedNonceBytes, 0, value, value.length - 4, 4);
find.setValue(value);
coinbaseHexViewer.setValue(coinbase);
final byte[] computeMerkleRoot = ComputeUtil.computeMerkleRoot(coinbase.getBytes());
rawBlock.setMerkleRoot(computeMerkleRoot);
merkleRootViewer.setValue(computeMerkleRoot);
}
private void decrementExtraNonce() {
final Entry<TransactionPartType, byte[]> find = coinbase.find(TransactionPartType.COINBASE_SCRIPT_SIG);
final byte[] value = find.getValue();
// Let's just take the last 4 bytes
final byte[] nonceBytes = new byte[4];
// Get the last four bytes
System.arraycopy(value, value.length - 4, nonceBytes, 0, 4);
// Increment the sucker
final long nonce = NumberParseUtil.parseUint32(nonceBytes) - 1;
final byte[] encodedNonceBytes = NumberEncodeUtil.encodeUint32(nonce);
// Stick the encoded result back in
System.arraycopy(encodedNonceBytes, 0, value, value.length - 4, 4);
find.setValue(value);
coinbaseHexViewer.setValue(coinbase);
final byte[] computeMerkleRoot = ComputeUtil.computeMerkleRoot(coinbase.getBytes());
rawBlock.setMerkleRoot(computeMerkleRoot);
merkleRootViewer.setValue(computeMerkleRoot);
}
private void synchronizeTime() {
final Date time = new Date();
timestampViewer.setValue(FormatUtil.formatDateTime(time));
rawBlock.setTimestamp(BlockEncodeUtil.encodeTimestamp(time));
}
private void doFullHashCycle() {
incrementNonce();
synchronizeTime();
doHashCycle();
}
private void doHashCycle() {
blockHexViewer.setValue(rawBlock.entrySet());
final byte[] computeDoubleSHA256 = ComputeUtil.computeDoubleSHA256(rawBlock.values());
ArrayUtil.reverse(computeDoubleSHA256);
blockHashViewer.setHash(computeDoubleSHA256);
}
}