├── project ├── build.properties └── plugins.sbt ├── .gitignore ├── src ├── main │ ├── c │ │ ├── build-mingw64.cmd │ │ ├── build.md │ │ ├── include │ │ │ ├── org_bitcoinakka_Consensus__.h │ │ │ └── bitcoinconsensus.h │ │ └── consensus-jni.c │ ├── resources │ │ ├── application.conf │ │ └── log4j2.xml │ └── scala │ │ └── org │ │ └── bitcoinakka │ │ ├── Script.scala │ │ ├── ImportExport.scala │ │ ├── Main.scala │ │ ├── UTXO.scala │ │ ├── Consensus.scala │ │ ├── Message.scala │ │ ├── Sync.scala │ │ └── PeerManager.scala └── test │ ├── scala │ └── org │ │ └── bitcoinakka │ │ ├── FullBlockCheck.scala │ │ └── SyncSpec.scala │ └── java │ └── org │ └── bitcoinj │ └── core │ └── FullBlockTestGenerator.java ├── macros ├── build.sbt └── src │ └── main │ └── scala │ └── org │ └── bitcoinakka │ └── MessageMacro.scala └── README.md /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.9 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.log 3 | .cache 4 | .classpath 5 | consensus-jni.dll 6 | bin/ 7 | target/ 8 | *.orig 9 | 10 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.0.3") 2 | 3 | addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.0") 4 | -------------------------------------------------------------------------------- /src/main/c/build-mingw64.cmd: -------------------------------------------------------------------------------- 1 | "C:\MinGW64\bin\gcc.exe" -m64 -I"C:\Program Files\Java\jdk1.8.0_51\include" -I"C:\Program Files\Java\jdk1.8.0_51\include\win32" -I"include" -Wall -D_JNI_IMPLEMENTATION_ -Wl,--kill-at -shared -o consensus-jni.dll consensus-jni.c -L"lib" -lbitcoinconsensus-0 2 | -------------------------------------------------------------------------------- /macros/build.sbt: -------------------------------------------------------------------------------- 1 | scalaVersion := "2.11.7" 2 | 3 | version := "0.1.0-SNAPSHOT" 4 | 5 | lazy val macros = Project("bitcoin-akka-tutorial-macros", file(".")) 6 | 7 | libraryDependencies ++= Seq( 8 | "org.scala-lang" % "scala-reflect" % scalaVersion.value 9 | ) 10 | 11 | addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.0-M5" cross CrossVersion.full) 12 | -------------------------------------------------------------------------------- /src/main/c/build.md: -------------------------------------------------------------------------------- 1 | # Build with mingw-64 2 | 3 | - Use the build command `build-mingw64.cmd`. If it doesn't work, it's probably because 4 | - you have a different version of the JDK. I have 1.8.0_51 5 | - you haven't built bitcoin core and you don't have the import dll in `/usr/local/lib` 6 | 7 | - At runtime, you need libbitcoinconsensus-0.dll or equivalent in your java library path 8 | -------------------------------------------------------------------------------- /src/main/c/include/org_bitcoinakka_Consensus__.h: -------------------------------------------------------------------------------- 1 | /* DO NOT EDIT THIS FILE - it is machine generated */ 2 | #include 3 | /* Header for class org_bitcoinakka_Consensus__ */ 4 | 5 | #ifndef _Included_org_bitcoinakka_Consensus__ 6 | #define _Included_org_bitcoinakka_Consensus__ 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | /* 11 | * Class: org_bitcoinakka_Consensus__ 12 | * Method: verifyScript 13 | * Signature: ([B[BII)I 14 | */ 15 | JNIEXPORT jint JNICALL Java_org_bitcoinakka_Consensus_00024_verifyScript 16 | (JNIEnv *, jobject, jbyteArray, jbyteArray, jint, jint); 17 | 18 | #ifdef __cplusplus 19 | } 20 | #endif 21 | #endif 22 | -------------------------------------------------------------------------------- /src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | targetConnectCount=5 2 | batchSize = 20 3 | blockBaseDir=/bitcoin-data 4 | baseDir=/bitcoin-data 5 | incomingPort = 8333 6 | nCores = 4 7 | 8 | # Import 9 | blockFiles { 10 | path="" 11 | start = 0 12 | checkpoints=[] 13 | } 14 | 15 | # Export 16 | saveBlocks { 17 | file = "" 18 | startHeight = 0 19 | endHash = "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f" 20 | } 21 | 22 | akka { 23 | loggers = ["akka.event.slf4j.Slf4jLogger"] 24 | loglevel = "DEBUG" 25 | logging-filter = "akka.event.slf4j.Slf4jLoggingFilter" 26 | actor { 27 | debug { 28 | receive = on 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/main/c/consensus-jni.c: -------------------------------------------------------------------------------- 1 | #include "org_bitcoinakka_Consensus__.h" 2 | #include "bitcoinconsensus.h" 3 | 4 | typedef unsigned char byte; 5 | 6 | JNIEXPORT jint JNICALL Java_org_bitcoinakka_Consensus_00024_verifyScript 7 | (JNIEnv *env, jobject jobj, jbyteArray pubscript, jbyteArray tx, jint index, jint flags) { 8 | 9 | jbyte* scriptPubKey = (*env)->GetByteArrayElements(env, pubscript, NULL); 10 | jsize scriptPubKeyLen = (*env)->GetArrayLength(env, pubscript); 11 | 12 | jbyte* txBytes = (*env)->GetByteArrayElements(env, tx, NULL); 13 | jsize txLen = (*env)->GetArrayLength(env, tx); 14 | 15 | bitcoinconsensus_error err; 16 | int result = bitcoinconsensus_verify_script((byte *)scriptPubKey, scriptPubKeyLen, 17 | (byte *)txBytes, txLen, 18 | index, flags, &err); 19 | 20 | (*env)->ReleaseByteArrayElements(env, pubscript, scriptPubKey, JNI_ABORT); 21 | (*env)->ReleaseByteArrayElements(env, tx, txBytes, JNI_ABORT); 22 | 23 | return result; 24 | } 25 | -------------------------------------------------------------------------------- /src/test/scala/org/bitcoinakka/FullBlockCheck.scala: -------------------------------------------------------------------------------- 1 | package org.bitcoinj.core 2 | 3 | import java.io.{File, FileOutputStream} 4 | 5 | import scala.language.postfixOps 6 | import org.bitcoinakka.BitcoinMessage._ 7 | import org.bitcoinj.params.RegTestParams 8 | import org.bitcoinj.utils.BriefLogFormatter 9 | import org.slf4j.LoggerFactory 10 | 11 | import scala.collection.JavaConversions._ 12 | import scala.pickling.Defaults._ 13 | import scala.pickling.binary._ 14 | 15 | trait FullBlockCheck 16 | case class MemoryPool() extends FullBlockCheck 17 | case class BlockCheck(hash: Hash, connects: Boolean, errors: Boolean, tipHash: Hash, tipHeight: Int, name: String) extends FullBlockCheck { 18 | override def toString() = s"BlockCheck(${name},${connects},${errors},${hashToString(tipHash)},${tipHeight})" 19 | } 20 | 21 | object BuildTestSuite extends App { 22 | val log = LoggerFactory.getLogger(getClass) 23 | BriefLogFormatter.init() 24 | val blockFile = new File("testBlocks.dat") 25 | val ruleFile = new File("testBlocks-rules.dat") 26 | val output = new StreamOutput(new FileOutputStream(ruleFile)) 27 | val params = RegTestParams.get() 28 | 29 | val c = Context.getOrCreate(params) 30 | val generator = new FullBlockTestGenerator(params) 31 | val blockList = generator.getBlocksToTest(false, false, blockFile) 32 | 33 | val checkList: List[FullBlockCheck] = blockList.list flatMap { rule => 34 | if (rule.isInstanceOf[BlockAndValidity]) { 35 | val bv = rule.asInstanceOf[BlockAndValidity] 36 | Some(BlockCheck(bv.blockHash.getBytes.reverse, bv.connects, bv.throwsException, bv.hashChainTipAfterBlock.getBytes.reverse, bv.heightAfterBlock, bv.ruleName)) 37 | } 38 | else None 39 | } toList 40 | 41 | checkList.pickleTo(output) 42 | } 43 | 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | --- 4 | 5 | # What is Bitcoin-Akka ? 6 | 7 | Bitcoin-akka is a minimalistic but nearly fully compliant implementation of the Bitcoin protocol in Scala using the Akka library. 8 | It runs as a server node, playing a similar role to the reference implementation `Bitcoin Core`. 9 | 10 | Before going further, here's the standard disclaimer: 11 | 12 | > I claim no responsibility if you lose money by using this software. It is provided for educational purposes only. 13 | 14 | Especially true since Bitcoin has real money equivalence, though the risk is limited by the absence 15 | of wallet functionality. 16 | 17 | That being said, care has been taken in replicating the same behavior as of Bitcoin Core. Bitcoin-akka passes the same [regression tests][1] 18 | and leverage the consensus library from [Bitcoin Core][2] for transaction verification. 19 | 20 | Nonetheless, it remains an accademic/educational project aiming to demonstrate the principles of functional programming and how to apply them 21 | in a concrete environment without a large amount of code (BTC-akka has below 1700 LOC) and run on Windows, Linux and Mac OS. 22 | 23 | As for the features, here's a short list: 24 | 25 | - Automatically synchronizes with the blockchain using headers first and parellel block download 26 | - Maintain database of unspent outputs: verifies and relays unconfirmed transactions 27 | - Import/Export blockchain 28 | - Serves headers and blocks - other nodes can synchronize the blockchain from Bitcoin-Akka 29 | 30 | The project is structured as a tutorial with each step associated with a page and a commit. There are 4 milestones before 31 | the end: 32 | 33 | - "handshake": It connects to a peer and completes the handshake phase. The two nodes are ready to communicate, 34 | - "download": It requests the blockchain and can download headers/blocks, 35 | - "persist": It keeps the data on disk and can be restarted without repeating the same tasks, 36 | - "validate": It checks that the data is correct per protocol rules, 37 | - "final": It is finished! 38 | 39 | At the bottom of every page, you have a commit hash. I recommend that you sync the project to it because I won't 40 | necessarily cover every aspect of the commit. The project should always build but it may not do anything useful yet. 41 | 42 | [1]: https://github.com/TheBlueMatt/test-scripts 43 | [2]: https://github.com/bitcoin/bitcoin 44 | -------------------------------------------------------------------------------- /src/main/c/include/bitcoinconsensus.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-2010 Satoshi Nakamoto 2 | // Copyright (c) 2009-2014 The Bitcoin Core developers 3 | // Distributed under the MIT software license, see the accompanying 4 | // file COPYING or http://www.opensource.org/licenses/mit-license.php. 5 | 6 | #ifndef BITCOIN_BITCOINCONSENSUS_H 7 | #define BITCOIN_BITCOINCONSENSUS_H 8 | 9 | #if defined(BUILD_BITCOIN_INTERNAL) && defined(HAVE_CONFIG_H) 10 | #include "config/bitcoin-config.h" 11 | #if defined(_WIN32) 12 | #if defined(DLL_EXPORT) 13 | #if defined(HAVE_FUNC_ATTRIBUTE_DLLEXPORT) 14 | #define EXPORT_SYMBOL __declspec(dllexport) 15 | #else 16 | #define EXPORT_SYMBOL 17 | #endif 18 | #endif 19 | #elif defined(HAVE_FUNC_ATTRIBUTE_VISIBILITY) 20 | #define EXPORT_SYMBOL __attribute__ ((visibility ("default"))) 21 | #endif 22 | #elif defined(MSC_VER) && !defined(STATIC_LIBBITCOINCONSENSUS) 23 | #define EXPORT_SYMBOL __declspec(dllimport) 24 | #endif 25 | 26 | #ifndef EXPORT_SYMBOL 27 | #define EXPORT_SYMBOL 28 | #endif 29 | 30 | #ifdef __cplusplus 31 | extern "C" { 32 | #endif 33 | 34 | #define BITCOINCONSENSUS_API_VER 0 35 | 36 | typedef enum bitcoinconsensus_error_t 37 | { 38 | bitcoinconsensus_ERR_OK = 0, 39 | bitcoinconsensus_ERR_TX_INDEX, 40 | bitcoinconsensus_ERR_TX_SIZE_MISMATCH, 41 | bitcoinconsensus_ERR_TX_DESERIALIZE, 42 | } bitcoinconsensus_error; 43 | 44 | /** Script verification flags */ 45 | enum 46 | { 47 | bitcoinconsensus_SCRIPT_FLAGS_VERIFY_NONE = 0, 48 | bitcoinconsensus_SCRIPT_FLAGS_VERIFY_P2SH = (1U << 0), // evaluate P2SH (BIP16) subscripts 49 | bitcoinconsensus_SCRIPT_FLAGS_VERIFY_DERSIG = (1U << 2), // enforce strict DER (BIP66) compliance 50 | }; 51 | 52 | /// Returns 1 if the input nIn of the serialized transaction pointed to by 53 | /// txTo correctly spends the scriptPubKey pointed to by scriptPubKey under 54 | /// the additional constraints specified by flags. 55 | /// If not NULL, err will contain an error/success code for the operation 56 | EXPORT_SYMBOL int bitcoinconsensus_verify_script(const unsigned char *scriptPubKey, unsigned int scriptPubKeyLen, 57 | const unsigned char *txTo , unsigned int txToLen, 58 | unsigned int nIn, unsigned int flags, bitcoinconsensus_error* err); 59 | 60 | EXPORT_SYMBOL unsigned int bitcoinconsensus_version(); 61 | 62 | #ifdef __cplusplus 63 | } // extern "C" 64 | #endif 65 | 66 | #undef EXPORT_SYMBOL 67 | 68 | #endif // BITCOIN_BITCOINCONSENSUS_H 69 | -------------------------------------------------------------------------------- /src/main/scala/org/bitcoinakka/Script.scala: -------------------------------------------------------------------------------- 1 | package org.bitcoinakka 2 | 3 | import BitcoinMessage._ 4 | import akka.util.ByteStringBuilder 5 | 6 | import collection.mutable 7 | 8 | class ScriptInterpreter { 9 | def verifyScript(scriptPubKey: Script, tx: Tx, index: Int, flags: Int): Int = ??? 10 | 11 | def sigCheck(hash: Hash, pub: Array[Byte], sig: Array[Byte]): Boolean = ??? 12 | } 13 | object ScriptInterpreter { 14 | type StackElement = Array[Byte] 15 | type ScriptStack = mutable.Stack[StackElement] 16 | val one = new Hash(32) 17 | one(0) = 1 18 | 19 | def sigHash(tx: Tx)(index: Int, subScript: Script, sigType: Int): Hash = { 20 | val anyoneCanPay = (sigType & 0x80) != 0 21 | val sigHashType = sigType match { 22 | case 2|3 => sigType 23 | case _ => 1 24 | } 25 | 26 | if (sigHashType == 3 && index >= tx.txOuts.length) 27 | one 28 | else { 29 | val bb = new ByteStringBuilder() 30 | bb.putInt(tx.version) 31 | if (anyoneCanPay) { 32 | bb.putVarInt(1) 33 | val txIn = TxIn(tx.txIns(index).prevOutPoint, subScript, tx.txIns(index).sequence) 34 | bb.append(txIn.toByteString()) 35 | } 36 | else { 37 | bb.putVarInt(tx.txIns.length) 38 | for {(txIn, i) <- tx.txIns.zipWithIndex} { 39 | val txIn2 = TxIn(txIn.prevOutPoint, 40 | if (i == index) subScript else Array.empty[Byte], 41 | if (sigHashType != 1 && i != index) 0 else txIn.sequence) 42 | bb.append(txIn2.toByteString()) 43 | } 44 | } 45 | 46 | sigHashType match { 47 | case 1 => 48 | bb.putVarInt(tx.txOuts.length) 49 | tx.txOuts.foreach(txOut => bb.append(txOut.toByteString())) 50 | case 2 => 51 | bb.putVarInt(0) 52 | case 3 => 53 | bb.putVarInt(index+1) 54 | for (i <- 0 to index) { 55 | if (i < index) 56 | bb.append(TxOut(-1L, Array.empty).toByteString()) 57 | else 58 | bb.append(tx.txOuts(index).toByteString()) 59 | } 60 | } 61 | bb.putInt(tx.lockTime) 62 | bb.putInt(sigType) 63 | 64 | dsha(bb.result().toArray) 65 | } 66 | } 67 | 68 | def push(stack: ScriptStack, v: StackElement) = stack.push(v) 69 | def pop(stack: ScriptStack): StackElement = stack.pop() 70 | def peek(stack: ScriptStack): StackElement = stack.head 71 | 72 | def bigIntToStackElement(i: BigInt): Array[Byte] = { 73 | val absBI = i.abs 74 | val bi = absBI.toByteArray // big endian 75 | val bi2 = if (i < 0 && (bi(0) & 0x80) != 0) 76 | 0.toByte +: bi 77 | else bi 78 | if (i < 0) 79 | bi2(0) = (bi2(0) | 0x80).toByte 80 | bi2.reverse 81 | } 82 | 83 | def intToStackElement(i: Int) = bigIntToStackElement(i) 84 | def longToStackElement(i: Long) = bigIntToStackElement(i) 85 | 86 | def bytesToInt(e: StackElement): BigInt = { 87 | val bi = e.reverse 88 | val isPositive = (bi(0) & 0x80) == 0 89 | bi(0) = (bi(0) & 0x7F).toByte 90 | val absBI = BigInt(bi) 91 | if (isPositive) absBI else -absBI 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/scala/org/bitcoinakka/ImportExport.scala: -------------------------------------------------------------------------------- 1 | package org.bitcoinakka 2 | 3 | import java.io.{FileOutputStream, FileInputStream, File} 4 | import java.sql.DriverManager 5 | 6 | import akka.util.{ByteStringBuilder, ByteString} 7 | import com.typesafe.config.ConfigFactory 8 | import BitcoinMessage._ 9 | import org.slf4j.LoggerFactory 10 | import resource._ 11 | 12 | import scala.collection.mutable 13 | 14 | class BlockchainFile { 15 | val log = LoggerFactory.getLogger(getClass) 16 | val config = ConfigFactory.load() 17 | val settings = new AppSettingsImpl(config) 18 | implicit val blockStore = new BlockStore(settings) 19 | val lastBlocks = mutable.Queue.empty[WHash] 20 | class Db(val appSettings: AppSettingsImpl) extends SyncPersistDb 21 | val db = new Db(settings) 22 | 23 | def r() = { 24 | var lastHeader: HeaderSyncData = null 25 | var pow: BigInt = 0 26 | var bIndex = settings.start-1 27 | for {utxoDb <- managed(new LevelDbUTXO(settings))} { 28 | val fileList = settings.blockFiles.checkpoints 29 | val blocks = for { 30 | file <- fileList.toIterator 31 | fileName = s"${settings.blockFiles.path}/bootstrap-${file}.dat" 32 | block <- parse(new File(fileName)) 33 | } yield block 34 | 35 | for {block <- blocks} { 36 | val wHash = new WHash(block.header.hash) 37 | if (lastBlocks.contains(wHash)) { 38 | log.warn(s"Duplicate block ${hashToString(wHash.array)}") 39 | } 40 | else { 41 | bIndex += 1 42 | pow += block.header.pow 43 | UTXOForwardOnlyBlockOperation.run(utxoDb, block, bIndex) 44 | val hsd = HeaderSyncData(block.header, bIndex, pow) 45 | lastHeader = hsd 46 | db.saveBlockchain(List(hsd)) 47 | lastBlocks.enqueue(wHash) 48 | if (lastBlocks.size > 10) 49 | lastBlocks.dequeue() 50 | } 51 | } 52 | } 53 | val hash = lastHeader.blockHeader.hash 54 | log.info(s"Final block height = ${bIndex} ${hashToString(hash)}") 55 | db.setMainTip(hash) 56 | } 57 | 58 | def parse(file: File): Stream[Block] = { 59 | val is = new FileInputStream(file) 60 | val mhBytes: Array[Byte] = new Array(8) 61 | scalaz.Scalaz.unfold(true) { _ => 62 | if (is.available() > 0) { 63 | is.read(mhBytes) 64 | val bs = ByteString(mhBytes) 65 | val bi = bs.iterator 66 | val magic = bi.getInt 67 | val length = bi.getInt 68 | val payloadBytes: Array[Byte] = new Array(length) 69 | is.read(payloadBytes) 70 | Some(Block.parse(ByteString(payloadBytes)), true) 71 | } else None 72 | } 73 | } 74 | 75 | def write(file: File)(hash: Hash, stopHeight: Int) = { 76 | val os = new FileOutputStream(file) 77 | val initial = db.getHeaderSync(hash).get 78 | Iterator 79 | .iterate(initial) { hsd => db.getHeaderSync(hsd.blockHeader.prevHash).get } 80 | .takeWhile(_.height > stopHeight) 81 | .toList 82 | .reverse 83 | .foreach { hsd => 84 | log.info(s"${hsd}") 85 | val block = blockStore.loadBlockBytes(hsd.blockHeader.hash, hsd.height).get 86 | val bb = new ByteStringBuilder 87 | bb.putInt(0xD9B4BEF9) 88 | bb.putInt(block.length) 89 | os.write(bb.result.toArray) 90 | os.write(block) 91 | } 92 | os.close() 93 | } 94 | 95 | def w() = { 96 | val outFile = new File(settings.saveBlocksConfig.getString("file")) 97 | write(outFile)(hashFromString(settings.saveBlocksConfig.getString("endHash")), settings.saveBlocksConfig.getInt("startHeight")) 98 | } 99 | } 100 | 101 | object BlockchainFileRead extends App { 102 | val bcf = new BlockchainFile 103 | bcf.r() 104 | } 105 | object BlockchainFileWrite extends App { 106 | val bcf = new BlockchainFile 107 | bcf.w() 108 | } 109 | -------------------------------------------------------------------------------- /src/test/scala/org/bitcoinakka/SyncSpec.scala: -------------------------------------------------------------------------------- 1 | package org.bitcoinakka 2 | 3 | import java.io.{File, FileInputStream} 4 | import java.sql.{Connection, DriverManager} 5 | 6 | import akka.actor.ActorSystem 7 | import BitcoinMessage._ 8 | import org.bitcoinj.core.{BlockCheck, FullBlockCheck} 9 | import org.scalatest.{FlatSpec, Matchers} 10 | import org.slf4j.LoggerFactory 11 | 12 | import scala.concurrent.duration._ 13 | import scala.concurrent.{Await, ExecutionContext, Future} 14 | import scala.pickling.Defaults._ 15 | import scala.pickling.binary.{BinaryPickle, StreamInput, _} 16 | import scalaz.StreamT 17 | 18 | class TestSync(val appSettings: AppSettingsImpl, _db: UTXODb, blocksIterator: Iterator[Block]) 19 | extends Sync with SyncPersistDb with SyncDataProvider { 20 | val log = LoggerFactory.getLogger(getClass) 21 | var testBlockchain: List[WHash] = null 22 | var blockchainSet: Set[WHash] = null 23 | var blocksSeen = Map.empty[WHash, Block] 24 | 25 | blockchain = loadBlockchain() 26 | implicit val ec = ExecutionContext.Implicits.global 27 | override def downloadBlocks(headers: List[HeaderSyncData]): Future[Unit] = { 28 | for { 29 | h <- headers 30 | } { 31 | val block = blocksSeen(new WHash(h.blockHeader.hash)) 32 | blockStore.saveBlock(block, h.height) 33 | } 34 | Future.successful(()) 35 | } 36 | override def getHeaders(locators: List[Hash]): Future[List[BlockHeader]] = { 37 | val locator = locators.find(locator => blockchainSet.contains(new WHash(locator))) 38 | val hs = locator map { locator => testBlockchain.dropWhile(_ != new WHash(locator)).tail } getOrElse testBlockchain 39 | 40 | Future.successful(for {h <- hs} yield { 41 | val block = blocksSeen(h) 42 | block.header 43 | }) 44 | } 45 | 46 | def add(bc: BlockCheck) = { 47 | log.info("***********************") 48 | log.info(s"BlockCheck ${bc.name} ${hashToString(bc.hash)}") 49 | log.info("***********************") 50 | val wHash = new WHash(bc.hash) 51 | val nextBlock = blocksIterator.next() 52 | blocksSeen += wHash -> nextBlock 53 | testBlockchain = getBlockchain(bc.hash) 54 | blockchainSet = testBlockchain.toSet 55 | } 56 | 57 | def getBlockchain(tip: Hash): List[WHash] = getBlockchainEndingAt(tip) ++ getBlockchainStartingFrom(tip) 58 | 59 | def getBlockchainEndingAt(tip: Hash): List[WHash] = { 60 | StreamT.unfold(tip) { h => 61 | val wh = new WHash(h) 62 | blocksSeen.get(wh) map { block => 63 | val ph = block.header.prevHash 64 | (wh, ph) 65 | } 66 | }.toStream.toList.reverse 67 | } 68 | 69 | def getBlockchainStartingFrom(head: Hash): List[WHash] = { 70 | StreamT.unfold(head) { h => 71 | val wh = new WHash(h) 72 | blocksSeen 73 | .find { case (_, bh) => new WHash(bh.header.prevHash) == wh } 74 | .map { case (nh, nb) => (nh, nh.array) } 75 | }.toStream.toList 76 | } 77 | 78 | implicit val db = _db 79 | implicit val blockStore: BlockStore = new BlockStore(appSettings) 80 | } 81 | 82 | class SyncSpec extends FlatSpec with Matchers { 83 | val log = LoggerFactory.getLogger(getClass) 84 | Class.forName("com.mysql.jdbc.Driver") 85 | System.loadLibrary("consensus-jni") 86 | val system = ActorSystem() 87 | implicit val settings = AppSettings(system) 88 | implicit val ec = system.dispatcher 89 | 90 | val db = new InMemUTXODb(NopUTXODb) 91 | 92 | val blockFile = new File("testBlocks.dat") 93 | val ruleFile = new File("testBlocks-rules.dat") 94 | val input = new StreamInput(new FileInputStream(ruleFile)) 95 | val p = BinaryPickle(input) 96 | 97 | val bcf = new BlockchainFile 98 | val blocksIterator = bcf.parse(blockFile).toIterator 99 | val checkList = p.unpickle[List[FullBlockCheck]] 100 | 101 | val sync = new TestSync(settings, db, blocksIterator) 102 | 103 | "Sync trait" should "run regression tests" in { 104 | for { fbc <- checkList } { 105 | fbc match { 106 | case bc: BlockCheck => 107 | sync.add(bc) 108 | val f = sync.synchronize(sync) 109 | val _ = Await.result(f, 1.minute) 110 | val tip = sync.blockchain.currentTip.blockHeader.hash 111 | log.info(s"Expected Tip = ${hashToString(bc.tipHash)}") 112 | log.info(s" Tip = ${hashToString(tip)}") 113 | if (!bc.tipHash.sameElements(tip)) 114 | log.error("************* MISMATCH ******************") 115 | } 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/main/scala/org/bitcoinakka/Main.scala: -------------------------------------------------------------------------------- 1 | package org.bitcoinakka 2 | 3 | import java.time.Instant 4 | 5 | import akka.actor.ActorRef 6 | import akka.util.ByteString 7 | import org.apache.commons.codec.binary.Hex 8 | import org.bitcoinakka.BitcoinMessage._ 9 | import org.slf4j.LoggerFactory 10 | 11 | import scalaz.EphemeralStream 12 | 13 | case class MessageHeader(command: String, length: Int, checksum: Array[Byte], payload: ByteString) { 14 | val totalLength = 24 + length 15 | } 16 | 17 | trait MessageHandler { 18 | def parseMessageHeader(bs: ByteString): Option[(MessageHeader, ByteString)] = { 19 | if (bs.length < 24) // got less than the length of the message header, stop 20 | None 21 | else { 22 | val bi = bs.iterator 23 | val magic = bi.getInt 24 | val command: Array[Byte] = new Array(12) 25 | bi.getBytes(command) 26 | val length = bi.getInt 27 | val checksum: Array[Byte] = new Array(4) 28 | bi.getBytes(checksum) 29 | val totalLength = 24 + length 30 | if (bs.length >= totalLength) { 31 | // got enough for a message 32 | val payload = bi.take(bs.length).toByteString 33 | val mh = MessageHeader(new String(command).trim(), length, checksum, payload.take(length)) 34 | Some(mh, bs.drop(totalLength)) 35 | } 36 | else None 37 | } 38 | } 39 | 40 | var buffer = ByteString.empty 41 | def frame(data: ByteString): List[MessageHeader] = { 42 | buffer ++= data 43 | 44 | val messages = EphemeralStream.unfold(buffer)(parseMessageHeader).toList 45 | buffer = buffer.drop(messages.map(_.totalLength).sum) 46 | messages 47 | } 48 | } 49 | 50 | trait BitcoinNetParams { 51 | val magic: Int 52 | val genesisHash: WHash 53 | val genesisBlockHeader: BlockHeader 54 | val firstDifficultyAdjustment: Int 55 | val checkBip34: Boolean 56 | } 57 | object MainNet extends BitcoinNetParams { 58 | val magic = 0xD9B4BEF9 59 | val genesisHash = hashFromString("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f").wrapped 60 | val genesisBytes = Hex.decodeHex("0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c".toCharArray) 61 | val genesisBlockHeader = BlockHeader.parse(ByteString(genesisBytes).iterator, genesisHash.array, false) 62 | val firstDifficultyAdjustment = 32256 63 | val checkBip34 = true 64 | } 65 | object RegTestNet extends BitcoinNetParams { 66 | val magic = 0xDAB5BFFA 67 | val genesisHash = new WHash(hashFromString("0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206")) 68 | val genesisBlockHeader = BlockHeader(genesisHash.array, 1, zeroHash, hashFromString("4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"), 69 | Instant.ofEpochSecond(1296688602), 545259519, 2, 0) 70 | val firstDifficultyAdjustment = 0 71 | val checkBip34 = false 72 | } 73 | case class Blockchain(chainRev: List[HeaderSyncData]) { 74 | def currentTip = chainRev.head 75 | val chain = chainRev.reverse 76 | } 77 | object Blockchain { 78 | val zeroHash = new WHash(new Array(32)) 79 | 80 | val net = System.getProperty("net", "main") 81 | val netParams = net match { 82 | case "main" => MainNet 83 | case "regtest" => RegTestNet 84 | } 85 | 86 | val magic = netParams.magic 87 | val genesisHash = netParams.genesisHash 88 | val genesisBlockHeader = netParams.genesisBlockHeader 89 | val firstDifficultyAdjustment = netParams.firstDifficultyAdjustment 90 | val checkBip34 = netParams.checkBip34 91 | } 92 | 93 | class TxPool(db: UTXODb, currentTip: HeaderSyncData) { 94 | val log = LoggerFactory.getLogger(getClass) 95 | var pool = Map.empty[WHash, Option[Tx]] 96 | val memUTXO = new InMemUTXODb(db) 97 | var currentBlockHeaderData: HeaderSyncData = currentTip 98 | 99 | def setTip(hsd: HeaderSyncData) = { 100 | log.info(s"Clearing Mempool of ${pool.size} transactions") 101 | currentBlockHeaderData = hsd 102 | memUTXO.clear() 103 | pool = Map.empty 104 | } 105 | 106 | def checkUnconfirmedTx(tx: Tx): Option[Tx] = 107 | Consensus.checkTx(tx, -1, currentBlockHeaderData.height, currentBlockHeaderData.blockHeader.timestamp.getEpochSecond, memUTXO).map(_ => tx) 108 | def addUnconfirmedTx(tx: Tx) = { pool = pool.updated(tx.hash.wrapped, Some(tx)) } 109 | 110 | def doRequestTx(hash: Hash, peer: ActorRef) = { 111 | val wHash = hash.wrapped 112 | if (!pool.contains(wHash)) { 113 | pool += wHash -> None 114 | peer ! Peer.GetTx(hash) 115 | } 116 | } 117 | } 118 | 119 | object TxPool { 120 | case class UnconfirmedTx(hash: Hash, peer: ActorRef) 121 | } 122 | -------------------------------------------------------------------------------- /macros/src/main/scala/org/bitcoinakka/MessageMacro.scala: -------------------------------------------------------------------------------- 1 | package org.bitcoinakka 2 | 3 | import java.time.Instant 4 | 5 | import scala.language.experimental.macros 6 | 7 | import scala.annotation.{StaticAnnotation, compileTimeOnly} 8 | import scala.reflect.macros.whitebox.Context 9 | 10 | 11 | @compileTimeOnly("enable macro paradise to expand macro annotations") 12 | class MessageMacro extends StaticAnnotation { 13 | def macroTransform(annottees: Any*): Any = macro MessageMacro.impl 14 | } 15 | 16 | object MessageMacro { 17 | val listOf = "List\\[(\\w+)\\]".r 18 | 19 | def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = { 20 | import c.universe._ 21 | 22 | def getField(tpe: String) = { 23 | tpe match { 24 | case "Byte" => q"bi.getByte" 25 | case "Int" => q"bi.getInt" 26 | case "Long" => q"bi.getLong" 27 | case "BigInt" => q"bi.getBigInt" 28 | case "Hash" => q"bi.getHash" 29 | case "String" => q"bi.getVarString" 30 | case "Script" => q"bi.getScript" 31 | case "BlockHeader" => q"bi.getBlockHeader" 32 | case "Instant" => q"Instant.ofEpochSecond(bi.getInt)" 33 | case "InetSocketAddress" => q"bi.getInetSocketAddress" 34 | case m => q"${TermName(m)}.parseBI(bi)" 35 | } 36 | } 37 | 38 | def readScalar(name: TermName, tpe: String): c.universe.Tree = q"val $name = ${getField(tpe)}" 39 | def readList(name: TermName, tpe: String) = { 40 | q"""val $name = { 41 | val c = bi.getVarInt 42 | (for (_ <- 0 until c) yield ${getField(tpe)}).toList 43 | }""" 44 | } 45 | 46 | def putField(name: TermName, tpe: String) = tpe match { 47 | case "Byte" => q"bb.putByte($name)" 48 | case "Int" => q"bb.putInt($name)" 49 | case "Long" => q"bb.putLong($name)" 50 | case "BigInt" => q"bb.putBigInt($name)" 51 | case "Hash" => q"bb.putBytes($name)" 52 | case "String" => q"bb.putVarString($name)" 53 | case "Script" => q"bb.putScript($name)" 54 | case "BlockHeader" => q"bb.putBlockHeader($name)" 55 | case "Instant" => q"bb.putInt($name.getEpochSecond.toInt)" 56 | case "InetSocketAddress" => q"bb.putInetSocketAddress($name)" 57 | case m => q"bb.append($name.toByteString())" 58 | } 59 | 60 | def writeList(name: TermName, tpe: String) = { 61 | q"""{ bb.putVarInt($name.length); $name.foreach { n => ${putField(TermName("n"), tpe)} } }""" 62 | } 63 | 64 | def modifyClass(classDef: ClassDef): c.Expr[Any] = { 65 | val (messageName: TypeName, fields: List[ValDef]) = 66 | try { 67 | val q"case class $messageName(..$fields)" = classDef 68 | (messageName, fields) 69 | } 70 | catch { 71 | case m: MatchError => c.abort(c.enclosingPosition, s"Must use @MessageMacro on a case class, ${m}") 72 | } 73 | 74 | val readBody = { 75 | for { 76 | field: ValDef <- fields 77 | } yield { 78 | val fieldType = field.tpt.toString() 79 | val name = field.name 80 | fieldType match { 81 | case listOf(tpe) => readList(name, tpe) 82 | case tpe => readScalar(name, tpe) 83 | } 84 | } 85 | } 86 | 87 | val writeBody = { 88 | for { 89 | field: ValDef <- fields 90 | } yield { 91 | val fieldType = field.tpt.toString() 92 | val name = field.name 93 | fieldType match { 94 | case listOf(tpe) => writeList(name, tpe) 95 | case tpe => putField(name, tpe) 96 | } 97 | } 98 | } 99 | 100 | val messageNameLC = messageName.toString.toLowerCase 101 | val fieldNames = fields.map(_.name) 102 | 103 | c.Expr(q"""case class $messageName(..$fields) extends BitcoinMessage { 104 | import BitcoinMessage.ByteStringBuilderExt 105 | val command = $messageNameLC 106 | def toByteString(): ByteString = { 107 | val bb = new ByteStringBuilder 108 | ..$writeBody 109 | bb.result() 110 | } 111 | } 112 | 113 | object ${messageName.toTermName} extends ByteOrderImplicit { 114 | import BitcoinMessage.ByteStringIteratorExt 115 | def parse(bs: ByteString) = parseBI(bs.iterator) 116 | def parseBI(bi: ByteIterator) = { 117 | ..$readBody 118 | new $messageName(..$fieldNames) 119 | } 120 | }""" 121 | ) 122 | } 123 | 124 | annottees.map(_.tree) match { 125 | case (classDef: ClassDef) :: Nil => modifyClass(classDef) 126 | case _ => c.abort(c.enclosingPosition, "Cannot use @MessageMacro on this symbol") 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/main/scala/org/bitcoinakka/UTXO.scala: -------------------------------------------------------------------------------- 1 | package org.bitcoinakka 2 | 3 | import java.io.File 4 | 5 | import akka.util.{ByteIterator, ByteString, ByteStringBuilder} 6 | import org.bitcoinakka.BitcoinMessage._ 7 | import org.fusesource.leveldbjni.JniDBFactory 8 | import org.iq80.leveldb.{DBException, Options} 9 | import org.slf4j.LoggerFactory 10 | import resource.Resource 11 | 12 | import scala.collection.mutable 13 | import scala.language.postfixOps 14 | 15 | case class UTxOut(txOut: TxOut, height: Option[Int]) extends ByteOrderImplicit { 16 | def toByteString(): ByteString = { 17 | val bb = new ByteStringBuilder 18 | bb.append(txOut.toByteString()) 19 | bb.putInt(height.getOrElse(0)) 20 | bb.result() 21 | } 22 | } 23 | object UTxOut extends ByteOrderImplicit { 24 | def parse(bi: ByteIterator) = { 25 | val txOut = TxOut.parseBI(bi) 26 | val height = bi.getInt 27 | UTxOut(txOut, if (height == 0) None else Some(height)) 28 | } 29 | } 30 | case class UTXOEntry(key: OutPoint, value: Option[UTxOut]) 31 | 32 | trait UTXODb { 33 | def add(entry: UTXOEntry): Unit 34 | def get(key: OutPoint): Option[UTxOut] 35 | } 36 | 37 | object NopUTXODb extends UTXODb { 38 | def add(entry: UTXOEntry): Unit = {} 39 | def get(key: OutPoint): Option[UTxOut] = None 40 | } 41 | 42 | class InMemUTXODb(underlyingDb: UTXODb) extends UTXODb { 43 | val log = LoggerFactory.getLogger(getClass) 44 | val map = new mutable.HashMap[WHash, Array[Byte]] 45 | override def add(entry: UTXOEntry): Unit = { 46 | val k = new WHash(entry.key.toByteString().toArray) 47 | entry.value match { 48 | case Some(e) => 49 | map.put(k, e.toByteString().toArray) 50 | case None => 51 | map.put(k, Array.empty) 52 | } 53 | } 54 | def get(key: OutPoint): Option[UTxOut] = { 55 | val k = new WHash(key.toByteString().toArray) 56 | val v = map.get(k) 57 | 58 | v.fold(underlyingDb.get(key)) { v => 59 | if (!v.isEmpty) 60 | Some(UTxOut.parse(ByteString(v).iterator)) 61 | else 62 | None 63 | } 64 | } 65 | def clear() = map.clear() 66 | } 67 | 68 | class LevelDbUTXO(settings: AppSettingsImpl) extends UTXODb with Resource[LevelDbUTXO] { 69 | val log = LoggerFactory.getLogger(getClass) 70 | val options = new Options() 71 | options.createIfMissing(true) 72 | val dbDir = new File(s"${settings.baseDir}/utxo") 73 | dbDir.mkdirs() 74 | val db = JniDBFactory.factory.open(dbDir, options) 75 | 76 | def close() = db.close() 77 | 78 | override def add(entry: UTXOEntry): Unit = { 79 | entry.value match { 80 | case Some(v) => db.put(entry.key.toByteString().toArray, v.toByteString().toArray) 81 | case None => db.delete(entry.key.toByteString().toArray) 82 | } 83 | } 84 | 85 | override def get(key: OutPoint): Option[UTxOut] = { 86 | try { 87 | val v = db.get(key.toByteString().toArray) 88 | if (v != null) { 89 | Some(UTxOut.parse(ByteString(v).iterator)) 90 | } 91 | else None 92 | } 93 | catch { 94 | case dbE: DBException => 95 | log.warn(dbE.toString) 96 | Thread.sleep(5000) // wait and retry 97 | get(key) 98 | } 99 | } 100 | 101 | override def close(r: LevelDbUTXO): Unit = close() 102 | } 103 | 104 | object UTXO { 105 | type UTXOEntryList = List[UTXOEntry] 106 | 107 | def ofTx(tx: Tx, isCoinbase: Boolean, height: Int): UTXOEntryList = { 108 | val deleted = if (!isCoinbase) tx.txIns.map(txIn => UTXOEntry(txIn.prevOutPoint, None)) else List.empty[UTXOEntry] 109 | val added = tx.txOuts.zipWithIndex.map { case (txOut, i) => 110 | val outpoint = OutPoint(tx.hash, i) 111 | val utxo = UTxOut(txOut, if (isCoinbase) Some(height) else None) 112 | UTXOEntry(outpoint, Some(utxo)) 113 | } 114 | (deleted ++ added) toList 115 | } 116 | def undoOf(db: UTXODb, entries: UTXOEntryList): UTXOEntryList = { 117 | entries.flatMap { entry => 118 | entry.value match { 119 | case Some(e) => Some(UTXOEntry(entry.key, None)) 120 | case None => 121 | db.get(entry.key) map { prevTxOut => // this should be checked earlier 122 | UTXOEntry(entry.key, Some(prevTxOut)) } 123 | } 124 | } 125 | } 126 | } 127 | 128 | trait UTXOOperation { 129 | def run(db: UTXODb): Unit 130 | def undo(db: UTXODb): Unit 131 | } 132 | 133 | object UTXOForwardOnlyBlockOperation { 134 | def run(db: UTXODb, block: Block, height: Int): Unit = { 135 | for { 136 | (tx, i) <- block.txs.zipWithIndex 137 | entry <- UTXO.ofTx(tx, i == 0, height) 138 | } { 139 | db.add(entry) 140 | } 141 | } 142 | } 143 | 144 | case class UTXOBlockOperation(hash: Hash, height: Int, isUndo: Boolean)(implicit blockStore: BlockStore) extends UTXOOperation { 145 | val log = LoggerFactory.getLogger(getClass) 146 | override def run(db: UTXODb): Unit = if (isUndo) undo_(db) else do_(db) 147 | override def undo(db: UTXODb): Unit = if (isUndo) do_(db) else undo_(db) 148 | 149 | private def do_(db: UTXODb) = { 150 | log.info(s"Applying tx in block ${hashToString(hash)}") 151 | val block = blockStore.loadBlock(hash, height) 152 | UTXOForwardOnlyBlockOperation.run(db, block, height) 153 | } 154 | private def undo_(db: UTXODb) = { 155 | log.info(s"Undoing tx in block ${hashToString(hash)}") 156 | blockStore.loadUndoBlock(hash, height).foreach(db.add(_)) 157 | } 158 | } 159 | object UTXOBlockOperation { 160 | val log = LoggerFactory.getLogger(getClass) 161 | def buildAndSaveUndoBlock(db: UTXODb, block: Block, height: Int)(implicit blockStore: BlockStore): Unit = { 162 | val doList = block.txs.toList.zipWithIndex.map { case (tx, i) => UTXO.ofTx(tx, i == 0, height) } 163 | val undoList = doList.reverse.flatMap(e => UTXO.undoOf(db, e)) 164 | blockStore.saveUndoBlock(block.header.hash, height, undoList) 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/main/scala/org/bitcoinakka/Consensus.scala: -------------------------------------------------------------------------------- 1 | package org.bitcoinakka 2 | 3 | import java.nio.{ByteBuffer, ByteOrder} 4 | import java.time.{Instant, ZoneId} 5 | 6 | import akka.util.ByteString 7 | import org.bitcoinakka.BitcoinMessage._ 8 | import org.slf4j.LoggerFactory 9 | 10 | import scalaz.syntax.std.boolean._ 11 | 12 | object Consensus { 13 | 14 | val log = LoggerFactory.getLogger(getClass) 15 | val difficultyAdjustmentInterval = 2016 16 | val targetElapsed = 600*difficultyAdjustmentInterval // 10 mn average per block 17 | val maxBlockSize = 1000000 18 | val coinbaseMaturity = 100 19 | val maxSighashCheckCount = 20000 20 | 21 | def adjustedDifficulty(blockHeader: BlockHeader, prevTarget: BigInt, elapsedSinceAdjustment: Long)(implicit blockchain: Blockchain): Int = { 22 | val boundedElapsed = (elapsedSinceAdjustment min targetElapsed*4) max targetElapsed/4 23 | val adjustedTarget = prevTarget*boundedElapsed/targetElapsed 24 | val bits = BlockHeader.getBits(adjustedTarget) 25 | log.debug(s"Old bits = ${BlockHeader.getBits(prevTarget)}, New bits = ${bits}") 26 | bits 27 | } 28 | 29 | def checkSize(block: Block):Option[Unit] = { 30 | val x = (block.toByteString().length <= maxBlockSize).option(()) 31 | log.debug(s"checkSize: ${x}") 32 | x 33 | } 34 | 35 | def merkleRoot(hashes: Array[Hash]): Hash = { 36 | if (hashes.length == 1) 37 | hashes(0) 38 | else { 39 | val hs = 40 | if (hashes.length % 2 == 0) 41 | hashes 42 | else 43 | hashes :+ hashes.last 44 | val nextLevelHashes = hs.sliding(2, 2).map { case Array(a, b) => 45 | BitcoinMessage.dsha(a ++ b) 46 | }.toArray 47 | merkleRoot(nextLevelHashes) 48 | } 49 | } 50 | 51 | def checkMerkleRoot(block: Block): Option[Unit] = { 52 | val blockRoot = merkleRoot(block.txs.map(_.hash)) 53 | (new WHash(blockRoot) == new WHash(block.header.merkleRoot)).option(()) 54 | } 55 | 56 | def getBlockReward(height: Int): Long = { 57 | val range = height / 210000 58 | val era = range min 33 59 | 5000000000L / (1L << era) 60 | } 61 | 62 | def checkDoubleSpend(tx: Tx, index: Int, db: UTXODb): Option[Long] = { 63 | if (index != 0) { 64 | val inputValues = tx.txIns.zipWithIndex.map { case (txIn, i) => 65 | db.get(txIn.prevOutPoint).map(_.txOut.value) 66 | } 67 | if (inputValues.forall(_.isDefined)) { 68 | val totalInputs = inputValues.flatten.sum 69 | val totalOutputs = tx.txOuts.map(_.value).sum 70 | val fee = totalInputs-totalOutputs 71 | Some(fee) 72 | } 73 | else { 74 | if (index > 0) 75 | log.error(s"Double spend in tx ${hashToString(tx.hash)}") 76 | None 77 | } 78 | } 79 | else Some(0L) 80 | } 81 | 82 | def checkScripts(tx: Tx, height: Int, db: UTXODb): Option[Int] = { 83 | val txBytes = tx.toByteString().toArray 84 | val r = for { 85 | (txIn, index) <- tx.txIns.zipWithIndex 86 | prevOutPoint = txIn.prevOutPoint 87 | utxo <- db.get(prevOutPoint) 88 | utxoHeight = utxo.height.getOrElse(-coinbaseMaturity) 89 | } yield { 90 | (height-utxoHeight >= coinbaseMaturity).option(()) flatMap { _ => 91 | val result = Consensus.verifyScript(utxo.txOut.script, txBytes, index, 5 /* BIP16 && 66 */) 92 | if (result == 1) 93 | Some(ScriptUtils.sighashCheckCountInput(utxo.txOut.script, txIn.sigScript)) 94 | else { 95 | log.error(s"Script verification failed in tx ${tx}") 96 | None 97 | } 98 | } 99 | } 100 | if (r.forall(_.isDefined)) { 101 | val sighashCheckCountOfInputs = r.map(_.get).sum 102 | val sighashCheckCountOfOutputs = tx.txOuts.map(txOut => ScriptUtils.sighashCheckCount1(txOut.script)).sum 103 | log.debug(s"Sighash check count for ${hashToString(tx.hash)} = ${sighashCheckCountOfInputs} + ${sighashCheckCountOfOutputs}") 104 | Some(sighashCheckCountOfInputs+sighashCheckCountOfOutputs) 105 | } 106 | else 107 | None 108 | } 109 | 110 | def bip34(script: => Script, height: Int): Option[Boolean] = { 111 | for { _ <- (script.length >= 2 && script.length <= 100).option(()) } yield { 112 | if (Blockchain.checkBip34) { 113 | val len = script(0) 114 | val bb = ByteBuffer.allocate(4) 115 | bb.order(ByteOrder.LITTLE_ENDIAN) 116 | bb.put(script, 1, len) 117 | bb.flip() 118 | bb.limit(4) 119 | bb.getInt == height 120 | } 121 | else true 122 | } 123 | } 124 | 125 | def checkCoinbase(tx: Tx, height: Int, fees: Long): Option[Unit] = { 126 | val totalOutputs = tx.txOuts.map(_.value).sum 127 | val feesMatch = totalOutputs <= fees+getBlockReward(height) 128 | if (!feesMatch) { 129 | log.debug(s"Fees don't match: coinbase ${fees+getBlockReward(height)} vs ${totalOutputs}") 130 | } 131 | val coinbaseFormatMatch = tx.txIns.length == 1 && tx.txIns(0).prevOutPoint.hash.sameElements(zeroHash) && 132 | tx.txIns(0).prevOutPoint.index == -1 133 | log.debug(s"feesMatch: ${feesMatch} coinbaseFormatMatch: ${coinbaseFormatMatch}") 134 | (feesMatch && coinbaseFormatMatch && bip34(tx.txIns(0).sigScript, height).getOrElse(false)).option(()) 135 | } 136 | 137 | def isFinal(tx: Tx, height: Int, timestamp: Long) = { 138 | val sequenceFinal = tx.txIns.exists(txIn => txIn.sequence == 0xFFFFFFFF) 139 | val lockTime = tx.lockTime & 0x7FFFFFFF 140 | (sequenceFinal || lockTime == 0 || (lockTime < 500000000 && height >= lockTime) || (lockTime >= 500000000 && timestamp >= lockTime)) 141 | } 142 | 143 | def checkTx(tx: Tx, index: Int, height: Int, timestamp: Long, db: UTXODb): Option[(Long, Int)] = { 144 | for { 145 | sighashCheckCount <- checkScripts(tx, height, db) 146 | fees <- checkDoubleSpend(tx, index, db) 147 | _ <- (isFinal(tx, height, timestamp)).option(()) 148 | } yield (fees, sighashCheckCount) 149 | } 150 | 151 | def timestampToString(timestamp: Long) = Instant.ofEpochSecond(timestamp).atZone(ZoneId.systemDefault()) 152 | 153 | @native def verifyScript(scriptPubKey: Script, tx: Array[Byte], index: Int, flags: Int): Int 154 | } 155 | 156 | object ScriptUtils extends ByteOrderImplicit { 157 | val log = LoggerFactory.getLogger(getClass) 158 | def sighashCheckCount1(script: Script): Int = { 159 | var count = 0 160 | var multisigWeight = 20 161 | val bi = ByteString(script).iterator 162 | 163 | try { 164 | while (!bi.isEmpty) { 165 | val next: Int = bi.getByte & 0x000000FF 166 | if (next >= 82 && next <= 96) { 167 | multisigWeight = next - 80 168 | } 169 | else { 170 | if (next == 172 || next == 173) 171 | count += 1 172 | else if (next == 174 || next == 175) 173 | count += multisigWeight 174 | multisigWeight = 20 175 | if (next >= 1 && next <= 75) 176 | bi.drop(next) 177 | else if (next == 76) { 178 | val len: Int = bi.getByte & 0x000000FF 179 | bi.drop(len) 180 | } 181 | else if (next == 77) { 182 | val len: Int = bi.getShort & 0x0000FFFF 183 | bi.drop(len) 184 | } 185 | else if (next == 78) { 186 | val len: Int = bi.getInt & 0x7FFFFFFF 187 | bi.drop(len) 188 | } 189 | } 190 | } 191 | } 192 | catch { 193 | case t: NoSuchElementException => 194 | } 195 | count 196 | } 197 | 198 | def getLastPushData(script: Script): Option[Script] = { 199 | val bi = ByteString(script).iterator 200 | var lastData: Option[Script] = None 201 | 202 | def getNextBytes(n: Int): Script = { 203 | val ba: Script = new Array(n) 204 | bi.getBytes(ba) 205 | ba 206 | } 207 | 208 | try { 209 | while (!bi.isEmpty) { 210 | val next: Int = bi.getByte & 0x000000FF 211 | if (next >= 1 && next <= 75) 212 | lastData = Some(getNextBytes(next)) 213 | else if (next == 76) { 214 | val len: Int = bi.getByte & 0x000000FF 215 | lastData = Some(getNextBytes(len)) 216 | } 217 | else if (next == 77) { 218 | val len: Int = bi.getShort & 0x0000FFFF 219 | lastData = Some(getNextBytes(len)) 220 | } 221 | else if (next == 78) { 222 | val len: Int = bi.getInt 223 | lastData = Some(getNextBytes(len)) 224 | } 225 | else 226 | lastData = None 227 | } 228 | } 229 | catch { 230 | case t: NoSuchElementException => 231 | } 232 | lastData 233 | } 234 | 235 | def isP2SH(script: Script) = script.length == 23 && script(0) == -87 && script(1) == 20 && script(22) == -121 236 | 237 | def getReedeemScript(pubScript: Script, sigScript: Script): Option[Script] = { 238 | for { 239 | _ <- (isP2SH(pubScript)).option(()) 240 | data <- getLastPushData(sigScript) 241 | } yield data 242 | } 243 | 244 | def sighashCheckCountInput(pubScript: Script, sigScript: Script) = getReedeemScript(pubScript, sigScript).map(sighashCheckCount1).getOrElse(0) 245 | } -------------------------------------------------------------------------------- /src/main/scala/org/bitcoinakka/Message.scala: -------------------------------------------------------------------------------- 1 | package org.bitcoinakka 2 | 3 | import java.net.{Inet6Address, Inet4Address, InetAddress, InetSocketAddress} 4 | import java.nio.{ByteBuffer, ByteOrder} 5 | import java.security.MessageDigest 6 | import java.time.Instant 7 | 8 | import akka.util.{ByteIterator, ByteString, ByteStringBuilder} 9 | import org.apache.commons.codec.binary.Hex 10 | import org.apache.commons.lang3.StringUtils 11 | import BitcoinMessage._ 12 | 13 | import scala.language.postfixOps 14 | 15 | trait ByteOrderImplicit { 16 | implicit val byteOrder = ByteOrder.LITTLE_ENDIAN 17 | } 18 | 19 | trait BitcoinMessage extends ByteOrderImplicit { 20 | import BitcoinMessage._ 21 | val command: String 22 | def toByteString(): ByteString 23 | def toMessage() = { 24 | val bb = new ByteStringBuilder 25 | bb.putInt(magic) 26 | bb.putBytes(StringUtils.rightPad(command, 12, 0.toChar).getBytes()) 27 | val payload = toByteString() 28 | bb.putInt(payload.length) 29 | val checksum = calcChecksum(payload) 30 | bb.putBytes(checksum) 31 | bb.append(payload) 32 | bb.result() 33 | } 34 | } 35 | 36 | object BitcoinMessage extends ByteOrderImplicit { 37 | val magic = 0xD9B4BEF9 38 | val version = 70001 39 | type Hash = Array[Byte] 40 | type WHash = collection.mutable.WrappedArray.ofByte 41 | type Script = Array[Byte] 42 | val zeroHash: Hash = new Array(32) 43 | 44 | def hashFromString(hs: String) = Hex.decodeHex(hs.toCharArray).reverse 45 | def hashToString(hash: Hash) = Hex.encodeHexString(hash.reverse) 46 | 47 | def calcChecksum(data: ByteString) = { 48 | val hash = dsha(data.toArray[Byte]) 49 | java.util.Arrays.copyOfRange(hash, 0, 4) 50 | } 51 | 52 | val sha256: (Array[Byte]) => Array[Byte] = { data => 53 | val md = MessageDigest.getInstance("SHA-256") 54 | md.update(data) 55 | md.digest() 56 | } 57 | val dsha = sha256 compose sha256 58 | 59 | implicit class ByteStringBuilderExt(bb: ByteStringBuilder) { 60 | def putVarString(s: String) = { 61 | putVarInt(s.length) 62 | bb.putBytes(s.getBytes) 63 | } 64 | def putScript(script: Script) = { 65 | putVarInt(script.length) 66 | bb.putBytes(script) 67 | } 68 | def putVarInt(i: Int) = { 69 | assert(i >= 0) 70 | if (i <= 0xFC) 71 | bb.putByte(i.toByte) 72 | else if (i <= 0xFFFF) { 73 | bb.putByte(0xFD.toByte) 74 | bb.putShort(i.toShort) 75 | } 76 | else { 77 | bb.putByte(0xFE.toByte) 78 | bb.putInt(i) 79 | } 80 | } 81 | def putInetSocketAddress(address: InetSocketAddress) = { 82 | bb.putLong(1) 83 | val addr = address.getAddress 84 | addr match { 85 | case _: Inet4Address => 86 | bb.putLong(0) 87 | bb.putInt(0xFFFF0000) 88 | case _: Inet6Address => 89 | } 90 | bb.putBytes(addr.getAddress) 91 | bb.putShort(java.lang.Short.reverseBytes(address.getPort.toShort)) 92 | } 93 | def putBlockHeader(bh: BlockHeader) = { 94 | bb.append(bh.toByteString()) 95 | bb.putVarInt(0) 96 | } 97 | def putBigInt(bi: BigInt) = { 98 | val paddedPowBytes: Array[Byte] = new Array(32) 99 | val powBytes = bi.toByteArray 100 | java.util.Arrays.fill(paddedPowBytes, 0, 32, 0.toByte) 101 | Array.copy(powBytes, 0, paddedPowBytes, 32 - powBytes.length, powBytes.length) 102 | bb.putBytes(paddedPowBytes) 103 | } 104 | } 105 | 106 | implicit class ByteStringIteratorExt(bi: ByteIterator) { 107 | def getVarString: String = { 108 | val length = getVarInt 109 | val s: Array[Byte] = new Array(length) 110 | bi.getBytes(s) 111 | new String(s) 112 | } 113 | def getVarInt: Int = { 114 | val b = bi.getByte 115 | b match { 116 | case -3 => (bi.getShort & 0x0000FFFF) 117 | case -2 => (bi.getInt & 0x7FFFFFFF) 118 | case -1 => bi.getLong.toInt 119 | case _ => (b.toInt & 0x000000FF) 120 | } 121 | } 122 | def getHash: Hash = { 123 | val h: Hash = new Array(32) 124 | bi.getBytes(h) 125 | h 126 | } 127 | def getScript: Script = { 128 | val len = bi.getVarInt 129 | val script: Script = new Array(len) 130 | bi.getBytes(script) 131 | script 132 | } 133 | def getInetSocketAddress: InetSocketAddress = { 134 | bi.drop(8) 135 | val addrBytes: Array[Byte] = new Array(16) 136 | bi.getBytes(addrBytes) 137 | val addr = InetAddress.getByAddress(addrBytes) 138 | val port: Int = bi.getShort(ByteOrder.BIG_ENDIAN).toInt & 0xFFFF 139 | new InetSocketAddress(addr, port) 140 | } 141 | def getBlockHeader: BlockHeader = { 142 | val hashedPart = bi.clone.slice(0, 80).toArray[Byte] 143 | val blockHash = dsha(hashedPart) 144 | BlockHeader.parse(bi, blockHash, true) 145 | } 146 | def getBigInt: BigInt = { 147 | val paddedPowBytes: Array[Byte] = new Array(32) 148 | bi.getBytes(paddedPowBytes) 149 | BigInt(paddedPowBytes) 150 | } 151 | } 152 | 153 | implicit class toWHash(hash: Hash) { 154 | def wrapped = new WHash(hash) 155 | } 156 | } 157 | 158 | class InternalBitcoinMessage(val hashes: List[Hash]) extends BitcoinMessage { 159 | val command = "" 160 | def toByteString() = ??? 161 | } 162 | case class GetTxData(_hashes: List[Hash]) extends InternalBitcoinMessage(_hashes) { 163 | def toGetData() = GetData(hashes.map(InvEntry(1, _))) 164 | } 165 | case class GetBlockData(_hashes: List[Hash]) extends InternalBitcoinMessage(_hashes) { 166 | def toGetData() = GetData(hashes.map(InvEntry(2, _))) 167 | } 168 | 169 | case class Block(header: BlockHeader, txs: Array[Tx], payload: ByteString) extends BitcoinMessage { 170 | override val command: String = "block" 171 | override def toByteString(): ByteString = { 172 | val bb = new ByteStringBuilder 173 | bb.append(header.toByteString()) 174 | bb.putVarInt(txs.length) 175 | txs.foreach(tx => bb.append(tx.toByteString)) 176 | bb.result() 177 | } 178 | override def toString() = s"Block($header)" 179 | } 180 | object Block { 181 | def parse(bs: ByteString): Block = { 182 | val iter = bs.iterator 183 | val hashedPart = iter.clone.slice(0, 80).toArray 184 | val blockHash = dsha(hashedPart) 185 | val bh = BlockHeader.parse(iter, blockHash, true) 186 | val txs = Array.range(0, bh.txCount).map { _ => Tx.parseBI(iter) } 187 | Block(bh, txs, bs) 188 | } 189 | } 190 | 191 | case class Tx(hash: Hash, version: Int, txIns: List[TxIn], txOuts: List[TxOut], lockTime: Int) extends BitcoinMessage { 192 | val command = "tx" 193 | def toByteString() = { 194 | val itx = InternalTx(version, txIns, txOuts, lockTime) 195 | itx.toByteString() 196 | } 197 | override def toString() = s"Tx(${hashToString(hash)}, ${txIns.size} -> ${txOuts.size})" 198 | } 199 | object Tx { 200 | def parseBI(bi: ByteIterator) = { 201 | val mark = bi.len 202 | val biCopy = bi.clone() 203 | val itx: InternalTx = InternalTx.parseBI(bi) 204 | val txLen = mark-bi.len 205 | val txBytes = biCopy.slice(0, txLen).toArray 206 | val txHash = dsha(txBytes) 207 | Tx(txHash, itx.version, itx.txIns, itx.txOuts, itx.lockTime) 208 | } 209 | } 210 | 211 | case class BlockHeader(hash: Hash, version: Int, prevHash: Hash, merkleRoot: Hash, timestamp: Instant, bits: Int, nonce: Int, txCount: Int) extends BitcoinMessage { 212 | val command = "blockheader" 213 | val target = BlockHeader.getTarget(bits) 214 | val pow = BlockHeader.maxHash/target 215 | override def toByteString(): ByteString = { 216 | val ibh = InternalBlockHeader(version, prevHash, merkleRoot, timestamp, bits, nonce) 217 | ibh.toByteString() // NB: txCount is not saved! 218 | } 219 | override def toString() = s"BlockHeader(${hashToString(hash)}, ${hashToString(prevHash)})" 220 | } 221 | 222 | object BlockHeader { 223 | val maxHash = BigInt(0).setBit(256) 224 | def parse(bi: ByteIterator, blockHash: Hash, readTxCount: Boolean): BlockHeader = { 225 | val ibh: InternalBlockHeader = InternalBlockHeader.parseBI(bi) 226 | val txCount = if (readTxCount) bi.getVarInt else 0 227 | BlockHeader(blockHash, ibh.version, ibh.prevHash, ibh.merkleRoot, ibh.timestamp, ibh.bits, ibh.nonce, txCount) 228 | } 229 | 230 | def getBits(target: BigInt): Int = { 231 | val exp256 = target.toByteArray.length 232 | val mantissa = if (exp256 <= 3) // shift the 3 msb of target 233 | target.intValue() << (8*(3-exp256)) 234 | else 235 | (target >> (8*(exp256-3))).intValue() 236 | 237 | if ((mantissa & 0x800000) != 0) // if this bit is set, it would be a negative number 238 | (mantissa>>8)|((exp256+1) << 24) 239 | else 240 | mantissa|(exp256 << 24) 241 | } 242 | 243 | def getTarget(bits: Int): BigInt = { 244 | val bb = ByteBuffer.allocate(4) 245 | bb.order(ByteOrder.BIG_ENDIAN) 246 | bb.putInt(bits) 247 | bb.flip() 248 | val b: Array[Byte] = new Array(4) 249 | bb.get(b) 250 | val mantissa = BigInt(1, b.slice(1, 4)) 251 | val exp: Int = b(0).toInt & 0x000000FF 252 | val target = mantissa << (8*(exp-3)) 253 | target 254 | } 255 | } 256 | 257 | case class IncomingMessage(m: BitcoinMessage) extends BitcoinMessage { 258 | val command = m.command 259 | def toByteString = m.toByteString() 260 | } 261 | 262 | case class OutgoingMessage(m: BitcoinMessage) extends BitcoinMessage { 263 | val command = m.command 264 | def toByteString = m.toByteString() 265 | } 266 | 267 | @MessageMacro case class Version(version: Int, services: Long, timestamp: Long, recv: InetSocketAddress, from: InetSocketAddress, nonce: Long, userAgent: String, height: Int) 268 | @MessageMacro case class Verack() 269 | @MessageMacro case class GetHeaders(version: Int, hashes: List[Hash], stopHash: Hash) 270 | @MessageMacro case class Headers(blockHeaders: List[BlockHeader]) 271 | @MessageMacro case class InvEntry(tpe: Int, hash: Hash) 272 | @MessageMacro case class GetData(invs: List[InvEntry]) 273 | @MessageMacro case class OutPoint(hash: Hash, index: Int) 274 | @MessageMacro case class TxIn(prevOutPoint: OutPoint, sigScript: Script, sequence: Int) 275 | @MessageMacro case class TxOut(value: Long, script: Script) 276 | @MessageMacro case class InternalTx(version: Int, txIns: List[TxIn], txOuts: List[TxOut], lockTime: Int) 277 | @MessageMacro case class InternalBlockHeader(version: Int, prevHash: Hash, merkleRoot: Hash, timestamp: Instant, bits: Int, nonce: Int) 278 | @MessageMacro case class Inv(invs: List[InvEntry]) 279 | @MessageMacro case class GetAddr() 280 | @MessageMacro case class Addr(addrs: List[AddrEntry]) 281 | @MessageMacro case class AddrEntry(timestamp: Instant, addr: InetSocketAddress) 282 | @MessageMacro case class Ping(nonce: Long) 283 | @MessageMacro case class Pong(nonce: Long) 284 | -------------------------------------------------------------------------------- /src/main/scala/org/bitcoinakka/Sync.scala: -------------------------------------------------------------------------------- 1 | package org.bitcoinakka 2 | 3 | import java.io.{FileInputStream, FileOutputStream, File} 4 | import java.nio.ByteBuffer 5 | import java.nio.channels.FileChannel 6 | import java.nio.file.{Files, Path, Paths, StandardOpenOption} 7 | import java.sql.Connection 8 | import java.time.Instant 9 | import java.time.temporal.ChronoUnit 10 | 11 | import akka.util.{ByteString, ByteStringBuilder} 12 | import org.bitcoinakka.BitcoinMessage._ 13 | import org.bitcoinakka.Consensus._ 14 | import org.bitcoinakka.UTXO.UTXOEntryList 15 | import org.fusesource.leveldbjni.JniDBFactory 16 | import org.iq80.leveldb.Options 17 | import org.slf4j.LoggerFactory 18 | import resource._ 19 | 20 | import akka.util.{ByteString, ByteIterator} 21 | import scala.collection.immutable.Queue 22 | import scala.concurrent.{ExecutionContext, Future} 23 | import scalaz.Free._ 24 | import scalaz.syntax.monad._ 25 | import scalaz.syntax.std.boolean._ 26 | import scalaz.syntax.std.list._ 27 | import scalaz.{EphemeralStream, OptionT, State, StateT, Trampoline} 28 | 29 | @MessageMacro case class HeaderSyncData(blockHeader: BlockHeader, height: Int, pow: BigInt) 30 | 31 | trait SyncDataProvider { 32 | def getHeaders(locators: List[Hash]): Future[List[BlockHeader]] 33 | def downloadBlocks(hashes: List[HeaderSyncData]): Future[Unit] 34 | } 35 | 36 | trait SyncPersist { 37 | def saveBlockchain(chain: List[HeaderSyncData]) 38 | def loadBlockchain(): Blockchain 39 | def setMainTip(newTip: Hash) 40 | def getHeaderSync(hash: Hash): Option[HeaderSyncData] 41 | } 42 | 43 | case class ForkHasLessPOW(message: String) extends Exception(message) 44 | trait Sync { self: SyncPersist => 45 | private val log = LoggerFactory.getLogger(getClass) 46 | implicit var blockchain: Blockchain = null 47 | implicit val ec: ExecutionContext 48 | implicit val db: UTXODb 49 | implicit val blockStore: BlockStore 50 | 51 | def synchronize(provider: SyncDataProvider): Future[Boolean] = { 52 | val f = for { 53 | headers <- provider.getHeaders(blockchain.chainRev.take(10).map(_.blockHeader.hash)) 54 | headersSyncData = attachHeaders(headers) 55 | _ <- Future.successful(()) if !headersSyncData.isEmpty 56 | _ <- isBetter(headersSyncData) 57 | (goodHeaders, badHeaders) = validateHeaders(headersSyncData) 58 | _ <- isBetter(goodHeaders) 59 | _ <- provider.downloadBlocks(goodHeaders.tail) 60 | (goodBlocks, badBlocks, undo) = validateBlocks(goodHeaders) 61 | _ <- isBetter(goodBlocks).transform(identity, t => { rollback(undo); t }) 62 | } yield { 63 | updateMain(goodBlocks) 64 | true 65 | } 66 | 67 | f recover { 68 | case _: java.util.NoSuchElementException => false 69 | case _: ForkHasLessPOW => false 70 | } 71 | } 72 | 73 | private def rollback(undo: List[UTXOOperation]) = undo.foreach(_.undo(db)) 74 | 75 | private def isBetter(chain: List[HeaderSyncData]): Future[Unit] = { 76 | val currentPOW = blockchain.chainRev.head.pow 77 | val newPOW = chain.last.pow 78 | if (newPOW > currentPOW) 79 | Future.successful(()) 80 | else 81 | Future.failed(new ForkHasLessPOW(s"New POW = ${newPOW}, Current POW = ${currentPOW}")) 82 | } 83 | 84 | private def attachHeaders(headers: List[BlockHeader]): List[HeaderSyncData] = { 85 | headers match { 86 | case Nil => Nil 87 | case head :: _ => 88 | val anchor = blockchain.chainRev.find(sd => sd.blockHeader.hash.sameElements(head.prevHash)) 89 | anchor map { anchor => 90 | headers.scanLeft(anchor) { case (prev, h) => 91 | HeaderSyncData(h, prev.height+1, prev.pow+h.pow) 92 | } 93 | } getOrElse Nil 94 | } 95 | } 96 | 97 | case class HeaderCheckData(height: Int, prevHash: Hash, timestamps: Queue[Long], prevBits: Int, now2h: Long, prevTimestamp: Long, elapsedSinceAdjustment: Long) { 98 | override def toString() = s"HeaderCheckData(${height},${hashToString(prevHash)},${timestamps},${prevBits},${now2h},${elapsedSinceAdjustment}})" 99 | } 100 | object HeaderCheckData { 101 | def now2h() = Instant.now.plus(2, ChronoUnit.HOURS).getEpochSecond 102 | def apply(initial: HeaderSyncData, blockchain: Blockchain): HeaderCheckData = { 103 | val previousTimestamps = Queue.empty[Long] ++ 104 | blockchain.chainRev.dropWhile(sd => !sd.blockHeader.hash.sameElements(initial.blockHeader.hash)).take(11).map(_.blockHeader.timestamp.getEpochSecond).padTo(11, 0L).reverse 105 | val height = initial.height 106 | val timestamp = initial.blockHeader.timestamp.getEpochSecond 107 | val timestampAtAdjustment = blockchain.chainRev.filter(_.height % Consensus.difficultyAdjustmentInterval == 0).head.blockHeader.timestamp.getEpochSecond 108 | val elapsedSinceAdjustment = timestamp-timestampAtAdjustment 109 | new HeaderCheckData(height, initial.blockHeader.hash, previousTimestamps, initial.blockHeader.bits, now2h(), timestamp, elapsedSinceAdjustment) 110 | } 111 | } 112 | 113 | def checkHeader(header: HeaderSyncData) = StateT[Trampoline, HeaderCheckData, Boolean] { checkData => 114 | val height = checkData.height+1 115 | val blockHeader = header.blockHeader 116 | val prevHashMatch = checkData.prevHash.sameElements(header.blockHeader.prevHash) 117 | val median = checkData.timestamps.sorted.apply(5) 118 | val timestamp = blockHeader.timestamp.getEpochSecond 119 | val timestampMatch = median < timestamp && timestamp < checkData.now2h 120 | 121 | var updatedCheckData = checkData copy (height = height, prevHash = blockHeader.hash, 122 | timestamps = checkData.timestamps.tail :+ timestamp, prevBits = blockHeader.bits, 123 | elapsedSinceAdjustment = checkData.elapsedSinceAdjustment+(timestamp-checkData.prevTimestamp), 124 | prevTimestamp = timestamp) 125 | 126 | val hashMatch = BigInt(1, blockHeader.hash.reverse) < blockHeader.target 127 | val versionMatch = true // blockHeader.version <= 4 // TODO T83 128 | val bitsMatch = if (height >= Blockchain.firstDifficultyAdjustment && height % difficultyAdjustmentInterval == 0) { 129 | updatedCheckData = updatedCheckData copy (elapsedSinceAdjustment = 0L) 130 | blockHeader.bits == adjustedDifficulty(blockHeader, BlockHeader.getTarget(checkData.prevBits), checkData.elapsedSinceAdjustment) 131 | } 132 | else (blockHeader.bits == checkData.prevBits) 133 | 134 | val result = prevHashMatch && timestampMatch && hashMatch && versionMatch && bitsMatch 135 | if (!result) { 136 | log.info(s"${checkData}") 137 | log.info(s"Block timestamp ${timestampToString(timestamp)}(${timestamp}}) must be between ${timestampToString(median)}(${median}}) and ${timestampToString(checkData.now2h)}") 138 | log.info(s"Result ${result} (${prevHashMatch} ${timestampMatch} ${hashMatch} ${versionMatch} ${bitsMatch})") 139 | } 140 | 141 | Trampoline.done(updatedCheckData, result) 142 | } 143 | 144 | type HeaderCheckStateTrampoline[T] = StateT[Trampoline, HeaderCheckData, T] 145 | private def validateHeaders(headers: List[HeaderSyncData])(implicit blockchain: Blockchain): (List[HeaderSyncData], List[HeaderSyncData]) = { 146 | log.info(s"+ validateHeaders ${headers}") 147 | assert(!headers.isEmpty) 148 | val anchor = headers.head 149 | val r = headers.tail.spanM[HeaderCheckStateTrampoline](checkHeader).eval(HeaderCheckData(anchor, blockchain)).run 150 | val r2 = (anchor :: r._1, r._2) 151 | log.debug(s"-validateHeaders ${r2}") 152 | r2 153 | } 154 | 155 | case class BlockCheckData(txLog: List[UTXOOperation]) 156 | object BlockCheckData { 157 | def apply(initial: HeaderSyncData, currentChainRev: List[HeaderSyncData]) = { 158 | val undoList = currentChainRev 159 | .takeWhile(sd => !sd.blockHeader.hash.sameElements(initial.blockHeader.hash)) 160 | .map(sd => UTXOBlockOperation(sd.blockHeader.hash, sd.height, isUndo = true)) 161 | undoList.foreach(_.run(db)) 162 | new BlockCheckData(undoList.reverse) 163 | } 164 | } 165 | type BlockCheckStateTrampoline[T] = StateT[Trampoline, BlockCheckData, T] 166 | type BlockCheckState[T] = State[BlockCheckData, T] 167 | type BlockCheckStateOption[T] = OptionT[BlockCheckState, T] 168 | 169 | private def validateBlocks(headers: List[HeaderSyncData]): (List[HeaderSyncData], List[HeaderSyncData], List[UTXOOperation]) = { 170 | log.info(s"+ validateBlocks ${headers}") 171 | val anchor = headers.head 172 | val (blockCheckData, r) = headers.tail.spanM[BlockCheckStateTrampoline](checkBlockData).run(BlockCheckData(anchor, blockchain.chainRev)).run 173 | log.debug(s"-validateBlocks ${r}") 174 | (anchor :: r._1, r._2, blockCheckData.txLog) 175 | } 176 | 177 | private def checkBlockData(header: HeaderSyncData): StateT[Trampoline, BlockCheckData, Boolean] = { 178 | StateT[Trampoline, BlockCheckData, Boolean] { checkData => 179 | log.info(s"++ checkBlockData ${header}") 180 | val block = blockStore.loadBlock(header.blockHeader.hash, header.height) 181 | val r = for { 182 | _ <- checkBlock(block) 183 | _ <- checkBlockContents(block, header.height) 184 | } yield () 185 | val (updatedCheckData, result) = r.run(checkData) 186 | Trampoline.done((updatedCheckData, result.isDefined)) 187 | } 188 | } 189 | 190 | private def checkBlock(block: Block): BlockCheckStateOption[Unit] = { 191 | val r = for { 192 | _ <- checkSize(block) 193 | _ <- (block.txs.length > 0).option(()) 194 | txHashesArray = block.txs.map(tx => hashToString(tx.hash)).toList 195 | txHashesSet = txHashesArray.toSet 196 | _ <- (txHashesSet.size == block.txs.size).option(()) 197 | _ <- checkMerkleRoot(block) 198 | } yield () 199 | 200 | val x = r.point[BlockCheckState] 201 | OptionT.optionT(x) 202 | } 203 | 204 | private def checkBlockContents(block: Block, height: Int) = { 205 | val x: BlockCheckState[Option[Unit]] = State[BlockCheckData, Option[Unit]] { checkData => 206 | val utxoDb = new InMemUTXODb(db) 207 | 208 | val feesAndSighashCheckCount = block.txs.zipWithIndex.map { case (tx, i) => 209 | val fee = Consensus.checkTx(tx, i, height, block.header.timestamp.getEpochSecond, utxoDb) 210 | fee foreach { _ => 211 | val utxoEntries = UTXO.ofTx(tx, i == 0, height) 212 | utxoEntries.foreach(utxoDb.add) 213 | } 214 | fee 215 | } 216 | 217 | val r = for { 218 | _ <- (feesAndSighashCheckCount.forall(_.isDefined)).option(()) 219 | (totalFees, totalSighashCheckCount) = feesAndSighashCheckCount.map(_.get).reduceLeft { (a, b) => (a._1 + b._1, a._2 + b._2) } 220 | _ = log.debug(s"Sighash check count = ${totalSighashCheckCount}") 221 | _ <- (totalSighashCheckCount <= Consensus.maxSighashCheckCount).option(()) 222 | _ <- Consensus.checkCoinbase(block.txs(0), height, totalFees) 223 | } yield () 224 | 225 | if (r.isDefined) { 226 | val op = updateUTXODb(block, height) 227 | (checkData copy (txLog = op :: checkData.txLog), r) 228 | } 229 | else (checkData, r) 230 | } 231 | 232 | OptionT.optionT(x) 233 | } 234 | 235 | private def updateMain(chain: List[HeaderSyncData]) = { 236 | chain match { 237 | case anchor :: newChain => 238 | val chainRev = newChain.reverse ++ blockchain.chainRev.drop(blockchain.currentTip.height-anchor.height) 239 | blockchain = blockchain copy (chainRev = chainRev.take(Consensus.difficultyAdjustmentInterval)) 240 | saveBlockchain(newChain) 241 | setMainTip(blockchain.currentTip.blockHeader.hash) 242 | log.info(s"Height = ${blockchain.currentTip.height}, POW = ${blockchain.currentTip.pow}") 243 | case _ => assert(false) 244 | } 245 | } 246 | 247 | private def updateUTXODb(block: Block, height: Int): UTXOBlockOperation = { 248 | UTXOBlockOperation.buildAndSaveUndoBlock(db, block, height) 249 | val op = UTXOBlockOperation(block.header.hash, height, false) 250 | op.run(db) 251 | op 252 | } 253 | } 254 | 255 | trait SyncPersistDb extends SyncPersist { 256 | private val log = LoggerFactory.getLogger(getClass) 257 | val appSettings: AppSettingsImpl 258 | 259 | val options = new Options() 260 | options.createIfMissing(true) 261 | val dbDir = new File(s"${appSettings.baseDir}/blockchain") 262 | dbDir.mkdirs() 263 | private val db = JniDBFactory.factory.open(dbDir, options) 264 | 265 | def saveBlockchain(chain: List[HeaderSyncData]) = { 266 | for { h <- chain } { 267 | val k = h.blockHeader.hash 268 | val v = h.toByteString().toArray 269 | db.put(k, v) 270 | } 271 | } 272 | 273 | def setMainTip(newTip: Hash) = db.put(Array.empty, newTip) 274 | 275 | def getHeaderSync(hash: Hash): Option[HeaderSyncData] = Option(db.get(hash)).map(v => HeaderSyncData.parse(ByteString(v))) 276 | 277 | def loadBlockchain(): Blockchain = { 278 | val main = Option(db.get(Array.empty)) 279 | val chainRev = 280 | main match { 281 | case Some(hash) => 282 | EphemeralStream.unfold(hash)(h => getHeaderSync(h).map(hsd => (hsd, hsd.blockHeader.prevHash))).take(Consensus.difficultyAdjustmentInterval).toList 283 | case None => 284 | val genesisChain = List(HeaderSyncData(Blockchain.genesisBlockHeader, 0, Blockchain.genesisBlockHeader.pow)) 285 | saveBlockchain(genesisChain) 286 | setMainTip(Blockchain.genesisHash.array) 287 | genesisChain 288 | } 289 | Blockchain(chainRev) 290 | } 291 | } 292 | 293 | class BlockStore(settings: AppSettingsImpl) { 294 | val log = LoggerFactory.getLogger(getClass) 295 | 296 | def getBlockPath(hash: Hash, height: Int, isUndo: Boolean = false): Path = { 297 | val baseDir = settings.blockBaseDir 298 | val hashString = hashToString(hash) 299 | val suffix = if (isUndo) ".undo" else "" 300 | val prefix = height / 1000 301 | Paths.get(baseDir, "blocks", prefix.toString, height.toString, hashString+suffix) 302 | } 303 | 304 | def haveBlock(hash: Hash, height: Int): Boolean = { 305 | val path = getBlockPath(hash, height) 306 | Files.exists(path) 307 | } 308 | 309 | def saveBlock(block: Block, height: Int) = { 310 | val path = getBlockPath(block.header.hash, height) 311 | path.toFile.getParentFile.mkdirs() 312 | managed(new FileOutputStream(path.toFile).getChannel).foreach { _.write(block.payload.toByteBuffer) } 313 | } 314 | 315 | def loadBlockBytes(hash: Hash, height: Int): Option[Array[Byte]] = { 316 | val path = getBlockPath(hash, height) 317 | Files.exists(path).option(()).map { _ => 318 | managed(new FileInputStream(path.toFile).getChannel).acquireAndGet { channel => 319 | val bb = ByteBuffer.allocate(channel.size.toInt) 320 | channel.read(bb) 321 | channel.close() 322 | bb.array() 323 | } 324 | } 325 | } 326 | 327 | def loadBlockOpt(hash: Hash, height: Int) = loadBlockBytes(hash, height).map(b => Block.parse(ByteString(b))) 328 | def loadBlock(hash: Hash, height: Int) = loadBlockOpt(hash, height).get 329 | 330 | def saveUndoBlock(hash: Hash, height: Int, undoList: UTXOEntryList): Unit = { 331 | val path = getBlockPath(hash, height, true) 332 | path.toFile.getParentFile.mkdirs() 333 | managed(new FileOutputStream(path.toFile).getChannel).foreach { channel => 334 | val bb = new ByteStringBuilder 335 | bb.putInt(undoList.size) 336 | undoList.foreach { entry => 337 | log.debug(s"Undo Write> ${entry.key}") 338 | bb.append(entry.key.toByteString()) 339 | bb.putByte(if (entry.value.isDefined) 1.toByte else 0.toByte) 340 | entry.value.foreach { v => bb.append(v.toByteString()) } 341 | } 342 | channel.write(bb.result().toByteBuffer) 343 | } 344 | } 345 | 346 | def loadUndoBlock(hash: Hash, height: Int): UTXOEntryList = { 347 | val path = getBlockPath(hash, height, true) 348 | managed(new FileInputStream(path.toFile).getChannel).acquireAndGet { channel => 349 | val bb = ByteBuffer.allocate(channel.size.toInt) 350 | channel.read(bb) 351 | channel.close() 352 | bb.flip() 353 | val bi = ByteString(bb).iterator 354 | val size = bi.getInt 355 | List.range(0, size).map { _ => 356 | val key = OutPoint.parseBI(bi) 357 | log.debug(s"Undo Read> ${key}") 358 | val hasValue = bi.getByte 359 | val value = 360 | if (hasValue != 0.toByte) 361 | Some(UTxOut.parse(bi)) 362 | else 363 | None 364 | UTXOEntry(key, value) 365 | } 366 | } 367 | } 368 | } 369 | -------------------------------------------------------------------------------- /src/main/scala/org/bitcoinakka/PeerManager.scala: -------------------------------------------------------------------------------- 1 | package org.bitcoinakka 2 | 3 | import java.net.{InetAddress, InetSocketAddress} 4 | import java.sql.DriverManager 5 | import java.time.Instant 6 | import java.time.temporal.ChronoUnit 7 | import java.util.concurrent.TimeoutException 8 | 9 | import akka.actor._ 10 | import akka.event.LoggingReceive 11 | import akka.io.Tcp._ 12 | import akka.io.{IO, Tcp} 13 | import akka.stream.{ActorMaterializer, OverflowStrategy} 14 | import akka.stream.scaladsl._ 15 | import akka.util.{ByteString, Timeout} 16 | import com.typesafe.config.Config 17 | import org.apache.commons.codec.binary.Hex 18 | import org.bitcoinakka.BitcoinMessage._ 19 | import org.slf4j.LoggerFactory 20 | 21 | import scala.collection.JavaConversions._ 22 | import scala.collection.immutable.Queue 23 | import scala.concurrent.duration._ 24 | import scala.concurrent.{Future, Promise} 25 | import scala.util.{Random, Success} 26 | 27 | object PeerState extends Enumeration { 28 | val Connecting, Connected, Ready = Value 29 | } 30 | 31 | class Peer(connection: ActorRef, local: InetSocketAddress, remote: InetSocketAddress, myHeight: Int) extends FSM[Peer.State, Peer.Data] with ActorLogging { 32 | import Peer._ 33 | 34 | implicit val ec = context.dispatcher 35 | val messageHandler = context.actorOf(Props(new MessageHandlerActor(connection))) 36 | 37 | var invs = List.empty[InvEntry] 38 | val invBroadcastCancel = context.system.scheduler.schedule(invBroadcast, invBroadcast, self, InvBroadcast) 39 | val pingCancel = context.system.scheduler.schedule(heartbeat, heartbeat, self, new Ping(Random.nextLong())) 40 | 41 | startWith(Initial, HandshakeData(None, false)) 42 | 43 | setTimer("handshake", StateTimeout, timeout, false) 44 | val now = Instant.now 45 | messageHandler ! Version(BitcoinMessage.version, 1L, now.getEpochSecond, remote, local, Random.nextLong(), "/Bitcoin-akka/", myHeight) 46 | 47 | when(Initial) { 48 | case Event(v: Version, d: HandshakeData) => 49 | log.info(s"Connection from ${v.userAgent}") 50 | messageHandler ! Verack() 51 | checkHandshakeFinished(d copy (height = Some(v.height))) 52 | 53 | case Event(Verack(), d: HandshakeData) => 54 | checkHandshakeFinished(d copy (ackReceived = true)) 55 | } 56 | 57 | when(Ready) { 58 | case Event(gh: GetHeaders, _) => 59 | log.info(s"Peer received request for headers ${hashToString(gh.hashes.head)}") 60 | messageHandler ! gh 61 | setTimer("getheaders", StateTimeout, timeout, false) 62 | stay using ReplyToData(sender) 63 | 64 | case Event(gb: GetBlocks, _) => 65 | val gbMessage = GetBlockData(gb.hsd.map(_.blockHeader.hash)).toGetData() 66 | messageHandler ! gbMessage 67 | log.info(s"Peer received request to download ${gb.hsd.map(hsd => hashToString(hsd.blockHeader.hash))}") 68 | setTimer("getblocks", StateTimeout, timeout, false) 69 | stay using ReplyToData(sender) 70 | 71 | case Event(headers: Headers, ReplyToData(s)) => 72 | log.info(s"Headers received (${headers.blockHeaders.length})") 73 | cancelTimer("getheaders") 74 | s ! headers 75 | stay 76 | 77 | case Event(block: Block, ReplyToData(s)) => 78 | log.info(s"Block received ${hashToString(block.header.hash)}") 79 | s ! block 80 | setTimer("getblocks", StateTimeout, timeout, false) 81 | stay 82 | 83 | case Event(GetBlocksFinished, _) => 84 | cancelTimer("getblocks") 85 | stay 86 | } 87 | 88 | whenUnhandled { 89 | case Event(ping: Ping, _) => 90 | messageHandler ! new Pong(ping.nonce) 91 | stay 92 | 93 | case Event(IncomingMessage(m), _) => 94 | context.parent ! m 95 | stay 96 | 97 | case Event(OutgoingMessage(m), _) => 98 | messageHandler ! m 99 | stay 100 | 101 | case Event(_: ConnectionClosed, _) => 102 | log.debug("Peer disconnected") 103 | context stop self 104 | stay 105 | 106 | case Event(StateTimeout, _) => 107 | log.debug("Peer timeout") 108 | context stop self 109 | stay 110 | 111 | case Event(GetTx(hash), _) => 112 | messageHandler ! GetTxData(List(hash)).toGetData() 113 | stay 114 | 115 | case Event(invEntry: InvEntry, _) => 116 | invs ::= invEntry 117 | stay 118 | 119 | case Event(InvBroadcast, _) => 120 | messageHandler ! Inv(invs) 121 | invs = Nil 122 | stay 123 | 124 | case Event(bm: BitcoinMessage, _) => 125 | log.info(s"Received ${bm}") 126 | stay 127 | } 128 | 129 | onTransition { 130 | case Initial -> Ready => 131 | log.debug("Handshake done") 132 | cancelTimer("handshake") 133 | messageHandler ! GetAddr() 134 | context.parent ! Handshaked(nextStateData.asInstanceOf[HandshakeData].height.get) 135 | } 136 | 137 | initialize() 138 | 139 | private def checkHandshakeFinished(d: HandshakeData) = d match { 140 | case HandshakeData(Some(height), true) => 141 | goto(Ready) 142 | case _ => 143 | stay using d 144 | } 145 | 146 | override def postStop() = { 147 | cancelTimer("handshake") 148 | cancelTimer("getheaders") 149 | cancelTimer("getblocks") 150 | invBroadcastCancel.cancel() 151 | pingCancel.cancel() 152 | } 153 | } 154 | 155 | object Peer { 156 | val timeout = 5.second 157 | val invBroadcast = 1.second 158 | val heartbeat = 5.minute 159 | 160 | case class GetBlocks(hsd: List[HeaderSyncData]) 161 | case class Handshaked(height: Int) 162 | case object GetBlocksFinished 163 | 164 | trait State 165 | object Initial extends State 166 | object Ready extends State 167 | 168 | trait Data 169 | case class HandshakeData(height: Option[Int], ackReceived: Boolean) extends Data 170 | case class ReplyToData(replyTo: ActorRef) extends Data 171 | 172 | case class GetTx(hash: Hash) 173 | case object InvBroadcast 174 | } 175 | 176 | case class PeerAddress(timestamp: Instant, address: InetSocketAddress, used: Boolean) 177 | 178 | class PeerManager(val appSettings: AppSettingsImpl) extends Actor with Sync with SyncPersistDb with Stash with ActorLogging { 179 | import Peer._ 180 | import PeerManager._ 181 | import context.system 182 | implicit val timeout = Timeout(1.hour) 183 | implicit val ec = context.dispatcher 184 | 185 | val db = new LevelDbUTXO(appSettings) 186 | 187 | blockchain = loadBlockchain() 188 | 189 | var peerId = 0 190 | var peersAvailable = Map.empty[String, PeerAddress] 191 | var peerStates = Map.empty[Int, PeerState.Value] 192 | var peersConnecting = Map.empty[String, Int] 193 | var peersConnected = Map.empty[ActorRef, Int] 194 | 195 | implicit val blockStore = new BlockStore(settings) 196 | val downloadManager = new DownloadManager(context) 197 | val txPool = new TxPool(db, blockchain.chainRev.head) 198 | 199 | IO(Tcp) ! Bind(self, new InetSocketAddress("0.0.0.0", settings.incomingPort)) 200 | 201 | def receive: Receive = ({ 202 | case sfp @ SyncFromPeer(peer, hash) => 203 | if (peersConnected.contains(peer) && getHeaderSync(hash).isEmpty) { 204 | context.become(syncingReceive(peer) orElse commonReceive, discardOld = false) 205 | synchronize(downloadManager.create(peer)).onComplete { 206 | case Success(true) => 207 | self ! sfp 208 | self ! SyncCompleted 209 | case _ => 210 | self ! SyncCompleted 211 | } 212 | } 213 | }: Receive) orElse commonReceive 214 | 215 | def syncingReceive(peer: ActorRef): Receive = { 216 | case _: SyncFromPeer => stash() 217 | case SyncCompleted => 218 | txPool.setTip(blockchain.chainRev.head) 219 | unstashAll() 220 | context.unbecome() 221 | } 222 | 223 | case class ConfirmedTx(tx: Tx) 224 | val x = Source.actorRef[Tx](100, OverflowStrategy.dropHead) 225 | val y = Sink.actorRef(self, ()) 226 | val verifyTx = Flow[Tx].collect(Function.unlift(tx => txPool.checkUnconfirmedTx(tx))).map(tx => ConfirmedTx(tx)) 227 | val v: Flow[Tx, ConfirmedTx, Unit] = Flow() { implicit builder => 228 | import FlowGraph.Implicits._ 229 | val nThreads = settings.nCores 230 | val dispatchTx = builder.add(Balance[Tx](nThreads)) 231 | val mergeTx = builder.add(Merge[ConfirmedTx](nThreads)) 232 | for (i <- 0 until nThreads) { 233 | dispatchTx.out(i) ~> verifyTx ~> mergeTx.in(i) 234 | } 235 | (dispatchTx.in, mergeTx.out) 236 | } 237 | implicit val materializer = ActorMaterializer()(context) 238 | val f = x.via(v).to(y).run() 239 | 240 | val commonReceive: Receive = LoggingReceive { 241 | case inv: Inv => 242 | val peer = sender 243 | for { 244 | hash <- invBlocks(inv) if getHeaderSync(hash).isEmpty 245 | } self ! SyncFromPeer(peer, hash) 246 | for { 247 | hash <- invTxs(inv) 248 | } txPool.doRequestTx(hash, peer) 249 | 250 | case tx: Tx => f ! tx 251 | 252 | case ConfirmedTx(tx) => 253 | txPool.addUnconfirmedTx(tx) 254 | peersConnected.keys foreach (_ ! InvEntry(1, tx.hash)) 255 | 256 | case GetHeaders(_, hashes, _) => 257 | hashes.map { h => blockchain.chain.dropWhile(!_.blockHeader.hash.sameElements(h)) }.find(!_.isEmpty).foreach { headers => 258 | sender ! OutgoingMessage(Headers(headers.tail.map(_.blockHeader))) 259 | } 260 | 261 | case GetTxData(hashes) => 262 | for { 263 | hash <- hashes 264 | txOpt <- txPool.pool.get(new WHash(hash)) 265 | tx <- txOpt 266 | } sender ! OutgoingMessage(tx) 267 | 268 | case GetBlockData(hashes) => 269 | for { 270 | hash <- hashes 271 | hsd <- blockchain.chainRev.find(hsd => hsd.blockHeader.hash.sameElements(hash)) 272 | block <- blockStore.loadBlockOpt(hash, hsd.height) 273 | } sender ! OutgoingMessage(block) 274 | 275 | case addr: Addr => 276 | addr.addrs.foreach(addr => self ! AddPeer(PeerAddress(addr.timestamp, addr.addr, false))) 277 | 278 | case ConnectToPeer(peerAddress) => 279 | IO(Tcp) ! Tcp.Connect(peerAddress) 280 | 281 | case AddPeer(peerAddress) => 282 | val p = peersAvailable.getOrElse(peerAddress.address.getHostString, peerAddress) 283 | peersAvailable = peersAvailable.updated(peerAddress.address.getHostString, p copy (timestamp = peerAddress.timestamp)) 284 | 285 | case Start => 286 | tryConnect() 287 | 288 | case CommandFailed(c: Connect) => 289 | log.debug(s"Connection to ${c.remoteAddress} failed") 290 | val peerHostString = c.remoteAddress.getHostString 291 | val peerId = peersConnecting(peerHostString) 292 | peersConnecting -= peerHostString 293 | peerStates -= peerId 294 | tryConnect() 295 | 296 | case Connected(remote, local) => 297 | log.debug(s"Connected to ${remote}") 298 | val connection = sender 299 | val peerHostString = remote.getHostString 300 | val peerId = peersConnecting.getOrElse(peerHostString, allocateNewId()) 301 | val peer = context.actorOf(Props(new Peer(connection, local, remote, blockchain.currentTip.height))) 302 | context watch peer 303 | peersConnecting -= peerHostString 304 | peersConnected += peer -> peerId 305 | peerStates = peerStates.updated(peerId, PeerState.Connected) 306 | 307 | case Handshaked(height) => 308 | val peer = sender 309 | val peerId = peersConnected(peer) 310 | peerStates = peerStates.updated(peerId, PeerState.Ready) 311 | log.debug(s"Handshake from ${peer}") 312 | downloadManager.addPeer(peer) 313 | if (height > blockchain.chainRev.head.height) 314 | self ! SyncFromPeer(peer, zeroHash) 315 | 316 | case Terminated(peer) => 317 | val peerId = peersConnected(peer) 318 | peersConnected -= peer 319 | peerStates -= peerId 320 | tryConnect() 321 | } 322 | 323 | private def peerCount(state: PeerState.Value) = peerStates.values.count(_ == state) 324 | private def tryConnect() = { 325 | val cutoff = Instant.now.minus(3, ChronoUnit.HOURS) 326 | peersAvailable = peersAvailable.filter { case (_, v) => v.timestamp.isAfter(cutoff) } 327 | 328 | val targetConnectCount = settings.targetConnectCount 329 | val c = peerStates.size 330 | log.debug(s"Peers available = ${peersAvailable.values.filter(!_.used).size}") 331 | log.debug(s"Peers count = ${c} (connecting = ${peerCount(PeerState.Connecting)}, not handshaked = ${peerCount(PeerState.Connected)})") 332 | if (c < targetConnectCount) { 333 | val ps = peersAvailable.values.filter(p => !p.used).toList.take(targetConnectCount-c) 334 | for { p <- ps } { 335 | peersAvailable = peersAvailable.updated(p.address.getHostString, p copy (used = true)) 336 | peersConnecting += p.address.getHostString -> allocateNewId() 337 | peerStates += peerId -> PeerState.Connecting 338 | self ! ConnectToPeer(p.address) 339 | } 340 | } 341 | } 342 | 343 | private def allocateNewId() = { 344 | val pid = peerId 345 | peerId += 1 346 | pid 347 | } 348 | 349 | private def invBlocks(inv: Inv): List[Hash] = inv.invs.filter(_.tpe == 2).map(_.hash) 350 | private def invTxs(inv: Inv): List[Hash] = inv.invs.filter(_.tpe == 1).map(_.hash) 351 | } 352 | 353 | object PeerManager extends App { 354 | case class ConnectToPeer(peerAddress: InetSocketAddress) 355 | case class AddPeer(address: PeerAddress) 356 | case object Start 357 | case class SyncFromPeer(peer: ActorRef, hash: Hash) 358 | case object SyncCompleted 359 | 360 | val log = LoggerFactory.getLogger(getClass) 361 | 362 | System.loadLibrary("consensus-jni") 363 | 364 | implicit val system = ActorSystem() 365 | implicit val settings = AppSettings(system) 366 | 367 | log.info("Hello, welcome to Bitcoin-akka") 368 | 369 | val peerManager = system.actorOf(Props(new PeerManager(settings)), "peermanager") 370 | 371 | loadFromDNSSeed() foreach (peerManager ! AddPeer(_)) 372 | peerManager ! Start 373 | 374 | private def loadFromDNSSeed(): Array[PeerAddress] = { 375 | for { address <- InetAddress.getAllByName("seed.bitnodes.io") } yield { 376 | val a = InetAddress.getByAddress(address.getAddress) 377 | val peerAddress = new InetSocketAddress(a, 8333) 378 | PeerAddress(Instant.now, peerAddress, false) 379 | } 380 | } 381 | } 382 | 383 | case object Ack extends Event 384 | class MessageHandlerActor(connection: ActorRef) extends Actor with MessageHandler with ActorLogging { 385 | connection ! Tcp.Register(self) 386 | def receive = { 387 | case Tcp.Received(data) => frame(data).flatMap(parse).foreach(context.parent ! _) 388 | case bm: BitcoinMessage => sendMessage(bm) 389 | case Ack => acknowledge() 390 | case other => context.parent ! other 391 | } 392 | 393 | private def parse(mh: MessageHeader): List[BitcoinMessage] = { 394 | mh.command match { 395 | case "version" => List(Version.parse(mh.payload)) 396 | case "verack" => List(Verack()) 397 | case "headers" => List(Headers.parse(mh.payload)) 398 | case "block" => List(Block.parse(mh.payload)) 399 | case "inv" => List(IncomingMessage(Inv.parse(mh.payload))) 400 | case "addr" => List(IncomingMessage(Addr.parse(mh.payload))) 401 | case "tx" => List(IncomingMessage(Tx.parseBI(mh.payload.iterator))) 402 | case "getheaders" => List(IncomingMessage(GetHeaders.parse(mh.payload))) 403 | case "getdata" => 404 | val gd = GetData.parse(mh.payload) 405 | val (getTxData, getBlockData) = (new GetTxData(gd.invs.filter(_.tpe == 1).map(_.hash)), new GetBlockData(gd.invs.filter(_.tpe == 2).map(_.hash))) 406 | List(getTxData, getBlockData).filter(!_.hashes.isEmpty).map(IncomingMessage(_)) 407 | case "ping" => List(Ping.parse(mh.payload)) 408 | case _ => Nil 409 | } 410 | } 411 | 412 | var acknowledged = true 413 | var writeBuffer = Queue.empty[ByteString] 414 | private def sendMessage(bm: BitcoinMessage) = { 415 | bm match { 416 | case block: Block => log.info(s"block ${hashToString(block.header.hash)}") 417 | case _ => 418 | } 419 | val bytes = bm.toMessage() 420 | writeBuffer :+= bytes 421 | if (acknowledged) { 422 | connection ! Tcp.Write(bytes, Ack) 423 | acknowledged = false 424 | } 425 | } 426 | private def acknowledge() = { 427 | writeBuffer = writeBuffer.drop(1) 428 | if (writeBuffer.isEmpty) 429 | acknowledged = true 430 | else 431 | connection ! Tcp.Write(writeBuffer(0), Ack) 432 | } 433 | } 434 | 435 | class DownloadManager(context: ActorRefFactory)(implicit settings: AppSettingsImpl, blockStore: BlockStore) { 436 | private val log = LoggerFactory.getLogger(getClass) 437 | import DownloadManager._ 438 | 439 | import concurrent.ExecutionContext.Implicits.global 440 | 441 | def addPeer(peer: ActorRef) = { 442 | d ! AddPeer(peer) 443 | } 444 | 445 | def create(syncPeer: ActorRef) = new SyncDataProvider { 446 | override def getHeaders(locators: List[Hash]): Future[List[BlockHeader]] = internalGetHeaders(syncPeer)(locators) 447 | override def downloadBlocks(hashes: List[HeaderSyncData]): Future[Unit] = internalDownloadBlocks(hashes) 448 | } 449 | 450 | def internalGetHeaders(syncPeer: ActorRef)(locators: List[Hash]): Future[List[BlockHeader]] = { 451 | val p = Promise[Headers]() 452 | context.actorOf(Props(new Actor { 453 | context watch syncPeer 454 | syncPeer ! GetHeaders(BitcoinMessage.version, locators, zeroHash) 455 | 456 | def receive: Receive = { 457 | case headers: Headers => 458 | context unwatch syncPeer 459 | context stop self 460 | p.success(headers) 461 | case Terminated(_) => 462 | p.failure(new TimeoutException()) 463 | } 464 | })) 465 | 466 | p.future.map(headers => headers.blockHeaders) 467 | } 468 | 469 | def internalDownloadBlocks(hashes: List[HeaderSyncData]): Future[Unit] = { 470 | val notDownloadedHashes = hashes.filter(hsd => !blockStore.haveBlock(hsd.blockHeader.hash, hsd.height)) 471 | val p = Promise[Unit]() 472 | d ! GetBlocks(notDownloadedHashes, p) 473 | p.future 474 | } 475 | 476 | case class IdleState(peers: Set[ActorRef]) 477 | type IdleStateFunc = IdleState => IdleState 478 | def addIdle(peer: ActorRef): IdleStateFunc = { s => s copy (peers = s.peers + peer) } 479 | def removeIdle(peer: ActorRef): IdleStateFunc = { s => s copy (peers = s.peers - peer) } 480 | 481 | case class DownloaderState(p: Promise[Unit], tasks: List[Task], hashToHSD: Map[WHash, HeaderSyncData], workerToHashes: Map[ActorRef, Set[WHash]]) { 482 | def toStringLong() = s"(${tasks.map(t => t.map(h => hashToString(h.blockHeader.hash)))}, ${workerToHashes.map { case (a, hs) => a -> hs.map(h => hashToString(h.array))}})" 483 | override def toString() = s"${tasks.size}, ${workerToHashes.map { case (a, hs) => a -> hs.size}}" 484 | } 485 | type DownloaderStateFunc = DownloaderState => DownloaderState 486 | def add(peer: ActorRef): DownloaderStateFunc = { s => s copy (workerToHashes = s.workerToHashes.updated(peer, Set.empty)) } 487 | def checkCompleted(self: ActorRef): DownloaderStateFunc = { s => 488 | if (s.tasks.isEmpty && s.workerToHashes.forall(_._2.isEmpty)) 489 | self ! DownloadFinished 490 | s 491 | } 492 | def assignTask(self: ActorRef): DownloaderStateFunc = { s => 493 | (for { 494 | task <- s.tasks.headOption 495 | idleWorker <- s.workerToHashes.find(_._2.isEmpty).map(_._1) 496 | } yield { 497 | idleWorker.tell(Peer.GetBlocks(task), self) 498 | s copy (tasks = s.tasks.tail, workerToHashes = s.workerToHashes.updated(idleWorker, task.map(hsd => hsd.blockHeader.hash.wrapped).toSet)) 499 | }) getOrElse s 500 | } 501 | def receiveBlock(peer: ActorRef, block: Block): DownloaderStateFunc = { s => 502 | val wHash = block.header.hash.wrapped 503 | (for { 504 | hashes <- s.workerToHashes.get(peer) 505 | } yield { 506 | val height = s.hashToHSD(wHash).height 507 | blockStore.saveBlock(block, height) 508 | s copy (workerToHashes = s.workerToHashes.updated(peer, hashes - wHash)) 509 | }) getOrElse s 510 | } 511 | def completeWorker(peer: ActorRef, self: ActorRef): DownloaderStateFunc = { s => 512 | (for { 513 | hashes <- s.workerToHashes.get(peer) 514 | } yield { 515 | if (hashes.isEmpty) { 516 | peer ! Peer.GetBlocksFinished 517 | assignTask(self)(s) 518 | } 519 | else s 520 | }) getOrElse s 521 | } 522 | def failWorker(peer: ActorRef): DownloaderStateFunc = { s => 523 | (for { 524 | failedHashes <- s.workerToHashes.get(peer) 525 | } yield { 526 | val task = failedHashes.map(h => s.hashToHSD(h)).toList 527 | s copy (tasks = if (task.isEmpty) s.tasks else task :: s.tasks, workerToHashes = s.workerToHashes - peer) 528 | }) getOrElse s 529 | } 530 | 531 | val d = context.actorOf(Props(new Actor { 532 | def receive = idleReceive(IdleState(Set.empty)) 533 | def idleReceive(state: IdleState): Receive = { 534 | case GetBlocks(hsd, p) => 535 | val tasks = hsd.grouped(settings.batchSize).toList 536 | val hashToHSD = hsd.map(h => h.blockHeader.hash.wrapped -> h).toMap 537 | 538 | def internalReceive(state: DownloaderState): Receive = { 539 | log.debug(s"${state}") 540 | checkCompleted(self)(state) 541 | 542 | { 543 | case AddPeer(peer) => 544 | log.debug(s"Adding peer ${peer}") 545 | context watch peer 546 | context become internalReceive((add(peer) andThen assignTask(self))(state)) 547 | 548 | case block: Block => 549 | val peer = sender 550 | context become internalReceive((receiveBlock(peer, block) andThen completeWorker(peer, self))(state)) 551 | 552 | case Terminated(peer) => 553 | log.debug(s"Peer ${peer} terminated") 554 | context become internalReceive((failWorker(peer) andThen assignTask(self))(state)) 555 | 556 | case DownloadFinished => 557 | state.p.success(()) 558 | context become idleReceive(IdleState(state.workerToHashes.keys.toSet)) 559 | } 560 | } 561 | val downloadState = DownloaderState(p, tasks, hashToHSD, state.peers.map(p => p -> Set.empty[WHash]).toMap) 562 | val assignTaskToAll = state.peers.map(p => assignTask(self)).foldLeft(identity[DownloaderState] _)(_ andThen _) 563 | context become internalReceive(assignTaskToAll(downloadState)) 564 | 565 | case AddPeer(peer) => 566 | log.debug(s"Adding peer ${peer}") 567 | context watch peer 568 | context become idleReceive(addIdle(peer)(state)) 569 | 570 | case Terminated(peer) => 571 | log.debug(s"Peer ${peer} terminated") 572 | context become idleReceive(removeIdle(peer)(state)) 573 | } 574 | })) 575 | } 576 | object DownloadManager { 577 | case class AddPeer(peer: ActorRef) 578 | case class GetBlocks(hashes: List[HeaderSyncData], p: Promise[Unit]) 579 | case object DownloadFinished 580 | type Task = List[HeaderSyncData] 581 | } 582 | 583 | case class BlockFilesConfig(path: String, start: Int, checkpoints: List[Int]) 584 | class AppSettingsImpl(config: Config) extends Extension { 585 | val targetConnectCount = config.getInt("targetConnectCount") 586 | val batchSize = config.getInt("batchSize") 587 | val blockBaseDir = config.getString("blockBaseDir") 588 | val baseDir = config.getString("baseDir") 589 | 590 | val blockFilesConfig = config.getConfig("blockFiles") 591 | val path = blockFilesConfig.getString("path") 592 | val checkpoints = blockFilesConfig.getIntList("checkpoints") 593 | val start = blockFilesConfig.getInt("start") 594 | val saveBlocksConfig = config.getConfig("saveBlocks") 595 | val blockFiles = BlockFilesConfig(path, start, checkpoints.map(_.toInt).toList) 596 | 597 | val incomingPort = config.getInt("incomingPort") 598 | val nCores = config.getInt("nCores") 599 | } 600 | object AppSettings extends ExtensionId[AppSettingsImpl] with ExtensionIdProvider { 601 | override def lookup = AppSettings 602 | override def createExtension(system: ExtendedActorSystem) = new AppSettingsImpl(system.settings.config) 603 | } 604 | -------------------------------------------------------------------------------- /src/test/java/org/bitcoinj/core/FullBlockTestGenerator.java: -------------------------------------------------------------------------------- 1 | package org.bitcoinj.core; 2 | 3 | import com.google.common.base.Preconditions; 4 | import org.bitcoinj.core.Transaction.SigHash; 5 | import org.bitcoinj.crypto.TransactionSignature; 6 | import org.bitcoinj.script.Script; 7 | import org.bitcoinj.script.ScriptBuilder; 8 | 9 | import javax.annotation.Nullable; 10 | import java.io.ByteArrayOutputStream; 11 | import java.io.File; 12 | import java.io.FileOutputStream; 13 | import java.io.IOException; 14 | import java.math.BigInteger; 15 | import java.util.*; 16 | 17 | import static com.google.common.base.Preconditions.checkNotNull; 18 | import static com.google.common.base.Preconditions.checkState; 19 | import static org.bitcoinj.core.Coin.*; 20 | import static org.bitcoinj.script.ScriptOpCodes.*; 21 | 22 | /** 23 | * YOU ARE READING THIS CODE BECAUSE EITHER... 24 | * 25 | * a) You are testing an alternative implementation with full validation rules. If you are doing this, you should go 26 | * rethink your life. Seriously, why are you reimplementing Bitcoin consensus rules? Instead, go work on making 27 | * Bitcoin Core consensus rules a shared library and use that. Seriously, you wont get it right, and starting with 28 | * this tester as a way to try to do so will simply end in pain and lost coins. SERIOUSLY, JUST STOP! 29 | * 30 | * b) Bitcoin Core is faling some test in here and you're wondering what test is causing failure. Just stop. There is no 31 | * hope trying to read this file and decipher it. Give up and ping BlueMatt. Seriously, this stuff is a huge mess. 32 | * 33 | * c) You are trying to add a new test. STOP! WHY THE HELL WOULD YOU EVEN DO THAT? GO REWRITE THIS TESTER! 34 | * 35 | * d) You are BlueMatt and you're trying to hack more crap onto this multi-headed lopsided Proof Of Stake. Why are you 36 | * doing this? Seriously, why have you not rewritten this thing yet? WTF man... 37 | * 38 | * IN ANY CASE, STOP READING NOW. IT WILL SAVE YOU MUCH PAIN AND MISERY LATER 39 | */ 40 | 41 | class NewBlock { 42 | public Block block; 43 | private TransactionOutPointWithValue spendableOutput; 44 | public NewBlock(Block block, TransactionOutPointWithValue spendableOutput) { 45 | this.block = block; this.spendableOutput = spendableOutput; 46 | } 47 | // Wrappers to make it more block-like 48 | public Sha256Hash getHash() { return block.getHash(); } 49 | public void solve() { block.solve(); } 50 | public void addTransaction(Transaction tx) { block.addTransaction(tx); } 51 | 52 | public TransactionOutPointWithValue getCoinbaseOutput() { 53 | return new TransactionOutPointWithValue(block.getTransactions().get(0), 0); 54 | } 55 | 56 | public TransactionOutPointWithValue getSpendableOutput() { 57 | return spendableOutput; 58 | } 59 | } 60 | 61 | class TransactionOutPointWithValue { 62 | public TransactionOutPoint outpoint; 63 | public Coin value; 64 | public Script scriptPubKey; 65 | 66 | public TransactionOutPointWithValue(TransactionOutPoint outpoint, Coin value, Script scriptPubKey) { 67 | this.outpoint = outpoint; 68 | this.value = value; 69 | this.scriptPubKey = scriptPubKey; 70 | } 71 | 72 | public TransactionOutPointWithValue(Transaction tx, int output) { 73 | this(new TransactionOutPoint(tx.getParams(), output, tx.getHash()), 74 | tx.getOutput(output).getValue(), tx.getOutput(output).getScriptPubKey()); 75 | } 76 | } 77 | 78 | /** An arbitrary rule which the testing client must match */ 79 | class Rule { 80 | String ruleName; 81 | Rule(String ruleName) { 82 | this.ruleName = ruleName; 83 | } 84 | } 85 | 86 | /** 87 | * A test which checks the mempool state (ie defined which transactions should be in memory pool 88 | */ 89 | class MemoryPoolState extends Rule { 90 | Set mempool; 91 | public MemoryPoolState(Set mempool, String ruleName) { 92 | super(ruleName); 93 | this.mempool = mempool; 94 | } 95 | } 96 | 97 | class RuleList { 98 | public List list; 99 | public int maximumReorgBlockCount; 100 | Map hashHeaderMap; 101 | public RuleList(List list, Map hashHeaderMap, int maximumReorgBlockCount) { 102 | this.list = list; 103 | this.hashHeaderMap = hashHeaderMap; 104 | this.maximumReorgBlockCount = maximumReorgBlockCount; 105 | } 106 | } 107 | 108 | public class FullBlockTestGenerator { 109 | // Used by BitcoindComparisonTool and AbstractFullPrunedBlockChainTest to create test cases 110 | NetworkParameters params; 111 | private ECKey coinbaseOutKey; 112 | private byte[] coinbaseOutKeyPubKey; 113 | 114 | // Used to double-check that we are always using the right next-height 115 | Map blockToHeightMap = new HashMap(); 116 | 117 | Map hashHeaderMap = new HashMap(); 118 | Map coinbaseBlockMap = new HashMap(); 119 | 120 | public FullBlockTestGenerator(NetworkParameters params) { 121 | this.params = params; 122 | coinbaseOutKey = new ECKey(); 123 | coinbaseOutKeyPubKey = coinbaseOutKey.getPubKey(); 124 | Utils.setMockClock(); 125 | } 126 | 127 | public RuleList getBlocksToTest(boolean runBarelyExpensiveTests, boolean runExpensiveTests, File blockStorageFile) throws ScriptException, ProtocolException, IOException { 128 | final FileOutputStream outStream = blockStorageFile != null ? new FileOutputStream(blockStorageFile) : null; 129 | 130 | final Script OP_TRUE_SCRIPT = new ScriptBuilder().op(OP_TRUE).build(); 131 | final Script OP_NOP_SCRIPT = new ScriptBuilder().op(OP_NOP).build(); 132 | 133 | // TODO: Rename this variable. 134 | List blocks = new LinkedList() { 135 | @Override 136 | public boolean add(Rule element) { 137 | if (outStream != null && element instanceof BlockAndValidity) { 138 | try { 139 | outStream.write((int) (params.getPacketMagic() >>> 24)); 140 | outStream.write((int) (params.getPacketMagic() >>> 16)); 141 | outStream.write((int) (params.getPacketMagic() >>> 8)); 142 | outStream.write((int) (params.getPacketMagic() >>> 0)); 143 | byte[] block = ((BlockAndValidity) element).block.bitcoinSerialize(); 144 | byte[] length = new byte[4]; 145 | Utils.uint32ToByteArrayBE(block.length, length, 0); 146 | outStream.write(Utils.reverseBytes(length)); 147 | outStream.write(block); 148 | ((BlockAndValidity) element).block = null; 149 | } catch (IOException e) { 150 | throw new RuntimeException(e); 151 | } 152 | } 153 | return super.add(element); 154 | } 155 | }; 156 | RuleList ret = new RuleList(blocks, hashHeaderMap, 10); 157 | 158 | Queue spendableOutputs = new LinkedList(); 159 | 160 | int chainHeadHeight = 1; 161 | Block chainHead = params.getGenesisBlock().createNextBlockWithCoinbase(coinbaseOutKeyPubKey); 162 | blocks.add(new BlockAndValidity(this, chainHead, true, false, chainHead.getHash(), 1, "Initial Block")); 163 | spendableOutputs.offer(new TransactionOutPointWithValue( 164 | new TransactionOutPoint(params, 0, chainHead.getTransactions().get(0).getHash()), 165 | FIFTY_COINS, chainHead.getTransactions().get(0).getOutputs().get(0).getScriptPubKey())); 166 | for (int i = 1; i < params.getSpendableCoinbaseDepth(); i++) { 167 | chainHead = chainHead.createNextBlockWithCoinbase(coinbaseOutKeyPubKey); 168 | chainHeadHeight++; 169 | blocks.add(new BlockAndValidity(this, chainHead, true, false, chainHead.getHash(), i + 1, "Initial Block chain output generation")); 170 | spendableOutputs.offer(new TransactionOutPointWithValue( 171 | new TransactionOutPoint(params, 0, chainHead.getTransactions().get(0).getHash()), 172 | FIFTY_COINS, chainHead.getTransactions().get(0).getOutputs().get(0).getScriptPubKey())); 173 | } 174 | 175 | // Start by building a couple of blocks on top of the genesis block. 176 | NewBlock b1 = createNextBlock(chainHead, chainHeadHeight + 1, spendableOutputs.poll(), null); 177 | blocks.add(new BlockAndValidity(this, b1, true, false, b1.getHash(), chainHeadHeight + 1, "b1")); 178 | spendableOutputs.offer(b1.getCoinbaseOutput()); 179 | 180 | TransactionOutPointWithValue out1 = spendableOutputs.poll(); 181 | checkState(out1 != null); 182 | NewBlock b2 = createNextBlock(b1, chainHeadHeight + 2, out1, null); 183 | blocks.add(new BlockAndValidity(this, b2, true, false, b2.getHash(), chainHeadHeight + 2, "b2")); 184 | // Make sure nothing funky happens if we try to re-add b2 185 | blocks.add(new BlockAndValidity(this, b2, true, false, b2.getHash(), chainHeadHeight + 2, "b2")); 186 | spendableOutputs.offer(b2.getCoinbaseOutput()); 187 | // We now have the following chain (which output is spent is in parentheses): 188 | // genesis -> b1 (0) -> b2 (1) 189 | // 190 | // so fork like this: 191 | // 192 | // genesis -> b1 (0) -> b2 (1) 193 | // \-> b3 (1) 194 | // 195 | // Nothing should happen at this point. We saw b2 first so it takes priority. 196 | NewBlock b3 = createNextBlock(b1, chainHeadHeight + 2, out1, null); 197 | blocks.add(new BlockAndValidity(this, b3, true, false, b2.getHash(), chainHeadHeight + 2, "b3")); 198 | // Make sure nothing breaks if we add b3 twice 199 | blocks.add(new BlockAndValidity(this, b3, true, false, b2.getHash(), chainHeadHeight + 2, "b3")); 200 | 201 | // Now we add another block to make the alternative chain longer. 202 | // 203 | // genesis -> b1 (0) -> b2 (1) 204 | // \-> b3 (1) -> b4 (2) 205 | // 206 | TransactionOutPointWithValue out2 = checkNotNull(spendableOutputs.poll()); 207 | NewBlock b4 = createNextBlock(b3, chainHeadHeight + 3, out2, null); 208 | blocks.add(new BlockAndValidity(this, b4, true, false, b4.getHash(), chainHeadHeight + 3, "b4")); 209 | 210 | // ... and back to the first chain. 211 | NewBlock b5 = createNextBlock(b2, chainHeadHeight + 3, out2, null); 212 | blocks.add(new BlockAndValidity(this, b5, true, false, b4.getHash(), chainHeadHeight + 3, "b5")); 213 | spendableOutputs.offer(b5.getCoinbaseOutput()); 214 | 215 | TransactionOutPointWithValue out3 = spendableOutputs.poll(); 216 | 217 | NewBlock b6 = createNextBlock(b5, chainHeadHeight + 4, out3, null); 218 | blocks.add(new BlockAndValidity(this, b6, true, false, b6.getHash(), chainHeadHeight + 4, "b6")); 219 | // 220 | // genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) 221 | // \-> b3 (1) -> b4 (2) 222 | // 223 | 224 | // Try to create a fork that double-spends 225 | // genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) 226 | // \-> b7 (2) -> b8 (4) 227 | // \-> b3 (1) -> b4 (2) 228 | // 229 | NewBlock b7 = createNextBlock(b5, chainHeadHeight + 5, out2, null); 230 | blocks.add(new BlockAndValidity(this, b7, true, false, b6.getHash(), chainHeadHeight + 4, "b7")); 231 | 232 | TransactionOutPointWithValue out4 = spendableOutputs.poll(); 233 | 234 | NewBlock b8 = createNextBlock(b7, chainHeadHeight + 6, out4, null); 235 | blocks.add(new BlockAndValidity(this, b8, false, true, b6.getHash(), chainHeadHeight + 4, "b8")); 236 | 237 | // Try to create a block that has too much fee 238 | // genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) 239 | // \-> b9 (4) 240 | // \-> b3 (1) -> b4 (2) 241 | // 242 | NewBlock b9 = createNextBlock(b6, chainHeadHeight + 5, out4, SATOSHI); 243 | blocks.add(new BlockAndValidity(this, b9, false, true, b6.getHash(), chainHeadHeight + 4, "b9")); 244 | 245 | // Create a fork that ends in a block with too much fee (the one that causes the reorg) 246 | // genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) 247 | // \-> b10 (3) -> b11 (4) 248 | // \-> b3 (1) -> b4 (2) 249 | // 250 | NewBlock b10 = createNextBlock(b5, chainHeadHeight + 4, out3, null); 251 | blocks.add(new BlockAndValidity(this, b10, true, false, b6.getHash(), chainHeadHeight + 4, "b10")); 252 | 253 | NewBlock b11 = createNextBlock(b10, chainHeadHeight + 5, out4, SATOSHI); 254 | blocks.add(new BlockAndValidity(this, b11, false, true, b6.getHash(), chainHeadHeight + 4, "b11")); 255 | 256 | // Try again, but with a valid fork first 257 | // genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) 258 | // \-> b12 (3) -> b13 (4) -> b14 (5) 259 | // (b12 added last) 260 | // \-> b3 (1) -> b4 (2) 261 | // 262 | NewBlock b12 = createNextBlock(b5, chainHeadHeight + 4, out3, null); 263 | spendableOutputs.offer(b12.getCoinbaseOutput()); 264 | 265 | NewBlock b13 = createNextBlock(b12, chainHeadHeight + 5, out4, null); 266 | blocks.add(new BlockAndValidity(this, b13, false, false, b6.getHash(), chainHeadHeight + 4, "b13")); 267 | // Make sure we dont die if an orphan gets added twice 268 | blocks.add(new BlockAndValidity(this, b13, false, false, b6.getHash(), chainHeadHeight + 4, "b13")); 269 | spendableOutputs.offer(b13.getCoinbaseOutput()); 270 | 271 | TransactionOutPointWithValue out5 = spendableOutputs.poll(); 272 | 273 | NewBlock b14 = createNextBlock(b13, chainHeadHeight + 6, out5, SATOSHI); 274 | // This will be "validly" added, though its actually invalid, it will just be marked orphan 275 | // and will be discarded when an attempt is made to reorg to it. 276 | // TODO: Use a WeakReference to check that it is freed properly after the reorg 277 | blocks.add(new BlockAndValidity(this, b14, false, false, b6.getHash(), chainHeadHeight + 4, "b14")); 278 | // Make sure we dont die if an orphan gets added twice 279 | blocks.add(new BlockAndValidity(this, b14, false, false, b6.getHash(), chainHeadHeight + 4, "b14")); 280 | 281 | blocks.add(new BlockAndValidity(this, b12, false, true, b13.getHash(), chainHeadHeight + 5, "b12")); 282 | 283 | // Add a block with MAX_BLOCK_SIGOPS and one with one more sigop 284 | // genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) 285 | // \-> b12 (3) -> b13 (4) -> b15 (5) -> b16 (6) 286 | // \-> b3 (1) -> b4 (2) 287 | // 288 | NewBlock b15 = createNextBlock(b13, chainHeadHeight + 6, out5, null); 289 | { 290 | int sigOps = 0; 291 | for (Transaction tx : b15.block.getTransactions()) 292 | sigOps += tx.getSigOpCount(); 293 | Transaction tx = new Transaction(params); 294 | byte[] outputScript = new byte[Block.MAX_BLOCK_SIGOPS - sigOps]; 295 | Arrays.fill(outputScript, (byte) OP_CHECKSIG); 296 | tx.addOutput(new TransactionOutput(params, tx, SATOSHI, outputScript)); 297 | addOnlyInputToTransaction(tx, b15); 298 | b15.addTransaction(tx); 299 | 300 | sigOps = 0; 301 | for (Transaction tx2 : b15.block.getTransactions()) 302 | sigOps += tx2.getSigOpCount(); 303 | checkState(sigOps == Block.MAX_BLOCK_SIGOPS); 304 | } 305 | b15.solve(); 306 | 307 | blocks.add(new BlockAndValidity(this, b15, true, false, b15.getHash(), chainHeadHeight + 6, "b15")); 308 | spendableOutputs.offer(b15.getCoinbaseOutput()); 309 | 310 | TransactionOutPointWithValue out6 = spendableOutputs.poll(); 311 | 312 | NewBlock b16 = createNextBlock(b15, chainHeadHeight + 7, out6, null); 313 | { 314 | int sigOps = 0; 315 | for (Transaction tx : b16.block.getTransactions()) { 316 | sigOps += tx.getSigOpCount(); 317 | } 318 | Transaction tx = new Transaction(params); 319 | byte[] outputScript = new byte[Block.MAX_BLOCK_SIGOPS - sigOps + 1]; 320 | Arrays.fill(outputScript, (byte) OP_CHECKSIG); 321 | tx.addOutput(new TransactionOutput(params, tx, SATOSHI, outputScript)); 322 | addOnlyInputToTransaction(tx, b16); 323 | b16.addTransaction(tx); 324 | 325 | sigOps = 0; 326 | for (Transaction tx2 : b16.block.getTransactions()) 327 | sigOps += tx2.getSigOpCount(); 328 | checkState(sigOps == Block.MAX_BLOCK_SIGOPS + 1); 329 | } 330 | b16.solve(); 331 | 332 | blocks.add(new BlockAndValidity(this, b16, false, true, b15.getHash(), chainHeadHeight + 6, "b16")); 333 | 334 | // Attempt to spend a transaction created on a different fork 335 | // genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) 336 | // \-> b12 (3) -> b13 (4) -> b15 (5) -> b17 (6) 337 | // \-> b3 (1) -> b4 (2) 338 | // 339 | NewBlock b17 = createNextBlock(b15, chainHeadHeight + 7, out6, null); 340 | { 341 | Transaction tx = new Transaction(params); 342 | tx.addOutput(new TransactionOutput(params, tx, SATOSHI, new byte[]{})); 343 | addOnlyInputToTransaction(tx, b3); 344 | b17.addTransaction(tx); 345 | } 346 | b17.solve(); 347 | blocks.add(new BlockAndValidity(this, b17, false, true, b15.getHash(), chainHeadHeight + 6, "b17")); 348 | 349 | // Attempt to spend a transaction created on a different fork (on a fork this time) 350 | // genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) 351 | // \-> b12 (3) -> b13 (4) -> b15 (5) 352 | // \-> b18 (5) -> b19 (6) 353 | // \-> b3 (1) -> b4 (2) 354 | // 355 | NewBlock b18 = createNextBlock(b13, chainHeadHeight + 6, out5, null); 356 | { 357 | Transaction tx = new Transaction(params); 358 | tx.addOutput(new TransactionOutput(params, tx, SATOSHI, new byte[]{})); 359 | addOnlyInputToTransaction(tx, b3); 360 | b18.addTransaction(tx); 361 | } 362 | b18.solve(); 363 | blocks.add(new BlockAndValidity(this, b18, true, false, b15.getHash(), chainHeadHeight + 6, "b17")); 364 | 365 | NewBlock b19 = createNextBlock(b18, chainHeadHeight + 7, out6, null); 366 | blocks.add(new BlockAndValidity(this, b19, false, true, b15.getHash(), chainHeadHeight + 6, "b19")); 367 | 368 | // Attempt to spend a coinbase at depth too low 369 | // genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) 370 | // \-> b12 (3) -> b13 (4) -> b15 (5) -> b20 (7) 371 | // \-> b3 (1) -> b4 (2) 372 | // 373 | TransactionOutPointWithValue out7 = spendableOutputs.poll(); 374 | 375 | NewBlock b20 = createNextBlock(b15.block, chainHeadHeight + 7, out7, null); 376 | blocks.add(new BlockAndValidity(this, b20, false, true, b15.getHash(), chainHeadHeight + 6, "b20")); 377 | 378 | // Attempt to spend a coinbase at depth too low (on a fork this time) 379 | // genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) 380 | // \-> b12 (3) -> b13 (4) -> b15 (5) 381 | // \-> b21 (6) -> b22 (5) 382 | // \-> b3 (1) -> b4 (2) 383 | // 384 | NewBlock b21 = createNextBlock(b13, chainHeadHeight + 6, out6, null); 385 | blocks.add(new BlockAndValidity(this, b21.block, true, false, b15.getHash(), chainHeadHeight + 6, "b21")); 386 | NewBlock b22 = createNextBlock(b21, chainHeadHeight + 7, out5, null); 387 | blocks.add(new BlockAndValidity(this, b22.block, false, true, b15.getHash(), chainHeadHeight + 6, "b22")); 388 | 389 | // Create a block on either side of MAX_BLOCK_SIZE and make sure its accepted/rejected 390 | // genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) 391 | // \-> b12 (3) -> b13 (4) -> b15 (5) -> b23 (6) 392 | // \-> b24 (6) -> b25 (7) 393 | // \-> b3 (1) -> b4 (2) 394 | // 395 | NewBlock b23 = createNextBlock(b15, chainHeadHeight + 7, out6, null); 396 | { 397 | Transaction tx = new Transaction(params); 398 | byte[] outputScript = new byte[Block.MAX_BLOCK_SIZE - b23.block.getMessageSize() - 65]; 399 | Arrays.fill(outputScript, (byte) OP_FALSE); 400 | tx.addOutput(new TransactionOutput(params, tx, ZERO, outputScript)); 401 | addOnlyInputToTransaction(tx, b23); 402 | b23.addTransaction(tx); 403 | } 404 | b23.solve(); 405 | checkState(b23.block.getMessageSize() == Block.MAX_BLOCK_SIZE); 406 | blocks.add(new BlockAndValidity(this, b23, true, false, b23.getHash(), chainHeadHeight + 7, "b23")); 407 | spendableOutputs.offer(b23.getCoinbaseOutput()); 408 | 409 | NewBlock b24 = createNextBlock(b15, chainHeadHeight + 7, out6, null); 410 | { 411 | Transaction tx = new Transaction(params); 412 | byte[] outputScript = new byte[Block.MAX_BLOCK_SIZE - b24.block.getMessageSize() - 64]; 413 | Arrays.fill(outputScript, (byte) OP_FALSE); 414 | tx.addOutput(new TransactionOutput(params, tx, ZERO, outputScript)); 415 | addOnlyInputToTransaction(tx, b24); 416 | b24.addTransaction(tx); 417 | } 418 | b24.solve(); 419 | checkState(b24.block.getMessageSize() == Block.MAX_BLOCK_SIZE + 1); 420 | blocks.add(new BlockAndValidity(this, b24, false, true, b23.getHash(), chainHeadHeight + 7, "b24")); 421 | 422 | // Extend the b24 chain to make sure bitcoind isn't accepting b24 423 | NewBlock b25 = createNextBlock(b24, chainHeadHeight + 8, out7, null); 424 | blocks.add(new BlockAndValidity(this, b25, false, false, b23.getHash(), chainHeadHeight + 7, "b25")); 425 | 426 | // Create blocks with a coinbase input script size out of range 427 | // genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) 428 | // \-> b12 (3) -> b13 (4) -> b15 (5) -> b23 (6) -> b30 (7) 429 | // \-> ... (6) -> ... (7) 430 | // \-> b3 (1) -> b4 (2) 431 | // 432 | NewBlock b26 = createNextBlock(b15, chainHeadHeight + 7, out6, null); 433 | // 1 is too small, but we already generate every other block with 2, so that is tested 434 | b26.block.getTransactions().get(0).getInputs().get(0).setScriptBytes(new byte[]{0}); 435 | b26.block.setMerkleRoot(null); 436 | b26.solve(); 437 | blocks.add(new BlockAndValidity(this, b26, false, true, b23.getHash(), chainHeadHeight + 7, "b26")); 438 | 439 | // Extend the b26 chain to make sure bitcoind isn't accepting b26 440 | NewBlock b27 = createNextBlock(b26, chainHeadHeight + 8, out7, null); 441 | blocks.add(new BlockAndValidity(this, b27, false, false, b23.getHash(), chainHeadHeight + 7, "b27")); 442 | 443 | NewBlock b28 = createNextBlock(b15, chainHeadHeight + 7, out6, null); 444 | { 445 | byte[] coinbase = new byte[101]; 446 | Arrays.fill(coinbase, (byte) 0); 447 | b28.block.getTransactions().get(0).getInputs().get(0).setScriptBytes(coinbase); 448 | } 449 | b28.block.setMerkleRoot(null); 450 | b28.solve(); 451 | blocks.add(new BlockAndValidity(this, b28, false, true, b23.getHash(), chainHeadHeight + 7, "b28")); 452 | 453 | // Extend the b28 chain to make sure bitcoind isn't accepting b28 454 | NewBlock b29 = createNextBlock(b28, chainHeadHeight + 8, out7, null); 455 | blocks.add(new BlockAndValidity(this, b29, false, false, b23.getHash(), chainHeadHeight + 7, "b29")); 456 | 457 | NewBlock b30 = createNextBlock(b23, chainHeadHeight + 8, out7, null); 458 | { 459 | byte[] coinbase = new byte[100]; 460 | Arrays.fill(coinbase, (byte) 0); 461 | b30.block.getTransactions().get(0).getInputs().get(0).setScriptBytes(coinbase); 462 | } 463 | b30.block.setMerkleRoot(null); 464 | b30.solve(); 465 | blocks.add(new BlockAndValidity(this, b30, true, false, b30.getHash(), chainHeadHeight + 8, "b30")); 466 | spendableOutputs.offer(b30.getCoinbaseOutput()); 467 | 468 | // Check sigops of OP_CHECKMULTISIG/OP_CHECKMULTISIGVERIFY/OP_CHECKSIGVERIFY 469 | // 6 (3) 470 | // 12 (3) -> b13 (4) -> b15 (5) -> b23 (6) -> b30 (7) -> b31 (8) -> b33 (9) -> b35 (10) 471 | // \-> b36 (11) 472 | // \-> b34 (10) 473 | // \-> b32 (9) 474 | // 475 | TransactionOutPointWithValue out8 = spendableOutputs.poll(); 476 | 477 | NewBlock b31 = createNextBlock(b30, chainHeadHeight + 9, out8, null); 478 | { 479 | int sigOps = 0; 480 | for (Transaction tx : b31.block.transactions) { 481 | sigOps += tx.getSigOpCount(); 482 | } 483 | Transaction tx = new Transaction(params); 484 | byte[] outputScript = new byte[(Block.MAX_BLOCK_SIGOPS - sigOps) / 20]; 485 | Arrays.fill(outputScript, (byte) OP_CHECKMULTISIG); 486 | tx.addOutput(new TransactionOutput(params, tx, SATOSHI, outputScript)); 487 | addOnlyInputToTransaction(tx, b31); 488 | b31.addTransaction(tx); 489 | } 490 | b31.solve(); 491 | 492 | blocks.add(new BlockAndValidity(this, b31, true, false, b31.getHash(), chainHeadHeight + 9, "b31")); 493 | spendableOutputs.offer(b31.getCoinbaseOutput()); 494 | 495 | TransactionOutPointWithValue out9 = spendableOutputs.poll(); 496 | 497 | NewBlock b32 = createNextBlock(b31, chainHeadHeight + 10, out9, null); 498 | { 499 | int sigOps = 0; 500 | for (Transaction tx : b32.block.transactions) { 501 | sigOps += tx.getSigOpCount(); 502 | } 503 | Transaction tx = new Transaction(params); 504 | byte[] outputScript = new byte[(Block.MAX_BLOCK_SIGOPS - sigOps) / 20 + (Block.MAX_BLOCK_SIGOPS - sigOps) % 20 + 1]; 505 | Arrays.fill(outputScript, (byte) OP_CHECKMULTISIG); 506 | for (int i = 0; i < (Block.MAX_BLOCK_SIGOPS - sigOps) % 20; i++) 507 | outputScript[i] = (byte) OP_CHECKSIG; 508 | tx.addOutput(new TransactionOutput(params, tx, SATOSHI, outputScript)); 509 | addOnlyInputToTransaction(tx, b32); 510 | b32.addTransaction(tx); 511 | } 512 | b32.solve(); 513 | blocks.add(new BlockAndValidity(this, b32, false, true, b31.getHash(), chainHeadHeight + 9, "b32")); 514 | 515 | NewBlock b33 = createNextBlock(b31, chainHeadHeight + 10, out9, null); 516 | { 517 | int sigOps = 0; 518 | for (Transaction tx : b33.block.transactions) { 519 | sigOps += tx.getSigOpCount(); 520 | } 521 | Transaction tx = new Transaction(params); 522 | byte[] outputScript = new byte[(Block.MAX_BLOCK_SIGOPS - sigOps) / 20]; 523 | Arrays.fill(outputScript, (byte) OP_CHECKMULTISIGVERIFY); 524 | tx.addOutput(new TransactionOutput(params, tx, SATOSHI, outputScript)); 525 | addOnlyInputToTransaction(tx, b33); 526 | b33.addTransaction(tx); 527 | } 528 | b33.solve(); 529 | 530 | blocks.add(new BlockAndValidity(this, b33, true, false, b33.getHash(), chainHeadHeight + 10, "b33")); 531 | spendableOutputs.offer(b33.getCoinbaseOutput()); 532 | 533 | TransactionOutPointWithValue out10 = spendableOutputs.poll(); 534 | 535 | NewBlock b34 = createNextBlock(b33, chainHeadHeight + 11, out10, null); 536 | { 537 | int sigOps = 0; 538 | for (Transaction tx : b34.block.getTransactions()) { 539 | sigOps += tx.getSigOpCount(); 540 | } 541 | Transaction tx = new Transaction(params); 542 | byte[] outputScript = new byte[(Block.MAX_BLOCK_SIGOPS - sigOps) / 20 + (Block.MAX_BLOCK_SIGOPS - sigOps) % 20 + 1]; 543 | Arrays.fill(outputScript, (byte) OP_CHECKMULTISIGVERIFY); 544 | for (int i = 0; i < (Block.MAX_BLOCK_SIGOPS - sigOps) % 20; i++) 545 | outputScript[i] = (byte) OP_CHECKSIG; 546 | tx.addOutput(new TransactionOutput(params, tx, SATOSHI, outputScript)); 547 | addOnlyInputToTransaction(tx, b34); 548 | b34.addTransaction(tx); 549 | } 550 | b34.solve(); 551 | blocks.add(new BlockAndValidity(this, b34, false, true, b33.getHash(), chainHeadHeight + 10, "b34")); 552 | 553 | NewBlock b35 = createNextBlock(b33, chainHeadHeight + 11, out10, null); 554 | { 555 | int sigOps = 0; 556 | for (Transaction tx : b35.block.getTransactions()) { 557 | sigOps += tx.getSigOpCount(); 558 | } 559 | Transaction tx = new Transaction(params); 560 | byte[] outputScript = new byte[Block.MAX_BLOCK_SIGOPS - sigOps]; 561 | Arrays.fill(outputScript, (byte) OP_CHECKSIGVERIFY); 562 | tx.addOutput(new TransactionOutput(params, tx, SATOSHI, outputScript)); 563 | addOnlyInputToTransaction(tx, b35); 564 | b35.addTransaction(tx); 565 | } 566 | b35.solve(); 567 | 568 | blocks.add(new BlockAndValidity(this, b35, true, false, b35.getHash(), chainHeadHeight + 11, "b35")); 569 | spendableOutputs.offer(b35.getCoinbaseOutput()); 570 | 571 | TransactionOutPointWithValue out11 = spendableOutputs.poll(); 572 | 573 | NewBlock b36 = createNextBlock(b35, chainHeadHeight + 12, out11, null); 574 | { 575 | int sigOps = 0; 576 | for (Transaction tx : b36.block.getTransactions()) { 577 | sigOps += tx.getSigOpCount(); 578 | } 579 | Transaction tx = new Transaction(params); 580 | byte[] outputScript = new byte[Block.MAX_BLOCK_SIGOPS - sigOps + 1]; 581 | Arrays.fill(outputScript, (byte) OP_CHECKSIGVERIFY); 582 | tx.addOutput(new TransactionOutput(params, tx, SATOSHI, outputScript)); 583 | addOnlyInputToTransaction(tx, b36); 584 | b36.addTransaction(tx); 585 | } 586 | b36.solve(); 587 | 588 | blocks.add(new BlockAndValidity(this, b36, false, true, b35.getHash(), chainHeadHeight + 11, "b36")); 589 | 590 | // Check spending of a transaction in a block which failed to connect 591 | // (test block store transaction abort handling, not that it should get this far if that's broken...) 592 | // 6 (3) 593 | // 12 (3) -> b13 (4) -> b15 (5) -> b23 (6) -> b30 (7) -> b31 (8) -> b33 (9) -> b35 (10) 594 | // \-> b37 (11) 595 | // \-> b38 (11) 596 | // 597 | NewBlock b37 = createNextBlock(b35, chainHeadHeight + 12, out11, null); 598 | { 599 | Transaction tx = new Transaction(params); 600 | tx.addOutput(new TransactionOutput(params, tx, SATOSHI, new byte[]{})); 601 | addOnlyInputToTransaction(tx, out11); // double spend out11 602 | b37.addTransaction(tx); 603 | } 604 | b37.solve(); 605 | blocks.add(new BlockAndValidity(this, b37, false, true, b35.getHash(), chainHeadHeight + 11, "b37")); 606 | 607 | NewBlock b38 = createNextBlock(b35, chainHeadHeight + 12, out11, null); 608 | { 609 | Transaction tx = new Transaction(params); 610 | tx.addOutput(new TransactionOutput(params, tx, SATOSHI, new byte[]{})); 611 | // Attempt to spend b37's first non-coinbase tx, at which point b37 was still considered valid 612 | addOnlyInputToTransaction(tx, b37); 613 | b38.addTransaction(tx); 614 | } 615 | b38.solve(); 616 | blocks.add(new BlockAndValidity(this, b38, false, true, b35.getHash(), chainHeadHeight + 11, "b38")); 617 | 618 | // Check P2SH SigOp counting 619 | // 13 (4) -> b15 (5) -> b23 (6) -> b30 (7) -> b31 (8) -> b33 (9) -> b35 (10) -> b39 (11) -> b41 (12) 620 | // \-> b40 (12) 621 | // 622 | // Create some P2SH outputs that will require 6 sigops to spend 623 | byte[] b39p2shScriptPubKey; 624 | int b39numP2SHOutputs = 0, b39sigOpsPerOutput = 6; 625 | NewBlock b39 = createNextBlock(b35, chainHeadHeight + 12, null, null); 626 | { 627 | ByteArrayOutputStream p2shScriptPubKey = new UnsafeByteArrayOutputStream(); 628 | try { 629 | Script.writeBytes(p2shScriptPubKey, coinbaseOutKeyPubKey); 630 | p2shScriptPubKey.write(OP_2DUP); 631 | p2shScriptPubKey.write(OP_CHECKSIGVERIFY); 632 | p2shScriptPubKey.write(OP_2DUP); 633 | p2shScriptPubKey.write(OP_CHECKSIGVERIFY); 634 | p2shScriptPubKey.write(OP_2DUP); 635 | p2shScriptPubKey.write(OP_CHECKSIGVERIFY); 636 | p2shScriptPubKey.write(OP_2DUP); 637 | p2shScriptPubKey.write(OP_CHECKSIGVERIFY); 638 | p2shScriptPubKey.write(OP_2DUP); 639 | p2shScriptPubKey.write(OP_CHECKSIGVERIFY); 640 | p2shScriptPubKey.write(OP_CHECKSIG); 641 | } catch (IOException e) { 642 | throw new RuntimeException(e); // Cannot happen. 643 | } 644 | b39p2shScriptPubKey = p2shScriptPubKey.toByteArray(); 645 | 646 | byte[] scriptHash = Utils.sha256hash160(b39p2shScriptPubKey); 647 | UnsafeByteArrayOutputStream scriptPubKey = new UnsafeByteArrayOutputStream(scriptHash.length + 3); 648 | scriptPubKey.write(OP_HASH160); 649 | try { 650 | Script.writeBytes(scriptPubKey, scriptHash); 651 | } catch (IOException e) { 652 | throw new RuntimeException(e); // Cannot happen. 653 | } 654 | scriptPubKey.write(OP_EQUAL); 655 | 656 | Coin lastOutputValue = out11.value.subtract(SATOSHI); 657 | TransactionOutPoint lastOutPoint; 658 | { 659 | Transaction tx = new Transaction(params); 660 | tx.addOutput(new TransactionOutput(params, tx, SATOSHI, scriptPubKey.toByteArray())); 661 | tx.addOutput(new TransactionOutput(params, tx, lastOutputValue, new byte[]{OP_1})); 662 | addOnlyInputToTransaction(tx, out11); 663 | lastOutPoint = new TransactionOutPoint(params, 1, tx.getHash()); 664 | b39.addTransaction(tx); 665 | } 666 | b39numP2SHOutputs++; 667 | 668 | while (b39.block.getMessageSize() < Block.MAX_BLOCK_SIZE) { 669 | Transaction tx = new Transaction(params); 670 | 671 | lastOutputValue = lastOutputValue.subtract(SATOSHI); 672 | tx.addOutput(new TransactionOutput(params, tx, SATOSHI, scriptPubKey.toByteArray())); 673 | tx.addOutput(new TransactionOutput(params, tx, lastOutputValue, new byte[]{OP_1})); 674 | tx.addInput(new TransactionInput(params, tx, new byte[]{OP_1}, lastOutPoint)); 675 | lastOutPoint = new TransactionOutPoint(params, 1, tx.getHash()); 676 | 677 | if (b39.block.getMessageSize() + tx.getMessageSize() < Block.MAX_BLOCK_SIZE) { 678 | b39.addTransaction(tx); 679 | b39numP2SHOutputs++; 680 | } else 681 | break; 682 | } 683 | } 684 | b39.solve(); 685 | blocks.add(new BlockAndValidity(this, b39, true, false, b39.getHash(), chainHeadHeight + 12, "b39")); 686 | spendableOutputs.offer(b39.getCoinbaseOutput()); 687 | 688 | TransactionOutPointWithValue out12 = spendableOutputs.poll(); 689 | 690 | NewBlock b40 = createNextBlock(b39, chainHeadHeight + 13, out12, null); 691 | { 692 | int sigOps = 0; 693 | for (Transaction tx : b40.block.getTransactions()) { 694 | sigOps += tx.getSigOpCount(); 695 | } 696 | 697 | int numTxes = (Block.MAX_BLOCK_SIGOPS - sigOps) / b39sigOpsPerOutput; 698 | checkState(numTxes <= b39numP2SHOutputs); 699 | 700 | TransactionOutPoint lastOutPoint = new TransactionOutPoint(params, 1, b40.block.getTransactions().get(1).getHash()); 701 | 702 | byte[] scriptSig = null; 703 | for (int i = 1; i <= numTxes; i++) { 704 | Transaction tx = new Transaction(params); 705 | tx.addOutput(new TransactionOutput(params, tx, SATOSHI, new byte[]{OP_1})); 706 | tx.addInput(new TransactionInput(params, tx, new byte[]{OP_1}, lastOutPoint)); 707 | 708 | TransactionInput input = new TransactionInput(params, tx, new byte[]{}, 709 | new TransactionOutPoint(params, 0, b39.block.getTransactions().get(i).getHash())); 710 | tx.addInput(input); 711 | 712 | if (scriptSig == null) { 713 | // Exploit the SigHash.SINGLE bug to avoid having to make more than one signature 714 | Sha256Hash hash = tx.hashForSignature(1, b39p2shScriptPubKey, SigHash.SINGLE, false); 715 | 716 | // Sign input 717 | try { 718 | ByteArrayOutputStream bos = new UnsafeByteArrayOutputStream(73); 719 | bos.write(coinbaseOutKey.sign(hash).encodeToDER()); 720 | bos.write(SigHash.SINGLE.ordinal() + 1); 721 | byte[] signature = bos.toByteArray(); 722 | 723 | ByteArrayOutputStream scriptSigBos = new UnsafeByteArrayOutputStream(signature.length + b39p2shScriptPubKey.length + 3); 724 | Script.writeBytes(scriptSigBos, new byte[]{(byte) OP_CHECKSIG}); 725 | scriptSigBos.write(Script.createInputScript(signature)); 726 | Script.writeBytes(scriptSigBos, b39p2shScriptPubKey); 727 | 728 | scriptSig = scriptSigBos.toByteArray(); 729 | } catch (IOException e) { 730 | throw new RuntimeException(e); // Cannot happen. 731 | } 732 | } 733 | 734 | input.setScriptBytes(scriptSig); 735 | 736 | lastOutPoint = new TransactionOutPoint(params, 0, tx.getHash()); 737 | 738 | b40.addTransaction(tx); 739 | } 740 | 741 | sigOps += numTxes * b39sigOpsPerOutput; 742 | Transaction tx = new Transaction(params); 743 | tx.addInput(new TransactionInput(params, tx, new byte[]{OP_1}, lastOutPoint)); 744 | byte[] scriptPubKey = new byte[Block.MAX_BLOCK_SIGOPS - sigOps + 1]; 745 | Arrays.fill(scriptPubKey, (byte) OP_CHECKSIG); 746 | tx.addOutput(new TransactionOutput(params, tx, ZERO, scriptPubKey)); 747 | b40.addTransaction(tx); 748 | } 749 | b40.solve(); 750 | blocks.add(new BlockAndValidity(this, b40, false, true, b39.getHash(), chainHeadHeight + 12, "b40")); 751 | 752 | NewBlock b41 = null; 753 | if (runBarelyExpensiveTests) { 754 | b41 = createNextBlock(b39, chainHeadHeight + 13, out12, null); 755 | { 756 | int sigOps = 0; 757 | for (Transaction tx : b41.block.getTransactions()) { 758 | sigOps += tx.getSigOpCount(); 759 | } 760 | 761 | int numTxes = (Block.MAX_BLOCK_SIGOPS - sigOps) 762 | / b39sigOpsPerOutput; 763 | checkState(numTxes <= b39numP2SHOutputs); 764 | 765 | TransactionOutPoint lastOutPoint = new TransactionOutPoint( 766 | params, 1, b41.block.getTransactions().get(1).getHash()); 767 | 768 | byte[] scriptSig = null; 769 | for (int i = 1; i <= numTxes; i++) { 770 | Transaction tx = new Transaction(params); 771 | tx.addOutput(new TransactionOutput(params, tx, Coin 772 | .SATOSHI, new byte[]{OP_1})); 773 | tx.addInput(new TransactionInput(params, tx, 774 | new byte[]{OP_1}, lastOutPoint)); 775 | 776 | TransactionInput input = new TransactionInput(params, tx, 777 | new byte[]{}, new TransactionOutPoint(params, 0, 778 | b39.block.getTransactions().get(i).getHash())); 779 | tx.addInput(input); 780 | 781 | if (scriptSig == null) { 782 | // Exploit the SigHash.SINGLE bug to avoid having to make more than one signature 783 | Sha256Hash hash = tx.hashForSignature(1, 784 | b39p2shScriptPubKey, SigHash.SINGLE, false); 785 | 786 | // Sign input 787 | try { 788 | ByteArrayOutputStream bos = new UnsafeByteArrayOutputStream( 789 | 73); 790 | bos.write(coinbaseOutKey.sign(hash).encodeToDER()); 791 | bos.write(SigHash.SINGLE.ordinal() + 1); 792 | byte[] signature = bos.toByteArray(); 793 | 794 | ByteArrayOutputStream scriptSigBos = new UnsafeByteArrayOutputStream( 795 | signature.length 796 | + b39p2shScriptPubKey.length + 3); 797 | Script.writeBytes(scriptSigBos, 798 | new byte[]{(byte) OP_CHECKSIG}); 799 | scriptSigBos.write(Script 800 | .createInputScript(signature)); 801 | Script.writeBytes(scriptSigBos, b39p2shScriptPubKey); 802 | 803 | scriptSig = scriptSigBos.toByteArray(); 804 | } catch (IOException e) { 805 | throw new RuntimeException(e); // Cannot happen. 806 | } 807 | } 808 | 809 | input.setScriptBytes(scriptSig); 810 | 811 | lastOutPoint = new TransactionOutPoint(params, 0, 812 | tx.getHash()); 813 | 814 | b41.addTransaction(tx); 815 | } 816 | 817 | sigOps += numTxes * b39sigOpsPerOutput; 818 | Transaction tx = new Transaction(params); 819 | tx.addInput(new TransactionInput(params, tx, 820 | new byte[]{OP_1}, lastOutPoint)); 821 | byte[] scriptPubKey = new byte[Block.MAX_BLOCK_SIGOPS - sigOps]; 822 | Arrays.fill(scriptPubKey, (byte) OP_CHECKSIG); 823 | tx.addOutput(new TransactionOutput(params, tx, ZERO, scriptPubKey)); 824 | b41.addTransaction(tx); 825 | } 826 | b41.solve(); 827 | blocks.add(new BlockAndValidity(this, b41, true, false, b41.getHash(), chainHeadHeight + 13, "b41")); 828 | } 829 | 830 | // Fork off of b39 to create a constant base again 831 | // b23 (6) -> b30 (7) -> b31 (8) -> b33 (9) -> b35 (10) -> b39 (11) -> b42 (12) -> b43 (13) 832 | // \-> b41 (12) 833 | // 834 | NewBlock b42 = createNextBlock(b39, chainHeadHeight + 13, out12, null); 835 | blocks.add(new BlockAndValidity(this, b42, true, false, b41 == null ? b42.getHash() : b41.getHash(), chainHeadHeight + 13, "b42")); 836 | spendableOutputs.offer(b42.getCoinbaseOutput()); 837 | 838 | TransactionOutPointWithValue out13 = spendableOutputs.poll(); 839 | 840 | NewBlock b43 = createNextBlock(b42, chainHeadHeight + 14, out13, null); 841 | blocks.add(new BlockAndValidity(this, b43, true, false, b43.getHash(), chainHeadHeight + 14, "b43")); 842 | spendableOutputs.offer(b43.getCoinbaseOutput()); 843 | 844 | // Test a number of really invalid scenarios 845 | // -> b31 (8) -> b33 (9) -> b35 (10) -> b39 (11) -> b42 (12) -> b43 (13) -> b44 (14) 846 | // \-> ??? (15) 847 | // 848 | TransactionOutPointWithValue out14 = spendableOutputs.poll(); 849 | 850 | // A valid block created exactly like b44 to make sure the creation itself works 851 | Block b44 = new Block(params); 852 | byte[] outScriptBytes = ScriptBuilder.createOutputScript(ECKey.fromPublicOnly(coinbaseOutKeyPubKey)).getProgram(); 853 | { 854 | b44.setDifficultyTarget(b43.block.getDifficultyTarget()); 855 | b44.addCoinbaseTransaction(coinbaseOutKeyPubKey, ZERO); 856 | 857 | Transaction t = new Transaction(params); 858 | // Entirely invalid scriptPubKey to ensure we aren't pre-verifying too much 859 | t.addOutput(new TransactionOutput(params, t, ZERO, new byte[]{OP_PUSHDATA1 - 1})); 860 | t.addOutput(new TransactionOutput(params, t, SATOSHI, outScriptBytes)); 861 | // Spendable output 862 | t.addOutput(new TransactionOutput(params, t, ZERO, new byte[]{OP_1})); 863 | addOnlyInputToTransaction(t, out14); 864 | b44.addTransaction(t); 865 | 866 | b44.setPrevBlockHash(b43.getHash()); 867 | b44.setTime(b43.block.getTimeSeconds() + 1); 868 | } 869 | b44.solve(); 870 | blocks.add(new BlockAndValidity(this, b44, true, false, b44.getHash(), chainHeadHeight + 15, "b44")); 871 | 872 | TransactionOutPointWithValue out15 = spendableOutputs.poll(); 873 | 874 | // A block with a non-coinbase as the first tx 875 | Block b45 = new Block(params); 876 | { 877 | b45.setDifficultyTarget(b44.getDifficultyTarget()); 878 | //b45.addCoinbaseTransaction(pubKey, coinbaseValue); 879 | 880 | Transaction t = new Transaction(params); 881 | // Entirely invalid scriptPubKey to ensure we aren't pre-verifying too much 882 | t.addOutput(new TransactionOutput(params, t, ZERO, new byte[]{OP_PUSHDATA1 - 1})); 883 | t.addOutput(new TransactionOutput(params, t, SATOSHI, outScriptBytes)); 884 | // Spendable output 885 | t.addOutput(new TransactionOutput(params, t, ZERO, new byte[]{OP_1})); 886 | addOnlyInputToTransaction(t, out15); 887 | try { 888 | b45.addTransaction(t); 889 | } catch (RuntimeException e) { 890 | } // Should happen 891 | if (b45.getTransactions().size() > 0) 892 | throw new RuntimeException("addTransaction doesn't properly check for adding a non-coinbase as first tx"); 893 | b45.addTransaction(t, false); 894 | 895 | b45.setPrevBlockHash(b44.getHash()); 896 | b45.setTime(b44.getTimeSeconds() + 1); 897 | } 898 | b45.solve(); 899 | blocks.add(new BlockAndValidity(this, b45, false, true, b44.getHash(), chainHeadHeight + 15, "b45")); 900 | 901 | // A block with no txn 902 | Block b46 = new Block(params); 903 | { 904 | b46.transactions = new ArrayList(); 905 | b46.setDifficultyTarget(b44.getDifficultyTarget()); 906 | b46.setMerkleRoot(Sha256Hash.ZERO_HASH); 907 | 908 | b46.setPrevBlockHash(b44.getHash()); 909 | b46.setTime(b44.getTimeSeconds() + 1); 910 | } 911 | b46.solve(); 912 | blocks.add(new BlockAndValidity(this, b46, false, true, b44.getHash(), chainHeadHeight + 15, "b46")); 913 | 914 | // A block with invalid work 915 | NewBlock b47 = createNextBlock(b44, chainHeadHeight + 16, out15, null); 916 | { 917 | try { 918 | // Inverse solve 919 | BigInteger target = b47.block.getDifficultyTargetAsInteger(); 920 | while (true) { 921 | BigInteger h = b47.getHash().toBigInteger(); 922 | if (h.compareTo(target) > 0) // if invalid 923 | break; 924 | // increment the nonce and try again. 925 | b47.block.setNonce(b47.block.getNonce() + 1); 926 | } 927 | } catch (VerificationException e) { 928 | throw new RuntimeException(e); // Cannot happen. 929 | } 930 | } 931 | blocks.add(new BlockAndValidity(this, b47, false, true, b44.getHash(), chainHeadHeight + 15, "b47")); 932 | 933 | // Block with timestamp > 2h in the future 934 | NewBlock b48 = createNextBlock(b44, chainHeadHeight + 16, out15, null); 935 | b48.block.setTime(Utils.currentTimeSeconds() + 60 * 60 * 3); 936 | b48.solve(); 937 | blocks.add(new BlockAndValidity(this, b48, false, true, b44.getHash(), chainHeadHeight + 15, "b48")); 938 | 939 | // Block with invalid merkle hash 940 | NewBlock b49 = createNextBlock(b44, chainHeadHeight + 16, out15, null); 941 | byte[] b49MerkleHash = Sha256Hash.ZERO_HASH.getBytes().clone(); 942 | b49MerkleHash[1] = (byte) 0xDE; 943 | b49.block.setMerkleRoot(Sha256Hash.create(b49MerkleHash)); 944 | b49.solve(); 945 | blocks.add(new BlockAndValidity(this, b49, false, true, b44.getHash(), chainHeadHeight + 15, "b49")); 946 | 947 | // Block with incorrect POW limit 948 | NewBlock b50 = createNextBlock(b44, chainHeadHeight + 16, out15, null); 949 | { 950 | long diffTarget = b44.getDifficultyTarget(); 951 | diffTarget &= 0xFFBFFFFF; // Make difficulty one bit harder 952 | b50.block.setDifficultyTarget(diffTarget); 953 | } 954 | b50.solve(); 955 | blocks.add(new BlockAndValidity(this, b50, false, true, b44.getHash(), chainHeadHeight + 15, "b50")); 956 | 957 | // A block with two coinbase txn 958 | NewBlock b51 = createNextBlock(b44, chainHeadHeight + 16, out15, null); 959 | { 960 | Transaction coinbase = new Transaction(params); 961 | coinbase.addInput(new TransactionInput(params, coinbase, new byte[]{(byte) 0xff, 110, 1})); 962 | coinbase.addOutput(new TransactionOutput(params, coinbase, SATOSHI, outScriptBytes)); 963 | b51.block.addTransaction(coinbase, false); 964 | } 965 | b51.solve(); 966 | blocks.add(new BlockAndValidity(this, b51, false, true, b44.getHash(), chainHeadHeight + 15, "b51")); 967 | 968 | // A block with duplicate txn 969 | NewBlock b52 = createNextBlock(b44, chainHeadHeight + 16, out15, null); 970 | { 971 | Transaction tx = new Transaction(params); 972 | tx.addOutput(new TransactionOutput(params, tx, SATOSHI, new byte[]{})); 973 | addOnlyInputToTransaction(tx, b52); 974 | b52.addTransaction(tx); 975 | b52.addTransaction(tx); 976 | } 977 | b52.solve(); 978 | blocks.add(new BlockAndValidity(this, b52, false, true, b44.getHash(), chainHeadHeight + 15, "b52")); 979 | 980 | // Test block timestamp 981 | // -> b31 (8) -> b33 (9) -> b35 (10) -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) 982 | // \-> b54 (15) 983 | // \-> b44 (14) 984 | // 985 | NewBlock b53 = createNextBlock(b43, chainHeadHeight + 15, out14, null); 986 | blocks.add(new BlockAndValidity(this, b53, true, false, b44.getHash(), chainHeadHeight + 15, "b53")); 987 | spendableOutputs.offer(b53.getCoinbaseOutput()); 988 | 989 | // Block with invalid timestamp 990 | NewBlock b54 = createNextBlock(b53, chainHeadHeight + 16, out15, null); 991 | b54.block.setTime(b35.block.getTimeSeconds() - 1); 992 | b54.solve(); 993 | blocks.add(new BlockAndValidity(this, b54, false, true, b44.getHash(), chainHeadHeight + 15, "b54")); 994 | 995 | // Block with valid timestamp 996 | NewBlock b55 = createNextBlock(b53, chainHeadHeight + 16, out15, null); 997 | b55.block.setTime(b35.block.getTimeSeconds()); 998 | b55.solve(); 999 | blocks.add(new BlockAndValidity(this, b55, true, false, b55.getHash(), chainHeadHeight + 16, "b55")); 1000 | spendableOutputs.offer(b55.getCoinbaseOutput()); 1001 | 1002 | // Test CVE-2012-2459 1003 | // -> b33 (9) -> b35 (10) -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) 1004 | // \-> b56 (16) 1005 | // 1006 | TransactionOutPointWithValue out16 = spendableOutputs.poll(); 1007 | 1008 | NewBlock b57 = createNextBlock(b55, chainHeadHeight + 17, out16, null); 1009 | Transaction b56txToDuplicate; 1010 | { 1011 | b56txToDuplicate = new Transaction(params); 1012 | b56txToDuplicate.addOutput(new TransactionOutput(params, b56txToDuplicate, SATOSHI, new byte[]{})); 1013 | addOnlyInputToTransaction(b56txToDuplicate, b57); 1014 | b57.addTransaction(b56txToDuplicate); 1015 | } 1016 | b57.solve(); 1017 | 1018 | Block b56; 1019 | try { 1020 | b56 = new Block(params, b57.block.bitcoinSerialize()); 1021 | } catch (ProtocolException e) { 1022 | throw new RuntimeException(e); // Cannot happen. 1023 | } 1024 | b56.addTransaction(b56txToDuplicate); 1025 | checkState(b56.getHash().equals(b57.getHash())); 1026 | blocks.add(new BlockAndValidity(this, b56, false, true, b55.getHash(), chainHeadHeight + 16, "b56")); 1027 | 1028 | NewBlock b57p2 = createNextBlock(b55, chainHeadHeight + 17, out16, null); 1029 | Transaction b56p2txToDuplicate1, b56p2txToDuplicate2; 1030 | { 1031 | Transaction tx1 = new Transaction(params); 1032 | tx1.addOutput(new TransactionOutput(params, tx1, SATOSHI, new byte[]{OP_TRUE})); 1033 | addOnlyInputToTransaction(tx1, b57p2); 1034 | b57p2.addTransaction(tx1); 1035 | 1036 | Transaction tx2 = new Transaction(params); 1037 | tx2.addOutput(new TransactionOutput(params, tx2, SATOSHI, new byte[]{OP_TRUE})); 1038 | addOnlyInputToTransaction(tx2, new TransactionOutPointWithValue( 1039 | new TransactionOutPoint(params, 0, tx1.getHash()), 1040 | SATOSHI, tx1.getOutputs().get(0).getScriptPubKey())); 1041 | b57p2.addTransaction(tx2); 1042 | 1043 | b56p2txToDuplicate1 = new Transaction(params); 1044 | b56p2txToDuplicate1.addOutput(new TransactionOutput(params, b56p2txToDuplicate1, SATOSHI, new byte[]{OP_TRUE})); 1045 | addOnlyInputToTransaction(b56p2txToDuplicate1, new TransactionOutPointWithValue( 1046 | new TransactionOutPoint(params, 0, tx2.getHash()), 1047 | SATOSHI, tx2.getOutputs().get(0).getScriptPubKey())); 1048 | b57p2.addTransaction(b56p2txToDuplicate1); 1049 | 1050 | b56p2txToDuplicate2 = new Transaction(params); 1051 | b56p2txToDuplicate2.addOutput(new TransactionOutput(params, b56p2txToDuplicate2, SATOSHI, new byte[]{})); 1052 | addOnlyInputToTransaction(b56p2txToDuplicate2, new TransactionOutPointWithValue( 1053 | new TransactionOutPoint(params, 0, b56p2txToDuplicate1.getHash()), 1054 | SATOSHI, b56p2txToDuplicate1.getOutputs().get(0).getScriptPubKey())); 1055 | b57p2.addTransaction(b56p2txToDuplicate2); 1056 | } 1057 | b57p2.solve(); 1058 | 1059 | Block b56p2; 1060 | try { 1061 | b56p2 = new Block(params, b57p2.block.bitcoinSerialize()); 1062 | } catch (ProtocolException e) { 1063 | throw new RuntimeException(e); // Cannot happen. 1064 | } 1065 | b56p2.addTransaction(b56p2txToDuplicate1); 1066 | b56p2.addTransaction(b56p2txToDuplicate2); 1067 | checkState(b56p2.getHash().equals(b57p2.getHash())); 1068 | blocks.add(new BlockAndValidity(this, b56p2, false, true, b55.getHash(), chainHeadHeight + 16, "b56p2")); 1069 | blocks.add(new BlockAndValidity(this, b57p2, true, false, b57p2.getHash(), chainHeadHeight + 17, "b57p2")); 1070 | 1071 | blocks.add(new BlockAndValidity(this, b57, true, false, b57p2.getHash(), chainHeadHeight + 17, "b57")); 1072 | spendableOutputs.offer(b57.getCoinbaseOutput()); 1073 | 1074 | // Test a few invalid tx types 1075 | // -> b35 (10) -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) 1076 | // \-> ??? (17) 1077 | // 1078 | TransactionOutPointWithValue out17 = spendableOutputs.poll(); 1079 | 1080 | // tx with prevout.n out of range 1081 | NewBlock b58 = createNextBlock(b57, chainHeadHeight + 18, out17, null); 1082 | { 1083 | Transaction tx = new Transaction(params); 1084 | tx.addOutput(new TransactionOutput(params, tx, ZERO, new byte[]{})); 1085 | b58.getSpendableOutput().outpoint.setIndex(42); 1086 | addOnlyInputToTransaction(tx, b58); 1087 | b58.addTransaction(tx); 1088 | } 1089 | b58.solve(); 1090 | blocks.add(new BlockAndValidity(this, b58, false, true, b57p2.getHash(), chainHeadHeight + 17, "b58")); 1091 | 1092 | // tx with output value > input value out of range 1093 | NewBlock b59 = createNextBlock(b57, chainHeadHeight + 18, out17, null); 1094 | { 1095 | Transaction tx = new Transaction(params); 1096 | tx.addOutput(new TransactionOutput(params, tx, 1097 | b59.getSpendableOutput().value.add(SATOSHI), new byte[]{})); 1098 | addOnlyInputToTransaction(tx, b59); 1099 | b59.addTransaction(tx); 1100 | } 1101 | b59.solve(); 1102 | blocks.add(new BlockAndValidity(this, b59, false, true, b57p2.getHash(), chainHeadHeight + 17, "b59")); 1103 | 1104 | NewBlock b60 = createNextBlock(b57, chainHeadHeight + 18, out17, null); 1105 | blocks.add(new BlockAndValidity(this, b60, true, false, b60.getHash(), chainHeadHeight + 18, "b60")); 1106 | spendableOutputs.offer(b60.getCoinbaseOutput()); 1107 | 1108 | // Test BIP30 1109 | // -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) 1110 | // \-> b61 (18) 1111 | // 1112 | TransactionOutPointWithValue out18 = spendableOutputs.poll(); 1113 | 1114 | NewBlock b61 = createNextBlock(b60, chainHeadHeight + 19, out18, null); 1115 | { 1116 | b61.block.getTransactions().get(0).getInput(0).setScriptBytes(b60.block.getTransactions().get(0).getInput(0).getScriptBytes()); 1117 | b61.block.unCache(); 1118 | checkState(b61.block.getTransactions().get(0).equals(b60.block.getTransactions().get(0))); 1119 | } 1120 | b61.solve(); 1121 | // blocks.add(new BlockAndValidity(this, b61, false, true, b60.getHash(), chainHeadHeight + 18, "b61")); 1122 | 1123 | // Test tx.isFinal is properly rejected (not an exhaustive tx.isFinal test, that should be in data-driven transaction tests) 1124 | // -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) 1125 | // \-> b62 (18) 1126 | // 1127 | NewBlock b62 = createNextBlock(b60, chainHeadHeight + 19, null, null); 1128 | { 1129 | Transaction tx = new Transaction(params); 1130 | tx.setLockTime(0xffffffffL); 1131 | tx.addOutput(ZERO, OP_TRUE_SCRIPT); 1132 | addOnlyInputToTransaction(tx, out18, 0); 1133 | b62.addTransaction(tx); 1134 | checkState(!tx.isFinal(chainHeadHeight + 17, b62.block.getTimeSeconds())); 1135 | } 1136 | b62.solve(); 1137 | blocks.add(new 1138 | 1139 | BlockAndValidity(this,b62, false,true,b60.getHash(),chainHeadHeight 1140 | 1141 | +18,"b62")); 1142 | 1143 | // Test a non-final coinbase is also rejected 1144 | // -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) 1145 | // \-> b63 (-) 1146 | // 1147 | NewBlock b63 = createNextBlock(b60, chainHeadHeight + 19, null, null); 1148 | { 1149 | b63.block.getTransactions().get(0).setLockTime(0xffffffffL); 1150 | b63.block.getTransactions().get(0).getInputs().get(0).setSequenceNumber(0xDEADBEEF); 1151 | checkState(!b63.block.getTransactions().get(0).isFinal(chainHeadHeight + 17, b63.block.getTimeSeconds())); 1152 | } 1153 | b63.solve(); 1154 | blocks.add(new 1155 | 1156 | BlockAndValidity(this, b63, false,true,b60.getHash(),chainHeadHeight +18,"b63")); 1157 | 1158 | // Check that a block which is (when properly encoded) <= MAX_BLOCK_SIZE is accepted 1159 | // Even when it is encoded with varints that make its encoded size actually > MAX_BLOCK_SIZE 1160 | // -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) 1161 | // 1162 | Block b64; 1163 | NewBlock b64Original; 1164 | { 1165 | b64Original = createNextBlock(b60, chainHeadHeight + 19, out18, null); 1166 | Transaction tx = new Transaction(params); 1167 | byte[] outputScript = new byte[Block.MAX_BLOCK_SIZE - b64Original.block.getMessageSize() - 65]; 1168 | Arrays.fill(outputScript, (byte) OP_FALSE); 1169 | tx.addOutput(new TransactionOutput(params, tx, ZERO, outputScript)); 1170 | addOnlyInputToTransaction(tx, b64Original); 1171 | b64Original.addTransaction(tx); 1172 | b64Original.solve(); 1173 | checkState(b64Original.block.getMessageSize() == Block.MAX_BLOCK_SIZE); 1174 | 1175 | UnsafeByteArrayOutputStream stream = new UnsafeByteArrayOutputStream(b64Original.block.getMessageSize() + 8); 1176 | b64Original.block.writeHeader(stream); 1177 | 1178 | byte[] varIntBytes = new byte[9]; 1179 | varIntBytes[0] = (byte) 255; 1180 | Utils.uint32ToByteArrayLE((long) b64Original.block.getTransactions().size(), varIntBytes, 1); 1181 | Utils.uint32ToByteArrayLE(((long) b64Original.block.getTransactions().size()) >>> 32, varIntBytes, 5); 1182 | stream.write(varIntBytes); 1183 | checkState(new VarInt(varIntBytes, 0).value == b64Original.block.getTransactions().size()); 1184 | 1185 | for (Transaction transaction : b64Original.block.getTransactions()) 1186 | transaction.bitcoinSerialize(stream); 1187 | b64 = new Block(params, stream.toByteArray(), false, true, stream.size()); 1188 | 1189 | // The following checks are checking to ensure block serialization functions in the way needed for this test 1190 | // If they fail, it is likely not an indication of error, but an indication that this test needs rewritten 1191 | checkState(stream.size() == b64Original.block.getMessageSize() + 8); 1192 | checkState(stream.size() == b64.getMessageSize()); 1193 | checkState(Arrays.equals(stream.toByteArray(), b64.bitcoinSerialize())); 1194 | checkState(b64.getOptimalEncodingMessageSize() == b64Original.block.getMessageSize()); 1195 | } 1196 | blocks.add(new BlockAndValidity(this, b64, true, false, b64.getHash(), chainHeadHeight + 19, "b64")); 1197 | spendableOutputs.offer(b64Original.getCoinbaseOutput()); 1198 | 1199 | // Spend an output created in the block itself 1200 | // -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) 1201 | // 1202 | TransactionOutPointWithValue out19 = spendableOutputs.poll(); 1203 | checkState(out19 != null);//TODO preconditions all the way up 1204 | 1205 | NewBlock b65 = createNextBlock(b64, chainHeadHeight + 20, null, null); 1206 | { 1207 | Transaction tx1 = new Transaction(params); 1208 | tx1.addOutput(out19.value, OP_TRUE_SCRIPT); 1209 | addOnlyInputToTransaction(tx1, out19, 0); 1210 | b65.addTransaction(tx1); 1211 | Transaction tx2 = new Transaction(params); 1212 | tx2.addOutput(ZERO, OP_TRUE_SCRIPT); 1213 | tx2.addInput(tx1.getHash(), 0, OP_TRUE_SCRIPT); 1214 | b65.addTransaction(tx2); 1215 | } 1216 | b65.solve(); 1217 | blocks.add(new BlockAndValidity(this, b65, true, false, b65.getHash(), chainHeadHeight + 20, "b65")); 1218 | spendableOutputs.offer(b65.getCoinbaseOutput()); 1219 | 1220 | // Attempt to spend an output created later in the same block 1221 | // -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) 1222 | // \-> b66 (20) 1223 | // 1224 | TransactionOutPointWithValue out20 = spendableOutputs.poll(); 1225 | checkState(out20 != null); 1226 | 1227 | NewBlock b66 = createNextBlock(b65, chainHeadHeight + 21, null, null); 1228 | { 1229 | Transaction tx1 = new Transaction(params); 1230 | tx1.addOutput(out20.value, OP_TRUE_SCRIPT); 1231 | addOnlyInputToTransaction(tx1, out20, 0); 1232 | Transaction tx2 = new Transaction(params); 1233 | tx2.addOutput(ZERO, OP_TRUE_SCRIPT); 1234 | tx2.addInput(tx1.getHash(), 0, OP_NOP_SCRIPT); 1235 | b66.addTransaction(tx2); 1236 | b66.addTransaction(tx1); 1237 | } 1238 | b66.solve(); 1239 | blocks.add(new BlockAndValidity(this, b66, false, true, b65.getHash(), chainHeadHeight + 20, "b66")); 1240 | 1241 | // Attempt to double-spend a transaction created in a block 1242 | // -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) 1243 | // \-> b67 (20) 1244 | // 1245 | NewBlock b67 = createNextBlock(b65, chainHeadHeight + 21, null, null); 1246 | { 1247 | Transaction tx1 = new Transaction(params); 1248 | tx1.addOutput(out20.value, OP_TRUE_SCRIPT); 1249 | addOnlyInputToTransaction(tx1, out20, 0); 1250 | b67.addTransaction(tx1); 1251 | Transaction tx2 = new Transaction(params); 1252 | tx2.addOutput(ZERO, OP_TRUE_SCRIPT); 1253 | tx2.addInput(tx1.getHash(), 0, OP_NOP_SCRIPT); 1254 | b67.addTransaction(tx2); 1255 | Transaction tx3 = new Transaction(params); 1256 | tx3.addOutput(out20.value, OP_TRUE_SCRIPT); 1257 | tx3.addInput(tx1.getHash(), 0, OP_NOP_SCRIPT); 1258 | b67.addTransaction(tx3); 1259 | } 1260 | b67.solve(); 1261 | blocks.add(new BlockAndValidity(this, b67, false, true, b65.getHash(), chainHeadHeight + 20, "b67")); 1262 | 1263 | // A few more tests of block subsidy 1264 | // -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20) 1265 | // \-> b68 (20) 1266 | // 1267 | NewBlock b68 = createNextBlock(b65, chainHeadHeight + 21, null, SATOSHI.multiply(10)); 1268 | { 1269 | Transaction tx = new Transaction(params); 1270 | tx.addOutput(out20.value.subtract(Coin.valueOf(9)), OP_TRUE_SCRIPT); 1271 | addOnlyInputToTransaction(tx, out20, 0); 1272 | b68.addTransaction(tx); 1273 | } 1274 | b68.solve(); 1275 | blocks.add(new BlockAndValidity(this, b68, false, true, b65.getHash(), chainHeadHeight + 20, "b68")); 1276 | 1277 | NewBlock b69 = createNextBlock(b65, chainHeadHeight + 21, null, SATOSHI.multiply(10)); 1278 | { 1279 | Transaction tx = new Transaction(params); 1280 | tx.addOutput(out20.value.subtract(Coin.valueOf(10)), OP_TRUE_SCRIPT); 1281 | addOnlyInputToTransaction(tx, out20, 0); 1282 | b69.addTransaction(tx); 1283 | } 1284 | b69.solve(); 1285 | blocks.add(new BlockAndValidity(this, b69, true, false, b69.getHash(), chainHeadHeight + 21, "b69")); 1286 | spendableOutputs.offer(b69.getCoinbaseOutput()); 1287 | 1288 | // Test spending the outpoint of a non-existent transaction 1289 | // -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20) 1290 | // \-> b70 (21) 1291 | // 1292 | TransactionOutPointWithValue out21 = spendableOutputs.poll(); 1293 | checkState(out21 != null); 1294 | NewBlock b70 = createNextBlock(b69, chainHeadHeight + 22, out21, null); 1295 | { 1296 | Transaction tx = new Transaction(params); 1297 | tx.addOutput(ZERO, OP_TRUE_SCRIPT); 1298 | tx.addInput(new Sha256Hash("23c70ed7c0506e9178fc1a987f40a33946d4ad4c962b5ae3a52546da53af0c5c"), 0, 1299 | OP_NOP_SCRIPT); 1300 | b70.addTransaction(tx); 1301 | } 1302 | b70.solve(); 1303 | blocks.add(new BlockAndValidity(this, b70, false, true, b69.getHash(), chainHeadHeight + 21, "b70")); 1304 | 1305 | // Test accepting an invalid block which has the same hash as a valid one (via merkle tree tricks) 1306 | // -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20) -> b71 (21) 1307 | // \-> b72 (21) 1308 | // 1309 | NewBlock b72 = createNextBlock(b69, chainHeadHeight + 22, out21, null); 1310 | { 1311 | Transaction tx = new Transaction(params); 1312 | tx.addOutput(ZERO, OP_TRUE_SCRIPT); 1313 | addOnlyInputToTransaction(tx, b72); 1314 | b72.addTransaction(tx); 1315 | } 1316 | b72.solve(); 1317 | 1318 | Block b71 = new Block(params, b72.block.bitcoinSerialize()); 1319 | b71.addTransaction(b72.block.getTransactions().get(2)); 1320 | checkState(b71.getHash().equals(b72.getHash())); 1321 | blocks.add(new BlockAndValidity(this, b71, false, true, b69.getHash(), chainHeadHeight + 21, "b71")); 1322 | blocks.add(new BlockAndValidity(this, b72, true, false, b72.getHash(), chainHeadHeight + 22, "b72")); 1323 | spendableOutputs.offer(b72.getCoinbaseOutput()); 1324 | 1325 | // Have some fun with invalid scripts and MAX_BLOCK_SIGOPS 1326 | // -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20) -> b72 (21) 1327 | // \-> b** (22) 1328 | // 1329 | TransactionOutPointWithValue out22 = spendableOutputs.poll(); 1330 | checkState(out22 != null); 1331 | 1332 | NewBlock b73 = createNextBlock(b72, chainHeadHeight + 23, out22, null); 1333 | { 1334 | int sigOps = 0; 1335 | for (Transaction tx : b73.block.getTransactions()) { 1336 | sigOps += tx.getSigOpCount(); 1337 | } 1338 | Transaction tx = new Transaction(params); 1339 | byte[] outputScript = new byte[Block.MAX_BLOCK_SIGOPS - sigOps + (int) Script.MAX_SCRIPT_ELEMENT_SIZE + 1 + 5 + 1]; 1340 | Arrays.fill(outputScript, (byte) OP_CHECKSIG); 1341 | // If we push an element that is too large, the CHECKSIGs after that push are still counted 1342 | outputScript[Block.MAX_BLOCK_SIGOPS - sigOps] = OP_PUSHDATA4; 1343 | Utils.uint32ToByteArrayLE(Script.MAX_SCRIPT_ELEMENT_SIZE + 1, outputScript, Block.MAX_BLOCK_SIGOPS - sigOps + 1); 1344 | tx.addOutput(new TransactionOutput(params, tx, SATOSHI, outputScript)); 1345 | addOnlyInputToTransaction(tx, b73); 1346 | b73.addTransaction(tx); 1347 | } 1348 | b73.solve(); 1349 | blocks.add(new BlockAndValidity(this, b73, false, true, b72.getHash(), chainHeadHeight + 22, "b73")); 1350 | 1351 | NewBlock b74 = createNextBlock(b72, chainHeadHeight + 23, out22, null); 1352 | { 1353 | int sigOps = 0; 1354 | for (Transaction tx : b74.block.getTransactions()) { 1355 | sigOps += tx.getSigOpCount(); 1356 | } 1357 | Transaction tx = new Transaction(params); 1358 | byte[] outputScript = new byte[Block.MAX_BLOCK_SIGOPS - sigOps + (int) Script.MAX_SCRIPT_ELEMENT_SIZE + 42]; 1359 | Arrays.fill(outputScript, (byte) OP_CHECKSIG); 1360 | // If we push an invalid element, all previous CHECKSIGs are counted 1361 | outputScript[Block.MAX_BLOCK_SIGOPS - sigOps + 1] = OP_PUSHDATA4; 1362 | outputScript[Block.MAX_BLOCK_SIGOPS - sigOps + 2] = (byte) 0xfe; 1363 | outputScript[Block.MAX_BLOCK_SIGOPS - sigOps + 3] = (byte) 0xff; 1364 | outputScript[Block.MAX_BLOCK_SIGOPS - sigOps + 4] = (byte) 0xff; 1365 | outputScript[Block.MAX_BLOCK_SIGOPS - sigOps + 5] = (byte) 0xff; 1366 | tx.addOutput(new TransactionOutput(params, tx, SATOSHI, outputScript)); 1367 | addOnlyInputToTransaction(tx, b74); 1368 | b74.addTransaction(tx); 1369 | } 1370 | b74.solve(); 1371 | blocks.add(new BlockAndValidity(this, b74, false, true, b72.getHash(), chainHeadHeight + 22, "b74")); 1372 | 1373 | NewBlock b75 = createNextBlock(b72, chainHeadHeight + 23, out22, null); 1374 | { 1375 | int sigOps = 0; 1376 | for (Transaction tx : b75.block.getTransactions()) { 1377 | sigOps += tx.getSigOpCount(); 1378 | } 1379 | Transaction tx = new Transaction(params); 1380 | byte[] outputScript = new byte[Block.MAX_BLOCK_SIGOPS - sigOps + (int) Script.MAX_SCRIPT_ELEMENT_SIZE + 42]; 1381 | Arrays.fill(outputScript, (byte) OP_CHECKSIG); 1382 | // If we push an invalid element, all subsequent CHECKSIGs are not counted 1383 | outputScript[Block.MAX_BLOCK_SIGOPS - sigOps] = OP_PUSHDATA4; 1384 | outputScript[Block.MAX_BLOCK_SIGOPS - sigOps + 1] = (byte) 0xff; 1385 | outputScript[Block.MAX_BLOCK_SIGOPS - sigOps + 2] = (byte) 0xff; 1386 | outputScript[Block.MAX_BLOCK_SIGOPS - sigOps + 3] = (byte) 0xff; 1387 | outputScript[Block.MAX_BLOCK_SIGOPS - sigOps + 4] = (byte) 0xff; 1388 | tx.addOutput(new TransactionOutput(params, tx, SATOSHI, outputScript)); 1389 | addOnlyInputToTransaction(tx, b75); 1390 | b75.addTransaction(tx); 1391 | } 1392 | b75.solve(); 1393 | blocks.add(new BlockAndValidity(this, b75, true, false, b75.getHash(), chainHeadHeight + 23, "b75")); 1394 | spendableOutputs.offer(b75.getCoinbaseOutput()); 1395 | 1396 | TransactionOutPointWithValue out23 = spendableOutputs.poll(); 1397 | checkState(out23 != null); 1398 | 1399 | NewBlock b76 = createNextBlock(b75, chainHeadHeight + 24, out23, null); 1400 | { 1401 | int sigOps = 0; 1402 | for (Transaction tx : b76.block.getTransactions()) { 1403 | sigOps += tx.getSigOpCount(); 1404 | } 1405 | Transaction tx = new Transaction(params); 1406 | byte[] outputScript = new byte[Block.MAX_BLOCK_SIGOPS - sigOps + (int) Script.MAX_SCRIPT_ELEMENT_SIZE + 1 + 5]; 1407 | Arrays.fill(outputScript, (byte) OP_CHECKSIG); 1408 | // If we push an element that is filled with CHECKSIGs, they (obviously) arent counted 1409 | outputScript[Block.MAX_BLOCK_SIGOPS - sigOps] = OP_PUSHDATA4; 1410 | Utils.uint32ToByteArrayLE(Block.MAX_BLOCK_SIGOPS, outputScript, Block.MAX_BLOCK_SIGOPS - sigOps + 1); 1411 | tx.addOutput(new TransactionOutput(params, tx, SATOSHI, outputScript)); 1412 | addOnlyInputToTransaction(tx, b76); 1413 | b76.addTransaction(tx); 1414 | } 1415 | b76.solve(); 1416 | blocks.add(new BlockAndValidity(this, b76, true, false, b76.getHash(), chainHeadHeight + 24, "b76")); 1417 | spendableOutputs.offer(b76.getCoinbaseOutput()); 1418 | 1419 | // Test transaction resurrection 1420 | // -> b77 (24) -> b78 (25) -> b79 (26) 1421 | // \-> b80 (25) -> b81 (26) -> b82 (27) 1422 | // b78 creates a tx, which is spent in b79. after b82, both should be in mempool 1423 | // 1424 | TransactionOutPointWithValue out24 = checkNotNull(spendableOutputs.poll()); 1425 | TransactionOutPointWithValue out25 = checkNotNull(spendableOutputs.poll()); 1426 | TransactionOutPointWithValue out26 = checkNotNull(spendableOutputs.poll()); 1427 | TransactionOutPointWithValue out27 = checkNotNull(spendableOutputs.poll()); 1428 | 1429 | NewBlock b77 = createNextBlock(b76, chainHeadHeight + 25, out24, null); 1430 | blocks.add(new BlockAndValidity(this, b77, true, false, b77.getHash(), chainHeadHeight + 25, "b77")); 1431 | spendableOutputs.offer(b77.getCoinbaseOutput()); 1432 | 1433 | NewBlock b78 = createNextBlock(b77, chainHeadHeight + 26, out25, null); 1434 | Transaction b78tx = new Transaction(params); 1435 | { 1436 | b78tx.addOutput(ZERO, OP_TRUE_SCRIPT); 1437 | addOnlyInputToTransaction(b78tx, b77); 1438 | b78.addTransaction(b78tx); 1439 | } 1440 | b78.solve(); 1441 | blocks.add(new BlockAndValidity(this, b78, true, false, b78.getHash(), chainHeadHeight + 26, "b78")); 1442 | 1443 | NewBlock b79 = createNextBlock(b78, chainHeadHeight + 27, out26, null); 1444 | Transaction b79tx = new Transaction(params); 1445 | 1446 | { 1447 | b79tx.addOutput(ZERO, OP_TRUE_SCRIPT); 1448 | b79tx.addInput(b78tx.getHash(), 0, OP_NOP_SCRIPT); 1449 | b79.addTransaction(b79tx); 1450 | } 1451 | b79.solve(); 1452 | blocks.add(new BlockAndValidity(this, b79, true, false, b79.getHash(), chainHeadHeight + 27, "b79")); 1453 | 1454 | blocks.add(new MemoryPoolState(new HashSet(), "post-b79 empty mempool")); 1455 | 1456 | NewBlock b80 = createNextBlock(b77, chainHeadHeight + 26, out25, null); 1457 | blocks.add(new BlockAndValidity(this, b80, true, false, b79.getHash(), chainHeadHeight + 27, "b80")); 1458 | spendableOutputs.offer(b80.getCoinbaseOutput()); 1459 | 1460 | NewBlock b81 = createNextBlock(b80, chainHeadHeight + 27, out26, null); 1461 | blocks.add(new BlockAndValidity(this, b81, true, false, b79.getHash(), chainHeadHeight + 27, "b81")); 1462 | spendableOutputs.offer(b81.getCoinbaseOutput()); 1463 | 1464 | NewBlock b82 = createNextBlock(b81, chainHeadHeight + 28, out27, null); 1465 | blocks.add(new BlockAndValidity(this, b82, true, false, b82.getHash(), chainHeadHeight + 28, "b82")); 1466 | spendableOutputs.offer(b82.getCoinbaseOutput()); 1467 | 1468 | HashSet post82Mempool = new HashSet(); 1469 | post82Mempool.add(new InventoryItem(InventoryItem.Type.Transaction, b78tx.getHash())); 1470 | post82Mempool.add(new InventoryItem(InventoryItem.Type.Transaction, b79tx.getHash())); 1471 | blocks.add(new MemoryPoolState(post82Mempool, "post-b82 tx resurrection")); 1472 | 1473 | // Test invalid opcodes in dead execution paths. 1474 | // -> b81 (26) -> b82 (27) -> b83 (28) 1475 | // b83 creates a tx which contains a transaction script with an invalid opcode in a dead execution path: 1476 | // OP_FALSE OP_IF OP_INVALIDOPCODE OP_ELSE OP_TRUE OP_ENDIF 1477 | // 1478 | TransactionOutPointWithValue out28 = spendableOutputs.poll(); 1479 | Preconditions.checkState(out28 != null); 1480 | 1481 | NewBlock b83 = createNextBlock(b82, chainHeadHeight + 29, null, null); 1482 | { 1483 | Transaction tx1 = new Transaction(params); 1484 | tx1.addOutput(new TransactionOutput(params, tx1, out28.value, 1485 | new byte[]{OP_IF, (byte) OP_INVALIDOPCODE, OP_ELSE, OP_TRUE, OP_ENDIF})); 1486 | addOnlyInputToTransaction(tx1, out28, 0); 1487 | b83.addTransaction(tx1); 1488 | Transaction tx2 = new Transaction(params); 1489 | tx2.addOutput(new TransactionOutput(params, tx2, ZERO, new byte[]{OP_TRUE})); 1490 | tx2.addInput(new TransactionInput(params, tx2, new byte[]{OP_FALSE}, 1491 | new TransactionOutPoint(params, 0, tx1.getHash()))); 1492 | b83.addTransaction(tx2); 1493 | } 1494 | b83.solve(); 1495 | blocks.add(new BlockAndValidity(this, b83, true, false, b83.getHash(), chainHeadHeight + 29, "b83")); 1496 | spendableOutputs.offer(b83.getCoinbaseOutput()); 1497 | 1498 | // Reorg on/off blocks that have OP_RETURN in them (and try to spend them) 1499 | // -> b81 (26) -> b82 (27) -> b83 (28) -> b84 (29) -> b87 (30) -> b88 (31) 1500 | // \-> b85 (29) -> b86 (30) \-> b89 (32) 1501 | // 1502 | TransactionOutPointWithValue out29 = spendableOutputs.poll(); 1503 | Preconditions.checkState(out29 != null); 1504 | TransactionOutPointWithValue out30 = spendableOutputs.poll(); 1505 | Preconditions.checkState(out30 != null); 1506 | TransactionOutPointWithValue out31 = spendableOutputs.poll(); 1507 | Preconditions.checkState(out31 != null); 1508 | TransactionOutPointWithValue out32 = spendableOutputs.poll(); 1509 | Preconditions.checkState(out32 != null); 1510 | 1511 | NewBlock b84 = createNextBlock(b83, chainHeadHeight + 30, out29, null); 1512 | Transaction b84tx1 = new Transaction(params); 1513 | { 1514 | b84tx1.addOutput(new TransactionOutput(params, b84tx1, ZERO, new byte[]{OP_RETURN})); 1515 | b84tx1.addOutput(new TransactionOutput(params, b84tx1, ZERO, new byte[]{OP_TRUE})); 1516 | b84tx1.addOutput(new TransactionOutput(params, b84tx1, ZERO, new byte[]{OP_TRUE})); 1517 | b84tx1.addOutput(new TransactionOutput(params, b84tx1, ZERO, new byte[]{OP_TRUE})); 1518 | b84tx1.addOutput(new TransactionOutput(params, b84tx1, ZERO, new byte[]{OP_TRUE})); 1519 | addOnlyInputToTransaction(b84tx1, b84); 1520 | b84.addTransaction(b84tx1); 1521 | 1522 | Transaction tx2 = new Transaction(params); 1523 | tx2.addOutput(new TransactionOutput(params, tx2, ZERO, new byte[]{OP_RETURN})); 1524 | tx2.addOutput(new TransactionOutput(params, tx2, ZERO, new byte[]{OP_RETURN})); 1525 | tx2.addInput(new TransactionInput(params, tx2, new byte[]{OP_TRUE}, new TransactionOutPoint(params, 1, b84tx1))); 1526 | b84.addTransaction(tx2); 1527 | 1528 | Transaction tx3 = new Transaction(params); 1529 | tx3.addOutput(new TransactionOutput(params, tx3, ZERO, new byte[]{OP_RETURN})); 1530 | tx3.addOutput(new TransactionOutput(params, tx3, ZERO, new byte[]{OP_TRUE})); 1531 | tx3.addInput(new TransactionInput(params, tx3, new byte[]{OP_TRUE}, new TransactionOutPoint(params, 2, b84tx1))); 1532 | b84.addTransaction(tx3); 1533 | 1534 | Transaction tx4 = new Transaction(params); 1535 | tx4.addOutput(new TransactionOutput(params, tx4, ZERO, new byte[]{OP_TRUE})); 1536 | tx4.addOutput(new TransactionOutput(params, tx4, ZERO, new byte[]{OP_RETURN})); 1537 | tx4.addInput(new TransactionInput(params, tx4, new byte[]{OP_TRUE}, new TransactionOutPoint(params, 3, b84tx1))); 1538 | b84.addTransaction(tx4); 1539 | 1540 | Transaction tx5 = new Transaction(params); 1541 | tx5.addOutput(new TransactionOutput(params, tx5, ZERO, new byte[]{OP_RETURN})); 1542 | tx5.addInput(new TransactionInput(params, tx5, new byte[]{OP_TRUE}, new TransactionOutPoint(params, 4, b84tx1))); 1543 | b84.addTransaction(tx5); 1544 | } 1545 | b84.solve(); 1546 | blocks.add(new BlockAndValidity(this, b84, true, false, b84.getHash(), chainHeadHeight + 30, "b84")); 1547 | spendableOutputs.offer(b84.getCoinbaseOutput()); 1548 | 1549 | NewBlock b85 = createNextBlock(b83, chainHeadHeight + 30, out29, null); 1550 | blocks.add(new BlockAndValidity(this, b85, true, false, b84.getHash(), chainHeadHeight + 30, "b85")); 1551 | 1552 | NewBlock b86 = createNextBlock(b85, chainHeadHeight + 31, out30, null); 1553 | blocks.add(new BlockAndValidity(this, b86, true, false, b86.getHash(), chainHeadHeight + 31, "b86")); 1554 | 1555 | NewBlock b87 = createNextBlock(b84, chainHeadHeight + 31, out30, null); 1556 | blocks.add(new BlockAndValidity(this, b87, true, false, b86.getHash(), chainHeadHeight + 31, "b87")); 1557 | spendableOutputs.offer(b87.getCoinbaseOutput()); 1558 | 1559 | NewBlock b88 = createNextBlock(b87, chainHeadHeight + 32, out31, null); 1560 | blocks.add(new BlockAndValidity(this, b88, true, false, b88.getHash(), chainHeadHeight + 32, "b88")); 1561 | spendableOutputs.offer(b88.getCoinbaseOutput()); 1562 | 1563 | NewBlock b89 = createNextBlock(b88, chainHeadHeight + 33, out32, null); 1564 | { 1565 | Transaction tx = new Transaction(params); 1566 | tx.addOutput(new TransactionOutput(params, tx, ZERO, new byte[]{OP_TRUE})); 1567 | tx.addInput(new TransactionInput(params, tx, new byte[]{OP_TRUE}, new TransactionOutPoint(params, 0, b84tx1))); 1568 | b89.addTransaction(tx); 1569 | b89.solve(); 1570 | } 1571 | blocks.add(new BlockAndValidity(this, b89, false, true, b88.getHash(), chainHeadHeight + 32, "b89")); 1572 | 1573 | // The remaining tests arent designed to fit in the standard flow, and thus must always come last 1574 | // Add new tests here. 1575 | 1576 | //TODO: Explicitly address MoneyRange() checks 1577 | 1578 | if (!runBarelyExpensiveTests) { 1579 | if (outStream != null) 1580 | outStream.close(); 1581 | 1582 | // (finally) return the created chain 1583 | return ret; 1584 | } 1585 | 1586 | // Test massive reorgs (in terms of block count/size) 1587 | // -> b81 (26) -> b82 (27) -> b83 (28) -> b84 (29) -> b87 (30) -> b88 (31) -> lots of blocks -> b1000 1588 | // \-> b85 (29) -> b86 (30) \-> lots more blocks 1589 | // 1590 | NewBlock largeReorgFinal; 1591 | int LARGE_REORG_SIZE = 1008; // +/- a week of blocks 1592 | int largeReorgLastHeight = chainHeadHeight + 33 + LARGE_REORG_SIZE + 1; 1593 | { 1594 | NewBlock nextBlock = b88; 1595 | int nextHeight = chainHeadHeight + 33; 1596 | TransactionOutPointWithValue largeReorgOutput = out32; 1597 | for (int i = 0; i < LARGE_REORG_SIZE; i++) { 1598 | nextBlock = createNextBlock(nextBlock, nextHeight, largeReorgOutput, null); 1599 | Transaction tx = new Transaction(params); 1600 | byte[] outputScript = new byte[Block.MAX_BLOCK_SIZE - nextBlock.block.getMessageSize() - 65]; 1601 | Arrays.fill(outputScript, (byte) OP_FALSE); 1602 | tx.addOutput(new TransactionOutput(params, tx, ZERO, outputScript)); 1603 | addOnlyInputToTransaction(tx, nextBlock); 1604 | nextBlock.addTransaction(tx); 1605 | nextBlock.solve(); 1606 | blocks.add(new BlockAndValidity(this, nextBlock, true, false, nextBlock.getHash(), nextHeight++, "large reorg initial blocks " + i)); 1607 | spendableOutputs.offer(nextBlock.getCoinbaseOutput()); 1608 | largeReorgOutput = spendableOutputs.poll(); 1609 | } 1610 | NewBlock reorgBase = b88; 1611 | int reorgBaseHeight = chainHeadHeight + 33; 1612 | for (int i = 0; i < LARGE_REORG_SIZE; i++) { 1613 | reorgBase = createNextBlock(reorgBase, reorgBaseHeight++, null, null); 1614 | blocks.add(new BlockAndValidity(this, reorgBase, true, false, nextBlock.getHash(), nextHeight - 1, "large reorg reorg block " + i)); 1615 | } 1616 | reorgBase = createNextBlock(reorgBase, reorgBaseHeight, null, null); 1617 | blocks.add(new BlockAndValidity(this, reorgBase, true, false, reorgBase.getHash(), reorgBaseHeight, "large reorg reorging block")); 1618 | nextBlock = createNextBlock(nextBlock, nextHeight, null, null); 1619 | blocks.add(new BlockAndValidity(this, nextBlock, true, false, reorgBase.getHash(), nextHeight++, "large reorg second reorg initial")); 1620 | spendableOutputs.offer(nextBlock.getCoinbaseOutput()); 1621 | nextBlock = createNextBlock(nextBlock, nextHeight, null, null); 1622 | spendableOutputs.poll(); 1623 | blocks.add(new BlockAndValidity(this, nextBlock, true, false, nextBlock.getHash(), nextHeight++, "large reorg second reorg")); 1624 | spendableOutputs.offer(nextBlock.getCoinbaseOutput()); 1625 | largeReorgFinal = nextBlock; 1626 | } 1627 | ret.maximumReorgBlockCount = Math.max(ret.maximumReorgBlockCount, LARGE_REORG_SIZE + 2); 1628 | 1629 | // Test massive reorgs (in terms of tx count) 1630 | // -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20) -> b72 (21) -> b1001 (22) -> lots of outputs -> lots of spends 1631 | // Reorg back to: 1632 | // -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20) -> b72 (21) -> b1001 (22) -> empty blocks 1633 | // 1634 | NewBlock b1001 = createNextBlock(largeReorgFinal, largeReorgLastHeight + 1, spendableOutputs.poll(), null); 1635 | blocks.add(new BlockAndValidity(this, b1001, true, false, b1001.getHash(), largeReorgLastHeight + 1, "b1001")); 1636 | spendableOutputs.offer(b1001.getCoinbaseOutput()); 1637 | int heightAfter1001 = largeReorgLastHeight + 2; 1638 | 1639 | if (runExpensiveTests) { 1640 | // No way you can fit this test in memory 1641 | Preconditions.checkArgument(blockStorageFile != null); 1642 | 1643 | NewBlock lastBlock = b1001; 1644 | TransactionOutPoint lastOutput = new TransactionOutPoint(params, 1, b1001.block.getTransactions().get(1).getHash()); 1645 | int blockCountAfter1001; 1646 | int nextHeight = heightAfter1001; 1647 | 1648 | List hashesToSpend = new LinkedList(); // all index 0 1649 | final int TRANSACTION_CREATION_BLOCKS = 100; 1650 | for (blockCountAfter1001 = 0; blockCountAfter1001 < TRANSACTION_CREATION_BLOCKS; blockCountAfter1001++) { 1651 | NewBlock block = createNextBlock(lastBlock, nextHeight++, null, null); 1652 | while (block.block.getMessageSize() < Block.MAX_BLOCK_SIZE - 500) { 1653 | Transaction tx = new Transaction(params); 1654 | tx.addInput(lastOutput.getHash(), lastOutput.getIndex(), OP_NOP_SCRIPT); 1655 | tx.addOutput(ZERO, OP_TRUE_SCRIPT); 1656 | tx.addOutput(ZERO, OP_TRUE_SCRIPT); 1657 | lastOutput = new TransactionOutPoint(params, 1, tx.getHash()); 1658 | hashesToSpend.add(tx.getHash()); 1659 | block.addTransaction(tx); 1660 | } 1661 | block.solve(); 1662 | blocks.add(new BlockAndValidity(this, block, true, false, block.getHash(), nextHeight - 1, 1663 | "post-b1001 repeated transaction generator " + blockCountAfter1001 + "/" + TRANSACTION_CREATION_BLOCKS).setSendOnce(true)); 1664 | lastBlock = block; 1665 | } 1666 | 1667 | Iterator hashes = hashesToSpend.iterator(); 1668 | for (int i = 0; hashes.hasNext(); i++) { 1669 | NewBlock block = createNextBlock(lastBlock, nextHeight++, null, null); 1670 | while (block.block.getMessageSize() < Block.MAX_BLOCK_SIZE - 500 && hashes.hasNext()) { 1671 | Transaction tx = new Transaction(params); 1672 | tx.addInput(hashes.next(), 0, OP_NOP_SCRIPT); 1673 | tx.addOutput(ZERO, OP_TRUE_SCRIPT); 1674 | block.addTransaction(tx); 1675 | } 1676 | block.solve(); 1677 | blocks.add(new BlockAndValidity(this, block, true, false, block.getHash(), nextHeight - 1, 1678 | "post-b1001 repeated transaction spender " + i).setSendOnce(true)); 1679 | lastBlock = block; 1680 | blockCountAfter1001++; 1681 | } 1682 | 1683 | // Reorg back to b1001 + empty blocks 1684 | Sha256Hash firstHash = lastBlock.getHash(); 1685 | int height = nextHeight - 1; 1686 | nextHeight = heightAfter1001; 1687 | lastBlock = b1001; 1688 | for (int i = 0; i < blockCountAfter1001; i++) { 1689 | NewBlock block = createNextBlock(lastBlock, nextHeight++, null, null); 1690 | blocks.add(new BlockAndValidity(this, block, true, false, firstHash, height, "post-b1001 empty reorg block " + i + "/" + blockCountAfter1001)); 1691 | lastBlock = block; 1692 | } 1693 | 1694 | // Try to spend from the other chain 1695 | NewBlock b1002 = createNextBlock(lastBlock, nextHeight, null, null); 1696 | { 1697 | Transaction tx = new Transaction(params); 1698 | tx.addInput(hashesToSpend.get(0), 0, OP_NOP_SCRIPT); 1699 | tx.addOutput(ZERO, OP_TRUE_SCRIPT); 1700 | b1002.addTransaction(tx); 1701 | } 1702 | b1002.solve(); 1703 | blocks.add(new BlockAndValidity(this, b1002, false, true, firstHash, height, "b1002")); 1704 | 1705 | // Now actually reorg 1706 | NewBlock b1003 = createNextBlock(lastBlock, nextHeight, null, null); 1707 | blocks.add(new BlockAndValidity(this, b1003, true, false, b1003.getHash(), nextHeight, "b1003")); 1708 | 1709 | // Now try to spend again 1710 | NewBlock b1004 = createNextBlock(b1003, nextHeight + 1, null, null); 1711 | { 1712 | Transaction tx = new Transaction(params); 1713 | tx.addInput(hashesToSpend.get(0), 0, OP_NOP_SCRIPT); 1714 | tx.addOutput(ZERO, OP_TRUE_SCRIPT); 1715 | b1004.addTransaction(tx); 1716 | } 1717 | b1004.solve(); 1718 | blocks.add(new BlockAndValidity(this, b1004, false, true, b1003.getHash(), nextHeight, "b1004")); 1719 | 1720 | ret.maximumReorgBlockCount = Math.max(ret.maximumReorgBlockCount, blockCountAfter1001); 1721 | } 1722 | 1723 | if (outStream != null) 1724 | outStream.close(); 1725 | 1726 | // (finally) return the created chain 1727 | return ret; 1728 | } 1729 | 1730 | private byte uniquenessCounter = 0; 1731 | 1732 | private NewBlock createNextBlock(Block baseBlock, int nextBlockHeight, @Nullable TransactionOutPointWithValue prevOut, 1733 | Coin additionalCoinbaseValue) throws ScriptException { 1734 | Integer height = blockToHeightMap.get(baseBlock.getHash()); 1735 | if (height != null) 1736 | checkState(height == nextBlockHeight - 1); 1737 | Coin coinbaseValue = FIFTY_COINS.shiftRight(nextBlockHeight / params.getSubsidyDecreaseBlockCount()) 1738 | .add((prevOut != null ? prevOut.value.subtract(SATOSHI) : ZERO)) 1739 | .add(additionalCoinbaseValue == null ? ZERO : additionalCoinbaseValue); 1740 | Block block = baseBlock.createNextBlockWithCoinbase(coinbaseOutKeyPubKey, coinbaseValue); 1741 | Transaction t = new Transaction(params); 1742 | if (prevOut != null) { 1743 | // Entirely invalid scriptPubKey to ensure we aren't pre-verifying too much 1744 | t.addOutput(new TransactionOutput(params, t, ZERO, new byte[]{(byte) (new Random().nextInt() & 0xff), uniquenessCounter++})); 1745 | // Spendable output 1746 | t.addOutput(new TransactionOutput(params, t, SATOSHI, new byte[]{OP_1})); 1747 | addOnlyInputToTransaction(t, prevOut); 1748 | block.addTransaction(t); 1749 | block.solve(); 1750 | } 1751 | return new NewBlock(block, prevOut == null ? null : new TransactionOutPointWithValue(t, 1)); 1752 | } 1753 | 1754 | private NewBlock createNextBlock(NewBlock baseBlock, int nextBlockHeight, @Nullable TransactionOutPointWithValue prevOut, 1755 | Coin additionalCoinbaseValue) throws ScriptException { 1756 | return createNextBlock(baseBlock.block, nextBlockHeight, prevOut, additionalCoinbaseValue); 1757 | } 1758 | 1759 | private void addOnlyInputToTransaction(Transaction t, NewBlock block) throws ScriptException { 1760 | addOnlyInputToTransaction(t, block.getSpendableOutput(), TransactionInput.NO_SEQUENCE); 1761 | } 1762 | 1763 | private void addOnlyInputToTransaction(Transaction t, TransactionOutPointWithValue prevOut) throws ScriptException { 1764 | addOnlyInputToTransaction(t, prevOut, TransactionInput.NO_SEQUENCE); 1765 | } 1766 | 1767 | private void addOnlyInputToTransaction(Transaction t, TransactionOutPointWithValue prevOut, long sequence) throws ScriptException { 1768 | TransactionInput input = new TransactionInput(params, t, new byte[]{}, prevOut.outpoint); 1769 | input.setSequenceNumber(sequence); 1770 | t.addInput(input); 1771 | 1772 | if (prevOut.scriptPubKey.getChunks().get(0).equalsOpCode(OP_TRUE)) { 1773 | input.setScriptSig(new ScriptBuilder().op(OP_1).build()); 1774 | } else { 1775 | // Sign input 1776 | checkState(prevOut.scriptPubKey.isSentToRawPubKey()); 1777 | Sha256Hash hash = t.hashForSignature(0, prevOut.scriptPubKey, SigHash.ALL, false); 1778 | input.setScriptSig(ScriptBuilder.createInputScript( 1779 | new TransactionSignature(coinbaseOutKey.sign(hash), SigHash.ALL, false)) 1780 | ); 1781 | } 1782 | } 1783 | } 1784 | 1785 | /** 1786 | * Represents a block which is sent to the tested application and which the application must either reject or accept, 1787 | * depending on the flags in the rule 1788 | */ 1789 | class BlockAndValidity extends Rule { 1790 | Block block; 1791 | Sha256Hash blockHash; 1792 | boolean connects; 1793 | boolean throwsException; 1794 | boolean sendOnce; // We can throw away the memory for this block once we send it the first time (if bitcoind asks again, its broken) 1795 | Sha256Hash hashChainTipAfterBlock; 1796 | int heightAfterBlock; 1797 | FullBlockTestGenerator generator; 1798 | 1799 | public BlockAndValidity(FullBlockTestGenerator generator, Block block, boolean connects, boolean throwsException, Sha256Hash hashChainTipAfterBlock, int heightAfterBlock, String blockName) { 1800 | super(blockName); 1801 | if (connects && throwsException) 1802 | throw new RuntimeException("A block cannot connect if an exception was thrown while adding it."); 1803 | this.block = block; 1804 | this.blockHash = block.getHash(); 1805 | this.connects = connects; 1806 | this.throwsException = throwsException; 1807 | this.hashChainTipAfterBlock = hashChainTipAfterBlock; 1808 | this.heightAfterBlock = heightAfterBlock; 1809 | this.generator = generator; 1810 | 1811 | // Keep track of the set of blocks indexed by hash 1812 | generator.hashHeaderMap.put(block.getHash(), block.cloneAsHeader()); 1813 | 1814 | // Double-check that we are always marking any given block at the same height 1815 | Integer height = generator.blockToHeightMap.get(hashChainTipAfterBlock); 1816 | if (height != null) 1817 | checkState(height == heightAfterBlock); 1818 | else 1819 | generator.blockToHeightMap.put(hashChainTipAfterBlock, heightAfterBlock); 1820 | } 1821 | 1822 | public BlockAndValidity(FullBlockTestGenerator generator, NewBlock block, boolean connects, boolean throwsException, Sha256Hash hashChainTipAfterBlock, int heightAfterBlock, String blockName) { 1823 | this(generator, block.block, connects, throwsException, hashChainTipAfterBlock, heightAfterBlock, blockName); 1824 | generator.coinbaseBlockMap.put(block.getCoinbaseOutput().outpoint.getHash(), block.getHash()); 1825 | Integer blockHeight = generator.blockToHeightMap.get(block.block.getPrevBlockHash()); 1826 | if (blockHeight != null) { 1827 | blockHeight++; 1828 | for (Transaction t : block.block.getTransactions()) 1829 | for (TransactionInput in : t.getInputs()) { 1830 | Sha256Hash blockSpendingHash = generator.coinbaseBlockMap.get(in.getOutpoint().getHash()); 1831 | checkState(blockSpendingHash == null || generator.blockToHeightMap.get(blockSpendingHash) == null || 1832 | generator.blockToHeightMap.get(blockSpendingHash) == blockHeight - generator.params.getSpendableCoinbaseDepth()); 1833 | } 1834 | } 1835 | } 1836 | 1837 | public BlockAndValidity setSendOnce(boolean sendOnce) { 1838 | this.sendOnce = sendOnce; 1839 | return this; 1840 | } 1841 | } 1842 | --------------------------------------------------------------------------------