mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-07-31 20:11:23 +00:00
Make the unit tests more realistic and fix two bugs this revealed:
1) Receiving coins regressed after the last optimization. Resolves issue 49. 2) Reorg handling expected to be able to connect all inputs. Also other minor fixes and small additions.
This commit is contained in:
@@ -477,28 +477,44 @@ public class Block extends Message {
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Unit testing related methods.
|
||||
|
||||
static private int coinbaseCounter;
|
||||
// Used to make transactions unique.
|
||||
static private int txCounter;
|
||||
|
||||
/** Adds a coinbase transaction to the block. This exists for unit tests. */
|
||||
void addCoinbaseTransaction(Address to) {
|
||||
void addCoinbaseTransaction(byte[] pubKeyTo) {
|
||||
transactions = new ArrayList<Transaction>();
|
||||
Transaction coinbase = new Transaction(params);
|
||||
// A real coinbase transaction has some stuff in the scriptSig like the extraNonce and difficulty. The
|
||||
// transactions are distinguished by every TX output going to a different key.
|
||||
//
|
||||
// Here we will do things a bit differently so a new address isn't needed every time. We'll put a simple
|
||||
// counter in the scriptSig so every transaction has a different hash. The output is also different.
|
||||
// Real coinbase transactions use <pubkey> OP_CHECKSIG rather than a send to an address though there's
|
||||
// nothing in the system that enforces that and both are just as valid.
|
||||
coinbase.inputs.add(new TransactionInput(params, coinbase, new byte[] { (byte) coinbaseCounter++ } ));
|
||||
coinbase.outputs.add(new TransactionOutput(params, coinbase, Utils.toNanoCoins(50, 0), to));
|
||||
// counter in the scriptSig so every transaction has a different hash.
|
||||
coinbase.inputs.add(new TransactionInput(params, coinbase, new byte[] { (byte) txCounter++ } ));
|
||||
coinbase.outputs.add(new TransactionOutput(params, coinbase, Script.createOutputScript(pubKeyTo)));
|
||||
transactions.add(coinbase);
|
||||
}
|
||||
|
||||
static final byte[] EMPTY_BYTES = new byte[32];
|
||||
|
||||
/** Returns a solved block that builds on top of this one. This exists for unit tests. */
|
||||
Block createNextBlock(Address to, long time) {
|
||||
Block b = new Block(params);
|
||||
b.setDifficultyTarget(difficultyTarget);
|
||||
b.addCoinbaseTransaction(to);
|
||||
b.addCoinbaseTransaction(EMPTY_BYTES);
|
||||
|
||||
// Add a transaction paying 50 coins to the "to" address.
|
||||
Transaction t = new Transaction(params);
|
||||
t.addOutput(new TransactionOutput(params, t, Utils.toNanoCoins(50, 0), to));
|
||||
// The input does not really need to be a valid signature, as long as it has the right general form.
|
||||
TransactionInput input = new TransactionInput(params, t, Script.createInputScript(EMPTY_BYTES, EMPTY_BYTES));
|
||||
// Importantly the outpoint hash cannot be zero as that's how we detect a coinbase transaction in isolation
|
||||
// but it must be unique to avoid 'different' transactions looking the same.
|
||||
byte[] counter = new byte[32];
|
||||
counter[0] = (byte) txCounter++;
|
||||
input.outpoint.hash = new Sha256Hash(counter);
|
||||
t.addInput(input);
|
||||
b.addTransaction(t);
|
||||
|
||||
b.setPrevBlockHash(getHash());
|
||||
b.setTime(time);
|
||||
b.solve();
|
||||
|
@@ -207,7 +207,7 @@ public class BlockChain {
|
||||
if (storedPrev.equals(chainHead)) {
|
||||
// This block connects to the best known block, it is a normal continuation of the system.
|
||||
setChainHead(newStoredBlock);
|
||||
log.trace("Chain is now {} blocks high", chainHead.getHeight());
|
||||
log.info("Chain is now {} blocks high", chainHead.getHeight());
|
||||
if (newTransactions != null)
|
||||
sendTransactionsToWallet(newStoredBlock, NewBlockType.BEST_CHAIN, newTransactions);
|
||||
} else {
|
||||
@@ -437,10 +437,11 @@ public class BlockChain {
|
||||
boolean shouldReceive = false;
|
||||
for (TransactionOutput output : tx.outputs) {
|
||||
// TODO: Handle more types of outputs, not just regular to address outputs.
|
||||
if (output.getScriptPubKey().isSentToIP()) return;
|
||||
if (output.getScriptPubKey().isSentToIP()) continue;
|
||||
// This is not thread safe as a key could be removed between the call to isMine and receive.
|
||||
if (output.isMine(wallet)) {
|
||||
shouldReceive = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -83,7 +83,7 @@ public class NetworkParameters implements Serializable {
|
||||
Script.writeBytes(scriptPubKeyBytes, Hex.decode
|
||||
("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f"));
|
||||
scriptPubKeyBytes.write(Script.OP_CHECKSIG);
|
||||
t.outputs.add(new TransactionOutput(n, scriptPubKeyBytes.toByteArray()));
|
||||
t.outputs.add(new TransactionOutput(n, t, scriptPubKeyBytes.toByteArray()));
|
||||
} catch (Exception e) {
|
||||
// Cannot happen.
|
||||
}
|
||||
|
@@ -388,6 +388,19 @@ public class Script {
|
||||
}
|
||||
}
|
||||
|
||||
/** Create a script that sends coins directly to the given public key (eg in a coinbase transaction). */
|
||||
static byte[] createOutputScript(byte[] pubkey) {
|
||||
try {
|
||||
// TODO: Do this by creating a Script *first* then having the script reassemble itself into bytes.
|
||||
ByteArrayOutputStream bits = new ByteArrayOutputStream();
|
||||
writeBytes(bits, pubkey);
|
||||
bits.write(OP_CHECKSIG);
|
||||
return bits.toByteArray();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e); // Cannot happen.
|
||||
}
|
||||
}
|
||||
|
||||
static byte[] createInputScript(byte[] signature, byte[] pubkey) {
|
||||
try {
|
||||
// TODO: Do this by creating a Script *first* then having the script reassemble itself into bytes.
|
||||
|
@@ -29,7 +29,7 @@ import java.util.Arrays;
|
||||
public class Sha256Hash implements Serializable {
|
||||
private byte[] bytes;
|
||||
|
||||
public static Sha256Hash ZERO_HASH = new Sha256Hash(new byte[32]);
|
||||
public static final Sha256Hash ZERO_HASH = new Sha256Hash(new byte[32]);
|
||||
|
||||
/** Creates a Sha256Hash by wrapping the given byte array. It must be 32 bytes long. */
|
||||
public Sha256Hash(byte[] bytes) {
|
||||
|
@@ -187,14 +187,19 @@ public class Transaction extends Message implements Serializable {
|
||||
* Connects all inputs using the provided transactions. If any input cannot be connected returns that input or
|
||||
* null on success.
|
||||
*/
|
||||
TransactionInput connectInputs(Map<Sha256Hash, Transaction> transactions, boolean disconnect) {
|
||||
TransactionInput connectForReorganize(Map<Sha256Hash, Transaction> transactions) {
|
||||
for (TransactionInput input : inputs) {
|
||||
// Coinbase transactions, by definition, do not have connectable inputs.
|
||||
if (input.isCoinBase()) continue;
|
||||
if (input.connect(transactions, disconnect) != TransactionInput.ConnectionResult.SUCCESS) {
|
||||
// Could not connect this input, so return it and abort.
|
||||
return input;
|
||||
}
|
||||
TransactionInput.ConnectionResult result = input.connect(transactions, false);
|
||||
// Connected to another tx in the wallet?
|
||||
if (result == TransactionInput.ConnectionResult.SUCCESS)
|
||||
continue;
|
||||
// The input doesn't exist in the wallet, eg because it belongs to somebody else (inbound spend).
|
||||
if (result == TransactionInput.ConnectionResult.NO_SUCH_TX)
|
||||
continue;
|
||||
// Could not connect this input, so return it and abort.
|
||||
return input;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -297,7 +302,12 @@ public class Transaction extends Message implements Serializable {
|
||||
* accepted by the network.
|
||||
*/
|
||||
public void addInput(TransactionOutput from) {
|
||||
inputs.add(new TransactionInput(params, this, from));
|
||||
addInput(new TransactionInput(params, this, from));
|
||||
}
|
||||
|
||||
/** Adds an input directly, with no checking that it's valid. */
|
||||
public void addInput(TransactionInput input) {
|
||||
inputs.add(input);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -124,7 +124,8 @@ public class TransactionInput extends Message implements Serializable {
|
||||
if (isCoinBase())
|
||||
return "TxIn: COINBASE";
|
||||
try {
|
||||
return "TxIn from " + Utils.bytesToHexString(getScriptSig().getPubKey()) + " script:" +
|
||||
return "TxIn from tx " + outpoint + " (pubkey: " + Utils.bytesToHexString(getScriptSig().getPubKey()) +
|
||||
") script:" +
|
||||
getScriptSig().toString();
|
||||
} catch (ScriptException e) {
|
||||
throw new RuntimeException(e);
|
||||
|
@@ -91,4 +91,9 @@ public class TransactionOutPoint extends Message implements Serializable {
|
||||
byte[] getConnectedPubKeyHash() throws ScriptException {
|
||||
return getConnectedOutput().getScriptPubKey().getPubKeyHash();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "outpoint " + index + ":" + hash.toString();
|
||||
}
|
||||
}
|
||||
|
@@ -67,10 +67,11 @@ public class TransactionOutput extends Message implements Serializable {
|
||||
}
|
||||
|
||||
/** Used only in creation of the genesis blocks and in unit tests. */
|
||||
TransactionOutput(NetworkParameters params, byte[] scriptBytes) {
|
||||
TransactionOutput(NetworkParameters params, Transaction parent, byte[] scriptBytes) {
|
||||
super(params);
|
||||
this.scriptBytes = scriptBytes;
|
||||
this.value = Utils.toNanoCoins(50, 0);
|
||||
parentTransaction = parent;
|
||||
availableForSpending = true;
|
||||
}
|
||||
|
||||
|
@@ -743,7 +743,7 @@ public class Wallet implements Serializable {
|
||||
tx.disconnectInputs();
|
||||
// Reconnect the transactions in the common part of the chain.
|
||||
for (Transaction tx : commonChainTransactions.values()) {
|
||||
TransactionInput badInput = tx.connectInputs(all, false);
|
||||
TransactionInput badInput = tx.connectForReorganize(all);
|
||||
assert badInput == null : "Failed to connect " + tx.getHashAsString() + ", " + badInput.toString();
|
||||
}
|
||||
// Recalculate the unspent/spent buckets for the transactions the re-org did not affect.
|
||||
|
@@ -69,13 +69,15 @@ public class PingService {
|
||||
// Fetch the first key in the wallet (should be the only key).
|
||||
ECKey key = wallet.keychain.get(0);
|
||||
|
||||
System.out.println(wallet);
|
||||
|
||||
// Load the block chain, if there is one stored locally.
|
||||
System.out.println("Reading block store from disk");
|
||||
BlockStore blockStore = new BoundedOverheadBlockStore(params, new File(filePrefix + ".blockchain"));
|
||||
|
||||
// Connect to the localhost node. One minute timeout since we won't try any other peers
|
||||
System.out.println("Connecting ...");
|
||||
NetworkConnection conn = new NetworkConnection(InetAddress.getLocalHost(), params,
|
||||
NetworkConnection conn = new NetworkConnection(InetAddress.getByName("plan99.net"), params,
|
||||
blockStore.getChainHead().getHeight(), 60000);
|
||||
BlockChain chain = new BlockChain(params, wallet, blockStore);
|
||||
final Peer peer = new Peer(params, conn, chain);
|
||||
|
@@ -45,7 +45,6 @@ public class BlockChainTest {
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
|
||||
testNetChain = new BlockChain(testNet, new Wallet(testNet), new MemoryBlockStore(testNet));
|
||||
|
||||
unitTestParams = NetworkParameters.unitTests();
|
||||
@@ -81,6 +80,17 @@ public class BlockChainTest {
|
||||
assertTrue(testNetChain.add(b2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void receiveCoins() throws Exception {
|
||||
// Quick check that we can actually receive coins.
|
||||
Transaction tx1 = createFakeTx(unitTestParams,
|
||||
Utils.toNanoCoins(1, 0),
|
||||
wallet.keychain.get(0).toAddress(unitTestParams));
|
||||
Block b1 = createFakeBlock(unitTestParams, blockStore, tx1).block;
|
||||
chain.add(b1);
|
||||
assertTrue(wallet.getBalance().compareTo(BigInteger.ZERO) > 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void merkleRoots() throws Exception {
|
||||
// Test that merkle root verification takes place when a relevant transaction is present and doesn't when
|
||||
|
@@ -22,7 +22,7 @@ import com.google.bitcoin.store.BlockStoreException;
|
||||
import java.math.BigInteger;
|
||||
|
||||
public class TestUtils {
|
||||
public static Transaction createFakeTx(NetworkParameters params, BigInteger nanocoins, Address to) {
|
||||
public static Transaction createFakeTx(NetworkParameters params, BigInteger nanocoins, Address to) {
|
||||
Transaction t = new Transaction(params);
|
||||
TransactionOutput o1 = new TransactionOutput(params, t, nanocoins, to);
|
||||
t.addOutput(o1);
|
||||
@@ -46,6 +46,7 @@ public class TestUtils {
|
||||
Transaction... transactions) {
|
||||
try {
|
||||
Block b = blockStore.getChainHead().getHeader().createNextBlock(new ECKey().toAddress(params));
|
||||
// Coinbase tx was already added.
|
||||
for (Transaction tx : transactions)
|
||||
b.addTransaction(tx);
|
||||
b.solve();
|
||||
|
Reference in New Issue
Block a user