Skip to content

Commit

Permalink
store confirmation and proof index and transaction ID for contracts
Browse files Browse the repository at this point in the history
  • Loading branch information
chris124567 committed Sep 17, 2024
1 parent ed35617 commit 29c1069
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 11 deletions.
7 changes: 7 additions & 0 deletions explorer/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,13 @@ type SiafundOutput struct {
type FileContract struct {
Resolved bool `json:"resolved"`
Valid bool `json:"valid"`

ConfirmationIndex *types.ChainIndex `json:"confirmationIndex"`
ConfirmationTransactionID *types.TransactionID `json:"confirmationTransactionID"`

ProofIndex *types.ChainIndex `json:"proofIndex"`
ProofTransactionID *types.TransactionID `json:"proofTransactionID"`

types.FileContractElement
}

Expand Down
59 changes: 53 additions & 6 deletions explorer/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ type (
FileContractElement types.FileContractElement
Revision *types.FileContractElement
Resolved, Valid bool

ConfirmationTransactionID *types.TransactionID `json:"transactionID"`
ProofTransactionID *types.TransactionID `json:"transactionID"`
}

// A DBFileContract represents a file contract element in the DB.
Expand Down Expand Up @@ -121,15 +124,37 @@ func applyChainUpdate(tx UpdateTx, cau chain.ApplyUpdate) error {
}
})

var fces []FileContractUpdate
fceMap := make(map[types.FileContractID]FileContractUpdate)
cau.ForEachFileContractElement(func(fce types.FileContractElement, created bool, rev *types.FileContractElement, resolved, valid bool) {
fces = append(fces, FileContractUpdate{
fceMap[types.FileContractID(fce.ID)] = FileContractUpdate{
FileContractElement: fce,
Revision: rev,
Resolved: resolved,
Valid: valid,
})
}
})
for _, txn := range cau.Block.Transactions {
txnID := txn.ID()
for i := range txn.FileContracts {
fcID := txn.FileContractID(i)

v := fceMap[fcID]
v.ConfirmationTransactionID = &txnID
fceMap[fcID] = v
}
for _, sp := range txn.StorageProofs {
fcID := sp.ParentID

v := fceMap[fcID]
v.ProofTransactionID = &txnID
fceMap[fcID] = v
}
}

var fces []FileContractUpdate
for _, fce := range fceMap {
fces = append(fces, fce)
}

var treeUpdates []TreeNodeUpdate
cau.ForEachTreeNode(func(row, column uint64, hash types.Hash256) {
Expand Down Expand Up @@ -217,15 +242,37 @@ func revertChainUpdate(tx UpdateTx, cru chain.RevertUpdate, revertedIndex types.
}
})

var fces []FileContractUpdate
fceMap := make(map[types.FileContractID]FileContractUpdate)
cru.ForEachFileContractElement(func(fce types.FileContractElement, created bool, rev *types.FileContractElement, resolved, valid bool) {
fces = append(fces, FileContractUpdate{
fceMap[types.FileContractID(fce.ID)] = FileContractUpdate{
FileContractElement: fce,
Revision: rev,
Resolved: resolved,
Valid: valid,
})
}
})
for _, txn := range cru.Block.Transactions {
txnID := txn.ID()
for i := range txn.FileContracts {
fcID := txn.FileContractID(i)

v := fceMap[fcID]
v.ConfirmationTransactionID = &txnID
fceMap[fcID] = v
}
for _, sp := range txn.StorageProofs {
fcID := sp.ParentID

v := fceMap[fcID]
v.ProofTransactionID = &txnID
fceMap[fcID] = v
}
}

var fces []FileContractUpdate
for _, fce := range fceMap {
fces = append(fces, fce)
}

