diff --git a/api/client.go b/api/client.go index ba83399b..7ba80929 100644 --- a/api/client.go +++ b/api/client.go @@ -186,26 +186,26 @@ func (c *Client) AddressBalance(address types.Address) (resp AddressBalanceRespo } // Contract returns the file contract with the specified ID. -func (c *Client) Contract(id types.FileContractID) (resp explorer.FileContract, err error) { +func (c *Client) Contract(id types.FileContractID) (resp explorer.EnhancedFileContract, err error) { err = c.c.GET(fmt.Sprintf("/contracts/%s", id), &resp) return } // Contracts returns the transactions with the specified IDs. -func (c *Client) Contracts(ids []types.FileContractID) (resp []explorer.FileContract, err error) { +func (c *Client) Contracts(ids []types.FileContractID) (resp []explorer.EnhancedFileContract, err error) { err = c.c.POST("/contracts", ids, &resp) return } // ContractsKey returns the contracts for a particular ed25519 key. -func (c *Client) ContractsKey(key types.PublicKey) (resp []explorer.FileContract, err error) { +func (c *Client) ContractsKey(key types.PublicKey) (resp []explorer.EnhancedFileContract, err error) { err = c.c.GET(fmt.Sprintf("/pubkey/%s/contracts", key), &resp) return } // ContractRevisions returns all the revisions of the contract with the // specified ID. -func (c *Client) ContractRevisions(id types.FileContractID) (resp []explorer.FileContract, err error) { +func (c *Client) ContractRevisions(id types.FileContractID) (resp []explorer.EnhancedFileContract, err error) { err = c.c.GET(fmt.Sprintf("/contracts/%s/revisions", id), &resp) return } diff --git a/api/server.go b/api/server.go index 699c5046..0f00f6d9 100644 --- a/api/server.go +++ b/api/server.go @@ -62,9 +62,9 @@ type ( UnspentSiacoinOutputs(address types.Address, offset, limit uint64) ([]explorer.SiacoinOutput, error) UnspentSiafundOutputs(address types.Address, offset, limit uint64) ([]explorer.SiafundOutput, error) AddressEvents(address types.Address, offset, limit uint64) (events []explorer.Event, err error) - Contracts(ids []types.FileContractID) (result []explorer.FileContract, err error) - ContractsKey(key types.PublicKey) (result []explorer.FileContract, err error) - ContractRevisions(id types.FileContractID) (result []explorer.FileContract, err error) + Contracts(ids []types.FileContractID) (result []explorer.EnhancedFileContract, err error) + ContractsKey(key types.PublicKey) (result []explorer.EnhancedFileContract, err error) + ContractRevisions(id types.FileContractID) (result []explorer.EnhancedFileContract, err error) Search(id types.Hash256) (explorer.SearchType, error) Hosts(pks []types.PublicKey) ([]explorer.Host, error) diff --git a/explorer/explorer.go b/explorer/explorer.go index 52bc70ac..5dc5013a 100644 --- a/explorer/explorer.go +++ b/explorer/explorer.go @@ -57,9 +57,9 @@ type Store interface { UnspentSiafundOutputs(address types.Address, offset, limit uint64) ([]SiafundOutput, error) AddressEvents(address types.Address, offset, limit uint64) (events []Event, err error) Balance(address types.Address) (sc types.Currency, immatureSC types.Currency, sf uint64, err error) - Contracts(ids []types.FileContractID) (result []FileContract, err error) - ContractsKey(key types.PublicKey) (result []FileContract, err error) - ContractRevisions(id types.FileContractID) (result []FileContract, err error) + Contracts(ids []types.FileContractID) (result []EnhancedFileContract, err error) + ContractsKey(key types.PublicKey) (result []EnhancedFileContract, err error) + ContractRevisions(id types.FileContractID) (result []EnhancedFileContract, err error) SiacoinElements(ids []types.SiacoinOutputID) (result []SiacoinOutput, err error) SiafundElements(ids []types.SiafundOutputID) (result []SiafundOutput, err error) @@ -247,18 +247,18 @@ func (e *Explorer) Balance(address types.Address) (sc types.Currency, immatureSC } // Contracts returns the contracts with the specified IDs. -func (e *Explorer) Contracts(ids []types.FileContractID) (result []FileContract, err error) { +func (e *Explorer) Contracts(ids []types.FileContractID) (result []EnhancedFileContract, err error) { return e.s.Contracts(ids) } // ContractsKey returns the contracts for a particular ed25519 key. -func (e *Explorer) ContractsKey(key types.PublicKey) (result []FileContract, err error) { +func (e *Explorer) ContractsKey(key types.PublicKey) (result []EnhancedFileContract, err error) { return e.s.ContractsKey(key) } // ContractRevisions returns all the revisions of the contract with the // specified ID. -func (e *Explorer) ContractRevisions(id types.FileContractID) (result []FileContract, err error) { +func (e *Explorer) ContractRevisions(id types.FileContractID) (result []EnhancedFileContract, err error) { return e.s.ContractRevisions(id) } diff --git a/explorer/types.go b/explorer/types.go index 269aa60a..dea4ee43 100644 --- a/explorer/types.go +++ b/explorer/types.go @@ -123,9 +123,38 @@ type SiafundOutput struct { types.SiafundElement } -// A FileContract is a types.FileContractElement with added fields for -// resolved/valid state. +type ContractSiacoinOutput struct { + ID types.SiacoinOutputID `json:"id"` + types.SiacoinOutput +} + +// A FileContract is a storage agreement between a renter and a host. It +// contains a bidirectional payment channel that resolves as either "valid" or +// "missed" depending on whether a valid StorageProof is submitted for the +// contract. type FileContract struct { + Filesize uint64 `json:"filesize"` + FileMerkleRoot types.Hash256 `json:"fileMerkleRoot"` + WindowStart uint64 `json:"windowStart"` + WindowEnd uint64 `json:"windowEnd"` + Payout types.Currency `json:"payout"` + ValidProofOutputs []ContractSiacoinOutput `json:"validProofOutputs"` + MissedProofOutputs []ContractSiacoinOutput `json:"missedProofOutputs"` + UnlockHash types.Hash256 `json:"unlockHash"` + RevisionNumber uint64 `json:"revisionNumber"` +} + +// A FileContractElement is a record of a FileContract within the state +// accumulator. +type FileContractElement struct { + ID types.FileContractID `json:"id"` + StateElement types.StateElement `json:"stateElement"` + FileContract FileContract `json:"fileContract"` +} + +// A EnhancedFileContract is a FileContractElement with added fields for +// resolved/valid state, and when the transaction was confirmed and proved. +type EnhancedFileContract struct { Resolved bool `json:"resolved"` Valid bool `json:"valid"` @@ -137,7 +166,7 @@ type FileContract struct { ProofIndex *types.ChainIndex `json:"proofIndex"` ProofTransactionID *types.TransactionID `json:"proofTransactionID"` - types.FileContractElement + FileContractElement } // A FileContractRevision is a FileContract with extra fields for revision @@ -146,7 +175,7 @@ type FileContractRevision struct { ParentID types.FileContractID `json:"parentID"` UnlockConditions types.UnlockConditions `json:"unlockConditions"` - FileContract + EnhancedFileContract } // A Transaction is a transaction that uses the wrapped types above. @@ -156,7 +185,7 @@ type Transaction struct { SiacoinOutputs []SiacoinOutput `json:"siacoinOutputs,omitempty"` SiafundInputs []SiafundInput `json:"siafundInputs,omitempty"` SiafundOutputs []SiafundOutput `json:"siafundOutputs,omitempty"` - FileContracts []FileContract `json:"fileContracts,omitempty"` + FileContracts []EnhancedFileContract `json:"fileContracts,omitempty"` FileContractRevisions []FileContractRevision `json:"fileContractRevisions,omitempty"` StorageProofs []types.StorageProof `json:"storageProofs,omitempty"` MinerFees []types.Currency `json:"minerFees,omitempty"` diff --git a/internal/testutil/check.go b/internal/testutil/check.go index 95fbe4f3..0667fb59 100644 --- a/internal/testutil/check.go +++ b/internal/testutil/check.go @@ -134,7 +134,7 @@ func CheckV2ChainIndices(t *testing.T, db explorer.Store, txnID types.Transactio // CheckFC checks the retrieved file contract with the source file contract in // addition to checking the resolved and valid fields. -func CheckFC(t *testing.T, revision, resolved, valid bool, expected types.FileContract, got explorer.FileContract) { +func CheckFC(t *testing.T, revision, resolved, valid bool, expected types.FileContract, got explorer.EnhancedFileContract) { t.Helper() Equal(t, "resolved state", resolved, got.Resolved) diff --git a/persist/sqlite/consensus.go b/persist/sqlite/consensus.go index 3ca93686..ab72906e 100644 --- a/persist/sqlite/consensus.go +++ b/persist/sqlite/consensus.go @@ -790,13 +790,13 @@ func updateFileContractElements(tx *txn, revert bool, b types.Block, fces []expl } defer revisionStmt.Close() - validOutputsStmt, err := tx.Prepare(`INSERT INTO file_contract_valid_proof_outputs(contract_id, contract_order, address, value) VALUES (?, ?, ?, ?) ON CONFLICT DO NOTHING`) + validOutputsStmt, err := tx.Prepare(`INSERT INTO file_contract_valid_proof_outputs(contract_id, contract_order, id, address, value) VALUES (?, ?, ?, ?, ?) ON CONFLICT DO NOTHING`) if err != nil { return nil, fmt.Errorf("addFileContracts: failed to prepare valid proof outputs statement: %w", err) } defer validOutputsStmt.Close() - missedOutputsStmt, err := tx.Prepare(`INSERT INTO file_contract_missed_proof_outputs(contract_id, contract_order, address, value) VALUES (?, ?, ?, ?) ON CONFLICT DO NOTHING`) + missedOutputsStmt, err := tx.Prepare(`INSERT INTO file_contract_missed_proof_outputs(contract_id, contract_order, id, address, value) VALUES (?, ?, ?, ?, ?) ON CONFLICT DO NOTHING`) if err != nil { return nil, fmt.Errorf("addFileContracts: failed to prepare missed proof outputs statement: %w", err) } @@ -861,12 +861,12 @@ func updateFileContractElements(tx *txn, revert bool, b types.Block, fces []expl } for i, sco := range fc.ValidProofOutputs { - if _, err := validOutputsStmt.Exec(dbID, i, encode(sco.Address), encode(sco.Value)); err != nil { + if _, err := validOutputsStmt.Exec(dbID, i, encode(fcID.ValidOutputID(i)), encode(sco.Address), encode(sco.Value)); err != nil { return fmt.Errorf("updateFileContractElements: failed to execute valid proof outputs statement: %w", err) } } for i, sco := range fc.MissedProofOutputs { - if _, err := missedOutputsStmt.Exec(dbID, i, encode(sco.Address), encode(sco.Value)); err != nil { + if _, err := missedOutputsStmt.Exec(dbID, i, encode(fcID.MissedOutputID(i)), encode(sco.Address), encode(sco.Value)); err != nil { return fmt.Errorf("updateFileContractElements: failed to execute missed proof outputs statement: %w", err) } } diff --git a/persist/sqlite/consensus_test.go b/persist/sqlite/consensus_test.go index 141fe92f..b6f92268 100644 --- a/persist/sqlite/consensus_test.go +++ b/persist/sqlite/consensus_test.go @@ -82,16 +82,34 @@ func CheckChainIndices(t *testing.T, db explorer.Store, txnID types.TransactionI } // CheckFCRevisions checks that the revision numbers for the file contracts match. -func CheckFCRevisions(t *testing.T, confirmationIndex types.ChainIndex, confirmationTransactionID types.TransactionID, valid, missed []types.SiacoinOutput, revisionNumbers []uint64, fcs []explorer.FileContract) { +func CheckFCRevisions(t *testing.T, confirmationIndex types.ChainIndex, confirmationTransactionID types.TransactionID, valid, missed []types.SiacoinOutput, revisionNumbers []uint64, fcs []explorer.EnhancedFileContract) { t.Helper() testutil.Equal(t, "number of revisions", len(revisionNumbers), len(fcs)) for i := range revisionNumbers { + testutil.Equal(t, "revision number", revisionNumbers[i], fcs[i].FileContract.RevisionNumber) testutil.Equal(t, "confirmation index", confirmationIndex, *fcs[i].ConfirmationIndex) testutil.Equal(t, "confirmation transaction ID", confirmationTransactionID, *fcs[i].ConfirmationTransactionID) - testutil.Equal(t, "valid proof outputs", valid, fcs[i].FileContract.ValidProofOutputs) - testutil.Equal(t, "missed proof outputs", missed, fcs[i].FileContract.MissedProofOutputs) - testutil.Equal(t, "revision number", revisionNumbers[i], fcs[i].FileContract.RevisionNumber) + + testutil.Equal(t, "valid proof outputs", len(valid), len(fcs[i].FileContract.ValidProofOutputs)) + for j := range valid { + expected := valid[j] + got := fcs[i].FileContract.ValidProofOutputs[j] + + testutil.Equal(t, "id", fcs[i].ID.ValidOutputID(j), got.ID) + testutil.Equal(t, "value", expected.Value, got.Value) + testutil.Equal(t, "address", expected.Address, got.Address) + } + + testutil.Equal(t, "missed proof outputs", len(missed), len(fcs[i].FileContract.MissedProofOutputs)) + for j := range missed { + expected := missed[j] + got := fcs[i].FileContract.MissedProofOutputs[j] + + testutil.Equal(t, "id", fcs[i].ID.MissedOutputID(j), got.ID) + testutil.Equal(t, "value", expected.Value, got.Value) + testutil.Equal(t, "address", expected.Address, got.Address) + } } } @@ -697,7 +715,7 @@ func TestFileContract(t *testing.T) { testutil.Equal(t, "confirmation index", prevTip, *fcr.ConfirmationIndex) testutil.Equal(t, "confirmation transaction ID", txn.ID(), *fcr.ConfirmationTransactionID) - testutil.CheckFC(t, false, false, false, fc, fcr.FileContract) + testutil.CheckFC(t, false, false, false, fc, fcr.EnhancedFileContract) } for i := cm.Tip().Height; i < windowEnd; i++ { @@ -920,7 +938,7 @@ func TestEphemeralFileContract(t *testing.T) { testutil.Equal(t, "parent id", txn.FileContractID(0), fcr.ParentID) testutil.Equal(t, "unlock conditions", uc, fcr.UnlockConditions) - testutil.CheckFC(t, true, false, false, revisedFC1, fcr.FileContract) + testutil.CheckFC(t, true, false, false, revisedFC1, fcr.EnhancedFileContract) } revisedFC2 := revisedFC1 @@ -1002,7 +1020,7 @@ func TestEphemeralFileContract(t *testing.T) { fcr := txns[0].FileContractRevisions[0] testutil.Equal(t, "parent id", txn.FileContractID(0), fcr.ParentID) testutil.Equal(t, "unlock conditions", uc, fcr.UnlockConditions) - testutil.CheckFC(t, true, false, false, revisedFC2, fcr.FileContract) + testutil.CheckFC(t, true, false, false, revisedFC2, fcr.EnhancedFileContract) } { @@ -1016,7 +1034,7 @@ func TestEphemeralFileContract(t *testing.T) { fcr := txns[0].FileContractRevisions[0] testutil.Equal(t, "parent id", txn.FileContractID(0), fcr.ParentID) testutil.Equal(t, "unlock conditions", uc, fcr.UnlockConditions) - testutil.CheckFC(t, true, false, false, revisedFC3, fcr.FileContract) + testutil.CheckFC(t, true, false, false, revisedFC3, fcr.EnhancedFileContract) } } @@ -2330,7 +2348,7 @@ func TestMultipleReorgFileContract(t *testing.T) { testutil.Equal(t, "parent id", txn.FileContractID(0), fcr.ParentID) testutil.Equal(t, "unlock conditions", uc, fcr.UnlockConditions) - testutil.CheckFC(t, false, false, false, revFC, fcr.FileContract) + testutil.CheckFC(t, false, false, false, revFC, fcr.EnhancedFileContract) } { diff --git a/persist/sqlite/contracts.go b/persist/sqlite/contracts.go index 89a82096..b13d8836 100644 --- a/persist/sqlite/contracts.go +++ b/persist/sqlite/contracts.go @@ -15,7 +15,7 @@ func encodedIDs(ids []types.FileContractID) []any { return result } -func scanFileContract(s scanner) (contractID int64, fc explorer.FileContract, err error) { +func scanFileContract(s scanner) (contractID int64, fc explorer.EnhancedFileContract, err error) { var confirmationIndex, proofIndex types.ChainIndex var confirmationTransactionID, proofTransactionID types.TransactionID err = s.Scan(&contractID, decode(&fc.ID), decode(&fc.StateElement.LeafIndex), &fc.Resolved, &fc.Valid, decode(&fc.TransactionID), 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)) @@ -37,7 +37,7 @@ func scanFileContract(s scanner) (contractID int64, fc explorer.FileContract, er } // Contracts implements explorer.Store. -func (s *Store) Contracts(ids []types.FileContractID) (result []explorer.FileContract, err error) { +func (s *Store) Contracts(ids []types.FileContractID) (result []explorer.EnhancedFileContract, err error) { err = s.transaction(func(tx *txn) error { query := `SELECT fc1.id, fc1.contract_id, fc1.leaf_index, fc1.resolved, fc1.valid, fc1.transaction_id, 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 @@ -50,10 +50,10 @@ func (s *Store) Contracts(ids []types.FileContractID) (result []explorer.FileCon defer rows.Close() var contractIDs []int64 - idContract := make(map[int64]explorer.FileContract) + idContract := make(map[int64]explorer.EnhancedFileContract) for rows.Next() { var contractID int64 - var fc explorer.FileContract + var fc explorer.EnhancedFileContract contractID, fc, err := scanFileContract(rows) if err != nil { @@ -82,7 +82,7 @@ func (s *Store) Contracts(ids []types.FileContractID) (result []explorer.FileCon } // ContractRevisions implements explorer.Store. -func (s *Store) ContractRevisions(id types.FileContractID) (revisions []explorer.FileContract, err error) { +func (s *Store) ContractRevisions(id types.FileContractID) (revisions []explorer.EnhancedFileContract, err error) { err = s.transaction(func(tx *txn) error { query := `SELECT fc.id, fc.contract_id, fc.leaf_index, fc.resolved, fc.valid, fc.transaction_id, rev.confirmation_index, rev.confirmation_transaction_id, rev.proof_index, rev.proof_transaction_id, 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 @@ -98,7 +98,7 @@ func (s *Store) ContractRevisions(id types.FileContractID) (revisions []explorer // fetch revisions type fce struct { ID int64 - FileContract explorer.FileContract + FileContract explorer.EnhancedFileContract } var fces []fce var contractIDs []int64 @@ -119,7 +119,7 @@ func (s *Store) ContractRevisions(id types.FileContractID) (revisions []explorer } // merge outputs into revisions - revisions = make([]explorer.FileContract, len(fces)) + revisions = make([]explorer.EnhancedFileContract, len(fces)) for i, revision := range fces { output, found := proofOutputs[revision.ID] if !found { @@ -140,7 +140,7 @@ func (s *Store) ContractRevisions(id types.FileContractID) (revisions []explorer } // ContractsKey implements explorer.Store. -func (s *Store) ContractsKey(key types.PublicKey) (result []explorer.FileContract, err error) { +func (s *Store) ContractsKey(key types.PublicKey) (result []explorer.EnhancedFileContract, err error) { err = s.transaction(func(tx *txn) error { query := `SELECT fc1.id, fc1.contract_id, fc1.leaf_index, fc1.resolved, fc1.valid, fc1.transaction_id, 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 @@ -153,7 +153,7 @@ func (s *Store) ContractsKey(key types.PublicKey) (result []explorer.FileContrac defer rows.Close() var contractIDs []int64 - idContract := make(map[int64]explorer.FileContract) + idContract := make(map[int64]explorer.EnhancedFileContract) for rows.Next() { contractID, fc, err := scanFileContract(rows) if err != nil { diff --git a/persist/sqlite/init.sql b/persist/sqlite/init.sql index d43a451f..18c6d63f 100644 --- a/persist/sqlite/init.sql +++ b/persist/sqlite/init.sql @@ -116,6 +116,7 @@ CREATE TABLE last_contract_revision ( 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, + id BLOB NOT NULL, address BLOB NOT NULL, value BLOB NOT NULL, UNIQUE(contract_id, contract_order) @@ -126,6 +127,7 @@ CREATE INDEX file_contract_valid_proof_outputs_contract_id_index ON file_contrac 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, + id BLOB NOT NULL, address BLOB NOT NULL, value BLOB NOT NULL, UNIQUE(contract_id, contract_order) diff --git a/persist/sqlite/transactions.go b/persist/sqlite/transactions.go index 683d8d32..92902e6e 100644 --- a/persist/sqlite/transactions.go +++ b/persist/sqlite/transactions.go @@ -221,14 +221,14 @@ ORDER BY ts.transaction_order ASC` } type fileContractProofOutputs struct { - valid []types.SiacoinOutput - missed []types.SiacoinOutput + valid []explorer.ContractSiacoinOutput + missed []explorer.ContractSiacoinOutput } func fileContractOutputs(tx *txn, contractIDs []int64) (map[int64]fileContractProofOutputs, error) { result := make(map[int64]fileContractProofOutputs) - validQuery := `SELECT contract_id, address, value + validQuery := `SELECT contract_id, id, address, value FROM file_contract_valid_proof_outputs WHERE contract_id IN (` + queryPlaceHolders(len(contractIDs)) + `) ORDER BY contract_order` @@ -240,8 +240,8 @@ ORDER BY contract_order` for validRows.Next() { var contractID int64 - var sco types.SiacoinOutput - if err := validRows.Scan(&contractID, decode(&sco.Address), decode(&sco.Value)); err != nil { + var sco explorer.ContractSiacoinOutput + if err := validRows.Scan(&contractID, decode(&sco.ID), decode(&sco.Address), decode(&sco.Value)); err != nil { return nil, fmt.Errorf("failed to scan valid proof output: %w", err) } @@ -250,7 +250,7 @@ ORDER BY contract_order` result[contractID] = r } - missedQuery := `SELECT contract_id, address, value + missedQuery := `SELECT contract_id, id, address, value FROM file_contract_missed_proof_outputs WHERE contract_id IN (` + queryPlaceHolders(len(contractIDs)) + `) ORDER BY contract_order` @@ -262,8 +262,8 @@ ORDER BY contract_order` for missedRows.Next() { var contractID int64 - var sco types.SiacoinOutput - if err := missedRows.Scan(&contractID, decode(&sco.Address), decode(&sco.Value)); err != nil { + var sco explorer.ContractSiacoinOutput + if err := missedRows.Scan(&contractID, decode(&sco.ID), decode(&sco.Address), decode(&sco.Value)); err != nil { return nil, fmt.Errorf("failed to scan missed proof output: %w", err) } @@ -281,7 +281,7 @@ type contractOrder struct { } // transactionFileContracts returns the file contracts for each transaction. -func transactionFileContracts(tx *txn, txnIDs []int64) (map[int64][]explorer.FileContract, error) { +func transactionFileContracts(tx *txn, txnIDs []int64) (map[int64][]explorer.EnhancedFileContract, error) { query := `SELECT ts.transaction_id, fc.id, rev.confirmation_index, rev.confirmation_transaction_id, rev.proof_index, rev.proof_transaction_id, fc.contract_id, fc.leaf_index, fc.resolved, fc.valid, fc.transaction_id, 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) @@ -296,12 +296,12 @@ ORDER BY ts.transaction_order ASC` var contractIDs []int64 // map transaction ID to contract list - result := make(map[int64][]explorer.FileContract) + result := make(map[int64][]explorer.EnhancedFileContract) // map contract ID to transaction ID contractTransaction := make(map[int64]contractOrder) for rows.Next() { var txnID, contractID int64 - var fc explorer.FileContract + var fc explorer.EnhancedFileContract var confirmationIndex, proofIndex types.ChainIndex var confirmationTransactionID, proofTransactionID types.TransactionID @@ -366,7 +366,7 @@ ORDER BY ts.transaction_order ASC` var confirmationIndex, proofIndex types.ChainIndex var confirmationTransactionID, proofTransactionID types.TransactionID - if err := rows.Scan(&txnID, &contractID, decodeNull(&confirmationIndex), decodeNull(&confirmationTransactionID), decodeNull(&proofIndex), decodeNull(&proofTransactionID), decode(&fc.ParentID), decode(&fc.UnlockConditions), decode(&fc.ID), decode(&fc.StateElement.LeafIndex), &fc.Resolved, &fc.Valid, decode(&fc.TransactionID), decode(&fc.FileContract.FileContract.Filesize), decode(&fc.FileContract.FileContract.FileMerkleRoot), decode(&fc.FileContract.FileContract.WindowStart), decode(&fc.FileContract.FileContract.WindowEnd), decode(&fc.FileContract.FileContract.Payout), decode(&fc.FileContract.FileContract.UnlockHash), decode(&fc.FileContract.FileContract.RevisionNumber)); err != nil { + if err := rows.Scan(&txnID, &contractID, decodeNull(&confirmationIndex), decodeNull(&confirmationTransactionID), decodeNull(&proofIndex), decodeNull(&proofTransactionID), decode(&fc.ParentID), decode(&fc.UnlockConditions), decode(&fc.ID), decode(&fc.StateElement.LeafIndex), &fc.Resolved, &fc.Valid, decode(&fc.TransactionID), decode(&fc.EnhancedFileContract.FileContract.Filesize), decode(&fc.EnhancedFileContract.FileContract.FileMerkleRoot), decode(&fc.EnhancedFileContract.FileContract.WindowStart), decode(&fc.EnhancedFileContract.FileContract.WindowEnd), decode(&fc.EnhancedFileContract.FileContract.Payout), decode(&fc.EnhancedFileContract.FileContract.UnlockHash), decode(&fc.EnhancedFileContract.FileContract.RevisionNumber)); err != nil { return nil, fmt.Errorf("failed to scan file contract: %w", err) } @@ -395,8 +395,8 @@ ORDER BY ts.transaction_order ASC` } for contractID, output := range proofOutputs { index := contractTransaction[contractID] - result[index.txnID][index.transactionOrder].FileContract.FileContract.ValidProofOutputs = output.valid - result[index.txnID][index.transactionOrder].FileContract.FileContract.MissedProofOutputs = output.missed + result[index.txnID][index.transactionOrder].EnhancedFileContract.FileContract.ValidProofOutputs = output.valid + result[index.txnID][index.transactionOrder].EnhancedFileContract.FileContract.MissedProofOutputs = output.missed } return result, nil