Skip to content

Commit

Permalink
support querying contracts and revisions
Browse files Browse the repository at this point in the history
  • Loading branch information
chris124567 committed Feb 27, 2024
1 parent fcf1064 commit 6e349ec
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 22 deletions.
12 changes: 11 additions & 1 deletion explorer/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ const (
SourceMinerPayout
// SourceTransaction means the source of the output is a transaction.
SourceTransaction
// SourceValidProofOutput me ans the source of the output is a valid proof
// output.
SourceValidProofOutput
// SourceMissedProofOutput me ans the source of the output is a missed
// proof output.
SourceMissedProofOutput
)

// MarshalJSON implements json.Marshaler.
Expand Down Expand Up @@ -48,6 +54,10 @@ type SiafundOutput types.SiafundElement
// internally.
type FileContract struct {
types.StateElement

Resolved bool `json:"resolved"`
Valid bool `json:"valid"`

Filesize uint64 `json:"filesize"`
FileMerkleRoot types.Hash256 `json:"fileMerkleRoot"`
WindowStart uint64 `json:"windowStart"`
Expand Down Expand Up @@ -80,7 +90,7 @@ type Transaction struct {
SiafundInputs []types.SiafundInput `json:"siafundInputs,omitempty"`
SiafundOutputs []SiafundOutput `json:"siafundOutputs,omitempty"`
FileContracts []FileContract `json:"fileContracts,omitempty"`
FileContractRevisions []FileContractRevision `json:"fileContracts,omitempty"`
FileContractRevisions []FileContractRevision `json:"fileContractRevisions,omitempty"`
ArbitraryData [][]byte `json:"arbitraryData,omitempty"`
}

Expand Down
68 changes: 56 additions & 12 deletions persist/sqlite/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,22 +121,56 @@ func (s *Store) addSiafundOutputs(dbTxn txn, id int64, txn types.Transaction, db
return nil
}

func (s *Store) addFileContracts(dbTxn txn, id int64, txn types.Transaction, dbIDs map[fileContract]int64) error {
func (s *Store) addFileContracts(dbTxn txn, id int64, txn types.Transaction, scDBIds map[types.SiacoinOutputID]int64, fcDBIds map[fileContract]int64) error {
stmt, err := dbTxn.Prepare(`INSERT INTO transaction_file_contracts(transaction_id, transaction_order, contract_id) VALUES (?, ?, ?)`)
if err != nil {
return fmt.Errorf("addFileContracts: failed to prepare statement: %w", err)
}
defer stmt.Close()

// validOutputsStmt, err := dbTxn.Prepare(`INSERT INTO file_contract_valid_proof_outputs(contract_id, contract_order, output_id) VALUES (?, ?, ?)`)
// if err != nil {
// return fmt.Errorf("addFileContracts: failed to prepare valid proof outputs statement: %w", err)
// }
// defer validOutputsStmt.Close()

// missedOutputsStmt, err := dbTxn.Prepare(`INSERT INTO file_contract_missed_proof_outputs(contract_id, contract_order, output_id) VALUES (?, ?, ?)`)
// if err != nil {
// return fmt.Errorf("addFileContracts: failed to prepare missed proof outputs statement: %w", err)
// }
// defer missedOutputsStmt.Close()

for i := range txn.FileContracts {
dbID, ok := dbIDs[fileContract{txn.FileContractID(i), 0}]
dbID, ok := fcDBIds[fileContract{txn.FileContractID(i), 0}]
if !ok {
return errors.New("addFileContracts: dbID not in map")
return errors.New("addFileContracts: fcDbID not in map")
}

if _, err := stmt.Exec(id, i, dbID); err != nil {
return fmt.Errorf("addFileContracts: failed to execute statement: %w", err)
return fmt.Errorf("addFileContracts: failed to execute transaction_file_contracts statement: %w", err)
}

// for j := range txn.FileContracts[i].ValidProofOutputs {
// scDBId, ok := scDBIds[txn.FileContractID(i).ValidOutputID(j)]
// if !ok {
// return errors.New("addFileContracts: valid scDBId not in map")
// }

// if _, err := validOutputsStmt.Exec(dbID, j, scDBId); err != nil {
// return fmt.Errorf("addFileContracts: failed to execute valid proof outputs statement: %w", err)
// }
// }

// for j := range txn.FileContracts[i].MissedProofOutputs {
// scDBId, ok := scDBIds[txn.FileContractID(i).MissedOutputID(j)]
// if !ok {
// return errors.New("addFileContracts: missed scDBId not in map")
// }

// if _, err := missedOutputsStmt.Exec(dbID, j, scDBId); err != nil {
// return fmt.Errorf("addFileContracts: failed to execute missed proof outputs statement: %w", err)
// }
// }
}
return nil
}
Expand Down Expand Up @@ -196,10 +230,10 @@ func (s *Store) addTransactions(dbTxn txn, bid types.BlockID, txns []types.Trans
return fmt.Errorf("failed to add siafund inputs: %w", err)
} else if err := s.addSiafundOutputs(dbTxn, txnID, txn, sfDBIds); err != nil {
return fmt.Errorf("failed to add siafund outputs: %w", err)
} else if err := s.addFileContracts(dbTxn, txnID, txn, fcDBIds); err != nil {
} else if err := s.addFileContracts(dbTxn, txnID, txn, scDBIds, fcDBIds); err != nil {
return fmt.Errorf("failed to add file contract: %w", err)
} else if err := s.addFileContractRevisions(dbTxn, txnID, txn, fcDBIds); err != nil {
return fmt.Errorf("failed to add file contract: %w", err)
return fmt.Errorf("failed to add file contract revisions: %w", err)
}
}
return nil
Expand Down Expand Up @@ -316,17 +350,27 @@ func (s *Store) addSiacoinElements(dbTxn txn, bid types.BlockID, update consensu
for i := range block.MinerPayouts {
sources[bid.MinerOutputID(i)] = explorer.SourceMinerPayout
}

for _, txn := range block.Transactions {
for i := range txn.SiacoinOutputs {
sources[txn.SiacoinOutputID(i)] = explorer.SourceTransaction
}
// TODO: contract valid/missed outputs

for i := range txn.FileContracts {
fcid := txn.FileContractID(i)
for j := range txn.FileContracts[i].ValidProofOutputs {
sources[fcid.ValidOutputID(j)] = explorer.SourceValidProofOutput
}
for j := range txn.FileContracts[i].MissedProofOutputs {
sources[fcid.MissedOutputID(j)] = explorer.SourceMissedProofOutput
}
}
}
}

stmt, err := dbTxn.Prepare(`INSERT INTO siacoin_elements(output_id, block_id, leaf_index, merkle_proof, spent, source, maturity_height, address, value)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(output_id)
ON CONFLICT
DO UPDATE SET spent = ?`)
if err != nil {
return nil, fmt.Errorf("addSiacoinElements: failed to prepare siacoin_elements statement: %w", err)
Expand Down Expand Up @@ -360,7 +404,7 @@ func (s *Store) addSiacoinElements(dbTxn txn, bid types.BlockID, update consensu
func (s *Store) addSiafundElements(dbTxn txn, bid types.BlockID, update consensusUpdate) (map[types.SiafundOutputID]int64, error) {
stmt, err := dbTxn.Prepare(`INSERT INTO siafund_elements(output_id, block_id, leaf_index, merkle_proof, spent, claim_start, address, value)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(output_id)
ON CONFLICT
DO UPDATE SET spent = ?`)
if err != nil {
return nil, fmt.Errorf("addSiafundElements: failed to prepare siafund_elements statement: %w", err)
Expand Down Expand Up @@ -397,8 +441,8 @@ type fileContract struct {
}

func (s *Store) addFileContractElements(dbTxn txn, bid types.BlockID, update consensusUpdate) (map[fileContract]int64, error) {
stmt, err := dbTxn.Prepare(`INSERT INTO file_contract_elements(block_id, contract_id, leaf_index, merkle_proof, resolved, valid, filesize, file_merkle_root, window_start, window_end, valid_proof_outputs, missed_proof_outputs, payout, unlock_hash, revision_number)
VALUES (?, ?, ?, ?, FALSE, TRUE, ?, ?, ?, ?, ?, ?, ?, ?, ?)
stmt, err := dbTxn.Prepare(`INSERT INTO file_contract_elements(block_id, contract_id, leaf_index, merkle_proof, resolved, valid, filesize, file_merkle_root, window_start, window_end, payout, unlock_hash, revision_number)
VALUES (?, ?, ?, ?, FALSE, TRUE, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT (contract_id, revision_number)
DO UPDATE SET resolved = ? AND valid = ?`)
if err != nil {
Expand All @@ -418,7 +462,7 @@ func (s *Store) addFileContractElements(dbTxn txn, bid types.BlockID, update con
fc = &rev.FileContract
}

result, err := stmt.Exec(dbEncode(bid), dbEncode(fce.StateElement.ID), dbEncode(fce.StateElement.LeafIndex), dbEncode(fce.StateElement.MerkleProof), fc.Filesize, dbEncode(fc.FileMerkleRoot), fc.WindowStart, fc.WindowEnd, len(fc.ValidProofOutputs), len(fc.MissedProofOutputs), dbEncode(fc.Payout), dbEncode(fc.UnlockHash), fc.RevisionNumber, resolved, valid)
result, err := stmt.Exec(dbEncode(bid), dbEncode(fce.StateElement.ID), dbEncode(fce.StateElement.LeafIndex), dbEncode(fce.StateElement.MerkleProof), fc.Filesize, dbEncode(fc.FileMerkleRoot), fc.WindowStart, fc.WindowEnd, dbEncode(fc.Payout), dbEncode(fc.UnlockHash), fc.RevisionNumber, resolved, valid)
if err != nil {
updateErr = fmt.Errorf("addFileContractElements: failed to execute file_contract_elements statement: %w", err)
return
Expand Down
20 changes: 18 additions & 2 deletions persist/sqlite/init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,30 @@ CREATE TABLE file_contract_elements (
file_merkle_root BLOB NOT NULL,
window_start INTEGER NOT NULL,
window_end INTEGER NOT NULL,
valid_proof_outputs INTEGER NOT NULL,
missed_proof_outputs INTEGER NOT NULL,
payout BLOB NOT NULL,
unlock_hash BLOB NOT NULL,
revision_number INTEGER NOT NULL,
UNIQUE(contract_id, revision_number)
);

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,
output_id INTEGER REFERENCES siacoin_elements(id) ON DELETE CASCADE NOT NULL,
UNIQUE(contract_id, contract_order)
);

CREATE INDEX file_contract_valid_proof_outputs_contract_id_index ON file_contract_valid_proof_outputs(contract_id);

CREATE TABLE file_contract_missed_proof_outputs (
contract_id INTEGER REFERENCES file_contract_elements(id) ON DELETE CASCADE NOT NULL,
contract_order INTEGER NOT NULL,
output_id INTEGER REFERENCES siacoin_elements(id) ON DELETE CASCADE NOT NULL,
UNIQUE(contract_id, contract_order)
);

CREATE INDEX file_contract_missed_proof_outputs_contract_id_index ON file_contract_missed_proof_outputs(contract_id);

CREATE TABLE miner_payouts (
block_id BLOB REFERENCES blocks(id) ON DELETE CASCADE NOT NULL,
block_order INTEGER NOT NULL,
Expand Down
76 changes: 69 additions & 7 deletions persist/sqlite/transactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,58 @@ ORDER BY ts.transaction_order DESC`
return result, nil
}

// transactionFileContracts returns the file contracts for each transaction.
func transactionFileContracts(tx txn, txnIDs []int64) (map[int64][]explorer.FileContract, error) {
query := `SELECT ts.transaction_id, 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)
WHERE ts.transaction_id IN (` + queryPlaceHolders(len(txnIDs)) + `)
ORDER BY ts.transaction_order DESC`
rows, err := tx.Query(query, queryArgs(txnIDs)...)
if err != nil {
return nil, fmt.Errorf("failed to query contract output ids: %w", err)
}
defer rows.Close()

// map transaction ID to contract list
result := make(map[int64][]explorer.FileContract)
for rows.Next() {
var txnID int64
var fc explorer.FileContract
if err := rows.Scan(&txnID, 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 nil, fmt.Errorf("failed to scan file contract: %w", err)
}
result[txnID] = append(result[txnID], fc)
}
return result, nil
}

// transactionFileContracts returns the file contract revisions for each transaction.
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)
WHERE ts.transaction_id IN (` + queryPlaceHolders(len(txnIDs)) + `)
ORDER BY ts.transaction_order DESC`
rows, err := tx.Query(query, queryArgs(txnIDs)...)
if err != nil {
return nil, fmt.Errorf("failed to query contract output ids: %w", err)
}
defer rows.Close()

// map transaction ID to contract list
result := make(map[int64][]explorer.FileContractRevision)
for rows.Next() {
var txnID int64
var fc explorer.FileContractRevision
if err := rows.Scan(&txnID, dbDecode(&fc.ParentID), dbDecode(&fc.UnlockConditions), 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 nil, fmt.Errorf("failed to scan file contract: %w", err)
}
result[txnID] = append(result[txnID], fc)
}
return result, nil
}

// blockTransactionIDs returns the database ID for each transaction in the
// block.
func blockTransactionIDs(tx txn, blockID types.BlockID) (dbIDs []int64, err error) {
Expand Down Expand Up @@ -235,19 +287,29 @@ func (s *Store) getTransactions(tx txn, dbIDs []int64) ([]explorer.Transaction,
return nil, fmt.Errorf("getTransactions: failed to get siafund outputs: %w", err)
}

// TODO: file contracts
// TODO: file contract revisions
txnFileContracts, err := transactionFileContracts(tx, dbIDs)
if err != nil {
return nil, fmt.Errorf("getTransactions: failed to get file contracts: %w", err)
}

txnFileContractRevisions, err := transactionFileContractRevisions(tx, dbIDs)
if err != nil {
return nil, fmt.Errorf("getTransactions: failed to get file contract revisions: %w", err)
}

// TODO: storage proofs
// TODO: signatures

var results []explorer.Transaction
for _, dbID := range dbIDs {
txn := explorer.Transaction{
ArbitraryData: txnArbitraryData[dbID],
SiacoinInputs: txnSiacoinInputs[dbID],
SiacoinOutputs: txnSiacoinOutputs[dbID],
SiafundInputs: txnSiafundInputs[dbID],
SiafundOutputs: txnSiafundOutputs[dbID],
ArbitraryData: txnArbitraryData[dbID],
SiacoinInputs: txnSiacoinInputs[dbID],
SiacoinOutputs: txnSiacoinOutputs[dbID],
SiafundInputs: txnSiafundInputs[dbID],
SiafundOutputs: txnSiafundOutputs[dbID],
FileContracts: txnFileContracts[dbID],
FileContractRevisions: txnFileContractRevisions[dbID],
}
results = append(results, txn)
}
Expand Down

0 comments on commit 6e349ec

Please sign in to comment.