Skip to content

Commit

Permalink
add api endpoints for contracts
Browse files Browse the repository at this point in the history
  • Loading branch information
chris124567 committed Feb 28, 2024
1 parent 6e349ec commit cfdef9a
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 6 deletions.
12 changes: 12 additions & 0 deletions api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,15 @@ func (c *Client) AddressBalance(address types.Address) (resp AddressBalanceRespo
err = c.c.GET(fmt.Sprintf("/explorer/addresses/%s/balance", address), &resp)
return
}

// Contract returns the file contract with the specified ID.
func (c *Client) Contract(id types.FileContractID) (resp explorer.FileContract, err error) {
err = c.c.GET(fmt.Sprintf("/explorer/contracts/%s", id), &resp)
return
}

// Contracts returns the transactions with the specified IDs.
func (c *Client) Contracts(ids []types.TransactionID) (resp []explorer.FileContract, err error) {
err = c.c.POST("/explorer/contracts", ids, &resp)

Check failure on line 109 in api/client.go

View workflow job for this annotation

GitHub Actions / test (macos-latest, 1.22)

Client has wrong request type for POST /explorer/contracts (got []go.sia.tech/core/types.TransactionID, should be []go.sia.tech/core/types.FileContractID)

Check failure on line 109 in api/client.go

View workflow job for this annotation

GitHub Actions / test (macos-latest, 1.22)

Analyzer warning in api

Client has wrong request type for POST /explorer/contracts (got []go.sia.tech/core/types.TransactionID, should be []go.sia.tech/core/types.FileContractID)

Check failure on line 109 in api/client.go

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 1.21)

Client has wrong request type for POST /explorer/contracts (got []go.sia.tech/core/types.TransactionID, should be []go.sia.tech/core/types.FileContractID)

Check failure on line 109 in api/client.go

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 1.21)

Analyzer warning in api

Client has wrong request type for POST /explorer/contracts (got []go.sia.tech/core/types.TransactionID, should be []go.sia.tech/core/types.FileContractID)
return
}
49 changes: 44 additions & 5 deletions api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,18 @@ type (
Balance(address types.Address) (sc types.Currency, sf uint64, err error)
UnspentSiacoinOutputs(address types.Address, limit, offset uint64) ([]explorer.SiacoinOutput, error)
UnspentSiafundOutputs(address types.Address, limit, offset uint64) ([]explorer.SiafundOutput, error)
Contracts(ids []types.FileContractID) (result []explorer.FileContract, err error)
}
)

const (
maxIDs = 5000
)

var (
errTooManyIDs = fmt.Errorf("too many IDs provided (provide less than %d)", maxIDs)
)

