@Test public void skipScripts() throws Exception { store = createStore(params, 10); chain = new FullPrunedBlockChain(params, store); // Check that we aren't accidentally leaving any references // to the full StoredUndoableBlock's lying around (ie memory leaks) ECKey outKey = new ECKey(); int height = 1; // Build some blocks on genesis block to create a spendable output Block rollingBlock = params .getGenesisBlock() .createNextBlockWithCoinbase(Block.BLOCK_VERSION_GENESIS, outKey.getPubKey(), height++); chain.add(rollingBlock); TransactionOutput spendableOutput = rollingBlock.getTransactions().get(0).getOutput(0); for (int i = 1; i < params.getSpendableCoinbaseDepth(); i++) { rollingBlock = rollingBlock.createNextBlockWithCoinbase( Block.BLOCK_VERSION_GENESIS, outKey.getPubKey(), height++); chain.add(rollingBlock); } rollingBlock = rollingBlock.createNextBlock(null); Transaction t = new Transaction(params); t.addOutput(new TransactionOutput(params, t, FIFTY_COINS, new byte[] {})); TransactionInput input = t.addInput(spendableOutput); // Invalid script. input.clearScriptBytes(); rollingBlock.addTransaction(t); rollingBlock.solve(); chain.setRunScripts(false); try { chain.add(rollingBlock); } catch (VerificationException e) { fail(); } try { store.close(); } catch (Exception e) { } }
@Test public void dataDrivenInvalidTransactions() throws Exception { BufferedReader in = new BufferedReader( new InputStreamReader( getClass().getResourceAsStream("tx_invalid.json"), Charset.forName("UTF-8"))); NetworkParameters params = TestNet3Params.get(); // Poor man's (aka. really, really poor) JSON parser (because pulling in a lib for this is // probably overkill) List<JSONObject> tx = new ArrayList<JSONObject>(1); in.read(); // remove first [ StringBuffer buffer = new StringBuffer(1000); while (in.ready()) { String line = in.readLine(); if (line == null || line.equals("")) continue; buffer.append(line); if (line.equals("]") && buffer.toString().equals("]") && !in.ready()) break; // ignore last ] boolean isFinished = appendToList(tx, buffer); while (tx.size() > 0 && tx.get(0).isList() && tx.get(0).list.size() == 1 && tx.get(0).list.get(0).isString()) tx.remove(0); if (isFinished && tx.size() == 1 && tx.get(0).list.size() == 3) { HashMap<TransactionOutPoint, Script> scriptPubKeys = new HashMap<TransactionOutPoint, Script>(); for (JSONObject input : tx.get(0).list.get(0).list) { String hash = input.list.get(0).string; int index = input.list.get(1).integer; String script = input.list.get(2).string; Sha256Hash sha256Hash = new Sha256Hash(Hex.decode(hash.getBytes(Charset.forName("UTF-8")))); scriptPubKeys.put( new TransactionOutPoint(params, index, sha256Hash), parseScriptString(script)); } byte[] bytes = tx.get(0).list.get(1).string.getBytes(Charset.forName("UTF-8")); Transaction transaction = new Transaction(params, Hex.decode(bytes)); boolean enforceP2SH = tx.get(0).list.get(2).booleanValue; assertTrue(tx.get(0).list.get(2).isBoolean()); boolean valid = true; try { transaction.verify(); } catch (VerificationException e) { valid = false; } // The reference client checks this case in CheckTransaction, but we leave it to // later where we will see an attempt to double-spend, so we explicitly check here HashSet<TransactionOutPoint> set = new HashSet<TransactionOutPoint>(); for (TransactionInput input : transaction.getInputs()) { if (set.contains(input.getOutpoint())) valid = false; set.add(input.getOutpoint()); } for (int i = 0; i < transaction.getInputs().size() && valid; i++) { TransactionInput input = transaction.getInputs().get(i); assertTrue(scriptPubKeys.containsKey(input.getOutpoint())); try { input .getScriptSig() .correctlySpends( transaction, i, scriptPubKeys.get(input.getOutpoint()), enforceP2SH); } catch (VerificationException e) { valid = false; } } if (valid) fail(); tx.clear(); } } in.close(); }
@Test public void dataDrivenValidTransactions() throws Exception { BufferedReader in = new BufferedReader( new InputStreamReader( getClass().getResourceAsStream("tx_valid.json"), Charset.forName("UTF-8"))); NetworkParameters params = TestNet3Params.get(); // Poor man's (aka. really, really poor) JSON parser (because pulling in a lib for this is // probably not overkill) int lineNum = -1; List<JSONObject> tx = new ArrayList<JSONObject>(3); in.read(); // remove first [ StringBuffer buffer = new StringBuffer(1000); while (in.ready()) { lineNum++; String line = in.readLine(); if (line == null || line.equals("")) continue; buffer.append(line); if (line.equals("]") && buffer.toString().equals("]") && !in.ready()) break; boolean isFinished = appendToList(tx, buffer); while (tx.size() > 0 && tx.get(0).isList() && tx.get(0).list.size() == 1 && tx.get(0).list.get(0).isString()) tx.remove(0); // ignore last ] if (isFinished && tx.size() == 1 && tx.get(0).list.size() == 3) { Transaction transaction = null; try { HashMap<TransactionOutPoint, Script> scriptPubKeys = new HashMap<TransactionOutPoint, Script>(); for (JSONObject input : tx.get(0).list.get(0).list) { String hash = input.list.get(0).string; int index = input.list.get(1).integer; String script = input.list.get(2).string; Sha256Hash sha256Hash = new Sha256Hash(Hex.decode(hash.getBytes(Charset.forName("UTF-8")))); scriptPubKeys.put( new TransactionOutPoint(params, index, sha256Hash), parseScriptString(script)); } byte[] bytes = tx.get(0).list.get(1).string.getBytes(Charset.forName("UTF-8")); transaction = new Transaction(params, Hex.decode(bytes)); boolean enforceP2SH = tx.get(0).list.get(2).booleanValue; assertTrue(tx.get(0).list.get(2).isBoolean()); transaction.verify(); for (int i = 0; i < transaction.getInputs().size(); i++) { TransactionInput input = transaction.getInputs().get(i); if (input.getOutpoint().getIndex() == 0xffffffffL) input.getOutpoint().setIndex(-1); assertTrue(scriptPubKeys.containsKey(input.getOutpoint())); input .getScriptSig() .correctlySpends( transaction, i, scriptPubKeys.get(input.getOutpoint()), enforceP2SH); } tx.clear(); } catch (Exception e) { System.err.println("Exception processing line " + lineNum + ": " + line); if (transaction != null) System.err.println(transaction); throw e; } } } in.close(); }
public void testTransaction( NetworkParameters params, byte[] txBytes, boolean isChild, boolean lazy, boolean retain) throws Exception { // reference serializer to produce comparison serialization output after changes to // message structure. BitcoinSerializer bsRef = new BitcoinSerializer(params, false, false); ByteArrayOutputStream bos = new ByteArrayOutputStream(); BitcoinSerializer bs = new BitcoinSerializer(params, lazy, retain); Transaction t1; Transaction tRef; t1 = (Transaction) bs.deserialize(new ByteArrayInputStream(txBytes)); tRef = (Transaction) bsRef.deserialize(new ByteArrayInputStream(txBytes)); // verify our reference BitcoinSerializer produces matching byte array. bos.reset(); bsRef.serialize(tRef, bos); assertTrue(Arrays.equals(bos.toByteArray(), txBytes)); // check lazy and retain status survive both before and after a serialization assertEquals(!lazy, t1.isParsed()); if (t1.isParsed()) assertEquals(retain, t1.isCached()); serDeser(bs, t1, txBytes, null, null); assertEquals(lazy, !t1.isParsed()); if (t1.isParsed()) assertEquals(retain, t1.isCached()); // compare to ref tx bos.reset(); bsRef.serialize(tRef, bos); serDeser(bs, t1, bos.toByteArray(), null, null); // retrieve a value from a child t1.getInputs(); assertTrue(t1.isParsed()); if (t1.getInputs().size() > 0) { assertTrue(t1.isParsed()); TransactionInput tin = t1.getInputs().get(0); assertEquals(!lazy, tin.isParsed()); if (tin.isParsed()) assertEquals(retain, tin.isCached()); // does it still match ref tx? serDeser(bs, t1, bos.toByteArray(), null, null); } // refresh tx t1 = (Transaction) bs.deserialize(new ByteArrayInputStream(txBytes)); tRef = (Transaction) bsRef.deserialize(new ByteArrayInputStream(txBytes)); // add an input if (t1.getInputs().size() > 0) { t1.addInput(t1.getInputs().get(0)); // replicate on reference tx tRef.addInput(tRef.getInputs().get(0)); assertFalse(t1.isCached()); assertTrue(t1.isParsed()); bos.reset(); bsRef.serialize(tRef, bos); byte[] source = bos.toByteArray(); // confirm we still match the reference tx. serDeser(bs, t1, source, null, null); } }
public void testBlock(byte[] blockBytes, boolean isChild, boolean lazy, boolean retain) throws Exception { // reference serializer to produce comparison serialization output after changes to // message structure. BitcoinSerializer bsRef = new BitcoinSerializer(unitTestParams, false, false); ByteArrayOutputStream bos = new ByteArrayOutputStream(); BitcoinSerializer bs = new BitcoinSerializer(unitTestParams, lazy, retain); Block b1; Block bRef; b1 = (Block) bs.deserialize(new ByteArrayInputStream(blockBytes)); bRef = (Block) bsRef.deserialize(new ByteArrayInputStream(blockBytes)); // verify our reference BitcoinSerializer produces matching byte array. bos.reset(); bsRef.serialize(bRef, bos); assertTrue(Arrays.equals(bos.toByteArray(), blockBytes)); // check lazy and retain status survive both before and after a serialization assertEquals(!lazy, b1.isParsedTransactions()); assertEquals(!lazy, b1.isParsedHeader()); if (b1.isParsedHeader()) assertEquals(retain, b1.isHeaderBytesValid()); if (b1.isParsedTransactions()) assertEquals(retain, b1.isTransactionBytesValid()); serDeser(bs, b1, blockBytes, null, null); assertEquals(!lazy, b1.isParsedTransactions()); assertEquals(!lazy, b1.isParsedHeader()); if (b1.isParsedHeader()) assertEquals(retain, b1.isHeaderBytesValid()); if (b1.isParsedTransactions()) assertEquals(retain, b1.isTransactionBytesValid()); // compare to ref block bos.reset(); bsRef.serialize(bRef, bos); serDeser(bs, b1, bos.toByteArray(), null, null); // retrieve a value from a child b1.getTransactions(); assertTrue(b1.isParsedTransactions()); if (b1.getTransactions().size() > 0) { assertTrue(b1.isParsedTransactions()); Transaction tx1 = b1.getTransactions().get(0); // this will always be true for all children of a block once they are retrieved. // the tx child inputs/outputs may not be parsed however. // no longer forced to parse if length not provided. // assertEquals(true, tx1.isParsed()); if (tx1.isParsed()) assertEquals(retain, tx1.isCached()); else assertTrue(tx1.isCached()); // does it still match ref block? serDeser(bs, b1, bos.toByteArray(), null, null); } // refresh block b1 = (Block) bs.deserialize(new ByteArrayInputStream(blockBytes)); bRef = (Block) bsRef.deserialize(new ByteArrayInputStream(blockBytes)); // retrieve a value from header b1.getDifficultyTarget(); assertTrue(b1.isParsedHeader()); assertEquals(lazy, !b1.isParsedTransactions()); // does it still match ref block? serDeser(bs, b1, bos.toByteArray(), null, null); // refresh block b1 = (Block) bs.deserialize(new ByteArrayInputStream(blockBytes)); bRef = (Block) bsRef.deserialize(new ByteArrayInputStream(blockBytes)); // retrieve a value from a child and header b1.getDifficultyTarget(); assertTrue(b1.isParsedHeader()); assertEquals(lazy, !b1.isParsedTransactions()); b1.getTransactions(); assertTrue(b1.isParsedTransactions()); if (b1.getTransactions().size() > 0) { assertTrue(b1.isParsedTransactions()); Transaction tx1 = b1.getTransactions().get(0); // no longer forced to parse if length not provided. // assertEquals(true, tx1.isParsed()); if (tx1.isParsed()) assertEquals(retain, tx1.isCached()); else assertTrue(tx1.isCached()); } // does it still match ref block? serDeser(bs, b1, bos.toByteArray(), null, null); // refresh block b1 = (Block) bs.deserialize(new ByteArrayInputStream(blockBytes)); bRef = (Block) bsRef.deserialize(new ByteArrayInputStream(blockBytes)); // change a value in header b1.setNonce(23); bRef.setNonce(23); assertTrue(b1.isParsedHeader()); assertEquals(lazy, !b1.isParsedTransactions()); assertFalse(b1.isHeaderBytesValid()); if (b1.isParsedTransactions()) assertEquals(retain, b1.isTransactionBytesValid()); else assertEquals(true, b1.isTransactionBytesValid()); // does it still match ref block? bos.reset(); bsRef.serialize(bRef, bos); serDeser(bs, b1, bos.toByteArray(), null, null); // refresh block b1 = (Block) bs.deserialize(new ByteArrayInputStream(blockBytes)); bRef = (Block) bsRef.deserialize(new ByteArrayInputStream(blockBytes)); // retrieve a value from a child of a child b1.getTransactions(); if (b1.getTransactions().size() > 0) { Transaction tx1 = b1.getTransactions().get(0); TransactionInput tin = tx1.getInputs().get(0); assertTrue(tx1.isParsed()); assertTrue(b1.isParsedTransactions()); assertEquals(!lazy, b1.isParsedHeader()); assertEquals(!lazy, tin.isParsed()); assertEquals(tin.isParsed() ? retain : true, tin.isCached()); // does it still match ref tx? bos.reset(); bsRef.serialize(bRef, bos); serDeser(bs, b1, bos.toByteArray(), null, null); } // refresh block b1 = (Block) bs.deserialize(new ByteArrayInputStream(blockBytes)); bRef = (Block) bsRef.deserialize(new ByteArrayInputStream(blockBytes)); // add an input b1.getTransactions(); if (b1.getTransactions().size() > 0) { Transaction tx1 = b1.getTransactions().get(0); if (tx1.getInputs().size() > 0) { tx1.addInput(tx1.getInputs().get(0)); // replicate on reference tx bRef.getTransactions().get(0).addInput(bRef.getTransactions().get(0).getInputs().get(0)); assertFalse(tx1.isCached()); assertTrue(tx1.isParsed()); assertFalse(b1.isTransactionBytesValid()); assertTrue(b1.isParsedHeader()); // confirm sibling cache status was unaffected if (tx1.getInputs().size() > 1) { boolean parsed = tx1.getInputs().get(1).isParsed(); assertEquals(parsed ? retain : true, tx1.getInputs().get(1).isCached()); assertEquals(!lazy, parsed); } // this has to be false. Altering a tx invalidates the merkle root. // when we have seperate merkle caching then the entire header won't need to be // invalidated. assertFalse(b1.isHeaderBytesValid()); bos.reset(); bsRef.serialize(bRef, bos); byte[] source = bos.toByteArray(); // confirm we still match the reference tx. serDeser(bs, b1, source, null, null); } // does it still match ref tx? bos.reset(); bsRef.serialize(bRef, bos); serDeser(bs, b1, bos.toByteArray(), null, null); } // refresh block b1 = (Block) bs.deserialize(new ByteArrayInputStream(blockBytes)); Block b2 = (Block) bs.deserialize(new ByteArrayInputStream(blockBytes)); bRef = (Block) bsRef.deserialize(new ByteArrayInputStream(blockBytes)); Block bRef2 = (Block) bsRef.deserialize(new ByteArrayInputStream(blockBytes)); // reparent an input b1.getTransactions(); if (b1.getTransactions().size() > 0) { Transaction tx1 = b1.getTransactions().get(0); Transaction tx2 = b2.getTransactions().get(0); if (tx1.getInputs().size() > 0) { TransactionInput fromTx1 = tx1.getInputs().get(0); tx2.addInput(fromTx1); // replicate on reference tx TransactionInput fromTxRef = bRef.getTransactions().get(0).getInputs().get(0); bRef2.getTransactions().get(0).addInput(fromTxRef); // b1 hasn't changed but it's no longer in the parent // chain of fromTx1 so has to have been uncached since it won't be // notified of changes throught the parent chain anymore. assertFalse(b1.isTransactionBytesValid()); // b2 should have it's cache invalidated because it has changed. assertFalse(b2.isTransactionBytesValid()); bos.reset(); bsRef.serialize(bRef2, bos); byte[] source = bos.toByteArray(); // confirm altered block matches altered ref block. serDeser(bs, b2, source, null, null); } // does unaltered block still match ref block? bos.reset(); bsRef.serialize(bRef, bos); serDeser(bs, b1, bos.toByteArray(), null, null); // how about if we refresh it? bRef = (Block) bsRef.deserialize(new ByteArrayInputStream(blockBytes)); bos.reset(); bsRef.serialize(bRef, bos); serDeser(bs, b1, bos.toByteArray(), null, null); } }