diff --git a/lnstate/README.md b/lnstate/README.md new file mode 100644 index 00000000..762ba15c --- /dev/null +++ b/lnstate/README.md @@ -0,0 +1,5 @@ +I'm using this as a temporary repo for the state machine. + +All hooks into real stuff isn't used right now. + +Will merge into lnwallet/channel.go later? diff --git a/lnstate/lnstate.go b/lnstate/lnstate.go new file mode 100644 index 00000000..4ec3fe79 --- /dev/null +++ b/lnstate/lnstate.go @@ -0,0 +1,418 @@ +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 +//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 for 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). 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... +// +//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 { + 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() + 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 +} diff --git a/lnwire/README.md b/lnwire/README.md index 6c2f27cb..38fe0ffd 100644 --- a/lnwire/README.md +++ b/lnwire/README.md @@ -56,3 +56,184 @@ Close Complete Returns the Txid and sig as a courtesy. The counterparty might not send this if they're being non-cooperative. +Commitments and HTLCs +===================== + +This is designed to be non-blocking where there can be multiple Commitments per +person and the Commitments do not need to match. A HTLC is only believed to be +added when it's in both parties' most recent Commitment (same with +timeout/settle). + +As a result, there can easily be hundreds of state updates/payments per second +per channel. + +Commitment States +----------------- + +Commitments: +1. HTLCs, can be modified. Any add/settlement/timeout/etc. gets added to + staging. +2. Signed, only one signed state at a time can exist per party. Takes HTLCs + staging and locks it in, can now be broadcast on-chain by the counterparty. +3. Completed and Revoked, other party sends their revocation accepting this + Commitment. Sending a revocation means you *ACCEPT* the Commitment. There + should never be a case where a Commitment Signature happens and the client + refusees to revoke -- instead the client should immediately close out the + channel. +4. Deprecated, a commitment is old, marked as deprecated when there is a new + Commitment and this one is revoked. These commitments never be broadcasted. +5. Invalid, close out channel immediately. + +There can be multiple commitments going at a time per party (currently limits a +total of 16 possible in-flight that can be broadcast for sanity, but there's no +real limit). + +For validity, all you do is ensure that the changes from the old commitment are +legit (based on your HTLC/staging data) +COMMIT\_STAGING +COMMIT\_SIGNED +COMMIT\_COMPLETE + +Messages: +CommitSignature: Signature to establish COMMIT\_SIGNED state +CommitRevocation: Revoke prior states + +ADD HTLCs +--------- + +Requester Add HTLC states (Adding HTLCs): +1. Pre-staged, don't know if the other person wants it +2. Staged, both parties agree to add this HTLC. If a staging request packet is + received, then BOTH PARTIES will have it in their next Commitment. Nothing + is guaranteed here, but violations are treated as immediate channel closure. +3. Signed and sent the Commitment Tx to the counterparty, one should now assume + that there's a possibility that this HTLC will be boradcast on-chain. +4. Completed and Revoked, *counterparty* has included this in the Commitment + they're broadcasting and revoked their prior state. This means the + *Requeseter* can continue to take action, since the Commitment they have, + the HTLC doesn't exist (no payment), and the *Responder* will broadcast with + the payment to the *Responder*. However, the *Responder* cannot treat the + HTLC as cleared. +5. Cleared. Both parties have signed and revoked. Responder can continue + routing. Make sure it's included in *BOTH COMMITMENTS and ALL PREVIOUS + REVOKED* +6. Staging Reject, removal request, tx rejected, begin flow to reject HTLC from + other channels, can only be sent during the *pre-staging* state + +In the event that an HTLC stays in "Completed and Revoked" and it is timed out, +and the counterparty refuses to add it into a new Commitment, the channel is +*closed out on-chain*. In other words, when checking which ones to send a +settle/timeout notification, do it for anything which is +ADD\_SIGNING\_AND\_REVOKING, *or* ADD\_COMPLETE (AND ALL OTHER PRE-COMPLETE +STAGES, e.g. in timeout or settlement). + +As part of moving to any further stage, check if it's timed out. + +If there is a request to stage and it's already staged, treat it as accepting. + +When it has cleared and timed out, a timeout notification is sent. + +HTLC ID numbers are uint64 and each counterparty is responsible to only make +sequential/incremental, and each party can only make evens/odds (odd channel +creation responder, evens channel creation initiator) + +State is for *YOUR* signatures (what kind of action you need to do in the future) +ADD\_PRESTAGE +ADD\_STAGED +ADD\_SIGNING\_AND\_REVOKING +ADD\_COMPLETE +ADD\_REJECTED + +Messages: +HTLCAddRequest: Request to add to staging +HTLCAddAccept: Add to staging (both parties have added when recv) +HTLCAddReject: Deny add to staging (both parties don't have in staging) + +HTLC Settle (payment success) +----------------------------- + +Requester Settle HTLC states (Fulfill HTLCs): +1. Pre-staged, don't know if the other person will agree to settle +2. Staged, both parties agree to settle this HTLC +3. Signed and sent Commitment Tx to the counterparty, there is now the + possibility that the HTLC does not exist on-chain (of course, the Commitment + includes the payment so there's no real loss of funds). In the event that it + does not complete past this step, then one *must* close out on-chain as if + it was never staged/signed in the first place and the counterparty went + offline. +4. Both parties have signed and revoked, the settlement is complete (there is + no intermediate step of Revoked because this is only reliable and actionable + if BOTH PARTIES have updated their settlement state). + +This has one less state because when adding, you're encumbering yourself. With +removing, both parties are potentially encumbered, so they cannot take action +until it's fully settled. + +State is for *your* signatures +SETTLE\_PRESTAGE +SETTLE\_STAGED +SETTLE\_SIGNING\_AND\_REVOKING +SETTLE\_COMPLETE + +Message: +HTLCSettleRequest: Request to add to staging the removal from Commitment. +HTLCSettleAccept: Add to staging the removal from Commitment. +(There is no HTLCSettleReject as the counterparty should immediately close out +or at worst ignore if it's getting garbage requests) + +Timeout (falure/refund) +----------------------- + +Requester Timeout HTLC States: +1. Pre-staged +2. Staged, both parties agree to time out the HTLC and refund the money +3. Signe dnad sent commitment to the counterparty, there is now the possibility + that the transaction will no longer exist on-chain (of course, they can be + redeemed either way). In the even that it does not complete past this step, + then one *must* close out on-chain as if it was never staged/signed in the + first place adn the counterparty was offline. +4. Both parties have signed and revoked, the settlement is complete (there is no + intermediate step of Revoked because there is only reliable and actionable if + BOTH PARTIES have updated their settlement state). + +Similar to HTLC Settlement, there is one less state. + +State is for *your* signatures +TIMEOUT\_PRESTAGE +TIMEOUT\_STAGED +TIMEOUT\_SIGNING\_AND\_REVOKING +TIMEOUT\_COMPLETE + + +Example +------- + +Adding a single HTLC process: +1. Requester flags as pre-staged, and sends an "add requeset" +2. Responder decides whether to add. If they don't, they invalidate it. If they + do, they send a message accepting the staging request. It is now marked as + staged on both sides and is ready to be accepted into a Commitment. +3. When a party wants to update with a new Commitment, they send a new signed + Commitment, this includes data that the HTLC is part of it. Let's say it's + the Requester that sends this new Commitment. As a result, the HTLC is + marked *BY THE RESPONDER* as Signed. It's only when the Responder includes a + transaction including the new HTLC in a new Commitment that the Requester + marks it as Signed. +4. Upon the Responder receiving the new Commitment, they send the revocation + for the old Commitment, and commit to broadcasting only the new one. +5. The *Requester* marks the HTLC as complete, but the *Responder* waits until + they receive a Commitment (and the old one is revoked) before marking it as + complete on the Responder's end. +6. When both parties have the new Commitments and the old ones are revoked, + then the HTLC is marked as complete + +The two Commitment Transactions may not be completely in sync, but that's OK! +What's added in both (and removed in both) are regarded as valid and locked-in. +If it's only added to one, then it's regarded as in-transit and can go either +way. + +The behavior is to sign after all additions/removals/cancellations, but there +may be multiple in the staging buffer. + +Each party has their own revocation height (*for the other party to use*), and +they may be different. diff --git a/lnwire/commit_signature.go b/lnwire/commit_signature.go index a43ef4bf..70c090c0 100644 --- a/lnwire/commit_signature.go +++ b/lnwire/commit_signature.go @@ -21,12 +21,8 @@ type CommitSignature struct { //each part of the Commitment. CommitmentHeight uint64 - //The last staging included by both parties - //Basically, the state is updated to this point on both parties' - //staging - //Staging inclusion is in order. - CommitterLastStaging uint64 - ReceiverLastStaging uint64 + //List of HTLC Keys which are updated from all parties + UpdatedHTLCKeys []uint64 //Hash of the revocation to use RevocationHash [20]byte @@ -41,16 +37,14 @@ type CommitSignature struct { func (c *CommitSignature) Decode(r io.Reader, pver uint32) error { //ChannelID(8) //CommitmentHeight(8) - //CommiterLastStaging(8) - //ReceiverLastStaging(8) + //c.UpdatedHTLCKeys(8*1000max) //RevocationHash(20) //Fee(8) //RequesterCommitSig(73max+2) err := readElements(r, &c.ChannelID, &c.CommitmentHeight, - &c.CommitterLastStaging, - &c.ReceiverLastStaging, + &c.UpdatedHTLCKeys, &c.RevocationHash, &c.Fee, &c.CommitSig, @@ -73,8 +67,7 @@ func (c *CommitSignature) Encode(w io.Writer, pver uint32) error { err := writeElements(w, c.ChannelID, c.CommitmentHeight, - c.CommitterLastStaging, - c.ReceiverLastStaging, + c.UpdatedHTLCKeys, c.RevocationHash, c.Fee, c.CommitSig, @@ -91,7 +84,7 @@ func (c *CommitSignature) Command() uint32 { } func (c *CommitSignature) MaxPayloadLength(uint32) uint32 { - return 135 + return 8192 } //Makes sure the struct data is valid (e.g. no negatives or invalid pkscripts) @@ -108,21 +101,23 @@ func (c *CommitSignature) Validate() error { func (c *CommitSignature) String() string { //c.ChannelID, //c.CommitmentHeight, - //c.CommitterLastStaging, - //c.ReceiverLastStaging, //c.RevocationHash, + //c.UpdatedHTLCKeys, //c.Fee, //c.CommitSig, var serializedSig []byte if &c.CommitSig != nil && c.CommitSig.R != nil { serializedSig = c.CommitSig.Serialize() } + var items string + for i := 0; i < len(c.UpdatedHTLCKeys); i++ { + items += fmt.Sprintf("%d ", c.UpdatedHTLCKeys[i]) + } return fmt.Sprintf("\n--- Begin CommitSignature ---\n") + fmt.Sprintf("ChannelID:\t\t%d\n", c.ChannelID) + fmt.Sprintf("CommitmentHeight:\t%d\n", c.CommitmentHeight) + - fmt.Sprintf("CommitterLastStaging:\t%d\n", c.CommitterLastStaging) + - fmt.Sprintf("ReceiverLastStaging:\t%d\n", c.ReceiverLastStaging) + + fmt.Sprintf("UpdatedHTLCKeys:\t%s\n", items) + fmt.Sprintf("RevocationHash:\t\t%x\n", c.RevocationHash) + fmt.Sprintf("Fee:\t\t\t%s\n", c.Fee.String()) + fmt.Sprintf("CommitSig:\t\t%x\n", serializedSig) + diff --git a/lnwire/commit_signature_test.go b/lnwire/commit_signature_test.go index f6d953c0..e04d8207 100644 --- a/lnwire/commit_signature_test.go +++ b/lnwire/commit_signature_test.go @@ -10,16 +10,16 @@ var ( _ = copy(revocationHash[:], revocationHashBytes) commitSignature = &CommitSignature{ - ChannelID: uint64(12345678), - CommitmentHeight: uint64(12345), - CommitterLastStaging: uint64(12345678), - ReceiverLastStaging: uint64(87654321), - RevocationHash: revocationHash, - Fee: btcutil.Amount(10000), - CommitSig: commitSig, + ChannelID: uint64(12345678), + CommitmentHeight: uint64(12345), + //CommitterLastStaging: uint64(12345678), + UpdatedHTLCKeys: []uint64{1, 2, 3, 4, 5}, + RevocationHash: revocationHash, + Fee: btcutil.Amount(10000), + CommitSig: commitSig, } - commitSignatureSerializedString = "0000000000bc614e00000000000030390000000000bc614e0000000005397fb14132b6b48371f7b022a16eacb9b2b0ebee134d4100000000000027104630440220333835e58e958f5e92b4ff4e6fa2470dac88094c97506b4d6d1f4e23e52cb481022057483ac18d6b9c9c14f0c626694c9ccf8b27b3dbbedfdf6b6c9a9fa9f427a1df" - commitSignatureSerializedMessage = "0709110b000007d0000000830000000000bc614e00000000000030390000000000bc614e0000000005397fb14132b6b48371f7b022a16eacb9b2b0ebee134d4100000000000027104630440220333835e58e958f5e92b4ff4e6fa2470dac88094c97506b4d6d1f4e23e52cb481022057483ac18d6b9c9c14f0c626694c9ccf8b27b3dbbedfdf6b6c9a9fa9f427a1df" + commitSignatureSerializedString = "0000000000bc614e00000000000030390005000000000000000100000000000000020000000000000003000000000000000400000000000000054132b6b48371f7b022a16eacb9b2b0ebee134d4100000000000027104630440220333835e58e958f5e92b4ff4e6fa2470dac88094c97506b4d6d1f4e23e52cb481022057483ac18d6b9c9c14f0c626694c9ccf8b27b3dbbedfdf6b6c9a9fa9f427a1df" + commitSignatureSerializedMessage = "0709110b000007d00000009d0000000000bc614e00000000000030390005000000000000000100000000000000020000000000000003000000000000000400000000000000054132b6b48371f7b022a16eacb9b2b0ebee134d4100000000000027104630440220333835e58e958f5e92b4ff4e6fa2470dac88094c97506b4d6d1f4e23e52cb481022057483ac18d6b9c9c14f0c626694c9ccf8b27b3dbbedfdf6b6c9a9fa9f427a1df" ) func TestCommitSignatureEncodeDecode(t *testing.T) { diff --git a/lnwire/htlc_addaccept.go b/lnwire/htlc_addaccept.go index e40a4372..22b9482f 100644 --- a/lnwire/htlc_addaccept.go +++ b/lnwire/htlc_addaccept.go @@ -7,18 +7,15 @@ import ( type HTLCAddAccept struct { ChannelID uint64 - StagingID uint64 + HTLCKey HTLCKey } func (c *HTLCAddAccept) Decode(r io.Reader, pver uint32) error { //ChannelID(8) - //CommitmentHeight(8) - //NextResponderCommitmentRevocationHash(20) - //ResponderRevocationPreimage(20) - //ResponderCommitSig(2+73max) + //HTLCKey(8) err := readElements(r, &c.ChannelID, - &c.StagingID, + &c.HTLCKey, ) if err != nil { return err @@ -37,7 +34,7 @@ func NewHTLCAddAccept() *HTLCAddAccept { func (c *HTLCAddAccept) Encode(w io.Writer, pver uint32) error { err := writeElements(w, c.ChannelID, - c.StagingID, + c.HTLCKey, ) if err != nil { @@ -65,6 +62,6 @@ func (c *HTLCAddAccept) Validate() error { func (c *HTLCAddAccept) String() string { return fmt.Sprintf("\n--- Begin HTLCAddAccept ---\n") + fmt.Sprintf("ChannelID:\t\t%d\n", c.ChannelID) + - fmt.Sprintf("StagingID:\t\t%d\n", c.StagingID) + + fmt.Sprintf("HTLCKey:\t\t%d\n", c.HTLCKey) + fmt.Sprintf("--- End HTLCAddAccept ---\n") } diff --git a/lnwire/htlc_addaccept_test.go b/lnwire/htlc_addaccept_test.go index 6428440d..c38869fa 100644 --- a/lnwire/htlc_addaccept_test.go +++ b/lnwire/htlc_addaccept_test.go @@ -7,7 +7,7 @@ import ( var ( htlcAddAccept = &HTLCAddAccept{ ChannelID: uint64(12345678), - StagingID: uint64(12345), + HTLCKey: HTLCKey(12345), } htlcAddAcceptSerializedString = "0000000000bc614e0000000000003039" htlcAddAcceptSerializedMessage = "0709110b000003f2000000100000000000bc614e0000000000003039" diff --git a/lnwire/htlc_addreject.go b/lnwire/htlc_addreject.go index 1cc70c38..c1c9ab6c 100644 --- a/lnwire/htlc_addreject.go +++ b/lnwire/htlc_addreject.go @@ -7,7 +7,7 @@ import ( type HTLCAddReject struct { ChannelID uint64 - StagingID uint64 + HTLCKey HTLCKey } func (c *HTLCAddReject) Decode(r io.Reader, pver uint32) error { @@ -18,7 +18,7 @@ func (c *HTLCAddReject) Decode(r io.Reader, pver uint32) error { //ResponderCommitSig(2+73max) err := readElements(r, &c.ChannelID, - &c.StagingID, + &c.HTLCKey, ) if err != nil { return err @@ -37,7 +37,7 @@ func NewHTLCAddReject() *HTLCAddReject { func (c *HTLCAddReject) Encode(w io.Writer, pver uint32) error { err := writeElements(w, c.ChannelID, - c.StagingID, + c.HTLCKey, ) if err != nil { @@ -65,6 +65,6 @@ func (c *HTLCAddReject) Validate() error { func (c *HTLCAddReject) String() string { return fmt.Sprintf("\n--- Begin HTLCAddReject ---\n") + fmt.Sprintf("ChannelID:\t\t%d\n", c.ChannelID) + - fmt.Sprintf("StagingID:\t\t%d\n", c.StagingID) + + fmt.Sprintf("HTLCKey:\t\t%d\n", c.HTLCKey) + fmt.Sprintf("--- End HTLCAddReject ---\n") } diff --git a/lnwire/htlc_addreject_test.go b/lnwire/htlc_addreject_test.go index 7a587e1e..4dfde23e 100644 --- a/lnwire/htlc_addreject_test.go +++ b/lnwire/htlc_addreject_test.go @@ -7,7 +7,7 @@ import ( var ( htlcAddReject = &HTLCAddReject{ ChannelID: uint64(12345678), - StagingID: uint64(12345), + HTLCKey: HTLCKey(12345), } htlcAddRejectSerializedString = "0000000000bc614e0000000000003039" htlcAddRejectSerializedMessage = "0709110b000003fc000000100000000000bc614e0000000000003039" diff --git a/lnwire/htlc_addrequest.go b/lnwire/htlc_addrequest.go index 8c8b5329..d36c2d71 100644 --- a/lnwire/htlc_addrequest.go +++ b/lnwire/htlc_addrequest.go @@ -12,7 +12,7 @@ type HTLCAddRequest struct { ChannelID uint64 //ID of this request - StagingID uint64 + HTLCKey HTLCKey //When the HTLC expires Expiry uint32 @@ -21,8 +21,9 @@ type HTLCAddRequest struct { //Difference between hop and first item in blob is the fee to complete Amount CreditsAmount - //Hash160 address of the next hop. - NextHop [20]byte + //RefundContext is for payment cancellation + //TODO (j): not currently in use, add later + RefundContext HTLCKey //Contract Type //first 4 bits is n, second for is m, in n-of-m "multisig" @@ -38,19 +39,17 @@ type HTLCAddRequest struct { func (c *HTLCAddRequest) Decode(r io.Reader, pver uint32) error { //ChannelID(8) - //StagingID(8) + //HTLCKey(8) //Expiry(4) //Amount(4) - //NextHop(20) //ContractType(1) //RedemptionHashes (numOfHashes * 20 + numOfHashes) //Blob(2+blobsize) err := readElements(r, &c.ChannelID, - &c.StagingID, + &c.HTLCKey, &c.Expiry, &c.Amount, - &c.NextHop, &c.ContractType, &c.RedemptionHashes, &c.Blob, @@ -72,10 +71,9 @@ func NewHTLCAddRequest() *HTLCAddRequest { func (c *HTLCAddRequest) Encode(w io.Writer, pver uint32) error { err := writeElements(w, c.ChannelID, - c.StagingID, + c.HTLCKey, c.Expiry, c.Amount, - c.NextHop, c.ContractType, c.RedemptionHashes, c.Blob, @@ -117,10 +115,9 @@ func (c *HTLCAddRequest) String() string { return fmt.Sprintf("\n--- Begin HTLCAddRequest ---\n") + fmt.Sprintf("ChannelID:\t%d\n", c.ChannelID) + - fmt.Sprintf("StagingID:\t%d\n", c.StagingID) + + fmt.Sprintf("HTLCKey:\t%d\n", c.HTLCKey) + fmt.Sprintf("Expiry:\t\t%d\n", c.Expiry) + fmt.Sprintf("Amount\t\t%d\n", c.Amount) + - fmt.Sprintf("NextHop\t\t%x\n", c.NextHop) + fmt.Sprintf("ContractType:\t%d (%b)\n", c.ContractType, c.ContractType) + fmt.Sprintf("RedemptionHashes:") + redemptionHashes + diff --git a/lnwire/htlc_addrequest_test.go b/lnwire/htlc_addrequest_test.go index 047b0e92..e38c17a2 100644 --- a/lnwire/htlc_addrequest_test.go +++ b/lnwire/htlc_addrequest_test.go @@ -8,23 +8,21 @@ var ( //Need to to do this here _ = copy(revocationHash[:], revocationHashBytes) _ = copy(redemptionHash[:], redemptionHashBytes) - _ = copy(nextHop[:], nextHopBytes) emptyRedemptionHashes = []*[20]byte{} redemptionHashes = append(emptyRedemptionHashes, &redemptionHash) htlcAddRequest = &HTLCAddRequest{ ChannelID: uint64(12345678), - StagingID: uint64(12345), + HTLCKey: HTLCKey(12345), Expiry: uint32(144), Amount: CreditsAmount(123456000), - NextHop: nextHop, ContractType: uint8(17), RedemptionHashes: redemptionHashes, Blob: []byte{255, 0, 255, 0, 255, 0, 255, 0}, } - htlcAddRequestSerializedString = "0000000000bc614e000000000000303900000090075bca0094a9ded5a30fc5944cb1e2cbcd980f30616a14401100015b315ebabb0d8c0d94281caa2dfee69a1a00436e0008ff00ff00ff00ff00" - htlcAddRequestSerializedMessage = "0709110b000003e80000004d0000000000bc614e000000000000303900000090075bca0094a9ded5a30fc5944cb1e2cbcd980f30616a14401100015b315ebabb0d8c0d94281caa2dfee69a1a00436e0008ff00ff00ff00ff00" + htlcAddRequestSerializedString = "0000000000bc614e000000000000303900000090075bca001100015b315ebabb0d8c0d94281caa2dfee69a1a00436e0008ff00ff00ff00ff00" + htlcAddRequestSerializedMessage = "0709110b000003e8000000390000000000bc614e000000000000303900000090075bca001100015b315ebabb0d8c0d94281caa2dfee69a1a00436e0008ff00ff00ff00ff00" ) func TestHTLCAddRequestEncodeDecode(t *testing.T) { diff --git a/lnwire/htlc_settleaccept.go b/lnwire/htlc_settleaccept.go index 3ec2aed5..e046a3c1 100644 --- a/lnwire/htlc_settleaccept.go +++ b/lnwire/htlc_settleaccept.go @@ -12,15 +12,15 @@ type HTLCSettleAccept struct { ChannelID uint64 //ID of this request - StagingID uint64 + HTLCKey HTLCKey } func (c *HTLCSettleAccept) Decode(r io.Reader, pver uint32) error { //ChannelID(8) - //StagingID(8) + //HTLCKey(8) err := readElements(r, &c.ChannelID, - &c.StagingID, + &c.HTLCKey, ) if err != nil { return err @@ -39,7 +39,7 @@ func NewHTLCSettleAccept() *HTLCSettleAccept { func (c *HTLCSettleAccept) Encode(w io.Writer, pver uint32) error { err := writeElements(w, c.ChannelID, - c.StagingID, + c.HTLCKey, ) if err != nil { return err @@ -66,6 +66,6 @@ func (c *HTLCSettleAccept) Validate() error { func (c *HTLCSettleAccept) String() string { return fmt.Sprintf("\n--- Begin HTLCSettleAccept ---\n") + fmt.Sprintf("ChannelID:\t%d\n", c.ChannelID) + - fmt.Sprintf("StagingID:\t%d\n", c.StagingID) + + fmt.Sprintf("HTLCKey:\t%d\n", c.HTLCKey) + fmt.Sprintf("--- End HTLCSettleAccept ---\n") } diff --git a/lnwire/htlc_settleaccept_test.go b/lnwire/htlc_settleaccept_test.go index 691c149d..94fef418 100644 --- a/lnwire/htlc_settleaccept_test.go +++ b/lnwire/htlc_settleaccept_test.go @@ -7,7 +7,7 @@ import ( var ( htlcSettleAccept = &HTLCSettleAccept{ ChannelID: uint64(12345678), - StagingID: uint64(12345), + HTLCKey: HTLCKey(12345), } htlcSettleAcceptSerializedString = "0000000000bc614e0000000000003039" htlcSettleAcceptSerializedMessage = "0709110b00000456000000100000000000bc614e0000000000003039" diff --git a/lnwire/htlc_settlerequest.go b/lnwire/htlc_settlerequest.go index c103c1b4..1b219331 100644 --- a/lnwire/htlc_settlerequest.go +++ b/lnwire/htlc_settlerequest.go @@ -12,7 +12,7 @@ type HTLCSettleRequest struct { ChannelID uint64 //ID of this request - StagingID uint64 + HTLCKey HTLCKey //Redemption Proofs (R-Values) RedemptionProofs []*[20]byte @@ -20,7 +20,7 @@ type HTLCSettleRequest struct { func (c *HTLCSettleRequest) Decode(r io.Reader, pver uint32) error { //ChannelID(8) - //StagingID(8) + //HTLCKey(8) //Expiry(4) //Amount(4) //NextHop(20) @@ -29,7 +29,7 @@ func (c *HTLCSettleRequest) Decode(r io.Reader, pver uint32) error { //Blob(2+blobsize) err := readElements(r, &c.ChannelID, - &c.StagingID, + &c.HTLCKey, &c.RedemptionProofs, ) if err != nil { @@ -49,7 +49,7 @@ func NewHTLCSettleRequest() *HTLCSettleRequest { func (c *HTLCSettleRequest) Encode(w io.Writer, pver uint32) error { err := writeElements(w, c.ChannelID, - c.StagingID, + c.HTLCKey, c.RedemptionProofs, ) if err != nil { @@ -83,7 +83,7 @@ func (c *HTLCSettleRequest) String() string { return fmt.Sprintf("\n--- Begin HTLCSettleRequest ---\n") + fmt.Sprintf("ChannelID:\t%d\n", c.ChannelID) + - fmt.Sprintf("StagingID:\t%d\n", c.StagingID) + + fmt.Sprintf("HTLCKey:\t%d\n", c.HTLCKey) + fmt.Sprintf("RedemptionHashes:") + redemptionProofs + fmt.Sprintf("--- End HTLCSettleRequest ---\n") diff --git a/lnwire/htlc_settlerequest_test.go b/lnwire/htlc_settlerequest_test.go index 6ac79ab6..6f07179b 100644 --- a/lnwire/htlc_settlerequest_test.go +++ b/lnwire/htlc_settlerequest_test.go @@ -12,7 +12,7 @@ var ( htlcSettleRequest = &HTLCSettleRequest{ ChannelID: uint64(12345678), - StagingID: uint64(12345), + HTLCKey: HTLCKey(12345), RedemptionProofs: redemptionProofs, } htlcSettleRequestSerializedString = "0000000000bc614e000000000000303900015b315ebabb0d8c0d94281caa2dfee69a1a00436e" diff --git a/lnwire/htlc_timeoutaccept.go b/lnwire/htlc_timeoutaccept.go index d7ab0f2d..d5437b47 100644 --- a/lnwire/htlc_timeoutaccept.go +++ b/lnwire/htlc_timeoutaccept.go @@ -12,15 +12,15 @@ type HTLCTimeoutAccept struct { ChannelID uint64 //ID of this request - StagingID uint64 + HTLCKey HTLCKey } func (c *HTLCTimeoutAccept) Decode(r io.Reader, pver uint32) error { //ChannelID(8) - //StagingID(8) + //HTLCKey(8) err := readElements(r, &c.ChannelID, - &c.StagingID, + &c.HTLCKey, ) if err != nil { return err @@ -39,7 +39,7 @@ func NewHTLCTimeoutAccept() *HTLCTimeoutAccept { func (c *HTLCTimeoutAccept) Encode(w io.Writer, pver uint32) error { err := writeElements(w, c.ChannelID, - c.StagingID, + c.HTLCKey, ) if err != nil { return err @@ -66,6 +66,6 @@ func (c *HTLCTimeoutAccept) Validate() error { func (c *HTLCTimeoutAccept) String() string { return fmt.Sprintf("\n--- Begin HTLCTimeoutAccept ---\n") + fmt.Sprintf("ChannelID:\t%d\n", c.ChannelID) + - fmt.Sprintf("StagingID:\t%d\n", c.StagingID) + + fmt.Sprintf("HTLCKey:\t%d\n", c.HTLCKey) + fmt.Sprintf("--- End HTLCTimeoutAccept ---\n") } diff --git a/lnwire/htlc_timeoutaccept_test.go b/lnwire/htlc_timeoutaccept_test.go index 8f189f3a..e806a65b 100644 --- a/lnwire/htlc_timeoutaccept_test.go +++ b/lnwire/htlc_timeoutaccept_test.go @@ -7,7 +7,7 @@ import ( var ( htlcTimeoutAccept = &HTLCTimeoutAccept{ ChannelID: uint64(12345678), - StagingID: uint64(12345), + HTLCKey: HTLCKey(12345), } htlcTimeoutAcceptSerializedString = "0000000000bc614e0000000000003039" htlcTimeoutAcceptSerializedMessage = "0709110b0000051e000000100000000000bc614e0000000000003039" diff --git a/lnwire/htlc_timeoutrequest.go b/lnwire/htlc_timeoutrequest.go index 1d950410..69cf2bb2 100644 --- a/lnwire/htlc_timeoutrequest.go +++ b/lnwire/htlc_timeoutrequest.go @@ -12,15 +12,15 @@ type HTLCTimeoutRequest struct { ChannelID uint64 //ID of this request - StagingID uint64 + HTLCKey HTLCKey } func (c *HTLCTimeoutRequest) Decode(r io.Reader, pver uint32) error { //ChannelID(8) - //StagingID(8) + //HTLCKey(8) err := readElements(r, &c.ChannelID, - &c.StagingID, + &c.HTLCKey, ) if err != nil { return err @@ -39,7 +39,7 @@ func NewHTLCTimeoutRequest() *HTLCTimeoutRequest { func (c *HTLCTimeoutRequest) Encode(w io.Writer, pver uint32) error { err := writeElements(w, c.ChannelID, - c.StagingID, + c.HTLCKey, ) if err != nil { return err @@ -66,6 +66,6 @@ func (c *HTLCTimeoutRequest) Validate() error { func (c *HTLCTimeoutRequest) String() string { return fmt.Sprintf("\n--- Begin HTLCTimeoutRequest ---\n") + fmt.Sprintf("ChannelID:\t%d\n", c.ChannelID) + - fmt.Sprintf("StagingID:\t%d\n", c.StagingID) + + fmt.Sprintf("HTLCKey:\t%d\n", c.HTLCKey) + fmt.Sprintf("--- End HTLCTimeoutRequest ---\n") } diff --git a/lnwire/htlc_timeoutrequest_test.go b/lnwire/htlc_timeoutrequest_test.go index fc61d5bb..5f1399cd 100644 --- a/lnwire/htlc_timeoutrequest_test.go +++ b/lnwire/htlc_timeoutrequest_test.go @@ -7,7 +7,7 @@ import ( var ( htlcTimeoutRequest = &HTLCTimeoutRequest{ ChannelID: uint64(12345678), - StagingID: uint64(12345), + HTLCKey: HTLCKey(12345), } htlcTimeoutRequestSerializedString = "0000000000bc614e0000000000003039" htlcTimeoutRequestSerializedMessage = "0709110b00000514000000100000000000bc614e0000000000003039" diff --git a/lnwire/lnwire.go b/lnwire/lnwire.go index ada7eee8..b0e4f710 100644 --- a/lnwire/lnwire.go +++ b/lnwire/lnwire.go @@ -16,6 +16,9 @@ var MAX_SLICE_LENGTH = 65535 //Actual pkScript, not redeemScript type PkScript []byte +type HTLCKey uint64 +type CommitHeight uint64 + //Subsatoshi amount (Micro-Satoshi, 1/1000th) //Should be a signed int to account for negative fees // @@ -76,6 +79,11 @@ func writeElement(w io.Writer, element interface{}) error { return err } return nil + case HTLCKey: + err = writeElement(w, uint64(e)) + if err != nil { + return err + } case btcutil.Amount: err = binary.Write(w, binary.BigEndian, int64(e)) if err != nil { @@ -94,6 +102,24 @@ func writeElement(w io.Writer, element interface{}) error { return err } return nil + case []uint64: + numItems := len(e) + if numItems > 65535 { + return fmt.Errorf("Too many []uint64s") + } + //Write the size + err = writeElement(w, uint16(numItems)) + if err != nil { + return err + } + //Write the data + for i := 0; i < numItems; i++ { + err = writeElement(w, e[i]) + if err != nil { + return err + } + } + return nil case []*btcec.Signature: numSigs := len(e) if numSigs > 127 { @@ -313,6 +339,14 @@ func readElement(r io.Reader, element interface{}) error { } *e = binary.BigEndian.Uint64(b[:]) return nil + case *HTLCKey: + var b [8]byte + _, err = io.ReadFull(r, b[:]) + if err != nil { + return err + } + *e = HTLCKey(binary.BigEndian.Uint64(b[:])) + return nil case *btcutil.Amount: var b [8]byte _, err = io.ReadFull(r, b[:]) @@ -341,6 +375,28 @@ func readElement(r io.Reader, element interface{}) error { } *e = &*x return nil + case *[]uint64: + var numItems uint16 + err = readElement(r, &numItems) + if err != nil { + return err + } + //if numItems > 65535 { + // return fmt.Errorf("Too many items in []uint64") + //} + + //Read the number of items + var items []uint64 + for i := uint16(0); i < numItems; i++ { + var item uint64 + err = readElement(r, &item) + if err != nil { + return err + } + items = append(items, item) + } + *e = *&items + return nil case *[]*btcec.Signature: var numSigs uint8 err = readElement(r, &numSigs)