kaspad/cmd/kaspawallet/daemon/server/create_unsigned_transaction_verbose.go
2022-11-16 22:21:52 +02:00

127 lines
3.6 KiB
Go

package server
import (
"context"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"golang.org/x/exp/slices"
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
"github.com/kaspanet/kaspad/util"
"github.com/pkg/errors"
)
func (s *server) CreateUnsignedTransactionVerbose(_ context.Context, request *pb.CreateUnsignedTransactionVerboseRequest) (
*pb.CreateUnsignedTransactionsResponse, error) {
s.lock.Lock()
defer s.lock.Unlock()
inputs, err := protoOutputsToDomainOutputs(request.Inputs)
outputs, err := protoPaymentToLibPayment(request.Outputs, s.params.Prefix)
unsignedTransactions, err := s.createUnsignedTransactionVerbose(inputs, outputs)
if err != nil {
return nil, err
}
return &pb.CreateUnsignedTransactionsResponse{UnsignedTransactions: unsignedTransactions}, nil
}
func (s *server) createUnsignedTransactionVerbose(inputs []externalapi.DomainOutpoint, payments []*libkaspawallet.Payment) ([][]byte, error) {
if !s.isSynced() {
return nil, errors.New("server is not synced")
}
err := s.refreshUTXOs()
if err != nil {
return nil, err
}
selectedUTXOs, totalValue, err := s.selectUTXOsByOutpoints(inputs)
if err != nil {
return nil, err
}
if len(selectedUTXOs) < len(inputs) {
return nil, errors.New("Some UTXOs are unavailable")
}
totalSpend := uint64(0)
for _, payment := range payments {
totalSpend += payment.Amount
}
changeSompi := totalValue - totalSpend - feePerInput*uint64(len(selectedUTXOs))
if changeSompi < 0 {
return nil, errors.New("Total input is not enough to cover total output and fees")
}
changeAddress, _, err := s.changeAddress()
if err != nil {
return nil, err
}
if changeSompi > 0 {
payments = append(payments, &libkaspawallet.Payment{
Address: changeAddress,
Amount: changeSompi,
})
}
unsignedTransaction, err := libkaspawallet.CreateUnsignedTransaction(s.keysFile.ExtendedPublicKeys,
s.keysFile.MinimumSignatures,
payments, selectedUTXOs)
if err != nil {
return nil, err
}
return [][]byte{unsignedTransaction}, nil
}
func (s *server) selectUTXOsByOutpoints(inputs []externalapi.DomainOutpoint) (selectedUTXOs []*libkaspawallet.UTXO, totalValue uint64, err error) {
dagInfo, err := s.rpcClient.GetBlockDAGInfo()
if err != nil {
return nil, 0, err
}
for _, utxo := range s.utxosSortedByAmount {
if !slices.Contains(inputs, *utxo.Outpoint) ||
!isUTXOSpendable(utxo, dagInfo.VirtualDAAScore, s.params.BlockCoinbaseMaturity) {
continue
}
selectedUTXOs = append(selectedUTXOs, &libkaspawallet.UTXO{
Outpoint: utxo.Outpoint,
UTXOEntry: utxo.UTXOEntry,
DerivationPath: s.walletAddressPath(utxo.address),
})
totalValue += utxo.UTXOEntry.Amount()
}
return selectedUTXOs, totalValue, nil
}
func protoOutputsToDomainOutputs(request_inputs []*pb.Outpoint) (inputs []externalapi.DomainOutpoint, err error) {
for _, input := range request_inputs {
txId, err := externalapi.NewDomainTransactionIDFromString(input.TransactionId)
if err != nil {
return nil, err
}
inputs = append(inputs, externalapi.DomainOutpoint{
TransactionID: *txId,
Index: input.Index,
})
}
return inputs, nil
}
func protoPaymentToLibPayment(request_outputs []*pb.PaymentOutput, prefix util.Bech32Prefix) (outputs []*libkaspawallet.Payment, err error) {
for _, output := range request_outputs {
address, err := util.DecodeAddress(output.Address, prefix)
if err != nil {
return nil, err
}
outputs = append(outputs, &libkaspawallet.Payment{
Address: address,
Amount: output.Amount,
})
}
return outputs, nil
}