Skip to content

Commit

Permalink
feat: return output specific errors when creating transactions
Browse files Browse the repository at this point in the history
  • Loading branch information
jackstar12 committed Apr 21, 2024
1 parent 7d58263 commit f6be84e
Show file tree
Hide file tree
Showing 7 changed files with 155 additions and 143 deletions.
18 changes: 6 additions & 12 deletions boltz/btc.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ func (transaction *BtcTransaction) FindVout(network *Network, addressToFind stri
return 0, 0, errors.New("Could not find address in transaction")
}

func (transaction *BtcTransaction) VoutValue(vout uint32) (uint64, error) {
return uint64(transaction.MsgTx().TxOut[vout].Value), nil
}

func getPrevoutFetcher(tx *wire.MsgTx, outputs []OutputDetails) txscript.PrevOutputFetcher {
previous := make(map[wire.OutPoint]*wire.TxOut)
for i, input := range tx.TxIn {
Expand Down Expand Up @@ -99,11 +103,9 @@ func btcTaprootHash(transaction Transaction, outputs []OutputDetails, index int)
)
}

func constructBtcTransaction(network *Network, outputs []OutputDetails) (Transaction, error) {
func constructBtcTransaction(network *Network, outputs []OutputDetails, outValues map[string]uint64) (Transaction, error) {
transaction := wire.NewMsgTx(wire.TxVersion)

outValues := make(map[string]int64)

for _, output := range outputs {
// Set the highest timeout block height as locktime
if !output.Cooperative {
Expand All @@ -119,14 +121,6 @@ func constructBtcTransaction(network *Network, outputs []OutputDetails) (Transac
input.Sequence = 0

transaction.AddTxIn(input)

value := lockupTx.MsgTx().TxOut[output.Vout].Value
if value < int64(output.Fee) {
return nil, fmt.Errorf("output value for swap %s less than fee: %d < %d", output.SwapId, value, output.Fee)
}
//nolint:gosimple
existingValue, _ := outValues[output.Address]
outValues[output.Address] = existingValue + value - int64(output.Fee)
}

for rawAddress, value := range outValues {
Expand All @@ -143,7 +137,7 @@ func constructBtcTransaction(network *Network, outputs []OutputDetails) (Transac

transaction.AddTxOut(&wire.TxOut{
PkScript: outputScript,
Value: value,
Value: int64(value),
})
}

Expand Down
25 changes: 12 additions & 13 deletions boltz/liquid.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@ func (transaction *LiquidTransaction) FindVout(network *Network, addressToFind s

}

func (transaction *LiquidTransaction) VoutValue(vout uint32) (uint64, error) {
result, err := confidential.UnblindOutputWithKey(transaction.Outputs[vout], transaction.OurOutputBlindingKey.Serialize())
if err != nil {
return 0, err
}
return result.Value, nil
}

func (transaction *LiquidTransaction) VSize() uint64 {
witnessSize := transaction.SerializeSize(true, true) - transaction.SerializeSize(false, true)
return uint64(transaction.SerializeSize(false, true)) + uint64(math.Ceil(float64(witnessSize)/4))
Expand Down Expand Up @@ -107,7 +115,7 @@ func liquidTaprootHash(transaction *liquidtx.Transaction, network *Network, outp
return hash[:]
}

func constructLiquidTransaction(network *Network, outputs []OutputDetails) (Transaction, error) {
func constructLiquidTransaction(network *Network, outputs []OutputDetails, outValues map[string]uint64) (Transaction, error) {
p, err := psetv2.New(nil, nil, nil)
if err != nil {
return nil, err
Expand All @@ -118,6 +126,7 @@ func constructLiquidTransaction(network *Network, outputs []OutputDetails) (Tran
}

var inPrivateBlindingKeys [][]byte
var totalFee uint64

for i, output := range outputs {
lockupTx := output.LockupTransaction.(*LiquidTransaction)
Expand Down Expand Up @@ -150,6 +159,8 @@ func constructLiquidTransaction(network *Network, outputs []OutputDetails) (Tran
}
inPrivateBlindingKeys = append(inPrivateBlindingKeys, lockupTx.OurOutputBlindingKey.Serialize())
}

totalFee += output.Fee
}

zkpGenerator := confidential.NewZKPGeneratorFromBlindingKeys(inPrivateBlindingKeys, nil)
Expand All @@ -159,18 +170,6 @@ func constructLiquidTransaction(network *Network, outputs []OutputDetails) (Tran
return nil, errors.New("Failed to unblind inputs: " + err.Error())
}

outValues := make(map[string]uint64)
var totalFee uint64
for i, input := range ownedInputs {
if input.Value < outputs[i].Fee {
return nil, fmt.Errorf("input value for swap %s less than fee: %d < %d", outputs[i].SwapId, input.Value, outputs[i].Fee)
}
address := outputs[i].Address
existingValue := outValues[address]
outValues[address] = existingValue + input.Value - outputs[i].Fee
totalFee += outputs[i].Fee
}

btcAsset := network.Liquid.AssetID

txOutputs := []psetv2.OutputArgs{
Expand Down
193 changes: 118 additions & 75 deletions boltz/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type Transaction interface {
Serialize() (string, error)
VSize() uint64
FindVout(network *Network, address string) (uint32, uint64, error)
VoutValue(vout uint32) (uint64, error)
}

type OutputDetails struct {
Expand Down Expand Up @@ -60,108 +61,150 @@ func NewTxFromHex(currency Currency, hexString string, ourOutputBlindingKey *btc
return NewBtcTxFromHex(hexString)
}

func ConstructTransaction(network *Network, currency Currency, outputs []OutputDetails, satPerVbyte float64, boltzApi *Boltz) (Transaction, []uint64, error) {
var construct func(*Network, []OutputDetails) (Transaction, error)
type OutputResult struct {
Err error
Fee uint64
}

func ConstructTransaction(network *Network, currency Currency, outputs []OutputDetails, satPerVbyte float64, boltzApi *Boltz) (Transaction, []OutputResult, error) {
construct := constructBtcTransaction
if currency == CurrencyLiquid {
construct = constructLiquidTransaction
} else if currency == CurrencyBtc {
construct = constructBtcTransaction
} else {
return nil, nil, fmt.Errorf("invalid pair: %v", currency)
}

noFeeTransaction, err := construct(network, outputs)
results := make([]OutputResult, len(outputs))

getOutValues := func(fee uint64) map[string]uint64 {
outValues := make(map[string]uint64)

outLen := uint64(len(outputs))
feePerOutput := fee / outLen
feeRemainder := fee % outLen

for i, output := range outputs {
output.Fee = feePerOutput + feeRemainder
results[i].Fee = output.Fee
feeRemainder = 0

value, err := output.LockupTransaction.VoutValue(output.Vout)
if err != nil {
results[i].Err = err
continue
}

if value < output.Fee {
results[i].Err = fmt.Errorf("value than fee: %d < %d", value, output.Fee)
continue
}

outValues[output.Address] += value - output.Fee
}
return outValues
}

noFeeTransaction, err := construct(network, outputs, getOutValues(0))
if err != nil {
return nil, nil, err
}

fee := uint64(float64(noFeeTransaction.VSize()) * satPerVbyte)

outLen := uint64(len(outputs))
feePerOutput := fee / outLen
feeRemainder := fee % outLen

fees := make([]uint64, len(outputs))
for i := range outputs {
outputs[i].Fee = feePerOutput + feeRemainder
fees[i] = outputs[i].Fee
feeRemainder = 0
}

transaction, err := construct(network, outputs)
transaction, err := construct(network, outputs, getOutValues(fee))
if err != nil {
return nil, nil, err
}

var retry []OutputDetails

for i, output := range outputs {
if output.Cooperative {
if boltzApi == nil {
return nil, nil, errors.New("boltzApi is required for cooperative transactions")
}
session, err := NewSigningSession(outputs[i].SwapTree)
if err != nil {
return nil, nil, fmt.Errorf("could not initialize signing session: %w", err)
}

serialized, err := transaction.Serialize()
if err != nil {
return nil, nil, fmt.Errorf("could not serialize transaction: %w", err)
}

pubNonce := session.PublicNonce()
refundRequest := &RefundRequest{
Transaction: serialized,
PubNonce: pubNonce[:],
Index: i,
}
claimRequest := &ClaimRequest{
Transaction: serialized,
PubNonce: pubNonce[:],
Index: i,
Preimage: output.Preimage,
}
var signature *PartialSignature
if output.SwapType == ReverseSwap {
signature, err = boltzApi.ClaimReverseSwap(output.SwapId, claimRequest)
} else if output.SwapType == NormalSwap {
signature, err = boltzApi.RefundSwap(output.SwapId, refundRequest)
} else {
signature, err = func() (*PartialSignature, error) {
if output.IsRefund() {
return boltzApi.RefundChainSwap(output.SwapId, refundRequest)
}
if output.RefundSwapTree == nil {
return nil, errors.New("RefundSwapTree is required for cooperatively claiming chain swap")
}
boltzSession, err := NewSigningSession(output.RefundSwapTree)
if err != nil {
return nil, fmt.Errorf("could not initialize signing session: %w", err)
}
details, err := boltzApi.GetChainSwapClaimDetails(output.SwapId)
if err != nil {
return nil, err
}
boltzSignature, err := boltzSession.Sign(details.TransactionHash, details.PubNonce)
if err != nil {
return nil, fmt.Errorf("could not sign transaction: %w", err)
}
return boltzApi.ExchangeChainSwapClaimSignature(output.SwapId, &ChainSwapSigningRequest{
Preimage: output.Preimage,
Signature: boltzSignature,
ToSign: claimRequest,
})
}()
}
if err != nil {
return nil, nil, fmt.Errorf("could not get partial signature from boltz: %w", err)
}
err = func() error {
if boltzApi == nil {
return errors.New("boltzApi is required for cooperative transactions")
}

session, err := NewSigningSession(outputs[i].SwapTree)
if err != nil {
return fmt.Errorf("could not initialize signing session: %w", err)
}

pubNonce := session.PublicNonce()
refundRequest := &RefundRequest{
Transaction: serialized,
PubNonce: pubNonce[:],
Index: i,
}
claimRequest := &ClaimRequest{
Transaction: serialized,
PubNonce: pubNonce[:],
Index: i,
Preimage: output.Preimage,
}
var signature *PartialSignature
if output.SwapType == ReverseSwap {
signature, err = boltzApi.ClaimReverseSwap(output.SwapId, claimRequest)
} else if output.SwapType == NormalSwap {
signature, err = boltzApi.RefundSwap(output.SwapId, refundRequest)
} else {
signature, err = func() (*PartialSignature, error) {
if output.IsRefund() {
return boltzApi.RefundChainSwap(output.SwapId, refundRequest)
}
if output.RefundSwapTree == nil {
return nil, errors.New("RefundSwapTree is required for cooperatively claiming chain swap")
}
boltzSession, err := NewSigningSession(output.RefundSwapTree)
if err != nil {
return nil, fmt.Errorf("could not initialize signing session: %w", err)
}
details, err := boltzApi.GetChainSwapClaimDetails(output.SwapId)
if err != nil {
return nil, err
}
boltzSignature, err := boltzSession.Sign(details.TransactionHash, details.PubNonce)
if err != nil {
return nil, fmt.Errorf("could not sign transaction: %w", err)
}
return boltzApi.ExchangeChainSwapClaimSignature(output.SwapId, &ChainSwapSigningRequest{
Preimage: output.Preimage,
Signature: boltzSignature,
ToSign: claimRequest,
})
}()
}
if err != nil {
return fmt.Errorf("could not get partial signature from boltz: %w", err)
}

if err := session.Finalize(transaction, outputs, network, signature); err != nil {
return fmt.Errorf("could not finalize signing session: %w", err)
}

return nil
}()

if err := session.Finalize(transaction, outputs, network, signature); err != nil {
return nil, nil, fmt.Errorf("could not finalize signing session: %w", err)
if err != nil {
if output.IsRefund() {
results[i].Err = err
} else {
nonCoop := outputs[i]
nonCoop.Cooperative = false
retry = append(retry, nonCoop)
}
}
}
}

return transaction, fees, err
if len(retry) > 0 {
return ConstructTransaction(network, currency, retry, satPerVbyte, boltzApi)
}

return transaction, results, err
}
2 changes: 1 addition & 1 deletion nursery/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ func (nursery *Nursery) handleChainSwapStatus(swap *database.ChainSwap, status b
}

output := nursery.getChainSwapClaimOutput(swap)
_, err := nursery.claimOutputs(swap.Pair.To, []*Output{output})
_, err := nursery.createTransaction(swap.Pair.To, []*Output{output})
if err != nil {
handleError("Could not claim chain swap output: " + err.Error())
return
Expand Down
Loading

0 comments on commit f6be84e

Please sign in to comment.