diff --git a/explorer/types.go b/explorer/types.go index 0cd34791..7db70b20 100644 --- a/explorer/types.go +++ b/explorer/types.go @@ -85,14 +85,15 @@ type FileContractRevision struct { // A Transaction is a transaction that uses the wrapped types above. type Transaction struct { - SiacoinInputs []types.SiacoinInput `json:"siacoinInputs,omitempty"` - SiacoinOutputs []SiacoinOutput `json:"siacoinOutputs,omitempty"` - SiafundInputs []types.SiafundInput `json:"siafundInputs,omitempty"` - SiafundOutputs []SiafundOutput `json:"siafundOutputs,omitempty"` - FileContracts []FileContract `json:"fileContracts,omitempty"` - FileContractRevisions []FileContractRevision `json:"fileContractRevisions,omitempty"` - MinerFees []types.Currency `json:"minerFees,omitempty"` - ArbitraryData [][]byte `json:"arbitraryData,omitempty"` + SiacoinInputs []types.SiacoinInput `json:"siacoinInputs,omitempty"` + SiacoinOutputs []SiacoinOutput `json:"siacoinOutputs,omitempty"` + SiafundInputs []types.SiafundInput `json:"siafundInputs,omitempty"` + SiafundOutputs []SiafundOutput `json:"siafundOutputs,omitempty"` + FileContracts []FileContract `json:"fileContracts,omitempty"` + FileContractRevisions []FileContractRevision `json:"fileContractRevisions,omitempty"` + MinerFees []types.Currency `json:"minerFees,omitempty"` + ArbitraryData [][]byte `json:"arbitraryData,omitempty"` + Signatures []types.TransactionSignature `json:"signatures,omitempty"` } // A Block is a block containing wrapped transactions and siacoin diff --git a/persist/sqlite/consensus.go b/persist/sqlite/consensus.go index 28dc50ab..2e5dfb28 100644 --- a/persist/sqlite/consensus.go +++ b/persist/sqlite/consensus.go @@ -55,6 +55,21 @@ func addMinerFees(tx *txn, id int64, txn types.Transaction) error { return nil } +func addSignatures(tx *txn, id int64, txn types.Transaction) error { + stmt, err := tx.Prepare(`INSERT INTO transaction_signatures(transaction_id, transaction_order, parent_id, public_key_index, timelock, covered_fields, signature) VALUES (?, ?, ?, ?, ?, ?, ?)`) + if err != nil { + return fmt.Errorf("addMinerFees: failed to prepare statement: %w", err) + } + defer stmt.Close() + + for i, sig := range txn.Signatures { + if _, err := stmt.Exec(id, i, encode(sig.ParentID), sig.PublicKeyIndex, sig.Timelock, encode(sig.CoveredFields), sig.Signature); err != nil { + return fmt.Errorf("addMinerFees: failed to execute statement: %w", err) + } + } + return nil +} + func addArbitraryData(tx *txn, id int64, txn types.Transaction) error { stmt, err := tx.Prepare(`INSERT INTO transaction_arbitrary_data(transaction_id, transaction_order, data) VALUES (?, ?, ?)`) @@ -281,6 +296,8 @@ func addTransactions(tx *txn, bid types.BlockID, txns []types.Transaction, scDBI return fmt.Errorf("failed to add miner fees: %w", err) } else if err := addArbitraryData(tx, txnID, txn); err != nil { return fmt.Errorf("failed to add arbitrary data: %w", err) + } else if err := addSignatures(tx, txnID, txn); err != nil { + return fmt.Errorf("failed to add signatures: %w", err) } else if err := addSiacoinInputs(tx, txnID, txn); err != nil { return fmt.Errorf("failed to add siacoin inputs: %w", err) } else if err := addSiacoinOutputs(tx, txnID, txn, scDBIds); err != nil { diff --git a/persist/sqlite/init.sql b/persist/sqlite/init.sql index 8e903387..9dc4071f 100644 --- a/persist/sqlite/init.sql +++ b/persist/sqlite/init.sql @@ -143,6 +143,19 @@ CREATE TABLE transaction_miner_fees ( CREATE INDEX transaction_miner_fees_transaction_id_index ON transaction_miner_fees(transaction_id); +CREATE TABLE transaction_signatures ( + transaction_id INTEGER REFERENCES transactions(id) ON DELETE CASCADE NOT NULL, + transaction_order INTEGER NOT NULL, + parent_id BLOB NOT NULL, + public_key_index INTEGER NOT NULL, + timelock INTEGER NOT NULL, + covered_fields BLOB NOT NULL, + signature BLOB NOT NULL, + UNIQUE(transaction_id, transaction_order) +); + +CREATE INDEX transaction_signatures_transaction_id_index ON transaction_signatures(transaction_id); + CREATE TABLE transaction_siacoin_inputs ( transaction_id INTEGER REFERENCES transactions(id) ON DELETE CASCADE NOT NULL, transaction_order INTEGER NOT NULL, diff --git a/persist/sqlite/transactions.go b/persist/sqlite/transactions.go index 26637d1a..5d79b9f5 100644 --- a/persist/sqlite/transactions.go +++ b/persist/sqlite/transactions.go @@ -55,6 +55,30 @@ ORDER BY transaction_order ASC` return result, nil } +// transactionSignatures returns the signatures for each transaction. +func transactionSignatures(tx *txn, txnIDs []int64) (map[int64][]types.TransactionSignature, error) { + query := `SELECT transaction_id, parent_id, public_key_index, timelock, covered_fields, signature +FROM transaction_signatures +WHERE transaction_id IN (` + queryPlaceHolders(len(txnIDs)) + `) +ORDER BY transaction_order ASC` + rows, err := tx.Query(query, queryArgs(txnIDs)...) + if err != nil { + return nil, err + } + defer rows.Close() + + result := make(map[int64][]types.TransactionSignature) + for rows.Next() { + var txnID int64 + var sig types.TransactionSignature + if err := rows.Scan(&txnID, decode(&sig.ParentID), &sig.PublicKeyIndex, &sig.Timelock, decode(&sig.CoveredFields), &sig.Signature); err != nil { + return nil, fmt.Errorf("failed to scan signature: %w", err) + } + result[txnID] = append(result[txnID], sig) + } + return result, nil +} + // transactionSiacoinOutputs returns the siacoin outputs for each transaction. func transactionSiacoinOutputs(tx *txn, txnIDs []int64) (map[int64][]explorer.SiacoinOutput, error) { query := `SELECT ts.transaction_id, sc.output_id, sc.leaf_index, sc.source, sc.maturity_height, sc.address, sc.value @@ -382,6 +406,11 @@ func (s *Store) getTransactions(tx *txn, dbIDs []int64) ([]explorer.Transaction, return nil, fmt.Errorf("getTransactions: failed to get miner fees: %w", err) } + txnSignatures, err := transactionSignatures(tx, dbIDs) + if err != nil { + return nil, fmt.Errorf("getTransactions: failed to get signatures: %w", err) + } + txnSiacoinInputs, err := transactionSiacoinInputs(tx, dbIDs) if err != nil { return nil, fmt.Errorf("getTransactions: failed to get siacoin inputs: %w", err) @@ -426,6 +455,7 @@ func (s *Store) getTransactions(tx *txn, dbIDs []int64) ([]explorer.Transaction, FileContractRevisions: txnFileContractRevisions[dbID], MinerFees: txnMinerFees[dbID], ArbitraryData: txnArbitraryData[dbID], + Signatures: txnSignatures[dbID], } results = append(results, txn) }