type server struct {
cm ChainManager
e Explorer
Expand Down Expand Up @@ -171,11 +180,6 @@ func (s *server) explorerTransactionsIDHandler(jc jape.Context) {
}

func (s *server) explorerTransactionsHandler(jc jape.Context) {
const (
maxIDs = 5000
)
errTooManyIDs := fmt.Errorf("too many IDs provided (provide less than %d)", maxIDs)

var ids []types.TransactionID
if jc.Decode(&ids) != nil {
return
Expand Down Expand Up @@ -235,6 +239,39 @@ func (s *server) explorerAddressessAddressBalanceHandler(jc jape.Context) {
})
}

func (s *server) explorerContractIDHandler(jc jape.Context) {
errNotFound := errors.New("no contract found")

var id types.FileContractID
if jc.DecodeParam("id", &id) != nil {
return
}
fcs, err := s.e.Contracts([]types.FileContractID{id})
if jc.Check("failed to get contract", err) != nil {
return
} else if len(fcs) == 0 {
jc.Error(errNotFound, http.StatusNotFound)
return
}
jc.Encode(fcs[0])
}

func (s *server) explorerContractsHandler(jc jape.Context) {
var ids []types.FileContractID
if jc.Decode(&ids) != nil {
return
} else if len(ids) > maxIDs {
jc.Error(errTooManyIDs, http.StatusBadRequest)
return
}

fcs, err := s.e.Contracts(ids)
if jc.Check("failed to get contracts", err) != nil {
return
}
jc.Encode(fcs)
}

// NewServer returns an HTTP handler that serves the explored API.
func NewServer(e Explorer, cm ChainManager, s Syncer) http.Handler {
srv := server{
Expand All @@ -257,5 +294,7 @@ func NewServer(e Explorer, cm ChainManager, s Syncer) http.Handler {
"POST /explorer/transactions": srv.explorerTransactionsHandler,
"GET /explorer/addresses/:address/utxos": srv.explorerAddressessAddressUtxosHandler,
"GET /explorer/addresses/:address/balance": srv.explorerAddressessAddressBalanceHandler,
"GET /explorer/contracts/:id": srv.explorerContractIDHandler,
"POST /explorer/contracts": srv.explorerContractsHandler,
})
}
6 changes: 6 additions & 0 deletions explorer/explorer.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type Store interface {
UnspentSiacoinOutputs(address types.Address, limit, offset uint64) ([]SiacoinOutput, error)
UnspentSiafundOutputs(address types.Address, limit, offset uint64) ([]SiafundOutput, error)
Balance(address types.Address) (sc types.Currency, sf uint64, err error)
Contracts(ids []types.FileContractID) (result []FileContract, err error)

MerkleProof(leafIndex uint64) ([]types.Hash256, error)
}
Expand Down Expand Up @@ -72,3 +73,8 @@ func (e *Explorer) UnspentSiafundOutputs(address types.Address, limit, offset ui
func (e *Explorer) Balance(address types.Address) (sc types.Currency, sf uint64, err error) {
return e.s.Balance(address)
}

// Contracts returns the contracts with the specified IDs.
func (e *Explorer) Contracts(ids []types.FileContractID) (result []FileContract, err error) {
return e.s.Contracts(ids)
}
41 changes: 41 additions & 0 deletions persist/sqlite/contracts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package sqlite

import (
"fmt"

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

// Contracts implements explorer.Store.
func (s *Store) Contracts(ids []types.FileContractID) (result []explorer.FileContract, err error) {
encodedIDs := func(ids []types.FileContractID) []any {
result := make([]any, len(ids))
for i, id := range ids {
result[i] = dbEncode(id)
}
return result
}

err = s.transaction(func(tx txn) error {
query := `SELECT fc1.contract_id, fc1.leaf_index, fc1.merkle_proof, fc1.resolved, fc1.valid, fc1.filesize, fc1.file_merkle_root, fc1.window_start, fc1.window_end, fc1.payout, fc1.unlock_hash, fc1.revision_number
FROM file_contract_elements fc1
WHERE fc1.contract_id IN (` + queryPlaceHolders(len(ids)) + `)
AND fc1.revision_number = (SELECT max(revision_number) FROM file_contract_elements fc2 WHERE fc2.contract_id = fc1.contract_id)`
rows, err := tx.Query(query, encodedIDs(ids)...)
if err != nil {
return err
}
defer rows.Close()

for rows.Next() {
var fc explorer.FileContract
if err := rows.Scan(dbDecode(&fc.StateElement.ID), dbDecode(&fc.StateElement.LeafIndex), dbDecode(&fc.StateElement.MerkleProof), &fc.Resolved, &fc.Valid, &fc.Filesize, dbDecode(&fc.FileMerkleRoot), &fc.WindowStart, &fc.WindowEnd, dbDecode(&fc.Payout), dbDecode(&fc.UnlockHash), &fc.RevisionNumber); err != nil {
return fmt.Errorf("failed to scan transaction: %w", err)
}
result = append(result, fc)
}
return nil
})
return
}
2 changes: 2 additions & 0 deletions persist/sqlite/init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ CREATE TABLE file_contract_elements (
UNIQUE(contract_id, revision_number)
);

CREATE INDEX file_contract_elements_contract_id_index ON file_contract_elements(contract_id);

CREATE TABLE file_contract_valid_proof_outputs (
contract_id INTEGER REFERENCES file_contract_elements(id) ON DELETE CASCADE NOT NULL,
contract_order INTEGER NOT NULL,
Expand Down
2 changes: 1 addition & 1 deletion persist/sqlite/transactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ ORDER BY ts.transaction_order DESC`
func transactionFileContractRevisions(tx txn, txnIDs []int64) (map[int64][]explorer.FileContractRevision, error) {
query := `SELECT ts.transaction_id, ts.parent_id, ts.unlock_conditions, fc.contract_id, fc.leaf_index, fc.merkle_proof, fc.resolved, fc.valid, fc.filesize, fc.file_merkle_root, fc.window_start, fc.window_end, fc.payout, fc.unlock_hash, fc.revision_number
FROM file_contract_elements fc
INNER JOIN transaction_file_contracts ts ON (ts.contract_id = fc.id)
INNER JOIN transaction_file_contract_revisions ts ON (ts.contract_id = fc.id)
WHERE ts.transaction_id IN (` + queryPlaceHolders(len(txnIDs)) + `)
ORDER BY ts.transaction_order DESC`
rows, err := tx.Query(query, queryArgs(txnIDs)...)
Expand Down

0 comments on commit cfdef9a

Please sign in to comment.