pool/clientdb/order.go
2022-10-18 06:44:01 -07:00

968 lines
26 KiB
Go

package clientdb
import (
"bytes"
"errors"
"fmt"
"io"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightninglabs/pool/event"
"github.com/lightninglabs/pool/order"
"github.com/lightninglabs/pool/sidecar"
"github.com/lightningnetwork/lnd/tlv"
"go.etcd.io/bbolt"
)
const (
// bidSelfChanBalanceType is the tlv type we use to store the self
// channel balance on bid orders.
bidSelfChanBalanceType tlv.Type = 1
// bidSidecarTicketType is the tlv type we use to store the sidecar
// ticket on bid orders.
bidSidecarTicketType tlv.Type = 2
// orderChannelType is the tlv type we use to store the desired channel
// type resulting from a matched order.
orderChannelType tlv.Type = 3
// allowedNodeIDsType is the tlv type we use to store the list of node
// ids the order is allowed to match with.
allowedNodeIDsType tlv.Type = 4
// notAllowedNodeIDsType is the tlv type we use to store the list of
// node ids the order is not allowed to match with.
notAllowedNodeIDsType tlv.Type = 5
// bidUnannouncedChannelType is the tlv type we use to store a flag
// value when a bid requires unannounced channels.
bidUnannouncedChannelType tlv.Type = 6
// askChannelAnnouncementConstraintsType is the tlv type that we use
// to store the channel announcement match preferences.
askChannelAnnouncementConstraintsType tlv.Type = 7
// bidZeroConfType is the tlv type we use to store a flag value when
// a bid asks for zero conf channels.
bidZeroConfType tlv.Type = 8
// askChannelConfirmationConstraintsType is the tlv type that we use
// to store the channel confirmation match preferences.
askChannelConfirmationConstraintsType tlv.Type = 9
// orderAuctionType is the tlv type that we use to store the auction
// type for an order.
orderAuctionType tlv.Type = 10
// orderIsPublicType is the tlv type we use to store a flag value when
// it is ok to share details of this order in public markets.
orderIsPublicType tlv.Type = 11
)
var (
// ErrNoOrder is the error returned if no order with the given nonce
// exists in the store.
ErrNoOrder = errors.New("no order found")
// ErrOrderExists is returned if an order is submitted that is already
// known to the store.
ErrOrderExists = errors.New("order with this nonce already exists")
// ordersBucketKey is a bucket that contains all orders that are
// currently pending or completed. This bucket is keyed by the nonce and
// leads to a nested sub-bucket that houses information for that order.
ordersBucketKey = []byte("orders")
// orderKey is the key that stores the serialized order. It is nested
// within the sub-bucket for each active order.
//
// path: ordersBucketKey -> orderBucket[nonce] -> orderKey
orderKey = []byte("order")
// orderTierKey is a key within the order bucket for an order. It
// stores the min node tier for an order. It allows an order to specify
// the required minimum node tier for matching and execution.
orderTierKey = []byte("order-tier")
// orderMinUnitsMatchKey is the key that stores the serialized order's
// minimum units match. It is nested within the sub-bucket for each
// active order.
//
// path: ordersBucketKey -> orderBucket[nonce] -> orderMinUnitsMatchKey
orderMinUnitsMatchKey = []byte("order-min-units-match")
// orderTlvKey is a key within the order bucket for additional, tlv
// encoded data.
orderTlvKey = []byte("order-tlv")
)
type extraOrderData struct {
minNodeTier order.NodeTier
minUnitsMatch order.SupplyUnit
tlvData []byte
}
// orderCallback is a function type that is used to pass as a callback into the
// store's fetch functions to deliver the results to the caller. It also
// includes any auxiliary order information as additional arguments after the
// raw order bytes.
type orderCallback func(nonce order.Nonce, rawOrder []byte,
extraData *extraOrderData) error
// SubmitOrder stores an order by using the orders's nonce as an identifier. If
// an order with the given nonce already exists in the store, ErrOrderExists is
// returned.
//
// NOTE: This is part of the Store interface.
func (db *DB) SubmitOrder(newOrder order.Order) error {
return db.Update(func(tx *bbolt.Tx) error {
rootBucket, err := getBucket(tx, ordersBucketKey)
if err != nil {
return err
}
err = fetchOrderTX(rootBucket, newOrder.Nonce(), nil)
switch err {
// No error means there is an order with that nonce in the DB
// already.
case nil:
return ErrOrderExists
// This is what we want, no order with that nonce should be
// known.
case ErrNoOrder:
// Surface any other error.
default:
return err
}
// Serialize and store the order now that we know it doesn't
// exist yet.
var w bytes.Buffer
err = SerializeOrder(newOrder, &w)
if err != nil {
return err
}
// Create an event for the initial order and store it in the
// order's event bucket but also in the global events under an
// unique timestamp.
evt := NewCreatedEvent(newOrder)
err = storeOrderTX(
rootBucket, newOrder.Nonce(), w.Bytes(), evt,
)
if err != nil {
return err
}
err = storeOrderMinUnitsMatchTX(
rootBucket, newOrder.Nonce(),
newOrder.Details().MinUnitsMatch,
)
if err != nil {
return err
}
// Next, store any additional order data as a tlv stream.
if err := storeOrderTlvTX(
rootBucket, newOrder.Nonce(), newOrder,
); err != nil {
return err
}
// Finally, store the min node tier, but only if this is a Bid
// order.
if bidOrder, ok := newOrder.(*order.Bid); ok {
return storeOrderMinNoderTierTX(
rootBucket, newOrder.Nonce(),
bidOrder.MinNodeTier,
)
}
return nil
})
}
// UpdateOrder updates an order in the database according to the given
// modifiers.
//
// NOTE: This is part of the Store interface.
func (db *DB) UpdateOrder(nonce order.Nonce, modifiers ...order.Modifier) error {
return db.Update(func(tx *bbolt.Tx) error {
rootBucket, err := getBucket(tx, ordersBucketKey)
if err != nil {
return err
}
_, err = updateOrder(rootBucket, rootBucket, nonce, modifiers)
return err
})
}
// UpdateOrders atomically updates a list of orders in the database according to
// the given modifiers.
//
// NOTE: This is part of the Store interface.
func (db *DB) UpdateOrders(nonces []order.Nonce,
modifiers [][]order.Modifier) error {
if len(nonces) != len(modifiers) {
return fmt.Errorf("invalid number of modifiers")
}
// Read and update the orders in one single transaction that they are
// updated atomically.
return db.Update(func(tx *bbolt.Tx) error {
rootBucket, err := getBucket(tx, ordersBucketKey)
if err != nil {
return err
}
for idx, nonce := range nonces {
_, err := updateOrder(
rootBucket, rootBucket, nonce, modifiers[idx],
)
if err != nil {
return err
}
}
return nil
})
}
// GetOrder returns an order by looking up the nonce. If no order with that
// nonce exists in the store, ErrNoOrder is returned.
//
// NOTE: This is part of the Store interface.
func (db *DB) GetOrder(nonce order.Nonce) (order.Order, error) {
var (
o order.Order
err error
callback = func(nonce order.Nonce, rawOrder []byte,
extraData *extraOrderData) error {
r := bytes.NewReader(rawOrder)
o, err = DeserializeOrder(nonce, r)
if err != nil {
return err
}
tlvReader := bytes.NewReader(extraData.tlvData)
err = deserializeOrderTlvData(tlvReader, o)
if err != nil {
return err
}
// TODO(roasbeef): factory func to de-dup w/ all other
// instances?
if bidOrder, ok := o.(*order.Bid); ok {
bidOrder.MinNodeTier = extraData.minNodeTier
}
o.Details().MinUnitsMatch = extraData.minUnitsMatch
return nil
}
)
err = db.View(func(tx *bbolt.Tx) error {
rootBucket, err := getBucket(tx, ordersBucketKey)
if err != nil {
return err
}
return fetchOrderTX(rootBucket, nonce, callback)
})
return o, err
}
// GetOrders returns all orders that are currently known to the store.
//
// NOTE: This is part of the Store interface.
func (db *DB) GetOrders() ([]order.Order, error) {
var (
orders []order.Order
callback = func(nonce order.Nonce, rawOrder []byte,
extraData *extraOrderData) error {
r := bytes.NewReader(rawOrder)
o, err := DeserializeOrder(nonce, r)
if err != nil {
return err
}
tlvReader := bytes.NewReader(extraData.tlvData)
err = deserializeOrderTlvData(tlvReader, o)
if err != nil {
return err
}
if bidOrder, ok := o.(*order.Bid); ok {
bidOrder.MinNodeTier = extraData.minNodeTier
}
o.Details().MinUnitsMatch = extraData.minUnitsMatch
orders = append(orders, o)
return nil
}
)
err := db.View(func(tx *bbolt.Tx) error {
// First, we'll grab our main order bucket key.
rootBucket, err := getBucket(tx, ordersBucketKey)
if err != nil {
return err
}
// We'll now traverse the root bucket for all orders. The
// primary key is the order nonce itself.
return rootBucket.ForEach(func(nonceBytes, val []byte) error {
// Only go into things that we know are sub-bucket keys.
if val != nil {
return nil
}
// Get the order from the bucket and pass it to the
// caller.
var nonce order.Nonce
copy(nonce[:], nonceBytes)
return fetchOrderTX(rootBucket, nonce, callback)
})
})
if err != nil {
return nil, err
}
return orders, nil
}
// DeleteOrder removes the order with the given nonce. If no order with that
// nonce exists in the store, ErrNoOrder is returned.
//
// NOTE: This is part of the Store interface.
func (db *DB) DeleteOrder(nonce order.Nonce) error {
return db.Update(func(tx *bbolt.Tx) error {
// First, we'll grab our main order bucket key.
rootBucket, err := getBucket(tx, ordersBucketKey)
if err != nil {
return err
}
// Check that the order exists in the main bucket.
orderBucket := rootBucket.Bucket(nonce[:])
if orderBucket == nil {
return ErrNoOrder
}
return rootBucket.DeleteBucket(nonce[:])
})
}
// storeOrderTX saves a byte serialized order in its specific sub bucket within
// the root orders bucket.
func storeOrderTX(rootBucket *bbolt.Bucket, nonce order.Nonce,
orderBytes []byte, evt event.Event) error {
// From the root bucket, we'll make a new sub order bucket using
// the order nonce.
orderBucket, err := rootBucket.CreateBucketIfNotExists(nonce[:])
if err != nil {
return err
}
// Add the event to the global event store but also add a reference to
// it in the order bucket.
if err := storeEventTX(orderBucket, evt); err != nil {
return err
}
// With the order bucket created, we'll store the order itself.
return orderBucket.Put(orderKey, orderBytes)
}
// storeOrderMinNoderTierTX saves the current node tier for a given order to
// the proper bucket.
func storeOrderMinNoderTierTX(rootBucket *bbolt.Bucket, nonce order.Nonce,
minNodeTier order.NodeTier) error {
orderBucket, err := rootBucket.CreateBucketIfNotExists(nonce[:])
if err != nil {
return err
}
var b bytes.Buffer
if err := WriteElements(&b, minNodeTier); err != nil {
return err
}
return orderBucket.Put(orderTierKey, b.Bytes())
}
// storeOrderMinUnitsMatchTX saves the order's minimum units match within the
// order's root bucket.
func storeOrderMinUnitsMatchTX(rootBucket *bbolt.Bucket, nonce order.Nonce,
minUnitsMatch order.SupplyUnit) error {
// From the root bucket, we'll make a new sub order bucket using
// the order nonce.
orderBucket, err := rootBucket.CreateBucketIfNotExists(nonce[:])
if err != nil {
return err
}
// With the order bucket created, we'll store the order's min units
// match.
var buf bytes.Buffer
if err := WriteElement(&buf, minUnitsMatch); err != nil {
return err
}
return orderBucket.Put(orderMinUnitsMatchKey, buf.Bytes())
}
// storeOrderTlvTX saves the order's additional information as a tlv stream
// within the order's root bucket.
func storeOrderTlvTX(rootBucket *bbolt.Bucket, nonce order.Nonce,
o order.Order) error {
orderBucket, err := rootBucket.CreateBucketIfNotExists(nonce[:])
if err != nil {
return err
}
var b bytes.Buffer
if err := serializeOrderTlvData(&b, o); err != nil {
return err
}
return orderBucket.Put(orderTlvKey, b.Bytes())
}
// fetchOrderTX fetches the binary data of one order specified by its nonce from
// the root orders bucket.
func fetchOrderTX(rootBucket *bbolt.Bucket, nonce order.Nonce,
callback orderCallback) error {
// Get the main order bucket next.
orderBucket := rootBucket.Bucket(nonce[:])
if orderBucket == nil {
return ErrNoOrder
}
// With the main order bucket obtained, we'll grab the raw order
// bytes and pass them back to the caller.
orderBytes := orderBucket.Get(orderKey)
if orderBytes == nil {
return fmt.Errorf("order bucket not found")
}
if callback == nil {
return nil
}
// Try to read our extra order data. Use a default of 1 for the min
// units matched if the field does not exist (possible for older
// orders).
extraData := &extraOrderData{
minUnitsMatch: order.SupplyUnit(1),
tlvData: orderBucket.Get(orderTlvKey),
}
nodeTierBytes := orderBucket.Get(orderTierKey)
if nodeTierBytes != nil {
err := ReadElements(
bytes.NewReader(nodeTierBytes), &extraData.minNodeTier,
)
if err != nil {
return err
}
}
minUnitsMatchBytes := orderBucket.Get(orderMinUnitsMatchKey)
if minUnitsMatchBytes != nil {
err := ReadElements(
bytes.NewReader(minUnitsMatchBytes),
&extraData.minUnitsMatch,
)
if err != nil {
return err
}
}
return callback(nonce, orderBytes, extraData)
}
// updateOrder fetches the binary data of one order specified by its nonce from
// the orders bucket, applies the modifiers, and stores it back into dst bucket.
// The dst bucket can be a different bucket than the orders bucket if an order
// update should be written to a staging area instead of being applied to the
// original order directly. Do not use this function for applying the staged
// changes to the final bucket but use copyOrder instead.
func updateOrder(ordersBucket, dst *bbolt.Bucket, nonce order.Nonce,
modifiers []order.Modifier) (order.Order, error) {
var (
o order.Order
err error
callback = func(nonce order.Nonce, rawOrder []byte,
extraData *extraOrderData) error {
r := bytes.NewReader(rawOrder)
o, err = DeserializeOrder(nonce, r)
if err != nil {
return err
}
tlvReader := bytes.NewReader(extraData.tlvData)
err = deserializeOrderTlvData(tlvReader, o)
if err != nil {
return err
}
if bidOrder, ok := o.(*order.Bid); ok {
bidOrder.MinNodeTier = extraData.minNodeTier
}
o.Details().MinUnitsMatch = extraData.minUnitsMatch
return nil
}
)
// Retrieve the order stored in the database.
err = fetchOrderTX(ordersBucket, nonce, callback)
if err != nil {
return nil, err
}
// Preserve the original state to detect a state change later.
prevState := o.Details().State
// Apply the given modifications to it and store it back.
for _, modifier := range modifiers {
modifier(o.Details())
}
var w bytes.Buffer
err = SerializeOrder(o, &w)
if err != nil {
return nil, err
}
// Create an event for the update now. We always store the event
// reference to the order in the main order bucket, even if the
// destination bucket is the pending orders bucket.
evt := NewUpdatedEvent(prevState, o)
orderBucket := ordersBucket.Bucket(nonce[:])
if orderBucket == nil {
return nil, ErrNoOrder
}
// Add the event to the global event store but also add a reference to
// it in the order's event reference sub bucket.
if err := storeEventTX(orderBucket, evt); err != nil {
return nil, err
}
// Store the order to the destination bucket now, skipping the event as
// we've already written that to the source bucket.
if err := storeOrderTX(dst, nonce, w.Bytes(), nil); err != nil {
return nil, err
}
err = storeOrderMinUnitsMatchTX(dst, nonce, o.Details().MinUnitsMatch)
if err != nil {
return nil, err
}
// Next, store any additional order data as a tlv stream.
if err := storeOrderTlvTX(dst, nonce, o); err != nil {
return nil, err
}
if bidOrder, ok := o.(*order.Bid); ok {
err = storeOrderMinNoderTierTX(dst, nonce, bidOrder.MinNodeTier)
if err != nil {
return nil, err
}
}
return o, nil
}
// copyOrder copies a single order from the source to destination bucket. No
// event references are copied, only the order itself.
func copyOrder(src, dst *bbolt.Bucket, nonce order.Nonce) error {
var (
o order.Order
orderBytes []byte
nodeTier order.NodeTier
minUnitsMatch order.SupplyUnit
err error
callback = func(_ order.Nonce, rawOrder []byte,
extraData *extraOrderData) error {
orderBytes = rawOrder
// In order to know if we need the node tier at all,
// we'll go ahead and decode fully this order in
// question.
r := bytes.NewReader(rawOrder)
o, err = DeserializeOrder(nonce, r)
if err != nil {
return err
}
tlvReader := bytes.NewReader(extraData.tlvData)
err = deserializeOrderTlvData(tlvReader, o)
if err != nil {
return err
}
nodeTier = extraData.minNodeTier
minUnitsMatch = extraData.minUnitsMatch
return nil
}
)
// Retrieve the order stored in the source bucket.
err = fetchOrderTX(src, nonce, callback)
if err != nil {
return err
}
// Store it in the new bucket.
if err := storeOrderTX(dst, nonce, orderBytes, nil); err != nil {
return err
}
// Next, store any additional order data as a tlv stream.
if err := storeOrderTlvTX(dst, nonce, o); err != nil {
return err
}
if _, ok := o.(*order.Bid); ok {
err := storeOrderMinNoderTierTX(dst, nonce, nodeTier)
if err != nil {
return err
}
}
return storeOrderMinUnitsMatchTX(dst, nonce, minUnitsMatch)
}
// SerializeOrder binary serializes an order to a writer using the common LN
// wire format.
func SerializeOrder(o order.Order, w *bytes.Buffer) error {
kit := o.Details()
// We don't have to deserialize the nonce as it's the sub bucket name.
return WriteElements(
w, kit.Preimage, kit.Version, o.Type(), kit.State,
kit.FixedRate, kit.Amt, kit.Units, kit.MultiSigKeyLocator,
kit.MaxBatchFeeRate, kit.AcctKey, kit.UnitsUnfulfilled,
kit.LeaseDuration,
)
}
// DeserializeOrder deserializes an order from the binary LN wire format.
func DeserializeOrder(nonce order.Nonce, r io.Reader) (
order.Order, error) {
var (
kit = order.NewKit(nonce)
orderType order.Type
)
// We don't serialize the nonce as it's part of the bucket name already.
err := ReadElements(
r, &kit.Preimage, &kit.Version, &orderType, &kit.State,
&kit.FixedRate, &kit.Amt, &kit.Units, &kit.MultiSigKeyLocator,
&kit.MaxBatchFeeRate, &kit.AcctKey, &kit.UnitsUnfulfilled,
&kit.LeaseDuration,
)
if err != nil {
return nil, err
}
// Now read the order type specific fields.
switch orderType {
case order.TypeAsk:
return &order.Ask{
Kit: *kit,
}, nil
case order.TypeBid:
return &order.Bid{
Kit: *kit,
}, nil
default:
return nil, fmt.Errorf("unknown order type: %d", orderType)
}
}
// deserializeOrderTlvData attempts to decode the remaining bytes in the
// supplied reader by interpreting it as a tlv stream. If successful any
// non-default values of the additional data will be set on the given order.
func deserializeOrderTlvData(r io.Reader, o order.Order) error {
var (
selfChanBalance uint64
sidecarTicket []byte
channelType uint8
allowedNodeIDs []byte
notAllowedNodeIDs []byte
bidUnannoucedChannel uint8
askAnnouncementConstraints uint8
bidZeroConf uint8
askConfirmationConstraints uint8
auctionType uint8
isPublic uint8
)
// We'll add records for all possible additional order data fields here
// but will check below which of them were actually set, depending on
// the order type as well.
tlvStream, err := tlv.NewStream(
tlv.MakePrimitiveRecord(
bidSelfChanBalanceType, &selfChanBalance,
),
tlv.MakePrimitiveRecord(bidSidecarTicketType, &sidecarTicket),
tlv.MakePrimitiveRecord(orderChannelType, &channelType),
tlv.MakePrimitiveRecord(allowedNodeIDsType, &allowedNodeIDs),
tlv.MakePrimitiveRecord(
notAllowedNodeIDsType, &notAllowedNodeIDs,
),
tlv.MakePrimitiveRecord(
bidUnannouncedChannelType, &bidUnannoucedChannel,
),
tlv.MakePrimitiveRecord(
askChannelAnnouncementConstraintsType,
&askAnnouncementConstraints,
),
tlv.MakePrimitiveRecord(bidZeroConfType, &bidZeroConf),
tlv.MakePrimitiveRecord(
askChannelConfirmationConstraintsType,
&askConfirmationConstraints,
),
tlv.MakePrimitiveRecord(orderAuctionType, &auctionType),
tlv.MakePrimitiveRecord(orderIsPublicType, &isPublic),
)
if err != nil {
return err
}
parsedTypes, err := tlvStream.DecodeWithParsedTypes(r)
if err != nil {
return err
}
// Now check what records were actually parsed from the stream and
// assign any parsed fields to our order.
switch castOrder := o.(type) {
case *order.Ask:
t, ok := parsedTypes[askChannelAnnouncementConstraintsType]
if ok && t == nil {
constraint := order.ChannelAnnouncementConstraints(
askAnnouncementConstraints,
)
castOrder.AnnouncementConstraints = constraint
}
t, ok = parsedTypes[askChannelConfirmationConstraintsType]
if ok && t == nil {
constraint := order.ChannelConfirmationConstraints(
askConfirmationConstraints,
)
castOrder.ConfirmationConstraints = constraint
}
case *order.Bid:
if t, ok := parsedTypes[bidSelfChanBalanceType]; ok && t == nil {
castOrder.SelfChanBalance = btcutil.Amount(
selfChanBalance,
)
}
if t, ok := parsedTypes[bidSidecarTicketType]; ok && t == nil {
castOrder.SidecarTicket, err = sidecar.DeserializeTicket(
bytes.NewReader(sidecarTicket),
)
if err != nil {
return err
}
}
t, ok := parsedTypes[bidUnannouncedChannelType]
if ok && t == nil && bidUnannoucedChannel == 1 {
castOrder.UnannouncedChannel = true
}
t, ok = parsedTypes[bidZeroConfType]
if ok && t == nil && bidZeroConf == 1 {
castOrder.ZeroConfChannel = true
}
}
if t, ok := parsedTypes[orderChannelType]; ok && t == nil {
o.Details().ChannelType = order.ChannelType(channelType)
}
if t, ok := parsedTypes[allowedNodeIDsType]; ok && t == nil {
nodeIDs, err := AssemblePubKeySlice(allowedNodeIDs)
if err != nil {
return fmt.Errorf("invalid allowedNodeIDs: %v", err)
}
o.Details().AllowedNodeIDs = nodeIDs
}
if t, ok := parsedTypes[notAllowedNodeIDsType]; ok && t == nil {
nodeIDs, err := AssemblePubKeySlice(notAllowedNodeIDs)
if err != nil {
return fmt.Errorf("invalid notAllowedNodeIDs: %v", err)
}
o.Details().NotAllowedNodeIDs = nodeIDs
}
if t, ok := parsedTypes[orderAuctionType]; ok && t == nil {
o.Details().AuctionType = order.AuctionType(auctionType)
}
t, ok := parsedTypes[orderIsPublicType]
if ok && t == nil && isPublic == 1 {
o.Details().IsPublic = true
}
return nil
}
// serializeOrderTlvData encodes all additional data of an order as a single tlv
// stream.
func serializeOrderTlvData(w io.Writer, o order.Order) error {
var (
tlvRecords []tlv.Record
askAnnouncementConstraints uint8
bidUnannouncedChannel uint8
askConfirmationConstraints uint8
bidZeroConfChannel uint8
)
switch castOrder := o.(type) {
case *order.Ask:
askAnnouncementConstraints = uint8(
castOrder.AnnouncementConstraints,
)
askConfirmationConstraints = uint8(
castOrder.ConfirmationConstraints,
)
case *order.Bid:
if castOrder.SelfChanBalance != 0 {
selfChanBalance := uint64(castOrder.SelfChanBalance)
tlvRecords = append(tlvRecords, tlv.MakePrimitiveRecord(
bidSelfChanBalanceType, &selfChanBalance,
))
}
if castOrder.SidecarTicket != nil {
var buf bytes.Buffer
if err := sidecar.SerializeTicket(
&buf, castOrder.SidecarTicket,
); err != nil {
return err
}
sidecarBytes := buf.Bytes()
tlvRecords = append(tlvRecords, tlv.MakePrimitiveRecord(
bidSidecarTicketType, &sidecarBytes,
))
}
if castOrder.UnannouncedChannel {
bidUnannouncedChannel = uint8(1)
}
if castOrder.ZeroConfChannel {
bidZeroConfChannel = uint8(1)
}
}
channelType := uint8(o.Details().ChannelType)
tlvRecords = append(tlvRecords, tlv.MakePrimitiveRecord(
orderChannelType, &channelType,
))
if len(o.Details().AllowedNodeIDs) > 0 {
idBytes := FlattenPubKeySlice(o.Details().AllowedNodeIDs)
tlvRecords = append(
tlvRecords, tlv.MakePrimitiveRecord(
allowedNodeIDsType, &idBytes,
),
)
}
if len(o.Details().NotAllowedNodeIDs) > 0 {
idBytes := FlattenPubKeySlice(o.Details().NotAllowedNodeIDs)
tlvRecords = append(
tlvRecords, tlv.MakePrimitiveRecord(
notAllowedNodeIDsType, &idBytes,
),
)
}
tlvRecords = append(tlvRecords, tlv.MakePrimitiveRecord(
bidUnannouncedChannelType, &bidUnannouncedChannel,
))
tlvRecords = append(tlvRecords, tlv.MakePrimitiveRecord(
askChannelAnnouncementConstraintsType,
&askAnnouncementConstraints,
))
tlvRecords = append(tlvRecords, tlv.MakePrimitiveRecord(
bidZeroConfType, &bidZeroConfChannel,
))
tlvRecords = append(tlvRecords, tlv.MakePrimitiveRecord(
askChannelConfirmationConstraintsType,
&askConfirmationConstraints,
))
auctionType := uint8(o.Details().AuctionType)
tlvRecords = append(tlvRecords, tlv.MakePrimitiveRecord(
orderAuctionType, &auctionType,
))
if o.Details().IsPublic {
isPublic := uint8(1)
tlvRecords = append(tlvRecords, tlv.MakePrimitiveRecord(
orderIsPublicType, &isPublic,
))
}
tlvStream, err := tlv.NewStream(tlvRecords...)
if err != nil {
return err
}
return tlvStream.Encode(w)
}
// FlattenPubKeySlice returns a flatten array of bytes from the
// given array of public keys.
func FlattenPubKeySlice(pubKeys [][33]byte) []byte {
flatten := make([]byte, len(pubKeys)*33)
for i, key := range pubKeys {
copy(flatten[i*33:(i+1)*33], key[:])
}
return flatten
}
// AssemblePubKeySlice is the inverse of FlattenPubKeySlice. Given a slice of
// bytes representing a flattened slice of pubkeys returns the original slice
// of pubkeys.
func AssemblePubKeySlice(flatten []byte) ([][33]byte, error) {
// Sanity check that this is a valid encoded slice.
if len(flatten)%33 != 0 {
return nil, fmt.Errorf("invalid length for a [][33]byte "+
"encoded slice: %v", len(flatten))
}
numOfPubKeys := len(flatten) / 33
pubKeys := make([][33]byte, 0, numOfPubKeys)
for i := 0; i < numOfPubKeys; i++ {
var pubKey [33]byte
copy(pubKey[:], flatten[i*33:(i+1)*33])
pubKeys = append(pubKeys, pubKey)
}
return pubKeys, nil
}