From c7c1d282a126ac2a22c9ad14de163662235bddb9 Mon Sep 17 00:00:00 2001 From: Christopher Tarry Date: Wed, 23 Oct 2024 16:32:13 -0400 Subject: [PATCH 1/4] add v2 siacoin/siafund inputs, use `RETURNING ID` in queries inserting into siacoin_elements and siafund_elements to get correct id in cases where we update instead of insert --- explorer/types.go | 16 ++++--- internal/testutil/check.go | 26 +++++++++++ persist/sqlite/consensus.go | 48 +++++++------------ persist/sqlite/init.sql | 19 ++++++++ persist/sqlite/v2consensus.go | 44 ++++++++++++++++++ persist/sqlite/v2transactions.go | 80 ++++++++++++++++++++++++++++++++ 6 files changed, 195 insertions(+), 38 deletions(-) diff --git a/explorer/types.go b/explorer/types.go index 399dda2a..ce62182a 100644 --- a/explorer/types.go +++ b/explorer/types.go @@ -151,7 +151,8 @@ type FileContractRevision struct { // A Transaction is a transaction that uses the wrapped types above. type Transaction struct { - ID types.TransactionID `json:"id"` + ID types.TransactionID `json:"id"` + SiacoinInputs []SiacoinInput `json:"siacoinInputs,omitempty"` SiacoinOutputs []SiacoinOutput `json:"siacoinOutputs,omitempty"` SiafundInputs []SiafundInput `json:"siafundInputs,omitempty"` @@ -168,11 +169,14 @@ type Transaction struct { // A V2Transaction is a v2 transaction that uses the wrapped types above. type V2Transaction struct { - ID types.TransactionID `json:"id"` - SiacoinOutputs []SiacoinOutput `json:"siacoinOutputs,omitempty"` - SiafundOutputs []SiafundOutput `json:"siafundOutputs,omitempty"` - Attestations []types.Attestation `json:"attestations,omitempty"` - ArbitraryData []byte `json:"arbitraryData,omitempty"` + ID types.TransactionID `json:"id"` + + SiacoinInputs []types.V2SiacoinInput `json:"siacoinInputs,omitempty"` + SiacoinOutputs []SiacoinOutput `json:"siacoinOutputs,omitempty"` + SiafundInputs []types.V2SiafundInput `json:"siacoinInputs,omitempty"` + SiafundOutputs []SiafundOutput `json:"siafundOutputs,omitempty"` + Attestations []types.Attestation `json:"attestations,omitempty"` + ArbitraryData []byte `json:"arbitraryData,omitempty"` NewFoundationAddress *types.Address `json:"newFoundationAddress,omitempty"` MinerFee types.Currency `json:"minerFee"` diff --git a/internal/testutil/check.go b/internal/testutil/check.go index 0a0b4fa8..c1556e87 100644 --- a/internal/testutil/check.go +++ b/internal/testutil/check.go @@ -117,6 +117,19 @@ func CheckV2Transaction(t *testing.T, expectTxn types.V2Transaction, gotTxn expl Equal(t, "new foundation address", expectTxn.NewFoundationAddress, gotTxn.NewFoundationAddress) Equal(t, "miner fee", expectTxn.MinerFee, gotTxn.MinerFee) + Equal(t, "siacoin inputs", len(expectTxn.SiacoinInputs), len(gotTxn.SiacoinInputs)) + for i := range expectTxn.SiacoinInputs { + expected := expectTxn.SiacoinInputs[i] + got := gotTxn.SiacoinInputs[i] + + // Equal(t, "address", expected.Parent.SiacoinOutput.Address, got.Parent.SiacoinOutput.Address) + Equal(t, "value", expected.Parent.SiacoinOutput.Value, got.Parent.SiacoinOutput.Value) + Equal(t, "maturity height", expected.Parent.MaturityHeight, got.Parent.MaturityHeight) + Equal(t, "id", expected.Parent.ID, got.Parent.ID) + Equal(t, "leaf index", expected.Parent.LeafIndex, got.Parent.LeafIndex) + Equal(t, "satisfied policy", expected.SatisfiedPolicy, got.SatisfiedPolicy) + } + Equal(t, "siacoin outputs", len(expectTxn.SiacoinOutputs), len(gotTxn.SiacoinOutputs)) for i := range expectTxn.SiacoinOutputs { expected := expectTxn.SiacoinOutputs[i] @@ -126,6 +139,19 @@ func CheckV2Transaction(t *testing.T, expectTxn types.V2Transaction, gotTxn expl Equal(t, "value", expected.Value, got.Value) } + Equal(t, "siafund inputs", len(expectTxn.SiafundInputs), len(gotTxn.SiafundInputs)) + for i := range expectTxn.SiafundInputs { + expected := expectTxn.SiafundInputs[i] + got := gotTxn.SiafundInputs[i] + + Equal(t, "address", expected.Parent.SiafundOutput.Address, got.Parent.SiafundOutput.Address) + Equal(t, "value", expected.Parent.SiafundOutput.Value, got.Parent.SiafundOutput.Value) + Equal(t, "claim address", expected.ClaimAddress, got.ClaimAddress) + Equal(t, "id", expected.Parent.ID, got.Parent.ID) + Equal(t, "leaf index", expected.Parent.LeafIndex, got.Parent.LeafIndex) + Equal(t, "satisfied policy", expected.SatisfiedPolicy, got.SatisfiedPolicy) + } + Equal(t, "siafund outputs", len(expectTxn.SiafundOutputs), len(gotTxn.SiafundOutputs)) for i := range expectTxn.SiafundOutputs { expected := expectTxn.SiafundOutputs[i] diff --git a/persist/sqlite/consensus.go b/persist/sqlite/consensus.go index a66d3f08..6b528d88 100644 --- a/persist/sqlite/consensus.go +++ b/persist/sqlite/consensus.go @@ -500,23 +500,19 @@ func addSiacoinElements(tx *txn, index types.ChainIndex, spentElements, newEleme stmt, err := tx.Prepare(`INSERT INTO siacoin_elements(output_id, block_id, leaf_index, source, maturity_height, address, value) VALUES (?, ?, ?, ?, ?, ?, ?) ON CONFLICT (output_id) - DO UPDATE SET leaf_index = ?, spent_index = NULL`) + DO UPDATE SET leaf_index = ?, spent_index = NULL + RETURNING id;`) if err != nil { return nil, fmt.Errorf("addSiacoinElements: failed to prepare siacoin_elements statement: %w", err) } defer stmt.Close() for _, sce := range newElements { - result, err := stmt.Exec(encode(sce.StateElement.ID), encode(index.ID), encode(sce.StateElement.LeafIndex), int(sce.Source), sce.MaturityHeight, encode(sce.SiacoinOutput.Address), encode(sce.SiacoinOutput.Value), encode(sce.StateElement.LeafIndex)) - if err != nil { + var dbID int64 + if err := stmt.QueryRow(encode(sce.StateElement.ID), encode(index.ID), encode(sce.StateElement.LeafIndex), int(sce.Source), sce.MaturityHeight, encode(sce.SiacoinOutput.Address), encode(sce.SiacoinOutput.Value), encode(sce.StateElement.LeafIndex)).Scan(&dbID); err != nil { return nil, fmt.Errorf("addSiacoinElements: failed to execute siacoin_elements statement: %w", err) } - dbID, err := result.LastInsertId() - if err != nil { - return nil, fmt.Errorf("addSiacoinElements: failed to get last insert ID: %w", err) - } - scDBIds[types.SiacoinOutputID(sce.StateElement.ID)] = dbID } } @@ -524,23 +520,19 @@ func addSiacoinElements(tx *txn, index types.ChainIndex, spentElements, newEleme stmt, err := tx.Prepare(`INSERT INTO siacoin_elements(output_id, block_id, leaf_index, spent_index, source, maturity_height, address, value) VALUES (?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT (output_id) - DO UPDATE SET spent_index = ?, leaf_index = ?`) + DO UPDATE SET spent_index = ?, leaf_index = ? + RETURNING id;`) if err != nil { return nil, fmt.Errorf("addSiacoinElements: failed to prepare siacoin_elements statement: %w", err) } defer stmt.Close() for _, sce := range spentElements { - result, err := stmt.Exec(encode(sce.StateElement.ID), encode(index.ID), encode(sce.StateElement.LeafIndex), encode(index), int(sce.Source), sce.MaturityHeight, encode(sce.SiacoinOutput.Address), encode(sce.SiacoinOutput.Value), encode(index), encode(sce.StateElement.LeafIndex)) - if err != nil { + var dbID int64 + if err := stmt.QueryRow(encode(sce.StateElement.ID), encode(index.ID), encode(sce.StateElement.LeafIndex), encode(index), int(sce.Source), sce.MaturityHeight, encode(sce.SiacoinOutput.Address), encode(sce.SiacoinOutput.Value), encode(index), encode(sce.StateElement.LeafIndex)).Scan(&dbID); err != nil { return nil, fmt.Errorf("addSiacoinElements: failed to execute siacoin_elements statement: %w", err) } - dbID, err := result.LastInsertId() - if err != nil { - return nil, fmt.Errorf("addSiacoinElements: failed to get last insert ID: %w", err) - } - scDBIds[types.SiacoinOutputID(sce.StateElement.ID)] = dbID } } @@ -554,23 +546,19 @@ func addSiafundElements(tx *txn, index types.ChainIndex, spentElements, newEleme stmt, err := tx.Prepare(`INSERT INTO siafund_elements(output_id, block_id, leaf_index, claim_start, address, value) VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT - DO UPDATE SET leaf_index = ?, spent_index = NULL`) + DO UPDATE SET leaf_index = ?, spent_index = NULL + RETURNING id;`) if err != nil { return nil, fmt.Errorf("addSiafundElements: failed to prepare siafund_elements statement: %w", err) } defer stmt.Close() for _, sfe := range newElements { - result, err := stmt.Exec(encode(sfe.StateElement.ID), encode(index.ID), encode(sfe.StateElement.LeafIndex), encode(sfe.ClaimStart), encode(sfe.SiafundOutput.Address), encode(sfe.SiafundOutput.Value), encode(sfe.StateElement.LeafIndex)) - if err != nil { + var dbID int64 + if err := stmt.QueryRow(encode(sfe.StateElement.ID), encode(index.ID), encode(sfe.StateElement.LeafIndex), encode(sfe.ClaimStart), encode(sfe.SiafundOutput.Address), encode(sfe.SiafundOutput.Value), encode(sfe.StateElement.LeafIndex)).Scan(&dbID); err != nil { return nil, fmt.Errorf("addSiafundElements: failed to execute siafund_elements statement: %w", err) } - dbID, err := result.LastInsertId() - if err != nil { - return nil, fmt.Errorf("addSiafundElements: failed to get last insert ID: %w", err) - } - sfDBIds[types.SiafundOutputID(sfe.StateElement.ID)] = dbID } } @@ -578,23 +566,19 @@ func addSiafundElements(tx *txn, index types.ChainIndex, spentElements, newEleme stmt, err := tx.Prepare(`INSERT INTO siafund_elements(output_id, block_id, leaf_index, spent_index, claim_start, address, value) VALUES (?, ?, ?, ?, ?, ?, ?) ON CONFLICT - DO UPDATE SET leaf_index = ?, spent_index = ?`) + DO UPDATE SET leaf_index = ?, spent_index = ? + RETURNING id;`) if err != nil { return nil, fmt.Errorf("addSiafundElements: failed to prepare siafund_elements statement: %w", err) } defer stmt.Close() for _, sfe := range spentElements { - result, err := stmt.Exec(encode(sfe.StateElement.ID), encode(index.ID), encode(sfe.StateElement.LeafIndex), encode(index), encode(sfe.ClaimStart), encode(sfe.SiafundOutput.Address), encode(sfe.SiafundOutput.Value), encode(sfe.StateElement.LeafIndex), encode(index)) - if err != nil { + var dbID int64 + if err := stmt.QueryRow(encode(sfe.StateElement.ID), encode(index.ID), encode(sfe.StateElement.LeafIndex), encode(index), encode(sfe.ClaimStart), encode(sfe.SiafundOutput.Address), encode(sfe.SiafundOutput.Value), encode(sfe.StateElement.LeafIndex), encode(index)).Scan(&dbID); err != nil { return nil, fmt.Errorf("addSiafundElements: failed to execute siafund_elements statement: %w", err) } - dbID, err := result.LastInsertId() - if err != nil { - return nil, fmt.Errorf("addSiafundElements: failed to get last insert ID: %w", err) - } - sfDBIds[types.SiafundOutputID(sfe.StateElement.ID)] = dbID } } diff --git a/persist/sqlite/init.sql b/persist/sqlite/init.sql index b69aed20..463a20c3 100644 --- a/persist/sqlite/init.sql +++ b/persist/sqlite/init.sql @@ -273,6 +273,15 @@ CREATE TABLE v2_block_transactions ( CREATE INDEX v2_block_transactions_block_id_index ON v2_block_transactions(block_id); CREATE INDEX v2_block_transactions_transaction_id_block_id ON v2_block_transactions(transaction_id, block_id); +CREATE TABLE v2_transaction_siacoin_inputs ( + transaction_id INTEGER REFERENCES v2_transactions(id) ON DELETE CASCADE NOT NULL, + transaction_order INTEGER NOT NULL, + parent_id INTEGER REFERENCES siacoin_elements(id) ON DELETE CASCADE NOT NULL, + satisfied_policy BLOB NOT NULL, + UNIQUE(transaction_id, transaction_order) +); +CREATE INDEX v2_transaction_siacoin_inputs_transaction_id_index ON v2_transaction_siacoin_inputs(transaction_id); + CREATE TABLE v2_transaction_siacoin_outputs ( transaction_id INTEGER REFERENCES v2_transactions(id) ON DELETE CASCADE NOT NULL, transaction_order INTEGER NOT NULL, @@ -281,6 +290,16 @@ CREATE TABLE v2_transaction_siacoin_outputs ( ); CREATE INDEX v2_transaction_siacoin_outputs_transaction_id_index ON v2_transaction_siacoin_outputs(transaction_id); +CREATE TABLE v2_transaction_siafund_inputs ( + transaction_id INTEGER REFERENCES v2_transactions(id) ON DELETE CASCADE NOT NULL, + transaction_order INTEGER NOT NULL, + parent_id INTEGER REFERENCES siafund_elements(id) ON DELETE CASCADE NOT NULL, + claim_address BLOB NOT NULL, + satisfied_policy BLOB NOT NULL, + UNIQUE(transaction_id, transaction_order) +); +CREATE INDEX v2_transaction_siafund_inputs_transaction_id_index ON v2_transaction_siafund_inputs(transaction_id); + CREATE TABLE v2_transaction_siafund_outputs ( transaction_id INTEGER REFERENCES v2_transactions(id) ON DELETE CASCADE NOT NULL, transaction_order INTEGER NOT NULL, diff --git a/persist/sqlite/v2consensus.go b/persist/sqlite/v2consensus.go index 89fe5a89..9f9d6efb 100644 --- a/persist/sqlite/v2consensus.go +++ b/persist/sqlite/v2consensus.go @@ -62,6 +62,26 @@ func addV2Transactions(tx *txn, bid types.BlockID, txns []types.V2Transaction) ( return txnDBIds, nil } +func addV2SiacoinInputs(tx *txn, txnID int64, txn types.V2Transaction, dbIDs map[types.SiacoinOutputID]int64) error { + stmt, err := tx.Prepare(`INSERT INTO v2_transaction_siacoin_inputs(transaction_id, transaction_order, parent_id, satisfied_policy) VALUES (?, ?, ?, ?)`) + if err != nil { + return fmt.Errorf("addV2SiacoinInputs: failed to prepare statement: %w", err) + } + defer stmt.Close() + + for i, sci := range txn.SiacoinInputs { + dbID, ok := dbIDs[types.SiacoinOutputID(sci.Parent.ID)] + if !ok { + return errors.New("addV2SiacoinInputs: dbID not in map") + } + + if _, err := stmt.Exec(txnID, i, dbID, encode(sci.SatisfiedPolicy)); err != nil { + return fmt.Errorf("addV2SiacoinInputs: failed to execute statement: %w", err) + } + } + return nil +} + func addV2SiacoinOutputs(tx *txn, txnID int64, txn types.V2Transaction, dbIDs map[types.SiacoinOutputID]int64) error { stmt, err := tx.Prepare(`INSERT INTO v2_transaction_siacoin_outputs(transaction_id, transaction_order, output_id) VALUES (?, ?, ?)`) if err != nil { @@ -83,6 +103,26 @@ func addV2SiacoinOutputs(tx *txn, txnID int64, txn types.V2Transaction, dbIDs ma return nil } +func addV2SiafundInputs(tx *txn, txnID int64, txn types.V2Transaction, dbIDs map[types.SiafundOutputID]int64) error { + stmt, err := tx.Prepare(`INSERT INTO v2_transaction_siafund_inputs(transaction_id, transaction_order, parent_id, claim_address, satisfied_policy) VALUES (?, ?, ?, ?, ?)`) + if err != nil { + return fmt.Errorf("addV2SiafundInputs: failed to prepare statement: %w", err) + } + defer stmt.Close() + + for i, sfi := range txn.SiafundInputs { + dbID, ok := dbIDs[types.SiafundOutputID(sfi.Parent.ID)] + if !ok { + return errors.New("addV2SiafundInputs: dbID not in map") + } + + if _, err := stmt.Exec(txnID, i, dbID, encode(sfi.ClaimAddress), encode(sfi.SatisfiedPolicy)); err != nil { + return fmt.Errorf("addV2SiafundInputs: failed to execute statement: %w", err) + } + } + return nil +} + func addV2SiafundOutputs(tx *txn, txnID int64, txn types.V2Transaction, dbIDs map[types.SiafundOutputID]int64) error { stmt, err := tx.Prepare(`INSERT INTO v2_transaction_siafund_outputs(transaction_id, transaction_order, output_id) VALUES (?, ?, ?)`) if err != nil { @@ -133,8 +173,12 @@ func addV2TransactionFields(tx *txn, txns []types.V2Transaction, scDBIds map[typ if err := addV2Attestations(tx, dbID.id, txn); err != nil { return fmt.Errorf("addV2TransactionFields: failed to add attestations: %w", err) + } else if err := addV2SiacoinInputs(tx, dbID.id, txn, scDBIds); err != nil { + return fmt.Errorf("failed to add siacoin inputs: %w", err) } else if err := addV2SiacoinOutputs(tx, dbID.id, txn, scDBIds); err != nil { return fmt.Errorf("failed to add siacoin outputs: %w", err) + } else if err := addV2SiafundInputs(tx, dbID.id, txn, sfDBIds); err != nil { + return fmt.Errorf("failed to add siafund inputs: %w", err) } else if err := addV2SiafundOutputs(tx, dbID.id, txn, sfDBIds); err != nil { return fmt.Errorf("failed to add siafund outputs: %w", err) } diff --git a/persist/sqlite/v2transactions.go b/persist/sqlite/v2transactions.go index 0b147675..935e0798 100644 --- a/persist/sqlite/v2transactions.go +++ b/persist/sqlite/v2transactions.go @@ -66,8 +66,12 @@ func getV2Transactions(tx *txn, ids []types.TransactionID) ([]explorer.V2Transac return nil, fmt.Errorf("getV2Transactions: failed to get base transactions: %w", err) } else if err := fillV2TransactionAttestations(tx, dbIDs, txns); err != nil { return nil, fmt.Errorf("getV2Transactions: failed to get attestations: %w", err) + } else if err := fillV2TransactionSiacoinInputs(tx, dbIDs, txns); err != nil { + return nil, fmt.Errorf("getV2Transactions: failed to get siacoin inputs: %w", err) } else if err := fillV2TransactionSiacoinOutputs(tx, dbIDs, txns); err != nil { return nil, fmt.Errorf("getV2Transactions: failed to get siacoin outputs: %w", err) + } else if err := fillV2TransactionSiafundInputs(tx, dbIDs, txns); err != nil { + return nil, fmt.Errorf("getV2Transactions: failed to get siafund inputs: %w", err) } else if err := fillV2TransactionSiafundOutputs(tx, dbIDs, txns); err != nil { return nil, fmt.Errorf("getV2Transactions: failed to get siafund outputs: %w", err) } @@ -145,6 +149,44 @@ func fillV2TransactionAttestations(tx *txn, dbIDs []int64, txns []explorer.V2Tra return nil } +// fillV2TransactionSiacoinInputs fills in the siacoin outputs for each +// transaction. +func fillV2TransactionSiacoinInputs(tx *txn, dbIDs []int64, txns []explorer.V2Transaction) error { + stmt, err := tx.Prepare(`SELECT ts.satisfied_policy, sc.output_id, sc.leaf_index, sc.maturity_height, sc.address, sc.value +FROM siacoin_elements sc +INNER JOIN v2_transaction_siacoin_inputs ts ON (ts.parent_id = sc.id) +WHERE ts.transaction_id = ? +ORDER BY ts.transaction_order ASC`) + if err != nil { + return fmt.Errorf("failed to prepare siacoin inputs statement: %w", err) + } + defer stmt.Close() + + for i, dbID := range dbIDs { + err := func() error { + rows, err := stmt.Query(dbID) + if err != nil { + return fmt.Errorf("failed to query siacoin inputs: %w", err) + } + defer rows.Close() + + for rows.Next() { + var sci types.V2SiacoinInput + if err := rows.Scan(decode(&sci.SatisfiedPolicy), decode(&sci.Parent.ID), decode(&sci.Parent.LeafIndex), &sci.Parent.MaturityHeight, decode(&sci.Parent.SiacoinOutput.Address), decode(&sci.Parent.SiacoinOutput.Value)); err != nil { + return fmt.Errorf("failed to scan siacoin inputs: %w", err) + } + + txns[i].SiacoinInputs = append(txns[i].SiacoinInputs, sci) + } + return nil + }() + if err != nil { + return err + } + } + return nil +} + // fillV2TransactionSiacoinOutputs fills in the siacoin outputs for each // transaction. func fillV2TransactionSiacoinOutputs(tx *txn, dbIDs []int64, txns []explorer.V2Transaction) error { @@ -187,6 +229,44 @@ ORDER BY ts.transaction_order ASC`) return nil } +// fillV2TransactionSiafundInputs fills in the siacoin outputs for each +// transaction. +func fillV2TransactionSiafundInputs(tx *txn, dbIDs []int64, txns []explorer.V2Transaction) error { + stmt, err := tx.Prepare(`SELECT ts.satisfied_policy, ts.claim_address, sf.output_id, sf.leaf_index, sf.address, sf.value +FROM siafund_elements sf +INNER JOIN v2_transaction_siafund_inputs ts ON (ts.parent_id = sf.id) +WHERE ts.transaction_id = ? +ORDER BY ts.transaction_order ASC`) + if err != nil { + return fmt.Errorf("failed to prepare siacoin inputs statement: %w", err) + } + defer stmt.Close() + + for i, dbID := range dbIDs { + err := func() error { + rows, err := stmt.Query(dbID) + if err != nil { + return fmt.Errorf("failed to query siacoin inputs: %w", err) + } + defer rows.Close() + + for rows.Next() { + var sfi types.V2SiafundInput + if err := rows.Scan(decode(&sfi.SatisfiedPolicy), decode(&sfi.ClaimAddress), decode(&sfi.Parent.ID), decode(&sfi.Parent.LeafIndex), decode(&sfi.Parent.SiafundOutput.Address), decode(&sfi.Parent.SiafundOutput.Value)); err != nil { + return fmt.Errorf("failed to scan siacoin inputs: %w", err) + } + + txns[i].SiafundInputs = append(txns[i].SiafundInputs, sfi) + } + return nil + }() + if err != nil { + return err + } + } + return nil +} + // fillV2TransactionSiafundOutputs fills in the siacoin outputs for each // transaction. func fillV2TransactionSiafundOutputs(tx *txn, dbIDs []int64, txns []explorer.V2Transaction) error { From 05d71bb00df56b46ded5be8b77fcf021f45ae5e7 Mon Sep 17 00:00:00 2001 From: Christopher Tarry Date: Wed, 23 Oct 2024 20:47:27 -0400 Subject: [PATCH 2/4] store integer reference to siacoin_elements and siafund_elements in inputs for v1 transactions too --- persist/sqlite/consensus.go | 25 ++++++++++++++++++------- persist/sqlite/init.sql | 4 ++-- persist/sqlite/transactions.go | 14 ++++++++------ 3 files changed, 28 insertions(+), 15 deletions(-) diff --git a/persist/sqlite/consensus.go b/persist/sqlite/consensus.go index 6b528d88..84283e18 100644 --- a/persist/sqlite/consensus.go +++ b/persist/sqlite/consensus.go @@ -94,7 +94,7 @@ func addSignatures(tx *txn, id int64, txn types.Transaction) error { return nil } -func addSiacoinInputs(tx *txn, id int64, txn types.Transaction) error { +func addSiacoinInputs(tx *txn, id int64, txn types.Transaction, dbIDs map[types.SiacoinOutputID]int64) error { stmt, err := tx.Prepare(`INSERT INTO transaction_siacoin_inputs(transaction_id, transaction_order, parent_id, unlock_conditions) VALUES (?, ?, ?, ?)`) if err != nil { return fmt.Errorf("addSiacoinInputs: failed to prepare statement: %w", err) @@ -102,7 +102,12 @@ func addSiacoinInputs(tx *txn, id int64, txn types.Transaction) error { defer stmt.Close() for i, sci := range txn.SiacoinInputs { - if _, err := stmt.Exec(id, i, encode(sci.ParentID), encode(sci.UnlockConditions)); err != nil { + dbID, ok := dbIDs[sci.ParentID] + if !ok { + return errors.New("addSiacoinOutputs: dbID not in map") + } + + if _, err := stmt.Exec(id, i, dbID, encode(sci.UnlockConditions)); err != nil { return fmt.Errorf("addSiacoinInputs: failed to execute statement: %w", err) } } @@ -129,18 +134,24 @@ func addSiacoinOutputs(tx *txn, id int64, txn types.Transaction, dbIDs map[types return nil } -func addSiafundInputs(tx *txn, id int64, txn types.Transaction) error { +func addSiafundInputs(tx *txn, id int64, txn types.Transaction, dbIDs map[types.SiafundOutputID]int64) error { stmt, err := tx.Prepare(`INSERT INTO transaction_siafund_inputs(transaction_id, transaction_order, parent_id, unlock_conditions, claim_address) VALUES (?, ?, ?, ?, ?)`) if err != nil { return fmt.Errorf("addSiafundInputs: failed to prepare statement: %w", err) } defer stmt.Close() - for i, sci := range txn.SiafundInputs { - if _, err := stmt.Exec(id, i, encode(sci.ParentID), encode(sci.UnlockConditions), encode(sci.ClaimAddress)); err != nil { + for i, sfi := range txn.SiafundInputs { + dbID, ok := dbIDs[sfi.ParentID] + if !ok { + return errors.New("addSiafundOutputs: dbID not in map") + } + + if _, err := stmt.Exec(id, i, dbID, encode(sfi.UnlockConditions), encode(sfi.ClaimAddress)); err != nil { return fmt.Errorf("addSiafundInputs: failed to execute statement: %w", err) } } + return nil } @@ -293,11 +304,11 @@ func addTransactionFields(tx *txn, txns []types.Transaction, scDBIds map[types.S return fmt.Errorf("failed to add arbitrary data: %w", err) } else if err := addSignatures(tx, dbID.id, txn); err != nil { return fmt.Errorf("failed to add signatures: %w", err) - } else if err := addSiacoinInputs(tx, dbID.id, txn); err != nil { + } else if err := addSiacoinInputs(tx, dbID.id, txn, scDBIds); err != nil { return fmt.Errorf("failed to add siacoin inputs: %w", err) } else if err := addSiacoinOutputs(tx, dbID.id, txn, scDBIds); err != nil { return fmt.Errorf("failed to add siacoin outputs: %w", err) - } else if err := addSiafundInputs(tx, dbID.id, txn); err != nil { + } else if err := addSiafundInputs(tx, dbID.id, txn, sfDBIds); err != nil { return fmt.Errorf("failed to add siafund inputs: %w", err) } else if err := addSiafundOutputs(tx, dbID.id, txn, sfDBIds); err != nil { return fmt.Errorf("failed to add siafund outputs: %w", err) diff --git a/persist/sqlite/init.sql b/persist/sqlite/init.sql index 463a20c3..6f829032 100644 --- a/persist/sqlite/init.sql +++ b/persist/sqlite/init.sql @@ -204,7 +204,7 @@ CREATE INDEX transaction_storage_proofs_parent_id_index ON transaction_storage_p CREATE TABLE transaction_siacoin_inputs ( transaction_id INTEGER REFERENCES transactions(id) ON DELETE CASCADE NOT NULL, transaction_order INTEGER NOT NULL, - parent_id BLOB NOT NULL, -- TODO: change this to a reference to the siacoin_element and join for queries + parent_id INTEGER REFERENCES siacoin_elements(id) ON DELETE CASCADE NOT NULL, unlock_conditions BLOB NOT NULL, UNIQUE(transaction_id, transaction_order) ); @@ -221,7 +221,7 @@ CREATE INDEX transaction_siacoin_outputs_transaction_id_index ON transaction_sia CREATE TABLE transaction_siafund_inputs ( transaction_id INTEGER REFERENCES transactions(id) ON DELETE CASCADE NOT NULL, transaction_order INTEGER NOT NULL, - parent_id BLOB NOT NULL, -- TODO: change this to a reference to the siacoin_element and join for queries + parent_id INTEGER REFERENCES siafund_elements(id) ON DELETE CASCADE NOT NULL, unlock_conditions BLOB NOT NULL, claim_address BLOB NOT NULL, UNIQUE(transaction_id, transaction_order) diff --git a/persist/sqlite/transactions.go b/persist/sqlite/transactions.go index 412ed41c..d605cc30 100644 --- a/persist/sqlite/transactions.go +++ b/persist/sqlite/transactions.go @@ -140,9 +140,9 @@ ORDER BY ts.transaction_order ASC` // transactionSiacoinInputs returns the siacoin inputs for each transaction. func transactionSiacoinInputs(tx *txn, txnIDs []int64) (map[int64][]explorer.SiacoinInput, error) { - query := `SELECT ts.transaction_id, ts.parent_id, ts.unlock_conditions, sc.value + 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.output_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)...) @@ -153,9 +153,9 @@ ORDER BY ts.transaction_order ASC` result := make(map[int64][]explorer.SiacoinInput) for rows.Next() { - var txnID int64 + var dbID, txnID int64 var sci explorer.SiacoinInput - if err := rows.Scan(&txnID, decode(&sci.ParentID), decode(&sci.UnlockConditions), decode(&sci.Value)); err != nil { + if err := rows.Scan(&dbID, &txnID, decode(&sci.ParentID), decode(&sci.UnlockConditions), decode(&sci.Value)); err != nil { return nil, fmt.Errorf("failed to scan siacoin input: %w", err) } sci.Address = sci.UnlockConditions.UnlockHash() @@ -166,9 +166,9 @@ ORDER BY ts.transaction_order ASC` // transactionSiafundInputs returns the siafund inputs for each transaction. func transactionSiafundInputs(tx *txn, txnIDs []int64) (map[int64][]explorer.SiafundInput, error) { - query := `SELECT ts.transaction_id, ts.parent_id, ts.unlock_conditions, ts.claim_address, sf.value + 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.output_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)...) @@ -184,6 +184,7 @@ ORDER BY ts.transaction_order ASC` if err := rows.Scan(&txnID, decode(&sfi.ParentID), decode(&sfi.UnlockConditions), decode(&sfi.ClaimAddress), decode(&sfi.Value)); err != nil { return nil, fmt.Errorf("failed to scan siafund input: %w", err) } + sfi.Address = sfi.UnlockConditions.UnlockHash() result[txnID] = append(result[txnID], sfi) } @@ -212,6 +213,7 @@ ORDER BY ts.transaction_order ASC` if err := rows.Scan(&txnID, decode(&sfo.StateElement.ID), decode(&sfo.StateElement.LeafIndex), decodeNull(&spentIndex), decode(&sfo.ClaimStart), decode(&sfo.SiafundOutput.Address), decode(&sfo.SiafundOutput.Value)); err != nil { return nil, fmt.Errorf("failed to scan siafund output: %w", err) } + if spentIndex != (types.ChainIndex{}) { sfo.SpentIndex = &spentIndex } From a6bc2ef2fd9f6d4a12916c075f780d15654a8a13 Mon Sep 17 00:00:00 2001 From: Christopher Tarry Date: Wed, 23 Oct 2024 20:59:53 -0400 Subject: [PATCH 3/4] fix lint --- explorer/types.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/explorer/types.go b/explorer/types.go index ce62182a..1b142746 100644 --- a/explorer/types.go +++ b/explorer/types.go @@ -173,7 +173,7 @@ type V2Transaction struct { SiacoinInputs []types.V2SiacoinInput `json:"siacoinInputs,omitempty"` SiacoinOutputs []SiacoinOutput `json:"siacoinOutputs,omitempty"` - SiafundInputs []types.V2SiafundInput `json:"siacoinInputs,omitempty"` + SiafundInputs []types.V2SiafundInput `json:"siafundInputs,omitempty"` SiafundOutputs []SiafundOutput `json:"siafundOutputs,omitempty"` Attestations []types.Attestation `json:"attestations,omitempty"` ArbitraryData []byte `json:"arbitraryData,omitempty"` From 7af448037d435858f98637eab2811103f6c15ca6 Mon Sep 17 00:00:00 2001 From: Christopher Tarry Date: Thu, 24 Oct 2024 10:21:43 -0400 Subject: [PATCH 4/4] uncomment out check --- internal/testutil/check.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/testutil/check.go b/internal/testutil/check.go index c1556e87..3693f49d 100644 --- a/internal/testutil/check.go +++ b/internal/testutil/check.go @@ -122,7 +122,7 @@ func CheckV2Transaction(t *testing.T, expectTxn types.V2Transaction, gotTxn expl expected := expectTxn.SiacoinInputs[i] got := gotTxn.SiacoinInputs[i] - // Equal(t, "address", expected.Parent.SiacoinOutput.Address, got.Parent.SiacoinOutput.Address) + Equal(t, "address", expected.Parent.SiacoinOutput.Address, got.Parent.SiacoinOutput.Address) Equal(t, "value", expected.Parent.SiacoinOutput.Value, got.Parent.SiacoinOutput.Value) Equal(t, "maturity height", expected.Parent.MaturityHeight, got.Parent.MaturityHeight) Equal(t, "id", expected.Parent.ID, got.Parent.ID)