2021 12 08 block parsing bug caused by 65 byte taproot signatures (#3887)

* Add block  0000000000000000000593310d3b0cdc082af49f38b8a1611239072aef8433a8 test vector

* Add test cases

* wip

* Fix bug where we were classifying taproot txs as P2WPKHWitnessV0

* revert logging level

* Some cleanup

* Add logback.xml for scripts, remove scheduler in scan bitcoind

* Fix imports

* Take ben's suggestion and add check to CryptoUtil.isValidPubKey
This commit is contained in:
Chris Stewart 2021-12-09 06:38:25 -06:00 committed by GitHub
parent cb704da927
commit 0d37c4b54f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 143 additions and 22 deletions

View file

@ -0,0 +1,109 @@
<configuration scan="true" scanPeriod="15 seconds" >
<appender name="STDOUT" target="System.out" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%date{yyyy-MM-dd'T'HH:mm:ss,SSXXX, UTC}UTC %level [%logger{0}] %msg%n</pattern>
</encoder>
</appender>
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<queueSize>8192</queueSize>
<neverBlock>true</neverBlock>
<appender-ref ref="STDOUT" />
</appender>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>bitcoin-s-scan-bitcoind.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- hourly rollover -->
<fileNamePattern>${bitcoins.log.location}/logs/bitcoin-s-%d{yyyy-MM-dd_HH}.%i.log</fileNamePattern>
<!-- each file should be at most 100MB, keep 2 days of history, and at most 2GB in the archive -->
<maxFileSize>100MB</maxFileSize>
<maxHistory>48</maxHistory>
<totalSizeCap>2GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>%date{yyyy-MM-dd'T'HH:mm:ss,SSXXX, UTC}UTC %level [%logger{0}] %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="ASYNC"/>
<appender-ref ref="FILE"/>
</root>
<!-- ╔═══════════════════════╗ -->
<!-- ║ Bitcoin-S logging ║-->
<!-- ╚═══════════════════════╝ -->
<!-- ╔═══════════════════╗ -->
<!-- ║ Configuration ║ -->
<!-- ╚═══════════════════╝ -->
<!-- inspect resolved DB connection -->
<logger name="org.bitcoins.db.SafeDatabase" level="WARN"/>
<!-- inspect resolved config -->
<logger name="org.bitcoins.chain.config" level="WARN"/>
<logger name="org.bitcoins.node.config" level="WARN"/>
<logger name="org.bitcoins.wallet.config" level="WARN"/>
<!-- inspect table creation, etc -->
<logger name="org.bitcoins.chain.db" level="WARN" />
<logger name="org.bitcoins.node.db" level="WARN" />
<logger name="org.bitcoins.wallet.db" level="WARN" />
<!-- ╔═════════════════╗ -->
<!-- ║ Node module ║ -->
<!-- ╚═════════════════╝ -->
<!-- See incoming message names and the peer it's sent from -->
<logger name="org.bitcoins.node.networking.peer.PeerMessageReceiver" level="INFO"/>
<!-- See outgoing message names and the peer it's sent to -->
<logger name="org.bitcoins.node.networking.peer.PeerMessageSender" level="INFO"/>
<!-- Inspect handling of headers and inventory messages -->
<logger name="org.bitcoins.node.networking.peer.DataMessageHandler" level="INFO"/>
<!-- inspect TCP details -->
<logger name="org.bitcoins.node.networking.P2PClientActor" level="INFO"/>
<!-- ╔════════════════════╗ -->
<!-- ║ Chain module ║ -->
<!-- ╚════════════════════╝ -->
<!-- See queries received by chain handler, as well as result of -->
<!-- connecting new block headers to chain -->
<logger name="org.bitcoins.chain.blockchain.ChainHandler" level="WARN"/>
<logger name="org.bitcoins.chain.validation" level="WARN"/>
<!-- ╔═════════════════════╗ -->
<!-- ║ Wallet module ║ -->
<!-- ╚═════════════════════╝ -->
<!-- ╔═══════════════════════════╗ -->
<!-- ║ Bitcoin-S logging end ║-->
<!-- ╚═══════════════════════════╝ -->
<!-- ╔═════════════════════════╗ -->
<!-- ║ External libraries ║ -->
<!-- ╚═════════════════════════╝ -->
<!-- Disable slick logging in server -->
<logger name="slick" level="OFF"/>
<logger name="com.zaxxer" level="INFO"/>
<!-- Get rid of messages like this:
Connection attempt failed. Backing off new connection
attempts for at least 800 milliseconds. -->
<logger name="akka.http.impl.engine.client.PoolGateway" level="OFF"/>
<!-- get rid of "Slf4jLogger started" messages -->
<logger name="akka.event.slf4j.Slf4jLogger" level="OFF"/>
<!-- get rid of "Running CoordinatedShutdown Phase" messages -->
<logger name="akka.actor.slf4j.CoordinatedShutdown" level="OFF"/>
</configuration>

View file

@ -0,0 +1 @@
akka.http.host-connection-pool.max-connections=128

View file

@ -3,8 +3,6 @@ package org.bitcoins.scripts
import akka.NotUsed
import akka.actor.ActorSystem
import akka.stream.scaladsl.{Keep, Sink, Source}
import org.bitcoins.core.config.MainNet
import org.bitcoins.core.protocol.Bech32mAddress
import org.bitcoins.core.protocol.blockchain.Block
import org.bitcoins.core.protocol.script._
import org.bitcoins.core.protocol.transaction.{Transaction, WitnessTransaction}
@ -16,7 +14,6 @@ import org.bitcoins.server.util.BitcoinSAppScalaDaemon
import java.time.Instant
import scala.concurrent.Future
import scala.concurrent.duration.DurationInt
/** Useful script for scanning bitcoind
* This file assumes you have pre-configured the connection
@ -35,17 +32,14 @@ class ScanBitcoind()(implicit
// val startHeight = 675000
val endHeightF: Future[Int] = bitcoindF.flatMap(_.getBlockCount)
system.scheduler.scheduleAtFixedRate(0.seconds, 1.minutes) { () =>
val f = for {
bitcoind <- bitcoindF
endHeight <- endHeightF
_ <- countWitV1MempoolTxs(bitcoind)
_ <- countTaprootTxsInBlocks(endHeight, 6, bitcoind)
} yield ()
f.failed.foreach(err =>
logger.error(s"Failed to count witnes v1 mempool txs", err))
()
}
val f = for {
bitcoind <- bitcoindF
endHeight <- endHeightF
//_ <- countWitV1MempoolTxs(bitcoind)
_ <- countTaprootTxsInBlocks(endHeight, 10000, bitcoind)
} yield ()
f.failed.foreach(err =>
logger.error(s"Failed to count witness v1 mempool txs", err))
Future.unit
}
@ -60,8 +54,8 @@ class ScanBitcoind()(implicit
bitcoind: BitcoindRpcClient,
source: Source[Int, NotUsed],
f: Block => T,
numParallelism: Int =
Runtime.getRuntime.availableProcessors() * 2): Future[Seq[T]] = {
numParallelism: Int = Runtime.getRuntime.availableProcessors()): Future[
Seq[T]] = {
source
.mapAsync(parallelism = numParallelism) { height =>
bitcoind
@ -116,9 +110,6 @@ class ScanBitcoind()(implicit
val outputs = block.transactions
.flatMap(_.outputs)
.filter(_.scriptPubKey.isInstanceOf[WitnessScriptPubKeyV1])
logger.info(
s"addresses=${outputs.map(_.scriptPubKey).map(spk => Bech32mAddress(spk.asInstanceOf[WitnessScriptPubKeyV1], MainNet))}")
outputs.length
}

View file

@ -349,6 +349,25 @@ class TransactionTest extends BitcoinSUnitTest {
assert(WitnessTransaction.fromHex(wtx.hex) == wtx)
}
it must "parse 66cc4de192001d970244ffe32896282a1994fef80f01d35b216033aeacac1651" in {
val hex =
"01000000000101d498f5f6e3a4f04e88170c4dfaa8acef0cf9b949086c15e23d75d98a5d516ec50000000000ffffffff02b6ca050000000000225120af68875a973914dd815fff548a9104eeb9e44bee55b2b1a7428bab71ef33bd362b0103000000000017a914f92c8425eb54c3ef38e507a786297d2648441c0e87" +
"014104720fcb29324a375b02b26c30cd45526dc7bdf668a59a4340f8bcf89275fafac91d0f763945373d17210650d717f5a1c465046c44c0ba2da74d6a76f61d1c370100000000"
val tx = Transaction.fromHex(hex)
assert(
tx.txIdBE.hex == "66cc4de192001d970244ffe32896282a1994fef80f01d35b216033aeacac1651")
assert(tx.hex == hex)
}
it must "parse e597ad88c1366e53743ac201d0156abda38cde9720b80baf7328ce9fa677772d" in {
val hex =
"02000000000102d003fe1e06554e70a8e6a7026407c54f9ad1c3ca29a5486fd9825c74534cf338070000006a47304402200fa8b5e8d4b32aaf060ceb1de381d2cb9481b95591ac086f548869dabe2b625402207e347389e0a92f48c3f7fd3f4ae0e05878c7fa55584941dec3f347fa9c87ea8c0121039766e910a95b9be9b6504bfc1585c1c739a295f29099c9ee20d8fd91f58191bfffffffff22c93527a49ee479e05fb00023430f519402ee70c1d1871c5df0f896be70bb1f0000000000ffffffff02fd423800000000001976a91425ff4d7161289d9950d332a93b226abf429ff97188ac62060000000000001600144b109941eba2e10e5feb034342e9124c9edf9d310002473044022034b207e3008680ca7e3785d1a4fcbbd4f167ee8dad86a506ef886928f5072ce3022055647f8a2352e1c09fd9127fffc91c7918f90c5a8870e07760a744a4412df701012102dfac2fa93040d21e83c98f3b2d6b0a1dc95921c301118a6597e373d5ba6bc08300000000"
val tx = Transaction.fromHex(hex)
assert(
tx.txIdBE.hex == "e597ad88c1366e53743ac201d0156abda38cde9720b80baf7328ce9fa677772d")
assert(tx.hex == hex)
}
private def findInput(
tx: Transaction,
outPoint: TransactionOutPoint): Option[(TransactionInput, Int)] = {

View file

@ -8,6 +8,7 @@ import org.bitcoins.core.serializers.script.{
}
import org.bitcoins.core.util.{BitcoinScriptUtil, BytesUtil}
import org.bitcoins.crypto.{
CryptoUtil,
ECDigitalSignature,
ECPublicKey,
ECPublicKeyBytes,
@ -180,9 +181,9 @@ object ScriptWitness extends Factory[ScriptWitness] {
//TODO: eventually only compressed public keys will be allowed in v0 scripts
//https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki#restrictions-on-public-key-type
val isPubKey = {
stack.nonEmpty &&
((stack.head.size == 33 && (stack.head.head == 0x02 || stack.head.head == 0x03))
|| (stack.head.size == 65 && stack.head.head == 0x04))
stack.nonEmpty && (stack.head.size == 33 && (stack.head.head == 0x02 || stack.head.head == 0x03)
|| (stack.head.size == 65 && stack.head.head == 0x04 && CryptoUtil
.isValidPubKey(ECPublicKeyBytes(stack.head))))
}
if (stack.isEmpty) {
EmptyScriptWitness