func (m *Manager) HasPendingBatch() bool {
	return atomic.LoadUint32(&m.hasPendingBatch) == 1
}
HasPendingBatch returns whether a pending batch is currently being processed.
package order
import (
	"context"
	"fmt"
	"net"
	"strconv"
	"strings"
	"sync"
	"sync/atomic"
	"time"
	"github.com/btcsuite/btcutil"
	"github.com/lightninglabs/lndclient"
	"github.com/lightninglabs/pool/account"
	"github.com/lightninglabs/pool/sidecar"
	"github.com/lightninglabs/pool/terms"
	"github.com/lightningnetwork/lnd/keychain"
	"github.com/lightningnetwork/lnd/lnwallet/chainfee"
	"github.com/lightningnetwork/lnd/tor"
)
const (
	defaultLndTimeout = time.Second * 30
	MinimumOrderDurationBlocks = 144
)
var (
	ErrVersionMismatch = fmt.Errorf("version %d mismatches server version",
		CurrentBatchVersion)
)
type ManagerConfig struct {
	Store Store
	AcctStore account.Store
	Lightning lndclient.LightningClient
	Wallet lndclient.WalletKitClient
	Signer lndclient.SignerClient
}
type Manager struct {
	hasPendingBatch uint32
	isStarted       uint32
	started sync.Once
	stopped sync.Once
	cfg ManagerConfig
	wg   sync.WaitGroup
	quit chan struct{}
	ourNodeInfo *lndclient.Info
	batchVerifier BatchVerifier
	batchSigner   BatchSigner
	batchStorer   BatchStorer
	pendingBatch  *Batch
}
func NewManager(cfg *ManagerConfig) *Manager {
	return &Manager{
		cfg:  *cfg,
		quit: make(chan struct{}),
	}
}
func (m *Manager) Start() error {
	if atomic.LoadUint32(&m.isStarted) == 1 {
		return fmt.Errorf("manager can only be started once")
	}
	var err error
	m.started.Do(func() {
		ctxt, cancel := context.WithTimeout(
			context.Background(), defaultLndTimeout,
		)
		defer cancel()
		m.ourNodeInfo, err = m.cfg.Lightning.GetInfo(ctxt)
		if err != nil {
			return
		}
		m.batchVerifier = &batchVerifier{
			orderStore:    m.cfg.Store,
			getAccount:    m.cfg.AcctStore.Account,
			wallet:        m.cfg.Wallet,
			ourNodePubkey: m.ourNodeInfo.IdentityPubkey,
		}
		m.batchSigner = &batchSigner{
			getAccount: m.cfg.AcctStore.Account,
			signer:     m.cfg.Signer,
		}
		m.batchStorer = &batchStorer{
			orderStore: m.cfg.Store,
			getAccount: m.cfg.AcctStore.Account,
		}
		atomic.StoreUint32(&m.isStarted, 1)
	})
	return err
}
func (m *Manager) Stop() {
	m.stopped.Do(func() {
		close(m.quit)
		m.wg.Wait()
	})
}
func (m *Manager) PrepareOrder(ctx context.Context, order Order,
	acct *account.Account,
	terms *terms.AuctioneerTerms) (*ServerOrderParams, error) {
	err := m.validateOrder(order, acct, terms)
	if err != nil {
		return nil, err
	}
	params := &ServerOrderParams{}
	bid, isBid := order.(*Bid)
	isSidecar := isBid && bid.SidecarTicket != nil
	if isSidecar {
		ticket := bid.SidecarTicket
		err := m.validateAndSignTicketForOrder(ctx, ticket, bid, acct)
		if err != nil {
			return nil, fmt.Errorf("error validating sidecar "+
				"ticket: %v", err)
		}
		copy(
			params.NodePubkey[:],
			ticket.Recipient.NodePubKey.SerializeCompressed(),
		)
		copy(
			params.MultiSigKey[:],
			ticket.Recipient.MultiSigPubKey.SerializeCompressed(),
		)
		params.Addrs = nil
	} else {
		nextMultiSigKey, err := m.cfg.Wallet.DeriveNextKey(
			ctx, int32(keychain.KeyFamilyMultiSig),
		)
		if err != nil {
			return nil, fmt.Errorf("unable to derive multi sig "+
				"key: %v", err)
		}
		order.Details().MultiSigKeyLocator = nextMultiSigKey.KeyLocator
		copy(
			params.MultiSigKey[:],
			nextMultiSigKey.PubKey.SerializeCompressed(),
		)
		info, err := m.cfg.Lightning.GetInfo(ctx)
		if err != nil {
			return nil, fmt.Errorf("unable to get local node "+
				"info: %v", err)
		}
		params.NodePubkey = info.IdentityPubkey
		params.Addrs, err = parseNodeUris(info.Uris)
		if err != nil {
			return nil, fmt.Errorf("unable to parse node uris: %v",
				err)
		}
	}
	if len(params.Addrs) == 0 && order.Type() == TypeAsk {
		return nil, fmt.Errorf("the lnd node must " +
			"be reachable on clearnet to negotiate channel " +
			"ask order")
	}
	digest, err := order.Digest()
	if err != nil {
		return nil, fmt.Errorf("could not digest "+
			"order: %v", err)
	}
	params.RawSig, err = m.cfg.Signer.SignMessage(
		ctx, digest[:], acct.TraderKey.KeyLocator,
	)
	if err != nil {
		return nil, fmt.Errorf("unable to sign "+
			"order: %v", err)
	}
	err = m.cfg.Store.SubmitOrder(order)
	if err != nil {
		return nil, fmt.Errorf("unable to store "+
			"order: %v", err)
	}
	return params, nil
}
func (m *Manager) validateOrder(order Order, acct *account.Account,
	terms *terms.AuctioneerTerms) error {
	duration := order.Details().LeaseDuration
	_, ok := terms.LeaseDurationBuckets[duration]
	if !ok {
		return fmt.Errorf("invalid lease duration, must be one of %v",
			terms.LeaseDurationBuckets)
	}
	if order.Details().MaxBatchFeeRate < chainfee.FeePerKwFloor {
		return fmt.Errorf("invalid max batch fee rate %v, must be "+
			"greater than %v", order.Details().MaxBatchFeeRate,
			chainfee.FeePerKwFloor)
	}
	bid, isBid := order.(*Bid)
	if isBid && bid.SelfChanBalance > 0 {
		if err := bid.ValidateSelfChanBalance(); err != nil {
			return err
		}
	}
	dbOrders, err := m.cfg.Store.GetOrders()
	if err != nil {
		return err
	}
	var acctKey [33]byte
	copy(acctKey[:], acct.TraderKey.PubKey.SerializeCompressed())
	feeSchedule := terms.FeeSchedule()
	reserved := order.ReservedValue(feeSchedule)
	for _, o := range dbOrders {
		if o.Details().AcctKey != acctKey {
			continue
		}
		reserved += o.ReservedValue(feeSchedule)
	}
	if acct.Value < reserved {
		return ErrInsufficientBalance
	}
	return nil
}
func (m *Manager) OrderMatchValidate(batch *Batch) error {
	err := m.batchVerifier.Verify(batch)
	if err != nil {
		return fmt.Errorf("error validating batch: %w", err)
	}
	m.pendingBatch = batch
	atomic.StoreUint32(&m.hasPendingBatch, 1)
	return nil
}