Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add v2 file contract revisions #133

Merged
merged 7 commits into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions explorer/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,14 @@ type V2FileContract struct {
types.V2FileContractElement
}

// A V2Transaction is a v2 transaction that uses the wrapped types above.
// A V2FileContractRevision is a V2 file contract revision with the
// explorer V2FileContract type.
type V2FileContractRevision struct {
Parent V2FileContract `json:"parent"`
Revision V2FileContract `json:"revision"`
}

// A V2Transaction is a V2 transaction that uses the wrapped types above.
type V2Transaction struct {
ID types.TransactionID `json:"id"`

Expand All @@ -190,7 +197,8 @@ type V2Transaction struct {
SiafundInputs []types.V2SiafundInput `json:"siafundInputs,omitempty"`
SiafundOutputs []SiafundOutput `json:"siafundOutputs,omitempty"`

FileContracts []V2FileContract `json:"fileContracts,omitempty"`
FileContracts []V2FileContract `json:"fileContracts,omitempty"`
FileContractRevisions []V2FileContractRevision `json:"fileContractRevisions,omitempty"`

Attestations []types.Attestation `json:"attestations,omitempty"`
ArbitraryData []byte `json:"arbitraryData,omitempty"`
Expand Down
11 changes: 11 additions & 0 deletions internal/testutil/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,17 @@ func CheckV2Transaction(t *testing.T, expectTxn types.V2Transaction, gotTxn expl
CheckV2FC(t, expected, got)
}

Equal(t, "file contract revision", len(expectTxn.FileContractRevisions), len(gotTxn.FileContractRevisions))
for i := range expectTxn.FileContractRevisions {
expected := expectTxn.FileContractRevisions[i]
got := gotTxn.FileContractRevisions[i]

Equal(t, "parent ID", expected.Parent.ID, got.Parent.ID)
Equal(t, "revision ID", expected.Parent.ID, got.Revision.ID)
CheckV2FC(t, expected.Parent.V2FileContract, got.Parent)
CheckV2FC(t, expected.Revision, got.Revision)
}

Equal(t, "attestations", len(expectTxn.Attestations), len(gotTxn.Attestations))
for i := range expectTxn.Attestations {
expected := expectTxn.Attestations[i]
Expand Down
8 changes: 4 additions & 4 deletions persist/sqlite/addresses.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ WHERE ev.event_id = ?`, eventID).Scan(decode(&m.SiacoinOutput.StateElement.ID),
var m explorer.EventMinerPayout
err = tx.QueryRow(`SELECT sc.output_id, sc.leaf_index, sc.maturity_height, sc.address, sc.value
FROM siacoin_elements sc
INNER JOIN miner_payout_events ev ON (ev.output_id = sc.id)
INNER JOIN miner_payout_events ev ON ev.output_id = sc.id
WHERE ev.event_id = ?`, eventID).Scan(decode(&m.SiacoinOutput.StateElement.ID), decode(&m.SiacoinOutput.StateElement.LeafIndex), decode(&m.SiacoinOutput.MaturityHeight), decode(&m.SiacoinOutput.SiacoinOutput.Address), decode(&m.SiacoinOutput.SiacoinOutput.Value))
if err != nil {
return explorer.Event{}, 0, fmt.Errorf("failed to fetch miner payout event data: %w", err)
Expand All @@ -85,7 +85,7 @@ WHERE ev.event_id = ?`, eventID).Scan(decode(&m.SiacoinOutput.StateElement.ID),
var m explorer.EventFoundationSubsidy
err = tx.QueryRow(`SELECT sc.output_id, sc.leaf_index, sc.maturity_height, sc.address, sc.value
FROM siacoin_elements sc
INNER JOIN foundation_subsidy_events ev ON (ev.output_id = sc.id)
INNER JOIN foundation_subsidy_events ev ON ev.output_id = sc.id
WHERE ev.event_id = ?`, eventID).Scan(decode(&m.SiacoinOutput.StateElement.ID), decode(&m.SiacoinOutput.StateElement.LeafIndex), decode(&m.SiacoinOutput.MaturityHeight), decode(&m.SiacoinOutput.SiacoinOutput.Address), decode(&m.SiacoinOutput.SiacoinOutput.Value))
ev.Data = &m
default:
Expand Down Expand Up @@ -153,8 +153,8 @@ func (s *Store) AddressEvents(address types.Address, offset, limit uint64) (even
err = s.transaction(func(tx *txn) error {
const query = `SELECT ev.id, ev.event_id, ev.maturity_height, ev.date_created, ev.height, ev.block_id, 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 event_addresses ea ON ev.id = ea.event_id
INNER JOIN address_balance sa ON ea.address_id = sa.id
WHERE sa.address = $1
ORDER BY ev.maturity_height DESC, ev.id DESC
LIMIT $2 OFFSET $3`
Expand Down
4 changes: 4 additions & 0 deletions persist/sqlite/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -928,6 +928,8 @@ func updateFileContractElements(tx *txn, revert bool, b types.Block, fces []expl
}

for _, txn := range b.Transactions {
// add in any contracts that are not the latest, i.e. contracts that
// were created and revised in the same block
for j, fc := range txn.FileContracts {
fcID := txn.FileContractID(j)
dbFC := explorer.DBFileContract{ID: txn.FileContractID(j), RevisionNumber: fc.RevisionNumber}
Expand All @@ -939,6 +941,8 @@ func updateFileContractElements(tx *txn, revert bool, b types.Block, fces []expl
return nil, fmt.Errorf("updateFileContractElements: %w", err)
}
}
// add in any revisions that are not the latest, i.e. contracts that
// were revised multiple times in one block
for _, fcr := range txn.FileContractRevisions {
fc := fcr.FileContract
dbFC := explorer.DBFileContract{ID: fcr.ParentID, RevisionNumber: fc.RevisionNumber}
Expand Down
6 changes: 3 additions & 3 deletions persist/sqlite/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func (s *Store) Contracts(ids []types.FileContractID) (result []explorer.FileCon
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
INNER JOIN last_contract_revision rev ON (rev.contract_element_id = fc1.id)
INNER JOIN last_contract_revision rev ON rev.contract_element_id = fc1.id
WHERE rev.contract_id IN (` + queryPlaceHolders(len(ids)) + `)`
rows, err := tx.Query(query, encodedIDs(ids)...)
if err != nil {
Expand Down Expand Up @@ -86,7 +86,7 @@ func (s *Store) ContractRevisions(id types.FileContractID) (revisions []explorer
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
JOIN last_contract_revision rev ON (rev.contract_id = fc.contract_id)
JOIN last_contract_revision rev ON rev.contract_id = fc.contract_id
WHERE fc.contract_id = ?
ORDER BY fc.revision_number ASC`
rows, err := tx.Query(query, encode(id))
Expand Down Expand Up @@ -144,7 +144,7 @@ func (s *Store) ContractsKey(key types.PublicKey) (result []explorer.FileContrac
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
INNER JOIN last_contract_revision rev ON (rev.contract_element_id = fc1.id)
INNER JOIN last_contract_revision rev ON rev.contract_element_id = fc1.id
WHERE rev.ed25519_renter_key = ? OR rev.ed25519_host_key = ?`
rows, err := tx.Query(query, encode(key), encode(key))
if err != nil {
Expand Down
11 changes: 10 additions & 1 deletion persist/sqlite/init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,16 @@ CREATE TABLE v2_transaction_file_contracts (
contract_id INTEGER REFERENCES v2_file_contract_elements(id) ON DELETE CASCADE NOT NULL, -- add an index to all foreign keys
UNIQUE(transaction_id, transaction_order)
);
CREATE INDEX v2_transaction_file_contracts_transaction_id_index ON v2_transaction_file_contracts(transaction_id);
CREATE INDEX v2_transaction_file_contracts_transaction_id_index ON v2_transaction_file_contracts(transaction_id);

CREATE TABLE v2_transaction_file_contract_revisions (
transaction_id INTEGER REFERENCES v2_transactions(id) ON DELETE CASCADE NOT NULL,
transaction_order INTEGER NOT NULL,
parent_contract_id INTEGER REFERENCES v2_file_contract_elements(id) ON DELETE CASCADE NOT NULL, -- add an index to all foreign keys
revision_contract_id INTEGER REFERENCES v2_file_contract_elements(id) ON DELETE CASCADE NOT NULL, -- add an index to all foreign keys
UNIQUE(transaction_id, transaction_order)
);
CREATE INDEX v2_transaction_file_contract_revisions_transaction_id_index ON v2_transaction_file_contract_revisions(transaction_id);

CREATE TABLE v2_transaction_attestations (
transaction_id INTEGER REFERENCES v2_transactions(id) ON DELETE CASCADE NOT NULL,
Expand Down
24 changes: 12 additions & 12 deletions persist/sqlite/transactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import (
func (s *Store) TransactionChainIndices(txnID types.TransactionID, offset, limit uint64) (indices []types.ChainIndex, err error) {
err = s.transaction(func(tx *txn) error {
rows, err := tx.Query(`SELECT DISTINCT b.id, b.height FROM blocks b
INNER JOIN block_transactions bt ON (bt.block_id = b.id)
INNER JOIN transactions t ON (t.id = bt.transaction_id)
INNER JOIN block_transactions bt ON bt.block_id = b.id
INNER JOIN transactions t ON t.id = bt.transaction_id
WHERE t.transaction_id = ?
ORDER BY b.height DESC
LIMIT ? OFFSET ?`, encode(txnID), limit, offset)
Expand Down Expand Up @@ -112,7 +112,7 @@ ORDER BY transaction_order ASC`
func transactionSiacoinOutputs(tx *txn, txnIDs []int64) (map[int64][]explorer.SiacoinOutput, error) {
query := `SELECT ts.transaction_id, sc.output_id, sc.leaf_index, sc.spent_index, sc.source, sc.maturity_height, sc.address, sc.value
FROM siacoin_elements sc
INNER JOIN transaction_siacoin_outputs ts ON (ts.output_id = sc.id)
INNER JOIN transaction_siacoin_outputs ts ON ts.output_id = sc.id
WHERE ts.transaction_id IN (` + queryPlaceHolders(len(txnIDs)) + `)
ORDER BY ts.transaction_order ASC`
rows, err := tx.Query(query, queryArgs(txnIDs)...)
Expand Down Expand Up @@ -142,7 +142,7 @@ ORDER BY ts.transaction_order ASC`
func transactionSiacoinInputs(tx *txn, txnIDs []int64) (map[int64][]explorer.SiacoinInput, error) {
query := `SELECT sc.id, ts.transaction_id, sc.output_id, ts.unlock_conditions, sc.value
FROM siacoin_elements sc
INNER JOIN transaction_siacoin_inputs ts ON (ts.parent_id = sc.id)
INNER JOIN transaction_siacoin_inputs ts ON ts.parent_id = sc.id
WHERE ts.transaction_id IN (` + queryPlaceHolders(len(txnIDs)) + `)
ORDER BY ts.transaction_order ASC`
rows, err := tx.Query(query, queryArgs(txnIDs)...)
Expand All @@ -168,7 +168,7 @@ ORDER BY ts.transaction_order ASC`
func transactionSiafundInputs(tx *txn, txnIDs []int64) (map[int64][]explorer.SiafundInput, error) {
query := `SELECT ts.transaction_id, sf.output_id, ts.unlock_conditions, ts.claim_address, sf.value
FROM siafund_elements sf
INNER JOIN transaction_siafund_inputs ts ON (ts.parent_id = sf.id)
INNER JOIN transaction_siafund_inputs ts ON ts.parent_id = sf.id
WHERE ts.transaction_id IN (` + queryPlaceHolders(len(txnIDs)) + `)
ORDER BY ts.transaction_order ASC`
rows, err := tx.Query(query, queryArgs(txnIDs)...)
Expand All @@ -195,7 +195,7 @@ ORDER BY ts.transaction_order ASC`
func transactionSiafundOutputs(tx *txn, txnIDs []int64) (map[int64][]explorer.SiafundOutput, error) {
query := `SELECT ts.transaction_id, sf.output_id, sf.leaf_index, sf.spent_index, sf.claim_start, sf.address, sf.value
FROM siafund_elements sf
INNER JOIN transaction_siafund_outputs ts ON (ts.output_id = sf.id)
INNER JOIN transaction_siafund_outputs ts ON ts.output_id = sf.id
WHERE ts.transaction_id IN (` + queryPlaceHolders(len(txnIDs)) + `)
ORDER BY ts.transaction_order ASC`
rows, err := tx.Query(query, queryArgs(txnIDs)...)
Expand Down Expand Up @@ -286,8 +286,8 @@ type contractOrder struct {
func transactionFileContracts(tx *txn, txnIDs []int64) (map[int64][]explorer.FileContract, 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)
INNER JOIN last_contract_revision rev ON (rev.contract_id = fc.contract_id)
INNER JOIN transaction_file_contracts ts ON ts.contract_id = fc.id
INNER JOIN last_contract_revision rev ON rev.contract_id = fc.contract_id
WHERE ts.transaction_id IN (` + queryPlaceHolders(len(txnIDs)) + `)
ORDER BY ts.transaction_order ASC`
rows, err := tx.Query(query, queryArgs(txnIDs)...)
Expand Down Expand Up @@ -347,8 +347,8 @@ ORDER BY ts.transaction_order ASC`
func transactionFileContractRevisions(tx *txn, txnIDs []int64) (map[int64][]explorer.FileContractRevision, error) {
query := `SELECT ts.transaction_id, fc.id, rev.confirmation_index, rev.confirmation_transaction_id, rev.proof_index, rev.proof_transaction_id, ts.parent_id, ts.unlock_conditions, 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_contract_revisions ts ON (ts.contract_id = fc.id)
INNER JOIN last_contract_revision rev ON (rev.contract_id = fc.contract_id)
INNER JOIN transaction_file_contract_revisions ts ON ts.contract_id = fc.id
INNER JOIN last_contract_revision rev ON rev.contract_id = fc.contract_id
WHERE ts.transaction_id IN (` + queryPlaceHolders(len(txnIDs)) + `)
ORDER BY ts.transaction_order ASC`
rows, err := tx.Query(query, queryArgs(txnIDs)...)
Expand Down Expand Up @@ -442,7 +442,7 @@ type transactionID struct {
func blockTransactionIDs(tx *txn, blockID types.BlockID) (idMap map[int64]transactionID, err error) {
rows, err := tx.Query(`SELECT bt.transaction_id, block_order, t.transaction_id
FROM block_transactions bt
INNER JOIN transactions t ON (t.id = bt.transaction_id)
INNER JOIN transactions t ON t.id = bt.transaction_id
WHERE block_id = ? ORDER BY block_order ASC`, encode(blockID))
if err != nil {
return nil, err
Expand All @@ -466,7 +466,7 @@ WHERE block_id = ? ORDER BY block_order ASC`, encode(blockID))
func blockMinerPayouts(tx *txn, blockID types.BlockID) ([]explorer.SiacoinOutput, error) {
query := `SELECT sc.output_id, sc.leaf_index, sc.spent_index, sc.source, sc.maturity_height, sc.address, sc.value
FROM siacoin_elements sc
INNER JOIN miner_payouts mp ON (mp.output_id = sc.id)
INNER JOIN miner_payouts mp ON mp.output_id = sc.id
WHERE mp.block_id = ?
ORDER BY mp.block_order ASC`
rows, err := tx.Query(query, encode(blockID))
Expand Down
61 changes: 58 additions & 3 deletions persist/sqlite/v2consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,13 @@ func updateV2FileContractElements(tx *txn, revert bool, b types.Block, fces []ex
}
defer revisionStmt.Close()

// so we can get the ids of revision parents to add to the DB
parentStmt, err := tx.Prepare(`SELECT id FROM v2_file_contract_elements WHERE contract_id = ? AND revision_number = ?`)
chris124567 marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return nil, fmt.Errorf("updateV2FileContractElements: failed to prepare parent statement: %w", err)
}
defer parentStmt.Close()

fcTxns := make(map[explorer.DBFileContract]types.TransactionID)
for _, txn := range b.V2Transactions() {
id := txn.ID()
Expand Down Expand Up @@ -164,6 +171,8 @@ func updateV2FileContractElements(tx *txn, revert bool, b types.Block, fces []ex
}

for _, txn := range b.V2Transactions() {
// add in any contracts that are not the latest, i.e. contracts that
// were created and revised in the same block
for j, fc := range txn.FileContracts {
fcID := txn.V2FileContractID(txn.ID(), j)
dbFC := explorer.DBFileContract{ID: fcID, RevisionNumber: fc.RevisionNumber}
Expand All @@ -175,18 +184,31 @@ func updateV2FileContractElements(tx *txn, revert bool, b types.Block, fces []ex
return nil, fmt.Errorf("updateFileContractElements: %w", err)
}
}
// add in any revisions that are not the latest, i.e. contracts that
// were revised multiple times in one block
for _, fcr := range txn.FileContractRevisions {
fc := fcr.Revision
fcid := types.FileContractID(fcr.Parent.ID)
dbFC := explorer.DBFileContract{ID: fcid, RevisionNumber: fc.RevisionNumber}
fcID := types.FileContractID(fcr.Parent.ID)
dbFC := explorer.DBFileContract{ID: fcID, RevisionNumber: fc.RevisionNumber}
if _, exists := fcDBIds[dbFC]; exists {
continue
}

if err := addFC(fcid, 0, fc, nil, false); err != nil {
if err := addFC(fcID, 0, fc, nil, false); err != nil {
return nil, fmt.Errorf("updateFileContractElements: %w", err)
}
}
// don't add anything, just set parent db IDs in fcDBIds map
for _, fcr := range txn.FileContractRevisions {
fcID := types.FileContractID(fcr.Parent.ID)
parentDBFC := explorer.DBFileContract{ID: fcID, RevisionNumber: fcr.Parent.V2FileContract.RevisionNumber}

var dbID int64
if err := parentStmt.QueryRow(encode(fcID), encode(parentDBFC.RevisionNumber)).Scan(&dbID); err != nil {
return nil, fmt.Errorf("updateFileContractElements: failed to get parent contract ID: %w", err)
}
fcDBIds[parentDBFC] = dbID
}
}

return fcDBIds, nil
Expand Down Expand Up @@ -342,6 +364,37 @@ func addV2FileContracts(tx *txn, txnID int64, txn types.V2Transaction, dbIDs map
return nil
}

func addV2FileContractRevisions(tx *txn, txnID int64, txn types.V2Transaction, dbIDs map[explorer.DBFileContract]int64) error {
stmt, err := tx.Prepare(`INSERT INTO v2_transaction_file_contract_revisions(transaction_id, transaction_order, parent_contract_id, revision_contract_id) VALUES (?, ?, ?, ?)`)
if err != nil {
return fmt.Errorf("addV2FileContractRevisions: failed to prepare statement: %w", err)
}
defer stmt.Close()

for i, fcr := range txn.FileContractRevisions {
parentDBID, ok := dbIDs[explorer.DBFileContract{
ID: types.FileContractID(fcr.Parent.ID),
RevisionNumber: fcr.Parent.V2FileContract.RevisionNumber,
}]
if !ok {
return errors.New("addV2FileContractRevisions: parent dbID not in map")
}

dbID, ok := dbIDs[explorer.DBFileContract{
ID: types.FileContractID(fcr.Parent.ID),
RevisionNumber: fcr.Revision.RevisionNumber,
}]
if !ok {
return errors.New("addV2FileContractRevisions: dbID not in map")
}

if _, err := stmt.Exec(txnID, i, parentDBID, dbID); err != nil {
return fmt.Errorf("addV2FileContractRevisions: failed to execute statement: %w", err)
}
}
return nil
}

func addV2Attestations(tx *txn, txnID int64, txn types.V2Transaction) error {
stmt, err := tx.Prepare(`INSERT INTO v2_transaction_attestations(transaction_id, transaction_order, public_key, key, value, signature) VALUES (?, ?, ?, ?, ?, ?)`)
if err != nil {
Expand Down Expand Up @@ -381,6 +434,8 @@ func addV2TransactionFields(tx *txn, txns []types.V2Transaction, scDBIds map[typ
return fmt.Errorf("failed to add siafund outputs: %w", err)
} else if err := addV2FileContracts(tx, dbID.id, txn, v2FcDBIds); err != nil {
return fmt.Errorf("failed to add file contracts: %w", err)
} else if err := addV2FileContractRevisions(tx, dbID.id, txn, v2FcDBIds); err != nil {
return fmt.Errorf("failed to add file contract revisions: %w", err)
}
}

Expand Down
Loading
Loading