public 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 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
getIncompleteRefundTransaction()
and pass the resultant transaction through to the
server. Once you have retrieved the signature, use provideRefundSignature(byte[])
.
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 getMultisigContract()
) 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.
|
Constructor and Description |
---|
PaymentChannelClientState(Wallet wallet,
ECKey myKey,
ECKey serverMultisigKey,
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(Wallet.SendRequest req)
You can override this method in order to control the construction of the initial contract that creates the
channel.
|
Transaction |
getCompletedRefundTransaction()
Once the servers signature over the refund transaction has been received and provided using
provideRefundSignature(byte[]) then this
method can be called to receive the now valid and broadcastable refund transaction. |
Transaction |
getIncompleteRefundTransaction()
Returns a partially signed (invalid) refund transaction that should be passed to the server.
|
Transaction |
getMultisigContract()
Returns the transaction that locks the money to the agreement of both parties.
|
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.
|
PaymentChannelClientState.State |
getState()
This object implements a state machine, and this accessor returns which state it's currently in.
|
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.
|
PaymentChannelClientState.IncrementedPayment |
incrementPaymentBy(Coin size)
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
getIncompleteRefundTransaction() and
getMultisigContract() . |
boolean |
isSettlementTransaction(Transaction tx)
Returns true if the tx is a valid settlement transaction.
|
void |
provideRefundSignature(byte[] theirSignature)
When the servers signature for the refund transaction is received, call this to verify it and sign the
complete refund ourselves.
|
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. |
public PaymentChannelClientState(Wallet wallet, ECKey myKey, ECKey serverMultisigKey, 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.serverMultisigKey
- 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.
It is suggested you use at least Utils#CENT
to avoid paying fees if you need to spend the refund transactionexpiryTimeInSeconds
- At what point (UNIX timestamp +/- a few hours) the channel will expireVerificationException
- If either myKey's pubkey or serverMultisigKey's pubkey are non-canonical (ie invalid)public boolean isSettlementTransaction(Transaction tx)
public PaymentChannelClientState.State getState()
public void initiate() throws ValueOutOfRangeException, InsufficientMoneyException
getIncompleteRefundTransaction()
and
getMultisigContract()
. The way the contract is crafted can be adjusted by
overriding editContractSendRequest(org.bitcoinj.core.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 initiateprotected void editContractSendRequest(Wallet.SendRequest req)
public Transaction getMultisigContract()
incrementPaymentBy(Coin)
to
start paying the server.public Transaction getIncompleteRefundTransaction()
provideRefundSignature(byte[])
with the result.public void provideRefundSignature(byte[] theirSignature) throws VerificationException
When the servers signature for the refund transaction is received, call this to verify it and sign the complete refund ourselves.
If this does not throw an exception, we are secure against the loss of funds and can safely provide the server with the multi-sig contract to lock in the agreement. In this case, both the multisig contract and the refund transaction are automatically committed to wallet so that it can handle broadcasting the refund transaction at the appropriate time if necessary.
VerificationException
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) 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 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.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 Coin getRefundTxFees()
initiate()
public Transaction getCompletedRefundTransaction()
provideRefundSignature(byte[])
then this
method can be called to receive the now valid and broadcastable refund transaction.public Coin getTotalValue()
public Coin getValueRefunded()
public Coin getValueSpent()
Copyright © 2014. All rights reserved.