diff --git a/explorer/events.go b/explorer/events.go index 919b91d..ad3321e 100644 --- a/explorer/events.go +++ b/explorer/events.go @@ -90,7 +90,7 @@ func AppliedEvents(cs consensus.State, b types.Block, cu ChainUpdate) (events [] // handle v1 transactions for _, txn := range b.Transactions { addresses := make(map[types.Address]struct{}) - e := &EventV1Transaction{ + e := EventV1Transaction{ Transaction: txn, SpentSiacoinElements: make([]types.SiacoinElement, 0, len(txn.SiacoinInputs)), SpentSiafundElements: make([]types.SiafundElement, 0, len(txn.SiafundInputs)), diff --git a/persist/sqlite/consensus.go b/persist/sqlite/consensus.go index 29a6656..5abdd2c 100644 --- a/persist/sqlite/consensus.go +++ b/persist/sqlite/consensus.go @@ -5,6 +5,7 @@ import ( "database/sql" "errors" "fmt" + "reflect" "time" "go.sia.tech/core/types" @@ -620,7 +621,7 @@ func addSiafundElements(tx *txn, index types.ChainIndex, spentElements, newEleme return sfDBIds, nil } -func addEvents(tx *txn, events []wallet.Event, bid types.BlockID) error { +func addEvents(tx *txn, bid types.BlockID, scDBIds map[types.SiacoinOutputID]int64, sfDBIds map[types.SiafundOutputID]int64, fcDBIds map[explorer.DBFileContract]int64, v2FcDBIds map[explorer.DBFileContract]int64, txnDBIds map[types.TransactionID]txnDBId, v2TxnDBIds map[types.TransactionID]txnDBId, events []wallet.Event) error { if len(events) == 0 { return nil } @@ -643,6 +644,36 @@ func addEvents(tx *txn, events []wallet.Event, bid types.BlockID) error { } defer relevantAddrStmt.Close() + v1TransactionEventStmt, err := tx.Prepare(`INSERT INTO v1_transaction_events (event_id, transaction_id) VALUES (?, ?)`) + if err != nil { + return fmt.Errorf("failed to prepare v1 transaction event statement: %w", err) + } + defer v1TransactionEventStmt.Close() + + v2TransactionEventStmt, err := tx.Prepare(`INSERT INTO v2_transaction_events (event_id, transaction_id) VALUES (?, ?)`) + if err != nil { + return fmt.Errorf("failed to prepare v2 transaction event statement: %w", err) + } + defer v2TransactionEventStmt.Close() + + payoutEventStmt, err := tx.Prepare(`INSERT INTO payout_events (event_id, output_id) VALUES (?, ?)`) + if err != nil { + return fmt.Errorf("failed to prepare minerpayout event statement: %w", err) + } + defer payoutEventStmt.Close() + + v1ContractResolutionEventStmt, err := tx.Prepare(`INSERT INTO v1_contract_resolution_events (event_id, output_id, parent_id, missed) VALUES (?, ?, ?, ?)`) + if err != nil { + return fmt.Errorf("failed to prepare v1 contract resolution event statement: %w", err) + } + defer v1ContractResolutionEventStmt.Close() + + v2ContractResolutionEventStmt, err := tx.Prepare(`INSERT INTO v2_contract_resolution_events (event_id, output_id, parent_id, missed) VALUES (?, ?, ?, ?)`) + if err != nil { + return fmt.Errorf("failed to prepare v2 contract resolution event statement: %w", err) + } + defer v2ContractResolutionEventStmt.Close() + var buf bytes.Buffer enc := types.NewEncoder(&buf) for _, event := range events { @@ -681,6 +712,30 @@ func addEvents(tx *txn, events []wallet.Event, bid types.BlockID) error { used[addr] = true } + + switch v := event.Data.(type) { + case wallet.EventV1Transaction: + dbID := txnDBIds[types.TransactionID(event.ID)].id + if _, err = v1TransactionEventStmt.Exec(eventID, dbID); err != nil { + return fmt.Errorf("failed to insert transaction event: %w", err) + } + case wallet.EventV2Transaction: + dbID := v2TxnDBIds[types.TransactionID(event.ID)].id + if _, err = v2TransactionEventStmt.Exec(eventID, dbID); err != nil { + return fmt.Errorf("failed to insert transaction event: %w", err) + } + case wallet.EventPayout: + _, err = payoutEventStmt.Exec(eventID, scDBIds[types.SiacoinOutputID(event.ID)]) + case wallet.EventV1ContractResolution: + _, err = v1ContractResolutionEventStmt.Exec(eventID, scDBIds[v.SiacoinElement.ID], fcDBIds[explorer.DBFileContract{ID: v.Parent.ID, RevisionNumber: v.Parent.FileContract.RevisionNumber}], v.Missed) + case wallet.EventV2ContractResolution: + _, err = v2ContractResolutionEventStmt.Exec(eventID, scDBIds[v.SiacoinElement.ID], v2FcDBIds[explorer.DBFileContract{ID: v.Resolution.Parent.ID, RevisionNumber: v.Resolution.Parent.V2FileContract.RevisionNumber}], v.Missed) + default: + return fmt.Errorf("unknown event type: %T", reflect.TypeOf(event.Data)) + } + if err != nil { + return fmt.Errorf("failed to insert %v event: %w", reflect.TypeOf(event.Data), err) + } } return nil } @@ -1011,7 +1066,7 @@ func (ut *updateTx) ApplyIndex(state explorer.UpdateState) error { return fmt.Errorf("ApplyIndex: failed to update metrics: %w", err) } else if err := addHostAnnouncements(ut.tx, state.Block.Timestamp, state.HostAnnouncements, state.V2HostAnnouncements); err != nil { return fmt.Errorf("ApplyIndex: failed to add host announcements: %w", err) - } else if err := addEvents(ut.tx /*scDBIds, fcDBIds, txnDBIds, v2TxnDBIds,*/, state.Events, state.Block.ID()); err != nil { + } else if err := addEvents(ut.tx, state.Block.ID(), scDBIds, sfDBIds, fcDBIds, v2FcDBIds, txnDBIds, v2TxnDBIds, 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) diff --git a/persist/sqlite/init.sql b/persist/sqlite/init.sql index 5d96b90..960a4c7 100644 --- a/persist/sqlite/init.sql +++ b/persist/sqlite/init.sql @@ -400,6 +400,35 @@ CREATE INDEX event_addresses_event_id_idx ON event_addresses (event_id); CREATE INDEX event_addresses_address_id_idx ON event_addresses (address_id); CREATE INDEX event_addresses_event_id_address_id_idx ON event_addresses (event_id, address_id); +CREATE TABLE v1_transaction_events ( + event_id INTEGER PRIMARY KEY REFERENCES events(id) ON DELETE CASCADE NOT NULL, + transaction_id INTEGER REFERENCES transactions(id) ON DELETE CASCADE NOT NULL +); + +CREATE TABLE v2_transaction_events ( + event_id INTEGER PRIMARY KEY REFERENCES events(id) ON DELETE CASCADE NOT NULL, + transaction_id INTEGER REFERENCES v2_transactions(id) ON DELETE CASCADE NOT NULL +); + +CREATE TABLE payout_events ( + event_id INTEGER PRIMARY KEY REFERENCES events(id) ON DELETE CASCADE NOT NULL, + output_id INTEGER REFERENCES siacoin_elements(id) ON DELETE CASCADE NOT NULL +); + +CREATE TABLE v1_contract_resolution_events ( + event_id INTEGER PRIMARY KEY REFERENCES events(id) ON DELETE CASCADE NOT NULL, + parent_id INTEGER REFERENCES file_contract_elements(id) ON DELETE CASCADE NOT NULL, + output_id INTEGER REFERENCES siacoin_elements(id) ON DELETE CASCADE NOT NULL, + missed INTEGER NOT NULL +); + +CREATE TABLE v2_contract_resolution_events ( + event_id INTEGER PRIMARY KEY REFERENCES events(id) ON DELETE CASCADE NOT NULL, + parent_id INTEGER REFERENCES v2_file_contract_elements(id) ON DELETE CASCADE NOT NULL, + output_id INTEGER REFERENCES siacoin_elements(id) ON DELETE CASCADE NOT NULL, + missed INTEGER NOT NULL +); + CREATE TABLE v2_file_contract_elements ( id INTEGER PRIMARY KEY, block_id BLOB REFERENCES blocks(id) ON DELETE CASCADE NOT NULL,