Skip to content

Commit

Permalink
mempool: add revocation orphan test.
Browse files Browse the repository at this point in the history
  • Loading branch information
dnldd committed Jul 28, 2018
1 parent 2cf00a5 commit fb05050
Show file tree
Hide file tree
Showing 2 changed files with 149 additions and 10 deletions.
21 changes: 16 additions & 5 deletions mempool/mempool.go
Expand Up @@ -325,9 +325,15 @@ func (mp *TxPool) removeOrphan(tx *dcrutil.Tx, removeRedeemers bool) {

// Remove any orphans that redeem outputs from this one if requested.
if removeRedeemers {
prevOut := wire.OutPoint{Hash: *txHash}
txType := stake.DetermineTxType(tx.MsgTx())
tree := wire.TxTreeRegular
if txType != stake.TxTypeRegular {
tree = wire.TxTreeStake
}

for txOutIdx := range tx.MsgTx().TxOut {
prevOut.Index = uint32(txOutIdx)
prevOut := wire.OutPoint{Hash: *txHash,
Index: uint32(txOutIdx), Tree: tree}
for _, orphan := range mp.orphansByPrev[prevOut] {
mp.removeOrphan(orphan, true)
}
Expand Down Expand Up @@ -389,7 +395,6 @@ func (mp *TxPool) addOrphan(tx *dcrutil.Tx) {
if _, exists := mp.orphansByPrev[txIn.PreviousOutPoint]; !exists {
mp.orphansByPrev[txIn.PreviousOutPoint] =
make(map[chainhash.Hash]*dcrutil.Tx)
log.Infof("added orphans by pre entry for %v", txIn.PreviousOutPoint)
}
mp.orphansByPrev[txIn.PreviousOutPoint][*tx.Hash()] = tx
}
Expand Down Expand Up @@ -1247,7 +1252,12 @@ func (mp *TxPool) processOrphans(acceptedTx *dcrutil.Tx) []*dcrutil.Tx {
firstElement := processList.Remove(processList.Front())
processItem := firstElement.(*dcrutil.Tx)

prevOut := wire.OutPoint{Hash: *processItem.Hash()}
txType := stake.DetermineTxType(processItem.MsgTx())
tree := wire.TxTreeRegular
if txType != stake.TxTypeRegular {
tree = wire.TxTreeStake
}

for txOutIdx := range processItem.MsgTx().TxOut {
// Look up all orphans that redeem the output that is
// now available. This will typically only be one, but
Expand All @@ -1260,7 +1270,8 @@ func (mp *TxPool) processOrphans(acceptedTx *dcrutil.Tx) []*dcrutil.Tx {
// would otherwise make outputs unspendable.
//
// Skip to the next available output if there are none.
prevOut.Index = uint32(txOutIdx)
prevOut := wire.OutPoint{Hash: *processItem.Hash(),
Index: uint32(txOutIdx), Tree: tree}
orphans, exists := mp.orphansByPrev[prevOut]
if !exists {
continue
Expand Down
138 changes: 133 additions & 5 deletions mempool/mempool_test.go
Expand Up @@ -33,7 +33,7 @@ import (
const (
// singleInputTicketSize is the typical size of a normal P2PKH ticket
// in bytes when the ticket has one input, rounded up.
singleInputTicketSize = int64(300)
singleInputTicketSize int64 = 300
)

// fakeChain is used by the pool harness to provide generated test utxos and
Expand Down Expand Up @@ -504,6 +504,51 @@ func (p *poolHarness) CreateVote(ticket *dcrutil.Tx) (*dcrutil.Tx, error) {
return dcrutil.NewTx(vote), nil
}

// CreateRevocation creates a revocation using the provided ticket.
func (p *poolHarness) CreateRevocation(ticket *dcrutil.Tx) (*dcrutil.Tx, error) {
ticketPurchase := ticket.MsgTx()
ticketHash := ticketPurchase.TxHash()

// Parse the ticket purchase transaction and generate the revocation value.
ticketPayKinds, ticketHash160s, ticketValues, _, _, _ :=
stake.TxSStxStakeOutputInfo(ticketPurchase)
revocationValues := stake.CalculateRewards(ticketValues,
ticketPurchase.TxOut[0].Value, 0)

// Add the ticket input.
revocation := wire.NewMsgTx()
ticketOutPoint := wire.NewOutPoint(&ticketHash, 0, wire.TxTreeStake)
ticketInput := wire.NewTxIn(ticketOutPoint,
ticketPurchase.TxOut[ticketOutPoint.Index].Value, nil)
revocation.AddTxIn(ticketInput)

// All remaining outputs pay to the output destinations and amounts tagged
// by the ticket purchase.
for i, hash160 := range ticketHash160s {
scriptFn := txscript.PayToSSRtxPKHDirect
if ticketPayKinds[i] { // P2SH
scriptFn = txscript.PayToSSRtxSHDirect
}
// Error is checking for a nil hash160, just ignore it.
script, _ := scriptFn(hash160)
revocation.AddTxOut(wire.NewTxOut(revocationValues[i], script))
}

// Sign the input.
inputToSign := 0
redeemTicketScript := ticket.MsgTx().TxOut[0].PkScript
signedScript, err := txscript.SignTxOutput(p.chainParams, revocation, inputToSign,
redeemTicketScript, txscript.SigHashAll, p,
p, revocation.TxIn[inputToSign].SignatureScript, dcrec.STEcdsaSecp256k1)
if err != nil {
return nil, err
}

revocation.TxIn[0].SignatureScript = signedScript

return dcrutil.NewTx(revocation), nil
}

// newPoolHarness returns a new instance of a pool harness initialized with a
// fake chain and a TxPool bound to it that is configured with a policy suitable
// for testing. Also, the fake chain is populated with the returned spendable
Expand Down Expand Up @@ -697,6 +742,8 @@ func TestSimpleOrphanChain(t *testing.T) {
}
}

// TestTicketPurchaseOrphan ensures that ticket purchases are orphaned when
// referenced outputs spent are from missing transactions.
func TestTicketPurchaseOrphan(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -752,6 +799,8 @@ func TestTicketPurchaseOrphan(t *testing.T) {
testPoolMembership(tc, ticket, false, true)
}

// TestVoteOrphan ensures that votes are orphaned when referenced outputs
// spent are from missing transactions.
func TestVoteOrphan(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -779,7 +828,7 @@ func TestVoteOrphan(t *testing.T) {

vote, err := harness.CreateVote(ticket)
if err != nil {
t.Fatalf("unable to create vote transaction: %v", err)
t.Fatalf("unable to create vote: %v", err)
}

// Ensure the vote is rejected because it is an orphan.
Expand Down Expand Up @@ -829,6 +878,85 @@ func TestVoteOrphan(t *testing.T) {
testPoolMembership(tc, vote, false, true)
}

// TestRevocationOrphan ensures that revocations are orphaned when
// referenced outputs spent are from missing transactions.
func TestRevocationOrphan(t *testing.T) {
t.Parallel()

harness, spendableOuts, err := newPoolHarness(&chaincfg.MainNetParams)
if err != nil {
t.Fatalf("unable to create test pool: %v", err)
}
tc := &testContext{t, harness}

// Create a regular transaction from the first spendable output
// provided by the harness.
tx, err := harness.createTx(spendableOuts[0])
if err != nil {
t.Fatalf("unable to create transaction: %v", err)
}

// Create a ticket purchase transaction spending the outputs of the
// prior regular transaction.
ticket, err := harness.CreateTicketPurchase(tx, 40000)
if err != nil {
t.Fatalf("unable to create ticket purchase transaction: %v", err)
}

harness.chain.SetHeight(harness.chainParams.StakeEnabledHeight + 1)

revocation, err := harness.CreateRevocation(ticket)
if err != nil {
t.Fatalf("unable to create revocation: %v", err)
}

// Ensure the vote is rejected because it is an orphan.
_, err = harness.txPool.ProcessTransaction(revocation,
false, false, true)
if err == nil {
t.Fatalf("ProcessTransaction: accepted transaction references " +
"outputs of unknown or fully-spent transaction")
}

testPoolMembership(tc, revocation, false, false)

// Ensure the ticket is accepted as an orphan.
_, err = harness.txPool.ProcessTransaction(ticket,
true, false, true)
if err != nil {
t.Fatalf("ProcessTransaction: failed to accept valid "+
"transaction %v", err)
}

testPoolMembership(tc, ticket, true, false)

// Ensure the regular tx whose ouput is spent by the ticket is accepted.
_, err = harness.txPool.ProcessTransaction(tx, false, false, true)
if err != nil {
t.Fatalf("ProcessTransaction: failed to accept valid "+
"transaction %v", err)
}

testPoolMembership(tc, tx, false, true)

// Generate fake mined utxos for the regular transaction and the
// ticket created.
harness.AddFakeUTXO(tx, int64(tx.MsgTx().TxIn[0].BlockHeight))
harness.AddFakeUTXO(ticket, int64(ticket.MsgTx().TxIn[0].BlockHeight))

// Ensure the previously rejected revocation is accepted now since all referenced
// utxos can now be found.
_, err = harness.txPool.ProcessTransaction(revocation, false, false, true)
if err != nil {
t.Fatalf("ProcessTransaction: failed to accept orphan "+
"transaction %v", err)
}

testPoolMembership(tc, tx, false, true)
testPoolMembership(tc, ticket, false, true)
testPoolMembership(tc, revocation, false, true)
}

// TestOrphanReject ensures that orphans are properly rejected when the allow
// orphans flag is not set on ProcessTransaction.
func TestOrphanReject(t *testing.T) {
Expand Down Expand Up @@ -985,7 +1113,7 @@ func TestExpirationPruning(t *testing.T) {
expiringTxns := make([]*dcrutil.Tx, 0, numTxns)
for i := 0; i < numTxns; i++ {
tx, err := harness.CreateSignedTx([]spendableOutput{
txOutToSpendableOut(multiOutputTx, uint32(i)),
txOutToSpendableOut(multiOutputTx, uint32(i), wire.TxTreeRegular),
}, 1, uint32(nextBlockHeight+int64(i)+1))
if err != nil {
t.Fatalf("unable to create signed tx: %v", err)
Expand Down Expand Up @@ -1072,7 +1200,7 @@ func TestBasicOrphanRemoval(t *testing.T) {
nonChainedOrphanTx, err := harness.CreateSignedTx([]spendableOutput{{
amount: dcrutil.Amount(5000000000),
outPoint: wire.OutPoint{Hash: chainhash.Hash{}, Index: 0},
}}, 1)
}}, 1, uint32(harness.chain.BestHeight()+1))
if err != nil {
t.Fatalf("unable to create signed tx: %v", err)
}
Expand Down Expand Up @@ -1208,7 +1336,7 @@ func TestMultiInputOrphanDoubleSpend(t *testing.T) {
doubleSpendTx, err := harness.CreateSignedTx([]spendableOutput{
txOutToSpendableOut(chainedTxns[1], 0, wire.TxTreeRegular),
txOutToSpendableOut(chainedTxns[maxOrphans], 0, wire.TxTreeRegular),
}, 1)
}, 1, wire.NoExpiryValue)
if err != nil {
t.Fatalf("unable to create signed tx: %v", err)
}
Expand Down

0 comments on commit fb05050

Please sign in to comment.