mirror of
https://github.com/ACINQ/eclair.git
synced 2025-03-13 19:37:35 +01:00
merge from master
This commit is contained in:
commit
e5509ed417
182 changed files with 3494 additions and 257 deletions
2
BUILD.md
2
BUILD.md
|
@ -1,7 +1,7 @@
|
||||||
# Building Eclair
|
# Building Eclair
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
- [Java Development Kit](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) 1.8
|
- [Java Development Kit](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) 1.8u161 or newer
|
||||||
- [Maven](https://maven.apache.org/download.cgi) 3.3.x
|
- [Maven](https://maven.apache.org/download.cgi) 3.3.x
|
||||||
- [Inno Setup](http://www.jrsoftware.org/isdl.php) 5.5.9 (optional, if you want to generate the windows installer)
|
- [Inno Setup](http://www.jrsoftware.org/isdl.php) 5.5.9 (optional, if you want to generate the windows installer)
|
||||||
|
|
||||||
|
|
2
LICENSE
2
LICENSE
|
@ -186,7 +186,7 @@ Apache License
|
||||||
same "printed page" as the copyright notice for easier
|
same "printed page" as the copyright notice for easier
|
||||||
identification within third-party archives.
|
identification within third-party archives.
|
||||||
|
|
||||||
Copyright 2014 ACINQ SAS
|
Copyright 2018 ACINQ SAS
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -27,11 +27,11 @@ Please see the latest [release note](https://github.com/ACINQ/eclair/releases) f
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
:warning: **Those are valid for the most up-to-date, unreleased, version of eclair. Here are the [instructions for Eclair 0.2-alpha10](https://github.com/ACINQ/eclair/blob/v0.2-alpha10/README.md#installation)**.
|
:warning: **Those are valid for the most up-to-date, unreleased, version of eclair. Here are the [instructions for Eclair 0.2-alpha11](https://github.com/ACINQ/eclair/blob/v0.2-alpha11/README.md#installation)**.
|
||||||
|
|
||||||
### Configuring Bitcoin Core
|
### Configuring Bitcoin Core
|
||||||
|
|
||||||
Eclair needs a _synchronized_, _segwit-ready_, **_zeromq-enabled_**, _wallet-enabled_, _non-pruning_, _tx-indexing_ [Bitcoin Core](https://github.com/bitcoin/bitcoin) node. This means that on Windows you will need Bitcoin Core 0.14+.
|
Eclair needs a _synchronized_, _segwit-ready_, **_zeromq-enabled_**, _wallet-enabled_, _non-pruning_, _tx-indexing_ [Bitcoin Core](https://github.com/bitcoin/bitcoin) node. This means that on Windows you will need Bitcoin Core 0.14+. We highly recommend that you use Bitcoin Core 0.16 and will soon drop support for older versions.
|
||||||
|
|
||||||
Run bitcoind with the following minimal `bitcoin.conf`:
|
Run bitcoind with the following minimal `bitcoin.conf`:
|
||||||
```
|
```
|
||||||
|
@ -147,6 +147,8 @@ java -Declair.datadir=/tmp/node1 -jar eclair-node-gui-<version>-<commit_id>.jar
|
||||||
allupdates | nodeId | list all channels updates for this nodeId
|
allupdates | nodeId | list all channels updates for this nodeId
|
||||||
receive | description | generate a payment request without a required amount (can be useful for donations)
|
receive | description | generate a payment request without a required amount (can be useful for donations)
|
||||||
receive | amountMsat, description | generate a payment request for a given amount
|
receive | amountMsat, description | generate a payment request for a given amount
|
||||||
|
checkinvoice | paymentRequest | returns node, amount and payment hash in an invoice/paymentRequest
|
||||||
|
findroute | paymentRequest|nodeId | given a payment request or nodeID checks if there is a valid payment route returns JSON with attempts, nodes and channels of route
|
||||||
send | amountMsat, paymentHash, nodeId | send a payment to a lightning node
|
send | amountMsat, paymentHash, nodeId | send a payment to a lightning node
|
||||||
send | paymentRequest | send a payment to a lightning node using a BOLT11 payment request
|
send | paymentRequest | send a payment to a lightning node using a BOLT11 payment request
|
||||||
send | paymentRequest, amountMsat | send a payment to a lightning node using a BOLT11 payment request and a custom amount
|
send | paymentRequest, amountMsat | send a payment to a lightning node using a BOLT11 payment request and a custom amount
|
||||||
|
|
|
@ -1,4 +1,20 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright 2018 ACINQ SAS
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
|
@ -56,7 +56,7 @@ eclair {
|
||||||
mindepth-blocks = 2
|
mindepth-blocks = 2
|
||||||
expiry-delta-blocks = 144
|
expiry-delta-blocks = 144
|
||||||
|
|
||||||
fee-base-msat = 10000
|
fee-base-msat = 1000
|
||||||
fee-proportional-millionths = 100 // fee charged per transferred satoshi in millionths of a satoshi (100 = 0.01%)
|
fee-proportional-millionths = 100 // fee charged per transferred satoshi in millionths of a satoshi (100 = 0.01%)
|
||||||
|
|
||||||
// maximum local vs remote feerate mismatch; 1.0 means 100%
|
// maximum local vs remote feerate mismatch; 1.0 means 100%
|
||||||
|
@ -77,4 +77,5 @@ eclair {
|
||||||
payment-handler = "local"
|
payment-handler = "local"
|
||||||
payment-request-expiry = 1 hour // default expiry for payment requests generated by this node
|
payment-request-expiry = 1 hour // default expiry for payment requests generated by this node
|
||||||
max-pending-payment-requests = 10000000
|
max-pending-payment-requests = 10000000
|
||||||
|
max-payment-fee = 0.03 // max total fee for outgoing payments, in percentage: sending a payment will not be attempted if the cheapest route found is more expensive than that
|
||||||
}
|
}
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair
|
package fr.acinq.eclair
|
||||||
|
|
||||||
import java.text.DecimalFormat
|
import java.text.DecimalFormat
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair
|
package fr.acinq.eclair
|
||||||
|
|
||||||
import grizzled.slf4j.Logging
|
import grizzled.slf4j.Logging
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair
|
package fr.acinq.eclair
|
||||||
|
|
||||||
import akka.actor.{Actor, FSM}
|
import akka.actor.{Actor, FSM}
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair
|
package fr.acinq.eclair
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair
|
package fr.acinq.eclair
|
||||||
|
|
||||||
import java.util.concurrent.atomic.{AtomicLong, AtomicReference}
|
import java.util.concurrent.atomic.{AtomicLong, AtomicReference}
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair
|
package fr.acinq.eclair
|
||||||
|
|
||||||
import com.ning.http.client.{AsyncCompletionHandler, AsyncHttpClient, AsyncHttpClientConfig, Response}
|
import com.ning.http.client.{AsyncCompletionHandler, AsyncHttpClient, AsyncHttpClientConfig, Response}
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair
|
package fr.acinq.eclair
|
||||||
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
@ -56,7 +72,8 @@ case class NodeParams(keyManager: KeyManager,
|
||||||
channelExcludeDuration: FiniteDuration,
|
channelExcludeDuration: FiniteDuration,
|
||||||
watcherType: WatcherType,
|
watcherType: WatcherType,
|
||||||
paymentRequestExpiry: FiniteDuration,
|
paymentRequestExpiry: FiniteDuration,
|
||||||
maxPendingPaymentRequests: Int) {
|
maxPendingPaymentRequests: Int,
|
||||||
|
maxPaymentFee: Double) {
|
||||||
val privateKey = keyManager.nodeKey.privateKey
|
val privateKey = keyManager.nodeKey.privateKey
|
||||||
val nodeId = keyManager.nodeId
|
val nodeId = keyManager.nodeId
|
||||||
}
|
}
|
||||||
|
@ -166,6 +183,7 @@ object NodeParams {
|
||||||
channelExcludeDuration = FiniteDuration(config.getDuration("channel-exclude-duration", TimeUnit.SECONDS), TimeUnit.SECONDS),
|
channelExcludeDuration = FiniteDuration(config.getDuration("channel-exclude-duration", TimeUnit.SECONDS), TimeUnit.SECONDS),
|
||||||
watcherType = watcherType,
|
watcherType = watcherType,
|
||||||
paymentRequestExpiry = FiniteDuration(config.getDuration("payment-request-expiry", TimeUnit.SECONDS), TimeUnit.SECONDS),
|
paymentRequestExpiry = FiniteDuration(config.getDuration("payment-request-expiry", TimeUnit.SECONDS), TimeUnit.SECONDS),
|
||||||
maxPendingPaymentRequests = config.getInt("max-pending-payment-requests"))
|
maxPendingPaymentRequests = config.getInt("max-pending-payment-requests"),
|
||||||
|
maxPaymentFee = config.getDouble("max-payment-fee"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair
|
package fr.acinq.eclair
|
||||||
|
|
||||||
import java.net.{InetAddress, ServerSocket}
|
import java.net.{InetAddress, ServerSocket}
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair
|
package fr.acinq.eclair
|
||||||
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
@ -7,7 +23,7 @@ import akka.util.Timeout
|
||||||
import com.typesafe.config.{Config, ConfigFactory}
|
import com.typesafe.config.{Config, ConfigFactory}
|
||||||
import fr.acinq.bitcoin.{BinaryData, Block}
|
import fr.acinq.bitcoin.{BinaryData, Block}
|
||||||
import fr.acinq.eclair.NodeParams.ELECTRUM
|
import fr.acinq.eclair.NodeParams.ELECTRUM
|
||||||
import fr.acinq.eclair.blockchain.electrum.{ElectrumClient, ElectrumEclairWallet, ElectrumWallet, ElectrumWatcher}
|
import fr.acinq.eclair.blockchain.electrum._
|
||||||
import fr.acinq.eclair.blockchain.fee.{ConstantFeeProvider, _}
|
import fr.acinq.eclair.blockchain.fee.{ConstantFeeProvider, _}
|
||||||
import fr.acinq.eclair.blockchain.{EclairWallet, _}
|
import fr.acinq.eclair.blockchain.{EclairWallet, _}
|
||||||
import fr.acinq.eclair.channel.Register
|
import fr.acinq.eclair.channel.Register
|
||||||
|
@ -61,8 +77,8 @@ class Setup(datadir: File, wallet_opt: Option[EclairWallet] = None, overrideDefa
|
||||||
case "regtest" => "/electrum/servers_regtest.json"
|
case "regtest" => "/electrum/servers_regtest.json"
|
||||||
}
|
}
|
||||||
val stream = classOf[Setup].getResourceAsStream(addressesFile)
|
val stream = classOf[Setup].getResourceAsStream(addressesFile)
|
||||||
val addresses = ElectrumClient.readServerAddresses(stream)
|
val addresses = ElectrumClientPool.readServerAddresses(stream)
|
||||||
val electrumClient = system.actorOf(SimpleSupervisor.props(Props(new ElectrumClient(addresses)), "electrum-client", SupervisorStrategy.Resume))
|
val electrumClient = system.actorOf(SimpleSupervisor.props(Props(new ElectrumClientPool(addresses)), "electrum-client", SupervisorStrategy.Resume))
|
||||||
Electrum(electrumClient)
|
Electrum(electrumClient)
|
||||||
case _ => ???
|
case _ => ???
|
||||||
}
|
}
|
||||||
|
@ -146,7 +162,6 @@ case class Kit(nodeParams: NodeParams,
|
||||||
wallet: EclairWallet)
|
wallet: EclairWallet)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
case object BitcoinWalletDisabledException extends RuntimeException("bitcoind must have wallet support enabled")
|
case object BitcoinWalletDisabledException extends RuntimeException("bitcoind must have wallet support enabled")
|
||||||
|
|
||||||
case object EmptyAPIPasswordException extends RuntimeException("must set a password for the json-rpc api")
|
case object EmptyAPIPasswordException extends RuntimeException("must set a password for the json-rpc api")
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair
|
package fr.acinq.eclair
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair
|
package fr.acinq.eclair
|
||||||
|
|
||||||
import akka.actor.{Actor, ActorLogging, OneForOneStrategy, Props, SupervisorStrategy}
|
import akka.actor.{Actor, ActorLogging, OneForOneStrategy, Props, SupervisorStrategy}
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair
|
package fr.acinq.eclair
|
||||||
|
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
|
@ -13,6 +29,8 @@ case class UInt64(private val underlying: BigInt) extends Ordered[UInt64] {
|
||||||
|
|
||||||
def toByteArray: Array[Byte] = underlying.toByteArray.takeRight(8)
|
def toByteArray: Array[Byte] = underlying.toByteArray.takeRight(8)
|
||||||
|
|
||||||
|
def toBigInt: BigInt = underlying
|
||||||
|
|
||||||
override def toString: String = underlying.toString
|
override def toString: String = underlying.toString
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.blockchain
|
package fr.acinq.eclair.blockchain
|
||||||
|
|
||||||
import fr.acinq.bitcoin.{Block, Transaction}
|
import fr.acinq.bitcoin.{Block, Transaction}
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.blockchain
|
package fr.acinq.eclair.blockchain
|
||||||
|
|
||||||
import fr.acinq.bitcoin.{BinaryData, Satoshi, Transaction}
|
import fr.acinq.bitcoin.{BinaryData, Satoshi, Transaction}
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.blockchain
|
package fr.acinq.eclair.blockchain
|
||||||
|
|
||||||
import akka.actor.ActorRef
|
import akka.actor.ActorRef
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.blockchain.bitcoind
|
package fr.acinq.eclair.blockchain.bitcoind
|
||||||
|
|
||||||
import akka.actor.ActorSystem
|
import akka.actor.ActorSystem
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.blockchain.bitcoind.zmq
|
package fr.acinq.eclair.blockchain.bitcoind.zmq
|
||||||
|
|
||||||
import akka.actor.{Actor, ActorLogging}
|
import akka.actor.{Actor, ActorLogging}
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.blockchain.bitcoind
|
package fr.acinq.eclair.blockchain.bitcoind
|
||||||
|
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
|
@ -48,7 +64,7 @@ class ZmqWatcher(client: ExtendedBitcoinClient)(implicit ec: ExecutionContext =
|
||||||
|
|
||||||
case NewBlock(block) =>
|
case NewBlock(block) =>
|
||||||
// using a Try because in tests we generate fake blocks
|
// using a Try because in tests we generate fake blocks
|
||||||
log.debug(s"received blockid=${Try(block.blockId).getOrElse(BinaryData(""))}")
|
log.debug(s"received blockid=${Try(block.blockId).getOrElse(BinaryData.empty)}")
|
||||||
nextTick.map(_.cancel()) // this may fail or succeed, worse case scenario we will have two ticks in a row (no big deal)
|
nextTick.map(_.cancel()) // this may fail or succeed, worse case scenario we will have two ticks in a row (no big deal)
|
||||||
log.debug(s"scheduling a new task to check on tx confirmations")
|
log.debug(s"scheduling a new task to check on tx confirmations")
|
||||||
// we do this to avoid herd effects in testing when generating a lots of blocks in a row
|
// we do this to avoid herd effects in testing when generating a lots of blocks in a row
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.blockchain.bitcoind.rpc
|
package fr.acinq.eclair.blockchain.bitcoind.rpc
|
||||||
|
|
||||||
import akka.actor.ActorSystem
|
import akka.actor.ActorSystem
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.blockchain.bitcoind.rpc
|
package fr.acinq.eclair.blockchain.bitcoind.rpc
|
||||||
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.blockchain.bitcoind.rpc
|
package fr.acinq.eclair.blockchain.bitcoind.rpc
|
||||||
|
|
||||||
import fr.acinq.bitcoin._
|
import fr.acinq.bitcoin._
|
||||||
|
@ -17,13 +33,6 @@ class ExtendedBitcoinClient(val rpcClient: BitcoinJsonRPCClient) {
|
||||||
|
|
||||||
implicit val formats = org.json4s.DefaultFormats
|
implicit val formats = org.json4s.DefaultFormats
|
||||||
|
|
||||||
// TODO: this will probably not be needed once segwit is merged into core
|
|
||||||
val protocolVersion = Protocol.PROTOCOL_VERSION
|
|
||||||
|
|
||||||
def tx2Hex(tx: Transaction): String = toHexString(Transaction.write(tx, protocolVersion))
|
|
||||||
|
|
||||||
def hex2tx(hex: String): Transaction = Transaction.read(hex, protocolVersion)
|
|
||||||
|
|
||||||
def getTxConfirmations(txId: String)(implicit ec: ExecutionContext): Future[Option[Int]] =
|
def getTxConfirmations(txId: String)(implicit ec: ExecutionContext): Future[Option[Int]] =
|
||||||
rpcClient.invoke("getrawtransaction", txId, 1) // we choose verbose output to get the number of confirmations
|
rpcClient.invoke("getrawtransaction", txId, 1) // we choose verbose output to get the number of confirmations
|
||||||
.map(json => Some((json \ "confirmations").extractOrElse[Int](0)))
|
.map(json => Some((json \ "confirmations").extractOrElse[Int](0)))
|
||||||
|
@ -126,7 +135,7 @@ class ExtendedBitcoinClient(val rpcClient: BitcoinJsonRPCClient) {
|
||||||
}
|
}
|
||||||
|
|
||||||
def publishTransaction(tx: Transaction)(implicit ec: ExecutionContext): Future[String] =
|
def publishTransaction(tx: Transaction)(implicit ec: ExecutionContext): Future[String] =
|
||||||
publishTransaction(tx2Hex(tx))
|
publishTransaction(tx.toString())
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We need this to compute absolute timeouts expressed in number of blocks (where getBlockCount would be equivalent
|
* We need this to compute absolute timeouts expressed in number of blocks (where getBlockCount would be equivalent
|
||||||
|
|
|
@ -1,14 +1,27 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.blockchain.electrum
|
package fr.acinq.eclair.blockchain.electrum
|
||||||
|
|
||||||
import java.io.InputStream
|
|
||||||
import java.net.InetSocketAddress
|
import java.net.InetSocketAddress
|
||||||
|
|
||||||
import akka.actor.{Actor, ActorLogging, ActorRef, Stash, Terminated}
|
import akka.actor.{Actor, ActorLogging, ActorRef, Props, Stash, Terminated}
|
||||||
import akka.io.{IO, Tcp}
|
import akka.io.{IO, Tcp}
|
||||||
import akka.util.ByteString
|
import akka.util.ByteString
|
||||||
import fr.acinq.bitcoin._
|
import fr.acinq.bitcoin._
|
||||||
import fr.acinq.eclair.Globals
|
|
||||||
import fr.acinq.eclair.blockchain.CurrentBlockCount
|
|
||||||
import fr.acinq.eclair.blockchain.bitcoind.rpc.{Error, JsonRPCRequest, JsonRPCResponse}
|
import fr.acinq.eclair.blockchain.bitcoind.rpc.{Error, JsonRPCRequest, JsonRPCResponse}
|
||||||
import org.json4s.JsonAST._
|
import org.json4s.JsonAST._
|
||||||
import org.json4s.jackson.JsonMethods
|
import org.json4s.jackson.JsonMethods
|
||||||
|
@ -16,11 +29,10 @@ import org.json4s.{DefaultFormats, JInt, JLong, JString}
|
||||||
import org.spongycastle.util.encoders.Hex
|
import org.spongycastle.util.encoders.Hex
|
||||||
|
|
||||||
import scala.annotation.tailrec
|
import scala.annotation.tailrec
|
||||||
import scala.concurrent.ExecutionContext.Implicits.global
|
import scala.concurrent.ExecutionContext
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
import scala.util.Random
|
|
||||||
|
|
||||||
class ElectrumClient(serverAddresses: Seq[InetSocketAddress]) extends Actor with Stash with ActorLogging {
|
class ElectrumClient(serverAddress: InetSocketAddress)(implicit val ec: ExecutionContext) extends Actor with Stash with ActorLogging {
|
||||||
|
|
||||||
import ElectrumClient._
|
import ElectrumClient._
|
||||||
import context.system
|
import context.system
|
||||||
|
@ -28,36 +40,36 @@ class ElectrumClient(serverAddresses: Seq[InetSocketAddress]) extends Actor with
|
||||||
implicit val formats = DefaultFormats
|
implicit val formats = DefaultFormats
|
||||||
|
|
||||||
val newline = "\n"
|
val newline = "\n"
|
||||||
val connectionFailures = collection.mutable.HashMap.empty[InetSocketAddress, Long]
|
|
||||||
val socketOptions = Tcp.SO.KeepAlive(true) :: Nil
|
val socketOptions = Tcp.SO.KeepAlive(true) :: Nil
|
||||||
|
var addressSubscriptions = Map.empty[String, Set[ActorRef]]
|
||||||
|
var scriptHashSubscriptions = Map.empty[BinaryData, Set[ActorRef]]
|
||||||
|
val headerSubscriptions = collection.mutable.HashSet.empty[ActorRef]
|
||||||
val version = ServerVersion("2.1.7", "1.1")
|
val version = ServerVersion("2.1.7", "1.1")
|
||||||
|
val statusListeners = collection.mutable.HashSet.empty[ActorRef]
|
||||||
|
val keepHeaders = 100
|
||||||
|
|
||||||
|
var reqId = 0L
|
||||||
|
|
||||||
|
self ! Tcp.Connect(serverAddress, options = socketOptions)
|
||||||
|
|
||||||
// we need to regularly send a ping in order not to get disconnected
|
// we need to regularly send a ping in order not to get disconnected
|
||||||
context.system.scheduler.schedule(30 seconds, 30 seconds, self, version)
|
val versionTrigger = context.system.scheduler.schedule(30 seconds, 30 seconds, self, version)
|
||||||
|
|
||||||
override def unhandled(message: Any): Unit = {
|
override def unhandled(message: Any): Unit = {
|
||||||
message match {
|
message match {
|
||||||
case _: Tcp.ConnectionClosed =>
|
case _: Tcp.ConnectionClosed =>
|
||||||
val nextAddress = nextPeer()
|
log.info(s"connection to $serverAddress closed")
|
||||||
log.warning(s"connection failed, trying $nextAddress")
|
|
||||||
self ! Tcp.Connect(nextAddress, options = socketOptions)
|
|
||||||
statusListeners.map(_ ! ElectrumDisconnected)
|
statusListeners.map(_ ! ElectrumDisconnected)
|
||||||
context.system.eventStream.publish(ElectrumDisconnected)
|
context stop self
|
||||||
context become disconnected
|
|
||||||
|
|
||||||
case Terminated(deadActor) =>
|
case Terminated(deadActor) =>
|
||||||
val removeMe = addressSubscriptions collect {
|
addressSubscriptions = addressSubscriptions.mapValues(subscribers => subscribers - deadActor)
|
||||||
case (address, actor) if actor == deadActor => address
|
scriptHashSubscriptions = scriptHashSubscriptions.mapValues(subscribers => subscribers - deadActor)
|
||||||
}
|
|
||||||
addressSubscriptions --= removeMe
|
|
||||||
|
|
||||||
val removeMe1 = scriptHashSubscriptions collect {
|
|
||||||
case (scriptHash, actor) if actor == deadActor => scriptHash
|
|
||||||
}
|
|
||||||
scriptHashSubscriptions --= removeMe1
|
|
||||||
statusListeners -= deadActor
|
statusListeners -= deadActor
|
||||||
headerSubscriptions -= deadActor
|
headerSubscriptions -= deadActor
|
||||||
|
|
||||||
|
case RemoveStatusListener(actor) => statusListeners -= actor
|
||||||
|
|
||||||
case _: ServerVersion => () // we only handle this when connected
|
case _: ServerVersion => () // we only handle this when connected
|
||||||
|
|
||||||
case _: ServerVersionResponse => () // we just ignore these messages, they are used as pings
|
case _: ServerVersionResponse => () // we just ignore these messages, they are used as pings
|
||||||
|
@ -66,7 +78,11 @@ class ElectrumClient(serverAddresses: Seq[InetSocketAddress]) extends Actor with
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val statusListeners = collection.mutable.HashSet.empty[ActorRef]
|
|
||||||
|
override def postStop(): Unit = {
|
||||||
|
versionTrigger.cancel()
|
||||||
|
super.postStop()
|
||||||
|
}
|
||||||
|
|
||||||
def send(connection: ActorRef, request: JsonRPCRequest): Unit = {
|
def send(connection: ActorRef, request: JsonRPCRequest): Unit = {
|
||||||
import org.json4s.JsonDSL._
|
import org.json4s.JsonDSL._
|
||||||
|
@ -83,32 +99,22 @@ class ElectrumClient(serverAddresses: Seq[InetSocketAddress]) extends Actor with
|
||||||
}) ~ ("id" -> request.id) ~ ("jsonrpc" -> request.jsonrpc)
|
}) ~ ("id" -> request.id) ~ ("jsonrpc" -> request.jsonrpc)
|
||||||
val serialized = compact(render(json))
|
val serialized = compact(render(json))
|
||||||
val bytes = (serialized + newline).getBytes
|
val bytes = (serialized + newline).getBytes
|
||||||
connection ! Tcp.Write(ByteString.fromArray(bytes))
|
connection ! ByteString.fromArray(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
private def nextPeer() = {
|
/**
|
||||||
val nextPos = Random.nextInt(serverAddresses.size)
|
* send an electrum request to the server
|
||||||
serverAddresses(nextPos)
|
* @param connection connection to the electrumx server
|
||||||
|
* @param request electrum request
|
||||||
|
* @return the request id used to send the request
|
||||||
|
*/
|
||||||
|
def send(connection: ActorRef, request: Request): String = {
|
||||||
|
val electrumRequestId = "" + reqId
|
||||||
|
send(connection, makeRequest(request, electrumRequestId))
|
||||||
|
reqId = reqId + 1
|
||||||
|
electrumRequestId
|
||||||
}
|
}
|
||||||
|
|
||||||
private def updateBlockCount(blockCount: Long) = {
|
|
||||||
// when synchronizing we don't want to advertise previous blocks
|
|
||||||
if (Globals.blockCount.get() < blockCount) {
|
|
||||||
log.debug(s"current blockchain height=$blockCount")
|
|
||||||
system.eventStream.publish(CurrentBlockCount(blockCount))
|
|
||||||
Globals.blockCount.set(blockCount)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val addressSubscriptions = collection.mutable.HashMap.empty[String, Set[ActorRef]]
|
|
||||||
val scriptHashSubscriptions = collection.mutable.HashMap.empty[BinaryData, Set[ActorRef]]
|
|
||||||
val headerSubscriptions = collection.mutable.HashSet.empty[ActorRef]
|
|
||||||
|
|
||||||
context.system.eventStream.publish(ElectrumDisconnected)
|
|
||||||
self ! Tcp.Connect(serverAddresses.head, options = socketOptions)
|
|
||||||
|
|
||||||
var reqId = 0L
|
|
||||||
|
|
||||||
def receive = disconnected
|
def receive = disconnected
|
||||||
|
|
||||||
def disconnected: Receive = {
|
def disconnected: Receive = {
|
||||||
|
@ -118,23 +124,16 @@ class ElectrumClient(serverAddresses: Seq[InetSocketAddress]) extends Actor with
|
||||||
|
|
||||||
case Tcp.Connected(remote, _) =>
|
case Tcp.Connected(remote, _) =>
|
||||||
log.info(s"connected to $remote")
|
log.info(s"connected to $remote")
|
||||||
connectionFailures.clear()
|
val conn = sender()
|
||||||
val connection = sender()
|
conn ! Tcp.Register(self)
|
||||||
connection ! Tcp.Register(self)
|
val connection = context.actorOf(Props(new WriteAckSender(conn)), name = "electrum-sender")
|
||||||
val request = version
|
send(connection, version)
|
||||||
send(connection, makeRequest(request, "" + reqId))
|
|
||||||
reqId = reqId + 1
|
|
||||||
context become waitingForVersion(connection, remote)
|
context become waitingForVersion(connection, remote)
|
||||||
|
|
||||||
case AddStatusListener(actor) => statusListeners += actor
|
case AddStatusListener(actor) => statusListeners += actor
|
||||||
|
|
||||||
case Tcp.CommandFailed(Tcp.Connect(remoteAddress, _, _, _, _)) =>
|
case Tcp.CommandFailed(Tcp.Connect(remoteAddress, _, _, _, _)) =>
|
||||||
val nextAddress = nextPeer()
|
context stop self
|
||||||
log.warning(s"connection to $remoteAddress failed, trying $nextAddress")
|
|
||||||
connectionFailures.put(remoteAddress, connectionFailures.getOrElse(remoteAddress, 0L) + 1L)
|
|
||||||
val count = connectionFailures.getOrElse(nextAddress, 0L)
|
|
||||||
val delay = Math.min(Math.pow(2.0, count), 20.0) seconds;
|
|
||||||
context.system.scheduler.scheduleOnce(delay, self, Tcp.Connect(nextAddress, options = socketOptions))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def waitingForVersion(connection: ActorRef, remote: InetSocketAddress): Receive = {
|
def waitingForVersion(connection: ActorRef, remote: InetSocketAddress): Receive = {
|
||||||
|
@ -142,11 +141,9 @@ class ElectrumClient(serverAddresses: Seq[InetSocketAddress]) extends Actor with
|
||||||
val response = parseResponse(new String(data.toArray)).right.get
|
val response = parseResponse(new String(data.toArray)).right.get
|
||||||
val serverVersion = parseJsonResponse(version, response)
|
val serverVersion = parseJsonResponse(version, response)
|
||||||
log.debug(s"serverVersion=$serverVersion")
|
log.debug(s"serverVersion=$serverVersion")
|
||||||
val request = HeaderSubscription(self)
|
send(connection, HeaderSubscription(self))
|
||||||
send(connection, makeRequest(request, "" + reqId))
|
|
||||||
headerSubscriptions += self
|
headerSubscriptions += self
|
||||||
log.debug("waiting for tip")
|
log.debug("waiting for tip")
|
||||||
reqId = reqId + 1
|
|
||||||
context become waitingForTip(connection, remote: InetSocketAddress)
|
context become waitingForTip(connection, remote: InetSocketAddress)
|
||||||
|
|
||||||
case AddStatusListener(actor) => statusListeners += actor
|
case AddStatusListener(actor) => statusListeners += actor
|
||||||
|
@ -157,10 +154,8 @@ class ElectrumClient(serverAddresses: Seq[InetSocketAddress]) extends Actor with
|
||||||
val response = parseResponse(new String(data.toArray)).right.get
|
val response = parseResponse(new String(data.toArray)).right.get
|
||||||
val header = parseHeader(response.result)
|
val header = parseHeader(response.result)
|
||||||
log.debug(s"connected, tip = ${header.block_hash} $header")
|
log.debug(s"connected, tip = ${header.block_hash} $header")
|
||||||
updateBlockCount(header.block_height)
|
statusListeners.map(_ ! ElectrumReady(header))
|
||||||
statusListeners.map(_ ! ElectrumReady)
|
context become connected(connection, remote, header, "", Map())
|
||||||
context.system.eventStream.publish(ElectrumConnected)
|
|
||||||
context become connected(connection, remote, header, "", Map.empty)
|
|
||||||
|
|
||||||
case AddStatusListener(actor) => statusListeners += actor
|
case AddStatusListener(actor) => statusListeners += actor
|
||||||
}
|
}
|
||||||
|
@ -168,7 +163,7 @@ class ElectrumClient(serverAddresses: Seq[InetSocketAddress]) extends Actor with
|
||||||
def connected(connection: ActorRef, remoteAddress: InetSocketAddress, tip: Header, buffer: String, requests: Map[String, (Request, ActorRef)]): Receive = {
|
def connected(connection: ActorRef, remoteAddress: InetSocketAddress, tip: Header, buffer: String, requests: Map[String, (Request, ActorRef)]): Receive = {
|
||||||
case AddStatusListener(actor) =>
|
case AddStatusListener(actor) =>
|
||||||
statusListeners += actor
|
statusListeners += actor
|
||||||
actor ! ElectrumReady
|
actor ! ElectrumReady(tip)
|
||||||
|
|
||||||
case HeaderSubscription(actor) =>
|
case HeaderSubscription(actor) =>
|
||||||
headerSubscriptions += actor
|
headerSubscriptions += actor
|
||||||
|
@ -176,18 +171,16 @@ class ElectrumClient(serverAddresses: Seq[InetSocketAddress]) extends Actor with
|
||||||
context watch actor
|
context watch actor
|
||||||
|
|
||||||
case request: Request =>
|
case request: Request =>
|
||||||
val curReqId = "" + reqId
|
val curReqId = send(connection, request)
|
||||||
send(connection, makeRequest(request, curReqId))
|
|
||||||
request match {
|
request match {
|
||||||
case AddressSubscription(address, actor) =>
|
case AddressSubscription(address, actor) =>
|
||||||
addressSubscriptions.update(address, addressSubscriptions.getOrElse(address, Set()) + actor)
|
addressSubscriptions = addressSubscriptions.updated(address, addressSubscriptions.getOrElse(address, Set()) + actor)
|
||||||
context watch actor
|
context watch actor
|
||||||
case ScriptHashSubscription(scriptHash, actor) =>
|
case ScriptHashSubscription(scriptHash, actor) =>
|
||||||
scriptHashSubscriptions.update(scriptHash, scriptHashSubscriptions.getOrElse(scriptHash, Set()) + actor)
|
scriptHashSubscriptions = scriptHashSubscriptions.updated(scriptHash, scriptHashSubscriptions.getOrElse(scriptHash, Set()) + actor)
|
||||||
context watch actor
|
context watch actor
|
||||||
case _ => ()
|
case _ => ()
|
||||||
}
|
}
|
||||||
reqId = reqId + 1
|
|
||||||
context become connected(connection, remoteAddress, tip, buffer, requests + (curReqId -> (request, sender())))
|
context become connected(connection, remoteAddress, tip, buffer, requests + (curReqId -> (request, sender())))
|
||||||
|
|
||||||
case Tcp.Received(data) =>
|
case Tcp.Received(data) =>
|
||||||
|
@ -218,19 +211,11 @@ class ElectrumClient(serverAddresses: Seq[InetSocketAddress]) extends Actor with
|
||||||
|
|
||||||
case HeaderSubscriptionResponse(newtip) =>
|
case HeaderSubscriptionResponse(newtip) =>
|
||||||
log.info(s"new tip $newtip")
|
log.info(s"new tip $newtip")
|
||||||
updateBlockCount(newtip.block_height)
|
|
||||||
context become connected(connection, remoteAddress, newtip, buffer, requests)
|
context become connected(connection, remoteAddress, newtip, buffer, requests)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
object ElectrumClient {
|
object ElectrumClient {
|
||||||
|
|
||||||
def apply(addresses: java.util.List[InetSocketAddress]): ElectrumClient = {
|
|
||||||
import collection.JavaConversions._
|
|
||||||
new ElectrumClient(addresses)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility function to converts a publicKeyScript to electrum's scripthash
|
* Utility function to converts a publicKeyScript to electrum's scripthash
|
||||||
*
|
*
|
||||||
|
@ -240,6 +225,9 @@ object ElectrumClient {
|
||||||
def computeScriptHash(publicKeyScript: BinaryData): BinaryData = Crypto.sha256(publicKeyScript).reverse
|
def computeScriptHash(publicKeyScript: BinaryData): BinaryData = Crypto.sha256(publicKeyScript).reverse
|
||||||
|
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
|
case class AddStatusListener(actor: ActorRef)
|
||||||
|
case class RemoveStatusListener(actor: ActorRef)
|
||||||
|
|
||||||
sealed trait Request
|
sealed trait Request
|
||||||
sealed trait Response
|
sealed trait Response
|
||||||
|
|
||||||
|
@ -268,6 +256,9 @@ object ElectrumClient {
|
||||||
case class GetTransaction(txid: BinaryData) extends Request
|
case class GetTransaction(txid: BinaryData) extends Request
|
||||||
case class GetTransactionResponse(tx: Transaction) extends Response
|
case class GetTransactionResponse(tx: Transaction) extends Response
|
||||||
|
|
||||||
|
case class GetHeader(height: Int) extends Request
|
||||||
|
case class GetHeaderResponse(header: Header) extends Response
|
||||||
|
|
||||||
case class GetMerkle(txid: BinaryData, height: Long) extends Request
|
case class GetMerkle(txid: BinaryData, height: Long) extends Request
|
||||||
case class GetMerkleResponse(txid: BinaryData, merkle: Seq[BinaryData], block_height: Long, pos: Int) extends Response {
|
case class GetMerkleResponse(txid: BinaryData, merkle: Seq[BinaryData], block_height: Long, pos: Int) extends Response {
|
||||||
lazy val root: BinaryData = {
|
lazy val root: BinaryData = {
|
||||||
|
@ -293,10 +284,9 @@ object ElectrumClient {
|
||||||
case class HeaderSubscriptionResponse(header: Header) extends Response
|
case class HeaderSubscriptionResponse(header: Header) extends Response
|
||||||
|
|
||||||
case class Header(block_height: Long, version: Long, prev_block_hash: BinaryData, merkle_root: BinaryData, timestamp: Long, bits: Long, nonce: Long) {
|
case class Header(block_height: Long, version: Long, prev_block_hash: BinaryData, merkle_root: BinaryData, timestamp: Long, bits: Long, nonce: Long) {
|
||||||
lazy val block_hash: BinaryData = {
|
def blockHeader = BlockHeader(version, prev_block_hash.reverse, merkle_root.reverse, timestamp, bits, nonce)
|
||||||
val blockHeader = BlockHeader(version, prev_block_hash.reverse, merkle_root.reverse, timestamp, bits, nonce)
|
lazy val block_id: BinaryData = blockHeader.hash
|
||||||
blockHeader.hash.reverse
|
lazy val block_hash: BinaryData = block_id.reverse
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
object Header {
|
object Header {
|
||||||
|
@ -311,11 +301,9 @@ object ElectrumClient {
|
||||||
case class AddressStatus(address: String, status: String) extends Response
|
case class AddressStatus(address: String, status: String) extends Response
|
||||||
|
|
||||||
case class ServerError(request: Request, error: Error) extends Response
|
case class ServerError(request: Request, error: Error) extends Response
|
||||||
case class AddStatusListener(actor: ActorRef) extends Response
|
|
||||||
|
|
||||||
sealed trait ElectrumEvent
|
sealed trait ElectrumEvent
|
||||||
case object ElectrumConnected extends ElectrumEvent
|
case class ElectrumReady(tip: Header) extends ElectrumEvent
|
||||||
case object ElectrumReady extends ElectrumEvent
|
|
||||||
case object ElectrumDisconnected extends ElectrumEvent
|
case object ElectrumDisconnected extends ElectrumEvent
|
||||||
|
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
|
@ -397,6 +385,7 @@ object ElectrumClient {
|
||||||
case BroadcastTransaction(tx) => JsonRPCRequest(id = reqId, method = "blockchain.transaction.broadcast", params = Hex.toHexString(Transaction.write(tx)) :: Nil)
|
case BroadcastTransaction(tx) => JsonRPCRequest(id = reqId, method = "blockchain.transaction.broadcast", params = Hex.toHexString(Transaction.write(tx)) :: Nil)
|
||||||
case GetTransaction(txid: BinaryData) => JsonRPCRequest(id = reqId, method = "blockchain.transaction.get", params = txid :: Nil)
|
case GetTransaction(txid: BinaryData) => JsonRPCRequest(id = reqId, method = "blockchain.transaction.get", params = txid :: Nil)
|
||||||
case HeaderSubscription(_) => JsonRPCRequest(id = reqId, method = "blockchain.headers.subscribe", params = Nil)
|
case HeaderSubscription(_) => JsonRPCRequest(id = reqId, method = "blockchain.headers.subscribe", params = Nil)
|
||||||
|
case GetHeader(height) => JsonRPCRequest(id = reqId, method = "blockchain.block.get_header", params = height :: Nil)
|
||||||
case GetMerkle(txid, height) => JsonRPCRequest(id = reqId, method = "blockchain.transaction.get_merkle", params = txid :: height :: Nil)
|
case GetMerkle(txid, height) => JsonRPCRequest(id = reqId, method = "blockchain.transaction.get_merkle", params = txid :: height :: Nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -464,6 +453,8 @@ object ElectrumClient {
|
||||||
val JString(txid) = json.result
|
val JString(txid) = json.result
|
||||||
require(BinaryData(txid) == tx.txid)
|
require(BinaryData(txid) == tx.txid)
|
||||||
BroadcastTransactionResponse(tx, None)
|
BroadcastTransactionResponse(tx, None)
|
||||||
|
case GetHeader(height) =>
|
||||||
|
GetHeaderResponse(parseHeader(json.result))
|
||||||
case GetMerkle(txid, height) =>
|
case GetMerkle(txid, height) =>
|
||||||
val JArray(hashes) = json.result \ "merkle"
|
val JArray(hashes) = json.result \ "merkle"
|
||||||
val leaves = hashes collect { case JString(value) => BinaryData(value) }
|
val leaves = hashes collect { case JString(value) => BinaryData(value) }
|
||||||
|
@ -473,17 +464,4 @@ object ElectrumClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def readServerAddresses(stream: InputStream): Seq[InetSocketAddress] = try {
|
|
||||||
val JObject(values) = JsonMethods.parse(stream)
|
|
||||||
val addresses = values.map {
|
|
||||||
case (name, fields) =>
|
|
||||||
val JString(port) = fields \ "t"
|
|
||||||
new InetSocketAddress(name, port.toInt)
|
|
||||||
}
|
|
||||||
val randomized = Random.shuffle(addresses)
|
|
||||||
randomized
|
|
||||||
} finally {
|
|
||||||
stream.close()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,188 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package fr.acinq.eclair.blockchain.electrum
|
||||||
|
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.net.InetSocketAddress
|
||||||
|
|
||||||
|
import akka.actor.{Actor, ActorRef, FSM, Props, Terminated}
|
||||||
|
import fr.acinq.eclair.Globals
|
||||||
|
import fr.acinq.eclair.blockchain.CurrentBlockCount
|
||||||
|
import org.json4s.JsonAST.{JObject, JString}
|
||||||
|
import org.json4s.jackson.JsonMethods
|
||||||
|
|
||||||
|
import scala.concurrent.ExecutionContext
|
||||||
|
import scala.concurrent.duration._
|
||||||
|
import scala.util.Random
|
||||||
|
|
||||||
|
class ElectrumClientPool(serverAddresses: Set[InetSocketAddress])(implicit val ec: ExecutionContext) extends Actor with FSM[ElectrumClientPool.State, ElectrumClientPool.Data] {
|
||||||
|
|
||||||
|
import ElectrumClientPool._
|
||||||
|
|
||||||
|
val statusListeners = collection.mutable.HashSet.empty[ActorRef]
|
||||||
|
val addresses = collection.mutable.Map.empty[ActorRef, InetSocketAddress]
|
||||||
|
|
||||||
|
// on startup, we attempt to connect to a number of electrum clients
|
||||||
|
// they will send us an `ElectrumReady` message when they're connected, or
|
||||||
|
// terminate if they cannot connect
|
||||||
|
(0 until MAX_CONNECTION_COUNT) foreach (_ => self ! Connect)
|
||||||
|
|
||||||
|
startWith(Disconnected, DisconnectedData)
|
||||||
|
|
||||||
|
when(Disconnected) {
|
||||||
|
case Event(ElectrumClient.ElectrumReady(tip), _) if addresses.contains(sender) =>
|
||||||
|
sender ! ElectrumClient.HeaderSubscription(self)
|
||||||
|
handleHeader(sender, tip, None)
|
||||||
|
|
||||||
|
case Event(ElectrumClient.AddStatusListener(listener), _) =>
|
||||||
|
statusListeners += listener
|
||||||
|
stay
|
||||||
|
|
||||||
|
case Event(Terminated(actor), _) =>
|
||||||
|
log.info("lost connection to {}", addresses(actor))
|
||||||
|
addresses -= actor
|
||||||
|
context.system.scheduler.scheduleOnce(5 seconds, self, Connect)
|
||||||
|
stay
|
||||||
|
}
|
||||||
|
|
||||||
|
when(Connected) {
|
||||||
|
case Event(ElectrumClient.ElectrumReady(tip), d: ConnectedData) if addresses.contains(sender) =>
|
||||||
|
sender ! ElectrumClient.HeaderSubscription(self)
|
||||||
|
handleHeader(sender, tip, Some(d))
|
||||||
|
|
||||||
|
case Event(ElectrumClient.HeaderSubscriptionResponse(tip), d: ConnectedData) if addresses.contains(sender) =>
|
||||||
|
handleHeader(sender, tip, Some(d))
|
||||||
|
|
||||||
|
case Event(request: ElectrumClient.Request, ConnectedData(master, _)) =>
|
||||||
|
master forward request
|
||||||
|
stay
|
||||||
|
|
||||||
|
case Event(ElectrumClient.AddStatusListener(listener), d: ConnectedData) =>
|
||||||
|
statusListeners += listener
|
||||||
|
listener ! ElectrumClient.ElectrumReady(d.tips(d.master))
|
||||||
|
stay
|
||||||
|
|
||||||
|
case Event(Terminated(actor), d: ConnectedData) =>
|
||||||
|
log.info("lost connection to {}", addresses(actor))
|
||||||
|
addresses -= actor
|
||||||
|
context.system.scheduler.scheduleOnce(5 seconds, self, Connect)
|
||||||
|
val tips1 = d.tips - actor
|
||||||
|
if (tips1.isEmpty) {
|
||||||
|
goto(Disconnected) using DisconnectedData // no more connections
|
||||||
|
} else if (d.master != actor) {
|
||||||
|
stay using d.copy(tips = tips1) // we don't care, this wasn't our master
|
||||||
|
} else {
|
||||||
|
// we choose next best candidate as master
|
||||||
|
val tips1 = d.tips - actor
|
||||||
|
val (bestClient, bestTip) = tips1.toSeq.maxBy(_._2.block_height)
|
||||||
|
handleHeader(bestClient, bestTip, Some(d.copy(tips = tips1)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
whenUnhandled {
|
||||||
|
case Event(Connect, _) =>
|
||||||
|
Random.shuffle(serverAddresses.toSeq diff addresses.values.toSeq).headOption match {
|
||||||
|
case Some(address) =>
|
||||||
|
val client = context.actorOf(Props(new ElectrumClient(address)))
|
||||||
|
client ! ElectrumClient.AddStatusListener(self)
|
||||||
|
// we watch each electrum client, they will stop on disconnection
|
||||||
|
context watch client
|
||||||
|
addresses += (client -> address)
|
||||||
|
case None => () // no more servers available
|
||||||
|
}
|
||||||
|
stay
|
||||||
|
|
||||||
|
case Event(ElectrumClient.ElectrumDisconnected, _) =>
|
||||||
|
stay // ignored, we rely on Terminated messages to detect disconnections
|
||||||
|
}
|
||||||
|
|
||||||
|
onTransition {
|
||||||
|
case Connected -> Disconnected =>
|
||||||
|
statusListeners.foreach(_ ! ElectrumClient.ElectrumDisconnected)
|
||||||
|
context.system.eventStream.publish(ElectrumClient.ElectrumDisconnected)
|
||||||
|
}
|
||||||
|
|
||||||
|
initialize()
|
||||||
|
|
||||||
|
private def handleHeader(connection: ActorRef, tip: ElectrumClient.Header, d: Option[ConnectedData]) = {
|
||||||
|
// we update our block count even if it doesn't come from our current master
|
||||||
|
updateBlockCount(tip.block_height)
|
||||||
|
d match {
|
||||||
|
case None =>
|
||||||
|
// as soon as we have a connection to an electrum server, we select it as master
|
||||||
|
log.info(s"selecting master ${addresses(connection)} at $tip")
|
||||||
|
statusListeners.foreach(_ ! ElectrumClient.ElectrumReady(tip))
|
||||||
|
context.system.eventStream.publish(ElectrumClient.ElectrumReady(tip))
|
||||||
|
goto(Connected) using ConnectedData(connection, Map(connection -> tip))
|
||||||
|
case Some(d) if tip.block_height >= d.blockHeight + 2L =>
|
||||||
|
// we only switch to a new master if there is a significant difference with our current master, because
|
||||||
|
// we don't want to switch to a new master every time a new block arrives (some servers will be notified before others)
|
||||||
|
log.info(s"switching to master ${addresses(connection)} at $tip")
|
||||||
|
// we've switched to a new master, treat this as a disconnection/reconnection
|
||||||
|
// so users (wallet, watcher, ...) will reset their subscriptions
|
||||||
|
statusListeners.foreach(_ ! ElectrumClient.ElectrumDisconnected)
|
||||||
|
context.system.eventStream.publish(ElectrumClient.ElectrumDisconnected)
|
||||||
|
statusListeners.foreach(_ ! ElectrumClient.ElectrumReady(tip))
|
||||||
|
context.system.eventStream.publish(ElectrumClient.ElectrumReady(tip))
|
||||||
|
goto(Connected) using d.copy(master = connection, tips = d.tips + (connection -> tip))
|
||||||
|
case Some(d) =>
|
||||||
|
log.debug(s"received tip from ${addresses(connection)} $tip")
|
||||||
|
stay using d.copy(tips = d.tips + (connection -> tip))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def updateBlockCount(blockCount: Long): Unit = {
|
||||||
|
// when synchronizing we don't want to advertise previous blocks
|
||||||
|
if (Globals.blockCount.get() < blockCount) {
|
||||||
|
log.debug(s"current blockchain height=$blockCount")
|
||||||
|
context.system.eventStream.publish(CurrentBlockCount(blockCount))
|
||||||
|
Globals.blockCount.set(blockCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object ElectrumClientPool {
|
||||||
|
|
||||||
|
val MAX_CONNECTION_COUNT = 3
|
||||||
|
|
||||||
|
def readServerAddresses(stream: InputStream): Set[InetSocketAddress] = try {
|
||||||
|
val JObject(values) = JsonMethods.parse(stream)
|
||||||
|
val addresses = values.map {
|
||||||
|
case (name, fields) =>
|
||||||
|
val JString(port) = fields \ "t"
|
||||||
|
new InetSocketAddress(name, port.toInt)
|
||||||
|
}
|
||||||
|
addresses.toSet
|
||||||
|
} finally {
|
||||||
|
stream.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// @formatter:off
|
||||||
|
sealed trait State
|
||||||
|
case object Disconnected extends State
|
||||||
|
case object Connected extends State
|
||||||
|
|
||||||
|
sealed trait Data
|
||||||
|
case object DisconnectedData extends Data
|
||||||
|
case class ConnectedData(master: ActorRef, tips: Map[ActorRef, ElectrumClient.Header]) extends Data {
|
||||||
|
def blockHeight = tips.get(master).map(_.block_height).getOrElse(0L)
|
||||||
|
}
|
||||||
|
|
||||||
|
case object Connect
|
||||||
|
// @formatter:on
|
||||||
|
}
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.blockchain.electrum
|
package fr.acinq.eclair.blockchain.electrum
|
||||||
|
|
||||||
import akka.actor.{ActorRef, ActorSystem}
|
import akka.actor.{ActorRef, ActorSystem}
|
||||||
|
@ -63,5 +79,17 @@ class ElectrumEclairWallet(val wallet: ActorRef)(implicit system: ActorSystem, e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def sendAll(address: String, feeRatePerKw: Long): Future[(Transaction, Satoshi)] = {
|
||||||
|
val publicKeyScript = Base58Check.decode(address) match {
|
||||||
|
case (Base58.Prefix.PubkeyAddressTestnet, pubKeyHash) => Script.pay2pkh(pubKeyHash)
|
||||||
|
case (Base58.Prefix.ScriptAddressTestnet, scriptHash) => OP_HASH160 :: OP_PUSHDATA(scriptHash) :: OP_EQUAL :: Nil
|
||||||
|
}
|
||||||
|
(wallet ? SendAll(Script.write(publicKeyScript), feeRatePerKw))
|
||||||
|
.mapTo[SendAllResponse]
|
||||||
|
.map {
|
||||||
|
case SendAllResponse(tx, fee) => (tx, fee)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override def rollback(tx: Transaction): Future[Boolean] = (wallet ? CancelTransaction(tx)).map(_ => true)
|
override def rollback(tx: Transaction): Future[Boolean] = (wallet ? CancelTransaction(tx)).map(_ => true)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,25 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.blockchain.electrum
|
package fr.acinq.eclair.blockchain.electrum
|
||||||
|
|
||||||
import akka.actor.{ActorRef, FSM, Props}
|
import akka.actor.{ActorRef, FSM, Props}
|
||||||
import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey}
|
import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey}
|
||||||
import fr.acinq.bitcoin.DeterministicWallet.{ExtendedPrivateKey, derivePrivateKey, hardened}
|
import fr.acinq.bitcoin.DeterministicWallet.{ExtendedPrivateKey, derivePrivateKey, hardened}
|
||||||
import fr.acinq.bitcoin.{Base58, Base58Check, BinaryData, Block, Crypto, DeterministicWallet, OP_PUSHDATA, OutPoint, SIGHASH_ALL, Satoshi, Script, ScriptWitness, SigVersion, Transaction, TxIn, TxOut}
|
import fr.acinq.bitcoin.{Base58, Base58Check, BinaryData, Block, Crypto, DeterministicWallet, OP_PUSHDATA, OutPoint, SIGHASH_ALL, Satoshi, Script, ScriptElt, ScriptWitness, SigVersion, Transaction, TxIn, TxOut}
|
||||||
import fr.acinq.eclair.blockchain.bitcoind.rpc.Error
|
import fr.acinq.eclair.blockchain.bitcoind.rpc.Error
|
||||||
import fr.acinq.eclair.blockchain.electrum.ElectrumClient.{GetTransaction, GetTransactionResponse, TransactionHistoryItem, computeScriptHash}
|
import fr.acinq.eclair.blockchain.electrum.ElectrumClient.{GetTransaction, GetTransactionResponse, TransactionHistoryItem, computeScriptHash}
|
||||||
import fr.acinq.eclair.transactions.Transactions
|
import fr.acinq.eclair.transactions.Transactions
|
||||||
|
@ -80,7 +96,7 @@ class ElectrumWallet(seed: BinaryData, client: ActorRef, params: ElectrumWallet.
|
||||||
})
|
})
|
||||||
|
|
||||||
when(DISCONNECTED) {
|
when(DISCONNECTED) {
|
||||||
case Event(ElectrumClient.ElectrumReady, data) =>
|
case Event(ElectrumClient.ElectrumReady(_), data) =>
|
||||||
client ! ElectrumClient.HeaderSubscription(self)
|
client ! ElectrumClient.HeaderSubscription(self)
|
||||||
goto(WAITING_FOR_TIP) using data
|
goto(WAITING_FOR_TIP) using data
|
||||||
}
|
}
|
||||||
|
@ -210,6 +226,10 @@ class ElectrumWallet(seed: BinaryData, client: ActorRef, params: ElectrumWallet.
|
||||||
case Failure(t) => stay replying CompleteTransactionResponse(tx, Some(t))
|
case Failure(t) => stay replying CompleteTransactionResponse(tx, Some(t))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case Event(SendAll(publicKeyScript, feeRatePerKw), data) =>
|
||||||
|
val (tx, fee) = data.spendAll(publicKeyScript, feeRatePerKw)
|
||||||
|
stay replying SendAllResponse(tx, fee)
|
||||||
|
|
||||||
case Event(CommitTransaction(tx), data) =>
|
case Event(CommitTransaction(tx), data) =>
|
||||||
log.info(s"committing txid=${tx.txid}")
|
log.info(s"committing txid=${tx.txid}")
|
||||||
val data1 = data.commitTransaction(tx)
|
val data1 = data.commitTransaction(tx)
|
||||||
|
@ -282,6 +302,9 @@ object ElectrumWallet {
|
||||||
case class CompleteTransaction(tx: Transaction, feeRatePerKw: Long) extends Request
|
case class CompleteTransaction(tx: Transaction, feeRatePerKw: Long) extends Request
|
||||||
case class CompleteTransactionResponse(tx: Transaction, error: Option[Throwable]) extends Response
|
case class CompleteTransactionResponse(tx: Transaction, error: Option[Throwable]) extends Response
|
||||||
|
|
||||||
|
case class SendAll(publicKeyScript: BinaryData, feeRatePerKw: Long) extends Request
|
||||||
|
case class SendAllResponse(tx: Transaction, fee: Satoshi) extends Response
|
||||||
|
|
||||||
case class CommitTransaction(tx: Transaction) extends Request
|
case class CommitTransaction(tx: Transaction) extends Request
|
||||||
case class CommitTransactionResponse(tx: Transaction) extends Response
|
case class CommitTransactionResponse(tx: Transaction) extends Response
|
||||||
|
|
||||||
|
@ -684,13 +707,7 @@ object ElectrumWallet {
|
||||||
}
|
}
|
||||||
|
|
||||||
// sign our tx
|
// sign our tx
|
||||||
val tx3 = tx2.copy(txIn = tx2.txIn.zipWithIndex.map { case (txIn, i) =>
|
val tx3 = signTransaction(tx2)
|
||||||
val key = utxos(i).key
|
|
||||||
val sig = Transaction.signInput(tx2, i, Script.pay2pkh(key.publicKey), SIGHASH_ALL, Satoshi(utxos(i).item.value), SigVersion.SIGVERSION_WITNESS_V0, key.privateKey)
|
|
||||||
val sigScript = Script.write(OP_PUSHDATA(Script.write(Script.pay2wpkh(key.publicKey))) :: Nil)
|
|
||||||
val witness = ScriptWitness(sig :: key.publicKey.toBin :: Nil)
|
|
||||||
txIn.copy(signatureScript = sigScript, witness = witness)
|
|
||||||
})
|
|
||||||
//Transaction.correctlySpends(tx3, utxos.map(utxo => utxo.outPoint -> TxOut(Satoshi(utxo.item.value), computePublicKeyScript(utxo.key.publicKey))).toMap, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
|
//Transaction.correctlySpends(tx3, utxos.map(utxo => utxo.outPoint -> TxOut(Satoshi(utxo.item.value), computePublicKeyScript(utxo.key.publicKey))).toMap, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
|
||||||
|
|
||||||
// and add the completed tx to the lokcs
|
// and add the completed tx to the lokcs
|
||||||
|
@ -699,6 +716,17 @@ object ElectrumWallet {
|
||||||
(data1, tx3)
|
(data1, tx3)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def signTransaction(tx: Transaction) : Transaction = {
|
||||||
|
tx.copy(txIn = tx.txIn.zipWithIndex.map { case (txIn, i) =>
|
||||||
|
val utxo = utxos.find(_.outPoint == txIn.outPoint).getOrElse(throw new RuntimeException(s"cannot sign input that spends from ${txIn.outPoint}"))
|
||||||
|
val key = utxo.key
|
||||||
|
val sig = Transaction.signInput(tx, i, Script.pay2pkh(key.publicKey), SIGHASH_ALL, Satoshi(utxos(i).item.value), SigVersion.SIGVERSION_WITNESS_V0, key.privateKey)
|
||||||
|
val sigScript = Script.write(OP_PUSHDATA(Script.write(Script.pay2wpkh(key.publicKey))) :: Nil)
|
||||||
|
val witness = ScriptWitness(sig :: key.publicKey.toBin :: Nil)
|
||||||
|
txIn.copy(signatureScript = sigScript, witness = witness)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* unlocks input locked by a pending tx. call this method if the tx will not be used after all
|
* unlocks input locked by a pending tx. call this method if the tx will not be used after all
|
||||||
*
|
*
|
||||||
|
@ -730,6 +758,28 @@ object ElectrumWallet {
|
||||||
}
|
}
|
||||||
this.copy(locks = this.locks - tx, transactions = this.transactions + (tx.txid -> tx), heights = this.heights + (tx.txid -> 0L), history = history1)
|
this.copy(locks = this.locks - tx, transactions = this.transactions + (tx.txid -> tx), heights = this.heights + (tx.txid -> 0L), history = history1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* spend all our balance, including unconfirmed utxos and locked utxos (i.e utxos
|
||||||
|
* that are used in funding transactions that have not been published yet
|
||||||
|
* @param publicKeyScript script to send all our funds to
|
||||||
|
* @param feeRatePerKw fee rate in satoshi per kiloweight
|
||||||
|
* @return a (tx, fee) tuple, tx is a signed transaction that spends all our balance and
|
||||||
|
* fee is the associated bitcoin network fee
|
||||||
|
*/
|
||||||
|
def spendAll(publicKeyScript: BinaryData, feeRatePerKw: Long) : (Transaction, Satoshi) = {
|
||||||
|
// use confirmed and unconfirmed balance
|
||||||
|
val amount = balance._1 + balance._2
|
||||||
|
val tx = Transaction(version = 2, txIn = Nil, txOut = TxOut(amount, publicKeyScript) :: Nil, lockTime = 0)
|
||||||
|
// use all uxtos, including locked ones
|
||||||
|
val tx1 = addUtxosWithDummySig(tx, utxos)
|
||||||
|
val fee = Transactions.weight2fee(feeRatePerKw, tx1.weight())
|
||||||
|
val tx2 = tx1.copy(txOut = TxOut(amount - fee, publicKeyScript) :: Nil)
|
||||||
|
val tx3 = signTransaction(tx2)
|
||||||
|
(tx3, fee)
|
||||||
|
}
|
||||||
|
|
||||||
|
def spendAll(publicKeyScript: Seq[ScriptElt], feeRatePerKw: Long) : (Transaction, Satoshi) = spendAll(Script.write(publicKeyScript), feeRatePerKw)
|
||||||
}
|
}
|
||||||
|
|
||||||
object Data {
|
object Data {
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.blockchain.electrum
|
package fr.acinq.eclair.blockchain.electrum
|
||||||
|
|
||||||
import java.net.InetSocketAddress
|
import java.net.InetSocketAddress
|
||||||
|
@ -36,7 +52,7 @@ class ElectrumWatcher(client: ActorRef) extends Actor with Stash with ActorLoggi
|
||||||
def receive = disconnected(Set.empty, Nil, SortedMap.empty)
|
def receive = disconnected(Set.empty, Nil, SortedMap.empty)
|
||||||
|
|
||||||
def disconnected(watches: Set[Watch], publishQueue: Seq[PublishAsap], block2tx: SortedMap[Long, Seq[Transaction]]): Receive = {
|
def disconnected(watches: Set[Watch], publishQueue: Seq[PublishAsap], block2tx: SortedMap[Long, Seq[Transaction]]): Receive = {
|
||||||
case ElectrumClient.ElectrumReady =>
|
case ElectrumClient.ElectrumReady(_) =>
|
||||||
client ! ElectrumClient.HeaderSubscription(self)
|
client ! ElectrumClient.HeaderSubscription(self)
|
||||||
case ElectrumClient.HeaderSubscriptionResponse(header) =>
|
case ElectrumClient.HeaderSubscriptionResponse(header) =>
|
||||||
watches.map(self ! _)
|
watches.map(self ! _)
|
||||||
|
@ -192,10 +208,10 @@ class ElectrumWatcher(client: ActorRef) extends Actor with Stash with ActorLoggi
|
||||||
object ElectrumWatcher extends App {
|
object ElectrumWatcher extends App {
|
||||||
|
|
||||||
val system = ActorSystem()
|
val system = ActorSystem()
|
||||||
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
|
|
||||||
class Root extends Actor with ActorLogging {
|
class Root extends Actor with ActorLogging {
|
||||||
val serverAddresses = Seq(new InetSocketAddress("localhost", 51000), new InetSocketAddress("localhost", 51001))
|
val client = context.actorOf(Props(new ElectrumClient(new InetSocketAddress("localhost", 51000))), "client")
|
||||||
val client = context.actorOf(Props(new ElectrumClient(serverAddresses)), "client")
|
|
||||||
client ! ElectrumClient.AddStatusListener(self)
|
client ! ElectrumClient.AddStatusListener(self)
|
||||||
|
|
||||||
override def unhandled(message: Any): Unit = {
|
override def unhandled(message: Any): Unit = {
|
||||||
|
@ -204,7 +220,7 @@ object ElectrumWatcher extends App {
|
||||||
}
|
}
|
||||||
|
|
||||||
def receive = {
|
def receive = {
|
||||||
case ElectrumClient.ElectrumReady =>
|
case ElectrumClient.ElectrumReady(_) =>
|
||||||
log.info(s"starting watcher")
|
log.info(s"starting watcher")
|
||||||
context become running(context.actorOf(Props(new ElectrumWatcher(client)), "watcher"))
|
context become running(context.actorOf(Props(new ElectrumWatcher(client)), "watcher"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package fr.acinq.eclair.blockchain.electrum
|
||||||
|
|
||||||
|
import akka.actor.{Actor, ActorLogging, ActorRef, PoisonPill, Terminated}
|
||||||
|
import akka.io.Tcp
|
||||||
|
import akka.util.ByteString
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple ACK-based throttling mechanism for sending messages to a TCP connection
|
||||||
|
* See https://doc.akka.io/docs/akka/snapshot/scala/io-tcp.html#throttling-reads-and-writes
|
||||||
|
*/
|
||||||
|
class WriteAckSender(connection: ActorRef) extends Actor with ActorLogging {
|
||||||
|
|
||||||
|
// this actor will kill itself if connection dies
|
||||||
|
context watch connection
|
||||||
|
|
||||||
|
case object Ack extends Tcp.Event
|
||||||
|
|
||||||
|
override def receive = idle
|
||||||
|
|
||||||
|
def idle: Receive = {
|
||||||
|
case data: ByteString =>
|
||||||
|
connection ! Tcp.Write(data, Ack)
|
||||||
|
context become buffering(Vector.empty[ByteString])
|
||||||
|
}
|
||||||
|
|
||||||
|
def buffering(buffer: Vector[ByteString]): Receive = {
|
||||||
|
case _: ByteString if buffer.size > MAX_BUFFERED =>
|
||||||
|
log.warning(s"buffer overrun, closing connection")
|
||||||
|
connection ! PoisonPill
|
||||||
|
case data: ByteString =>
|
||||||
|
log.debug("buffering write {}", data)
|
||||||
|
context become buffering(buffer :+ data)
|
||||||
|
case Ack =>
|
||||||
|
buffer.headOption match {
|
||||||
|
case Some(data) =>
|
||||||
|
connection ! Tcp.Write(data, Ack)
|
||||||
|
context become buffering(buffer.drop(1))
|
||||||
|
case None =>
|
||||||
|
log.debug(s"got last ack, back to idle")
|
||||||
|
context become idle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override def unhandled(message: Any): Unit = message match {
|
||||||
|
case _: Tcp.ConnectionClosed => context stop self
|
||||||
|
case Terminated(_) => context stop self
|
||||||
|
case _ => log.warning(s"unhandled message $message")
|
||||||
|
}
|
||||||
|
|
||||||
|
val MAX_BUFFERED = 100000L
|
||||||
|
|
||||||
|
}
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.blockchain.fee
|
package fr.acinq.eclair.blockchain.fee
|
||||||
|
|
||||||
import fr.acinq.bitcoin._
|
import fr.acinq.bitcoin._
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.blockchain.fee
|
package fr.acinq.eclair.blockchain.fee
|
||||||
|
|
||||||
import akka.actor.ActorSystem
|
import akka.actor.ActorSystem
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.blockchain.fee
|
package fr.acinq.eclair.blockchain.fee
|
||||||
|
|
||||||
import scala.concurrent.Future
|
import scala.concurrent.Future
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.blockchain.fee
|
package fr.acinq.eclair.blockchain.fee
|
||||||
|
|
||||||
import akka.actor.ActorSystem
|
import akka.actor.ActorSystem
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.blockchain.fee
|
package fr.acinq.eclair.blockchain.fee
|
||||||
|
|
||||||
import scala.concurrent.{ExecutionContext, Future}
|
import scala.concurrent.{ExecutionContext, Future}
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.blockchain.fee
|
package fr.acinq.eclair.blockchain.fee
|
||||||
|
|
||||||
import fr.acinq.eclair.feerateByte2Kw
|
import fr.acinq.eclair.feerateByte2Kw
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.channel
|
package fr.acinq.eclair.channel
|
||||||
|
|
||||||
import akka.actor.{Actor, ActorLogging, ActorRef}
|
import akka.actor.{Actor, ActorLogging, ActorRef}
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.channel
|
package fr.acinq.eclair.channel
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.channel
|
package fr.acinq.eclair.channel
|
||||||
|
|
||||||
import akka.actor.ActorRef
|
import akka.actor.ActorRef
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.channel
|
package fr.acinq.eclair.channel
|
||||||
|
|
||||||
import fr.acinq.bitcoin.{BinaryData, Transaction}
|
import fr.acinq.bitcoin.{BinaryData, Transaction}
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.channel
|
package fr.acinq.eclair.channel
|
||||||
|
|
||||||
import akka.actor.ActorRef
|
import akka.actor.ActorRef
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.channel
|
package fr.acinq.eclair.channel
|
||||||
|
|
||||||
import akka.event.LoggingAdapter
|
import akka.event.LoggingAdapter
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.channel
|
package fr.acinq.eclair.channel
|
||||||
|
|
||||||
import akka.actor.{Actor, ActorLogging, ActorRef}
|
import akka.actor.{Actor, ActorLogging, ActorRef}
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.channel
|
package fr.acinq.eclair.channel
|
||||||
|
|
||||||
import akka.event.LoggingAdapter
|
import akka.event.LoggingAdapter
|
||||||
|
@ -96,8 +112,7 @@ object Helpers {
|
||||||
}
|
}
|
||||||
|
|
||||||
def makeAnnouncementSignatures(nodeParams: NodeParams, commitments: Commitments, shortChannelId: ShortChannelId) = {
|
def makeAnnouncementSignatures(nodeParams: NodeParams, commitments: Commitments, shortChannelId: ShortChannelId) = {
|
||||||
// TODO: empty features
|
val features = BinaryData.empty // empty features for now
|
||||||
val features = BinaryData("")
|
|
||||||
val (localNodeSig, localBitcoinSig) = nodeParams.keyManager.signChannelAnnouncement(commitments.localParams.channelKeyPath, nodeParams.chainHash, shortChannelId, commitments.remoteParams.nodeId, commitments.remoteParams.fundingPubKey, features)
|
val (localNodeSig, localBitcoinSig) = nodeParams.keyManager.signChannelAnnouncement(commitments.localParams.channelKeyPath, nodeParams.chainHash, shortChannelId, commitments.remoteParams.nodeId, commitments.remoteParams.fundingPubKey, features)
|
||||||
AnnouncementSignatures(commitments.channelId, shortChannelId, localNodeSig, localBitcoinSig)
|
AnnouncementSignatures(commitments.channelId, shortChannelId, localNodeSig, localBitcoinSig)
|
||||||
}
|
}
|
||||||
|
@ -281,7 +296,6 @@ object Helpers {
|
||||||
// all htlc output to us are delayed, so we need to claim them as soon as the delay is over
|
// all htlc output to us are delayed, so we need to claim them as soon as the delay is over
|
||||||
val htlcDelayedTxes = htlcTxes.flatMap {
|
val htlcDelayedTxes = htlcTxes.flatMap {
|
||||||
txinfo: TransactionWithInputInfo => generateTx("claim-delayed-output")(Try {
|
txinfo: TransactionWithInputInfo => generateTx("claim-delayed-output")(Try {
|
||||||
// TODO: we should use the current fee rate, not the initial fee rate that we get from localParams
|
|
||||||
val claimDelayed = Transactions.makeClaimDelayedOutputTx(
|
val claimDelayed = Transactions.makeClaimDelayedOutputTx(
|
||||||
txinfo.tx,
|
txinfo.tx,
|
||||||
Satoshi(localParams.dustLimitSatoshis),
|
Satoshi(localParams.dustLimitSatoshis),
|
||||||
|
@ -438,13 +452,12 @@ object Helpers {
|
||||||
|
|
||||||
// then we punish them by stealing their main output
|
// then we punish them by stealing their main output
|
||||||
val mainPenaltyTx = generateTx("main-penalty")(Try {
|
val mainPenaltyTx = generateTx("main-penalty")(Try {
|
||||||
// TODO: we should use the current fee rate, not the initial fee rate that we get from localParams
|
|
||||||
val txinfo = Transactions.makeMainPenaltyTx(tx, Satoshi(localParams.dustLimitSatoshis), remoteRevocationPubkey, localParams.defaultFinalScriptPubKey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, feeratePerKwPenalty)
|
val txinfo = Transactions.makeMainPenaltyTx(tx, Satoshi(localParams.dustLimitSatoshis), remoteRevocationPubkey, localParams.defaultFinalScriptPubKey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, feeratePerKwPenalty)
|
||||||
val sig = keyManager.sign(txinfo, keyManager.revocationPoint(localParams.channelKeyPath), remotePerCommitmentSecret)
|
val sig = keyManager.sign(txinfo, keyManager.revocationPoint(localParams.channelKeyPath), remotePerCommitmentSecret)
|
||||||
Transactions.addSigs(txinfo, sig)
|
Transactions.addSigs(txinfo, sig)
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO: we don't claim htlcs outputs yet
|
// TODO: we don't claim htlcs outputs yet for revoked transactions
|
||||||
|
|
||||||
// OPTIONAL: let's check transactions are actually spendable
|
// OPTIONAL: let's check transactions are actually spendable
|
||||||
//val txes = mainDelayedRevokedTx :: Nil
|
//val txes = mainDelayedRevokedTx :: Nil
|
||||||
|
@ -496,7 +509,6 @@ object Helpers {
|
||||||
// let's just pretend we received the preimage from the counterparty and build a fulfill message
|
// let's just pretend we received the preimage from the counterparty and build a fulfill message
|
||||||
(add, UpdateFulfillHtlc(add.channelId, add.id, paymentPreimage))
|
(add, UpdateFulfillHtlc(add.channelId, add.id, paymentPreimage))
|
||||||
}
|
}
|
||||||
// TODO: should we handle local htlcs here as well? currently timed out htlcs that we sent will never have an answer
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -620,7 +632,7 @@ object Helpers {
|
||||||
val isCommitTx = revokedCommitPublished.commitTx.txid == tx.txid
|
val isCommitTx = revokedCommitPublished.commitTx.txid == tx.txid
|
||||||
// does the tx spend an output of the local commitment tx?
|
// does the tx spend an output of the local commitment tx?
|
||||||
val spendsTheCommitTx = revokedCommitPublished.commitTx.txid == outPoint.txid
|
val spendsTheCommitTx = revokedCommitPublished.commitTx.txid == outPoint.txid
|
||||||
// TODO: we don't currently spend/steal htlc transactions
|
// TODO: we don't claim htlcs outputs yet for revoked transactions
|
||||||
isCommitTx || spendsTheCommitTx
|
isCommitTx || spendsTheCommitTx
|
||||||
}
|
}
|
||||||
// then we add the relevant outpoints to the map keeping track of which txid spends which outpoint
|
// then we add the relevant outpoints to the map keeping track of which txid spends which outpoint
|
||||||
|
@ -677,7 +689,7 @@ object Helpers {
|
||||||
// are there remaining spendable outputs from the commitment tx?
|
// are there remaining spendable outputs from the commitment tx?
|
||||||
val commitOutputsSpendableByUs = (revokedCommitPublished.claimMainOutputTx.toSeq ++ revokedCommitPublished.mainPenaltyTx)
|
val commitOutputsSpendableByUs = (revokedCommitPublished.claimMainOutputTx.toSeq ++ revokedCommitPublished.mainPenaltyTx)
|
||||||
.flatMap(_.txIn.map(_.outPoint)).toSet -- revokedCommitPublished.irrevocablySpent.keys
|
.flatMap(_.txIn.map(_.outPoint)).toSet -- revokedCommitPublished.irrevocablySpent.keys
|
||||||
// TODO: we don't currently spend htlc transactions
|
// TODO: we don't claim htlcs outputs yet for revoked transactions
|
||||||
isCommitTxConfirmed && commitOutputsSpendableByUs.isEmpty
|
isCommitTxConfirmed && commitOutputsSpendableByUs.isEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.channel
|
package fr.acinq.eclair.channel
|
||||||
|
|
||||||
import akka.actor.Status.Failure
|
import akka.actor.Status.Failure
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.crypto
|
package fr.acinq.eclair.crypto
|
||||||
|
|
||||||
import org.spongycastle.util.encoders.Hex
|
import org.spongycastle.util.encoders.Hex
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.crypto
|
package fr.acinq.eclair.crypto
|
||||||
|
|
||||||
import java.nio.ByteOrder
|
import java.nio.ByteOrder
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.crypto
|
package fr.acinq.eclair.crypto
|
||||||
|
|
||||||
import fr.acinq.bitcoin.Crypto.{Point, PrivateKey, PublicKey, Scalar}
|
import fr.acinq.bitcoin.Crypto.{Point, PrivateKey, PublicKey, Scalar}
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.crypto
|
package fr.acinq.eclair.crypto
|
||||||
|
|
||||||
import fr.acinq.bitcoin.{BinaryData, Crypto, DeterministicWallet}
|
import fr.acinq.bitcoin.{BinaryData, Crypto, DeterministicWallet}
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.crypto
|
package fr.acinq.eclair.crypto
|
||||||
|
|
||||||
import com.google.common.cache.{CacheBuilder, CacheLoader, LoadingCache}
|
import com.google.common.cache.{CacheBuilder, CacheLoader, LoadingCache}
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.crypto
|
package fr.acinq.eclair.crypto
|
||||||
|
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.crypto
|
package fr.acinq.eclair.crypto
|
||||||
|
|
||||||
import fr.acinq.bitcoin._
|
import fr.acinq.bitcoin._
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.crypto
|
package fr.acinq.eclair.crypto
|
||||||
|
|
||||||
import java.io.{ByteArrayInputStream, ByteArrayOutputStream, InputStream, OutputStream}
|
import java.io.{ByteArrayInputStream, ByteArrayOutputStream, InputStream, OutputStream}
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.crypto
|
package fr.acinq.eclair.crypto
|
||||||
|
|
||||||
import java.nio.ByteOrder
|
import java.nio.ByteOrder
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.db
|
package fr.acinq.eclair.db
|
||||||
|
|
||||||
import fr.acinq.bitcoin.BinaryData
|
import fr.acinq.bitcoin.BinaryData
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.db
|
package fr.acinq.eclair.db
|
||||||
|
|
||||||
import fr.acinq.bitcoin.{BinaryData, Satoshi}
|
import fr.acinq.bitcoin.{BinaryData, Satoshi}
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.db
|
package fr.acinq.eclair.db
|
||||||
|
|
||||||
import fr.acinq.bitcoin.BinaryData
|
import fr.acinq.bitcoin.BinaryData
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.db
|
package fr.acinq.eclair.db
|
||||||
|
|
||||||
import java.net.InetSocketAddress
|
import java.net.InetSocketAddress
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.db
|
package fr.acinq.eclair.db
|
||||||
|
|
||||||
import fr.acinq.bitcoin.BinaryData
|
import fr.acinq.bitcoin.BinaryData
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.db.sqlite
|
package fr.acinq.eclair.db.sqlite
|
||||||
|
|
||||||
import java.sql.Connection
|
import java.sql.Connection
|
||||||
|
@ -11,7 +27,11 @@ class SqliteChannelsDb(sqlite: Connection) extends ChannelsDb {
|
||||||
|
|
||||||
import SqliteUtils._
|
import SqliteUtils._
|
||||||
|
|
||||||
|
val DB_NAME = "channels"
|
||||||
|
val CURRENT_VERSION = 1
|
||||||
|
|
||||||
using(sqlite.createStatement()) { statement =>
|
using(sqlite.createStatement()) { statement =>
|
||||||
|
require(getVersion(statement, DB_NAME, CURRENT_VERSION) == CURRENT_VERSION) // there is only one version currently deployed
|
||||||
statement.executeUpdate("CREATE TABLE IF NOT EXISTS local_channels (channel_id BLOB NOT NULL PRIMARY KEY, data BLOB NOT NULL)")
|
statement.executeUpdate("CREATE TABLE IF NOT EXISTS local_channels (channel_id BLOB NOT NULL PRIMARY KEY, data BLOB NOT NULL)")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.db.sqlite
|
package fr.acinq.eclair.db.sqlite
|
||||||
|
|
||||||
import java.sql.Connection
|
import java.sql.Connection
|
||||||
|
@ -16,7 +32,11 @@ class SqliteNetworkDb(sqlite: Connection) extends NetworkDb {
|
||||||
|
|
||||||
import SqliteUtils._
|
import SqliteUtils._
|
||||||
|
|
||||||
|
val DB_NAME = "network"
|
||||||
|
val CURRENT_VERSION = 1
|
||||||
|
|
||||||
using(sqlite.createStatement()) { statement =>
|
using(sqlite.createStatement()) { statement =>
|
||||||
|
require(getVersion(statement, DB_NAME, CURRENT_VERSION) == CURRENT_VERSION) // there is only one version currently deployed
|
||||||
statement.execute("PRAGMA foreign_keys = ON")
|
statement.execute("PRAGMA foreign_keys = ON")
|
||||||
statement.executeUpdate("CREATE TABLE IF NOT EXISTS nodes (node_id BLOB NOT NULL PRIMARY KEY, data BLOB NOT NULL)")
|
statement.executeUpdate("CREATE TABLE IF NOT EXISTS nodes (node_id BLOB NOT NULL PRIMARY KEY, data BLOB NOT NULL)")
|
||||||
statement.executeUpdate("CREATE TABLE IF NOT EXISTS channels (short_channel_id INTEGER NOT NULL PRIMARY KEY, node_id_1 BLOB NOT NULL, node_id_2 BLOB NOT NULL)")
|
statement.executeUpdate("CREATE TABLE IF NOT EXISTS channels (short_channel_id INTEGER NOT NULL PRIMARY KEY, node_id_1 BLOB NOT NULL, node_id_2 BLOB NOT NULL)")
|
||||||
|
|
|
@ -1,9 +1,25 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.db.sqlite
|
package fr.acinq.eclair.db.sqlite
|
||||||
|
|
||||||
import java.sql.Connection
|
import java.sql.Connection
|
||||||
|
|
||||||
import fr.acinq.bitcoin.BinaryData
|
import fr.acinq.bitcoin.BinaryData
|
||||||
import fr.acinq.eclair.db.sqlite.SqliteUtils.using
|
import fr.acinq.eclair.db.sqlite.SqliteUtils.{getVersion, using}
|
||||||
import fr.acinq.eclair.db.{Payment, PaymentsDb}
|
import fr.acinq.eclair.db.{Payment, PaymentsDb}
|
||||||
import grizzled.slf4j.Logging
|
import grizzled.slf4j.Logging
|
||||||
|
|
||||||
|
@ -21,7 +37,11 @@ import scala.collection.immutable.Queue
|
||||||
*/
|
*/
|
||||||
class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging {
|
class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging {
|
||||||
|
|
||||||
|
val DB_NAME = "payments"
|
||||||
|
val CURRENT_VERSION = 1
|
||||||
|
|
||||||
using(sqlite.createStatement()) { statement =>
|
using(sqlite.createStatement()) { statement =>
|
||||||
|
require(getVersion(statement, DB_NAME, CURRENT_VERSION) == CURRENT_VERSION) // there is only one version currently deployed
|
||||||
statement.executeUpdate("CREATE TABLE IF NOT EXISTS payments (payment_hash BLOB NOT NULL PRIMARY KEY, amount_msat INTEGER NOT NULL, timestamp INTEGER NOT NULL)")
|
statement.executeUpdate("CREATE TABLE IF NOT EXISTS payments (payment_hash BLOB NOT NULL PRIMARY KEY, amount_msat INTEGER NOT NULL, timestamp INTEGER NOT NULL)")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.db.sqlite
|
package fr.acinq.eclair.db.sqlite
|
||||||
|
|
||||||
import java.net.InetSocketAddress
|
import java.net.InetSocketAddress
|
||||||
|
@ -6,13 +22,17 @@ import java.sql.Connection
|
||||||
import fr.acinq.bitcoin.Crypto
|
import fr.acinq.bitcoin.Crypto
|
||||||
import fr.acinq.bitcoin.Crypto.PublicKey
|
import fr.acinq.bitcoin.Crypto.PublicKey
|
||||||
import fr.acinq.eclair.db.PeersDb
|
import fr.acinq.eclair.db.PeersDb
|
||||||
import fr.acinq.eclair.db.sqlite.SqliteUtils.using
|
import fr.acinq.eclair.db.sqlite.SqliteUtils.{getVersion, using}
|
||||||
import fr.acinq.eclair.wire.LightningMessageCodecs.socketaddress
|
import fr.acinq.eclair.wire.LightningMessageCodecs.socketaddress
|
||||||
import scodec.bits.BitVector
|
import scodec.bits.BitVector
|
||||||
|
|
||||||
class SqlitePeersDb(sqlite: Connection) extends PeersDb {
|
class SqlitePeersDb(sqlite: Connection) extends PeersDb {
|
||||||
|
|
||||||
|
val DB_NAME = "peers"
|
||||||
|
val CURRENT_VERSION = 1
|
||||||
|
|
||||||
using(sqlite.createStatement()) { statement =>
|
using(sqlite.createStatement()) { statement =>
|
||||||
|
require(getVersion(statement, DB_NAME, CURRENT_VERSION) == CURRENT_VERSION) // there is only one version currently deployed
|
||||||
statement.executeUpdate("CREATE TABLE IF NOT EXISTS peers (node_id BLOB NOT NULL PRIMARY KEY, data BLOB NOT NULL)")
|
statement.executeUpdate("CREATE TABLE IF NOT EXISTS peers (node_id BLOB NOT NULL PRIMARY KEY, data BLOB NOT NULL)")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.db.sqlite
|
package fr.acinq.eclair.db.sqlite
|
||||||
|
|
||||||
import java.sql.Connection
|
import java.sql.Connection
|
||||||
|
@ -5,12 +21,16 @@ import java.sql.Connection
|
||||||
import fr.acinq.bitcoin.BinaryData
|
import fr.acinq.bitcoin.BinaryData
|
||||||
import fr.acinq.eclair.channel.Command
|
import fr.acinq.eclair.channel.Command
|
||||||
import fr.acinq.eclair.db.PendingRelayDb
|
import fr.acinq.eclair.db.PendingRelayDb
|
||||||
import fr.acinq.eclair.db.sqlite.SqliteUtils.{codecSequence, using}
|
import fr.acinq.eclair.db.sqlite.SqliteUtils.{codecSequence, getVersion, using}
|
||||||
import fr.acinq.eclair.wire.CommandCodecs.cmdCodec
|
import fr.acinq.eclair.wire.CommandCodecs.cmdCodec
|
||||||
|
|
||||||
class SqlitePendingRelayDb(sqlite: Connection) extends PendingRelayDb {
|
class SqlitePendingRelayDb(sqlite: Connection) extends PendingRelayDb {
|
||||||
|
|
||||||
|
val DB_NAME = "pending_relay"
|
||||||
|
val CURRENT_VERSION = 1
|
||||||
|
|
||||||
using(sqlite.createStatement()) { statement =>
|
using(sqlite.createStatement()) { statement =>
|
||||||
|
require(getVersion(statement, DB_NAME, CURRENT_VERSION) == CURRENT_VERSION) // there is only one version currently deployed
|
||||||
// note: should we use a foreign key to local_channels table here?
|
// note: should we use a foreign key to local_channels table here?
|
||||||
statement.executeUpdate("CREATE TABLE IF NOT EXISTS pending_relay (channel_id BLOB NOT NULL, htlc_id INTEGER NOT NULL, data BLOB NOT NULL, PRIMARY KEY(channel_id, htlc_id))")
|
statement.executeUpdate("CREATE TABLE IF NOT EXISTS pending_relay (channel_id BLOB NOT NULL, htlc_id INTEGER NOT NULL, data BLOB NOT NULL, PRIMARY KEY(channel_id, htlc_id))")
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.db.sqlite
|
package fr.acinq.eclair.db.sqlite
|
||||||
|
|
||||||
import java.sql.{ResultSet, Statement}
|
import java.sql.{ResultSet, Statement}
|
||||||
|
@ -23,6 +39,24 @@ object SqliteUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Several logical databases (channels, network, peers) may be stored in the same physical sqlite database.
|
||||||
|
* We keep track of their respective version using a dedicated table.
|
||||||
|
*
|
||||||
|
* @param statement
|
||||||
|
* @param db_name
|
||||||
|
* @param currentVersion
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
def getVersion(statement: Statement, db_name: String, currentVersion: Int): Int = {
|
||||||
|
statement.executeUpdate("CREATE TABLE IF NOT EXISTS versions (db_name TEXT NOT NULL PRIMARY KEY, version INTEGER NOT NULL)")
|
||||||
|
// if there was no version for the current db, then insert the current version
|
||||||
|
statement.executeUpdate(s"INSERT OR IGNORE INTO versions VALUES ('$db_name', $currentVersion)")
|
||||||
|
// if there was a previous version installed, this will return a different value from current version
|
||||||
|
val res = statement.executeQuery(s"SELECT version FROM versions WHERE db_name='$db_name'")
|
||||||
|
res.getInt("version")
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This helper assumes that there is a "data" column available, decodable with the provided codec
|
* This helper assumes that there is a "data" column available, decodable with the provided codec
|
||||||
*
|
*
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.io
|
package fr.acinq.eclair.io
|
||||||
|
|
||||||
import java.net.InetSocketAddress
|
import java.net.InetSocketAddress
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.io
|
package fr.acinq.eclair.io
|
||||||
|
|
||||||
import java.net.InetSocketAddress
|
import java.net.InetSocketAddress
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.io
|
package fr.acinq.eclair.io
|
||||||
|
|
||||||
import com.google.common.net.HostAndPort
|
import com.google.common.net.HostAndPort
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.io
|
package fr.acinq.eclair.io
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.io
|
package fr.acinq.eclair.io
|
||||||
|
|
||||||
import java.net.InetSocketAddress
|
import java.net.InetSocketAddress
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.io
|
package fr.acinq.eclair.io
|
||||||
|
|
||||||
import java.net.InetSocketAddress
|
import java.net.InetSocketAddress
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq
|
package fr.acinq
|
||||||
|
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.payment
|
package fr.acinq.eclair.payment
|
||||||
|
|
||||||
import akka.actor.{Actor, ActorLogging, ActorRef}
|
import akka.actor.{Actor, ActorLogging, ActorRef}
|
||||||
|
|
|
@ -1,9 +1,26 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.payment
|
package fr.acinq.eclair.payment
|
||||||
|
|
||||||
import akka.actor.{Actor, ActorLogging, Props, Status}
|
import akka.actor.{Actor, ActorLogging, Props, Status}
|
||||||
import fr.acinq.bitcoin.{BinaryData, Crypto, MilliSatoshi}
|
import fr.acinq.bitcoin.{BinaryData, Crypto, MilliSatoshi}
|
||||||
import fr.acinq.eclair.channel.{CMD_FAIL_HTLC, CMD_FULFILL_HTLC, Channel}
|
import fr.acinq.eclair.channel.{CMD_FAIL_HTLC, CMD_FULFILL_HTLC, Channel}
|
||||||
import fr.acinq.eclair.db.Payment
|
import fr.acinq.eclair.db.Payment
|
||||||
|
import fr.acinq.eclair.payment.PaymentLifecycle.{CheckPayment, ReceivePayment}
|
||||||
import fr.acinq.eclair.wire._
|
import fr.acinq.eclair.wire._
|
||||||
|
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.payment
|
package fr.acinq.eclair.payment
|
||||||
|
|
||||||
import akka.actor.{Actor, ActorLogging, ActorRef}
|
import akka.actor.{Actor, ActorLogging, ActorRef}
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.payment
|
package fr.acinq.eclair.payment
|
||||||
|
|
||||||
import fr.acinq.bitcoin.{BinaryData, MilliSatoshi}
|
import fr.acinq.bitcoin.{BinaryData, MilliSatoshi}
|
||||||
|
|
|
@ -1,7 +1,24 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.payment
|
package fr.acinq.eclair.payment
|
||||||
|
|
||||||
import akka.actor.{Actor, ActorLogging, ActorRef, Props}
|
import akka.actor.{Actor, ActorLogging, ActorRef, Props}
|
||||||
import fr.acinq.bitcoin.Crypto.PublicKey
|
import fr.acinq.bitcoin.Crypto.PublicKey
|
||||||
|
import fr.acinq.eclair.payment.PaymentLifecycle.SendPayment
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by PM on 29/08/2016.
|
* Created by PM on 29/08/2016.
|
||||||
|
|
|
@ -1,13 +1,33 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.payment
|
package fr.acinq.eclair.payment
|
||||||
|
|
||||||
|
import java.text.NumberFormat
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
import akka.actor.{ActorRef, FSM, Props, Status}
|
import akka.actor.{ActorRef, FSM, Props, Status}
|
||||||
import fr.acinq.bitcoin.Crypto.PublicKey
|
import fr.acinq.bitcoin.Crypto.PublicKey
|
||||||
import fr.acinq.bitcoin.{BinaryData, MilliSatoshi}
|
import fr.acinq.bitcoin.{BinaryData, MilliSatoshi}
|
||||||
import fr.acinq.eclair._
|
import fr.acinq.eclair._
|
||||||
import fr.acinq.eclair.blockchain.WatchEventSpentBasic
|
import fr.acinq.eclair.blockchain.WatchEventSpentBasic
|
||||||
import fr.acinq.eclair.channel._
|
import fr.acinq.eclair.channel.{AddHtlcFailed, CMD_ADD_HTLC, Channel, Register, _}
|
||||||
import fr.acinq.eclair.crypto.{Sphinx, TransportHandler}
|
|
||||||
import fr.acinq.eclair.crypto.Sphinx.{ErrorPacket, Packet}
|
import fr.acinq.eclair.crypto.Sphinx.{ErrorPacket, Packet}
|
||||||
|
import fr.acinq.eclair.crypto.{Sphinx, TransportHandler}
|
||||||
|
import fr.acinq.eclair.payment.PaymentLifecycle._
|
||||||
import fr.acinq.eclair.payment.PaymentRequest.ExtraHop
|
import fr.acinq.eclair.payment.PaymentRequest.ExtraHop
|
||||||
import fr.acinq.eclair.router._
|
import fr.acinq.eclair.router._
|
||||||
import fr.acinq.eclair.wire._
|
import fr.acinq.eclair.wire._
|
||||||
|
@ -15,40 +35,10 @@ import scodec.Attempt
|
||||||
|
|
||||||
import scala.util.{Failure, Success}
|
import scala.util.{Failure, Success}
|
||||||
|
|
||||||
// @formatter:off
|
|
||||||
case class ReceivePayment(amountMsat_opt: Option[MilliSatoshi], description: String)
|
|
||||||
/**
|
|
||||||
* @param finalCltvExpiry by default we choose finalCltvExpiry = Channel.MIN_CLTV_EXPIRY + 1 to not have our htlc fail when a new block has just been found
|
|
||||||
*/
|
|
||||||
case class SendPayment(amountMsat: Long, paymentHash: BinaryData, targetNodeId: PublicKey, assistedRoutes: Seq[Seq[ExtraHop]] = Nil, finalCltvExpiry: Long = Channel.MIN_CLTV_EXPIRY + 1, maxAttempts: Int = 5)
|
|
||||||
case class CheckPayment(paymentHash: BinaryData)
|
|
||||||
|
|
||||||
sealed trait PaymentResult
|
|
||||||
case class PaymentSucceeded(amountMsat: Long, paymentHash: BinaryData, paymentPreimage: BinaryData, route: Seq[Hop]) extends PaymentResult // note: the amount includes fees
|
|
||||||
sealed trait PaymentFailure
|
|
||||||
case class LocalFailure(t: Throwable) extends PaymentFailure
|
|
||||||
case class RemoteFailure(route: Seq[Hop], e: ErrorPacket) extends PaymentFailure
|
|
||||||
case class UnreadableRemoteFailure(route: Seq[Hop]) extends PaymentFailure
|
|
||||||
case class PaymentFailed(paymentHash: BinaryData, failures: Seq[PaymentFailure]) extends PaymentResult
|
|
||||||
|
|
||||||
sealed trait Data
|
|
||||||
case object WaitingForRequest extends Data
|
|
||||||
case class WaitingForRoute(sender: ActorRef, c: SendPayment, failures: Seq[PaymentFailure]) extends Data
|
|
||||||
case class WaitingForComplete(sender: ActorRef, c: SendPayment, cmd: CMD_ADD_HTLC, failures: Seq[PaymentFailure], sharedSecrets: Seq[(BinaryData, PublicKey)], ignoreNodes: Set[PublicKey], ignoreChannels: Set[ChannelDesc], hops: Seq[Hop]) extends Data
|
|
||||||
|
|
||||||
sealed trait State
|
|
||||||
case object WAITING_FOR_REQUEST extends State
|
|
||||||
case object WAITING_FOR_ROUTE extends State
|
|
||||||
case object WAITING_FOR_PAYMENT_COMPLETE extends State
|
|
||||||
|
|
||||||
// @formatter:on
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by PM on 26/08/2016.
|
* Created by PM on 26/08/2016.
|
||||||
*/
|
*/
|
||||||
class PaymentLifecycle(sourceNodeId: PublicKey, router: ActorRef, register: ActorRef) extends FSM[State, Data] {
|
class PaymentLifecycle(sourceNodeId: PublicKey, router: ActorRef, register: ActorRef) extends FSM[PaymentLifecycle.State, PaymentLifecycle.Data] {
|
||||||
|
|
||||||
import PaymentLifecycle._
|
|
||||||
|
|
||||||
startWith(WAITING_FOR_REQUEST, WaitingForRequest)
|
startWith(WAITING_FOR_REQUEST, WaitingForRequest)
|
||||||
|
|
||||||
|
@ -64,8 +54,15 @@ class PaymentLifecycle(sourceNodeId: PublicKey, router: ActorRef, register: Acto
|
||||||
val firstHop = hops.head
|
val firstHop = hops.head
|
||||||
val finalExpiry = Globals.blockCount.get().toInt + c.finalCltvExpiry.toInt
|
val finalExpiry = Globals.blockCount.get().toInt + c.finalCltvExpiry.toInt
|
||||||
val (cmd, sharedSecrets) = buildCommand(c.amountMsat, finalExpiry, c.paymentHash, hops)
|
val (cmd, sharedSecrets) = buildCommand(c.amountMsat, finalExpiry, c.paymentHash, hops)
|
||||||
register ! Register.ForwardShortId(firstHop.lastUpdate.shortChannelId, cmd)
|
val feePct = (cmd.amountMsat - c.amountMsat) / c.amountMsat.toDouble // c.amountMsat is required to be > 0, have to convert to double, otherwise will be rounded
|
||||||
goto(WAITING_FOR_PAYMENT_COMPLETE) using WaitingForComplete(s, c, cmd, failures, sharedSecrets, ignoreNodes, ignoreChannels, hops)
|
if (feePct > c.maxFeePct) {
|
||||||
|
log.info(s"cheapest route found is too expensive: feePct=$feePct maxFeePct=${c.maxFeePct}")
|
||||||
|
reply(s, PaymentFailed(c.paymentHash, failures = failures :+ LocalFailure(RouteTooExpensive(feePct, c.maxFeePct))))
|
||||||
|
stop(FSM.Normal)
|
||||||
|
} else {
|
||||||
|
register ! Register.ForwardShortId(firstHop.lastUpdate.shortChannelId, cmd)
|
||||||
|
goto(WAITING_FOR_PAYMENT_COMPLETE) using WaitingForComplete(s, c, cmd, failures, sharedSecrets, ignoreNodes, ignoreChannels, hops)
|
||||||
|
}
|
||||||
|
|
||||||
case Event(Status.Failure(t), WaitingForRoute(s, c, failures)) =>
|
case Event(Status.Failure(t), WaitingForRoute(s, c, failures)) =>
|
||||||
reply(s, PaymentFailed(c.paymentHash, failures = failures :+ LocalFailure(t)))
|
reply(s, PaymentFailed(c.paymentHash, failures = failures :+ LocalFailure(t)))
|
||||||
|
@ -190,6 +187,42 @@ object PaymentLifecycle {
|
||||||
|
|
||||||
def props(sourceNodeId: PublicKey, router: ActorRef, register: ActorRef) = Props(classOf[PaymentLifecycle], sourceNodeId, router, register)
|
def props(sourceNodeId: PublicKey, router: ActorRef, register: ActorRef) = Props(classOf[PaymentLifecycle], sourceNodeId, router, register)
|
||||||
|
|
||||||
|
// @formatter:off
|
||||||
|
case class ReceivePayment(amountMsat_opt: Option[MilliSatoshi], description: String)
|
||||||
|
/**
|
||||||
|
* @param finalCltvExpiry by default we choose finalCltvExpiry = Channel.MIN_CLTV_EXPIRY + 1 to not have our htlc fail when a new block has just been found
|
||||||
|
* @param maxFeePct set by default to 3% as a safety measure (even if a route is found, if fee is higher than that payment won't be attempted)
|
||||||
|
*/
|
||||||
|
case class SendPayment(amountMsat: Long, paymentHash: BinaryData, targetNodeId: PublicKey, assistedRoutes: Seq[Seq[ExtraHop]] = Nil, finalCltvExpiry: Long = Channel.MIN_CLTV_EXPIRY + 1, maxAttempts: Int = 5, maxFeePct: Double = 0.03) {
|
||||||
|
require(amountMsat > 0, s"amountMsat must be > 0")
|
||||||
|
}
|
||||||
|
case class CheckPayment(paymentHash: BinaryData)
|
||||||
|
|
||||||
|
sealed trait PaymentResult
|
||||||
|
case class PaymentSucceeded(amountMsat: Long, paymentHash: BinaryData, paymentPreimage: BinaryData, route: Seq[Hop]) extends PaymentResult // note: the amount includes fees
|
||||||
|
sealed trait PaymentFailure
|
||||||
|
case class LocalFailure(t: Throwable) extends PaymentFailure
|
||||||
|
case class RemoteFailure(route: Seq[Hop], e: ErrorPacket) extends PaymentFailure
|
||||||
|
case class UnreadableRemoteFailure(route: Seq[Hop]) extends PaymentFailure
|
||||||
|
case class PaymentFailed(paymentHash: BinaryData, failures: Seq[PaymentFailure]) extends PaymentResult
|
||||||
|
|
||||||
|
sealed trait Data
|
||||||
|
case object WaitingForRequest extends Data
|
||||||
|
case class WaitingForRoute(sender: ActorRef, c: SendPayment, failures: Seq[PaymentFailure]) extends Data
|
||||||
|
case class WaitingForComplete(sender: ActorRef, c: SendPayment, cmd: CMD_ADD_HTLC, failures: Seq[PaymentFailure], sharedSecrets: Seq[(BinaryData, PublicKey)], ignoreNodes: Set[PublicKey], ignoreChannels: Set[ChannelDesc], hops: Seq[Hop]) extends Data
|
||||||
|
|
||||||
|
sealed trait State
|
||||||
|
case object WAITING_FOR_REQUEST extends State
|
||||||
|
case object WAITING_FOR_ROUTE extends State
|
||||||
|
case object WAITING_FOR_PAYMENT_COMPLETE extends State
|
||||||
|
|
||||||
|
val percentageFormatter = NumberFormat.getPercentInstance(Locale.US) // force US locale to always get "fee=0.272% max=0.1%" (otherwise depending on locale it can be "fee=0,272 % max=0,1 %")
|
||||||
|
percentageFormatter.setMaximumFractionDigits(3)
|
||||||
|
case class RouteTooExpensive(feePct: Double, maxFeePct: Double) extends RuntimeException(s"cheapest route found is too expensive: fee=${percentageFormatter.format(feePct)} max=${percentageFormatter.format(maxFeePct)}")
|
||||||
|
|
||||||
|
// @formatter:on
|
||||||
|
|
||||||
|
|
||||||
def buildOnion(nodes: Seq[PublicKey], payloads: Seq[PerHopPayload], associatedData: BinaryData): Sphinx.PacketAndSecrets = {
|
def buildOnion(nodes: Seq[PublicKey], payloads: Seq[PerHopPayload], associatedData: BinaryData): Sphinx.PacketAndSecrets = {
|
||||||
require(nodes.size == payloads.size)
|
require(nodes.size == payloads.size)
|
||||||
val sessionKey = randomKey
|
val sessionKey = randomKey
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.payment
|
package fr.acinq.eclair.payment
|
||||||
|
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
|
@ -105,15 +121,16 @@ object PaymentRequest {
|
||||||
// https://github.com/lightningnetwork/lightning-rfc/blob/master/02-peer-protocol.md#adding-an-htlc-update_add_htlc
|
// https://github.com/lightningnetwork/lightning-rfc/blob/master/02-peer-protocol.md#adding-an-htlc-update_add_htlc
|
||||||
val MAX_AMOUNT = MilliSatoshi(4294967296L)
|
val MAX_AMOUNT = MilliSatoshi(4294967296L)
|
||||||
|
|
||||||
|
val prefixes = Map(
|
||||||
|
Block.RegtestGenesisBlock.hash -> "lnbcrt",
|
||||||
|
Block.TestnetGenesisBlock.hash -> "lntb",
|
||||||
|
Block.LivenetGenesisBlock.hash -> "lnbc")
|
||||||
|
|
||||||
def apply(chainHash: BinaryData, amount: Option[MilliSatoshi], paymentHash: BinaryData, privateKey: PrivateKey,
|
def apply(chainHash: BinaryData, amount: Option[MilliSatoshi], paymentHash: BinaryData, privateKey: PrivateKey,
|
||||||
description: String, fallbackAddress: Option[String] = None, expirySeconds: Option[Long] = None,
|
description: String, fallbackAddress: Option[String] = None, expirySeconds: Option[Long] = None,
|
||||||
extraHops: Seq[Seq[ExtraHop]] = Nil, timestamp: Long = System.currentTimeMillis() / 1000L): PaymentRequest = {
|
extraHops: Seq[Seq[ExtraHop]] = Nil, timestamp: Long = System.currentTimeMillis() / 1000L): PaymentRequest = {
|
||||||
|
|
||||||
val prefix = chainHash match {
|
val prefix = prefixes(chainHash)
|
||||||
case Block.RegtestGenesisBlock.hash => "lntb"
|
|
||||||
case Block.TestnetGenesisBlock.hash => "lntb"
|
|
||||||
case Block.LivenetGenesisBlock.hash => "lnbc"
|
|
||||||
}
|
|
||||||
|
|
||||||
PaymentRequest(
|
PaymentRequest(
|
||||||
prefix = prefix,
|
prefix = prefix,
|
||||||
|
@ -216,11 +233,11 @@ object PaymentRequest {
|
||||||
/**
|
/**
|
||||||
* Extra hop contained in RoutingInfoTag
|
* Extra hop contained in RoutingInfoTag
|
||||||
*
|
*
|
||||||
* @param nodeId start of the channel
|
* @param nodeId start of the channel
|
||||||
* @param shortChannelId channel id
|
* @param shortChannelId channel id
|
||||||
* @param feeBaseMsat node fixed fee
|
* @param feeBaseMsat node fixed fee
|
||||||
* @param feeProportionalMillionths node proportional fee
|
* @param feeProportionalMillionths node proportional fee
|
||||||
* @param cltvExpiryDelta node cltv expiry delta
|
* @param cltvExpiryDelta node cltv expiry delta
|
||||||
*/
|
*/
|
||||||
case class ExtraHop(nodeId: PublicKey, shortChannelId: ShortChannelId, feeBaseMsat: Long, feeProportionalMillionths: Long, cltvExpiryDelta: Int) {
|
case class ExtraHop(nodeId: PublicKey, shortChannelId: ShortChannelId, feeBaseMsat: Long, feeProportionalMillionths: Long, cltvExpiryDelta: Int) {
|
||||||
def pack: Seq[Byte] = nodeId.toBin ++ Protocol.writeUInt64(shortChannelId.toLong, ByteOrder.BIG_ENDIAN) ++
|
def pack: Seq[Byte] = nodeId.toBin ++ Protocol.writeUInt64(shortChannelId.toLong, ByteOrder.BIG_ENDIAN) ++
|
||||||
|
@ -494,8 +511,8 @@ object PaymentRequest {
|
||||||
val message: BinaryData = hrp.getBytes ++ stream1.bytes
|
val message: BinaryData = hrp.getBytes ++ stream1.bytes
|
||||||
val (pub1, pub2) = Crypto.recoverPublicKey((r, s), Crypto.sha256(message))
|
val (pub1, pub2) = Crypto.recoverPublicKey((r, s), Crypto.sha256(message))
|
||||||
val pub = if (recid % 2 != 0) pub2 else pub1
|
val pub = if (recid % 2 != 0) pub2 else pub1
|
||||||
val prefix = hrp.take(4)
|
val prefix = prefixes.values.find(prefix => hrp.startsWith(prefix)).getOrElse(throw new RuntimeException("unknown prefix"))
|
||||||
val amount_opt = Amount.decode(hrp.drop(4))
|
val amount_opt = Amount.decode(hrp.drop(prefix.length))
|
||||||
val pr = PaymentRequest(prefix, amount_opt, timestamp, pub, tags.toList, signature)
|
val pr = PaymentRequest(prefix, amount_opt, timestamp, pub, tags.toList, signature)
|
||||||
val validSig = Crypto.verifySignature(Crypto.sha256(message), (r, s), pub)
|
val validSig = Crypto.verifySignature(Crypto.sha256(message), (r, s), pub)
|
||||||
require(validSig, "invalid signature")
|
require(validSig, "invalid signature")
|
||||||
|
|
|
@ -1,9 +1,26 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.payment
|
package fr.acinq.eclair.payment
|
||||||
|
|
||||||
import akka.actor.{Actor, ActorLogging, ActorRef, Props, Status}
|
import akka.actor.{Actor, ActorLogging, ActorRef, Props, Status}
|
||||||
import fr.acinq.bitcoin.{BinaryData, Crypto, MilliSatoshi}
|
import fr.acinq.bitcoin.{BinaryData, Crypto, MilliSatoshi}
|
||||||
import fr.acinq.eclair.channel._
|
import fr.acinq.eclair.channel._
|
||||||
import fr.acinq.eclair.crypto.Sphinx
|
import fr.acinq.eclair.crypto.Sphinx
|
||||||
|
import fr.acinq.eclair.payment.PaymentLifecycle.{PaymentFailed, PaymentSucceeded}
|
||||||
import fr.acinq.eclair.router.Announcements
|
import fr.acinq.eclair.router.Announcements
|
||||||
import fr.acinq.eclair.wire._
|
import fr.acinq.eclair.wire._
|
||||||
import fr.acinq.eclair.{Globals, NodeParams, ShortChannelId}
|
import fr.acinq.eclair.{Globals, NodeParams, ShortChannelId}
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.router
|
package fr.acinq.eclair.router
|
||||||
|
|
||||||
import java.net.InetSocketAddress
|
import java.net.InetSocketAddress
|
||||||
|
@ -54,7 +70,7 @@ object Announcements {
|
||||||
nodeId2 = nodeId2,
|
nodeId2 = nodeId2,
|
||||||
bitcoinKey1 = bitcoinKey1,
|
bitcoinKey1 = bitcoinKey1,
|
||||||
bitcoinKey2 = bitcoinKey2,
|
bitcoinKey2 = bitcoinKey2,
|
||||||
features = BinaryData(""),
|
features = BinaryData.empty,
|
||||||
chainHash = chainHash
|
chainHash = chainHash
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.router
|
package fr.acinq.eclair.router
|
||||||
|
|
||||||
import java.io.{ByteArrayInputStream, ByteArrayOutputStream}
|
import java.io.{ByteArrayInputStream, ByteArrayOutputStream}
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.router
|
package fr.acinq.eclair.router
|
||||||
|
|
||||||
import fr.acinq.bitcoin.Crypto.PublicKey
|
import fr.acinq.bitcoin.Crypto.PublicKey
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.router
|
package fr.acinq.eclair.router
|
||||||
|
|
||||||
import akka.actor.{ActorRef, FSM, Props, Status}
|
import akka.actor.{ActorRef, FSM, Props, Status}
|
||||||
|
@ -278,6 +294,7 @@ class Router(nodeParams: NodeParams, watcher: ActorRef) extends FSM[State, Data]
|
||||||
// it takes precedence over all other channel_updates we know
|
// it takes precedence over all other channel_updates we know
|
||||||
val assistedUpdates = assistedRoutes.flatMap(toFakeUpdates(_, end)).toMap
|
val assistedUpdates = assistedRoutes.flatMap(toFakeUpdates(_, end)).toMap
|
||||||
// we also filter out updates corresponding to channels/nodes that are blacklisted for this particular request
|
// we also filter out updates corresponding to channels/nodes that are blacklisted for this particular request
|
||||||
|
// TODO: in case of duplicates, d.updates will be overriden by assistedUpdates even if they are more recent!
|
||||||
val ignoredUpdates = getIgnoredChannelDesc(d.updates ++ d.privateUpdates ++ assistedUpdates, ignoreNodes) ++ ignoreChannels ++ d.excludedChannels
|
val ignoredUpdates = getIgnoredChannelDesc(d.updates ++ d.privateUpdates ++ assistedUpdates, ignoreNodes) ++ ignoreChannels ++ d.excludedChannels
|
||||||
log.info(s"finding a route $start->$end with assistedChannels={} ignoreNodes={} ignoreChannels={} excludedChannels={}", assistedUpdates.keys.mkString(","), ignoreNodes.map(_.toBin).mkString(","), ignoreChannels.mkString(","), d.excludedChannels.mkString(","))
|
log.info(s"finding a route $start->$end with assistedChannels={} ignoreNodes={} ignoreChannels={} excludedChannels={}", assistedUpdates.keys.mkString(","), ignoreNodes.map(_.toBin).mkString(","), ignoreChannels.mkString(","), d.excludedChannels.mkString(","))
|
||||||
findRoute(d.graph, start, end, withEdges = assistedUpdates, withoutEdges = ignoredUpdates)
|
findRoute(d.graph, start, end, withEdges = assistedUpdates, withoutEdges = ignoredUpdates)
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.router
|
package fr.acinq.eclair.router
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.transactions
|
package fr.acinq.eclair.transactions
|
||||||
|
|
||||||
import fr.acinq.eclair.wire._
|
import fr.acinq.eclair.wire._
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.transactions
|
package fr.acinq.eclair.transactions
|
||||||
|
|
||||||
import fr.acinq.bitcoin.Crypto.{PublicKey, ripemd160}
|
import fr.acinq.bitcoin.Crypto.{PublicKey, ripemd160}
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.transactions
|
package fr.acinq.eclair.transactions
|
||||||
|
|
||||||
import java.nio.ByteOrder
|
import java.nio.ByteOrder
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.wire
|
package fr.acinq.eclair.wire
|
||||||
|
|
||||||
import fr.acinq.bitcoin.DeterministicWallet.{ExtendedPrivateKey, KeyPath}
|
import fr.acinq.bitcoin.DeterministicWallet.{ExtendedPrivateKey, KeyPath}
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.wire
|
package fr.acinq.eclair.wire
|
||||||
|
|
||||||
import fr.acinq.eclair.channel.{CMD_FAIL_HTLC, CMD_FAIL_MALFORMED_HTLC, CMD_FULFILL_HTLC, Command}
|
import fr.acinq.eclair.channel.{CMD_FAIL_HTLC, CMD_FAIL_MALFORMED_HTLC, CMD_FULFILL_HTLC, Command}
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.wire
|
package fr.acinq.eclair.wire
|
||||||
|
|
||||||
import fr.acinq.bitcoin.BinaryData
|
import fr.acinq.bitcoin.BinaryData
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.wire
|
package fr.acinq.eclair.wire
|
||||||
|
|
||||||
import scodec.bits.{BitVector, ByteVector}
|
import scodec.bits.{BitVector, ByteVector}
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.wire
|
package fr.acinq.eclair.wire
|
||||||
|
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair.wire
|
package fr.acinq.eclair.wire
|
||||||
|
|
||||||
import java.net.InetSocketAddress
|
import java.net.InetSocketAddress
|
||||||
|
|
|
@ -1,27 +1,20 @@
|
||||||
package fr.acinq.eclair.crypto;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2016 Southern Storm Software, Pty Ltd.
|
* Copyright 2018 ACINQ SAS
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* copy of this software and associated documentation files (the "Software"),
|
* you may not use this file except in compliance with the License.
|
||||||
* to deal in the Software without restriction, including without limitation
|
* You may obtain a copy of the License at
|
||||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
||||||
* and/or sell copies of the Software, and to permit persons to whom the
|
|
||||||
* Software is furnished to do so, subject to the following conditions:
|
|
||||||
*
|
*
|
||||||
* The above copyright notice and this permission notice shall be included
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
* in all copies or substantial portions of the Software.
|
|
||||||
*
|
*
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
* See the License for the specific language governing permissions and
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
* limitations under the License.
|
||||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
||||||
* DEALINGS IN THE SOFTWARE.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
package fr.acinq.eclair.crypto;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,20 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright 2018 ACINQ SAS
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
<configuration scan="true" debug="false">
|
<configuration scan="true" debug="false">
|
||||||
|
|
||||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair
|
package fr.acinq.eclair
|
||||||
|
|
||||||
import fr.acinq.bitcoin.{Btc, MilliBtc, MilliSatoshi, Satoshi}
|
import fr.acinq.bitcoin.{Btc, MilliBtc, MilliSatoshi, Satoshi}
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package fr.acinq.eclair
|
package fr.acinq.eclair
|
||||||
|
|
||||||
import java.nio.ByteOrder
|
import java.nio.ByteOrder
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue