lnwire: add new 32-byte persistent/pending channel ID's

This commit does to things: moves the prior ShortChannelID struct into
a new short_channel_id.go file, and also implements the new ChannelID’s
currently used within he specification.

These new ID’s are 32-bytes in length and used during initial channel
funding as well as during normal channel updates. During initial
channel funding, the ID is to be a random 32-byte string, while once
normal channel operation has began, the ID is to be (txid XOR index),
where index is the index of the funding outpoint.
This commit is contained in:
Olaoluwa Osuntokun 2017-04-16 15:19:39 -07:00
parent 1bb225a9bd
commit 8147151fbf
No known key found for this signature in database
GPG Key ID: 9CC5B105D03521A2
4 changed files with 200 additions and 59 deletions

@ -1,39 +1,85 @@
package lnwire
// ShortChannelID represent the set of data which is needed to retrieve all
// necessary data to validate the channel existence.
type ShortChannelID struct {
// BlockHeight is the height of the block where funding transaction
// located.
//
// NOTE: This field is limited to 3 bytes.
BlockHeight uint32
import (
"encoding/binary"
"encoding/hex"
"math"
// TxIndex is a position of funding transaction within a block.
//
// NOTE: This field is limited to 3 bytes.
TxIndex uint32
"github.com/roasbeef/btcd/chaincfg/chainhash"
"github.com/roasbeef/btcd/wire"
)
// TxPosition indicating transaction output which pays to the channel.
TxPosition uint16
const (
// MaxFundingTxOutputs is the maximum number of allowed outputs on a
// funding transaction within the protocol. This is due to the fact
// that we use 2-bytes to encode the index within the funding output
// during the funding workflow. Funding transaction with more outputs
// than this are considered invalid within the protocol.
MaxFundingTxOutputs = math.MaxUint16
)
// ChannelID is a series of 32-bytes that uniquely identifies all channels
// within the network. The ChannelID is computed using the outpoint of the
// funding transaction (the txid, and output index). Given a funding output the
// ChannelID can be calculated by XOR'ing the big-endian serialization of the
type ChannelID [32]byte
// String returns the string representation of the ChannelID. This is just the
// hex string encoding of the ChannelID itself.
func (c ChannelID) String() string {
return hex.EncodeToString(c[:])
}
// NewShortChanIDFromInt returns a new ShortChannelID which is the decoded
// version of the compact channel ID encoded within the uint64. The format of
// the compact channel ID is as follows: 3 bytes for the block height, 3 bytes
// for the transaction index, and 2 bytes for the output index.
func NewShortChanIDFromInt(chanID uint64) ShortChannelID {
return ShortChannelID{
BlockHeight: uint32(chanID >> 40),
TxIndex: uint32(chanID>>16) & 0xFFFFFF,
TxPosition: uint16(chanID),
// NewChanIDFromOutPoint converts a target OutPoint into a ChannelID that is
// usable within the network. In order to covert the OutPoint into a ChannelID,
// we XOR the lower 2-bytes of the txid within the OutPoint with the big-endian
// serialization of the Index of the OutPoint, truncated to 2-bytes.
func NewChanIDFromOutPoint(op *wire.OutPoint) ChannelID {
// First we'll copy the txid of the outpoint into our channel ID slice.
var cid ChannelID
copy(cid[:], op.Hash[:])
// With the txid copied over, we'll now XOR the lower 2-bytes of the
// partial channelID with big-endian serialization of output index.
xorTxid(cid, uint16(op.Index))
return cid
}
// xorTxid performs the transformation needed to transform an OutPoint into a
// ChannelID. To do this, we expect the cid parameter to contain the txid
// unaltered and the outputIndex to be the output index
func xorTxid(cid ChannelID, outputIndex uint16) {
var buf [32]byte
binary.BigEndian.PutUint16(buf[:30], outputIndex)
buf[30] = cid[30] ^ buf[30]
buf[31] = cid[31] ^ buf[31]
}
// GenPossibleOutPoints generates all the possible outputs given a channel ID.
// In order to generate these possible outpoints, we perform a brute-force
// search through the candidate output index space, performing a reverse
// mapping from channelID back to OutPoint.
func (c *ChannelID) GenPossibleOutPoints() [MaxFundingTxOutputs]wire.OutPoint {
var possiblePoints [MaxFundingTxOutputs]wire.OutPoint
for i := uint32(0); i < MaxFundingTxOutputs; i++ {
cidCopy := *c
xorTxid(cidCopy, uint16(i))
possiblePoints[i] = wire.OutPoint{
Hash: chainhash.Hash(cidCopy),
Index: i,
}
}
return possiblePoints
}
// ToUint64 converts the ShortChannelID into a compact format encoded within a
// uint64 (8 bytes).
func (c *ShortChannelID) ToUint64() uint64 {
// TODO(roasbeef): explicit error on overflow?
return ((uint64(c.BlockHeight) << 40) | (uint64(c.TxIndex) << 16) |
(uint64(c.TxPosition)))
// IsChanPoint returns true if the OutPoint passed corresponds to the target
// ChannelID.
func (c ChannelID) IsChanPoint(op *wire.OutPoint) bool {
candidateCid := NewChanIDFromOutPoint(op)
return candidateCid == c
}

@ -1,39 +1,56 @@
package lnwire
import (
"reflect"
"testing"
import "testing"
"github.com/davecgh/go-spew/spew"
)
// TestChannelIDOutPointConversion ensures that the IsChanPoint always
// recognizes its seed OutPoint for all possible values of an output index.
func TestChannelIDOutPointConversion(t *testing.T) {
testChanPoint := *outpoint1
func TestChannelIDEncoding(t *testing.T) {
var testCases = []ShortChannelID{
{
BlockHeight: (1 << 24) - 1,
TxIndex: (1 << 24) - 1,
TxPosition: (1 << 16) - 1,
},
{
BlockHeight: 2304934,
TxIndex: 2345,
TxPosition: 5,
},
{
BlockHeight: 9304934,
TxIndex: 2345,
TxPosition: 5233,
},
}
// For a given OutPoint, we'll run through all the possible output
// index values, mutating our test outpoint to match that output index.
for i := uint32(0); i < MaxFundingTxOutputs; i++ {
testChanPoint.Index = i
for _, testCase := range testCases {
chanInt := testCase.ToUint64()
// With the output index mutated, we'll convert it into a
// ChannelID.
cid := NewChanIDFromOutPoint(&testChanPoint)
newChanID := NewShortChanIDFromInt(chanInt)
if !reflect.DeepEqual(testCase, newChanID) {
t.Fatalf("chan ID's don't match: expected %v got %v",
spew.Sdump(testCase), spew.Sdump(newChanID))
// Once the channel point has been converted to a channelID, it
// should recognize its original outpoint.
if !cid.IsChanPoint(&testChanPoint) {
t.Fatalf("channelID not recognized as seed channel "+
"point: cid=%v, op=%v", cid, testChanPoint)
}
}
}
// TestGenPossibleOutPoints ensures taht the GenPossibleOutPoints generates a
// vali set of outpoints for a channelID. A set of outpoints is valid iff, the
// root outpoint (the outpoint that generated the ChannelID) is included in the
// returned set of outpoints.
func TestGenPossibleOutPoints(t *testing.T) {
// We'll first convert out test outpoint into a ChannelID.
testChanPoint := *outpoint1
chanID := NewChanIDFromOutPoint(&testChanPoint)
// With the chan ID created, we'll generate all possible root outpoints
// given this channel ID.
possibleOutPoints := chanID.GenPossibleOutPoints()
// If we run through the generated possible outpoints, the original
// root outpoint MUST be found in this generated set.
var opFound bool
for _, op := range possibleOutPoints {
if op == testChanPoint {
opFound = true
break
}
}
// If we weren't able to locate the original outpoint in the set of
// possible outpoints, then we'll fail the test.
if !opFound {
t.Fatalf("possible outpoints did not contain the root outpoint")
}
}

@ -0,0 +1,39 @@
package lnwire
// ShortChannelID represent the set of data which is needed to retrieve all
// necessary data to validate the channel existence.
type ShortChannelID struct {
// BlockHeight is the height of the block where funding transaction
// located.
//
// NOTE: This field is limited to 3 bytes.
BlockHeight uint32
// TxIndex is a position of funding transaction within a block.
//
// NOTE: This field is limited to 3 bytes.
TxIndex uint32
// TxPosition indicating transaction output which pays to the channel.
TxPosition uint16
}
// NewShortChanIDFromInt returns a new ShortChannelID which is the decoded
// version of the compact channel ID encoded within the uint64. The format of
// the compact channel ID is as follows: 3 bytes for the block height, 3 bytes
// for the transaction index, and 2 bytes for the output index.
func NewShortChanIDFromInt(chanID uint64) ShortChannelID {
return ShortChannelID{
BlockHeight: uint32(chanID >> 40),
TxIndex: uint32(chanID>>16) & 0xFFFFFF,
TxPosition: uint16(chanID),
}
}
// ToUint64 converts the ShortChannelID into a compact format encoded within a
// uint64 (8 bytes).
func (c *ShortChannelID) ToUint64() uint64 {
// TODO(roasbeef): explicit error on overflow?
return ((uint64(c.BlockHeight) << 40) | (uint64(c.TxIndex) << 16) |
(uint64(c.TxPosition)))
}

@ -0,0 +1,39 @@
package lnwire
import (
"reflect"
"testing"
"github.com/davecgh/go-spew/spew"
)
func TestShortChannelIDEncoding(t *testing.T) {
var testCases = []ShortChannelID{
{
BlockHeight: (1 << 24) - 1,
TxIndex: (1 << 24) - 1,
TxPosition: (1 << 16) - 1,
},
{
BlockHeight: 2304934,
TxIndex: 2345,
TxPosition: 5,
},
{
BlockHeight: 9304934,
TxIndex: 2345,
TxPosition: 5233,
},
}
for _, testCase := range testCases {
chanInt := testCase.ToUint64()
newChanID := NewShortChanIDFromInt(chanInt)
if !reflect.DeepEqual(testCase, newChanID) {
t.Fatalf("chan ID's don't match: expected %v got %v",
spew.Sdump(testCase), spew.Sdump(newChanID))
}
}
}