Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: chain swaps cli #136

Merged
merged 25 commits into from
Apr 26, 2024
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
fe52a93
feat: chain swap cli
jackstar12 Apr 17, 2024
b3f77cc
fix: chain swap help description
kilrau Apr 18, 2024
8240caf
fix: help output missing , statement
kilrau Apr 18, 2024
1aa8934
fix: chain swap help description from/to
kilrau Apr 18, 2024
53a0813
fix: createswap,createreverseswap,createchainswap help description
kilrau Apr 18, 2024
64e633b
fix: autoswapper -> autoswap in help outputs, comments
kilrau Apr 18, 2024
bd6d078
fix: typo
kilrau Apr 18, 2024
63295dc
refactor: dont implicitly select wallets
jackstar12 Apr 18, 2024
a97ecff
fix: check for state in timeout condition when querying refundable swaps
jackstar12 Apr 18, 2024
3197c57
fix: only set fees of valid outputs after transaction was created
jackstar12 Apr 18, 2024
6684aca
feat: implicitly set external pay in cli when not specifying from wallet
jackstar12 Apr 18, 2024
7b375d4
chore: cleanup
jackstar12 Apr 18, 2024
7d58263
fix: check if value is less than fee when constructing transaction
jackstar12 Apr 21, 2024
9357ce1
feat: return output specific errors when creating transactions
jackstar12 Apr 21, 2024
98bfc26
fix: refund help description
kilrau Apr 24, 2024
476c17d
fix: --from-external, --to-address help output
kilrau Apr 24, 2024
ede81bd
refactor: dont default to mainchain when creating swap
jackstar12 Apr 24, 2024
c00edbc
refactor: use from and to wallet terminology everywhere
jackstar12 Apr 24, 2024
75dfa6f
chore: add db migration
jackstar12 Apr 25, 2024
64b0d6d
fix: display correct refund transaction when refunding in cli
jackstar12 Apr 25, 2024
7e000d6
fix: show correct timeout in cli
jackstar12 Apr 25, 2024
07a34b3
feat: recover pending chain swaps
jackstar12 Apr 25, 2024
87f8e97
fix: correctly set external pay when creating submarine swap
jackstar12 Apr 25, 2024
b04d533
feat: display currency of lockup tx in cli
jackstar12 Apr 26, 2024
c843ce7
fix: dont try to broadcast empty transaction
jackstar12 Apr 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 6 additions & 10 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,12 +121,6 @@ func constructBtcTransaction(network *Network, outputs []OutputDetails) (Transac
input.Sequence = 0

transaction.AddTxIn(input)

value := lockupTx.MsgTx().TxOut[output.Vout].Value - int64(output.Fee)
//nolint:gosimple
existingValue, _ := outValues[output.Address]
outValues[output.Address] = existingValue + value

}

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

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

Expand Down
23 changes: 12 additions & 11 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
jackstar12 marked this conversation as resolved.
Show resolved Hide resolved
}

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,16 +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 {
address := outputs[i].Address
//nolint:gosimple
existingValue, _ := outValues[address]
outValues[address] = existingValue + input.Value - outputs[i].Fee
totalFee += outputs[i].Fee
}

btcAsset := network.Liquid.AssetID

txOutputs := []psetv2.OutputArgs{
Expand Down
195 changes: 118 additions & 77 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,148 @@ 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)
}
results := make([]OutputResult, len(outputs))

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

noFeeTransaction, err := construct(network, outputs)
outLen := uint64(len(outputs))
feePerOutput := fee / outLen
feeRemainder := fee % outLen

for i := range outputs {
output := &outputs[i]
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
}
8 changes: 4 additions & 4 deletions boltzrpc/autoswaprpc/autoswaprpc.proto
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import "boltzrpc.proto";

service AutoSwap {
/*
Returns a list of swaps which are currently recommended by the autoswapper. Also works when the autoswapper is not running.
Returns a list of swaps which are currently recommended by autoswap. Also works when autoswap is not running.
*/
rpc GetSwapRecommendations (GetSwapRecommendationsRequest) returns (GetSwapRecommendationsResponse);

/*
Returns the current budget of the autoswapper and some relevant stats.
Returns the current budget of autoswap and some relevant stats.
*/
rpc GetStatus (GetStatusRequest) returns (GetStatusResponse);

Expand All @@ -23,12 +23,12 @@ service AutoSwap {
rpc ResetConfig(google.protobuf.Empty) returns (Config);

/*
Allows setting multiple json-encoded config values at once. The autoswapper will reload the configuration after this call.
Allows setting multiple json-encoded config values at once. Autoswap will reload the configuration after this call.
*/
rpc SetConfig(Config) returns (Config);

/*
Allows setting a specific value in the configuration. The autoswapper will reload the configuration after this call.
Allows setting a specific value in the configuration. Autoswap will reload the configuration after this call.
*/
rpc SetConfigValue(SetConfigValueRequest) returns (Config);

Expand Down
Loading
Loading