diff --git a/account/interface.go b/account/interfaces.go similarity index 80% rename from account/interface.go rename to account/interfaces.go index 6fade5771..7773f5b53 100644 --- a/account/interface.go +++ b/account/interfaces.go @@ -14,6 +14,7 @@ import ( "github.com/lightninglabs/lndclient" "github.com/lightninglabs/pool/poolscript" "github.com/lightninglabs/pool/terms" + "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwallet" @@ -518,3 +519,91 @@ func (o OutputsWithImplicitFee) CloseOutputs(accountValue btcutil.Amount, return o, nil } + +// Manager is the interface a manager implements to deal with the accounts. +type Manager interface { + // Start resumes all account on-chain operation after a restart. + Start() error + + // Stop safely stops any ongoing operations within the Manager. + Stop() + + // QuoteAccount returns the expected fee rate and total miner fee to send to an + // account funding output with the given confTarget. + QuoteAccount(ctx context.Context, value btcutil.Amount, + confTarget uint32) (chainfee.SatPerKWeight, btcutil.Amount, error) + + // InitAccount handles a request to create a new account with the provided + // parameters. + InitAccount(ctx context.Context, value btcutil.Amount, + feeRate chainfee.SatPerKWeight, expiry, + bestHeight uint32) (*Account, error) + + // WatchMatchedAccounts resumes accounts that were just matched in a batch and + // are expecting the batch transaction to confirm as their next account output. + // This will cancel all previous spend and conf watchers of all accounts + // involved in the batch. + WatchMatchedAccounts(ctx context.Context, + matchedAccounts []*btcec.PublicKey) error + + // HandleAccountConf takes the necessary steps after detecting the confirmation + // of an account on-chain. + HandleAccountConf(traderKey *btcec.PublicKey, + confDetails *chainntnfs.TxConfirmation) error + + // HandleAccountSpend handles the different spend paths of an account. If an + // account is spent by the expiration path, it'll always be marked as closed + // thereafter. If it is spent by the cooperative path with the auctioneer, then + // the account will only remain open if the spending transaction recreates the + // account with the expected next account script. Otherwise, it is also marked + // as closed. In case of multiple consecutive batches with the same account, we + // only track the spend of the latest batch, after it confirmed. So the account + // output in the spend transaction should always match our database state if + // it was a cooperative spend. + HandleAccountSpend(traderKey *btcec.PublicKey, + spendDetails *chainntnfs.SpendDetail) error + + // HandleAccountExpiry marks an account as expired within the database. + HandleAccountExpiry(traderKey *btcec.PublicKey, + height uint32) error + + // DepositAccount attempts to deposit funds into the account associated with the + // given trader key such that the new account value is met using inputs sourced + // from the backing lnd node's wallet. If needed, a change output that does back + // to lnd may be added to the deposit transaction. + DepositAccount(ctx context.Context, traderKey *btcec.PublicKey, + depositAmount btcutil.Amount, feeRate chainfee.SatPerKWeight, + bestHeight, expiryHeight uint32) (*Account, *wire.MsgTx, error) + + // WithdrawAccount attempts to withdraw funds from the account associated with + // the given trader key into the provided outputs. + WithdrawAccount(ctx context.Context, traderKey *btcec.PublicKey, + outputs []*wire.TxOut, feeRate chainfee.SatPerKWeight, + bestHeight, expiryHeight uint32) (*Account, *wire.MsgTx, error) + + // RenewAccount updates the expiration of an open/expired account. This will + // always require a signature from the auctioneer, even after the account has + // expired, to ensure the auctioneer is aware the account is being renewed. + RenewAccount(ctx context.Context, traderKey *btcec.PublicKey, + newExpiry uint32, feeRate chainfee.SatPerKWeight, + bestHeight uint32) (*Account, *wire.MsgTx, error) + + // BumpAccountFee attempts to bump the fee of an account's most recent + // transaction. This is done by locating an eligible output for lnd to CPFP, + // otherwise the fee bump will not succeed. Further invocations of this call for + // the same account will result in the child being replaced by the higher fee + // transaction (RBF). + BumpAccountFee(ctx context.Context, traderKey *btcec.PublicKey, + newFeeRate chainfee.SatPerKWeight) error + + // CloseAccount attempts to close the account associated with the given trader + // key. Closing the account requires a signature of the auctioneer if the + // account has not yet expired. The account funds are swept according to the + // provided fee expression. + CloseAccount(ctx context.Context, traderKey *btcec.PublicKey, + feeExpr FeeExpr, bestHeight uint32) (*wire.MsgTx, error) + + // RecoverAccount re-introduces a recovered account into the database and starts + // all watchers necessary depending on the account's state. + RecoverAccount(ctx context.Context, account *Account) error +} diff --git a/account/interface_test.go b/account/interfaces_test.go similarity index 100% rename from account/interface_test.go rename to account/interfaces_test.go diff --git a/account/manager.go b/account/manager.go index bfd12565f..671ccd328 100644 --- a/account/manager.go +++ b/account/manager.go @@ -123,7 +123,7 @@ type ManagerConfig struct { } // Manager is responsible for the management of accounts on-chain. -type Manager struct { +type manager struct { started sync.Once stopped sync.Once @@ -146,9 +146,12 @@ type Manager struct { quit chan struct{} } +// Compile time assertion that manager implements the Manager interface. +var _ Manager = (*manager)(nil) + // NewManager instantiates a new Manager backed by the given config. -func NewManager(cfg *ManagerConfig) *Manager { - m := &Manager{ +func NewManager(cfg *ManagerConfig) *manager { // nolint:golint + m := &manager{ cfg: *cfg, quit: make(chan struct{}), } @@ -163,7 +166,7 @@ func NewManager(cfg *ManagerConfig) *Manager { } // Start resumes all account on-chain operation after a restart. -func (m *Manager) Start() error { +func (m *manager) Start() error { var err error m.started.Do(func() { err = m.start() @@ -172,7 +175,7 @@ func (m *Manager) Start() error { } // start resumes all account on-chain operation after a restart. -func (m *Manager) start() error { +func (m *manager) start() error { ctx := context.Background() // We'll start by resuming all of our accounts. This requires the @@ -226,7 +229,7 @@ func (m *Manager) start() error { } // Stop safely stops any ongoing operations within the Manager. -func (m *Manager) Stop() { +func (m *manager) Stop() { m.stopped.Do(func() { m.watcherCtrl.Stop() @@ -237,7 +240,7 @@ func (m *Manager) Stop() { // QuoteAccount returns the expected fee rate and total miner fee to send to an // account funding output with the given confTarget. -func (m *Manager) QuoteAccount(ctx context.Context, value btcutil.Amount, +func (m *manager) QuoteAccount(ctx context.Context, value btcutil.Amount, confTarget uint32) (chainfee.SatPerKWeight, btcutil.Amount, error) { // First, make sure we have a valid amount to create the account. We @@ -275,7 +278,7 @@ func (m *Manager) QuoteAccount(ctx context.Context, value btcutil.Amount, // InitAccount handles a request to create a new account with the provided // parameters. -func (m *Manager) InitAccount(ctx context.Context, value btcutil.Amount, +func (m *manager) InitAccount(ctx context.Context, value btcutil.Amount, feeRate chainfee.SatPerKWeight, expiry, bestHeight uint32) (*Account, error) { @@ -361,7 +364,7 @@ func (m *Manager) InitAccount(ctx context.Context, value btcutil.Amount, // are expecting the batch transaction to confirm as their next account output. // This will cancel all previous spend and conf watchers of all accounts // involved in the batch. -func (m *Manager) WatchMatchedAccounts(ctx context.Context, +func (m *manager) WatchMatchedAccounts(ctx context.Context, matchedAccounts []*btcec.PublicKey) error { for _, matchedAccount := range matchedAccounts { @@ -402,7 +405,7 @@ func (m *Manager) WatchMatchedAccounts(ctx context.Context, // maybeBroadcastTx attempts to broadcast the transaction only if all of its // inputs have been signed for. -func (m *Manager) maybeBroadcastTx(ctx context.Context, tx *wire.MsgTx, +func (m *manager) maybeBroadcastTx(ctx context.Context, tx *wire.MsgTx, label string) error { // If any of the transaction inputs aren't signed, don't broadcast. @@ -418,7 +421,7 @@ func (m *Manager) maybeBroadcastTx(ctx context.Context, tx *wire.MsgTx, // verifyAccountSigner ensures that we are able to recreate the account // secret for active accounts. That means that the LND signerClient did // not change and we are able to generate valid signatures for this account. -func (m *Manager) verifyAccountSigner(ctx context.Context, +func (m *manager) verifyAccountSigner(ctx context.Context, account *Account) error { // The secret was based on both base keys, the trader and auctioneer's. @@ -442,7 +445,7 @@ func (m *Manager) verifyAccountSigner(ctx context.Context, // resumeAccount performs different operations based on the account's state. // This method serves as a way to consolidate the logic of resuming accounts on // startup and during normal operation. -func (m *Manager) resumeAccount(ctx context.Context, account *Account, // nolint +func (m *manager) resumeAccount(ctx context.Context, account *Account, // nolint onRestart bool, onRecovery bool, feeRate chainfee.SatPerKWeight) error { accountOutput, err := account.Output() @@ -767,7 +770,7 @@ func (m *Manager) resumeAccount(ctx context.Context, account *Account, // nolint // locateTxByOutput locates a transaction from the Manager's TxSource by one of // its outputs. If a transaction is not found containing the output, then // errTxNotFound is returned. -func (m *Manager) locateTxByOutput(ctx context.Context, +func (m *manager) locateTxByOutput(ctx context.Context, output *wire.TxOut, fullTx *wire.MsgTx) (*wire.MsgTx, error) { // We now store the full raw transaction of the last modification. We @@ -802,7 +805,7 @@ func (m *Manager) locateTxByOutput(ctx context.Context, // locateTxByHash locates a transaction from the Manager's TxSource by its hash. // If the transaction is not found, then errTxNotFound is returned. -func (m *Manager) locateTxByHash(ctx context.Context, +func (m *manager) locateTxByHash(ctx context.Context, hash chainhash.Hash) (*wire.MsgTx, error) { // Get all transactions, starting from block 0 and including unconfirmed @@ -823,7 +826,7 @@ func (m *Manager) locateTxByHash(ctx context.Context, // handleStateOpen performs the necessary operations for accounts found in // StateOpen. -func (m *Manager) handleStateOpen(ctx context.Context, account *Account) error { +func (m *manager) handleStateOpen(ctx context.Context, account *Account) error { var traderKey [33]byte copy(traderKey[:], account.TraderKey.PubKey.SerializeCompressed()) @@ -864,7 +867,7 @@ func (m *Manager) handleStateOpen(ctx context.Context, account *Account) error { // HandleAccountConf takes the necessary steps after detecting the confirmation // of an account on-chain. -func (m *Manager) HandleAccountConf(traderKey *btcec.PublicKey, +func (m *manager) HandleAccountConf(traderKey *btcec.PublicKey, confDetails *chainntnfs.TxConfirmation) error { account, err := m.cfg.Store.Account(traderKey) @@ -906,7 +909,7 @@ func (m *Manager) HandleAccountConf(traderKey *btcec.PublicKey, return m.handleStateOpen(context.Background(), account) } -// handleAccountSpend handles the different spend paths of an account. If an +// HandleAccountSpend handles the different spend paths of an account. If an // account is spent by the expiration path, it'll always be marked as closed // thereafter. If it is spent by the cooperative path with the auctioneer, then // the account will only remain open if the spending transaction recreates the @@ -915,7 +918,7 @@ func (m *Manager) HandleAccountConf(traderKey *btcec.PublicKey, // only track the spend of the latest batch, after it confirmed. So the account // output in the spend transaction should always match our database state if // it was a cooperative spend. -func (m *Manager) HandleAccountSpend(traderKey *btcec.PublicKey, +func (m *manager) HandleAccountSpend(traderKey *btcec.PublicKey, spendDetails *chainntnfs.SpendDetail) error { account, err := m.cfg.Store.Account(traderKey) @@ -1011,8 +1014,8 @@ func (m *Manager) HandleAccountSpend(traderKey *btcec.PublicKey, ) } -// handleAccountExpiry marks an account as expired within the database. -func (m *Manager) HandleAccountExpiry(traderKey *btcec.PublicKey, +// HandleAccountExpiry marks an account as expired within the database. +func (m *manager) HandleAccountExpiry(traderKey *btcec.PublicKey, height uint32) error { account, err := m.cfg.Store.Account(traderKey) @@ -1051,7 +1054,7 @@ func (m *Manager) HandleAccountExpiry(traderKey *btcec.PublicKey, // given trader key such that the new account value is met using inputs sourced // from the backing lnd node's wallet. If needed, a change output that does back // to lnd may be added to the deposit transaction. -func (m *Manager) DepositAccount(ctx context.Context, +func (m *manager) DepositAccount(ctx context.Context, traderKey *btcec.PublicKey, depositAmount btcutil.Amount, feeRate chainfee.SatPerKWeight, bestHeight, expiryHeight uint32) (*Account, *wire.MsgTx, error) { @@ -1133,7 +1136,7 @@ func (m *Manager) DepositAccount(ctx context.Context, // WithdrawAccount attempts to withdraw funds from the account associated with // the given trader key into the provided outputs. -func (m *Manager) WithdrawAccount(ctx context.Context, +func (m *manager) WithdrawAccount(ctx context.Context, traderKey *btcec.PublicKey, outputs []*wire.TxOut, feeRate chainfee.SatPerKWeight, bestHeight, expiryHeight uint32) (*Account, *wire.MsgTx, error) { @@ -1196,7 +1199,7 @@ func (m *Manager) WithdrawAccount(ctx context.Context, // RenewAccount updates the expiration of an open/expired account. This will // always require a signature from the auctioneer, even after the account has // expired, to ensure the auctioneer is aware the account is being renewed. -func (m *Manager) RenewAccount(ctx context.Context, +func (m *manager) RenewAccount(ctx context.Context, traderKey *btcec.PublicKey, newExpiry uint32, feeRate chainfee.SatPerKWeight, bestHeight uint32) (*Account, *wire.MsgTx, error) { @@ -1259,7 +1262,7 @@ func (m *Manager) RenewAccount(ctx context.Context, // otherwise the fee bump will not succeed. Further invocations of this call for // the same account will result in the child being replaced by the higher fee // transaction (RBF). -func (m *Manager) BumpAccountFee(ctx context.Context, +func (m *manager) BumpAccountFee(ctx context.Context, traderKey *btcec.PublicKey, newFeeRate chainfee.SatPerKWeight) error { account, err := m.cfg.Store.Account(traderKey) @@ -1317,7 +1320,7 @@ func (m *Manager) BumpAccountFee(ctx context.Context, // key. Closing the account requires a signature of the auctioneer if the // account has not yet expired. The account funds are swept according to the // provided fee expression. -func (m *Manager) CloseAccount(ctx context.Context, traderKey *btcec.PublicKey, +func (m *manager) CloseAccount(ctx context.Context, traderKey *btcec.PublicKey, feeExpr FeeExpr, bestHeight uint32) (*wire.MsgTx, error) { account, err := m.cfg.Store.Account(traderKey) @@ -1379,7 +1382,7 @@ func (m *Manager) CloseAccount(ctx context.Context, traderKey *btcec.PublicKey, // on-chain. These operations are performed in this order to ensure trader are // able to resume the spend of an account upon restarts if they happen to // shutdown mid-process. -func (m *Manager) spendAccount(ctx context.Context, account *Account, +func (m *manager) spendAccount(ctx context.Context, account *Account, inputs []chanfunding.Coin, outputs []*wire.TxOut, witnessType witnessType, modifiers []Modifier, isClose bool, bestHeight uint32) (*Account, *spendPackage, error) { @@ -1475,7 +1478,7 @@ func (m *Manager) spendAccount(ctx context.Context, account *Account, // RecoverAccount re-introduces a recovered account into the database and starts // all watchers necessary depending on the account's state. -func (m *Manager) RecoverAccount(ctx context.Context, account *Account) error { +func (m *manager) RecoverAccount(ctx context.Context, account *Account) error { if account.TraderKey == nil || account.TraderKey.PubKey == nil { return fmt.Errorf("account is missing trader key") } @@ -1517,7 +1520,7 @@ func determineWitnessType(account *Account, bestHeight uint32) witnessType { // spendAccountExpiry creates the closing transaction of an account based on the // expiration script path and signs it. bestHeight is used as the lock time of // the transaction in order to satisfy the output's CHECKLOCKTIMEVERIFY. -func (m *Manager) spendAccountExpiry(ctx context.Context, account *Account, +func (m *manager) spendAccountExpiry(ctx context.Context, account *Account, outputs []*wire.TxOut, bestHeight uint32) (*spendPackage, error) { spendPkg, err := m.createSpendTx(ctx, account, nil, outputs, bestHeight) @@ -1535,7 +1538,7 @@ func (m *Manager) spendAccountExpiry(ctx context.Context, account *Account, // constructMultiSigWitness requests a signature from the auctioneer for the // given spending transaction of an account and returns the fully constructed // witness to spend the account input. -func (m *Manager) constructMultiSigWitness(ctx context.Context, +func (m *manager) constructMultiSigWitness(ctx context.Context, account *Account, spendPkg *spendPackage, modifiers []Modifier, isClose bool) (wire.TxWitness, error) { @@ -1582,7 +1585,7 @@ func (m *Manager) constructMultiSigWitness(ctx context.Context, // If the spending transaction takes the expiration path, bestHeight is used as // the lock time of the transaction, otherwise it is 0. The transaction has its // inputs and outputs sorted according to BIP-69. -func (m *Manager) createSpendTx(ctx context.Context, account *Account, +func (m *manager) createSpendTx(ctx context.Context, account *Account, inputs []chanfunding.Coin, outputs []*wire.TxOut, bestHeight uint32) (*spendPackage, error) { @@ -1745,7 +1748,7 @@ func valueAfterAccountUpdate(account *Account, outputs []*wire.TxOut, // the inputs is also provided to use when coming across an unexpected failure. // If needed, a change output from the backing lnd node's wallet may be returned // as well. -func (m *Manager) inputsForDeposit(ctx context.Context, +func (m *manager) inputsForDeposit(ctx context.Context, depositAmount btcutil.Amount, witnessType witnessType, feeRate chainfee.SatPerKWeight) ([]chanfunding.Coin, func(), *wire.TxOut, error) { @@ -1922,7 +1925,7 @@ func locateAccountInput(tx *wire.MsgTx, account *Account) (int, error) { } // signInput signs a P2WKH or NP2WKH input of a transaction. -func (m *Manager) signInput(ctx context.Context, tx *wire.MsgTx, +func (m *manager) signInput(ctx context.Context, tx *wire.MsgTx, in chanfunding.Coin, idx int, sigHashType txscript.SigHashType) (*input.Script, error) { @@ -1947,7 +1950,7 @@ func (m *Manager) signInput(ctx context.Context, tx *wire.MsgTx, // signAccountInput signs the account input in the spending transaction of an // account. If the account is being spent with cooperation of the auctioneer, // their signature will be required as well. -func (m *Manager) signAccountInput(ctx context.Context, tx *wire.MsgTx, +func (m *manager) signAccountInput(ctx context.Context, tx *wire.MsgTx, account *Account, idx int, sigHashType txscript.SigHashType) ([]byte, []byte, error) { diff --git a/account/manager_test.go b/account/manager_test.go index 6874319a3..9e92bbec8 100644 --- a/account/manager_test.go +++ b/account/manager_test.go @@ -48,7 +48,7 @@ type testHarness struct { notifier *mockChainNotifier wallet *mockWallet auctioneer *mockAuctioneer - manager *Manager + manager Manager } func newTestHarness(t *testing.T) *testHarness { @@ -458,15 +458,20 @@ func (h *testHarness) restartManager() { h.manager.Stop() + mgr, ok := h.manager.(*manager) + if !ok { + h.t.Fatalf("unable to assert account manager type") + } + auctioneer := newMockAuctioneer() h.auctioneer = auctioneer h.manager = NewManager(&ManagerConfig{ - Store: h.manager.cfg.Store, + Store: mgr.cfg.Store, Auctioneer: auctioneer, - Wallet: h.manager.cfg.Wallet, - Signer: h.manager.cfg.Signer, - ChainNotifier: h.manager.cfg.ChainNotifier, - TxSource: h.manager.cfg.TxSource, + Wallet: mgr.cfg.Wallet, + Signer: mgr.cfg.Signer, + ChainNotifier: mgr.cfg.ChainNotifier, + TxSource: mgr.cfg.TxSource, }) if err := h.manager.Start(); err != nil { diff --git a/account/mock_interfaces.go b/account/mock_interfaces.go new file mode 100644 index 000000000..5e60e00f9 --- /dev/null +++ b/account/mock_interfaces.go @@ -0,0 +1,587 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: account/interfaces.go + +// Package account is a generated GoMock package. +package account + +import ( + context "context" + reflect "reflect" + + btcec "github.com/btcsuite/btcd/btcec" + wire "github.com/btcsuite/btcd/wire" + btcutil "github.com/btcsuite/btcutil" + wtxmgr "github.com/btcsuite/btcwallet/wtxmgr" + gomock "github.com/golang/mock/gomock" + lndclient "github.com/lightninglabs/lndclient" + terms "github.com/lightninglabs/pool/terms" + chainntnfs "github.com/lightningnetwork/lnd/chainntnfs" + keychain "github.com/lightningnetwork/lnd/keychain" + chainfee "github.com/lightningnetwork/lnd/lnwallet/chainfee" +) + +// MockStore is a mock of Store interface. +type MockStore struct { + ctrl *gomock.Controller + recorder *MockStoreMockRecorder +} + +// MockStoreMockRecorder is the mock recorder for MockStore. +type MockStoreMockRecorder struct { + mock *MockStore +} + +// NewMockStore creates a new mock instance. +func NewMockStore(ctrl *gomock.Controller) *MockStore { + mock := &MockStore{ctrl: ctrl} + mock.recorder = &MockStoreMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockStore) EXPECT() *MockStoreMockRecorder { + return m.recorder +} + +// Account mocks base method. +func (m *MockStore) Account(arg0 *btcec.PublicKey) (*Account, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Account", arg0) + ret0, _ := ret[0].(*Account) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Account indicates an expected call of Account. +func (mr *MockStoreMockRecorder) Account(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Account", reflect.TypeOf((*MockStore)(nil).Account), arg0) +} + +// Accounts mocks base method. +func (m *MockStore) Accounts() ([]*Account, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Accounts") + ret0, _ := ret[0].([]*Account) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Accounts indicates an expected call of Accounts. +func (mr *MockStoreMockRecorder) Accounts() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Accounts", reflect.TypeOf((*MockStore)(nil).Accounts)) +} + +// AddAccount mocks base method. +func (m *MockStore) AddAccount(arg0 *Account) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddAccount", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddAccount indicates an expected call of AddAccount. +func (mr *MockStoreMockRecorder) AddAccount(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddAccount", reflect.TypeOf((*MockStore)(nil).AddAccount), arg0) +} + +// LockID mocks base method. +func (m *MockStore) LockID() (wtxmgr.LockID, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LockID") + ret0, _ := ret[0].(wtxmgr.LockID) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// LockID indicates an expected call of LockID. +func (mr *MockStoreMockRecorder) LockID() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LockID", reflect.TypeOf((*MockStore)(nil).LockID)) +} + +// MarkBatchComplete mocks base method. +func (m *MockStore) MarkBatchComplete() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MarkBatchComplete") + ret0, _ := ret[0].(error) + return ret0 +} + +// MarkBatchComplete indicates an expected call of MarkBatchComplete. +func (mr *MockStoreMockRecorder) MarkBatchComplete() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarkBatchComplete", reflect.TypeOf((*MockStore)(nil).MarkBatchComplete)) +} + +// PendingBatch mocks base method. +func (m *MockStore) PendingBatch() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PendingBatch") + ret0, _ := ret[0].(error) + return ret0 +} + +// PendingBatch indicates an expected call of PendingBatch. +func (mr *MockStoreMockRecorder) PendingBatch() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PendingBatch", reflect.TypeOf((*MockStore)(nil).PendingBatch)) +} + +// UpdateAccount mocks base method. +func (m *MockStore) UpdateAccount(arg0 *Account, arg1 ...Modifier) error { + m.ctrl.T.Helper() + varargs := []interface{}{arg0} + for _, a := range arg1 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "UpdateAccount", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateAccount indicates an expected call of UpdateAccount. +func (mr *MockStoreMockRecorder) UpdateAccount(arg0 interface{}, arg1 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0}, arg1...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateAccount", reflect.TypeOf((*MockStore)(nil).UpdateAccount), varargs...) +} + +// MockAuctioneer is a mock of Auctioneer interface. +type MockAuctioneer struct { + ctrl *gomock.Controller + recorder *MockAuctioneerMockRecorder +} + +// MockAuctioneerMockRecorder is the mock recorder for MockAuctioneer. +type MockAuctioneerMockRecorder struct { + mock *MockAuctioneer +} + +// NewMockAuctioneer creates a new mock instance. +func NewMockAuctioneer(ctrl *gomock.Controller) *MockAuctioneer { + mock := &MockAuctioneer{ctrl: ctrl} + mock.recorder = &MockAuctioneerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockAuctioneer) EXPECT() *MockAuctioneerMockRecorder { + return m.recorder +} + +// InitAccount mocks base method. +func (m *MockAuctioneer) InitAccount(arg0 context.Context, arg1 *Account) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "InitAccount", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// InitAccount indicates an expected call of InitAccount. +func (mr *MockAuctioneerMockRecorder) InitAccount(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InitAccount", reflect.TypeOf((*MockAuctioneer)(nil).InitAccount), arg0, arg1) +} + +// ModifyAccount mocks base method. +func (m *MockAuctioneer) ModifyAccount(arg0 context.Context, arg1 *Account, arg2 []*wire.TxIn, arg3 []*wire.TxOut, arg4 []Modifier) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ModifyAccount", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ModifyAccount indicates an expected call of ModifyAccount. +func (mr *MockAuctioneerMockRecorder) ModifyAccount(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ModifyAccount", reflect.TypeOf((*MockAuctioneer)(nil).ModifyAccount), arg0, arg1, arg2, arg3, arg4) +} + +// ReserveAccount mocks base method. +func (m *MockAuctioneer) ReserveAccount(arg0 context.Context, arg1 btcutil.Amount, arg2 uint32, arg3 *btcec.PublicKey) (*Reservation, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ReserveAccount", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*Reservation) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ReserveAccount indicates an expected call of ReserveAccount. +func (mr *MockAuctioneerMockRecorder) ReserveAccount(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReserveAccount", reflect.TypeOf((*MockAuctioneer)(nil).ReserveAccount), arg0, arg1, arg2, arg3) +} + +// StartAccountSubscription mocks base method. +func (m *MockAuctioneer) StartAccountSubscription(arg0 context.Context, arg1 *keychain.KeyDescriptor) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StartAccountSubscription", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// StartAccountSubscription indicates an expected call of StartAccountSubscription. +func (mr *MockAuctioneerMockRecorder) StartAccountSubscription(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StartAccountSubscription", reflect.TypeOf((*MockAuctioneer)(nil).StartAccountSubscription), arg0, arg1) +} + +// Terms mocks base method. +func (m *MockAuctioneer) Terms(ctx context.Context) (*terms.AuctioneerTerms, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Terms", ctx) + ret0, _ := ret[0].(*terms.AuctioneerTerms) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Terms indicates an expected call of Terms. +func (mr *MockAuctioneerMockRecorder) Terms(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Terms", reflect.TypeOf((*MockAuctioneer)(nil).Terms), ctx) +} + +// MockTxSource is a mock of TxSource interface. +type MockTxSource struct { + ctrl *gomock.Controller + recorder *MockTxSourceMockRecorder +} + +// MockTxSourceMockRecorder is the mock recorder for MockTxSource. +type MockTxSourceMockRecorder struct { + mock *MockTxSource +} + +// NewMockTxSource creates a new mock instance. +func NewMockTxSource(ctrl *gomock.Controller) *MockTxSource { + mock := &MockTxSource{ctrl: ctrl} + mock.recorder = &MockTxSourceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockTxSource) EXPECT() *MockTxSourceMockRecorder { + return m.recorder +} + +// ListTransactions mocks base method. +func (m *MockTxSource) ListTransactions(ctx context.Context, startHeight, endHeight int32) ([]lndclient.Transaction, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListTransactions", ctx, startHeight, endHeight) + ret0, _ := ret[0].([]lndclient.Transaction) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListTransactions indicates an expected call of ListTransactions. +func (mr *MockTxSourceMockRecorder) ListTransactions(ctx, startHeight, endHeight interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListTransactions", reflect.TypeOf((*MockTxSource)(nil).ListTransactions), ctx, startHeight, endHeight) +} + +// MockTxFeeEstimator is a mock of TxFeeEstimator interface. +type MockTxFeeEstimator struct { + ctrl *gomock.Controller + recorder *MockTxFeeEstimatorMockRecorder +} + +// MockTxFeeEstimatorMockRecorder is the mock recorder for MockTxFeeEstimator. +type MockTxFeeEstimatorMockRecorder struct { + mock *MockTxFeeEstimator +} + +// NewMockTxFeeEstimator creates a new mock instance. +func NewMockTxFeeEstimator(ctrl *gomock.Controller) *MockTxFeeEstimator { + mock := &MockTxFeeEstimator{ctrl: ctrl} + mock.recorder = &MockTxFeeEstimatorMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockTxFeeEstimator) EXPECT() *MockTxFeeEstimatorMockRecorder { + return m.recorder +} + +// EstimateFeeToP2WSH mocks base method. +func (m *MockTxFeeEstimator) EstimateFeeToP2WSH(ctx context.Context, amt btcutil.Amount, confTarget int32) (btcutil.Amount, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EstimateFeeToP2WSH", ctx, amt, confTarget) + ret0, _ := ret[0].(btcutil.Amount) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EstimateFeeToP2WSH indicates an expected call of EstimateFeeToP2WSH. +func (mr *MockTxFeeEstimatorMockRecorder) EstimateFeeToP2WSH(ctx, amt, confTarget interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EstimateFeeToP2WSH", reflect.TypeOf((*MockTxFeeEstimator)(nil).EstimateFeeToP2WSH), ctx, amt, confTarget) +} + +// MockFeeExpr is a mock of FeeExpr interface. +type MockFeeExpr struct { + ctrl *gomock.Controller + recorder *MockFeeExprMockRecorder +} + +// MockFeeExprMockRecorder is the mock recorder for MockFeeExpr. +type MockFeeExprMockRecorder struct { + mock *MockFeeExpr +} + +// NewMockFeeExpr creates a new mock instance. +func NewMockFeeExpr(ctrl *gomock.Controller) *MockFeeExpr { + mock := &MockFeeExpr{ctrl: ctrl} + mock.recorder = &MockFeeExprMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockFeeExpr) EXPECT() *MockFeeExprMockRecorder { + return m.recorder +} + +// CloseOutputs mocks base method. +func (m *MockFeeExpr) CloseOutputs(arg0 btcutil.Amount, arg1 witnessType) ([]*wire.TxOut, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CloseOutputs", arg0, arg1) + ret0, _ := ret[0].([]*wire.TxOut) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CloseOutputs indicates an expected call of CloseOutputs. +func (mr *MockFeeExprMockRecorder) CloseOutputs(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CloseOutputs", reflect.TypeOf((*MockFeeExpr)(nil).CloseOutputs), arg0, arg1) +} + +// MockManager is a mock of Manager interface. +type MockManager struct { + ctrl *gomock.Controller + recorder *MockManagerMockRecorder +} + +// MockManagerMockRecorder is the mock recorder for MockManager. +type MockManagerMockRecorder struct { + mock *MockManager +} + +// NewMockManager creates a new mock instance. +func NewMockManager(ctrl *gomock.Controller) *MockManager { + mock := &MockManager{ctrl: ctrl} + mock.recorder = &MockManagerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockManager) EXPECT() *MockManagerMockRecorder { + return m.recorder +} + +// BumpAccountFee mocks base method. +func (m *MockManager) BumpAccountFee(ctx context.Context, traderKey *btcec.PublicKey, newFeeRate chainfee.SatPerKWeight) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BumpAccountFee", ctx, traderKey, newFeeRate) + ret0, _ := ret[0].(error) + return ret0 +} + +// BumpAccountFee indicates an expected call of BumpAccountFee. +func (mr *MockManagerMockRecorder) BumpAccountFee(ctx, traderKey, newFeeRate interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BumpAccountFee", reflect.TypeOf((*MockManager)(nil).BumpAccountFee), ctx, traderKey, newFeeRate) +} + +// CloseAccount mocks base method. +func (m *MockManager) CloseAccount(ctx context.Context, traderKey *btcec.PublicKey, feeExpr FeeExpr, bestHeight uint32) (*wire.MsgTx, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CloseAccount", ctx, traderKey, feeExpr, bestHeight) + ret0, _ := ret[0].(*wire.MsgTx) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CloseAccount indicates an expected call of CloseAccount. +func (mr *MockManagerMockRecorder) CloseAccount(ctx, traderKey, feeExpr, bestHeight interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CloseAccount", reflect.TypeOf((*MockManager)(nil).CloseAccount), ctx, traderKey, feeExpr, bestHeight) +} + +// DepositAccount mocks base method. +func (m *MockManager) DepositAccount(ctx context.Context, traderKey *btcec.PublicKey, depositAmount btcutil.Amount, feeRate chainfee.SatPerKWeight, bestHeight, expiryHeight uint32) (*Account, *wire.MsgTx, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DepositAccount", ctx, traderKey, depositAmount, feeRate, bestHeight, expiryHeight) + ret0, _ := ret[0].(*Account) + ret1, _ := ret[1].(*wire.MsgTx) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// DepositAccount indicates an expected call of DepositAccount. +func (mr *MockManagerMockRecorder) DepositAccount(ctx, traderKey, depositAmount, feeRate, bestHeight, expiryHeight interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DepositAccount", reflect.TypeOf((*MockManager)(nil).DepositAccount), ctx, traderKey, depositAmount, feeRate, bestHeight, expiryHeight) +} + +// HandleAccountConf mocks base method. +func (m *MockManager) HandleAccountConf(traderKey *btcec.PublicKey, confDetails *chainntnfs.TxConfirmation) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HandleAccountConf", traderKey, confDetails) + ret0, _ := ret[0].(error) + return ret0 +} + +// HandleAccountConf indicates an expected call of HandleAccountConf. +func (mr *MockManagerMockRecorder) HandleAccountConf(traderKey, confDetails interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandleAccountConf", reflect.TypeOf((*MockManager)(nil).HandleAccountConf), traderKey, confDetails) +} + +// HandleAccountExpiry mocks base method. +func (m *MockManager) HandleAccountExpiry(traderKey *btcec.PublicKey, height uint32) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HandleAccountExpiry", traderKey, height) + ret0, _ := ret[0].(error) + return ret0 +} + +// HandleAccountExpiry indicates an expected call of HandleAccountExpiry. +func (mr *MockManagerMockRecorder) HandleAccountExpiry(traderKey, height interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandleAccountExpiry", reflect.TypeOf((*MockManager)(nil).HandleAccountExpiry), traderKey, height) +} + +// HandleAccountSpend mocks base method. +func (m *MockManager) HandleAccountSpend(traderKey *btcec.PublicKey, spendDetails *chainntnfs.SpendDetail) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HandleAccountSpend", traderKey, spendDetails) + ret0, _ := ret[0].(error) + return ret0 +} + +// HandleAccountSpend indicates an expected call of HandleAccountSpend. +func (mr *MockManagerMockRecorder) HandleAccountSpend(traderKey, spendDetails interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandleAccountSpend", reflect.TypeOf((*MockManager)(nil).HandleAccountSpend), traderKey, spendDetails) +} + +// InitAccount mocks base method. +func (m *MockManager) InitAccount(ctx context.Context, value btcutil.Amount, feeRate chainfee.SatPerKWeight, expiry, bestHeight uint32) (*Account, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "InitAccount", ctx, value, feeRate, expiry, bestHeight) + ret0, _ := ret[0].(*Account) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// InitAccount indicates an expected call of InitAccount. +func (mr *MockManagerMockRecorder) InitAccount(ctx, value, feeRate, expiry, bestHeight interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InitAccount", reflect.TypeOf((*MockManager)(nil).InitAccount), ctx, value, feeRate, expiry, bestHeight) +} + +// QuoteAccount mocks base method. +func (m *MockManager) QuoteAccount(ctx context.Context, value btcutil.Amount, confTarget uint32) (chainfee.SatPerKWeight, btcutil.Amount, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "QuoteAccount", ctx, value, confTarget) + ret0, _ := ret[0].(chainfee.SatPerKWeight) + ret1, _ := ret[1].(btcutil.Amount) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// QuoteAccount indicates an expected call of QuoteAccount. +func (mr *MockManagerMockRecorder) QuoteAccount(ctx, value, confTarget interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QuoteAccount", reflect.TypeOf((*MockManager)(nil).QuoteAccount), ctx, value, confTarget) +} + +// RecoverAccount mocks base method. +func (m *MockManager) RecoverAccount(ctx context.Context, account *Account) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RecoverAccount", ctx, account) + ret0, _ := ret[0].(error) + return ret0 +} + +// RecoverAccount indicates an expected call of RecoverAccount. +func (mr *MockManagerMockRecorder) RecoverAccount(ctx, account interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecoverAccount", reflect.TypeOf((*MockManager)(nil).RecoverAccount), ctx, account) +} + +// RenewAccount mocks base method. +func (m *MockManager) RenewAccount(ctx context.Context, traderKey *btcec.PublicKey, newExpiry uint32, feeRate chainfee.SatPerKWeight, bestHeight uint32) (*Account, *wire.MsgTx, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RenewAccount", ctx, traderKey, newExpiry, feeRate, bestHeight) + ret0, _ := ret[0].(*Account) + ret1, _ := ret[1].(*wire.MsgTx) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// RenewAccount indicates an expected call of RenewAccount. +func (mr *MockManagerMockRecorder) RenewAccount(ctx, traderKey, newExpiry, feeRate, bestHeight interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RenewAccount", reflect.TypeOf((*MockManager)(nil).RenewAccount), ctx, traderKey, newExpiry, feeRate, bestHeight) +} + +// Start mocks base method. +func (m *MockManager) Start() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Start") + ret0, _ := ret[0].(error) + return ret0 +} + +// Start indicates an expected call of Start. +func (mr *MockManagerMockRecorder) Start() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Start", reflect.TypeOf((*MockManager)(nil).Start)) +} + +// Stop mocks base method. +func (m *MockManager) Stop() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Stop") +} + +// Stop indicates an expected call of Stop. +func (mr *MockManagerMockRecorder) Stop() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stop", reflect.TypeOf((*MockManager)(nil).Stop)) +} + +// WatchMatchedAccounts mocks base method. +func (m *MockManager) WatchMatchedAccounts(ctx context.Context, matchedAccounts []*btcec.PublicKey) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WatchMatchedAccounts", ctx, matchedAccounts) + ret0, _ := ret[0].(error) + return ret0 +} + +// WatchMatchedAccounts indicates an expected call of WatchMatchedAccounts. +func (mr *MockManagerMockRecorder) WatchMatchedAccounts(ctx, matchedAccounts interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WatchMatchedAccounts", reflect.TypeOf((*MockManager)(nil).WatchMatchedAccounts), ctx, matchedAccounts) +} + +// WithdrawAccount mocks base method. +func (m *MockManager) WithdrawAccount(ctx context.Context, traderKey *btcec.PublicKey, outputs []*wire.TxOut, feeRate chainfee.SatPerKWeight, bestHeight, expiryHeight uint32) (*Account, *wire.MsgTx, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WithdrawAccount", ctx, traderKey, outputs, feeRate, bestHeight, expiryHeight) + ret0, _ := ret[0].(*Account) + ret1, _ := ret[1].(*wire.MsgTx) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// WithdrawAccount indicates an expected call of WithdrawAccount. +func (mr *MockManagerMockRecorder) WithdrawAccount(ctx, traderKey, outputs, feeRate, bestHeight, expiryHeight interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WithdrawAccount", reflect.TypeOf((*MockManager)(nil).WithdrawAccount), ctx, traderKey, outputs, feeRate, bestHeight, expiryHeight) +} diff --git a/gen.go b/gen.go index 2f72aa6e5..0915509af 100644 --- a/gen.go +++ b/gen.go @@ -5,6 +5,13 @@ package pool // make mock // +//go:generate mockgen -source=interfaces.go -package=pool -destination=mock_interfaces.go + //go:generate mockgen -source=sidecar/interfaces.go -package=sidecar -destination=sidecar/mock_interfaces.go + //go:generate mockgen -source=internal/test/interfaces.go -package=test -destination=internal/test/mock_interfaces.go + +//go:generate mockgen -source=account/interfaces.go -package=account -destination=account/mock_interfaces.go //go:generate mockgen -source=account/watcher/interfaces.go -package=watcher -destination=account/watcher/mock_interface_test.go + +//go:generate mockgen -source=order/interfaces.go -package=order -destination=order/mock_interfaces.go diff --git a/interfaces.go b/interfaces.go new file mode 100644 index 000000000..4f15c84a5 --- /dev/null +++ b/interfaces.go @@ -0,0 +1,16 @@ +package pool + +import ( + "context" + + "github.com/lightninglabs/pool/account" + "github.com/lightninglabs/pool/poolrpc" +) + +// Marshaler interface to transform internal types to decorated RPC ones. +type Marshaler interface { + // MarshallAccountsWithAvailableBalance returns the RPC representation + // of an account with the account.AvailableBalance value populated. + MarshallAccountsWithAvailableBalance(ctx context.Context, + accounts []*account.Account) ([]*poolrpc.Account, error) +} diff --git a/marshaler.go b/marshaler.go new file mode 100644 index 000000000..9b3a39a58 --- /dev/null +++ b/marshaler.go @@ -0,0 +1,106 @@ +package pool + +import ( + "context" + "fmt" + + "github.com/btcsuite/btcutil" + "github.com/lightninglabs/pool/account" + "github.com/lightninglabs/pool/order" + "github.com/lightninglabs/pool/poolrpc" + "github.com/lightninglabs/pool/terms" +) + +// marshalerConfig contains all of the marshaler's dependencies in order to +// carry out its duties. +type marshalerConfig struct { + // GetOrders returns all orders that are currently known to the store. + GetOrders func() ([]order.Order, error) + + // Terms returns the current dynamic auctioneer terms like max account + // size, max order duration in blocks and the auction fee schedule. + Terms func(ctx context.Context) (*terms.AuctioneerTerms, error) +} + +// marshaler is an internal struct type that implements the Marshaler interface. +type marshaler struct { + cfg *marshalerConfig +} + +// NewMarshaler returns an internal type that implements the Marshaler interface. +func NewMarshaler(cfg *marshalerConfig) *marshaler { // nolint:golint + return &marshaler{ + cfg: cfg, + } +} + +// MarshallAccountsWithAvailableBalance returns the RPC representation of an account +// with the account.AvailableBalance value populated. +func (m *marshaler) MarshallAccountsWithAvailableBalance(ctx context.Context, + accounts []*account.Account) ([]*poolrpc.Account, error) { + + rpcAccounts := make([]*poolrpc.Account, 0, len(accounts)) + for _, acct := range accounts { + rpcAccount, err := MarshallAccount(acct) + if err != nil { + return nil, err + } + rpcAccounts = append(rpcAccounts, rpcAccount) + } + + // For each account, we'll need to compute the available balance, which + // requires us to sum up all the debits from outstanding orders. + orders, err := m.cfg.GetOrders() + if err != nil { + return nil, err + } + + // Get the current fee schedule so we can compute the worst-case + // account debit assuming all our standing orders were matched. + auctionTerms, err := m.cfg.Terms(ctx) + if err != nil { + return nil, fmt.Errorf("unable to query auctioneer terms: %v", + err) + } + + // For each active account, consume the worst-case account delta if the + // order were to be matched. + accountDebits := make(map[[33]byte]btcutil.Amount) + auctionFeeSchedule := auctionTerms.FeeSchedule() + for _, acct := range accounts { + var ( + debitAmt btcutil.Amount + acctKey [33]byte + ) + + copy( + acctKey[:], + acct.TraderKey.PubKey.SerializeCompressed(), + ) + + // We'll make sure to accumulate a distinct sum for each + // outstanding account the user has. + for _, o := range orders { + if o.Details().AcctKey != acctKey { + continue + } + + debitAmt += o.ReservedValue(auctionFeeSchedule) + } + + accountDebits[acctKey] = debitAmt + } + + // Finally, we'll populate the available balance value for each of the + // existing accounts. + for _, rpcAccount := range rpcAccounts { + var acctKey [33]byte + copy(acctKey[:], rpcAccount.TraderKey) + + accountDebit := accountDebits[acctKey] + availableBalance := rpcAccount.Value - uint64(accountDebit) + + rpcAccount.AvailableBalance = availableBalance + } + return rpcAccounts, nil +} diff --git a/mock_interfaces.go b/mock_interfaces.go new file mode 100644 index 000000000..3f915274a --- /dev/null +++ b/mock_interfaces.go @@ -0,0 +1,52 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: interfaces.go + +// Package pool is a generated GoMock package. +package pool + +import ( + context "context" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + account "github.com/lightninglabs/pool/account" + poolrpc "github.com/lightninglabs/pool/poolrpc" +) + +// MockMarshaler is a mock of Marshaler interface. +type MockMarshaler struct { + ctrl *gomock.Controller + recorder *MockMarshalerMockRecorder +} + +// MockMarshalerMockRecorder is the mock recorder for MockMarshaler. +type MockMarshalerMockRecorder struct { + mock *MockMarshaler +} + +// NewMockMarshaler creates a new mock instance. +func NewMockMarshaler(ctrl *gomock.Controller) *MockMarshaler { + mock := &MockMarshaler{ctrl: ctrl} + mock.recorder = &MockMarshalerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockMarshaler) EXPECT() *MockMarshalerMockRecorder { + return m.recorder +} + +// MarshallAccountsWithAvailableBalance mocks base method. +func (m *MockMarshaler) MarshallAccountsWithAvailableBalance(ctx context.Context, accounts []*account.Account) ([]*poolrpc.Account, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MarshallAccountsWithAvailableBalance", ctx, accounts) + ret0, _ := ret[0].([]*poolrpc.Account) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MarshallAccountsWithAvailableBalance indicates an expected call of MarshallAccountsWithAvailableBalance. +func (mr *MockMarshalerMockRecorder) MarshallAccountsWithAvailableBalance(ctx, accounts interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarshallAccountsWithAvailableBalance", reflect.TypeOf((*MockMarshaler)(nil).MarshallAccountsWithAvailableBalance), ctx, accounts) +} diff --git a/order/interface.go b/order/interfaces.go similarity index 93% rename from order/interface.go rename to order/interfaces.go index 46626b430..58b58fbc2 100644 --- a/order/interface.go +++ b/order/interfaces.go @@ -2,6 +2,7 @@ package order import ( "bytes" + "context" "crypto/sha256" "encoding/hex" "errors" @@ -22,6 +23,13 @@ import ( // Nonce is a 32 byte pseudo randomly generated unique order ID. type Nonce [32]byte +// The size of a SHA256 checksum in bytes. +// +// Note: this matches the sha256.Size definition. However, mockgen +// complains about not being able to find the sha256 package. When +// that bug is fixed, we can change back to [sha256.Size]byte. +const hashSize = 32 + // String returns the hex encoded representation of the nonce. func (n Nonce) String() string { return hex.EncodeToString(n[:]) @@ -265,7 +273,7 @@ type Order interface { // Digest returns a deterministic SHA256 hash over the contents of an // order. Deterministic in this context means that if two orders have // the same content, their digest have to be identical as well. - Digest() ([sha256.Size]byte, error) + Digest() ([hashSize]byte, error) // ReservedValue returns the maximum value that could be deducted from // the account if the order is is matched, and therefore has to be @@ -391,10 +399,10 @@ func (a *Ask) Type() Type { // their digest have to be identical as well. // // NOTE: This method is part of the Order interface. -func (a *Ask) Digest() ([sha256.Size]byte, error) { +func (a *Ask) Digest() ([hashSize]byte, error) { var ( msg bytes.Buffer - result [sha256.Size]byte + result [hashSize]byte ) switch a.Kit.Version { case VersionDefault: @@ -590,10 +598,10 @@ func (b *Bid) Type() Type { // their digest have to be identical as well. // // NOTE: This method is part of the Order interface. -func (b *Bid) Digest() ([sha256.Size]byte, error) { +func (b *Bid) Digest() ([hashSize]byte, error) { var ( msg bytes.Buffer - result [sha256.Size]byte + result [hashSize]byte ) switch b.Kit.Version { case VersionDefault: @@ -815,3 +823,38 @@ func PendingChanKey(askNonce, bidNonce Nonce) [32]byte { return pid } + +// Manager is the interface a manager implements to deal with +// the orders. +type Manager interface { + // Start starts all concurrent tasks the manager is responsible for. + Start() error + + // Stop stops all concurrent tasks the manager is responsible for. + Stop() + + // PrepareOrder validates an order, signs it and then stores it locally. + PrepareOrder(ctx context.Context, order Order, acct *account.Account, + terms *terms.AuctioneerTerms) (*ServerOrderParams, error) + + // OrderMatchValidate verifies an incoming batch is sane before accepting it. + OrderMatchValidate(batch *Batch, bestHeight uint32) error + + // HasPendingBatch returns whether a pending batch is currently being processed. + HasPendingBatch() bool + + // PendingBatch returns the current pending batch being validated. + PendingBatch() *Batch + + // BatchSign returns the witness stack of all account inputs in a batch that + // belong to the trader. + BatchSign() (BatchSignature, error) + + // BatchFinalize marks a batch as complete upon receiving the finalize message + // from the auctioneer. + BatchFinalize(batchID BatchID) error + + // OurNodePubkey returns our lnd node's public identity key or an error if the + // manager wasn't fully started yet. + OurNodePubkey() ([33]byte, error) +} diff --git a/order/interface_test.go b/order/interfaces_test.go similarity index 100% rename from order/interface_test.go rename to order/interfaces_test.go diff --git a/order/manager.go b/order/manager.go index e107ceed1..e99670f3f 100644 --- a/order/manager.go +++ b/order/manager.go @@ -68,8 +68,8 @@ type ManagerConfig struct { Signer lndclient.SignerClient } -// Manager is responsible for the management of orders. -type Manager struct { +// manager is responsible for the management of orders. +type manager struct { // NOTE: This must be used atomically. hasPendingBatch uint32 isStarted uint32 @@ -90,16 +90,19 @@ type Manager struct { pendingBatch *Batch } +// Compile time assertion that manager implements the Manager interface. +var _ Manager = (*manager)(nil) + // NewManager instantiates a new Manager backed by the given config. -func NewManager(cfg *ManagerConfig) *Manager { - return &Manager{ +func NewManager(cfg *ManagerConfig) *manager { // nolint:golint + return &manager{ cfg: *cfg, quit: make(chan struct{}), } } // Start starts all concurrent tasks the manager is responsible for. -func (m *Manager) Start() error { +func (m *manager) Start() error { if atomic.LoadUint32(&m.isStarted) == 1 { return fmt.Errorf("manager can only be started once") } @@ -139,7 +142,7 @@ func (m *Manager) Start() error { } // Stop stops all concurrent tasks the manager is responsible for. -func (m *Manager) Stop() { +func (m *manager) Stop() { m.stopped.Do(func() { close(m.quit) m.wg.Wait() @@ -147,7 +150,7 @@ func (m *Manager) Stop() { } // PrepareOrder validates an order, signs it and then stores it locally. -func (m *Manager) PrepareOrder(ctx context.Context, order Order, +func (m *manager) PrepareOrder(ctx context.Context, order Order, acct *account.Account, terms *terms.AuctioneerTerms) (*ServerOrderParams, error) { @@ -258,7 +261,7 @@ func (m *Manager) PrepareOrder(ctx context.Context, order Order, // validateOrder makes sure an order is formally correct and that the associated // account contains enough balance to execute the order. -func (m *Manager) validateOrder(order Order, acct *account.Account, +func (m *manager) validateOrder(order Order, acct *account.Account, terms *terms.AuctioneerTerms) error { duration := order.Details().LeaseDuration @@ -312,7 +315,7 @@ func (m *Manager) validateOrder(order Order, acct *account.Account, } // OrderMatchValidate verifies an incoming batch is sane before accepting it. -func (m *Manager) OrderMatchValidate(batch *Batch, bestHeight uint32) error { +func (m *manager) OrderMatchValidate(batch *Batch, bestHeight uint32) error { // Make sure we have no objection to the current batch. Then store // it in case it ends up being the final version. err := m.batchVerifier.Verify(batch, bestHeight) @@ -330,12 +333,12 @@ func (m *Manager) OrderMatchValidate(batch *Batch, bestHeight uint32) error { } // HasPendingBatch returns whether a pending batch is currently being processed. -func (m *Manager) HasPendingBatch() bool { +func (m *manager) HasPendingBatch() bool { return atomic.LoadUint32(&m.hasPendingBatch) == 1 } // PendingBatch returns the current pending batch being validated. -func (m *Manager) PendingBatch() *Batch { +func (m *manager) PendingBatch() *Batch { return m.pendingBatch } @@ -343,7 +346,7 @@ func (m *Manager) PendingBatch() *Batch { // belong to the trader. Before sending off the signature to the auctioneer, // we'll also persist the batch to disk as pending to ensure we can recover // after a crash. -func (m *Manager) BatchSign() (BatchSignature, error) { +func (m *manager) BatchSign() (BatchSignature, error) { sig, err := m.batchSigner.Sign(m.pendingBatch) if err != nil { return nil, err @@ -359,7 +362,7 @@ func (m *Manager) BatchSign() (BatchSignature, error) { // BatchFinalize marks a batch as complete upon receiving the finalize message // from the auctioneer. -func (m *Manager) BatchFinalize(batchID BatchID) error { +func (m *manager) BatchFinalize(batchID BatchID) error { // Only accept the last batch we verified to make sure we didn't miss // a message somewhere in the process. if batchID != m.pendingBatch.ID { @@ -381,7 +384,7 @@ func (m *Manager) BatchFinalize(batchID BatchID) error { // OurNodePubkey returns our lnd node's public identity key or an error if the // manager wasn't fully started yet. -func (m *Manager) OurNodePubkey() ([33]byte, error) { +func (m *manager) OurNodePubkey() ([33]byte, error) { if atomic.LoadUint32(&m.isStarted) != 1 { return [33]byte{}, fmt.Errorf("manager not started yet") } @@ -394,7 +397,7 @@ func (m *Manager) OurNodePubkey() ([33]byte, error) { // information set. We also check that our node initially offered to lease this // channel by checking the embedded signature. If everything checks out, we add // our signature over the order part to the ticket. -func (m *Manager) validateAndSignTicketForOrder(ctx context.Context, +func (m *manager) validateAndSignTicketForOrder(ctx context.Context, t *sidecar.Ticket, bid *Bid, acct *account.Account) error { if t.State != sidecar.StateRegistered { diff --git a/order/mock_interfaces.go b/order/mock_interfaces.go new file mode 100644 index 000000000..4231577d7 --- /dev/null +++ b/order/mock_interfaces.go @@ -0,0 +1,387 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: order/interfaces.go + +// Package order is a generated GoMock package. +package order + +import ( + context "context" + reflect "reflect" + + btcutil "github.com/btcsuite/btcutil" + gomock "github.com/golang/mock/gomock" + account "github.com/lightninglabs/pool/account" + terms "github.com/lightninglabs/pool/terms" +) + +// MockOrder is a mock of Order interface. +type MockOrder struct { + ctrl *gomock.Controller + recorder *MockOrderMockRecorder +} + +// MockOrderMockRecorder is the mock recorder for MockOrder. +type MockOrderMockRecorder struct { + mock *MockOrder +} + +// NewMockOrder creates a new mock instance. +func NewMockOrder(ctrl *gomock.Controller) *MockOrder { + mock := &MockOrder{ctrl: ctrl} + mock.recorder = &MockOrderMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockOrder) EXPECT() *MockOrderMockRecorder { + return m.recorder +} + +// Details mocks base method. +func (m *MockOrder) Details() *Kit { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Details") + ret0, _ := ret[0].(*Kit) + return ret0 +} + +// Details indicates an expected call of Details. +func (mr *MockOrderMockRecorder) Details() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Details", reflect.TypeOf((*MockOrder)(nil).Details)) +} + +// Digest mocks base method. +func (m *MockOrder) Digest() ([32]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Digest") + ret0, _ := ret[0].([32]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Digest indicates an expected call of Digest. +func (mr *MockOrderMockRecorder) Digest() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Digest", reflect.TypeOf((*MockOrder)(nil).Digest)) +} + +// Nonce mocks base method. +func (m *MockOrder) Nonce() Nonce { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Nonce") + ret0, _ := ret[0].(Nonce) + return ret0 +} + +// Nonce indicates an expected call of Nonce. +func (mr *MockOrderMockRecorder) Nonce() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Nonce", reflect.TypeOf((*MockOrder)(nil).Nonce)) +} + +// ReservedValue mocks base method. +func (m *MockOrder) ReservedValue(feeSchedule terms.FeeSchedule) btcutil.Amount { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ReservedValue", feeSchedule) + ret0, _ := ret[0].(btcutil.Amount) + return ret0 +} + +// ReservedValue indicates an expected call of ReservedValue. +func (mr *MockOrderMockRecorder) ReservedValue(feeSchedule interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReservedValue", reflect.TypeOf((*MockOrder)(nil).ReservedValue), feeSchedule) +} + +// Type mocks base method. +func (m *MockOrder) Type() Type { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Type") + ret0, _ := ret[0].(Type) + return ret0 +} + +// Type indicates an expected call of Type. +func (mr *MockOrderMockRecorder) Type() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Type", reflect.TypeOf((*MockOrder)(nil).Type)) +} + +// MockStore is a mock of Store interface. +type MockStore struct { + ctrl *gomock.Controller + recorder *MockStoreMockRecorder +} + +// MockStoreMockRecorder is the mock recorder for MockStore. +type MockStoreMockRecorder struct { + mock *MockStore +} + +// NewMockStore creates a new mock instance. +func NewMockStore(ctrl *gomock.Controller) *MockStore { + mock := &MockStore{ctrl: ctrl} + mock.recorder = &MockStoreMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockStore) EXPECT() *MockStoreMockRecorder { + return m.recorder +} + +// GetOrder mocks base method. +func (m *MockStore) GetOrder(arg0 Nonce) (Order, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetOrder", arg0) + ret0, _ := ret[0].(Order) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetOrder indicates an expected call of GetOrder. +func (mr *MockStoreMockRecorder) GetOrder(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOrder", reflect.TypeOf((*MockStore)(nil).GetOrder), arg0) +} + +// GetOrders mocks base method. +func (m *MockStore) GetOrders() ([]Order, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetOrders") + ret0, _ := ret[0].([]Order) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetOrders indicates an expected call of GetOrders. +func (mr *MockStoreMockRecorder) GetOrders() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOrders", reflect.TypeOf((*MockStore)(nil).GetOrders)) +} + +// MarkBatchComplete mocks base method. +func (m *MockStore) MarkBatchComplete() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MarkBatchComplete") + ret0, _ := ret[0].(error) + return ret0 +} + +// MarkBatchComplete indicates an expected call of MarkBatchComplete. +func (mr *MockStoreMockRecorder) MarkBatchComplete() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarkBatchComplete", reflect.TypeOf((*MockStore)(nil).MarkBatchComplete)) +} + +// StorePendingBatch mocks base method. +func (m *MockStore) StorePendingBatch(arg0 *Batch, orders []Nonce, orderModifiers [][]Modifier, accounts []*account.Account, accountModifiers [][]account.Modifier) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StorePendingBatch", arg0, orders, orderModifiers, accounts, accountModifiers) + ret0, _ := ret[0].(error) + return ret0 +} + +// StorePendingBatch indicates an expected call of StorePendingBatch. +func (mr *MockStoreMockRecorder) StorePendingBatch(arg0, orders, orderModifiers, accounts, accountModifiers interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StorePendingBatch", reflect.TypeOf((*MockStore)(nil).StorePendingBatch), arg0, orders, orderModifiers, accounts, accountModifiers) +} + +// SubmitOrder mocks base method. +func (m *MockStore) SubmitOrder(arg0 Order) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SubmitOrder", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// SubmitOrder indicates an expected call of SubmitOrder. +func (mr *MockStoreMockRecorder) SubmitOrder(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubmitOrder", reflect.TypeOf((*MockStore)(nil).SubmitOrder), arg0) +} + +// UpdateOrder mocks base method. +func (m *MockStore) UpdateOrder(arg0 Nonce, arg1 ...Modifier) error { + m.ctrl.T.Helper() + varargs := []interface{}{arg0} + for _, a := range arg1 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "UpdateOrder", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateOrder indicates an expected call of UpdateOrder. +func (mr *MockStoreMockRecorder) UpdateOrder(arg0 interface{}, arg1 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0}, arg1...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateOrder", reflect.TypeOf((*MockStore)(nil).UpdateOrder), varargs...) +} + +// UpdateOrders mocks base method. +func (m *MockStore) UpdateOrders(arg0 []Nonce, arg1 [][]Modifier) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateOrders", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateOrders indicates an expected call of UpdateOrders. +func (mr *MockStoreMockRecorder) UpdateOrders(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateOrders", reflect.TypeOf((*MockStore)(nil).UpdateOrders), arg0, arg1) +} + +// MockManager is a mock of Manager interface. +type MockManager struct { + ctrl *gomock.Controller + recorder *MockManagerMockRecorder +} + +// MockManagerMockRecorder is the mock recorder for MockManager. +type MockManagerMockRecorder struct { + mock *MockManager +} + +// NewMockManager creates a new mock instance. +func NewMockManager(ctrl *gomock.Controller) *MockManager { + mock := &MockManager{ctrl: ctrl} + mock.recorder = &MockManagerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockManager) EXPECT() *MockManagerMockRecorder { + return m.recorder +} + +// BatchFinalize mocks base method. +func (m *MockManager) BatchFinalize(batchID BatchID) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BatchFinalize", batchID) + ret0, _ := ret[0].(error) + return ret0 +} + +// BatchFinalize indicates an expected call of BatchFinalize. +func (mr *MockManagerMockRecorder) BatchFinalize(batchID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BatchFinalize", reflect.TypeOf((*MockManager)(nil).BatchFinalize), batchID) +} + +// BatchSign mocks base method. +func (m *MockManager) BatchSign() (BatchSignature, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BatchSign") + ret0, _ := ret[0].(BatchSignature) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// BatchSign indicates an expected call of BatchSign. +func (mr *MockManagerMockRecorder) BatchSign() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BatchSign", reflect.TypeOf((*MockManager)(nil).BatchSign)) +} + +// HasPendingBatch mocks base method. +func (m *MockManager) HasPendingBatch() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HasPendingBatch") + ret0, _ := ret[0].(bool) + return ret0 +} + +// HasPendingBatch indicates an expected call of HasPendingBatch. +func (mr *MockManagerMockRecorder) HasPendingBatch() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasPendingBatch", reflect.TypeOf((*MockManager)(nil).HasPendingBatch)) +} + +// OrderMatchValidate mocks base method. +func (m *MockManager) OrderMatchValidate(batch *Batch, bestHeight uint32) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "OrderMatchValidate", batch, bestHeight) + ret0, _ := ret[0].(error) + return ret0 +} + +// OrderMatchValidate indicates an expected call of OrderMatchValidate. +func (mr *MockManagerMockRecorder) OrderMatchValidate(batch, bestHeight interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OrderMatchValidate", reflect.TypeOf((*MockManager)(nil).OrderMatchValidate), batch, bestHeight) +} + +// OurNodePubkey mocks base method. +func (m *MockManager) OurNodePubkey() ([33]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "OurNodePubkey") + ret0, _ := ret[0].([33]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// OurNodePubkey indicates an expected call of OurNodePubkey. +func (mr *MockManagerMockRecorder) OurNodePubkey() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OurNodePubkey", reflect.TypeOf((*MockManager)(nil).OurNodePubkey)) +} + +// PendingBatch mocks base method. +func (m *MockManager) PendingBatch() *Batch { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PendingBatch") + ret0, _ := ret[0].(*Batch) + return ret0 +} + +// PendingBatch indicates an expected call of PendingBatch. +func (mr *MockManagerMockRecorder) PendingBatch() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PendingBatch", reflect.TypeOf((*MockManager)(nil).PendingBatch)) +} + +// PrepareOrder mocks base method. +func (m *MockManager) PrepareOrder(ctx context.Context, order Order, acct *account.Account, terms *terms.AuctioneerTerms) (*ServerOrderParams, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PrepareOrder", ctx, order, acct, terms) + ret0, _ := ret[0].(*ServerOrderParams) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PrepareOrder indicates an expected call of PrepareOrder. +func (mr *MockManagerMockRecorder) PrepareOrder(ctx, order, acct, terms interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrepareOrder", reflect.TypeOf((*MockManager)(nil).PrepareOrder), ctx, order, acct, terms) +} + +// Start mocks base method. +func (m *MockManager) Start() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Start") + ret0, _ := ret[0].(error) + return ret0 +} + +// Start indicates an expected call of Start. +func (mr *MockManagerMockRecorder) Start() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Start", reflect.TypeOf((*MockManager)(nil).Start)) +} + +// Stop mocks base method. +func (m *MockManager) Stop() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Stop") +} + +// Stop indicates an expected call of Stop. +func (mr *MockManagerMockRecorder) Stop() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stop", reflect.TypeOf((*MockManager)(nil).Stop)) +} diff --git a/rpcserver.go b/rpcserver.go index d4d5e9800..887f2367a 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -62,8 +62,9 @@ type rpcServer struct { lndServices *lndclient.LndServices lndClient lnrpc.LightningClient auctioneer *auctioneer.Client - accountManager *account.Manager - orderManager *order.Manager + accountManager account.Manager + orderManager order.Manager + marshaler Marshaler quit chan struct{} wg sync.WaitGroup @@ -117,6 +118,10 @@ func newRPCServer(server *Server) *rpcServer { Wallet: lndServices.WalletKit, Signer: lndServices.Signer, }), + marshaler: NewMarshaler(&marshalerConfig{ + GetOrders: server.db.GetOrders, + Terms: server.AuctioneerClient.Terms, + }), quit: make(chan struct{}), } } @@ -589,7 +594,7 @@ func (s *rpcServer) ListAccounts(ctx context.Context, validAccounts = append(validAccounts, acct) } - rpcAccounts, err := s.MarshallAccountsWithAvailableBalance( + rpcAccounts, err := s.marshaler.MarshallAccountsWithAvailableBalance( ctx, validAccounts, ) if err != nil { @@ -602,77 +607,6 @@ func (s *rpcServer) ListAccounts(ctx context.Context, }, nil } -// MarshallAccountsWithAvailableBalance returns the RPC representation of an account -// with the account.AvailableBalance value populated. -func (s *rpcServer) MarshallAccountsWithAvailableBalance(ctx context.Context, - accounts []*account.Account) ([]*poolrpc.Account, error) { - - rpcAccounts := make([]*poolrpc.Account, 0, len(accounts)) - for _, acct := range accounts { - rpcAccount, err := MarshallAccount(acct) - if err != nil { - return nil, err - } - rpcAccounts = append(rpcAccounts, rpcAccount) - } - - // For each account, we'll need to compute the available balance, which - // requires us to sum up all the debits from outstanding orders. - orders, err := s.server.db.GetOrders() - if err != nil { - return nil, err - } - - // Get the current fee schedule so we can compute the worst-case - // account debit assuming all our standing orders were matched. - auctionTerms, err := s.auctioneer.Terms(ctx) - if err != nil { - return nil, fmt.Errorf("unable to query auctioneer terms: %v", - err) - } - - // For each active account, consume the worst-case account delta if the - // order were to be matched. - accountDebits := make(map[[33]byte]btcutil.Amount) - auctionFeeSchedule := auctionTerms.FeeSchedule() - for _, acct := range accounts { - var ( - debitAmt btcutil.Amount - acctKey [33]byte - ) - - copy( - acctKey[:], - acct.TraderKey.PubKey.SerializeCompressed(), - ) - - // We'll make sure to accumulate a distinct sum for each - // outstanding account the user has. - for _, o := range orders { - if o.Details().AcctKey != acctKey { - continue - } - - debitAmt += o.ReservedValue(auctionFeeSchedule) - } - - accountDebits[acctKey] = debitAmt - } - - // Finally, we'll populate the available balance value for each of the - // existing accounts. - for _, rpcAccount := range rpcAccounts { - var acctKey [33]byte - copy(acctKey[:], rpcAccount.TraderKey) - - accountDebit := accountDebits[acctKey] - availableBalance := rpcAccount.Value - uint64(accountDebit) - - rpcAccount.AvailableBalance = availableBalance - } - return rpcAccounts, nil -} - // MarshallAccount returns the RPC representation of an account. func MarshallAccount(a *account.Account) (*poolrpc.Account, error) { var rpcState poolrpc.AccountState @@ -770,7 +704,7 @@ func (s *rpcServer) DepositAccount(ctx context.Context, return nil, err } - rpcModifiedAccounts, err := s.MarshallAccountsWithAvailableBalance( + rpcModAccounts, err := s.marshaler.MarshallAccountsWithAvailableBalance( ctx, []*account.Account{modifiedAccount}, ) if err != nil { @@ -779,7 +713,7 @@ func (s *rpcServer) DepositAccount(ctx context.Context, txHash := tx.TxHash() return &poolrpc.DepositAccountResponse{ - Account: rpcModifiedAccounts[0], + Account: rpcModAccounts[0], DepositTxid: txHash[:], }, nil } @@ -837,7 +771,7 @@ func (s *rpcServer) WithdrawAccount(ctx context.Context, return nil, err } - rpcModifiedAccounts, err := s.MarshallAccountsWithAvailableBalance( + rpcModAccounts, err := s.marshaler.MarshallAccountsWithAvailableBalance( ctx, []*account.Account{modifiedAccount}, ) if err != nil { @@ -846,7 +780,7 @@ func (s *rpcServer) WithdrawAccount(ctx context.Context, txHash := tx.TxHash() return &poolrpc.WithdrawAccountResponse{ - Account: rpcModifiedAccounts[0], + Account: rpcModAccounts[0], WithdrawTxid: txHash[:], }, nil } @@ -895,7 +829,7 @@ func (s *rpcServer) RenewAccount(ctx context.Context, return nil, err } - rpcModifiedAccounts, err := s.MarshallAccountsWithAvailableBalance( + rpcModAccounts, err := s.marshaler.MarshallAccountsWithAvailableBalance( ctx, []*account.Account{modifiedAccount}, ) if err != nil { @@ -904,7 +838,7 @@ func (s *rpcServer) RenewAccount(ctx context.Context, txHash := tx.TxHash() return &poolrpc.RenewAccountResponse{ - Account: rpcModifiedAccounts[0], + Account: rpcModAccounts[0], RenewalTxid: txHash[:], }, nil } diff --git a/rpcserver_test.go b/rpcserver_test.go new file mode 100644 index 000000000..0c63173e0 --- /dev/null +++ b/rpcserver_test.go @@ -0,0 +1,172 @@ +package pool + +import ( + "context" + "encoding/hex" + "testing" + "time" + + "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/wire" + gomock "github.com/golang/mock/gomock" + "github.com/lightninglabs/pool/account" + "github.com/lightninglabs/pool/order" + "github.com/lightninglabs/pool/poolrpc" + "github.com/lightningnetwork/lnd/lnwallet/chainfee" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var ( + ctxTimeout = 1 * time.Second + traderKeyStr = "036b51e0cc2d9e5988ee4967e0ba67ef3727bb633fea21a0af58e0c9395446ba09" + traderKeyRaw, _ = hex.DecodeString(traderKeyStr) +) + +func getAccountKey(acctKey []byte) *btcec.PublicKey { + res, _ := btcec.ParsePubKey(acctKey, btcec.S256()) + return res +} + +func genRenewAccountReq(accountKey []byte, absolute, relative uint32, + feerate uint64) *poolrpc.RenewAccountRequest { + + req := &poolrpc.RenewAccountRequest{} + + req.AccountKey = accountKey + req.FeeRateSatPerKw = feerate + if absolute != 0 { + req.AccountExpiry = &poolrpc.RenewAccountRequest_AbsoluteExpiry{ + AbsoluteExpiry: absolute, + } + } else { + req.AccountExpiry = &poolrpc.RenewAccountRequest_RelativeExpiry{ + RelativeExpiry: relative, + } + } + + return req +} + +var renewAccountTestCases = []struct { + name string + getReq func() *poolrpc.RenewAccountRequest + checkResponse func(*poolrpc.RenewAccountResponse) error + mockSetter func(*poolrpc.RenewAccountRequest, + *account.MockManager, *MockMarshaler) + expectedError string +}{{ + name: "we are able to successfully renew an account", + getReq: func() *poolrpc.RenewAccountRequest { + return genRenewAccountReq(traderKeyRaw, 0, 10, 1000) + }, + mockSetter: func(req *poolrpc.RenewAccountRequest, + accMgr *account.MockManager, marshalerMock *MockMarshaler) { + // Renew account params + bestHeight := uint32(100) + feeRate := chainfee.SatPerKWeight(req.FeeRateSatPerKw) + expiryHeight := req.GetAbsoluteExpiry() + if expiryHeight == 0 { + expiryHeight = 100 + req.GetRelativeExpiry() + } + // RenewAccount returns + acc := &account.Account{} + tx := &wire.MsgTx{} + accMgr.EXPECT(). + RenewAccount( + gomock.Any(), getAccountKey(req.AccountKey), + expiryHeight, feeRate, bestHeight, + ). + Return(acc, tx, nil) + + rpcAccount := poolrpc.Account{} + marshalerMock.EXPECT(). + MarshallAccountsWithAvailableBalance( + gomock.Any(), gomock.Eq([]*account.Account{acc}), + ). + Return([]*poolrpc.Account{&rpcAccount}, nil) + }, + checkResponse: func(*poolrpc.RenewAccountResponse) error { + return nil + }, +}, { + name: "account key must be valid", + getReq: func() *poolrpc.RenewAccountRequest { + return &poolrpc.RenewAccountRequest{ + AccountKey: []byte{3, 5, 8}, + } + }, + expectedError: "invalid pub key length 3", + mockSetter: func(req *poolrpc.RenewAccountRequest, + accMgr *account.MockManager, marshalerMock *MockMarshaler) { + }, + checkResponse: func(*poolrpc.RenewAccountResponse) error { + return nil + }, +}, { + name: "req should specify absolute/relative expiry", + getReq: func() *poolrpc.RenewAccountRequest { + return genRenewAccountReq(traderKeyRaw, 0, 0, 1000) + }, + expectedError: "either relative or absolute height must be specified", + mockSetter: func(req *poolrpc.RenewAccountRequest, + accMgr *account.MockManager, marshalerMock *MockMarshaler) { + }, + checkResponse: func(*poolrpc.RenewAccountResponse) error { + return nil + }, +}, { + name: "req should specify a valid fee rate", + getReq: func() *poolrpc.RenewAccountRequest { + return genRenewAccountReq(traderKeyRaw, 0, 100, 0) + }, + expectedError: "fee rate of 0 sat/kw is too low, minimum is 253 sat/kw", + mockSetter: func(req *poolrpc.RenewAccountRequest, + accMgr *account.MockManager, marshalerMock *MockMarshaler) { + }, + checkResponse: func(*poolrpc.RenewAccountResponse) error { + return nil + }, +}} + +func TestRenewAccount(t *testing.T) { + for _, tc := range renewAccountTestCases { + tc := tc + + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + req := tc.getReq() + + accountMgr := account.NewMockManager(mockCtrl) + orderMgr := order.NewMockManager(mockCtrl) + marshaler := NewMockMarshaler(mockCtrl) + tc.mockSetter(req, accountMgr, marshaler) + + srv := rpcServer{ + accountManager: accountMgr, + orderManager: orderMgr, + marshaler: marshaler, + } + srv.bestHeight = 100 + + ctx, cancel := context.WithTimeout( + context.Background(), ctxTimeout, + ) + defer cancel() + + resp, err := srv.RenewAccount(ctx, req) + if tc.expectedError != "" { + assert.EqualError(t, err, tc.expectedError) + return + } + require.NoError(t, err) + + err = tc.checkResponse(resp) + require.NoError(t, err) + }) + } +}