-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add initial implementation of DB-backed tx store
- Loading branch information
Showing
7 changed files
with
235 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
13 changes: 13 additions & 0 deletions
13
cmd/soroban-rpc/internal/db/migrations/02_transactions.sql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
-- +migrate Up | ||
|
||
-- index to find transactions in ledgers by hash | ||
CREATE TABLE transactions ( | ||
hash BLOB PRIMARY KEY, | ||
ledger_sequence INTEGER NOT NULL, | ||
application_order INTEGER NOT NULL, | ||
FOREIGN KEY (ledger_sequence) | ||
REFERENCES ledger_close_meta (sequence) | ||
); | ||
|
||
-- +migrate Down | ||
drop table transactions cascade; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
package db | ||
|
||
import ( | ||
"encoding/hex" | ||
"fmt" | ||
|
||
sq "github.com/Masterminds/squirrel" | ||
"github.com/stellar/go/ingest" | ||
"github.com/stellar/go/support/db" | ||
"github.com/stellar/go/xdr" | ||
"github.com/stellar/soroban-rpc/cmd/soroban-rpc/internal/ledgerbucketwindow" | ||
"github.com/stellar/soroban-rpc/cmd/soroban-rpc/internal/transactions" | ||
) | ||
|
||
const ( | ||
transactionTableName = "transactions" | ||
) | ||
|
||
type TransactionHandler struct { | ||
stmtCache *sq.StmtCache | ||
db db.SessionInterface | ||
passphrase string | ||
} | ||
|
||
func NewTransactionHandler(db db.SessionInterface, passphrase string) *TransactionHandler { | ||
return &TransactionHandler{db: db, passphrase: passphrase} | ||
} | ||
|
||
func (txn *TransactionHandler) InsertTransactions(lcm xdr.LedgerCloseMeta) error { | ||
reader, err := ingest.NewLedgerTransactionReaderFromLedgerCloseMeta(txn.passphrase, lcm) | ||
if err != nil { | ||
return err | ||
} | ||
txCount := lcm.CountTransactions() | ||
transactions := make(map[xdr.Hash]ingest.LedgerTransaction, txCount) | ||
|
||
for i := 0; i < txCount; i++ { | ||
tx, err := reader.Read() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if tx.Envelope.IsFeeBump() { | ||
transactions[tx.Result.InnerHash()] = tx | ||
} | ||
transactions[tx.Result.TransactionHash] = tx | ||
} | ||
|
||
query := sq. | ||
Insert(transactionTableName). | ||
RunWith(txn.stmtCache). | ||
Columns("hash", "ledger_sequence", "application_order") | ||
for hash, tx := range transactions { | ||
query = query.Values(hash, lcm.LedgerSequence(), tx.Index) | ||
} | ||
|
||
_, err = query.Exec() | ||
return err | ||
} | ||
|
||
func (txn *TransactionHandler) GetLedgerRange() ( | ||
ledgerbucketwindow.LedgerRange, | ||
error, | ||
) { | ||
var ledgerRange ledgerbucketwindow.LedgerRange | ||
newestQ := sq. | ||
Select("meta"). | ||
From(ledgerCloseMetaTableName). | ||
OrderBy("sequence DESC"). | ||
Limit(1). | ||
RunWith(txn.stmtCache). | ||
QueryRow() | ||
oldestQ := sq. | ||
Select("meta"). | ||
From(ledgerCloseMetaTableName). | ||
OrderBy("sequence ASC"). | ||
Limit(1). | ||
RunWith(txn.stmtCache). | ||
QueryRow() | ||
|
||
var row1, row2 []byte | ||
if err := newestQ.Scan(&row1); err != nil { | ||
return ledgerRange, err | ||
} | ||
if err := oldestQ.Scan(&row2); err != nil { | ||
return ledgerRange, err | ||
} | ||
|
||
var lcm1, lcm2 xdr.LedgerCloseMeta | ||
if err := lcm1.UnmarshalBinary(row1); err != nil { | ||
return ledgerRange, err | ||
} | ||
if err := lcm2.UnmarshalBinary(row2); err != nil { | ||
return ledgerRange, err | ||
} | ||
|
||
return ledgerbucketwindow.LedgerRange{ | ||
FirstLedger: ledgerbucketwindow.LedgerInfo{ | ||
Sequence: lcm1.LedgerSequence(), | ||
CloseTime: int64(lcm1.LedgerHeaderHistoryEntry().Header.ScpValue.CloseTime), | ||
}, | ||
LastLedger: ledgerbucketwindow.LedgerInfo{ | ||
Sequence: lcm2.LedgerSequence(), | ||
CloseTime: int64(lcm2.LedgerHeaderHistoryEntry().Header.ScpValue.CloseTime), | ||
}, | ||
}, nil | ||
} | ||
|
||
func (txn *TransactionHandler) GetTransactionByHash(hash string) ( | ||
xdr.LedgerCloseMeta, ingest.LedgerTransaction, error, | ||
) { | ||
rows := sq. | ||
Select("t.application_order", "lcm.meta"). | ||
From(fmt.Sprintf("%s t", transactionTableName)). | ||
Join(fmt.Sprintf("%s lcm ON (t.ledger_sequence = lcm.sequence)", ledgerCloseMetaTableName)). | ||
Limit(1). | ||
RunWith(txn.stmtCache). | ||
QueryRow() | ||
|
||
var row struct { | ||
txIndex int | ||
meta []byte | ||
} | ||
if err := rows.Scan(&row); err != nil { | ||
return xdr.LedgerCloseMeta{}, ingest.LedgerTransaction{}, err | ||
} | ||
|
||
var lcm xdr.LedgerCloseMeta | ||
if err := lcm.UnmarshalBinary(row.meta); err != nil { | ||
return lcm, ingest.LedgerTransaction{}, err | ||
} | ||
|
||
reader, err := ingest.NewLedgerTransactionReaderFromLedgerCloseMeta(txn.passphrase, lcm) | ||
reader.Seek(row.txIndex - 1) | ||
if err != nil { | ||
return lcm, ingest.LedgerTransaction{}, err | ||
} | ||
|
||
ledgerTx, err := reader.Read() | ||
return lcm, ledgerTx, err | ||
} | ||
|
||
// GetTransaction conforms to the interface in | ||
// methods/get_transaction.go#NewGetTransactionHandler so that it can be used | ||
// directly against the RPC handler. | ||
func (txn *TransactionHandler) GetTransaction(hash xdr.Hash) ( | ||
transactions.Transaction, bool, ledgerbucketwindow.LedgerRange, | ||
) { | ||
tx := transactions.Transaction{} | ||
|
||
ledgerRange, err := txn.GetLedgerRange() | ||
if err != nil { | ||
return tx, false, ledgerRange | ||
} | ||
|
||
lcm, ingestTx, err := txn.GetTransactionByHash(hex.EncodeToString(hash[:])) | ||
if err != nil { | ||
return tx, false, ledgerRange | ||
} | ||
|
||
// | ||
// On-the-fly ingestion: extract all of the fields | ||
// | ||
|
||
if tx.Result, err = ingestTx.Result.MarshalBinary(); err != nil { | ||
return tx, false, ledgerRange | ||
} | ||
if tx.Meta, err = ingestTx.UnsafeMeta.MarshalBinary(); err != nil { | ||
return tx, false, ledgerRange | ||
} | ||
if tx.Envelope, err = ingestTx.Envelope.MarshalBinary(); err != nil { | ||
return tx, false, ledgerRange | ||
} | ||
if events, errr := ingestTx.GetDiagnosticEvents(); errr != nil { | ||
tx.Events = make([][]byte, 0, len(events)) | ||
for _, event := range events { | ||
bytes, ierr := event.MarshalBinary() | ||
if ierr != nil { | ||
return tx, false, ledgerRange | ||
} | ||
tx.Events = append(tx.Events, bytes) | ||
} | ||
} | ||
tx.FeeBump = ingestTx.Envelope.IsFeeBump() | ||
tx.ApplicationOrder = int32(ingestTx.Index) | ||
tx.Successful = ingestTx.Result.Successful() | ||
tx.Ledger = ledgerbucketwindow.LedgerInfo{ | ||
Sequence: lcm.LedgerSequence(), | ||
CloseTime: int64(lcm.LedgerHeaderHistoryEntry().Header.ScpValue.CloseTime), | ||
} | ||
|
||
return tx, true, ledgerRange | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters