Skip to content

Commit

Permalink
rpcserver: Use CreateRevocationFromTicket.
Browse files Browse the repository at this point in the history
This reworks the createrawssrtx handler to use the
CreateRevocationFromTicket function from the stake package rather than
creating the transaction directly.

It also adds additional error handling on the transaction input:
  - The input must be a ticket submission output (output index 0)
  - The input amount must equal the ticket submission amount
  • Loading branch information
rstaudt2 committed Aug 27, 2021
1 parent a5abb4c commit 98608fb
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 85 deletions.
107 changes: 32 additions & 75 deletions internal/rpcserver/rpcserver.go
Expand Up @@ -949,22 +949,23 @@ func handleCreateRawSSRtx(_ context.Context, s *Server, cmd interface{}) (interf
return nil, rpcInvalidError("SSRtx invalid number of inputs")
}

// Decode the fee as coins.
var feeAmt dcrutil.Amount
if c.Fee != nil {
var err error
feeAmt, err = dcrutil.NewAmount(*c.Fee)
if err != nil {
return nil, rpcInvalidError("Invalid fee amount: %v",
err)
}
// The input must be in the stake tree.
input := c.Inputs[0]
if input.Tree != wire.TxTreeStake {
return nil, rpcInvalidError("Input tree is not TxTreeStake type")
}

// The input must be a ticket submission output.
const ticketSubmissionOutput = 0
if input.Vout != ticketSubmissionOutput {
return nil, rpcInvalidError("Input is not a ticket submission output " +
"(output index 0)")
}

// 1. Fetch the SStx, then calculate all the values we'll need later
// for the generation of the SSRtx tx outputs.
//
// Convert the provided transaction hash hex to a chainhash.Hash.
input := c.Inputs[0]
txHash, err := chainhash.NewHashFromStr(input.Txid)
if err != nil {
return nil, rpcDecodeHexError(input.Txid)
Expand All @@ -987,77 +988,33 @@ func handleCreateRawSSRtx(_ context.Context, s *Server, cmd interface{}) (interf
return nil, rpcInternalError("Missing ticket minimal outputs", "")
}

ssrtxPayTypes, ssrtxPkhs, sstxAmts, _, _, _ :=
stake.SStxStakeOutputInfo(minimalOutputs)

// 2. Add all transaction inputs to a new transaction after performing
// some validity checks; the only input for an SSRtx is an OP_SSTX tagged
// output.
mtx := wire.NewMsgTx()

if !(input.Tree == wire.TxTreeStake) {
return nil, rpcInvalidError("Input tree is not TxTreeStake type")
// The input amount must be the ticket submission amount.
ticketSubmission := minimalOutputs[ticketSubmissionOutput]
ticketSubmissionAmount := dcrutil.Amount(ticketSubmission.Value)
inputAmount, err := dcrutil.NewAmount(input.Amount)
if err != nil {
return nil, rpcInvalidError(err.Error())
}
if inputAmount != ticketSubmissionAmount {
return nil, rpcInvalidError("Input amount %v is not equal to ticket "+
"submission amount %v", inputAmount, ticketSubmissionAmount)
}

prevOutV := wire.NullValueIn
if input.Amount > 0 {
amt, err := dcrutil.NewAmount(input.Amount)
// Decode the fee as coins.
var feeAmt dcrutil.Amount
if c.Fee != nil {
var err error
feeAmt, err = dcrutil.NewAmount(*c.Fee)
if err != nil {
return nil, rpcInvalidError(err.Error())
return nil, rpcInvalidError("Invalid fee amount: %v", err)
}
prevOutV = int64(amt)
}

prevOut := wire.NewOutPoint(txHash, input.Vout, input.Tree)
txIn := wire.NewTxIn(prevOut, prevOutV, []byte{})
mtx.AddTxIn(txIn)

// 3. Add all the OP_SSRTX tagged outputs.

// Calculate the output values from this data.
ssrtxCalcAmts := stake.CalculateRewards(sstxAmts,
minimalOutputs[0].Value, 0) // No subsidy for a revocation

// Add all the SSRtx-tagged transaction outputs to the transaction after
// performing some validity checks.
feeApplied := false
for i, ssrtxPkh := range ssrtxPkhs {
// Ensure amount is in the valid range for monetary amounts.
if sstxAmts[i] <= 0 || sstxAmts[i] > dcrutil.MaxAmount {
return nil, rpcInvalidError("Invalid SSTx amount: 0 >="+
" %v > %v", sstxAmts[i] <= 0, dcrutil.MaxAmount)
}

// Create a new script which pays to the provided address specified in
// the original ticket tx.
var ssrtxOutScript []byte
switch ssrtxPayTypes[i] {
case false: // P2PKH
addr, err := stdaddr.NewAddressPubKeyHashEcdsaSecp256k1V0(ssrtxPkh,
s.cfg.ChainParams)
if err != nil {
return nil, rpcInvalidError("Could not "+
"generate P2PKH script: %v", err)
}
_, ssrtxOutScript = addr.PayRevokeCommitmentScript()
case true: // P2SH
addr, err := stdaddr.NewAddressScriptHashV0FromHash(ssrtxPkh,
s.cfg.ChainParams)
if err != nil {
return nil, rpcInvalidError("Could not "+
"generate P2SH script: %v", err)
}
_, ssrtxOutScript = addr.PayRevokeCommitmentScript()
}

// Add the txout to our SSRtx tx.
amt := ssrtxCalcAmts[i]
if !feeApplied && int64(feeAmt) < amt {
amt -= int64(feeAmt)
feeApplied = true
}
txOut := wire.NewTxOut(amt, ssrtxOutScript)
mtx.AddTxOut(txOut)
const revocationTxVersion = 1
mtx, err := stake.CreateRevocationFromTicket(txHash, minimalOutputs, feeAmt,
revocationTxVersion, s.cfg.ChainParams)
if err != nil {
return nil, rpcInvalidError(err.Error(), "Invalid SSRtx")
}

// Check to make sure our SSRtx was created correctly.
Expand Down
50 changes: 40 additions & 10 deletions internal/rpcserver/rpcserverhandlers_test.go
Expand Up @@ -2138,13 +2138,14 @@ func TestHandleCreateRawSStx(t *testing.T) {
func TestHandleCreateRawSSRtx(t *testing.T) {
t.Parallel()

defaultTxId := "1189cbe656c2ef1e0fcb91f107624d9aa8f0db7b28e6a86f694a4cf49abc5e39"
defaultCmdInputs := []types.TransactionInput{{
Amount: 100,
Txid: "1189cbe656c2ef1e0fcb91f107624d9aa8f0db7b28e6a86f694a4cf49abc5e39",
Amount: 1,
Txid: defaultTxId,
Vout: 0,
Tree: 1,
}}
defaultFee := dcrjson.Float64(1)
defaultFee := dcrjson.Float64(0.1)
testRPCServerHandler(t, []rpcTest{{
name: "handleCreateRawSSRtx: ok",
handler: handleCreateRawSSRtx,
Expand All @@ -2153,9 +2154,9 @@ func TestHandleCreateRawSSRtx(t *testing.T) {
Fee: defaultFee,
},
result: "0100000001395ebc9af44c4a696fa8e6287bdbf0a89a4d6207f191cb0f1eefc25" +
"6e6cb89110000000001ffffffff0100e1f5050000000000001abc76a914355c96" +
"f48612d57509140e9a049981d5f9970f9488ac00000000000000000100e40b540" +
"200000000000000ffffffff00",
"6e6cb89110000000001ffffffff01804a5d050000000000001abc76a914355c96" +
"f48612d57509140e9a049981d5f9970f9488ac00000000000000000100e1f5050" +
"000000000000000ffffffff00",
}, {
name: "handleCreateRawSSRtx: ok P2SH",
handler: handleCreateRawSSRtx,
Expand Down Expand Up @@ -2201,8 +2202,8 @@ func TestHandleCreateRawSSRtx(t *testing.T) {
return chain
}(),
result: "0100000001395ebc9af44c4a696fa8e6287bdbf0a89a4d6207f191cb0f1eefc25" +
"6e6cb89110000000001ffffffff0100e1f50500000000000018bca914355c96f4" +
"8612d57509140e9a049981d5f9970f948700000000000000000100e40b5402000" +
"6e6cb89110000000001ffffffff01804a5d0500000000000018bca914355c96f4" +
"8612d57509140e9a049981d5f9970f948700000000000000000100e1f50500000" +
"00000000000ffffffff00",
}, {
name: "handleCreateRawSSRtx: invalid number of inputs",
Expand All @@ -2213,6 +2214,35 @@ func TestHandleCreateRawSSRtx(t *testing.T) {
},
wantErr: true,
errCode: dcrjson.ErrRPCInvalidParameter,
}, {
name: "handleCreateRawSSRtx: invalid output index",
handler: handleCreateRawSSRtx,
cmd: &types.CreateRawSSRtxCmd{
Inputs: []types.TransactionInput{{
Amount: 1,
Txid: defaultTxId,
Vout: 1,
Tree: 1,
}},
Fee: defaultFee,
},
wantErr: true,
errCode: dcrjson.ErrRPCInvalidParameter,
}, {
name: "handleCreateRawSSRtx: input amount not equal to ticket submission " +
"amount",
handler: handleCreateRawSSRtx,
cmd: &types.CreateRawSSRtxCmd{
Inputs: []types.TransactionInput{{
Amount: 100,
Txid: defaultTxId,
Vout: 0,
Tree: 1,
}},
Fee: defaultFee,
},
wantErr: true,
errCode: dcrjson.ErrRPCInvalidParameter,
}, {
name: "handleCreateRawSSRtx: invalid fee amount",
handler: handleCreateRawSSRtx,
Expand Down Expand Up @@ -2276,7 +2306,7 @@ func TestHandleCreateRawSSRtx(t *testing.T) {
cmd: &types.CreateRawSSRtxCmd{
Inputs: []types.TransactionInput{{
Amount: 100,
Txid: "1189cbe656c2ef1e0fcb91f107624d9aa8f0db7b28e6a86f694a4cf49abc5e39",
Txid: defaultTxId,
Vout: 0,
Tree: 0,
}},
Expand All @@ -2290,7 +2320,7 @@ func TestHandleCreateRawSSRtx(t *testing.T) {
cmd: &types.CreateRawSSRtxCmd{
Inputs: []types.TransactionInput{{
Amount: math.Inf(1),
Txid: "1189cbe656c2ef1e0fcb91f107624d9aa8f0db7b28e6a86f694a4cf49abc5e39",
Txid: defaultTxId,
Vout: 0,
Tree: 1,
}},
Expand Down

0 comments on commit 98608fb

Please sign in to comment.