Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

api: optimize /transactions/pending/{txid} endpoint #5891

Merged
merged 6 commits into from
Jan 17, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions ledger/ledger.go
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,11 @@
return l.txTail.checkDup(currentProto, current, firstValid, lastValid, txid, txl)
}

// CheckConfirmed return whether a transaction was confirmed in last up to MaxTxnLife rounds.
func (l *Ledger) CheckConfirmed(txid transactions.Txid) (basics.Round, bool) {
return l.txTail.checkConfirmed(txid)

Check warning on line 662 in ledger/ledger.go

View check run for this annotation

Codecov / codecov/patch

ledger/ledger.go#L661-L662

Added lines #L661 - L662 were not covered by tests
}

// Latest returns the latest known block round added to the ledger.
func (l *Ledger) Latest() basics.Round {
return l.blockQ.latest()
Expand Down
41 changes: 29 additions & 12 deletions ledger/txtail.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
// lastValid, recent, lowWaterMark, roundTailHashes, roundTailSerializedDeltas and blockHeaderData.
tailMu deadlock.RWMutex

lastValid map[basics.Round]map[transactions.Txid]struct{} // map tx.LastValid -> tx confirmed set
lastValid map[basics.Round]map[transactions.Txid]basics.Round // map tx.LastValid -> tx confirmed set
jannotti marked this conversation as resolved.
Show resolved Hide resolved

// duplicate detection queries with LastValid before
// lowWaterMark are not guaranteed to succeed
Expand Down Expand Up @@ -115,14 +115,18 @@
}

t.lowWaterMark = l.Latest()
t.lastValid = make(map[basics.Round]map[transactions.Txid]struct{})
t.lastValid = make(map[basics.Round]map[transactions.Txid]basics.Round)
t.recent = make(map[basics.Round]roundLeases)

// the lastValid is a temporary map used during the execution of
// loadFromDisk, allowing us to construct the lastValid maps in their
// optimal size. This would ensure that upon startup, we don't preallocate
// more memory than we truly need.
lastValid := make(map[basics.Round][]transactions.Txid)
type lastValidEntry struct {
rnd basics.Round
txid transactions.Txid
}
lastValid := make(map[basics.Round][]lastValidEntry)

// the roundTailHashes and blockHeaderData need a single element to start with
// in order to allow lookups on zero offsets when they are empty (new database)
Expand Down Expand Up @@ -153,16 +157,16 @@
list := lastValid[txTailRound.LastValid[i]]
// if the list reached capacity, resize.
if len(list) == cap(list) {
var newList []transactions.Txid
var newList []lastValidEntry
if cap(list) == 0 {
newList = make([]transactions.Txid, 0, initialLastValidArrayLen)
newList = make([]lastValidEntry, 0, initialLastValidArrayLen)
} else {
newList = make([]transactions.Txid, len(list), len(list)*2)
newList = make([]lastValidEntry, len(list), len(list)*2)

Check warning on line 164 in ledger/txtail.go

View check run for this annotation

Codecov / codecov/patch

ledger/txtail.go#L164

Added line #L164 was not covered by tests
}
copy(newList[:], list[:])
list = newList
}
list = append(list, txTailRound.TxnIDs[i])
list = append(list, lastValidEntry{txTailRound.Hdr.Round, txTailRound.TxnIDs[i]})
lastValid[txTailRound.LastValid[i]] = list
}
}
Expand All @@ -173,9 +177,9 @@

