Skip to content

Commit

Permalink
Merge pull request #90 from SiaFoundation/nate/add-indices-endpoint
Browse files Browse the repository at this point in the history
Add indices endpoint
  • Loading branch information
n8maninger authored Sep 6, 2024
2 parents d501d3f + 8b47464 commit 19687fa
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 34 deletions.
7 changes: 7 additions & 0 deletions api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,13 @@ func (c *Client) Transactions(ids []types.TransactionID) (resp []explorer.Transa
return
}

// TransactionChainIndices returns chain indices a transaction was
// included in.
func (c *Client) TransactionChainIndices(id types.TransactionID, offset, limit uint64) (resp []types.ChainIndex, err error) {
err = c.c.GET(fmt.Sprintf("/transactions/%s/indices?offset=%d&limit=%d", id, offset, limit), &resp)
return
}

// AddressSiacoinUTXOs returns the specified address' unspent outputs.
func (c *Client) AddressSiacoinUTXOs(address types.Address, offset, limit uint64) (resp []explorer.SiacoinOutput, err error) {
err = c.c.GET(fmt.Sprintf("/addresses/%s/utxos/siacoin?offset=%d&limit=%d", address, offset, limit), &resp)
Expand Down
29 changes: 27 additions & 2 deletions api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ type (
Metrics(id types.BlockID) (explorer.Metrics, error)
HostMetrics() (explorer.HostMetrics, error)
Transactions(ids []types.TransactionID) ([]explorer.Transaction, error)
TransactionChainIndices(id types.TransactionID, offset, limit uint64) ([]types.ChainIndex, error)
Balance(address types.Address) (sc types.Currency, immatureSC types.Currency, sf uint64, err error)
SiacoinElements(ids []types.SiacoinOutputID) (result []explorer.SiacoinOutput, err error)
SiafundElements(ids []types.SiafundOutputID) (result []explorer.SiafundOutput, err error)
Expand Down Expand Up @@ -262,6 +263,29 @@ func (s *server) transactionsIDHandler(jc jape.Context) {
jc.Encode(txns[0])
}

func (s *server) transactionsIDIndicesHandler(jc jape.Context) {
var id types.TransactionID
if jc.DecodeParam("id", &id) != nil {
return
}

limit := uint64(100)
offset := uint64(0)
if jc.DecodeForm("limit", &limit) != nil || jc.DecodeForm("offset", &offset) != nil {
return
}

if limit > 500 {
limit = 500
}

indices, err := s.e.TransactionChainIndices(id, offset, limit)
if jc.Check("failed to get transaction indices", err) != nil {
return
}
jc.Encode(indices)
}

func (s *server) transactionsBatchHandler(jc jape.Context) {
var ids []types.TransactionID
if jc.Decode(&ids) != nil {
Expand Down Expand Up @@ -507,8 +531,9 @@ func NewServer(e Explorer, cm ChainManager, s Syncer) http.Handler {

"GET /blocks/:id": srv.blocksIDHandler,

"GET /transactions/:id": srv.transactionsIDHandler,
"POST /transactions": srv.transactionsBatchHandler,
"GET /transactions/:id": srv.transactionsIDHandler,
"POST /transactions": srv.transactionsBatchHandler,
"GET /transactions/:id/indices": srv.transactionsIDIndicesHandler,

"GET /addresses/:address/utxos/siacoin": srv.addressessAddressUtxosSiacoinHandler,
"GET /addresses/:address/utxos/siafund": srv.addressessAddressUtxosSiafundHandler,
Expand Down
8 changes: 8 additions & 0 deletions explorer/explorer.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ type Store interface {
Metrics(id types.BlockID) (Metrics, error)
HostMetrics() (HostMetrics, error)
Transactions(ids []types.TransactionID) ([]Transaction, error)
TransactionChainIndices(txid types.TransactionID, offset, limit uint64) ([]types.ChainIndex, error)
UnspentSiacoinOutputs(address types.Address, offset, limit uint64) ([]SiacoinOutput, error)
UnspentSiafundOutputs(address types.Address, offset, limit uint64) ([]SiafundOutput, error)
AddressEvents(address types.Address, offset, limit uint64) (events []Event, err error)
Expand Down Expand Up @@ -196,6 +197,13 @@ func (e *Explorer) Transactions(ids []types.TransactionID) ([]Transaction, error
return e.s.Transactions(ids)
}

// TransactionChainIndices returns the chain indices of the blocks the transaction
// was included in. If the transaction has not been included in any blocks, the
// result will be nil,nil.
func (e *Explorer) TransactionChainIndices(id types.TransactionID, offset, limit uint64) ([]types.ChainIndex, error) {
return e.s.TransactionChainIndices(id, offset, limit)
}

// UnspentSiacoinOutputs returns the unspent siacoin outputs owned by the
// specified address.
func (e *Explorer) UnspentSiacoinOutputs(address types.Address, offset, limit uint64) ([]SiacoinOutput, error) {
Expand Down
1 change: 0 additions & 1 deletion explorer/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,6 @@ type FileContractRevision struct {
// A Transaction is a transaction that uses the wrapped types above.
type Transaction struct {
ID types.TransactionID `json:"id"`
ChainIndices []types.ChainIndex `json:"chainIndices,omitempty"`
SiacoinInputs []SiacoinInput `json:"siacoinInputs,omitempty"`
SiacoinOutputs []SiacoinOutput `json:"siacoinOutputs,omitempty"`
SiafundInputs []SiafundInput `json:"siafundInputs,omitempty"`
Expand Down
30 changes: 28 additions & 2 deletions persist/sqlite/consensus_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,19 @@ func TestSendTransactions(t *testing.T) {
check(t, "siafunds", expectSF, sf)
}

checkChainIndices := func(t *testing.T, txnID types.TransactionID, expected []types.ChainIndex) {
indices, err := db.TransactionChainIndices(txnID, 0, 100)
switch {
case err != nil:
t.Fatal(err)
case len(indices) != len(expected):
t.Fatalf("expected %d indices, got %d", len(expected), len(indices))
}
for i := range indices {
check(t, "index", expected[i], indices[i])
}
}

checkTransaction := func(expectTxn types.Transaction, gotTxn explorer.Transaction) {
check(t, "siacoin inputs", len(expectTxn.SiacoinInputs), len(gotTxn.SiacoinInputs))
check(t, "siacoin outputs", len(expectTxn.SiacoinOutputs), len(gotTxn.SiacoinOutputs))
Expand Down Expand Up @@ -578,7 +591,7 @@ func TestSendTransactions(t *testing.T) {
// with the actual transactions
for i := range b.Transactions {
checkTransaction(b.Transactions[i], block.Transactions[i])
check(t, "chain indices", []types.ChainIndex{cm.Tip()}, block.Transactions[i].ChainIndices)
checkChainIndices(t, b.Transactions[i].ID(), []types.ChainIndex{cm.Tip()})

txns, err := db.Transactions([]types.TransactionID{b.Transactions[i].ID()})
if err != nil {
Expand Down Expand Up @@ -1625,6 +1638,19 @@ func TestRevertSendTransactions(t *testing.T) {
check(t, "siafunds", expectSF, sf)
}

checkChainIndices := func(t *testing.T, txnID types.TransactionID, expected []types.ChainIndex) {
indices, err := db.TransactionChainIndices(txnID, 0, 100)
switch {
case err != nil:
t.Fatal(err)
case len(indices) != len(expected):
t.Fatalf("expected %d indices, got %d", len(expected), len(indices))
}
for i := range indices {
check(t, "index", expected[i], indices[i])
}
}

checkTransaction := func(expectTxn types.Transaction, gotTxn explorer.Transaction) {
check(t, "siacoin inputs", len(expectTxn.SiacoinInputs), len(gotTxn.SiacoinInputs))
check(t, "siacoin outputs", len(expectTxn.SiacoinOutputs), len(gotTxn.SiacoinOutputs))
Expand Down Expand Up @@ -1782,7 +1808,7 @@ func TestRevertSendTransactions(t *testing.T) {
// with the actual transactions
for i := range b.Transactions {
checkTransaction(b.Transactions[i], block.Transactions[i])
check(t, "chain indices", []types.ChainIndex{cm.Tip()}, block.Transactions[i].ChainIndices)
checkChainIndices(t, b.Transactions[i].ID(), []types.ChainIndex{cm.Tip()})

txns, err := db.Transactions([]types.TransactionID{b.Transactions[i].ID()})
if err != nil {
Expand Down
54 changes: 25 additions & 29 deletions persist/sqlite/transactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,32 @@ import (
"go.sia.tech/explored/explorer"
)

// transactionChainIndices returns the chain indices of the blocks the transaction
// was in.
func transactionChainIndices(tx *txn, txnIDs []int64) (map[int64][]types.ChainIndex, error) {
query := `SELECT bt.transaction_id, bt.block_id, b.height
FROM block_transactions bt
JOIN blocks b ON bt.block_id = b.id
WHERE bt.transaction_id IN (` + queryPlaceHolders(len(txnIDs)) + `)
ORDER BY bt.block_order ASC`
rows, err := tx.Query(query, queryArgs(txnIDs)...)
if err != nil {
return nil, err
}
defer rows.Close()

result := make(map[int64][]types.ChainIndex)
for rows.Next() {
var txnID int64
var index types.ChainIndex
if err := rows.Scan(&txnID, decode(&index.ID), decode(&index.Height)); err != nil {
return nil, fmt.Errorf("failed to scan chain index: %w", err)
// TransactionChainIndices returns the chain indices of the blocks the transaction
// was included in. If the transaction has not been included in any blocks, the
// result will be nil,nil.
func (s *Store) TransactionChainIndices(txnID types.TransactionID, offset, limit uint64) (indices []types.ChainIndex, err error) {
err = s.transaction(func(tx *txn) error {
rows, err := tx.Query(`SELECT DISTINCT b.id, b.height FROM blocks b
INNER JOIN block_transactions bt ON (bt.block_id = b.id)
INNER JOIN transactions t ON (t.id = bt.transaction_id)
WHERE t.transaction_id = ?
ORDER BY b.height DESC
LIMIT ? OFFSET ?`, encode(txnID), limit, offset)
if err != nil {
return err
}
result[txnID] = append(result[txnID], index)
}
return result, nil
defer rows.Close()

for rows.Next() {
var index types.ChainIndex
if err := rows.Scan(decode(&index.ID), decode(&index.Height)); err != nil {
return fmt.Errorf("failed to scan chain index: %w", err)
}
indices = append(indices, index)
}
return rows.Err()
})
return
}

// transactionMinerFee returns the miner fees for each transaction.
Expand Down Expand Up @@ -484,11 +486,6 @@ func getTransactions(tx *txn, idMap map[int64]transactionID) ([]explorer.Transac
dbIDs[id.order] = dbID
}

txnChainIndices, err := transactionChainIndices(tx, dbIDs)
if err != nil {
return nil, fmt.Errorf("getTransactions: failed to get chain indices: %w", err)
}

txnArbitraryData, err := transactionArbitraryData(tx, dbIDs)
if err != nil {
return nil, fmt.Errorf("getTransactions: failed to get arbitrary data: %w", err)
Expand Down Expand Up @@ -543,7 +540,6 @@ func getTransactions(tx *txn, idMap map[int64]transactionID) ([]explorer.Transac
for _, dbID := range dbIDs {
txn := explorer.Transaction{
ID: idMap[dbID].id,
ChainIndices: txnChainIndices[dbID],
SiacoinInputs: txnSiacoinInputs[dbID],
SiacoinOutputs: txnSiacoinOutputs[dbID],
SiafundInputs: txnSiafundInputs[dbID],
Expand Down

0 comments on commit 19687fa

Please sign in to comment.