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 inputs #121

Merged
merged 4 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
16 changes: 10 additions & 6 deletions explorer/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand All @@ -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:"siafundInputs,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"`
Expand Down
26 changes: 26 additions & 0 deletions internal/testutil/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
chris124567 marked this conversation as resolved.
Show resolved Hide resolved
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]
Expand All @@ -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]
Expand Down
73 changes: 34 additions & 39 deletions persist/sqlite/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,15 +94,20 @@ 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)
}
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)
}
}
Expand All @@ -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
}

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -500,47 +511,39 @@ 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
}
}
if len(spentElements) > 0 {
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
}
}
Expand All @@ -554,47 +557,39 @@ 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
}
}
if len(spentElements) > 0 {
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
}
}
Expand Down
23 changes: 21 additions & 2 deletions persist/sqlite/init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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)
);
Expand All @@ -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)
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down
14 changes: 8 additions & 6 deletions persist/sqlite/transactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)...)
Expand All @@ -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()
Expand All @@ -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)...)
Expand All @@ -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)
}
Expand Down Expand Up @@ -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
}
Expand Down
Loading
Loading