// add all the entries in roundsLastValids to their corresponding map entry in t.lastValid
for lastValid, list := range lastValid {
lastValueMap := make(map[transactions.Txid]struct{}, len(list))
for _, id := range list {
lastValueMap[id] = struct{}{}
lastValueMap := make(map[transactions.Txid]basics.Round, len(list))
for _, entry := range list {
lastValueMap[entry.txid] = entry.rnd
}
t.lastValid[lastValid] = lastValueMap
}
Expand Down Expand Up @@ -210,9 +214,9 @@

for txid, txnInc := range delta.Txids {
if _, ok := t.lastValid[txnInc.LastValid]; !ok {
t.lastValid[txnInc.LastValid] = make(map[transactions.Txid]struct{})
t.lastValid[txnInc.LastValid] = make(map[transactions.Txid]basics.Round)
}
t.lastValid[txnInc.LastValid][txid] = struct{}{}
t.lastValid[txnInc.LastValid][txid] = blk.BlockHeader.Round

tail.TxnIDs[txnInc.Intra] = txid
tail.LastValid[txnInc.Intra] = txnInc.LastValid
Expand Down Expand Up @@ -381,6 +385,19 @@
return nil
}

// checkConfirmed test to see if the given transaction id already exists.
func (t *txTail) checkConfirmed(txid transactions.Txid) (basics.Round, bool) {
t.tailMu.RLock()
defer t.tailMu.RUnlock()

Check warning on line 391 in ledger/txtail.go

View check run for this annotation

Codecov / codecov/patch

ledger/txtail.go#L389-L391

Added lines #L389 - L391 were not covered by tests

for _, lastValid := range t.lastValid {
if rnd, confirmed := lastValid[txid]; confirmed {
return rnd, true

Check warning on line 395 in ledger/txtail.go

View check run for this annotation

Codecov / codecov/patch

ledger/txtail.go#L393-L395

Added lines #L393 - L395 were not covered by tests
}
}
return 0, false

Check warning on line 398 in ledger/txtail.go

View check run for this annotation

Codecov / codecov/patch

ledger/txtail.go#L398

Added line #L398 was not covered by tests
}

func (t *txTail) recentTailHash(offset uint64, retainSize uint64) (crypto.Digest, error) {
// prepare a buffer to hash.
buffer := make([]byte, (retainSize)*crypto.DigestSize)
Expand Down
40 changes: 5 additions & 35 deletions node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -653,40 +653,10 @@
// Keep looking in the ledger.
}

var maxLife basics.Round
latest := node.ledger.Latest()
proto, err := node.ledger.ConsensusParams(latest)
if err == nil {
maxLife = basics.Round(proto.MaxTxnLife)
} else {
node.log.Errorf("node.GetPendingTransaction: cannot get consensus params for latest round %v", latest)
}

// Search from newest to oldest round up to the max life of a transaction.
maxRound := latest
minRound := maxRound.SubSaturate(maxLife)

// Since we're using uint64, if the minRound is 0, we need to check for an underflow.
if minRound == 0 {
minRound++
}

// If we did find the transaction, we know there is no point
// checking rounds earlier or later than validity rounds
if found {
if tx.Txn.FirstValid > minRound {
minRound = tx.Txn.FirstValid
}

if tx.Txn.LastValid < maxRound {
maxRound = tx.Txn.LastValid
}
}

for r := maxRound; r >= minRound; r-- {
tx, found, err := node.ledger.LookupTxid(txID, r)
if err != nil || !found {
continue
if r, confirmed := node.ledger.CheckConfirmed(txID); confirmed {
tx, foundBlk, err := node.ledger.LookupTxid(txID, r)
if err != nil || !foundBlk {
return res, found

Check warning on line 659 in node/node.go

View check run for this annotation

Codecov / codecov/patch

node/node.go#L656-L659

Added lines #L656 - L659 were not covered by tests
}
return TxnWithStatus{
Txn: tx.SignedTxn,
Expand All @@ -696,7 +666,7 @@
}

// Return whatever we found in the pool (if anything).
return
return res, found

Check warning on line 669 in node/node.go

View check run for this annotation

Codecov / codecov/patch

node/node.go#L669

Added line #L669 was not covered by tests
}

// Status returns a StatusReport structure reporting our status as Active and with our ledger's LastRound
Expand Down