-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add events.go and fix bug in v2 contract resolution retrieval
- Loading branch information
1 parent
c1c650c
commit 6dd0557
Showing
1 changed file
with
272 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,272 @@ | ||
package sqlite | ||
|
||
import ( | ||
"database/sql" | ||
"errors" | ||
"fmt" | ||
"reflect" | ||
|
||
"go.sia.tech/core/types" | ||
"go.sia.tech/coreutils/wallet" | ||
"go.sia.tech/explored/explorer" | ||
) | ||
|
||
// Events returns the events with the given event IDs. If an event is not found, | ||
// it is skipped. | ||
func (s *Store) Events(eventIDs []types.Hash256) (events []explorer.Event, err error) { | ||
err = s.transaction(func(tx *txn) error { | ||
// sqlite doesn't have easy support for IN clauses, use a statement since | ||
// the number of event IDs is likely to be small instead of dynamically | ||
// building the query | ||
const query = ` | ||
WITH last_chain_index (height) AS ( | ||
SELECT MAX(height) FROM blocks | ||
) | ||
SELECT | ||
ev.id, | ||
ev.event_id, | ||
ev.maturity_height, | ||
ev.date_created, | ||
b.height, | ||
b.id, | ||
CASE | ||
WHEN last_chain_index.height < b.height THEN 0 | ||
ELSE last_chain_index.height - b.height | ||
END AS confirmations, | ||
ev.event_type | ||
FROM events ev | ||
INNER JOIN event_addresses ea ON (ev.id = ea.event_id) | ||
INNER JOIN address_balance sa ON (ea.address_id = sa.id) | ||
INNER JOIN blocks b ON (ev.block_id = b.id) | ||
CROSS JOIN last_chain_index | ||
WHERE ev.event_id = $1` | ||
|
||
stmt, err := tx.Prepare(query) | ||
if err != nil { | ||
return fmt.Errorf("failed to prepare statement: %w", err) | ||
} | ||
defer stmt.Close() | ||
|
||
events = make([]explorer.Event, 0, len(eventIDs)) | ||
for _, id := range eventIDs { | ||
event, _, err := scanEvent(tx, stmt.QueryRow(encode(id))) | ||
if errors.Is(err, sql.ErrNoRows) { | ||
continue | ||
} else if err != nil { | ||
return fmt.Errorf("failed to query transaction %q: %w", id, err) | ||
} | ||
events = append(events, event) | ||
} | ||
return nil | ||
}) | ||
return | ||
} | ||
|
||
func explorerToTypesV2Resolution(e explorer.V2FileContractResolution) (fcr types.V2FileContractResolution) { | ||
fcr.Parent = e.Parent.V2FileContractElement | ||
|
||
switch v := e.Resolution.(type) { | ||
case *explorer.V2FileContractRenewal: | ||
fcr.Resolution = &types.V2FileContractRenewal{ | ||
FinalRenterOutput: v.FinalRenterOutput, | ||
FinalHostOutput: v.FinalHostOutput, | ||
RenterRollover: v.RenterRollover, | ||
HostRollover: v.HostRollover, | ||
NewContract: v.NewContract.V2FileContractElement.V2FileContract, | ||
RenterSignature: v.RenterSignature, | ||
HostSignature: v.HostSignature, | ||
} | ||
case *types.V2StorageProof: | ||
case *types.V2FileContractExpiration: | ||
fcr.Resolution = v | ||
default: | ||
panic(fmt.Errorf("unexpected revision type: %v", reflect.TypeOf(v))) | ||
} | ||
return | ||
} | ||
|
||
func explorerToEventV1Transaction(e explorer.Transaction) (ev wallet.EventV1Transaction) { | ||
extendedFCToTypes := func(fc explorer.ExtendedFileContract) types.FileContract { | ||
result := types.FileContract{ | ||
Filesize: fc.Filesize, | ||
FileMerkleRoot: fc.FileMerkleRoot, | ||
WindowStart: fc.WindowStart, | ||
WindowEnd: fc.WindowEnd, | ||
Payout: fc.Payout, | ||
UnlockHash: fc.UnlockHash, | ||
RevisionNumber: fc.RevisionNumber, | ||
} | ||
for _, vpo := range fc.ValidProofOutputs { | ||
result.ValidProofOutputs = append(result.ValidProofOutputs, vpo.SiacoinOutput) | ||
} | ||
for _, mpo := range fc.MissedProofOutputs { | ||
result.MissedProofOutputs = append(result.MissedProofOutputs, mpo.SiacoinOutput) | ||
} | ||
return result | ||
} | ||
|
||
txn := &ev.Transaction | ||
for _, sci := range e.SiacoinInputs { | ||
txn.SiacoinInputs = append(txn.SiacoinInputs, sci.SiacoinInput) | ||
} | ||
for _, sco := range e.SiacoinOutputs { | ||
txn.SiacoinOutputs = append(txn.SiacoinOutputs, sco.SiacoinOutput) | ||
} | ||
for _, sfi := range e.SiafundInputs { | ||
txn.SiafundInputs = append(txn.SiafundInputs, sfi.SiafundInput) | ||
} | ||
for _, sfo := range e.SiafundOutputs { | ||
txn.SiafundOutputs = append(txn.SiafundOutputs, sfo.SiafundOutput) | ||
} | ||
for _, fc := range e.FileContracts { | ||
txn.FileContracts = append(txn.FileContracts, extendedFCToTypes(fc)) | ||
} | ||
for _, fcr := range e.FileContractRevisions { | ||
txn.FileContractRevisions = append(txn.FileContractRevisions, types.FileContractRevision{ | ||
ParentID: fcr.ParentID, | ||
UnlockConditions: fcr.UnlockConditions, | ||
FileContract: extendedFCToTypes(fcr.ExtendedFileContract), | ||
}) | ||
} | ||
for _, sp := range e.StorageProofs { | ||
txn.StorageProofs = append(txn.StorageProofs, sp) | ||
} | ||
for _, fee := range e.MinerFees { | ||
txn.MinerFees = append(txn.MinerFees, fee) | ||
} | ||
for _, arb := range e.ArbitraryData { | ||
txn.ArbitraryData = append(txn.ArbitraryData, arb) | ||
} | ||
for _, sig := range e.Signatures { | ||
txn.Signatures = append(txn.Signatures, sig) | ||
} | ||
|
||
return | ||
} | ||
|
||
func explorerToEventV2Transaction(e explorer.V2Transaction) (txn wallet.EventV2Transaction) { | ||
for _, sci := range e.SiacoinInputs { | ||
txn.SiacoinInputs = append(txn.SiacoinInputs, sci) | ||
} | ||
for _, sco := range e.SiacoinOutputs { | ||
txn.SiacoinOutputs = append(txn.SiacoinOutputs, sco.SiacoinOutput) | ||
} | ||
for _, sfi := range e.SiafundInputs { | ||
txn.SiafundInputs = append(txn.SiafundInputs, sfi) | ||
} | ||
for _, sfo := range e.SiafundOutputs { | ||
txn.SiafundOutputs = append(txn.SiafundOutputs, sfo.SiafundOutput) | ||
} | ||
for _, fc := range e.FileContracts { | ||
txn.FileContracts = append(txn.FileContracts, fc.V2FileContractElement.V2FileContract) | ||
} | ||
for _, fcr := range e.FileContractRevisions { | ||
txn.FileContractRevisions = append(txn.FileContractRevisions, types.V2FileContractRevision{ | ||
Parent: fcr.Parent.V2FileContractElement, | ||
Revision: fcr.Revision.V2FileContractElement.V2FileContract, | ||
}) | ||
} | ||
for _, fcr := range e.FileContractResolutions { | ||
txn.FileContractResolutions = append(txn.FileContractResolutions, explorerToTypesV2Resolution(fcr)) | ||
} | ||
for _, a := range e.Attestations { | ||
txn.Attestations = append(txn.Attestations, a) | ||
} | ||
for _, arb := range e.ArbitraryData { | ||
txn.ArbitraryData = append(txn.ArbitraryData, arb) | ||
} | ||
txn.NewFoundationAddress = e.NewFoundationAddress | ||
txn.MinerFee = e.MinerFee | ||
|
||
return | ||
} | ||
|
||
func scanEvent(tx *txn, s scanner) (ev explorer.Event, eventID int64, err error) { | ||
err = s.Scan(&eventID, decode(&ev.ID), &ev.MaturityHeight, decode(&ev.Timestamp), &ev.Index.Height, decode(&ev.Index.ID), &ev.Confirmations, &ev.Type) | ||
if err != nil { | ||
return | ||
} | ||
|
||
switch ev.Type { | ||
case wallet.EventTypeV1Transaction: | ||
var txnID int64 | ||
err = tx.QueryRow(`SELECT transaction_id FROM v1_transaction_events WHERE event_id = ?`, eventID).Scan(&txnID) | ||
if err != nil { | ||
return explorer.Event{}, 0, fmt.Errorf("failed to fetch v1 transaction ID: %w", err) | ||
} | ||
txns, err := getTransactions(tx, map[int64]transactionID{0: {dbID: txnID, id: types.TransactionID(ev.ID)}}) | ||
if err != nil || len(txns) == 0 { | ||
return explorer.Event{}, 0, fmt.Errorf("failed to fetch v1 transaction: %w", err) | ||
} | ||
ev.Data = explorerToEventV1Transaction(txns[0]) | ||
case wallet.EventTypeV2Transaction: | ||
txns, err := getV2Transactions(tx, []types.TransactionID{types.TransactionID(ev.ID)}) | ||
if err != nil || len(txns) == 0 { | ||
return explorer.Event{}, 0, fmt.Errorf("failed to fetch v2 transaction: %w", err) | ||
} | ||
ev.Data = explorerToEventV2Transaction(txns[0]) | ||
case wallet.EventTypeV1ContractResolution: | ||
var resolution wallet.EventV1ContractResolution | ||
fce, sce := &resolution.Parent, &resolution.SiacoinElement | ||
err := tx.QueryRow(`SELECT sce.output_id, sce.leaf_index, sce.maturity_height, sce.address, sce.value, fce.contract_id, fce.leaf_index, fce.filesize, fce.file_merkle_root, fce.window_start, fce.window_end, fce.payout, fce.unlock_hash, fce.revision_number, ev.missed | ||
FROM v1_contract_resolution_events ev | ||
JOIN siacoin_elements sce ON ev.output_id = sce.id | ||
JOIN file_contract_elements fce ON ev.parent_id = fce.id | ||
WHERE ev.event_id = ?`, eventID).Scan(decode(&sce.ID), decode(&sce.StateElement.LeafIndex), decode(&sce.MaturityHeight), decode(&sce.SiacoinOutput.Address), decode(&sce.SiacoinOutput.Value), decode(&fce.ID), decode(&fce.StateElement.LeafIndex), decode(&fce.FileContract.Filesize), decode(&fce.FileContract.FileMerkleRoot), decode(&fce.FileContract.WindowStart), decode(&fce.FileContract.WindowEnd), decode(&fce.FileContract.Payout), decode(&fce.FileContract.UnlockHash), decode(&fce.FileContract.RevisionNumber), &resolution.Missed) | ||
if err != nil { | ||
return wallet.Event{}, 0, fmt.Errorf("failed to retrieve v1 resolution event: %w", err) | ||
} | ||
ev.Data = resolution | ||
case wallet.EventTypeV2ContractResolution: | ||
var resolution wallet.EventV2ContractResolution | ||
var parentContractID types.FileContractID | ||
var resolutionTransactionID types.TransactionID | ||
sce := &resolution.SiacoinElement | ||
err := tx.QueryRow(`SELECT sce.output_id, sce.leaf_index, sce.maturity_height, sce.address, sce.value, rev.contract_id, rev.resolution_transaction_id, ev.missed | ||
FROM v2_contract_resolution_events ev | ||
JOIN siacoin_elements sce ON ev.output_id = sce.id | ||
JOIN v2_file_contract_elements fce ON ev.parent_id = fce.id | ||
JOIN v2_last_contract_revision rev ON fce.contract_id = rev.contract_id | ||
WHERE ev.event_id = ?`, eventID).Scan(decode(&sce.ID), decode(&sce.StateElement.LeafIndex), decode(&sce.MaturityHeight), decode(&sce.SiacoinOutput.Address), decode(&sce.SiacoinOutput.Value), decode(&parentContractID), decode(&resolutionTransactionID), &resolution.Missed) | ||
if err != nil { | ||
return wallet.Event{}, 0, fmt.Errorf("failed to retrieve v2 resolution event: %w", err) | ||
} | ||
|
||
resolutionTxns, err := getV2Transactions(tx, []types.TransactionID{resolutionTransactionID}) | ||
if err != nil { | ||
return wallet.Event{}, 0, fmt.Errorf("failed to get transaction with v2 resolution: %w", err) | ||
} else if len(resolutionTxns) == 0 { | ||
return wallet.Event{}, 0, fmt.Errorf("v2 resolution transaction not found") | ||
} | ||
txn := resolutionTxns[0] | ||
|
||
found := false | ||
for _, fcr := range txn.FileContractResolutions { | ||
if fcr.Parent.ID == parentContractID { | ||
found = true | ||
resolution.Resolution = explorerToTypesV2Resolution(fcr) | ||
break | ||
} | ||
} | ||
if !found { | ||
return wallet.Event{}, 0, fmt.Errorf("failed to find resolution in v2 resolution transaction") | ||
} | ||
|
||
ev.Data = resolution | ||
case wallet.EventTypeSiafundClaim, wallet.EventTypeMinerPayout, wallet.EventTypeFoundationSubsidy: | ||
var payout wallet.EventPayout | ||
sce := &payout.SiacoinElement | ||
err := tx.QueryRow(`SELECT sce.output_id, sce.leaf_index, sce.maturity_height, sce.address, sce.value | ||
FROM payout_events ev | ||
JOIN siacoin_elements sce ON ev.output_id = sce.id | ||
WHERE ev.event_id = ?`, eventID).Scan(decode(&sce.ID), decode(&sce.StateElement.LeafIndex), decode(&sce.MaturityHeight), decode(&sce.SiacoinOutput.Address), decode(&sce.SiacoinOutput.Value)) | ||
if err != nil { | ||
return wallet.Event{}, 0, fmt.Errorf("failed to retrieve payout event: %w", err) | ||
} | ||
ev.Data = payout | ||
default: | ||
return wallet.Event{}, 0, fmt.Errorf("unknown event type: %q", ev.Type) | ||
} | ||
|
||
return | ||
} |