public abstract class PaymentChannelClientState extends Object
A payment channel is a method of sending money to someone such that the amount of money you send can be adjusted after the fact, in an efficient manner that does not require broadcasting to the network. This can be used to implement micropayments or other payment schemes in which immediate settlement is not required, but zero trust negotiation is. Note that this class only allows the amount of money sent to be incremented, not decremented.
This class has two subclasses, PaymentChannelV1ClientState
and PaymentChannelV2ClientState
for
protocols version 1 and 2.
This class implements the core state machine for the client side of the protocol. The server side is implemented
by PaymentChannelServerState
and PaymentChannelClientConnection
implements a network protocol
suitable for TCP/IP connections which moves this class through each state. We say that the party who is sending funds
is the client or initiating party. The party that is receiving the funds is the server or
receiving party. Although the underlying Bitcoin protocol is capable of more complex relationships than that,
this class implements only the simplest case.
A channel has an expiry parameter. If the server halts after the multi-signature contract which locks up the given value is broadcast you could get stuck in a state where you've lost all the money put into the contract. To avoid this, a refund transaction is agreed ahead of time but it may only be used/broadcast after the expiry time. This is specified in terms of block timestamps and once the timestamp of the chain chain approaches the given time (within a few hours), the channel must be closed or else the client will broadcast the refund transaction and take back all the money once the expiry time is reached.
To begin, the client calls initiate()
, which moves the channel into state
INITIATED and creates the initial multi-sig contract and refund transaction. If the wallet has insufficient funds an
exception will be thrown at this point. Once this is done, call
PaymentChannelV1ClientState.getIncompleteRefundTransaction()
and pass the resultant transaction through to the
server. Once you have retrieved the signature, use PaymentChannelV1ClientState.provideRefundSignature(byte[], KeyParameter)
.
You must then call storeChannelInWallet(Sha256Hash)
to store the refund transaction
in the wallet, protecting you against a malicious server attempting to destroy all your coins. At this point, you can
provide the server with the multi-sig contract (via PaymentChannelV1ClientState.getContract()
) safely.
Modifier and Type | Class and Description |
---|---|
static class |
PaymentChannelClientState.IncrementedPayment
Container for a signature and an amount that was sent.
|
static class |
PaymentChannelClientState.State
The different logical states the channel can be in.
|
Modifier and Type | Field and Description |
---|---|
protected StateMachine<PaymentChannelClientState.State> |
stateMachine |
protected org.bitcoinj.protocols.channels.StoredClientChannel |
storedChannel |
protected Coin |
valueToMe |
Constructor and Description |
---|
PaymentChannelClientState(Wallet wallet,
ECKey myKey,
ECKey serverKey,
Coin value,
long expiryTimeInSeconds)
Creates a state object for a payment channel client.
|
Modifier and Type | Method and Description |
---|---|
void |
checkNotExpired()
Checks if the channel is expired, setting state to
PaymentChannelClientState.State.EXPIRED , removing this channel from wallet
storage and throwing an IllegalStateException if it is. |
void |
disconnectFromChannel()
Sets this channel's state in
StoredPaymentChannelClientStates to unopened so this channel can be reopened
later. |
protected void |
editContractSendRequest(SendRequest req)
You can override this method in order to control the construction of the initial contract that creates the
channel.
|
abstract Transaction |
getContract()
Gets the contract which was used to initialize this channel
|
protected abstract Transaction |
getContractInternal()
Gets the contract without changing the state machine
|
protected abstract Script |
getContractScript() |
protected abstract long |
getExpiryTime() |
abstract int |
getMajorVersion() |
abstract Coin |
getRefundTxFees()
Returns the fees that will be paid if the refund transaction has to be claimed because the server failed to settle
the channel properly.
|
protected abstract Script |
getSignedScript()
Gets the script that is signed.
|
PaymentChannelClientState.State |
getState() |
protected abstract Multimap<PaymentChannelClientState.State,PaymentChannelClientState.State> |
getStateTransitions() |
abstract Coin |
getTotalValue()
Gets the total value of this channel (ie the maximum payment possible)
|
Coin |
getValueRefunded()
Gets the current amount refunded to us from the multisig contract (ie totalValue-valueSentToServer)
|
Coin |
getValueSpent()
Returns the amount of money sent on this channel so far.
|
protected abstract Coin |
getValueToMe() |
PaymentChannelClientState.IncrementedPayment |
incrementPaymentBy(Coin size,
org.spongycastle.crypto.params.KeyParameter userKey)
Updates the outputs on the payment contract transaction and re-signs it.
|
void |
initiate()
Creates the initial multisig contract and incomplete refund transaction which can be requested at the appropriate
time using
PaymentChannelV1ClientState.getIncompleteRefundTransaction() and
PaymentChannelV1ClientState.getContract() . |
abstract void |
initiate(org.spongycastle.crypto.params.KeyParameter userKey)
Creates the initial multisig contract and incomplete refund transaction which can be requested at the appropriate
time using
PaymentChannelV1ClientState.getIncompleteRefundTransaction() and
PaymentChannelV1ClientState.getContract() . |
protected void |
initWalletListeners() |
boolean |
isSettlementTransaction(Transaction tx)
Returns true if the tx is a valid settlement transaction.
|
void |
storeChannelInWallet(Sha256Hash id)
Stores this channel's state in the wallet as a part of a
StoredPaymentChannelClientStates wallet
extension and keeps it up-to-date each time payment is incremented. |
protected void |
updateChannelInWallet() |
protected void |
watchCloseConfirmations() |
protected Coin valueToMe
protected final StateMachine<PaymentChannelClientState.State> stateMachine
protected org.bitcoinj.protocols.channels.StoredClientChannel storedChannel
public PaymentChannelClientState(Wallet wallet, ECKey myKey, ECKey serverKey, Coin value, long expiryTimeInSeconds) throws VerificationException
initiate()
after construction (to avoid creating objects for channels which are
not going to finish opening) and thus some parameters provided here are only used in
initiate()
to create the Multisig contract and refund transaction.wallet
- a wallet that contains at least the specified amount of value.myKey
- a freshly generated private key for this channel.serverKey
- a public key retrieved from the server used for the initial multisig contractvalue
- how many satoshis to put into this contract. If the channel reaches this limit, it must be closed.expiryTimeInSeconds
- At what point (UNIX timestamp +/- a few hours) the channel will expireVerificationException
- If either myKey's pubkey or serverKey's pubkey are non-canonical (ie invalid)public boolean isSettlementTransaction(Transaction tx)
protected void initWalletListeners()
protected void watchCloseConfirmations()
public PaymentChannelClientState.State getState()
protected abstract Multimap<PaymentChannelClientState.State,PaymentChannelClientState.State> getStateTransitions()
public abstract int getMajorVersion()
public void initiate() throws ValueOutOfRangeException, InsufficientMoneyException
PaymentChannelV1ClientState.getIncompleteRefundTransaction()
and
PaymentChannelV1ClientState.getContract()
. The way the contract is crafted can be adjusted by
overriding PaymentChannelV1ClientState#editContractSendRequest(org.bitcoinj.wallet.Wallet.SendRequest)
.
By default unconfirmed coins are allowed to be used, as for micropayments the risk should be relatively low.ValueOutOfRangeException
- if the value being used is too small to be accepted by the networkInsufficientMoneyException
- if the wallet doesn't contain enough balance to initiatepublic abstract void initiate(@Nullable org.spongycastle.crypto.params.KeyParameter userKey) throws ValueOutOfRangeException, InsufficientMoneyException
PaymentChannelV1ClientState.getIncompleteRefundTransaction()
and
PaymentChannelV1ClientState.getContract()
. The way the contract is crafted can be adjusted by
overriding PaymentChannelV1ClientState#editContractSendRequest(org.bitcoinj.wallet.Wallet.SendRequest)
.
By default unconfirmed coins are allowed to be used, as for micropayments the risk should be relatively low.userKey
- Key derived from a user password, needed for any signing when the wallet is encrypted.
The wallet KeyCrypter is assumed.ValueOutOfRangeException
- if the value being used is too small to be accepted by the networkInsufficientMoneyException
- if the wallet doesn't contain enough balance to initiateprotected void editContractSendRequest(SendRequest req)
public abstract Transaction getContract()
public void checkNotExpired()
PaymentChannelClientState.State.EXPIRED
, removing this channel from wallet
storage and throwing an IllegalStateException
if it is.public PaymentChannelClientState.IncrementedPayment incrementPaymentBy(Coin size, @Nullable org.spongycastle.crypto.params.KeyParameter userKey) throws ValueOutOfRangeException
Updates the outputs on the payment contract transaction and re-signs it. The state must be READY in order to call this method. The signature that is returned should be sent to the server so it has the ability to broadcast the best seen payment when the channel closes or times out.
The returned signature is over the payment transaction, which we never have a valid copy of and thus there is no accessor for it on this object.
To spend the whole channel increment by PaymentChannelV1ClientState.getTotalValue()
-
getValueRefunded()
size
- How many satoshis to increment the payment by (note: not the new total).ValueOutOfRangeException
- If size is negative or the channel does not have sufficient money in it to
complete this payment.protected void updateChannelInWallet()
public void disconnectFromChannel()
StoredPaymentChannelClientStates
to unopened so this channel can be reopened
later.storeChannelInWallet(Sha256Hash)
public void storeChannelInWallet(Sha256Hash id)
Stores this channel's state in the wallet as a part of a StoredPaymentChannelClientStates
wallet
extension and keeps it up-to-date each time payment is incremented. This allows the
StoredPaymentChannelClientStates
object to keep track of timeouts and broadcast the refund transaction
when the channel expires.
A channel may only be stored after it has fully opened (ie state == State.READY). The wallet provided in the
constructor must already have a StoredPaymentChannelClientStates
object in its extensions set.
id
- A hash providing this channel with an id which uniquely identifies this server. It does not have to be
unique.public abstract Coin getRefundTxFees()
initiate()
public abstract Coin getTotalValue()
public Coin getValueRefunded()
public Coin getValueSpent()
protected abstract Coin getValueToMe()
protected abstract long getExpiryTime()
protected abstract Transaction getContractInternal()
protected abstract Script getContractScript()
protected abstract Script getSignedScript()
Copyright © 2016. All rights reserved.