package lighthouse.protocol;
import org.bitcoinj.core.*;
import org.bitcoinj.crypto.TransactionSignature;
import org.bitcoinj.params.UnitTestParams;
import org.bitcoinj.script.ScriptBuilder;
import org.bitcoinj.store.BlockStoreException;
import org.bitcoinj.store.MemoryBlockStore;
import org.bitcoinj.testing.FakeTxBuilder;
import org.bitcoinj.testing.MockTransactionBroadcaster;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.bitcoinj.core.Coin;
import java.util.Random;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
/**
* A set of objects that are useful when writing unit tests that require a wallet.
*/
public class WalletTestObjects {
private static final Logger log = LoggerFactory.getLogger(WalletTestObjects.class);
public final Wallet wallet;
public final BlockChain chain;
public final MemoryBlockStore store;
public final NetworkParameters params;
public final MockTransactionBroadcaster broadcaster;
public WalletTestObjects() {
this(Suppliers.ofInstance(new Wallet(UnitTestParams.get())));
}
public WalletTestObjects(Supplier<? extends Wallet> walletFactory) {
try {
wallet = walletFactory.get();
wallet.setKeychainLookaheadSize(2);
checkArgument(wallet.getParams() == UnitTestParams.get());
params = wallet.getParams();
store = new MemoryBlockStore(params);
chain = new BlockChain(params, wallet, store);
broadcaster = new MockTransactionBroadcaster(wallet);
} catch (BlockStoreException e) {
throw new RuntimeException(e); // Cannot happen.
}
}
public void sendAmounts(long... amounts) {
sendAmounts(new Random(), amounts);
}
/**
* Create a set of fake transactions that send outputs in the given amounts to the wallet, distributed across
* a random number of transactions, all in one block.
*/
public void sendAmounts(Random random, long... amounts) {
int numTxns = Math.max(1, random.nextInt(amounts.length));
log.info("sendAmounts creating {} txns", numTxns);
final double outputsPerTx = amounts.length / (double) numTxns;
checkState(outputsPerTx >= 1.0);
double cursor = 0.0;
int numOutsSoFar = 0;
ECKey feederKey = new ECKey();
for (int i = 0; i < amounts.length; /* nothing */) {
Transaction currentTx = new Transaction(params);
cursor += outputsPerTx;
long totalVal = 0;
int j = 0;
for (; j < ((int) cursor) - numOutsSoFar; j++) {
long val = amounts[i++];
totalVal += val;
currentTx.addOutput(Coin.valueOf(val), wallet.freshReceiveKey().toAddress(params));
}
numOutsSoFar += j;
Transaction fakeFeeder = new Transaction(params);
TransactionOutput output = fakeFeeder.addOutput(Coin.valueOf(totalVal), feederKey);
currentTx.addInput(output).setScriptSig(ScriptBuilder.createInputScript(TransactionSignature.dummy()));
currentTx = FakeTxBuilder.roundTripTransaction(params, currentTx);
// currentTx now "looks real" except for the fake input signature, but that's not checked anywhere as it
// would not be ours anyway.
final Transaction tx = currentTx;
receiveViaBlock(tx);
}
}
public void receiveViaBlock(Transaction... txns) {
Block block = chain.getChainHead().getHeader().createNextBlock(null);
for (Transaction tx : txns) {
block.addTransaction(tx);
}
block.solve();
try {
chain.add(block);
} catch (PrunedException e) {
throw new RuntimeException(e);
}
}
}