lnd.xprv/lnstate/lnstate.go
Olaoluwa Osuntokun fcff17c336
multi: change all imports to roasbeef's forks
This commit will allow the general public to build lnd without jumping
through hoops setting up their local git branches nicely with all of
our forks.
2016-05-15 17:22:37 +03:00

437 lines
12 KiB
Go

package lnstate
import (
"fmt"
//"github.com/roasbeef/btcd/btcec"
//"atomic"
"sync"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/shachain"
"github.com/roasbeef/btcd/wire"
"github.com/roasbeef/btcutil"
)
//This is a state machine which allows for simultaneous high-volume
//bidirectional payments. The design goal is to for all steps to be
//non-blocking over-the-wire on signing/revoking with the counterparty.
//
//A *single* Lightning Network node on one reasonably high-speced computer
//should be able to conduct transactions at Visa-scale (one Lightning Network
//node can process thousands of transactions per second total), enabling
//incredible volume for the entire network. This state machine design should
//enable transaction volume for the entire network greater than current Visa
//card transaction load by many orders of magnitude.
//
//More info on the design is in lnwire/README.md
//NOTE: WORK IN PROGRESS (current working code in wallet is blocking -- it is
//implemented, but this version will be merged and replacing it soon)
//TODO: Will make channel-friendly & add in mutex stuff
//DUMMY FunCTIONS FOR UPDATING LATER
func disk() {
}
func net(msg lnwire.Message) {
fmt.Println(msg)
}
func (l *LNChannel) sendErrorPkt() {
}
//Will copy in code from channel.go...
const (
MAX_STAGED_HTLCS = 1000
MAX_UNREVOKED_COMMITMENTS = 16
MAX_UPDATED_HTLCS_PER_COMMITMENT = 1000
)
//Currently, the mutex locks across the entire channel, it's possible to update
//it to be more granular and to have each PaymentDescriptor have its own locks
//(and lock both simultaneously for minimal parts). However, this is
//complicated and will be updated in the future. Right now, since there is a
//per-channel global-lock, this should work fine and be fairly performant.
//
//Before calling another function which does a lock, make sure to l.Unlock()
//first, e.g. if a packet received triggers a packet to be sent.
//PaymentDescriptor states
//PRESTAGE: We're not sure if the other guy wants to follow through
//STAGED: Both parties informally agree to add to their commitments
//SIGNING_AND_REVOKING: In the process of activating the HTLC
//
//Only one state is allowed at a time. timeout/settle can only begin on an
//ADD_COMPLETE state. If it times out before it hits completion, channel closes
//out uncooperatively.
//
//NOTE: Current design assumes if value%1000 == 200 as partially
//signed/revoked, this means that the code is reliant upon this as knowing when
//to force close out channels, etc.
const (
//HTLC Add
ADD_PRESTAGE = 1000
ADD_STAGED = 1100
ADD_SIGNING_AND_REVOKING = 1200
ADD_COMPLETE = 1300 //Most HTLCs should be this
ADD_REJECTED = 1999 //Staging request rejected
//HTLC Timeout
TIMEOUT_PRESTAGE = 2000
TIMEOUT_STAGED = 2100
TIMEOUT_SIGNING_AND_REVOKING = 2200
TIMEOUT_COMPLETE = 2300
//HTLC Settle
SETTLE_PRESTAGE = 3000
SETTLE_STAGED = 3100
SETTLE_SIGNING_AND_REVOKING = 3200
SETTLE_COMPLETE = 3300
//TODO: Commitment states
)
//Example, Add Flow:
//1. Create the HTLC
//2. (you+they) Sign the commit
//3. (you+they) Send revocation when you get a new commit
//4. Update the state to account for revocation
//5. Both sides committed and revoked prior states, mark state as finished
type LNChannel struct {
fundingTxIn *wire.TxIn
channelDB *channeldb.DB
channelID uint64
//The person who set up the channel is even, the person who responded
//is odd. All HTLCKeys use even/odd numbering.
//NOTE: should we change this to be a int64 and use positive/negative?
isEven bool
sync.RWMutex
//Should update with new Commitments when fees are renegotiated
feePerKb btcutil.Amount
//HTLCs
//Even/odd numbering in effect
//Will just take the next number
HTLCs map[lnwire.HTLCKey]*PaymentDescriptor
//Last key in the map for incrementing
//If the other party wants to use some stupid high number, then
//they're basically wanting the channel to close out early...
ourLastKey lnwire.HTLCKey
theirLastKey lnwire.HTLCKey
//ourCommit/theirCommit is the most recent *signed* Commitment
ourCommit *wire.MsgTx
theirCommit *wire.MsgTx
ourCommitHeight lnwire.CommitHeight
ourUnrevokedCommitments []lnwire.CommitHeight
theirCommitHeight lnwire.CommitHeight
theirUnrevokedCommitments []lnwire.CommitHeight
//UnrevoedStates indexed by CommitHeight, slice of HTLCKeys to update
//as revoked/complete
//NOTE: I think it's EASIER to reconstruct the Commitment than
//to update because finding indicies and blah blah blah,
//validation/verification screw that, the state is in the HTLCs
//and the sig. We can optimize later.
//ShaChain Height
//Difference between ourCommitHeight and ourShaChain index is the
//Commitments not yet revoked. If the difference is 1, then all prior
//Commitments are revoked.
ourShaChain *shachain.HyperShaChain
theirShaChain *shachain.HyperShaChain
}
type PaymentDescriptor struct {
RHashes []*[20]byte
Timeout uint32
CreditsAmount lnwire.CreditsAmount
Revocation []*[20]byte
Blob []byte //next hop data
PayToUs bool
State uint32 //Current state
Index uint32 //Position in txout
p2sh [20]byte //cached p2sh script to use
//Used during the SIGNING_AND_REVOKING process
weSigned bool
theyRevoked bool
theySignedAndWeRevoked bool
//This is the height which is revoked, anything before that
//is assumed to still be valid.
//This data point is useful for ...UnrevokedCommitments
//if there's unrevoked commitments below these values,
//then the state is not locked in
//Used for add/settle/timeout
ourRevokedHeight lnwire.CommitHeight
theirRevokedHeight lnwire.CommitHeight
}
//addHTLC will take in a PaymentDescriptor, adds to HTLCs and writes to disk
//Assumes the data has already been validated!
//NOTE: **MUST** HAVE THE MUTEX LOCKED ALREADY WHEN CALLED
func (l *LNChannel) addHTLC(h *PaymentDescriptor) (lnwire.HTLCKey, error) {
//Sanity check
if h.State != ADD_PRESTAGE {
return 0, fmt.Errorf("addHTLC can only add PRESTAGE")
}
//Make sure we're not overfull
//We should *never* hit this...
//we subtract 3 in case we need to add one to correct for even/odd
if l.ourLastKey >= ^lnwire.HTLCKey(0)-3 {
return 0, fmt.Errorf("Channel full!!!")
}
//Assign a new value
//We add 2 due to even/odd assignments
l.ourLastKey += 2
//Check whether the even/odd is invalid..
//If it is, we iterate to the next one
if l.ourLastKey%1 == 1 {
if l.isEven {
l.ourLastKey += 1
}
} else {
if !l.isEven {
l.ourLastKey += 1
}
}
//Write the new HTLC
l.HTLCs[l.ourLastKey] = h
disk() //save l.ourLastKey and the htlcs
return l.ourLastKey, nil
}
func (l *LNChannel) CreateHTLC(h *PaymentDescriptor) error {
l.Lock()
var err error
//if h.State == ADD_PRESTAGE {
// //We already have it created, but let's re-send!
// //Send a payment request LNWire
//}
if h.State > ADD_PRESTAGE {
l.Unlock()
return fmt.Errorf("HTLC is already created!")
}
if !h.PayToUs { //We created the payment
//Validate the data
err = l.validateHTLC(h, false)
if err != nil {
l.Unlock()
return err
}
//Update state as pre-commit
h.State = ADD_PRESTAGE
if _, err := l.addHTLC(h); err != nil {
return err
}
//Send a ADDHTLC LNWire
l.Unlock()
// net() //TODO
} else {
//Future version may be able to do this..
l.Unlock()
return fmt.Errorf("Cannot pull money")
}
return nil
}
//Receives an HTLCAddRequest message
//Adds to the HTLC staging list
func (l *LNChannel) recvHTLCAddRequest(p *lnwire.HTLCAddRequest) error {
l.Lock()
var err error
//Check if we already have a PaymentDescriptor
if l.HTLCs[p.HTLCKey] != nil {
//Don't do anything because we already have one
l.Unlock()
return fmt.Errorf("Counterparty attempted to re-send AddRequest")
}
//Make sure they aren't reusing
if p.HTLCKey <= l.theirLastKey {
//They're reusing.... uhoh
l.Unlock()
l.sendAddReject(p.HTLCKey) //This may be a dupe!
return fmt.Errorf("Counterparty cannot re-use HTLCKeys")
}
//Update newest id
l.theirLastKey = p.HTLCKey
//Create a new HTLCKey entry
htlc := new(PaymentDescriptor)
//Populate the entries
htlc.RHashes = p.RedemptionHashes
htlc.Timeout = p.Expiry
htlc.CreditsAmount = p.Amount
htlc.Blob = p.Blob
htlc.State = ADD_STAGED //mark as staged by both parties
htlc.PayToUs = true //assume this is paid to us, may change in the future
//Validate the HTLC
err = l.validateHTLC(htlc, true)
if err != nil {
//Update state just in case (not used but y'know..)
htlc.State = ADD_REJECTED
//currently not yet added to staging
//so we don't need to worry about the above htlc
//we also don't add to disk
//However, we do need to send a AddReject packet
l.Unlock()
l.sendAddReject(p.HTLCKey)
return err
}
//Validation passed, so we continue
if _, err := l.addHTLC(htlc); err != nil {
return err
}
//Send add accept packet, and we're done
l.Unlock()
l.sendAddAccept(p.HTLCKey)
return nil
}
func (l *LNChannel) sendAddReject(htlckey lnwire.HTLCKey) error {
l.Lock()
defer l.Unlock()
msg := new(lnwire.HTLCAddReject)
msg.ChannelID = l.channelID
msg.HTLCKey = htlckey
net(msg)
return nil
}
func (l *LNChannel) recvAddReject(htlckey lnwire.HTLCKey) error {
l.Lock()
defer l.Unlock()
htlc := l.HTLCs[htlckey]
if htlc == nil {
return fmt.Errorf("Counterparty rejected non-existent HTLC")
}
if htlc.State != ADD_PRESTAGE {
return fmt.Errorf("Counterparty atttempted to reject invalid state")
}
htlc.State = ADD_REJECTED
disk()
return nil
}
//Notifies the other party that it is now staged on our end
func (l *LNChannel) sendAddAccept(htlckey lnwire.HTLCKey) error {
htlc := l.HTLCs[htlckey]
msg := new(lnwire.HTLCAddAccept)
msg.ChannelID = l.channelID
msg.HTLCKey = htlckey
htlc.State = ADD_STAGED
disk()
net(msg)
return nil
}
//The other party has accepted the staging request, so we are staging now
func (l *LNChannel) recvAddAccept(p *lnwire.HTLCAddAccept) error {
htlc := l.HTLCs[p.HTLCKey]
//Make sure it's in the list
if htlc == nil {
return fmt.Errorf("Counterparty accepted non-existent HTLC")
}
//Update pre-stage to staged
//Everything else it won't do anything
if htlc.State == ADD_PRESTAGE {
//Update to staged
htlc.State = ADD_STAGED
disk()
}
return nil
}
func (l *LNChannel) timeoutHTLC(htlcKey lnwire.HTLCKey) error {
return nil
}
func (l *LNChannel) settleHTLC(htlcKey lnwire.HTLCKey) error {
return nil
}
//receive AddAcceptHTLC: Find the HTLC and call createHTLC
func (l *LNChannel) addAccept(h *PaymentDescriptor) error {
if h.State == ADD_PRESTAGE {
//Mark stage as accepted
h.State = ADD_SIGNING_AND_REVOKING
//Write to disk
disk()
}
return nil
}
//Timeout, Settle
//Create a commitment
// Update the markings on the HTLCs
func (l *LNChannel) createCommitment() error {
//Take all staging marked as SIGNING_AND_REVOKING *and* we have not signed
// Mark each as weSigned
return nil
}
//They revoke prior
//After we send a Commitment, they should send a revocation
// Update shaChain and HTLCs
func (l *LNChannel) receiveRevocation() error {
//Revoke the prior Commitment
//Update HTLCs with theyRevoked
//Check each HTLC for being complete and mark as complete if so
return nil
}
//Receive a commitment & send out a revocation
// Update the markings on the HTLCs
func (l *LNChannel) receiveCommitment() error {
//Validate new Commitment (sigs, even/odd for commitment height, incremental commitment height, etc)
// Reconstruct Commitment
//Update with new Commitment
//Send revocation
//Mark as theySignedAndWeRevoked
//Check each HTLC for being complete and mark as complete if so
return nil
}
//Mark the HTLC as revoked if it is fully signed and revoked by both parties
func (l *LNChannel) addCompleteHTLC(h *PaymentDescriptor) error {
//Check/validate values
//Mark as ADD_COMPLETE
return nil
}
//Validate whether we want to add the HTLC
func (l *LNChannel) validateHTLC(h *PaymentDescriptor, toUs bool) error {
//Make sure we have available spots; not too many outputs
//Make sure there is available funds
//Make sure there is sufficient reserve for fees
//Make sure the money is going in the right direction
return nil
}