Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
59 changes: 58 additions & 1 deletion services/account_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package services
import (
"context"
"encoding/hex"
"fmt"
"github.com/deso-protocol/rosetta-deso/deso"
"strconv"

Expand Down Expand Up @@ -162,5 +163,61 @@ func (s *AccountAPIService) AccountCoins(
ctx context.Context,
request *types.AccountCoinsRequest,
) (*types.AccountCoinsResponse, *types.Error) {
return nil, ErrUnimplemented
if s.config.Mode != deso.Online {
return nil, ErrUnavailableOffline
}

blockchain := s.node.GetBlockchain()
currentBlock := blockchain.BlockTip()

publicKeyBytes, _, err := lib.Base58CheckDecode(request.AccountIdentifier.Address)
if err != nil {
return nil, wrapErr(ErrInvalidPublicKey, err)
}

utxoView, err := lib.NewUtxoView(blockchain.DB(), s.node.Params, nil)
if err != nil {
return nil, wrapErr(ErrDeSo, err)
}

utxoEntries, err := utxoView.GetUnspentUtxoEntrysForPublicKey(publicKeyBytes)
if err != nil {
return nil, wrapErr(ErrDeSo, err)
}

coins := []*types.Coin{}

for _, utxoEntry := range utxoEntries {
confirmations := uint64(currentBlock.Height) - uint64(utxoEntry.BlockHeight) + 1

metadata, err := types.MarshalMap(&amountMetadata{
Confirmations: confirmations,
})
if err != nil {
return nil, wrapErr(ErrUnableToParseIntermediateResult, err)
}

coins = append(coins, &types.Coin{
CoinIdentifier: &types.CoinIdentifier{
Identifier: fmt.Sprintf("%v:%d", utxoEntry.UtxoKey.TxID.String(), utxoEntry.UtxoKey.Index),
},
Amount: &types.Amount{
Value: strconv.FormatUint(utxoEntry.AmountNanos, 10),
Currency: s.config.Currency,
Metadata: metadata,
},
})
}

block := &types.BlockIdentifier{
Index: int64(currentBlock.Height),
Hash: currentBlock.Hash.String(),
}

result := &types.AccountCoinsResponse{
BlockIdentifier: block,
Coins: coins,
}

return result, nil
}
64 changes: 56 additions & 8 deletions services/construction_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ import (
"strconv"
)

const (
BytesPerKb = 1000
MaxDERSigLen = 74

// FeeByteBuffer adds a byte buffer to the length of the transaction when calculating the suggested fee.
// We need this buffer because the size of the transaction can increase by a few bytes after
// the preprocess step and before the combine step
FeeByteBuffer = 2
)

type ConstructionAPIService struct {
config *deso.Config
node *deso.Node
Expand Down Expand Up @@ -48,6 +58,17 @@ func (s *ConstructionAPIService) ConstructionPreprocess(ctx context.Context, req
if op.Type == deso.InputOpType {
// Add the account identifier to our map
inputPubKeysFoundMap[op.Account.Address] = true

txId, txnIndex, err := ParseCoinIdentifier(op.CoinChange.CoinIdentifier)
if err != nil {
return nil, wrapErr(ErrInvalidCoin, err)
}

// Include the inputs in case we use legacy utxo selection
optionsObj.DeSoInputs = append(optionsObj.DeSoInputs, &desoInput{
TxHex: hex.EncodeToString(txId.ToBytes()),
Index: txnIndex,
})
} else if op.Type == deso.OutputOpType {
// Parse the amount of this output
amount, err := strconv.ParseUint(op.Amount.Value, 10, 64)
Expand All @@ -59,7 +80,6 @@ func (s *ConstructionAPIService) ConstructionPreprocess(ctx context.Context, req
AmountNanos: amount,
})
}

}

// Exactly one public key is required, otherwise that's an error.
Expand All @@ -70,8 +90,8 @@ func (s *ConstructionAPIService) ConstructionPreprocess(ctx context.Context, req
}

// Set the from public key on the options
for kk, _ := range inputPubKeysFoundMap {
optionsObj.FromPublicKey = kk
for publicKey := range inputPubKeysFoundMap {
optionsObj.FromPublicKey = publicKey
break
}

Expand Down Expand Up @@ -123,19 +143,47 @@ func (s *ConstructionAPIService) ConstructionMetadata(ctx context.Context, reque
if err != nil {
return nil, wrapErr(ErrDeSo, err)
}
txnn := &lib.MsgDeSoTxn{
txn := &lib.MsgDeSoTxn{
// The inputs will be set below.
TxInputs: []*lib.DeSoInput{},
TxOutputs: fullDeSoOutputs,
PublicKey: fromPubKeyBytes,
TxnMeta: &lib.BasicTransferMetadata{},
}
_, _, _, fee, err := s.node.GetBlockchain().AddInputsAndChangeToTransaction(txnn, feePerKB, s.node.GetMempool())
if err != nil {
return nil, wrapErr(ErrInvalidTransaction, err)

var fee uint64

// Support legacy utxo selection
if options.LegacyUTXOSelection {
txn.TxInputs = []*lib.DeSoInput{}
for _, input := range options.DeSoInputs {
txId, err := hex.DecodeString(input.TxHex)
if err != nil {
return nil, wrapErr(ErrInvalidTransaction, err)
}

txn.TxInputs = append(txn.TxInputs, &lib.DeSoInput{
TxID: *lib.NewBlockHash(txId),
Index: input.Index,
})
}

txnBytes, err := txn.ToBytes(true)
if err != nil {
return nil, wrapErr(ErrInvalidTransaction, err)
}

// Override fee calculation
txnSize := uint64(len(txnBytes) + MaxDERSigLen + FeeByteBuffer)
fee = feePerKB * txnSize / BytesPerKb
} else {
_, _, _, fee, err = s.node.GetBlockchain().AddInputsAndChangeToTransaction(txn, feePerKB, s.node.GetMempool())
if err != nil {
return nil, wrapErr(ErrInvalidTransaction, err)
}
}

desoTxnBytes, err := txnn.ToBytes(true)
desoTxnBytes, err := txn.ToBytes(true)
if err != nil {
return nil, wrapErr(ErrInvalidTransaction, err)
}
Expand Down
25 changes: 20 additions & 5 deletions services/types.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
package services

type desoOutput struct {
PublicKey string
AmountNanos uint64
}

type preprocessOptions struct {
// The from public key is used to gather UTXOs in the metadata portion. This
// obviates the need to rely on UTXOs in the Rosetta API, and supports a future
Expand All @@ -13,6 +8,22 @@ type preprocessOptions struct {

// The outputs are used to compute the fee estimate in the metadata portion.
DeSoOutputs []*desoOutput `json:"deso_outputs"`

// Allow legacy manual selection of UTXOs
LegacyUTXOSelection bool `json:"legacy_utxo_selection"` // Deprecated
DeSoInputs []*desoInput `json:"deso_inputs"` // Deprecated
}

type desoOutput struct {
PublicKey string
AmountNanos uint64
}

type desoInput struct {
// The 32-byte transaction id where the unspent output occurs.
TxHex string
// The index within the txn where the unspent output occurs.
Index uint32
}

type constructionMetadata struct {
Expand All @@ -28,3 +39,7 @@ type transactionMetadata struct {
Transaction []byte `json:"transaction"`
InputAmount string `json:"input_amount"`
}

type amountMetadata struct {
Confirmations uint64 `json:"confirmations"`
}