var treeUpdates []TreeNodeUpdate
cru.ForEachTreeNode(func(row, column uint64, hash types.Hash256) {
Expand Down
51 changes: 50 additions & 1 deletion persist/sqlite/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -767,7 +767,7 @@ func updateFileContractElements(tx *txn, revert bool, b types.Block, fces []expl
DO UPDATE SET resolved = ?, valid = ?, leaf_index = ?
RETURNING id;`)
if err != nil {
return nil, fmt.Errorf("updateFileContractElements: failed to prepare file_contract_elements statement: %w", err)
return nil, fmt.Errorf("updateFileContractElements: failed to prepare main statement: %w", err)
}
defer stmt.Close()

Expand Down Expand Up @@ -909,6 +909,51 @@ func updateFileContractElements(tx *txn, revert bool, b types.Block, fces []expl
return fcDBIds, nil
}

func updateFileContractIndices(tx *txn, revert bool, index types.ChainIndex, fces []explorer.FileContractUpdate) error {
confirmationIndexStmt, err := tx.Prepare(`UPDATE last_contract_revision SET confirmation_index = ?, confirmation_transaction_id = ? WHERE contract_id = ?`)
if err != nil {
return fmt.Errorf("updateFileContractIndices: failed to prepare confirmation index statement: %w", err)
}
defer confirmationIndexStmt.Close()

proofIndexStmt, err := tx.Prepare(`UPDATE last_contract_revision SET proof_index = ?, proof_transaction_id = ? WHERE contract_id = ?`)
if err != nil {
return fmt.Errorf("updateFileContractIndices: failed to prepare proof index statement: %w", err)
}
defer proofIndexStmt.Close()

for _, update := range fces {
// id stays the same even if revert happens so we don't need to check that here
fcID := update.FileContractElement.ID

if revert {
if update.ConfirmationTransactionID != nil {
if _, err := confirmationIndexStmt.Exec(nil, nil, encode(fcID)); err != nil {
return fmt.Errorf("updateFileContractIndices: failed to update confirmation index: %w", err)
}
}
if update.ProofTransactionID != nil {
if _, err := proofIndexStmt.Exec(nil, nil, encode(fcID)); err != nil {
return fmt.Errorf("updateFileContractIndices: failed to update proof index: %w", err)
}
}
} else {
if update.ConfirmationTransactionID != nil {
if _, err := confirmationIndexStmt.Exec(encode(index), encode(update.ConfirmationTransactionID), encode(fcID)); err != nil {
return fmt.Errorf("updateFileContractIndices: failed to update confirmation index: %w", err)
}
}
if update.ProofTransactionID != nil {
if _, err := proofIndexStmt.Exec(encode(index), encode(update.ProofTransactionID), encode(fcID)); err != nil {
return fmt.Errorf("updateFileContractIndices: failed to update proof index: %w", err)
}
}
}
}

return nil
}

func addMetrics(tx *txn, s explorer.UpdateState) error {
_, err := tx.Exec(`INSERT INTO network_metrics(block_id, height, difficulty, siafund_pool, total_hosts, active_contracts, failed_contracts, successful_contracts, storage_utilization, circulating_supply, contract_revenue) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
encode(s.Metrics.Index.ID),
Expand Down Expand Up @@ -983,6 +1028,8 @@ func (ut *updateTx) ApplyIndex(state explorer.UpdateState) error {
return fmt.Errorf("ApplyIndex: failed to update metrics: %w", err)
} else if err := addEvents(ut.tx, scDBIds, fcDBIds, txnDBIds, state.Events); err != nil {
return fmt.Errorf("ApplyIndex: failed to add events: %w", err)
} else if err := updateFileContractIndices(ut.tx, false, state.Metrics.Index, state.FileContractElements); err != nil {
return fmt.Errorf("ApplyIndex: failed to update file contract element indices: %w", err)
}

return nil
Expand Down Expand Up @@ -1013,6 +1060,8 @@ func (ut *updateTx) RevertIndex(state explorer.UpdateState) error {
return fmt.Errorf("RevertIndex: failed to delete block: %w", err)
} else if err := updateStateTree(ut.tx, state.TreeUpdates); err != nil {
return fmt.Errorf("RevertIndex: failed to update state tree: %w", err)
} else if err := updateFileContractIndices(ut.tx, true, state.Metrics.Index, state.FileContractElements); err != nil {
return fmt.Errorf("RevertIndex: failed to update file contract element indices: %w", err)
}

return nil
Expand Down
5 changes: 5 additions & 0 deletions persist/sqlite/consensus_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -855,6 +855,8 @@ func TestFileContract(t *testing.T) {
}
check(t, "fcs", 1, len(dbFCs))
checkFC(false, false, fc, dbFCs[0])
check(t, "confirmation index", cm.Tip(), *dbFCs[0].ConfirmationIndex)
check(t, "confirmation transaction ID", txn.ID(), *dbFCs[0].ConfirmationTransactionID)
}

{
Expand Down Expand Up @@ -902,6 +904,9 @@ func TestFileContract(t *testing.T) {
check(t, "len(contracts)", 1, len(renterContracts))
checkFC(false, false, fc, renterContracts[0])
checkFC(false, false, fc, hostContracts[0])

check(t, "confirmation index", cm.Tip(), *renterContracts[0].ConfirmationIndex)
check(t, "confirmation transaction ID", txn.ID(), *renterContracts[0].ConfirmationTransactionID)
}

checkMetrics(t, db, cm, explorer.Metrics{
Expand Down
42 changes: 38 additions & 4 deletions persist/sqlite/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func encodedIDs(ids []types.FileContractID) []any {
// Contracts implements explorer.Store.
func (s *Store) Contracts(ids []types.FileContractID) (result []explorer.FileContract, err error) {
err = s.transaction(func(tx *txn) error {
query := `SELECT fc1.id, fc1.contract_id, fc1.leaf_index, fc1.resolved, fc1.valid, fc1.filesize, fc1.file_merkle_root, fc1.window_start, fc1.window_end, fc1.payout, fc1.unlock_hash, fc1.revision_number
query := `SELECT fc1.id, fc1.contract_id, fc1.leaf_index, fc1.resolved, fc1.valid, rev.confirmation_index, rev.confirmation_transaction_id, rev.proof_index, rev.proof_transaction_id, 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
INNER JOIN last_contract_revision rev ON (rev.contract_element_id = fc1.id)
WHERE rev.contract_id IN (` + queryPlaceHolders(len(ids)) + `)`
Expand All @@ -33,10 +33,27 @@ func (s *Store) Contracts(ids []types.FileContractID) (result []explorer.FileCon
for rows.Next() {
var contractID int64
var fc explorer.FileContract
if err := rows.Scan(&contractID, decode(&fc.StateElement.ID), decode(&fc.StateElement.LeafIndex), &fc.Resolved, &fc.Valid, decode(&fc.FileContract.Filesize), decode(&fc.FileContract.FileMerkleRoot), decode(&fc.FileContract.WindowStart), decode(&fc.FileContract.WindowEnd), decode(&fc.FileContract.Payout), decode(&fc.FileContract.UnlockHash), decode(&fc.FileContract.RevisionNumber)); err != nil {

var confirmationIndex, proofIndex types.ChainIndex
var confirmationTransactionID, proofTransactionID types.TransactionID
if err := rows.Scan(&contractID, decode(&fc.StateElement.ID), decode(&fc.StateElement.LeafIndex), &fc.Resolved, &fc.Valid, decodeNull(&confirmationIndex), decodeNull(&confirmationTransactionID), decodeNull(&proofIndex), decodeNull(&proofTransactionID), decode(&fc.FileContract.Filesize), decode(&fc.FileContract.FileMerkleRoot), decode(&fc.FileContract.WindowStart), decode(&fc.FileContract.WindowEnd), decode(&fc.FileContract.Payout), decode(&fc.FileContract.UnlockHash), decode(&fc.FileContract.RevisionNumber)); err != nil {
return fmt.Errorf("failed to scan transaction: %w", err)
}

if confirmationIndex != (types.ChainIndex{}) {
fc.ConfirmationIndex = &confirmationIndex
}
if confirmationTransactionID != (types.TransactionID{}) {
fc.ConfirmationTransactionID = &confirmationTransactionID
}

if proofIndex != (types.ChainIndex{}) {
fc.ProofIndex = &proofIndex
}
if proofTransactionID != (types.TransactionID{}) {
fc.ProofTransactionID = &proofTransactionID
}

idContract[contractID] = fc
contractIDs = append(contractIDs, contractID)
}
Expand All @@ -61,7 +78,7 @@ func (s *Store) Contracts(ids []types.FileContractID) (result []explorer.FileCon
// ContractsKey implements explorer.Store.
func (s *Store) ContractsKey(key types.PublicKey) (result []explorer.FileContract, err error) {
err = s.transaction(func(tx *txn) error {
query := `SELECT fc1.id, fc1.contract_id, fc1.leaf_index, fc1.resolved, fc1.valid, fc1.filesize, fc1.file_merkle_root, fc1.window_start, fc1.window_end, fc1.payout, fc1.unlock_hash, fc1.revision_number
query := `SELECT fc1.id, fc1.contract_id, fc1.leaf_index, fc1.resolved, fc1.valid, rev.confirmation_index, rev.confirmation_transaction_id, rev.proof_index, rev.proof_transaction_id, 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
INNER JOIN last_contract_revision rev ON (rev.contract_element_id = fc1.id)
WHERE rev.ed25519_renter_key = ? OR rev.ed25519_host_key = ?`
Expand All @@ -76,10 +93,27 @@ func (s *Store) ContractsKey(key types.PublicKey) (result []explorer.FileContrac
for rows.Next() {
var contractID int64
var fc explorer.FileContract
if err := rows.Scan(&contractID, decode(&fc.StateElement.ID), decode(&fc.StateElement.LeafIndex), &fc.Resolved, &fc.Valid, decode(&fc.FileContract.Filesize), decode(&fc.FileContract.FileMerkleRoot), decode(&fc.FileContract.WindowStart), decode(&fc.FileContract.WindowEnd), decode(&fc.FileContract.Payout), decode(&fc.FileContract.UnlockHash), decode(&fc.FileContract.RevisionNumber)); err != nil {

var confirmationIndex, proofIndex types.ChainIndex
var confirmationTransactionID, proofTransactionID types.TransactionID
if err := rows.Scan(&contractID, decode(&fc.StateElement.ID), decode(&fc.StateElement.LeafIndex), &fc.Resolved, &fc.Valid, decodeNull(&confirmationIndex), decodeNull(&confirmationTransactionID), decodeNull(&proofIndex), decodeNull(&proofTransactionID), decode(&fc.FileContract.Filesize), decode(&fc.FileContract.FileMerkleRoot), decode(&fc.FileContract.WindowStart), decode(&fc.FileContract.WindowEnd), decode(&fc.FileContract.Payout), decode(&fc.FileContract.UnlockHash), decode(&fc.FileContract.RevisionNumber)); err != nil {
return fmt.Errorf("failed to scan transaction: %w", err)
}

if confirmationIndex != (types.ChainIndex{}) {
fc.ConfirmationIndex = &confirmationIndex
}
if confirmationTransactionID != (types.TransactionID{}) {
fc.ConfirmationTransactionID = &confirmationTransactionID
}

if proofIndex != (types.ChainIndex{}) {
fc.ProofIndex = &proofIndex
}
if proofTransactionID != (types.TransactionID{}) {
fc.ProofTransactionID = &proofTransactionID
}

idContract[contractID] = fc
contractIDs = append(contractIDs, contractID)
}
Expand Down
8 changes: 8 additions & 0 deletions persist/sqlite/init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,16 @@ CREATE INDEX file_contract_elements_contract_id_index ON file_contract_elements(
CREATE TABLE last_contract_revision (
contract_id BLOB PRIMARY KEY NOT NULL,
block_id BLOB REFERENCES blocks(id) ON DELETE CASCADE NOT NULL,

ed25519_renter_key BLOB,
ed25519_host_key BLOB,

confirmation_index BLOB,
confirmation_transaction_id BLOB REFERENCES transactions(transaction_id),

proof_index BLOB,
proof_transaction_id BLOB REFERENCES transactions(transaction_id),

contract_element_id INTEGER UNIQUE REFERENCES file_contract_elements(id) NOT NULL
);

Expand Down

0 comments on commit 29c1069

Please sign in to comment.