mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2024-11-19 01:40:55 +01:00
2021 04 24 bitcoin s scripts (#2961)
* Add new app/scripts project which is meant to store useful bitcoin-s scripts * Add ScanBitcoind with an example of counting segwit txs between a range * Fix bug of creating a new actor system everytime BitcoindRpcClient.apply(instance) is called * Add BitcoindRpcClient.fromVersionNoSystem() * Take ben's suggestions * Fix compile * Rework P2SHScriptSignature.isStandardNonP2SH() to account for nesting p2sh script inside of it * fix compile on java8 * Enable app packaging in scripts project
This commit is contained in:
parent
27afb66220
commit
9ecea9f710
@ -0,0 +1,94 @@
|
||||
package org.bitcoins.scripts
|
||||
|
||||
import akka.NotUsed
|
||||
import akka.stream.scaladsl.{Keep, Sink, Source}
|
||||
import org.bitcoins.core.protocol.blockchain.Block
|
||||
import org.bitcoins.core.protocol.transaction.WitnessTransaction
|
||||
import org.bitcoins.rpc.client.common.BitcoindRpcClient
|
||||
import org.bitcoins.server.BitcoindRpcAppConfig
|
||||
import org.bitcoins.server.routes.BitcoinSRunner
|
||||
|
||||
import scala.concurrent.Future
|
||||
|
||||
/** Useful script for scanning bitcoind
|
||||
* This file assumes you have pre-configured the connection
|
||||
* between bitcoin-s and bitcoind inside of bitcoin-s.conf
|
||||
* @see https://bitcoin-s.org/docs/config/configuration#example-configuration-file
|
||||
*/
|
||||
class ScanBitcoind(override val args: Array[String]) extends BitcoinSRunner {
|
||||
override val actorSystemName = "scan-bitcoind"
|
||||
|
||||
implicit val rpcAppConfig: BitcoindRpcAppConfig =
|
||||
BitcoindRpcAppConfig(datadir, baseConfig)
|
||||
|
||||
override def startup: Future[Unit] = {
|
||||
|
||||
val bitcoind = rpcAppConfig.client
|
||||
|
||||
val startHeight = 675000
|
||||
val endHeightF: Future[Int] = bitcoind.getBlockCount
|
||||
|
||||
for {
|
||||
endHeight <- endHeightF
|
||||
_ <- countSegwitTxs(bitcoind, startHeight, endHeight)
|
||||
_ <- system.terminate()
|
||||
} yield {
|
||||
sys.exit(0)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** Searches a given Source[Int] that represents block heights applying f to them and returning a Seq[T] with the results */
|
||||
def searchBlocks[T](
|
||||
bitcoind: BitcoindRpcClient,
|
||||
source: Source[Int, NotUsed],
|
||||
f: Block => T,
|
||||
numParallelism: Int =
|
||||
Runtime.getRuntime.availableProcessors() * 2): Future[Seq[T]] = {
|
||||
source
|
||||
.mapAsync(parallelism = numParallelism) { height =>
|
||||
bitcoind
|
||||
.getBlockHash(height)
|
||||
.flatMap(h => bitcoind.getBlockRaw(h))
|
||||
.map(b => (b, height))
|
||||
}
|
||||
.mapAsync(numParallelism) { case (block, height) =>
|
||||
logger.info(
|
||||
s"Searching block at height=$height hashBE=${block.blockHeader.hashBE.hex}")
|
||||
Future {
|
||||
f(block)
|
||||
}
|
||||
}
|
||||
.toMat(Sink.seq)(Keep.right)
|
||||
.run()
|
||||
}
|
||||
|
||||
def countSegwitTxs(
|
||||
bitcoind: BitcoindRpcClient,
|
||||
startHeight: Int,
|
||||
endHeight: Int): Future[Unit] = {
|
||||
val startTime = System.currentTimeMillis()
|
||||
val source: Source[Int, NotUsed] = Source(startHeight.to(endHeight))
|
||||
|
||||
//in this simple example, we are going to count the number of witness transactions
|
||||
val countSegwitTxs: Block => Int = { block: Block =>
|
||||
block.transactions.count(_.isInstanceOf[WitnessTransaction])
|
||||
}
|
||||
val countsF: Future[Seq[Int]] = for {
|
||||
counts <- searchBlocks[Int](bitcoind, source, countSegwitTxs)
|
||||
} yield counts
|
||||
|
||||
val countF: Future[Int] = countsF.map(_.sum)
|
||||
|
||||
for {
|
||||
count <- countF
|
||||
endTime = System.currentTimeMillis()
|
||||
_ = println(
|
||||
s"Count of segwit txs from height=${startHeight} to endHeight=${endHeight} is ${count}. It took ${endTime - startTime}ms ")
|
||||
} yield ()
|
||||
}
|
||||
}
|
||||
|
||||
object ScanBitcoind extends App {
|
||||
new ScanBitcoind(args).run()
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package org.bitcoins.scripts
|
||||
|
||||
import org.bitcoins.server.BitcoinSAppConfig
|
||||
import org.bitcoins.server.routes.BitcoinSRunner
|
||||
|
||||
import java.nio.file.Paths
|
||||
import scala.concurrent.Future
|
||||
|
||||
/** This script zips your $HOME/.bitcoin-s/ directory to a specified path, excluding chaindb.sqlite */
|
||||
class ZipDatadir(override val args: Array[String]) extends BitcoinSRunner {
|
||||
|
||||
override def actorSystemName: String = "Zip-datadir"
|
||||
|
||||
implicit lazy val conf: BitcoinSAppConfig =
|
||||
BitcoinSAppConfig(datadir, baseConfig)
|
||||
|
||||
override def startup: Future[Unit] = {
|
||||
|
||||
//replace the line below with where you want to zip too
|
||||
val path = Paths.get("/tmp", "bitcoin-s.zip")
|
||||
val target = conf.zipDatadir(path)
|
||||
logger.info(s"Done zipping to $target!")
|
||||
for {
|
||||
_ <- system.terminate()
|
||||
} yield sys.exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
object Zip extends App {
|
||||
new ZipDatadir(args).run()
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
package org.bitcoins.server
|
||||
|
||||
import akka.actor.ActorSystem
|
||||
import com.typesafe.config.Config
|
||||
import org.bitcoins.db._
|
||||
import org.bitcoins.node.NodeType
|
||||
@ -128,9 +127,7 @@ case class BitcoindRpcAppConfig(
|
||||
|
||||
lazy val client: BitcoindRpcClient = {
|
||||
val version = bitcoindInstance.getVersion
|
||||
implicit val system: ActorSystem =
|
||||
ActorSystem.create("bitcoind-rpc-client-created-by-bitcoin-s", config)
|
||||
BitcoindRpcClient.fromVersion(version, bitcoindInstance)
|
||||
BitcoindRpcClient.fromVersionNoSystem(version, bitcoindInstance)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -222,6 +222,8 @@ object BitcoindRpcClient {
|
||||
*/
|
||||
private[rpc] val ActorSystemName = "bitcoind-rpc-client-created-by-bitcoin-s"
|
||||
|
||||
implicit private lazy val system = ActorSystem.create(ActorSystemName)
|
||||
|
||||
/** Creates an RPC client from the given instance.
|
||||
*
|
||||
* Behind the scenes, we create an actor system for
|
||||
@ -229,8 +231,7 @@ object BitcoindRpcClient {
|
||||
* manually specify an actor system for the RPC client.
|
||||
*/
|
||||
def apply(instance: BitcoindInstance): BitcoindRpcClient = {
|
||||
implicit val system = ActorSystem.create(ActorSystemName)
|
||||
withActorSystem(instance)
|
||||
withActorSystem(instance)(system)
|
||||
}
|
||||
|
||||
/** Creates an RPC client from the given instance,
|
||||
@ -272,6 +273,12 @@ object BitcoindRpcClient {
|
||||
|
||||
bitcoind
|
||||
}
|
||||
|
||||
def fromVersionNoSystem(
|
||||
version: BitcoindVersion,
|
||||
instance: BitcoindInstance): BitcoindRpcClient = {
|
||||
fromVersion(version, instance)(system)
|
||||
}
|
||||
}
|
||||
|
||||
sealed trait BitcoindVersion
|
||||
|
16
build.sbt
16
build.sbt
@ -207,7 +207,8 @@ lazy val `bitcoin-s` = project
|
||||
oracleServerTest,
|
||||
serverRoutes,
|
||||
lndRpc,
|
||||
lndRpcTest
|
||||
lndRpcTest,
|
||||
scripts
|
||||
)
|
||||
.dependsOn(
|
||||
secp256k1jni,
|
||||
@ -253,7 +254,8 @@ lazy val `bitcoin-s` = project
|
||||
oracleServerTest,
|
||||
serverRoutes,
|
||||
lndRpc,
|
||||
lndRpcTest
|
||||
lndRpcTest,
|
||||
scripts
|
||||
)
|
||||
.settings(CommonSettings.settings: _*)
|
||||
// unidoc aggregates Scaladocs for all subprojects into one big doc
|
||||
@ -700,6 +702,16 @@ lazy val oracleExplorerClient = project
|
||||
)
|
||||
.dependsOn(coreJVM, appCommons, testkit % "test->test")
|
||||
|
||||
lazy val scripts = project
|
||||
.in(file("app/scripts"))
|
||||
.settings(CommonSettings.settings: _*)
|
||||
.settings(
|
||||
name := "bitcoin-s-scripts",
|
||||
publishArtifact := false //do not want to publish our scripts
|
||||
)
|
||||
.dependsOn(appServer)
|
||||
.enablePlugins(JavaAppPackaging)
|
||||
|
||||
/** Given a database name, returns the appropriate
|
||||
* Flyway settings we apply to a project (chain, node, wallet)
|
||||
*/
|
||||
|
@ -0,0 +1,36 @@
|
||||
package org.bitcoins.core.protocol.script
|
||||
|
||||
import org.bitcoins.core.script.constant.ScriptConstant
|
||||
import org.bitcoins.testkitcore.util.BitcoinSJvmTest
|
||||
|
||||
class ConditionalScriptPubKeyTest extends BitcoinSJvmTest {
|
||||
|
||||
behavior of "ConditionalScriptPubKey"
|
||||
|
||||
it must "be able to parse a conditional spk with a nested p2sh script" in {
|
||||
//see: https://github.com/bitcoin-s/bitcoin-s/issues/2962
|
||||
val scriptSigHex =
|
||||
"48304502207ffb30631d837895ac1b415408b70a809090b25d4070198043299fe247f62d2602210089b52f0d243dea1e4313ef67723b0d0642ff77bb6ca05ea85296b2539c797f5c81513d632103184b16f5d6c01e2d6ded3f8a292e5b81608318ecb8e93aa3747bc88b8dbf256cac67a914f5862841f254a1483eab66909ae588e45d617c5e8768"
|
||||
|
||||
val scriptSig = ScriptSignature.fromAsmHex(scriptSigHex)
|
||||
|
||||
scriptSig match {
|
||||
case p2sh: P2SHScriptSignature =>
|
||||
assert(p2sh.redeemScript.isInstanceOf[IfConditionalScriptPubKey])
|
||||
case x => fail(s"Did not parse a p2sh script sig, got=$x")
|
||||
}
|
||||
}
|
||||
|
||||
it must "consider a redeem script that contains OP_IF in it with a nested p2sh script pubkey in it" in {
|
||||
//see: https://github.com/bitcoin-s/bitcoin-s/issues/2962
|
||||
val hex =
|
||||
"632103184b16f5d6c01e2d6ded3f8a292e5b81608318ecb8e93aa3747bc88b8dbf256cac67a914f5862841f254a1483eab66909ae588e45d617c5e8768"
|
||||
val scriptPubKey = RawScriptPubKey.fromAsmHex(hex)
|
||||
|
||||
assert(scriptPubKey.isInstanceOf[IfConditionalScriptPubKey])
|
||||
|
||||
val constant = ScriptConstant.fromHex(hex)
|
||||
|
||||
assert(P2SHScriptSignature.isRedeemScript(constant))
|
||||
}
|
||||
}
|
@ -613,9 +613,6 @@ sealed trait ConditionalScriptPubKey extends RawScriptPubKey {
|
||||
|
||||
val opElseIndex: Int = opElseIndexOpt.get
|
||||
|
||||
require(!P2SHScriptPubKey.isValidAsm(trueSPK.asm) && !P2SHScriptPubKey
|
||||
.isValidAsm(falseSPK.asm),
|
||||
"ConditionalScriptPubKey cannot wrap P2SH")
|
||||
require(
|
||||
!WitnessScriptPubKey
|
||||
.isValidAsm(trueSPK.asm) && !WitnessScriptPubKey
|
||||
|
@ -273,20 +273,42 @@ object P2SHScriptSignature extends ScriptFactory[P2SHScriptSignature] {
|
||||
_: P2PKScriptPubKey | _: P2PKWithTimeoutScriptPubKey |
|
||||
_: WitnessScriptPubKeyV0 | _: UnassignedWitnessScriptPubKey =>
|
||||
true
|
||||
case _: P2SHScriptPubKey =>
|
||||
true
|
||||
case EmptyScriptPubKey => isRecursiveCall // Fine if nested
|
||||
case conditional: ConditionalScriptPubKey =>
|
||||
isStandardNonP2SH(conditional.firstSPK,
|
||||
isRecursiveCall = true) && isStandardNonP2SH(
|
||||
conditional.secondSPK,
|
||||
isRecursiveCall = true)
|
||||
val first =
|
||||
isStandardNonP2SH(conditional.firstSPK, isRecursiveCall = true)
|
||||
|
||||
val second =
|
||||
isStandardNonP2SH(conditional.secondSPK, isRecursiveCall = true)
|
||||
|
||||
//we need to see if we have a p2sh scriptpubkey nested
|
||||
//inside of the conditional spk. This can actually happen
|
||||
//when you literally just want to use the script OP_HASH160 <bytes> OP_EQUAL
|
||||
//independent of the normal p2sh flow
|
||||
//see: https://github.com/bitcoin-s/bitcoin-s/issues/2962
|
||||
(first, second) match {
|
||||
case (true, true) => true
|
||||
case (true, false) =>
|
||||
P2SHScriptPubKey.isValidAsm(conditional.secondSPK.asm)
|
||||
case (false, true) =>
|
||||
P2SHScriptPubKey.isValidAsm(conditional.firstSPK.asm)
|
||||
case (false, false) =>
|
||||
val isP2SHFirst =
|
||||
P2SHScriptPubKey.isValidAsm(conditional.firstSPK.asm)
|
||||
val isP2SHSecond =
|
||||
P2SHScriptPubKey.isValidAsm(conditional.secondSPK.asm)
|
||||
|
||||
isP2SHFirst && isP2SHSecond
|
||||
}
|
||||
case locktime: LockTimeScriptPubKey =>
|
||||
if (Try(locktime.locktime).isSuccess) {
|
||||
isStandardNonP2SH(locktime.nestedScriptPubKey, isRecursiveCall = true)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
case _: NonStandardScriptPubKey | _: WitnessCommitment |
|
||||
_: P2SHScriptPubKey =>
|
||||
case _: NonStandardScriptPubKey | _: WitnessCommitment =>
|
||||
false
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user