Message interface and stuff.

* Added Message interface (similar to btcd's)
* Moved Funding Request to its own file
* Refacored Funding Request Code (*MUCH* better)
* Various fixes
This commit is contained in:
Joseph Poon 2015-12-28 03:24:16 -08:00
parent f51a5a6458
commit 161b1b5e4c
5 changed files with 595 additions and 322 deletions

188
lnwire/funding_request.go Normal file
View File

@ -0,0 +1,188 @@
package lnwire
import (
"bytes"
"fmt"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"io"
)
type FundingRequest struct {
ChannelType uint8
FundingAmount btcutil.Amount
ReserveAmount btcutil.Amount
MinFeePerKb btcutil.Amount
//Should double-check the total funding later
MinTotalFundingAmount btcutil.Amount
//CLTV lock-time to use
LockTime uint32
//Who pays the fees
//0: (default) channel initiator
//1: split
//2: channel responder
FeePayer uint8
RevocationHash [20]byte
Pubkey *btcec.PublicKey
DeliveryPkScript PkScript //*MUST* be either P2PKH or P2SH
Inputs []*wire.TxIn
}
func (c *FundingRequest) Decode(r io.Reader, pver uint32) error {
//Channel Type (0/1)
// default to 0 for CLTV-only
//Funding Amount (1/8)
//Channel Minimum Capacity (9/8)
//Revocation Hash (17/20)
//Commitment Pubkey (37/32)
//Reserve Amount (69/8)
//Minimum Transaction Fee Per Kb (77/8)
//LockTime (85/4)
//FeePayer (89/1)
//DeliveryPkScript
// First byte length then pkscript
//Inputs: Create the TxIns
// First byte is number of inputs
// For each input, it's 32bytes txin & 4bytes index
err := readElements(r, false,
&c.ChannelType,
&c.FundingAmount,
&c.MinTotalFundingAmount,
&c.RevocationHash,
&c.Pubkey,
&c.ReserveAmount,
&c.MinFeePerKb,
&c.LockTime,
&c.FeePayer,
&c.DeliveryPkScript,
&c.Inputs)
if err != nil {
return err
}
return c.Validate()
}
//Creates a new FundingRequest
func NewFundingRequest() *FundingRequest {
return &FundingRequest{}
}
//Serializes the item from the FundingRequest struct
//Writes the data to w
func (c *FundingRequest) Encode(w io.Writer, pver uint32) error {
//Channel Type
//Funding Amont
//Channel Minimum Capacity
//Revocation Hash
//Commitment Pubkey
//Reserve Amount
//Minimum Transaction Fee Per KB
//LockTime
//FeePayer
//DeliveryPkScript
//Inputs: Append the actual Txins
err := writeElements(w, false,
c.ChannelType,
c.FundingAmount,
c.MinTotalFundingAmount,
c.RevocationHash,
c.Pubkey,
c.ReserveAmount,
c.MinFeePerKb,
c.LockTime,
c.FeePayer,
c.DeliveryPkScript,
c.Inputs)
if err != nil {
return err
}
return nil
}
func (c *FundingRequest) Command() uint32 {
return CmdFundingRequest
}
func (c *FundingRequest) MaxPayloadLength(uint32) uint32 {
//90 (base size) + 26 (pkscript) + 1 (numTxes) + 127*36(127 inputs * sha256+idx)
//4690
return 4689
}
//Makes sure the struct data is valid (e.g. no negatives or invalid pkscripts)
func (c *FundingRequest) Validate() error {
//No negative values
if c.FundingAmount < 0 {
return fmt.Errorf("FundingAmount cannot be negative")
}
if c.ReserveAmount < 0 {
return fmt.Errorf("ReserveAmount cannot be negative")
}
if c.MinFeePerKb < 0 {
return fmt.Errorf("MinFeePerKb cannot be negative")
}
if c.MinTotalFundingAmount < 0 {
return fmt.Errorf("MinTotalFundingAmount cannot be negative")
}
//PkScript is either P2SH or P2PKH
//P2PKH
if len(c.DeliveryPkScript) == 25 {
//Begins with OP_DUP OP_HASH160 PUSHDATA(20)
if !(bytes.Equal(c.DeliveryPkScript[0:3], []byte{118, 169, 20}) &&
//Ends with OP_EQUALVERIFY OP_CHECKSIG
bytes.Equal(c.DeliveryPkScript[23:25], []byte{136, 172})) {
//If it's not correct, return error
return fmt.Errorf("PkScript only allows P2SH or P2PKH")
}
//P2SH
} else if len(c.DeliveryPkScript) == 23 {
//Begins with OP_HASH160 PUSHDATA(20)
if !(bytes.Equal(c.DeliveryPkScript[0:2], []byte{169, 20}) &&
//Ends with OP_EQUAL
bytes.Equal(c.DeliveryPkScript[22:23], []byte{135})) {
//If it's not correct, return error
return fmt.Errorf("PkScript only allows P2SH or P2PKH")
}
} else {
//Length not 23 or 25
return fmt.Errorf("PkScript only allows P2SH or P2PKH")
}
//We're good!
return nil
}
func (c *FundingRequest) String() string {
var inputs string
for i, in := range c.Inputs {
inputs += fmt.Sprintf("\n Slice\t%d\n", i)
inputs += fmt.Sprintf("\tHash\t%s\n", in.PreviousOutPoint.Hash)
inputs += fmt.Sprintf("\tIndex\t%d\n", in.PreviousOutPoint.Index)
}
return fmt.Sprintf("\n--- Begin FundingRequest ---\n") +
fmt.Sprintf("ChannelType:\t\t%x\n", c.ChannelType) +
fmt.Sprintf("FundingAmount:\t\t%s\n", c.FundingAmount.String()) +
fmt.Sprintf("ReserveAmount:\t\t%s\n", c.ReserveAmount.String()) +
fmt.Sprintf("MinFeePerKb:\t\t%s\n", c.MinFeePerKb.String()) +
fmt.Sprintf("MinTotalFundingAmount\t%s\n", c.MinTotalFundingAmount.String()) +
fmt.Sprintf("LockTime\t\t%d\n", c.LockTime) +
fmt.Sprintf("FeePayer\t\t%x\n", c.FeePayer) +
fmt.Sprintf("RevocationHash\t\t%x\n", c.RevocationHash) +
fmt.Sprintf("Pubkey\t\t\t%x\n", c.Pubkey.SerializeCompressed()) +
fmt.Sprintf("DeliveryPkScript\t%x\n", c.DeliveryPkScript) +
fmt.Sprintf("Inputs:") +
inputs +
fmt.Sprintf("--- End FundingRequest ---\n")
}

View File

@ -0,0 +1,127 @@
package lnwire
import (
"bytes"
"encoding/hex"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
// "io"
"io/ioutil"
"reflect"
"testing"
)
var (
//For debugging, writes to /dev/shm/
//Maybe in the future do it if you do "go test -v"
WRITE_FILE = false
FILENAME = "/dev/shm/fundingRequest.raw"
//preimage: 9a2cbd088763db88dd8ba79e5726daa6aba4aa7e
//echo -n | openssl sha256 | openssl ripemd160 | openssl sha256 | openssl ripemd160
revocationHashBytes, _ = hex.DecodeString("4132b6b48371f7b022a16eacb9b2b0ebee134d41")
revocationHash [20]byte
_ = copy(revocationHash[:], revocationHashBytes)
//privkey: 9fa1d55217f57019a3c37f49465896b15836f54cb8ef6963870a52926420a2dd
pubKeyBytes, _ = hex.DecodeString("02f977808cb9577897582d7524b562691e180953dd0008eb44e09594c539d6daee")
pubKey, _ = btcec.ParsePubKey(pubKeyBytes, btcec.S256())
// Delivery PkScript
//Privkey: f2c00ead9cbcfec63098dc0a5f152c0165aff40a2ab92feb4e24869a284c32a7
//PKhash: n2fkWVphUzw3zSigzPsv9GuDyg9mohzKpz
deliveryPkScript, _ = hex.DecodeString("76a914e8048c0fb75bdecc91ebfb99c174f4ece29ffbd488ac")
//echo -n | openssl sha256
//This stuff gets reversed!!!
shaHash1Bytes, _ = hex.DecodeString("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")
shaHash1, _ = wire.NewShaHash(shaHash1Bytes)
outpoint1 = wire.NewOutPoint(shaHash1, 0)
//echo | openssl sha256
//This stuff gets reversed!!!
shaHash2Bytes, _ = hex.DecodeString("01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b")
shaHash2, _ = wire.NewShaHash(shaHash2Bytes)
outpoint2 = wire.NewOutPoint(shaHash2, 1)
//create inputs from outpoint1 and outpoint2
inputs = []*wire.TxIn{wire.NewTxIn(outpoint1, nil), wire.NewTxIn(outpoint2, nil)}
//funding request
fundingRequest = &FundingRequest{
ChannelType: uint8(0),
FundingAmount: btcutil.Amount(100000000),
ReserveAmount: btcutil.Amount(131072),
MinFeePerKb: btcutil.Amount(20000),
MinTotalFundingAmount: btcutil.Amount(150000000),
LockTime: uint32(4320), //30 block-days
FeePayer: uint8(0),
RevocationHash: revocationHash,
Pubkey: pubKey,
DeliveryPkScript: deliveryPkScript,
Inputs: inputs,
}
serializedString = "000000000005f5e1000000000008f0d1804132b6b48371f7b022a16eacb9b2b0ebee134d4102f977808cb9577897582d7524b562691e180953dd0008eb44e09594c539d6daee00000000000200000000000000004e20000010e0001976a914e8048c0fb75bdecc91ebfb99c174f4ece29ffbd488ac02e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b8550000000001ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b00000001"
serializedMessage = "0709110b00000014000000be000000000005f5e1000000000008f0d1804132b6b48371f7b022a16eacb9b2b0ebee134d4102f977808cb9577897582d7524b562691e180953dd0008eb44e09594c539d6daee00000000000200000000000000004e20000010e0001976a914e8048c0fb75bdecc91ebfb99c174f4ece29ffbd488ac02e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b8550000000001ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b00000001"
)
func TestFundingRequestEncodeDecode(t *testing.T) {
//Test serialization
b := new(bytes.Buffer)
err := fundingRequest.Encode(b, 0)
if err != nil {
t.Error("Serialization error")
t.Error(err.Error())
}
t.Logf("Encoded Funding Request: %x\n", b.Bytes())
//Check if we serialized correctly
if serializedString != hex.EncodeToString(b.Bytes()) {
t.Error("Serialization does not match expected")
}
//So I can do: hexdump -C /dev/shm/fundingRequest.raw
if WRITE_FILE {
err = ioutil.WriteFile(FILENAME, b.Bytes(), 0644)
if err != nil {
t.Error("File write error")
t.Error(err.Error())
}
}
//Test deserialization
//Make a new buffer just to be clean
c := new(bytes.Buffer)
c.Write(b.Bytes())
newFunding := NewFundingRequest()
err = newFunding.Decode(c, 0)
if err != nil {
t.Error("Decoding Error")
t.Error(err.Error())
}
if !reflect.DeepEqual(newFunding, fundingRequest) {
t.Error("Decoding does not match!")
}
//Show the struct
t.Log(newFunding.String())
//Test message using Message interface
//Serialize/Encode
b = new(bytes.Buffer)
_, err = WriteMessage(b, fundingRequest, uint32(1), wire.TestNet3)
t.Logf("%x\n", b.Bytes())
if hex.EncodeToString(b.Bytes()) != serializedMessage {
t.Error("Message encoding error")
}
//Deserialize/Decode
c = new(bytes.Buffer)
c.Write(b.Bytes())
_, msg, _, err := ReadMessage(c, uint32(1), wire.TestNet3)
if err != nil {
t.Errorf(err.Error())
}
if !reflect.DeepEqual(msg, fundingRequest) {
t.Error("Message decoding does not match!")
}
t.Logf(msg.String())
}

View File

@ -16,61 +16,11 @@ type PkScript []byte
//Subsatoshi amount
type MicroSatoshi int32
type CreateChannel struct {
ChannelType uint8
FundingAmount btcutil.Amount
ReserveAmount btcutil.Amount
MinFeePerKb btcutil.Amount
//Should double-check the total funding later
MinTotalFundingAmount btcutil.Amount
//CLTV lock-time to use
LockTime uint32
//Who pays the fees
//0: (default) channel initiator
//1: split
//2: channel responder
FeePayer uint8
RevocationHash [20]byte
Pubkey *btcec.PublicKey
DeliveryPkScript PkScript //*MUST* be either P2PKH or P2SH
Inputs []*wire.TxIn
}
func (c *CreateChannel) String() string {
var inputs string
for i, in := range c.Inputs {
inputs += fmt.Sprintf("\n Slice\t%d\n", i)
inputs += fmt.Sprintf("\tHash\t%s\n", in.PreviousOutPoint.Hash)
inputs += fmt.Sprintf("\tIndex\t%d\n", in.PreviousOutPoint.Index)
}
return fmt.Sprintf("\n--- Begin CreateChannel ---\n") +
fmt.Sprintf("ChannelType:\t\t%x\n", c.ChannelType) +
fmt.Sprintf("FundingAmount:\t\t%s\n", c.FundingAmount.String()) +
fmt.Sprintf("ReserveAmount:\t\t%s\n", c.ReserveAmount.String()) +
fmt.Sprintf("MinFeePerKb:\t\t%s\n", c.MinFeePerKb.String()) +
fmt.Sprintf("MinTotalFundingAmount\t%s\n", c.MinTotalFundingAmount.String()) +
fmt.Sprintf("LockTime\t\t%d\n", c.LockTime) +
fmt.Sprintf("FeePayer\t\t%x\n", c.FeePayer) +
fmt.Sprintf("RevocationHash\t\t%x\n", c.RevocationHash) +
fmt.Sprintf("Pubkey\t\t\t%x\n", c.Pubkey.SerializeCompressed()) +
fmt.Sprintf("DeliveryPkScript\t%x\n", c.DeliveryPkScript) +
fmt.Sprintf("Inputs:") +
inputs +
fmt.Sprintf("--- End CreateChannel ---\n")
}
//Writes the big endian representation of element
//Unified function to call when writing different types
//Pre-allocate a byte-array of the correct size for cargo-cult security
//More copies but whatever...
func writeElement(w io.Writer, element interface{}) error {
func writeElement(w io.Writer, includeSig bool, element interface{}) error {
var err error
switch e := element.(type) {
case uint8:
@ -80,6 +30,7 @@ func writeElement(w io.Writer, element interface{}) error {
if err != nil {
return err
}
return nil
case uint32:
var b [4]byte
binary.BigEndian.PutUint32(b[:], uint32(e))
@ -87,11 +38,13 @@ func writeElement(w io.Writer, element interface{}) error {
if err != nil {
return err
}
return nil
case btcutil.Amount:
err = binary.Write(w, binary.BigEndian, int64(e))
if err != nil {
return err
}
return nil
case *btcec.PublicKey:
var b [33]byte
serializedPubkey := e.SerializeCompressed()
@ -103,11 +56,21 @@ func writeElement(w io.Writer, element interface{}) error {
if err != nil {
return err
}
return nil
case [20]byte:
_, err = w.Write(e[:])
if err != nil {
return err
}
return nil
case wire.BitcoinNet:
var b [4]byte
binary.BigEndian.PutUint32(b[:], uint32(e))
_, err := w.Write(b[:])
if err != nil {
return err
}
return nil
case PkScript:
scriptLength := len(e)
//Make sure it's P2PKH or P2SH size or less
@ -124,6 +87,7 @@ func writeElement(w io.Writer, element interface{}) error {
if err != nil {
return err
}
return nil
case []*wire.TxIn:
//Append the unsigned(!!!) txins
//Write the size (1-byte)
@ -151,7 +115,18 @@ func writeElement(w io.Writer, element interface{}) error {
if err != nil {
return err
}
//Signature(optional)
if includeSig {
var sig [33]byte
copy(sig[:], in.SignatureScript)
_, err = w.Write(sig[:])
if err != nil {
return err
}
}
}
return nil
default:
return fmt.Errorf("Unknown type in writeElement: %T", e)
}
@ -159,7 +134,17 @@ func writeElement(w io.Writer, element interface{}) error {
return nil
}
func readElement(r io.Reader, element interface{}) error {
func writeElements(w io.Writer, includeSig bool, elements ...interface{}) error {
for _, element := range elements {
err := writeElement(w, includeSig, element)
if err != nil {
return err
}
}
return nil
}
func readElement(r io.Reader, includeSig bool, element interface{}) error {
var err error
switch e := element.(type) {
case *uint8:
@ -169,6 +154,7 @@ func readElement(r io.Reader, element interface{}) error {
return err
}
*e = b[0]
return nil
case *uint32:
var b [4]byte
_, err = io.ReadFull(r, b[:])
@ -176,6 +162,7 @@ func readElement(r io.Reader, element interface{}) error {
return err
}
*e = binary.BigEndian.Uint32(b[:])
return nil
case *btcutil.Amount:
var b [8]byte
_, err = io.ReadFull(r, b[:])
@ -183,6 +170,7 @@ func readElement(r io.Reader, element interface{}) error {
return err
}
*e = btcutil.Amount(int64(binary.BigEndian.Uint64(b[:])))
return nil
case **btcec.PublicKey:
var b [33]byte
_, err = io.ReadFull(r, b[:])
@ -194,11 +182,21 @@ func readElement(r io.Reader, element interface{}) error {
if err != nil {
return err
}
return nil
case *[20]byte:
_, err = io.ReadFull(r, e[:])
if err != nil {
return err
}
return nil
case *wire.BitcoinNet:
var b [4]byte
_, err := io.ReadFull(r, b[:])
if err != nil {
return err
}
*e = wire.BitcoinNet(binary.BigEndian.Uint32(b[:]))
return nil
case *PkScript:
//Get the script length first
var scriptLength [1]uint8
@ -217,6 +215,7 @@ func readElement(r io.Reader, element interface{}) error {
if err != nil {
return err
}
return nil
case *[]*wire.TxIn:
//Read the size (1-byte number of txins)
var numScripts [1]uint8
@ -249,12 +248,23 @@ func readElement(r io.Reader, element interface{}) error {
}
index := binary.BigEndian.Uint32(idxBytes[:])
outPoint := wire.NewOutPoint(shaHash, index)
//Signature(optional)
if includeSig {
var sig [33]byte
_, err = io.ReadFull(r, sig[:])
if err != nil {
return err
}
//Create TxIn
txins = append(txins, wire.NewTxIn(outPoint, sig[:]))
} else { //no signature
//Create TxIn
txins = append(txins, wire.NewTxIn(outPoint, nil))
}
//Create TxIn
txins = append(txins, wire.NewTxIn(outPoint, nil))
}
*e = *&txins
return nil
default:
return fmt.Errorf("Unknown type in readElement: %T", e)
}
@ -262,168 +272,12 @@ func readElement(r io.Reader, element interface{}) error {
return nil
}
func (c *CreateChannel) DeserializeFundingRequest(r io.Reader) error {
var err error
//Message Type (0/1)
var messageType uint8
err = readElement(r, &messageType)
if err != nil {
return err
func readElements(r io.Reader, includeSig bool, elements ...interface{}) error {
for _, element := range elements {
err := readElement(r, includeSig, element)
if err != nil {
return err
}
}
if messageType != 0x30 {
return fmt.Errorf("Message type does not match FundingRequest")
}
//Channel Type (1/1)
err = readElement(r, &c.ChannelType)
if err != nil {
return err
}
// Funding Amount (2/8)
err = readElement(r, &c.FundingAmount)
if err != nil {
return err
}
// Channel Minimum Capacity (10/8)
err = readElement(r, &c.MinTotalFundingAmount)
if err != nil {
return err
}
// Revocation Hash (18/20)
err = readElement(r, &c.RevocationHash)
if err != nil {
return err
}
// Commitment Pubkey (38/32)
err = readElement(r, &c.Pubkey)
if err != nil {
return err
}
// Reserve Amount (70/8)
err = readElement(r, &c.ReserveAmount)
if err != nil {
return err
}
//Minimum Transaction Fee Per Kb (78/8)
err = readElement(r, &c.MinFeePerKb)
if err != nil {
return err
}
//LockTime (86/4)
err = readElement(r, &c.LockTime)
if err != nil {
return err
}
//FeePayer (90/1)
err = readElement(r, &c.FeePayer)
if err != nil {
return err
}
// Delivery PkScript
err = readElement(r, &c.DeliveryPkScript)
if err != nil {
return err
}
//Create the TxIns
err = readElement(r, &c.Inputs)
if err != nil {
return err
}
return nil
}
//Serializes the fundingRequest from the CreateChannel struct
//Writes the data to w
func (c *CreateChannel) SerializeFundingRequest(w io.Writer) error {
var err error
//Fund request byte
err = writeElement(w, uint8(0x30))
if err != nil {
return err
}
//Channel Type
//default to 0 for CLTV-only
err = writeElement(w, c.ChannelType)
if err != nil {
return err
}
//Funding Amont
err = writeElement(w, c.FundingAmount)
if err != nil {
return err
}
// Channel Minimum Capacity
err = writeElement(w, c.MinTotalFundingAmount)
if err != nil {
return err
}
// Revocation Hash
err = writeElement(w, c.RevocationHash)
if err != nil {
return err
}
// Commitment Pubkey
err = writeElement(w, c.Pubkey)
if err != nil {
return err
}
// Reserve Amount
err = writeElement(w, c.ReserveAmount)
if err != nil {
return err
}
//Minimum Transaction Fee Per KB
err = writeElement(w, c.MinFeePerKb)
if err != nil {
return err
}
//LockTime
err = writeElement(w, c.LockTime)
if err != nil {
return err
}
//FeePayer
err = writeElement(w, c.FeePayer)
if err != nil {
return err
}
// Delivery PkScript
//First byte length then pkscript
err = writeElement(w, c.DeliveryPkScript)
if err != nil {
return err
}
//Append the actual Txins
//First byte is number of inputs
//For each input, it's 32bytes txin & 4bytes index
err = writeElement(w, c.Inputs)
if err != nil {
return err
}
return nil
}

View File

@ -1,105 +0,0 @@
package lnwire
import (
"bytes"
"encoding/hex"
"fmt"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
// "io"
"io/ioutil"
"reflect"
"testing"
)
var (
//For debugging, writes to /dev/shm/
//Maybe in the future do it if you do "go test -v"
WRITE_FILE = false
FILENAME = "/dev/shm/fundingRequest.raw"
//echo -n | openssl sha256 | openssl ripemd160
ourRevocationHashBytes, _ = hex.DecodeString("9a2cbd088763db88dd8ba79e5726daa6aba4aa7e")
ourRevocationHash [20]byte
_ = copy(ourRevocationHash[:], ourRevocationHashBytes)
//privkey: 9fa1d55217f57019a3c37f49465896b15836f54cb8ef6963870a52926420a2dd
ourPubKeyBytes, _ = hex.DecodeString("02f977808cb9577897582d7524b562691e180953dd0008eb44e09594c539d6daee")
ourPubKey, _ = btcec.ParsePubKey(ourPubKeyBytes, btcec.S256())
// Delivery PkScript
//Privkey: f2c00ead9cbcfec63098dc0a5f152c0165aff40a2ab92feb4e24869a284c32a7
//PKhash: n2fkWVphUzw3zSigzPsv9GuDyg9mohzKpz
ourDeliveryPkScript, _ = hex.DecodeString("76a914e8048c0fb75bdecc91ebfb99c174f4ece29ffbd488ac")
//echo -n | openssl sha256
//This stuff gets reversed!!!
shaHash1Bytes, _ = hex.DecodeString("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")
shaHash1, _ = wire.NewShaHash(shaHash1Bytes)
outpoint1 = wire.NewOutPoint(shaHash1, 0)
//echo | openssl sha256
//This stuff gets reversed!!!
shaHash2Bytes, _ = hex.DecodeString("01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b")
shaHash2, _ = wire.NewShaHash(shaHash2Bytes)
outpoint2 = wire.NewOutPoint(shaHash2, 1)
//create inputs from outpoint1 and outpoint2
ourInputs = []*wire.TxIn{wire.NewTxIn(outpoint1, nil), wire.NewTxIn(outpoint2, nil)}
//funding request
createChannel = CreateChannel{
ChannelType: uint8(0),
FundingAmount: btcutil.Amount(100000000),
ReserveAmount: btcutil.Amount(131072),
MinFeePerKb: btcutil.Amount(20000),
MinTotalFundingAmount: btcutil.Amount(150000000),
LockTime: uint32(4320), //30 block-days
FeePayer: uint8(0),
RevocationHash: ourRevocationHash,
Pubkey: ourPubKey,
DeliveryPkScript: ourDeliveryPkScript,
Inputs: ourInputs,
}
serializedString = "30000000000005f5e1000000000008f0d1809a2cbd088763db88dd8ba79e5726daa6aba4aa7e02f977808cb9577897582d7524b562691e180953dd0008eb44e09594c539d6daee00000000000200000000000000004e20000010e0001976a914e8048c0fb75bdecc91ebfb99c174f4ece29ffbd488ac02e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b8550000000001ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b00000001"
)
func TestFundingRequestSerializeDeserialize(t *testing.T) {
b := new(bytes.Buffer)
err := createChannel.SerializeFundingRequest(b)
if err != nil {
fmt.Println(err)
t.Error("Serialization error")
}
t.Logf("Serialized Funding Request: %x\n", b.Bytes())
//Check if we serialized correctly
if serializedString != hex.EncodeToString(b.Bytes()) {
t.Error("Serialization does not match expected")
}
//So I can do: hexdump -C /dev/shm/fundingRequest.raw
if WRITE_FILE {
err = ioutil.WriteFile(FILENAME, b.Bytes(), 0644)
if err != nil {
t.Error("File write error")
}
}
//Test deserialization
//Make a new buffer just to be clean
c := new(bytes.Buffer)
c.Write(b.Bytes())
var newChannel CreateChannel
err = newChannel.DeserializeFundingRequest(c)
if err != nil {
fmt.Println(err)
t.Error("Deserialiation Error")
}
if !reflect.DeepEqual(newChannel, createChannel) {
t.Error("Deserialization does not match!")
}
t.Log(newChannel.String())
}

209
lnwire/message.go Normal file
View File

@ -0,0 +1,209 @@
//Code derived from https://github.com/btcsuite/btcd/blob/master/wire/message.go
package lnwire
import (
"bytes"
"fmt"
"github.com/btcsuite/btcd/wire"
"io"
)
//4-byte network + 4-byte message id + payload-length 4-byte
//Maybe add checksum or something?
const MessageHeaderSize = 12
const MaxMessagePayload = 1024 * 1024 * 32 // 32MB
const (
CmdFundingRequest = uint32(20)
)
//Every message has these functions:
type Message interface {
Decode(io.Reader, uint32) error //(io, protocol version)
Encode(io.Writer, uint32) error //(io, protocol version)
Command() uint32 //returns ID of the message
MaxPayloadLength(uint32) uint32 //(version) maxpayloadsize
Validate() error //Validates the data struct
String() string
}
func makeEmptyMessage(command uint32) (Message, error) {
var msg Message
switch command {
case CmdFundingRequest:
msg = &FundingRequest{}
default:
return nil, fmt.Errorf("unhandled command [%x]", command)
}
return msg, nil
}
type messageHeader struct {
//NOTE(j): We don't need to worry about the magic overlapping with
//bitcoin since this is inside encrypted comms anyway, but maybe we
//should use the XOR (^wire.TestNet3) just in case???
magic wire.BitcoinNet
command uint32
length uint32
//Do we need a checksum here?
}
func readMessageHeader(r io.Reader) (int, *messageHeader, error) {
var headerBytes [MessageHeaderSize]byte
n, err := io.ReadFull(r, headerBytes[:])
if err != nil {
return n, nil, err
}
hr := bytes.NewReader(headerBytes[:])
hdr := messageHeader{}
err = readElements(hr, false,
&hdr.magic,
&hdr.command,
&hdr.length)
if err != nil {
return n, nil, err
}
return n, &hdr, nil
}
// discardInput reads n bytes from reader r in chunks and discards the read
// bytes. This is used to skip payloads when various errors occur and helps
// prevent rogue nodes from causing massive memory allocation through forging
// header length.
func discardInput(r io.Reader, n uint32) {
maxSize := uint32(10 * 1024) //10k at a time
numReads := n / maxSize
bytesRemaining := n % maxSize
if n > 0 {
buf := make([]byte, maxSize)
for i := uint32(0); i < numReads; i++ {
io.ReadFull(r, buf)
}
}
if bytesRemaining > 0 {
buf := make([]byte, bytesRemaining)
io.ReadFull(r, buf)
}
}
func WriteMessage(w io.Writer, msg Message, pver uint32, btcnet wire.BitcoinNet) (int, error) {
totalBytes := 0
cmd := msg.Command()
//Encode the message payload
var bw bytes.Buffer
err := msg.Encode(&bw, pver)
if err != nil {
return totalBytes, err
}
payload := bw.Bytes()
lenp := len(payload)
//Enforce maximum overall message payload
if lenp > MaxMessagePayload {
return totalBytes, fmt.Errorf("message payload is too large - encoded %d bytes, but maximum message payload is %d bytes", lenp, MaxMessagePayload)
}
//Enforce maximum message payload on the message type
mpl := msg.MaxPayloadLength(pver)
if uint32(lenp) > mpl {
return totalBytes, fmt.Errorf("message payload is too large - encoded %d bytes, but maximum message payload of type %x is %d bytes", lenp, cmd, mpl)
}
//Create header for the message
hdr := messageHeader{}
hdr.magic = btcnet
hdr.command = cmd
hdr.length = uint32(lenp)
//Checksum goes here if needed... and also add to writeElements
// Encode the header for the message. This is done to a buffer
// rather than directly to the writer since writeElements doesn't
// return the number of bytes written.
hw := bytes.NewBuffer(make([]byte, 0, MessageHeaderSize))
writeElements(hw, false, hdr.magic, hdr.command, hdr.length)
//Write header
n, err := w.Write(hw.Bytes())
totalBytes += n
if err != nil {
return totalBytes, err
}
//Write payload
n, err = w.Write(payload)
totalBytes += n
if err != nil {
return totalBytes, err
}
return totalBytes, nil
}
func ReadMessage(r io.Reader, pver uint32, btcnet wire.BitcoinNet) (int, Message, []byte, error) {
totalBytes := 0
n, hdr, err := readMessageHeader(r)
totalBytes += n
if err != nil {
return totalBytes, nil, nil, err
}
//Enforce maximum message payload
if hdr.length > MaxMessagePayload {
return totalBytes, nil, nil, fmt.Errorf("message payload is too large - header indicates %d bytes, but max message payload is %d bytes.", hdr.length, MaxMessagePayload)
}
//Check for messages in the wrong bitcoin network
if hdr.magic != btcnet {
discardInput(r, hdr.length)
return totalBytes, nil, nil, fmt.Errorf("message from other network [%v]", hdr.magic)
}
//Create struct of appropriate message type based on the command
command := hdr.command
msg, err := makeEmptyMessage(command)
if err != nil {
discardInput(r, hdr.length)
return totalBytes, nil, nil, fmt.Errorf("ReadMessage %s", err.Error())
}
//Check for maximum length based on the message type
mpl := msg.MaxPayloadLength(pver)
if hdr.length > mpl {
discardInput(r, hdr.length)
return totalBytes, nil, nil, fmt.Errorf("payload exceeds max length. indicates %v bytes, but max of message type %v is %v.", hdr.length, command, mpl)
}
//Read payload
payload := make([]byte, hdr.length)
n, err = io.ReadFull(r, payload)
totalBytes += n
if err != nil {
return totalBytes, nil, nil, err
}
//If we want to use checksums, test it here
//Unmarshal message
pr := bytes.NewBuffer(payload)
err = msg.Decode(pr, pver)
if err != nil {
return totalBytes, nil, nil, err
}
//Validate the data
msg.Validate()
if err != nil {
return totalBytes, nil, nil, err
}
//We're good!
return totalBytes, msg, payload, nil
}