mirror of
https://github.com/ACINQ/eclair.git
synced 2024-11-19 01:43:22 +01:00
Use bitcoinheaders.net v2 format (#2787)
The format used by bitcoinheaders.net is changing to use whole bytes instead of nibles, which is easier to parse. We start using the v2 format exclusively, which will allow deprecating the previous format. Fixes #2786
This commit is contained in:
parent
f0cb58aed4
commit
d4a498cdd6
@ -28,7 +28,7 @@ import fr.acinq.eclair.BlockHeight
|
||||
import fr.acinq.eclair.blockchain.watchdogs.BlockchainWatchdog.BlockHeaderAt
|
||||
import fr.acinq.eclair.blockchain.watchdogs.Monitoring.{Metrics, Tags}
|
||||
import org.slf4j.Logger
|
||||
import scodec.bits.BitVector
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
/**
|
||||
* Created by t-bast on 29/09/2020.
|
||||
@ -57,7 +57,7 @@ object HeadersOverDns {
|
||||
case Block.LivenetGenesisBlock.hash =>
|
||||
// We try to get the next 10 blocks; if we're late by more than 10 blocks, this is bad, no need to even look further.
|
||||
(currentBlockHeight.toLong until currentBlockHeight.toLong + 10).foreach(blockHeight => {
|
||||
val hostname = s"$blockHeight.${blockHeight / 10000}.bitcoinheaders.net"
|
||||
val hostname = s"v2.$blockHeight.${blockHeight / 10_000}.bitcoinheaders.net"
|
||||
IO(Dns)(context.system.classicSystem).tell(DnsProtocol.resolve(hostname, DnsProtocol.Ip(ipv4 = false, ipv6 = true)), dnsAdapters)
|
||||
})
|
||||
collect(replyTo, currentBlockHeight, Set.empty, 10)
|
||||
@ -75,7 +75,7 @@ object HeadersOverDns {
|
||||
Behaviors.receiveMessage {
|
||||
case WrappedDnsResolved(response) =>
|
||||
val blockHeader_opt = for {
|
||||
blockHeight <- parseBlockCount(response)(context.log)
|
||||
blockHeight <- parseBlockHeight(response)(context.log)
|
||||
blockHeader <- parseBlockHeader(response)(context.log)
|
||||
} yield BlockHeaderAt(blockHeight, blockHeader)
|
||||
val received1 = blockHeader_opt match {
|
||||
@ -103,42 +103,48 @@ object HeadersOverDns {
|
||||
collect(replyTo, currentBlockHeight, received, remaining)
|
||||
}
|
||||
|
||||
private def parseBlockCount(response: DnsProtocol.Resolved)(implicit log: Logger): Option[BlockHeight] = {
|
||||
response.name.split('.').headOption match {
|
||||
case Some(blockHeight) => blockHeight.toLongOption.map(l => BlockHeight(l))
|
||||
case None =>
|
||||
log.error("bitcoinheaders.net response did not contain block count: {}", response)
|
||||
None
|
||||
private def parseBlockHeight(response: DnsProtocol.Resolved)(implicit log: Logger): Option[BlockHeight] = {
|
||||
// v2.height.(height / 10000).bitcoinheaders.net
|
||||
val parts = response.name.split('.')
|
||||
if (parts.length < 2) {
|
||||
log.error("bitcoinheaders.net response did not contain block height: {}", response)
|
||||
None
|
||||
} else {
|
||||
parts(1).toLongOption.map(l => BlockHeight(l))
|
||||
}
|
||||
}
|
||||
|
||||
private def parseBlockHeader(response: DnsProtocol.Resolved)(implicit log: Logger): Option[BlockHeader] = {
|
||||
val addresses = response.records.collect { case record: AAAARecord => record.ip.getAddress }
|
||||
if (addresses.nonEmpty) {
|
||||
val countOk = addresses.length == 6
|
||||
// addresses must be prefixed with 0x2001
|
||||
val prefixOk = addresses.forall(_.startsWith(Array(0x20.toByte, 0x01.toByte)))
|
||||
// the first nibble after the prefix encodes the order since nameservers often reorder responses
|
||||
val orderOk = addresses.map(a => a(2) & 0xf0).toSet == Set(0x00, 0x10, 0x20, 0x30, 0x40, 0x50)
|
||||
if (countOk && prefixOk && orderOk) {
|
||||
val header = addresses.sortBy(a => a(2)).foldLeft(BitVector.empty) {
|
||||
case (current, address) =>
|
||||
// The first address contains an additional 0x00 prefix
|
||||
val toDrop = if (current.isEmpty) 28 else 20
|
||||
current ++ BitVector(address).drop(toDrop)
|
||||
}.bytes
|
||||
header.length match {
|
||||
case 80 => Some(BlockHeader.read(header.toArray))
|
||||
case _ =>
|
||||
log.error("bitcoinheaders.net response did not contain block header (invalid length): {}", response)
|
||||
None
|
||||
}
|
||||
} else {
|
||||
log.error("invalid response from bitcoinheaders.net: {}", response)
|
||||
// From https://bitcoinheaders.net/:
|
||||
// All headers are encoded with an arbitrary one byte prefix (which you must ignore, as it may change in the
|
||||
// future), followed by a 0-indexed order byte (as nameservers often reorder responses). Entries are then prefixed
|
||||
// by a single version byte (currently version 1) and placed into the remaining bytes of the IPv6 addresses.
|
||||
// For example with the genesis block:
|
||||
// v2.0.0.bitcoinheaders.net. 604800 IN AAAA 2603:7b12:b27a:c72c:3e67:768f:617f:c81b
|
||||
// v2.0.0.bitcoinheaders.net. 604800 IN AAAA 2600:101::
|
||||
// v2.0.0.bitcoinheaders.net. 604800 IN AAAA 2601::
|
||||
// v2.0.0.bitcoinheaders.net. 604800 IN AAAA 2602::3b:a3ed:fd7a
|
||||
// v2.0.0.bitcoinheaders.net. 604800 IN AAAA 2605:ab5f:49ff:ff00:1d1d:ac2b:7c00:0
|
||||
// v2.0.0.bitcoinheaders.net. 604800 IN AAAA 2604:c388:8a51:323a:9fb8:aa4b:1e5e:4a29
|
||||
// Which decodes to 0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c.
|
||||
val data = addresses
|
||||
.filter(_.length >= 2)
|
||||
.map(_.tail) // the first byte is a prefix that we must ignore
|
||||
.sortBy(_.head) // the second byte is a 0-indexed order byte
|
||||
.flatMap(_.tail) // the remaining bytes contain the header chunks
|
||||
if (data.length < 81) {
|
||||
log.error("bitcoinheaders.net response did not contain a 1-byte version followed by a block header: {}", ByteVector(data).toHex)
|
||||
None
|
||||
} else if (data.head != 0x01) {
|
||||
log.error("bitcoinheaders.net response is not using version 1: version={}", data.head)
|
||||
None
|
||||
} else {
|
||||
Some(BlockHeader.read(data.tail.take(80).toArray))
|
||||
}
|
||||
} else {
|
||||
// Instead of not resolving the DNS request when block height is unknown, bitcoinheaders sometimes returns an empty response.
|
||||
// When the block height is unknown, bitcoinheaders returns an empty response.
|
||||
None
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user