account: add support for account renewals

This commit is contained in:
Wilmer Paulino 2020-12-01 16:56:49 -08:00 committed by Oliver Gugger
parent 1fd51e4c55
commit 79c7bb029b
No known key found for this signature in database
GPG key ID: 8E4256593F177720

View file

@ -1064,15 +1064,14 @@ func (m *Manager) DepositAccount(ctx context.Context,
// required new value of the account as part of the deposit. The
// selected inputs, along with a change output if needed, will then be
// included in the deposit transaction we'll broadcast.
witnessType := determineWitnessType(account, bestHeight)
inputs, releaseInputs, changeOutput, err := m.inputsForDeposit(
ctx, depositAmount, witnessType, feeRate,
ctx, depositAmount, multiSigWitness, feeRate,
)
if err != nil {
return nil, nil, err
}
newAccountOutput, modifiers, err := createNewAccountOutput(
account, newAccountValue,
account, newAccountValue, nil,
)
if err != nil {
releaseInputs()
@ -1089,7 +1088,7 @@ func (m *Manager) DepositAccount(ctx context.Context,
}
modifiers = append(modifiers, StateModifier(StatePendingUpdate))
modifiedAccount, spendPkg, err := m.spendAccount(
ctx, account, inputs, outputs, witnessType, modifiers, false,
ctx, account, inputs, outputs, multiSigWitness, modifiers, false,
bestHeight,
)
if err != nil {
@ -1122,15 +1121,14 @@ func (m *Manager) WithdrawAccount(ctx context.Context,
// To start, we'll need to determine the new value of the account after
// creating the outputs specified as part of the withdrawal, which we'll
// then use to create the new account output.
witnessType := determineWitnessType(account, bestHeight)
newAccountValue, err := valueAfterWithdrawal(
account, outputs, witnessType, feeRate,
newAccountValue, err := valueAfterAccountUpdate(
account, outputs, multiSigWitness, feeRate,
)
if err != nil {
return nil, nil, err
}
newAccountOutput, modifiers, err := createNewAccountOutput(
account, newAccountValue,
account, newAccountValue, nil,
)
if err != nil {
return nil, nil, err
@ -1143,7 +1141,7 @@ func (m *Manager) WithdrawAccount(ctx context.Context,
outputs = append(outputs, newAccountOutput)
modifiers = append(modifiers, StateModifier(StatePendingUpdate))
modifiedAccount, spendPkg, err := m.spendAccount(
ctx, account, nil, outputs, witnessType, modifiers, false,
ctx, account, nil, outputs, multiSigWitness, modifiers, false,
bestHeight,
)
if err != nil {
@ -1153,6 +1151,70 @@ func (m *Manager) WithdrawAccount(ctx context.Context,
return modifiedAccount, spendPkg.tx, nil
}
// 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,
traderKey *btcec.PublicKey, newExpiry uint32,
feeRate chainfee.SatPerKWeight, bestHeight uint32) (*Account,
*wire.MsgTx, error) {
// The account can only have its expiry updated if it has confirmed
// and/or has expired.
account, err := m.cfg.Store.Account(traderKey)
if err != nil {
return nil, nil, err
}
switch account.State {
case StateOpen, StatePendingBatch, StateExpired:
default:
return nil, nil, fmt.Errorf("account must be in either of %v "+
"to be renewed",
[]State{StateOpen, StatePendingBatch, StateExpired})
}
// Validate the new expiry.
if err := validateAccountExpiry(newExpiry, bestHeight); err != nil {
return nil, nil, err
}
// Determine the new account output after attempting the expiry update.
newAccountValue, err := valueAfterAccountUpdate(
account, nil, multiSigWitness, feeRate,
)
if err != nil {
return nil, nil, err
}
newAccountOutput, modifiers, err := createNewAccountOutput(
account, newAccountValue, &newExpiry,
)
if err != nil {
return nil, nil, err
}
// With the output created, we'll tack on an additional
// `StatePendingUpdate` modifier to our account and proceed with the
// rest of the flow. This should request a signature from the auctioneer
// and assuming it's valid, broadcast the update transaction.
modifiers = append(modifiers, StateModifier(StatePendingUpdate))
modifiedAccount, spendPkg, err := m.spendAccount(
ctx, account, nil, []*wire.TxOut{newAccountOutput},
multiSigWitness, modifiers, false, bestHeight,
)
if err != nil {
return nil, nil, err
}
// Begin to track the new account expiration, which will overwrite the
// existing expiration request.
err = m.watcher.WatchAccountExpiration(traderKey, modifiedAccount.Expiry)
if err != nil {
return nil, nil, err
}
return modifiedAccount, spendPkg.tx, nil
}
// 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
@ -1285,8 +1347,6 @@ func (m *Manager) spendAccount(ctx context.Context, account *Account,
)
switch witnessType {
case expiryWitness:
// TODO(wilmer): Support modifications through the expiry path.
// This will require a new account expiration.
if !isClose {
return nil, nil, errors.New("modifications for expired " +
"accounts are not currently supported")
@ -1298,6 +1358,9 @@ func (m *Manager) spendAccount(ctx context.Context, account *Account,
case multiSigWitness:
spendPkg, err = m.createSpendTx(ctx, account, inputs, outputs, 0)
default:
err = fmt.Errorf("unhandled witness type: %v", witnessType)
}
if err != nil {
return nil, nil, err
@ -1573,11 +1636,11 @@ func addBaseAccountModificationWeight(weightEstimator *input.TxWeightEstimator,
return nil
}
// valueAfterWithdrawal determines the new value of an account after processing
// a withdrawal to the specified outputs at the provided fee rate.
func valueAfterWithdrawal(accountBeforeWithdrawl *Account,
withdrawalOutputs []*wire.TxOut, witnessType witnessType,
feeRate chainfee.SatPerKWeight) (btcutil.Amount, error) {
// valueAfterAccountUpdate determines the new value of an account after
// processing a withdrawal to the specified outputs at the provided fee rate.
func valueAfterAccountUpdate(account *Account, outputs []*wire.TxOut,
witnessType witnessType, feeRate chainfee.SatPerKWeight) (
btcutil.Amount, error) {
// To determine the new value of the account, we'll need to subtract the
// values of all additional outputs and the resulting fee of the
@ -1595,7 +1658,7 @@ func valueAfterWithdrawal(accountBeforeWithdrawl *Account,
// We'll then add the weight estimates for any additional outputs
// provided, keeping track of the total output value sum as we go.
var outputTotal btcutil.Amount
for _, out := range withdrawalOutputs {
for _, out := range outputs {
// To determine the proper weight of the output, we'll need to
// know its type.
pkScript, err := txscript.ParsePkScript(out.PkScript)
@ -1623,7 +1686,7 @@ func valueAfterWithdrawal(accountBeforeWithdrawl *Account,
// from our input total and ensure our new account value isn't below our
// required minimum.
fee := feeRate.FeeForWeight(int64(weightEstimator.Weight()))
newAccountValue := accountBeforeWithdrawl.Value - outputTotal - fee
newAccountValue := account.Value - outputTotal - fee
if newAccountValue < MinAccountValue {
return 0, fmt.Errorf("new account value is below accepted "+
"minimum of %v", MinAccountValue)
@ -1738,24 +1801,22 @@ coinSelection:
}
// createNewAccountOutput creates the next account output in the sequence using
// the new account value.
func createNewAccountOutput(account *Account, newAccountValue btcutil.Amount) (
*wire.TxOut, []Modifier, error) {
// the new account value and optional new account expiry.
func createNewAccountOutput(account *Account, newAccountValue btcutil.Amount,
newAccountExpiry *uint32) (*wire.TxOut, []Modifier, error) {
// Use the next output script in the sequence to avoid script reuse.
newPkScript, err := account.NextOutputScript()
if err != nil {
return nil, nil, err
}
newAccountOutput := &wire.TxOut{
Value: int64(newAccountValue),
PkScript: newPkScript,
}
modifiers := []Modifier{
ValueModifier(newAccountValue),
IncrementBatchKey(),
}
if newAccountExpiry != nil {
modifiers = append(modifiers, ExpiryModifier(*newAccountExpiry))
}
newAccountOutput, err := account.Copy(modifiers...).Output()
if err != nil {
return nil, nil, err
}
return newAccountOutput, modifiers, nil
}
@ -1898,16 +1959,9 @@ func validateAccountValue(value, maxValue btcutil.Amount) error {
return nil
}
// validateAccountParams ensures that a trader has provided sane parameters for
// the creation of a new account.
func validateAccountParams(value, maxValue btcutil.Amount, expiry,
bestHeight uint32) error {
err := validateAccountValue(value, maxValue)
if err != nil {
return err
}
// validateAccountExpiry ensures that a trader has provided a sane account expiry
// for the creation/modification of an account.
func validateAccountExpiry(expiry, bestHeight uint32) error {
if expiry < bestHeight+minAccountExpiry {
return fmt.Errorf("current minimum account expiry allowed is "+
"height %v", bestHeight+minAccountExpiry)
@ -1920,6 +1974,18 @@ func validateAccountParams(value, maxValue btcutil.Amount, expiry,
return nil
}
// validateAccountParams ensures that a trader has provided sane parameters for
// the creation of a new account.
func validateAccountParams(value, maxValue btcutil.Amount, expiry,
bestHeight uint32) error {
err := validateAccountValue(value, maxValue)
if err != nil {
return err
}
return validateAccountExpiry(expiry, bestHeight)
}
// numConfsForValue chooses an appropriate number of confirmations to wait for
// an account based on its initial value.
//