Skip to content

Commit

Permalink
store addresses in transaction
Browse files Browse the repository at this point in the history
  • Loading branch information
chris124567 committed Apr 1, 2024
1 parent 04264f4 commit abba1ef
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 0 deletions.
6 changes: 6 additions & 0 deletions explorer/explorer.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type Store interface {
Block(id types.BlockID) (Block, error)
BestTip(height uint64) (types.ChainIndex, error)
Transactions(ids []types.TransactionID) ([]Transaction, error)
TransactionAddresses(id types.TransactionID, limit, offset uint64) ([]types.Address, error)
UnspentSiacoinOutputs(address types.Address, limit, offset uint64) ([]SiacoinOutput, error)
UnspentSiafundOutputs(address types.Address, limit, offset uint64) ([]SiafundOutput, error)
Balance(address types.Address) (sc types.Currency, immatureSC types.Currency, sf uint64, err error)
Expand Down Expand Up @@ -57,6 +58,11 @@ func (e *Explorer) Transactions(ids []types.TransactionID) ([]Transaction, error
return e.s.Transactions(ids)
}

// TransactionAddresses returns the addresses in the transaction.
func (e *Explorer) TransactionAddresses(id types.TransactionID, limit, offset uint64) (results []types.Address, err error) {
return e.s.TransactionAddresses(id, limit, offset)
}

// UnspentSiacoinOutputs returns the unspent siacoin outputs owned by the
// specified address.
func (e *Explorer) UnspentSiacoinOutputs(address types.Address, limit, offset uint64) ([]SiacoinOutput, error) {
Expand Down
49 changes: 49 additions & 0 deletions persist/sqlite/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,53 @@ func (s *Store) addMinerPayouts(dbTxn txn, bid types.BlockID, height uint64, sco
return nil
}

func (s *Store) addTransactionAddresses(dbTxn txn, id int64, txn types.Transaction) error {
m := make(map[types.Address]struct{})
for _, sci := range txn.SiacoinInputs {
m[sci.UnlockConditions.UnlockHash()] = struct{}{}
}
for _, sco := range txn.SiacoinOutputs {
m[sco.Address] = struct{}{}
}
for _, sfi := range txn.SiafundInputs {
m[sfi.UnlockConditions.UnlockHash()] = struct{}{}
}
for _, sfo := range txn.SiafundOutputs {
m[sfo.Address] = struct{}{}
}
for _, fc := range txn.FileContracts {
for _, vpo := range fc.ValidProofOutputs {
m[vpo.Address] = struct{}{}
}
for _, mpo := range fc.MissedProofOutputs {
m[mpo.Address] = struct{}{}
}
m[types.Address(fc.UnlockHash)] = struct{}{}
}
for _, fcr := range txn.FileContractRevisions {
for _, vpo := range fcr.FileContract.ValidProofOutputs {
m[vpo.Address] = struct{}{}
}
for _, mpo := range fcr.FileContract.MissedProofOutputs {
m[mpo.Address] = struct{}{}
}
m[fcr.UnlockConditions.UnlockHash()] = struct{}{}
}

stmt, err := dbTxn.Prepare(`INSERT INTO transaction_addresses(transaction_id, address) VALUES (?, ?);`)
if err != nil {
return fmt.Errorf("addTransactionAddresses: failed to prepare statement: %w", err)
}
defer stmt.Close()

for addr := range m {
if _, err := stmt.Exec(id, dbEncode(addr)); err != nil {
return fmt.Errorf("addTransactionAddresses: failed to execute statement: %w", err)
}
}
return nil
}

func (s *Store) addArbitraryData(dbTxn txn, id int64, txn types.Transaction) error {
stmt, err := dbTxn.Prepare(`INSERT INTO transaction_arbitrary_data(transaction_id, transaction_order, data) VALUES (?, ?, ?)`)
if err != nil {
Expand Down Expand Up @@ -235,6 +282,8 @@ func (s *Store) addTransactions(dbTxn txn, bid types.BlockID, txns []types.Trans

if _, err := blockTransactionsStmt.Exec(dbEncode(bid), txnID, i); err != nil {
return fmt.Errorf("failed to insert into block_transactions: %w", err)
} else if err := s.addTransactionAddresses(dbTxn, txnID, txn); err != nil {
return fmt.Errorf("failed to add transaction addresses: %w", err)
} else if err := s.addArbitraryData(dbTxn, txnID, txn); err != nil {
return fmt.Errorf("failed to add arbitrary data: %w", err)
} else if err := s.addSiacoinInputs(dbTxn, txnID, txn); err != nil {
Expand Down
25 changes: 25 additions & 0 deletions persist/sqlite/consensus_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package sqlite_test
import (
"path/filepath"
"reflect"
"sort"
"testing"

"go.sia.tech/core/consensus"
Expand Down Expand Up @@ -516,6 +517,30 @@ func TestSendTransactions(t *testing.T) {
}
}
}

{
expected := []types.Address{addr1, addr2, addr3}
addresses, err := db.TransactionAddresses(parentTxn.ID(), 3, 0)
if err != nil {
t.Fatal(err)
}
if len(expected) != len(addresses) {
t.Fatalf("expected %d addresses, got %d", len(expected), len(addresses))
}

sort.Slice(expected, func(i, j int) bool {
return expected[i][0] > expected[j][0]
})
sort.Slice(addresses, func(i, j int) bool {
return addresses[i][0] > addresses[j][0]
})

for i := range expected {
if expected[i] != addresses[i] {
t.Fatalf("expect address %v, got %v", expected[i], addresses[i])
}
}
}
}
}

Expand Down
8 changes: 8 additions & 0 deletions persist/sqlite/init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,14 @@ CREATE TABLE block_transactions (

CREATE INDEX block_transactions_block_id_index ON block_transactions(block_id);

CREATE TABLE transaction_addresses (
transaction_id INTEGER REFERENCES transactions(id) ON DELETE CASCADE NOT NULL,
address BLOB NOT NULL,
UNIQUE(transaction_id, address)
);

CREATE INDEX transaction_addresses_transaction_id_index ON transaction_addresses(transaction_id);

CREATE TABLE transaction_arbitrary_data (
transaction_id INTEGER REFERENCES transactions(id) ON DELETE CASCADE NOT NULL,
transaction_order INTEGER NOT NULL,
Expand Down
33 changes: 33 additions & 0 deletions persist/sqlite/transactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -425,3 +425,36 @@ func (s *Store) Transactions(ids []types.TransactionID) (results []explorer.Tran
})
return
}

// TransactionAddresses implements explorer.Store.
func (s *Store) TransactionAddresses(id types.TransactionID, limit, offset uint64) (results []types.Address, err error) {
err = s.transaction(func(tx txn) error {
dbIDs, err := transactionDatabaseIDs(tx, []types.TransactionID{id})
if err != nil {
return fmt.Errorf("failed to get transaction IDs: %w", err)
} else if len(dbIDs) == 0 {
return errors.New("no such transaction")
}

query := `SELECT address
FROM transaction_addresses
WHERE transaction_id = ?
LIMIT ? OFFSET ?
`
rows, err := tx.Query(query, dbIDs[0], limit, offset)
if err != nil {
return fmt.Errorf("failed to get addresses: %w", err)
}

for rows.Next() {
var addr types.Address
if err := rows.Scan(dbDecode(&addr)); err != nil {
return fmt.Errorf("failed to scan address: %w", err)
}
results = append(results, addr)
}

return nil
})
return
}

0 comments on commit abba1ef

Please sign in to comment.