Skip to content

Commit

Permalink
Fix "transaction spends nonexisting siacoin output" when refreshing c…
Browse files Browse the repository at this point in the history
…ontracts (#1111)

`UnconfirmedParents` would only ever return direct parents for a txn but
not grandparents and so on. Leading to hosts not knowing about certain
parents yet and txns failing.
  • Loading branch information
ChrisSchinnerl authored Mar 28, 2024
1 parent 82246d9 commit c3155f8
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 15 deletions.
41 changes: 26 additions & 15 deletions internal/node/transactionpool.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package node

import (
"errors"
"slices"

"go.sia.tech/core/types"
"go.sia.tech/renterd/bus"
Expand Down Expand Up @@ -41,32 +42,42 @@ func (tp txpool) AcceptTransactionSet(txns []types.Transaction) error {
}

func (tp txpool) UnconfirmedParents(txn types.Transaction) ([]types.Transaction, error) {
pool := tp.Transactions()
return unconfirmedParents(txn, tp.Transactions()), nil
}

func (tp txpool) Subscribe(subscriber modules.TransactionPoolSubscriber) {
tp.tp.TransactionPoolSubscribe(subscriber)
}

func (tp txpool) Close() error {
return tp.tp.Close()
}

func unconfirmedParents(txn types.Transaction, pool []types.Transaction) []types.Transaction {
outputToParent := make(map[types.SiacoinOutputID]*types.Transaction)
for i, txn := range pool {
for j := range txn.SiacoinOutputs {
outputToParent[txn.SiacoinOutputID(j)] = &pool[i]
}
}
var parents []types.Transaction
txnsToCheck := []*types.Transaction{&txn}
seen := make(map[types.TransactionID]bool)
for _, sci := range txn.SiacoinInputs {
if parent, ok := outputToParent[sci.ParentID]; ok {
if txid := parent.ID(); !seen[txid] {
seen[txid] = true
parents = append(parents, *parent)
for len(txnsToCheck) > 0 {
nextTxn := txnsToCheck[0]
txnsToCheck = txnsToCheck[1:]
for _, sci := range nextTxn.SiacoinInputs {
if parent, ok := outputToParent[sci.ParentID]; ok {
if txid := parent.ID(); !seen[txid] {
seen[txid] = true
parents = append(parents, *parent)
txnsToCheck = append(txnsToCheck, parent)
}
}
}
}
return parents, nil
}

func (tp txpool) Subscribe(subscriber modules.TransactionPoolSubscriber) {
tp.tp.TransactionPoolSubscribe(subscriber)
}

func (tp txpool) Close() error {
return tp.tp.Close()
slices.Reverse(parents)
return parents
}

func NewTransactionPool(tp modules.TransactionPool) bus.TransactionPool {
Expand Down
40 changes: 40 additions & 0 deletions internal/node/transactionpool_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package node

import (
"reflect"
"testing"

"go.sia.tech/core/types"
)

func TestUnconfirmedParents(t *testing.T) {
grandparent := types.Transaction{
SiacoinOutputs: []types.SiacoinOutput{{}},
}
parent := types.Transaction{
SiacoinInputs: []types.SiacoinInput{
{
ParentID: grandparent.SiacoinOutputID(0),
},
},
SiacoinOutputs: []types.SiacoinOutput{{}},
}
txn := types.Transaction{
SiacoinInputs: []types.SiacoinInput{
{
ParentID: parent.SiacoinOutputID(0),
},
},
SiacoinOutputs: []types.SiacoinOutput{{}},
}
pool := []types.Transaction{grandparent, parent}

parents := unconfirmedParents(txn, pool)
if len(parents) != 2 {
t.Fatalf("expected 2 parents, got %v", len(parents))
} else if !reflect.DeepEqual(parents[0], grandparent) {
t.Fatalf("expected grandparent")
} else if !reflect.DeepEqual(parents[1], parent) {
t.Fatalf("expected parent")
}
}

0 comments on commit c3155f8

Please sign in to comment.