package lnstate import ( "fmt" //"github.com/btcsuite/btcd/btcec" //"atomic" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" "li.lan/labs/plasma/channeldb" "li.lan/labs/plasma/lnwire" "li.lan/labs/plasma/shachain" "sync" ) //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 l.addHTLC(h) //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.CreditsAmount 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() sendAddReject(p.HTLCKey) return err } //Validation passed, so we continue addHTLC(h) //Send add accept packet, and we're done l.Unlock() sendAddAccept(p.HTLCKey) } func (l *LNChannel) sendAddReject(htlckey lnwire.HTLCKey) error { l.Lock() defer l.Unlock() msg = new(lnwire.HTLCAddReject) msg.ChannelID = l.channelID msg.HTLCKey = h.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 { h = l.HTLCs[htlckey] msg = new(lnwire.HTLCAddAccept) msg.ChannelID = l.channelID msg.HTLCKey = h.HTLCKey disk() net(msg) return nil } //The other party has accepted the staging request, so we are staging now func (l *LNChannel) recvAddAccept(p *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() } } func (l *LNChannel) timeoutHTLC(htlcKey lnwire.HTLCKey) error { } func (l *LNChannel) settleHTLC(htlcKey lnwire.HTLCKey) error { } //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() } } //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 } //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 } //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 } //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 } //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 }