From 5c7fb5cda03f4c340e7c4b7354ee0bc41d97b628 Mon Sep 17 00:00:00 2001 From: Christopher Tarry Date: Mon, 3 Jun 2024 14:51:58 -0400 Subject: [PATCH 01/11] update last_contract_revision table --- persist/sqlite/init.sql | 2 ++ 1 file changed, 2 insertions(+) diff --git a/persist/sqlite/init.sql b/persist/sqlite/init.sql index 17bc22d6..790ed8b8 100644 --- a/persist/sqlite/init.sql +++ b/persist/sqlite/init.sql @@ -96,6 +96,8 @@ CREATE INDEX file_contract_elements_contract_id_index ON file_contract_elements( CREATE TABLE last_contract_revision ( contract_id BLOB PRIMARY KEY NOT NULL, + ed25519_host_key BLOB, + ed25519_renter_key BLOB, contract_element_id INTEGER UNIQUE REFERENCES file_contract_elements(id) ON DELETE CASCADE NOT NULL ); From 11ea3aeddffa22c00ffa35429ec27696ab3306f1 Mon Sep 17 00:00:00 2001 From: Christopher Tarry Date: Mon, 3 Jun 2024 16:48:25 -0400 Subject: [PATCH 02/11] store keys for each fc revision in a map --- persist/sqlite/consensus.go | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/persist/sqlite/consensus.go b/persist/sqlite/consensus.go index def11692..7fe2df8f 100644 --- a/persist/sqlite/consensus.go +++ b/persist/sqlite/consensus.go @@ -741,8 +741,8 @@ func addFileContractElements(tx *txn, b types.Block, fces []explorer.FileContrac } defer stmt.Close() - revisionStmt, err := tx.Prepare(`INSERT INTO last_contract_revision(contract_id, contract_element_id) - VALUES (?, ?) + revisionStmt, err := tx.Prepare(`INSERT INTO last_contract_revision(contract_id, contract_element_id, ed25519_host_key, ed25519_renter_key) + VALUES (?, ?, ?, ?) ON CONFLICT (contract_id) DO UPDATE SET contract_element_id = ?`) if err != nil { @@ -750,9 +750,20 @@ func addFileContractElements(tx *txn, b types.Block, fces []explorer.FileContrac } defer revisionStmt.Close() + fcKeys := make(map[explorer.DBFileContract][]types.UnlockKey) + // populate fcKeys using revision UnlockConditions fields + for _, txn := range b.Transactions { + for _, fcr := range txn.FileContractRevisions { + fc := fcr.FileContract + dbFC := explorer.DBFileContract{ID: fcr.ParentID, RevisionNumber: fc.RevisionNumber} + fcKeys[dbFC] = fcr.UnlockConditions.PublicKeys + } + } + fcDBIds := make(map[explorer.DBFileContract]int64) addFC := func(fcID types.FileContractID, leafIndex uint64, fc types.FileContract, resolved, valid, lastRevision bool) error { var dbID int64 + dbFC := explorer.DBFileContract{ID: fcID, RevisionNumber: fc.RevisionNumber} err := stmt.QueryRow(encode(b.ID()), encode(fcID), encode(leafIndex), fc.Filesize, encode(fc.FileMerkleRoot), fc.WindowStart, fc.WindowEnd, encode(fc.Payout), encode(fc.UnlockHash), encode(fc.RevisionNumber), resolved, valid, encode(leafIndex)).Scan(&dbID) if err != nil { return fmt.Errorf("failed to execute file_contract_elements statement: %w", err) @@ -761,12 +772,18 @@ func addFileContractElements(tx *txn, b types.Block, fces []explorer.FileContrac // only update if it's the most recent revision which will come from // running ForEachFileContractElement on the update if lastRevision { - if _, err := revisionStmt.Exec(encode(fcID), dbID, dbID); err != nil { + var renterKey, hostKey []byte + if keys, ok := fcKeys[dbFC]; ok && len(keys) == 2 { + renterKey = keys[0].Key + hostKey = keys[1].Key + } + + if _, err := revisionStmt.Exec(encode(fcID), dbID, renterKey, hostKey, dbID); err != nil { return fmt.Errorf("failed to update last revision number: %w", err) } } - fcDBIds[explorer.DBFileContract{ID: fcID, RevisionNumber: fc.RevisionNumber}] = dbID + fcDBIds[dbFC] = dbID return nil } From d4990c21bfff9d9a7b67c1d3e668bcde2316b587 Mon Sep 17 00:00:00 2001 From: Christopher Tarry Date: Mon, 3 Jun 2024 16:59:52 -0400 Subject: [PATCH 03/11] check key algorithm before storing --- persist/sqlite/consensus.go | 22 ++++++++++++++++++++-- persist/sqlite/contracts.go | 16 ++++++++-------- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/persist/sqlite/consensus.go b/persist/sqlite/consensus.go index 7fe2df8f..4b583433 100644 --- a/persist/sqlite/consensus.go +++ b/persist/sqlite/consensus.go @@ -755,8 +755,26 @@ func addFileContractElements(tx *txn, b types.Block, fces []explorer.FileContrac for _, txn := range b.Transactions { for _, fcr := range txn.FileContractRevisions { fc := fcr.FileContract + uc := fcr.UnlockConditions dbFC := explorer.DBFileContract{ID: fcr.ParentID, RevisionNumber: fc.RevisionNumber} - fcKeys[dbFC] = fcr.UnlockConditions.PublicKeys + + // check for 2 ed25519 keys + ok := true + for i := 0; i < 2; i++ { + // fewer than 2 keys + if i >= len(uc.PublicKeys) { + ok = false + break + } + + // not an ed25519 key + if uc.PublicKeys[i].Algorithm != types.SpecifierEd25519 { + ok = false + } + } + if ok { + fcKeys[dbFC] = fcr.UnlockConditions.PublicKeys + } } } @@ -773,7 +791,7 @@ func addFileContractElements(tx *txn, b types.Block, fces []explorer.FileContrac // running ForEachFileContractElement on the update if lastRevision { var renterKey, hostKey []byte - if keys, ok := fcKeys[dbFC]; ok && len(keys) == 2 { + if keys, ok := fcKeys[dbFC]; ok { renterKey = keys[0].Key hostKey = keys[1].Key } diff --git a/persist/sqlite/contracts.go b/persist/sqlite/contracts.go index bb57e1af..a42c8199 100644 --- a/persist/sqlite/contracts.go +++ b/persist/sqlite/contracts.go @@ -7,16 +7,16 @@ import ( "go.sia.tech/explored/explorer" ) -// Contracts implements explorer.Store. -func (s *Store) Contracts(ids []types.FileContractID) (result []explorer.FileContract, err error) { - encodedIDs := func(ids []types.FileContractID) []any { - result := make([]any, len(ids)) - for i, id := range ids { - result[i] = encode(id) - } - return result +func encodedIDs(ids []types.FileContractID) []any { + result := make([]any, len(ids)) + for i, id := range ids { + result[i] = encode(id) } + return result +} +// Contracts implements explorer.Store. +func (s *Store) Contracts(ids []types.FileContractID) (result []explorer.FileContract, err error) { err = s.transaction(func(tx *txn) error { query := `SELECT fc1.id, fc1.contract_id, fc1.leaf_index, fc1.resolved, fc1.valid, 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 From 9eaeab266da35f63e96f790d13fa4444e524c2bb Mon Sep 17 00:00:00 2001 From: Christopher Tarry Date: Mon, 3 Jun 2024 18:13:18 -0400 Subject: [PATCH 04/11] add ContractsKey method to Explorer, fix bug that prevented keys from being stored when revision wasn't in same block as contract creation --- explorer/explorer.go | 6 ++++++ persist/sqlite/consensus.go | 6 +++--- persist/sqlite/contracts.go | 43 +++++++++++++++++++++++++++++++++++++ persist/sqlite/init.sql | 2 +- 4 files changed, 53 insertions(+), 4 deletions(-) diff --git a/explorer/explorer.go b/explorer/explorer.go index 3f0e425f..9e239348 100644 --- a/explorer/explorer.go +++ b/explorer/explorer.go @@ -42,6 +42,7 @@ type Store interface { 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 []byte) (result []FileContract, err error) } // Explorer implements a Sia explorer. @@ -168,3 +169,8 @@ func (e *Explorer) Balance(address types.Address) (sc types.Currency, immatureSC func (e *Explorer) Contracts(ids []types.FileContractID) (result []FileContract, err error) { return e.s.Contracts(ids) } + +// ContractsKey returns the contracts for a particular ed25519 key. +func (e *Explorer) ContractsKey(key []byte) (result []FileContract, err error) { + return e.s.ContractsKey(key) +} diff --git a/persist/sqlite/consensus.go b/persist/sqlite/consensus.go index 4b583433..1c59ec13 100644 --- a/persist/sqlite/consensus.go +++ b/persist/sqlite/consensus.go @@ -741,10 +741,10 @@ func addFileContractElements(tx *txn, b types.Block, fces []explorer.FileContrac } defer stmt.Close() - revisionStmt, err := tx.Prepare(`INSERT INTO last_contract_revision(contract_id, contract_element_id, ed25519_host_key, ed25519_renter_key) + revisionStmt, err := tx.Prepare(`INSERT INTO last_contract_revision(contract_id, contract_element_id, ed25519_renter_key, ed25519_host_key) VALUES (?, ?, ?, ?) ON CONFLICT (contract_id) - DO UPDATE SET contract_element_id = ?`) + DO UPDATE SET contract_element_id = ?, ed25519_renter_key = ?, ed25519_host_key = ?`) if err != nil { return nil, fmt.Errorf("addFileContractElements: failed to prepare last_contract_revision statement: %w", err) } @@ -796,7 +796,7 @@ func addFileContractElements(tx *txn, b types.Block, fces []explorer.FileContrac hostKey = keys[1].Key } - if _, err := revisionStmt.Exec(encode(fcID), dbID, renterKey, hostKey, dbID); err != nil { + if _, err := revisionStmt.Exec(encode(fcID), dbID, renterKey, hostKey, dbID, renterKey, hostKey); err != nil { return fmt.Errorf("failed to update last revision number: %w", err) } } diff --git a/persist/sqlite/contracts.go b/persist/sqlite/contracts.go index a42c8199..21817cec 100644 --- a/persist/sqlite/contracts.go +++ b/persist/sqlite/contracts.go @@ -57,3 +57,46 @@ func (s *Store) Contracts(ids []types.FileContractID) (result []explorer.FileCon return } + +// ContractsKey implements explorer.Store. +func (s *Store) ContractsKey(key []byte) (result []explorer.FileContract, err error) { + err = s.transaction(func(tx *txn) error { + query := `SELECT fc1.id, fc1.contract_id, fc1.leaf_index, fc1.resolved, fc1.valid, 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) + WHERE rev.ed25519_renter_key = ? OR rev.ed25519_host_key = ?` + rows, err := tx.Query(query, key, key) + if err != nil { + return err + } + defer rows.Close() + + var contractIDs []int64 + idContract := make(map[int64]explorer.FileContract) + for rows.Next() { + var contractID int64 + var fc explorer.FileContract + if err := rows.Scan(&contractID, decode(&fc.StateElement.ID), decode(&fc.StateElement.LeafIndex), &fc.Resolved, &fc.Valid, &fc.Filesize, decode(&fc.FileMerkleRoot), &fc.WindowStart, &fc.WindowEnd, decode(&fc.Payout), decode(&fc.UnlockHash), decode(&fc.RevisionNumber)); err != nil { + return fmt.Errorf("failed to scan transaction: %w", err) + } + + idContract[contractID] = fc + contractIDs = append(contractIDs, contractID) + } + + proofOutputs, err := fileContractOutputs(tx, contractIDs) + if err != nil { + return fmt.Errorf("failed to get file contract outputs: %w", err) + } + for contractID, output := range proofOutputs { + fc := idContract[contractID] + fc.ValidProofOutputs = output.valid + fc.MissedProofOutputs = output.missed + result = append(result, fc) + } + + return nil + }) + + return +} diff --git a/persist/sqlite/init.sql b/persist/sqlite/init.sql index 790ed8b8..9e9add26 100644 --- a/persist/sqlite/init.sql +++ b/persist/sqlite/init.sql @@ -96,8 +96,8 @@ CREATE INDEX file_contract_elements_contract_id_index ON file_contract_elements( CREATE TABLE last_contract_revision ( contract_id BLOB PRIMARY KEY NOT NULL, - ed25519_host_key BLOB, ed25519_renter_key BLOB, + ed25519_host_key BLOB, contract_element_id INTEGER UNIQUE REFERENCES file_contract_elements(id) ON DELETE CASCADE NOT NULL ); From 47af0149daff172c943864645cdbae5c21c0fee0 Mon Sep 17 00:00:00 2001 From: Christopher Tarry Date: Tue, 4 Jun 2024 10:22:35 -0400 Subject: [PATCH 05/11] add checks in tests --- persist/sqlite/consensus.go | 2 +- persist/sqlite/consensus_test.go | 60 ++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/persist/sqlite/consensus.go b/persist/sqlite/consensus.go index 1c59ec13..aa432117 100644 --- a/persist/sqlite/consensus.go +++ b/persist/sqlite/consensus.go @@ -744,7 +744,7 @@ func addFileContractElements(tx *txn, b types.Block, fces []explorer.FileContrac revisionStmt, err := tx.Prepare(`INSERT INTO last_contract_revision(contract_id, contract_element_id, ed25519_renter_key, ed25519_host_key) VALUES (?, ?, ?, ?) ON CONFLICT (contract_id) - DO UPDATE SET contract_element_id = ?, ed25519_renter_key = ?, ed25519_host_key = ?`) + DO UPDATE SET contract_element_id = ?, ed25519_renter_key = COALESCE(?, ed25519_renter_key), ed25519_host_key = COALESCE(?, ed25519_host_key)`) if err != nil { return nil, fmt.Errorf("addFileContractElements: failed to prepare last_contract_revision statement: %w", err) } diff --git a/persist/sqlite/consensus_test.go b/persist/sqlite/consensus_test.go index 1c6a7e62..182c866a 100644 --- a/persist/sqlite/consensus_test.go +++ b/persist/sqlite/consensus_test.go @@ -858,6 +858,21 @@ func TestFileContract(t *testing.T) { } syncDB(t, db, cm) + { + renterContracts, err := db.ContractsKey(renterPublicKey.UnlockKey().Key) + if err != nil { + t.Fatal(err) + } + hostContracts, err := db.ContractsKey(hostPublicKey.UnlockKey().Key) + if err != nil { + t.Fatal(err) + } + check(t, "renter contracts and host contracts", len(renterContracts), len(hostContracts)) + check(t, "len(contracts)", 1, len(renterContracts)) + checkFC(false, true, fc, renterContracts[0]) + checkFC(false, true, fc, hostContracts[0]) + } + checkMetrics(t, db, explorer.Metrics{ Height: 2, Difficulty: cm.TipState().Difficulty, @@ -930,6 +945,21 @@ func TestFileContract(t *testing.T) { syncDB(t, db, cm) } + { + renterContracts, err := db.ContractsKey(renterPublicKey.UnlockKey().Key) + if err != nil { + t.Fatal(err) + } + hostContracts, err := db.ContractsKey(hostPublicKey.UnlockKey().Key) + if err != nil { + t.Fatal(err) + } + check(t, "renter contracts and host contracts", len(renterContracts), len(hostContracts)) + check(t, "len(contracts)", 1, len(renterContracts)) + checkFC(true, false, fc, renterContracts[0]) + checkFC(true, false, fc, hostContracts[0]) + } + checkMetrics(t, db, explorer.Metrics{ Height: cm.Tip().Height, Difficulty: cm.TipState().Difficulty, @@ -1069,6 +1099,21 @@ func TestEphemeralFileContract(t *testing.T) { } syncDB(t, db, cm) + { + renterContracts, err := db.ContractsKey(renterPublicKey.UnlockKey().Key) + if err != nil { + t.Fatal(err) + } + hostContracts, err := db.ContractsKey(hostPublicKey.UnlockKey().Key) + if err != nil { + t.Fatal(err) + } + check(t, "renter contracts and host contracts", len(renterContracts), len(hostContracts)) + check(t, "len(contracts)", 1, len(renterContracts)) + checkFC(true, false, true, revisedFC1, renterContracts[0]) + checkFC(true, false, true, revisedFC1, hostContracts[0]) + } + // Explorer.Contracts should return latest revision { dbFCs, err := db.Contracts([]types.FileContractID{fcID}) @@ -1142,6 +1187,21 @@ func TestEphemeralFileContract(t *testing.T) { checkFC(true, false, true, revisedFC3, dbFCs[0]) } + { + renterContracts, err := db.ContractsKey(renterPublicKey.UnlockKey().Key) + if err != nil { + t.Fatal(err) + } + hostContracts, err := db.ContractsKey(hostPublicKey.UnlockKey().Key) + if err != nil { + t.Fatal(err) + } + check(t, "renter contracts and host contracts", len(renterContracts), len(hostContracts)) + check(t, "len(contracts)", 1, len(renterContracts)) + checkFC(true, false, true, revisedFC3, renterContracts[0]) + checkFC(true, false, true, revisedFC3, hostContracts[0]) + } + { txns, err := db.Transactions([]types.TransactionID{reviseTxn2.ID()}) if err != nil { From 36b6236936fdb332fd710d9a4374a741914f5373 Mon Sep 17 00:00:00 2001 From: Christopher Tarry Date: Tue, 4 Jun 2024 10:45:01 -0400 Subject: [PATCH 06/11] use UnlockKey instead of byte slice for ContractsKey --- explorer/explorer.go | 4 ++-- persist/sqlite/consensus_test.go | 16 ++++++++-------- persist/sqlite/contracts.go | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/explorer/explorer.go b/explorer/explorer.go index 9e239348..8f3dc276 100644 --- a/explorer/explorer.go +++ b/explorer/explorer.go @@ -42,7 +42,7 @@ type Store interface { 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 []byte) (result []FileContract, err error) + ContractsKey(key types.UnlockKey) (result []FileContract, err error) } // Explorer implements a Sia explorer. @@ -171,6 +171,6 @@ func (e *Explorer) Contracts(ids []types.FileContractID) (result []FileContract, } // ContractsKey returns the contracts for a particular ed25519 key. -func (e *Explorer) ContractsKey(key []byte) (result []FileContract, err error) { +func (e *Explorer) ContractsKey(key types.UnlockKey) (result []FileContract, err error) { return e.s.ContractsKey(key) } diff --git a/persist/sqlite/consensus_test.go b/persist/sqlite/consensus_test.go index 182c866a..6a69af23 100644 --- a/persist/sqlite/consensus_test.go +++ b/persist/sqlite/consensus_test.go @@ -859,11 +859,11 @@ func TestFileContract(t *testing.T) { syncDB(t, db, cm) { - renterContracts, err := db.ContractsKey(renterPublicKey.UnlockKey().Key) + renterContracts, err := db.ContractsKey(renterPublicKey.UnlockKey()) if err != nil { t.Fatal(err) } - hostContracts, err := db.ContractsKey(hostPublicKey.UnlockKey().Key) + hostContracts, err := db.ContractsKey(hostPublicKey.UnlockKey()) if err != nil { t.Fatal(err) } @@ -946,11 +946,11 @@ func TestFileContract(t *testing.T) { } { - renterContracts, err := db.ContractsKey(renterPublicKey.UnlockKey().Key) + renterContracts, err := db.ContractsKey(renterPublicKey.UnlockKey()) if err != nil { t.Fatal(err) } - hostContracts, err := db.ContractsKey(hostPublicKey.UnlockKey().Key) + hostContracts, err := db.ContractsKey(hostPublicKey.UnlockKey()) if err != nil { t.Fatal(err) } @@ -1100,11 +1100,11 @@ func TestEphemeralFileContract(t *testing.T) { syncDB(t, db, cm) { - renterContracts, err := db.ContractsKey(renterPublicKey.UnlockKey().Key) + renterContracts, err := db.ContractsKey(renterPublicKey.UnlockKey()) if err != nil { t.Fatal(err) } - hostContracts, err := db.ContractsKey(hostPublicKey.UnlockKey().Key) + hostContracts, err := db.ContractsKey(hostPublicKey.UnlockKey()) if err != nil { t.Fatal(err) } @@ -1188,11 +1188,11 @@ func TestEphemeralFileContract(t *testing.T) { } { - renterContracts, err := db.ContractsKey(renterPublicKey.UnlockKey().Key) + renterContracts, err := db.ContractsKey(renterPublicKey.UnlockKey()) if err != nil { t.Fatal(err) } - hostContracts, err := db.ContractsKey(hostPublicKey.UnlockKey().Key) + hostContracts, err := db.ContractsKey(hostPublicKey.UnlockKey()) if err != nil { t.Fatal(err) } diff --git a/persist/sqlite/contracts.go b/persist/sqlite/contracts.go index 21817cec..b477bed1 100644 --- a/persist/sqlite/contracts.go +++ b/persist/sqlite/contracts.go @@ -59,13 +59,13 @@ func (s *Store) Contracts(ids []types.FileContractID) (result []explorer.FileCon } // ContractsKey implements explorer.Store. -func (s *Store) ContractsKey(key []byte) (result []explorer.FileContract, err error) { +func (s *Store) ContractsKey(key types.UnlockKey) (result []explorer.FileContract, err error) { err = s.transaction(func(tx *txn) error { query := `SELECT fc1.id, fc1.contract_id, fc1.leaf_index, fc1.resolved, fc1.valid, 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) WHERE rev.ed25519_renter_key = ? OR rev.ed25519_host_key = ?` - rows, err := tx.Query(query, key, key) + rows, err := tx.Query(query, key.Key, key.Key) if err != nil { return err } From 535d3da891bba5d72a9b139e10fbf2675ffd98ba Mon Sep 17 00:00:00 2001 From: Christopher Tarry Date: Tue, 4 Jun 2024 10:47:51 -0400 Subject: [PATCH 07/11] add API endpoint --- api/client.go | 8 +++++++- api/server.go | 21 ++++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/api/client.go b/api/client.go index 370a14ee..f92a88b7 100644 --- a/api/client.go +++ b/api/client.go @@ -106,7 +106,7 @@ 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) { - err = c.c.GET(fmt.Sprintf("/explorer/contracts/%s", id), &resp) + err = c.c.GET(fmt.Sprintf("/explorer/contracts/id/%s", id), &resp) return } @@ -116,6 +116,12 @@ func (c *Client) Contracts(ids []types.FileContractID) (resp []explorer.FileCont return } +// ContractsKey returns the contracts for a particular ed25519 key. +func (c *Client) ContractsKey(key types.UnlockKey) (resp []explorer.FileContract, err error) { + err = c.c.GET(fmt.Sprintf("/explorer/contracts/key/%s", key), &resp) + return +} + // Metrics returns the most recent metrics about Sia. func (c *Client) Metrics() (resp explorer.Metrics, err error) { err = c.c.GET("/explorer/metrics", &resp) diff --git a/api/server.go b/api/server.go index 3529cb39..62ddc485 100644 --- a/api/server.go +++ b/api/server.go @@ -51,6 +51,7 @@ type ( 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.UnlockKey) (result []explorer.FileContract, err error) } ) @@ -305,6 +306,23 @@ func (s *server) explorerContractIDHandler(jc jape.Context) { jc.Encode(fcs[0]) } +func (s *server) explorerContractKeyHandler(jc jape.Context) { + errNotFound := errors.New("no contract found") + + var key types.UnlockKey + if jc.DecodeParam("key", &key) != nil { + return + } + fcs, err := s.e.ContractsKey(key) + if jc.Check("failed to get contracts", err) != nil { + return + } else if len(fcs) == 0 { + jc.Error(errNotFound, http.StatusNotFound) + return + } + jc.Encode(fcs) +} + func (s *server) explorerContractsHandler(jc jape.Context) { var ids []types.FileContractID if jc.Decode(&ids) != nil { @@ -346,7 +364,8 @@ func NewServer(e Explorer, cm ChainManager, s Syncer) http.Handler { "GET /explorer/addresses/:address/utxos": srv.explorerAddressessAddressUtxosHandler, "GET /explorer/addresses/:address/events": srv.explorerAddressessAddressEventsHandler, "GET /explorer/addresses/:address/balance": srv.explorerAddressessAddressBalanceHandler, - "GET /explorer/contracts/:id": srv.explorerContractIDHandler, + "GET /explorer/contracts/id/:id": srv.explorerContractIDHandler, + "GET /explorer/contracts/key/:key": srv.explorerContractKeyHandler, "POST /explorer/contracts": srv.explorerContractsHandler, }) } From 6f527751a4c70bd9e54ed93ea83732ceea2fdc00 Mon Sep 17 00:00:00 2001 From: Christopher Tarry Date: Wed, 5 Jun 2024 11:50:46 -0400 Subject: [PATCH 08/11] use PublicKey instead of UnlockKey --- api/server.go | 4 ++-- explorer/explorer.go | 4 ++-- persist/sqlite/consensus.go | 15 +++++++++------ persist/sqlite/consensus_test.go | 16 ++++++++-------- persist/sqlite/contracts.go | 4 ++-- 5 files changed, 23 insertions(+), 20 deletions(-) diff --git a/api/server.go b/api/server.go index 62ddc485..632666c8 100644 --- a/api/server.go +++ b/api/server.go @@ -51,7 +51,7 @@ type ( 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.UnlockKey) (result []explorer.FileContract, err error) + ContractsKey(key types.PublicKey) (result []explorer.FileContract, err error) } ) @@ -309,7 +309,7 @@ func (s *server) explorerContractIDHandler(jc jape.Context) { func (s *server) explorerContractKeyHandler(jc jape.Context) { errNotFound := errors.New("no contract found") - var key types.UnlockKey + var key types.PublicKey if jc.DecodeParam("key", &key) != nil { return } diff --git a/explorer/explorer.go b/explorer/explorer.go index 8f3dc276..1dfb4e7e 100644 --- a/explorer/explorer.go +++ b/explorer/explorer.go @@ -42,7 +42,7 @@ type Store interface { 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.UnlockKey) (result []FileContract, err error) + ContractsKey(key types.PublicKey) (result []FileContract, err error) } // Explorer implements a Sia explorer. @@ -171,6 +171,6 @@ func (e *Explorer) Contracts(ids []types.FileContractID) (result []FileContract, } // ContractsKey returns the contracts for a particular ed25519 key. -func (e *Explorer) ContractsKey(key types.UnlockKey) (result []FileContract, err error) { +func (e *Explorer) ContractsKey(key types.PublicKey) (result []FileContract, err error) { return e.s.ContractsKey(key) } diff --git a/persist/sqlite/consensus.go b/persist/sqlite/consensus.go index aa432117..e4aaba16 100644 --- a/persist/sqlite/consensus.go +++ b/persist/sqlite/consensus.go @@ -750,7 +750,7 @@ func addFileContractElements(tx *txn, b types.Block, fces []explorer.FileContrac } defer revisionStmt.Close() - fcKeys := make(map[explorer.DBFileContract][]types.UnlockKey) + fcKeys := make(map[explorer.DBFileContract][2]types.PublicKey) // populate fcKeys using revision UnlockConditions fields for _, txn := range b.Transactions { for _, fcr := range txn.FileContractRevisions { @@ -760,6 +760,7 @@ func addFileContractElements(tx *txn, b types.Block, fces []explorer.FileContrac // check for 2 ed25519 keys ok := true + var result [2]types.PublicKey for i := 0; i < 2; i++ { // fewer than 2 keys if i >= len(uc.PublicKeys) { @@ -767,13 +768,15 @@ func addFileContractElements(tx *txn, b types.Block, fces []explorer.FileContrac break } - // not an ed25519 key - if uc.PublicKeys[i].Algorithm != types.SpecifierEd25519 { + if uc.PublicKeys[i].Algorithm == types.SpecifierEd25519 { + result[i] = types.PublicKey(uc.PublicKeys[i].Key) + } else { + // not an ed25519 key ok = false } } if ok { - fcKeys[dbFC] = fcr.UnlockConditions.PublicKeys + fcKeys[dbFC] = result } } } @@ -792,8 +795,8 @@ func addFileContractElements(tx *txn, b types.Block, fces []explorer.FileContrac if lastRevision { var renterKey, hostKey []byte if keys, ok := fcKeys[dbFC]; ok { - renterKey = keys[0].Key - hostKey = keys[1].Key + renterKey = encode(keys[0]).([]byte) + hostKey = encode(keys[1]).([]byte) } if _, err := revisionStmt.Exec(encode(fcID), dbID, renterKey, hostKey, dbID, renterKey, hostKey); err != nil { diff --git a/persist/sqlite/consensus_test.go b/persist/sqlite/consensus_test.go index 6a69af23..6290d6eb 100644 --- a/persist/sqlite/consensus_test.go +++ b/persist/sqlite/consensus_test.go @@ -859,11 +859,11 @@ func TestFileContract(t *testing.T) { syncDB(t, db, cm) { - renterContracts, err := db.ContractsKey(renterPublicKey.UnlockKey()) + renterContracts, err := db.ContractsKey(renterPublicKey) if err != nil { t.Fatal(err) } - hostContracts, err := db.ContractsKey(hostPublicKey.UnlockKey()) + hostContracts, err := db.ContractsKey(hostPublicKey) if err != nil { t.Fatal(err) } @@ -946,11 +946,11 @@ func TestFileContract(t *testing.T) { } { - renterContracts, err := db.ContractsKey(renterPublicKey.UnlockKey()) + renterContracts, err := db.ContractsKey(renterPublicKey) if err != nil { t.Fatal(err) } - hostContracts, err := db.ContractsKey(hostPublicKey.UnlockKey()) + hostContracts, err := db.ContractsKey(hostPublicKey) if err != nil { t.Fatal(err) } @@ -1100,11 +1100,11 @@ func TestEphemeralFileContract(t *testing.T) { syncDB(t, db, cm) { - renterContracts, err := db.ContractsKey(renterPublicKey.UnlockKey()) + renterContracts, err := db.ContractsKey(renterPublicKey) if err != nil { t.Fatal(err) } - hostContracts, err := db.ContractsKey(hostPublicKey.UnlockKey()) + hostContracts, err := db.ContractsKey(hostPublicKey) if err != nil { t.Fatal(err) } @@ -1188,11 +1188,11 @@ func TestEphemeralFileContract(t *testing.T) { } { - renterContracts, err := db.ContractsKey(renterPublicKey.UnlockKey()) + renterContracts, err := db.ContractsKey(renterPublicKey) if err != nil { t.Fatal(err) } - hostContracts, err := db.ContractsKey(hostPublicKey.UnlockKey()) + hostContracts, err := db.ContractsKey(hostPublicKey) if err != nil { t.Fatal(err) } diff --git a/persist/sqlite/contracts.go b/persist/sqlite/contracts.go index b477bed1..f51ea073 100644 --- a/persist/sqlite/contracts.go +++ b/persist/sqlite/contracts.go @@ -59,13 +59,13 @@ func (s *Store) Contracts(ids []types.FileContractID) (result []explorer.FileCon } // ContractsKey implements explorer.Store. -func (s *Store) ContractsKey(key types.UnlockKey) (result []explorer.FileContract, err error) { +func (s *Store) ContractsKey(key types.PublicKey) (result []explorer.FileContract, err error) { err = s.transaction(func(tx *txn) error { query := `SELECT fc1.id, fc1.contract_id, fc1.leaf_index, fc1.resolved, fc1.valid, 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) WHERE rev.ed25519_renter_key = ? OR rev.ed25519_host_key = ?` - rows, err := tx.Query(query, key.Key, key.Key) + rows, err := tx.Query(query, encode(key), encode(key)) if err != nil { return err } From d142b09445fc7a31d9a9570a08dccd7f13758b6d Mon Sep 17 00:00:00 2001 From: Christopher Tarry Date: Wed, 5 Jun 2024 12:15:13 -0400 Subject: [PATCH 09/11] use top down approach for naming routes --- api/client.go | 6 +++--- api/server.go | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/api/client.go b/api/client.go index f92a88b7..055e59b6 100644 --- a/api/client.go +++ b/api/client.go @@ -88,19 +88,19 @@ func (c *Client) Transactions(ids []types.TransactionID) (resp []explorer.Transa // AddressUTXOs returns the specified address' unspent outputs. func (c *Client) AddressUTXOs(address types.Address, offset, limit uint64) (resp AddressUTXOsResponse, err error) { - err = c.c.GET(fmt.Sprintf("/explorer/addresses/%s/utxos?offset=%d&limit=%d", address, offset, limit), &resp) + err = c.c.GET(fmt.Sprintf("/explorer/addresses/utxos/%s?offset=%d&limit=%d", address, offset, limit), &resp) return } // AddressEvents returns the specified address' events. func (c *Client) AddressEvents(address types.Address, offset, limit uint64) (resp []explorer.Event, err error) { - err = c.c.GET(fmt.Sprintf("/explorer/addresses/%s/events?offset=%d&limit=%d", address, offset, limit), &resp) + err = c.c.GET(fmt.Sprintf("/explorer/addresses/events/%s?offset=%d&limit=%d", address, offset, limit), &resp) return } // AddressBalance returns the specified address' balance. func (c *Client) AddressBalance(address types.Address) (resp AddressBalanceResponse, err error) { - err = c.c.GET(fmt.Sprintf("/explorer/addresses/%s/balance", address), &resp) + err = c.c.GET(fmt.Sprintf("/explorer/addresses/balance/%s", address), &resp) return } diff --git a/api/server.go b/api/server.go index 632666c8..9fdc7c0d 100644 --- a/api/server.go +++ b/api/server.go @@ -361,9 +361,9 @@ func NewServer(e Explorer, cm ChainManager, s Syncer) http.Handler { "GET /explorer/metrics/:id": srv.explorerMetricsIDHandler, "GET /explorer/transactions/:id": srv.explorerTransactionsIDHandler, "POST /explorer/transactions": srv.explorerTransactionsHandler, - "GET /explorer/addresses/:address/utxos": srv.explorerAddressessAddressUtxosHandler, - "GET /explorer/addresses/:address/events": srv.explorerAddressessAddressEventsHandler, - "GET /explorer/addresses/:address/balance": srv.explorerAddressessAddressBalanceHandler, + "GET /explorer/addresses/utxos/:address": srv.explorerAddressessAddressUtxosHandler, + "GET /explorer/addresses/events/:address": srv.explorerAddressessAddressEventsHandler, + "GET /explorer/addresses/balance/:address": srv.explorerAddressessAddressBalanceHandler, "GET /explorer/contracts/id/:id": srv.explorerContractIDHandler, "GET /explorer/contracts/key/:key": srv.explorerContractKeyHandler, "POST /explorer/contracts": srv.explorerContractsHandler, From 693f75781f3f063571caf0c3ff81e5d3abdfc83a Mon Sep 17 00:00:00 2001 From: Christopher Tarry Date: Wed, 5 Jun 2024 12:19:36 -0400 Subject: [PATCH 10/11] fix client analyzer warning --- api/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/client.go b/api/client.go index 055e59b6..08bf9e2c 100644 --- a/api/client.go +++ b/api/client.go @@ -117,7 +117,7 @@ func (c *Client) Contracts(ids []types.FileContractID) (resp []explorer.FileCont } // ContractsKey returns the contracts for a particular ed25519 key. -func (c *Client) ContractsKey(key types.UnlockKey) (resp []explorer.FileContract, err error) { +func (c *Client) ContractsKey(key types.PublicKey) (resp []explorer.FileContract, err error) { err = c.c.GET(fmt.Sprintf("/explorer/contracts/key/%s", key), &resp) return } From 1252ebaddc9b2cf12e41621e9e13c18f38f276ce Mon Sep 17 00:00:00 2001 From: Christopher Tarry Date: Wed, 5 Jun 2024 21:00:19 -0400 Subject: [PATCH 11/11] change API routes --- api/client.go | 10 +++++----- api/server.go | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/api/client.go b/api/client.go index 08bf9e2c..723560a4 100644 --- a/api/client.go +++ b/api/client.go @@ -88,25 +88,25 @@ func (c *Client) Transactions(ids []types.TransactionID) (resp []explorer.Transa // AddressUTXOs returns the specified address' unspent outputs. func (c *Client) AddressUTXOs(address types.Address, offset, limit uint64) (resp AddressUTXOsResponse, err error) { - err = c.c.GET(fmt.Sprintf("/explorer/addresses/utxos/%s?offset=%d&limit=%d", address, offset, limit), &resp) + err = c.c.GET(fmt.Sprintf("/explorer/addresses/%s/utxos?offset=%d&limit=%d", address, offset, limit), &resp) return } // AddressEvents returns the specified address' events. func (c *Client) AddressEvents(address types.Address, offset, limit uint64) (resp []explorer.Event, err error) { - err = c.c.GET(fmt.Sprintf("/explorer/addresses/events/%s?offset=%d&limit=%d", address, offset, limit), &resp) + err = c.c.GET(fmt.Sprintf("/explorer/addresses/%s/events?offset=%d&limit=%d", address, offset, limit), &resp) return } // AddressBalance returns the specified address' balance. func (c *Client) AddressBalance(address types.Address) (resp AddressBalanceResponse, err error) { - err = c.c.GET(fmt.Sprintf("/explorer/addresses/balance/%s", address), &resp) + err = c.c.GET(fmt.Sprintf("/explorer/addresses/%s/balance", address), &resp) return } // Contract returns the file contract with the specified ID. func (c *Client) Contract(id types.FileContractID) (resp explorer.FileContract, err error) { - err = c.c.GET(fmt.Sprintf("/explorer/contracts/id/%s", id), &resp) + err = c.c.GET(fmt.Sprintf("/explorer/contracts/%s", id), &resp) return } @@ -118,7 +118,7 @@ func (c *Client) Contracts(ids []types.FileContractID) (resp []explorer.FileCont // ContractsKey returns the contracts for a particular ed25519 key. func (c *Client) ContractsKey(key types.PublicKey) (resp []explorer.FileContract, err error) { - err = c.c.GET(fmt.Sprintf("/explorer/contracts/key/%s", key), &resp) + err = c.c.GET(fmt.Sprintf("/explorer/pubkey/%s/contracts", key), &resp) return } diff --git a/api/server.go b/api/server.go index 9fdc7c0d..6b92cb51 100644 --- a/api/server.go +++ b/api/server.go @@ -361,11 +361,11 @@ func NewServer(e Explorer, cm ChainManager, s Syncer) http.Handler { "GET /explorer/metrics/:id": srv.explorerMetricsIDHandler, "GET /explorer/transactions/:id": srv.explorerTransactionsIDHandler, "POST /explorer/transactions": srv.explorerTransactionsHandler, - "GET /explorer/addresses/utxos/:address": srv.explorerAddressessAddressUtxosHandler, - "GET /explorer/addresses/events/:address": srv.explorerAddressessAddressEventsHandler, - "GET /explorer/addresses/balance/:address": srv.explorerAddressessAddressBalanceHandler, - "GET /explorer/contracts/id/:id": srv.explorerContractIDHandler, - "GET /explorer/contracts/key/:key": srv.explorerContractKeyHandler, + "GET /explorer/addresses/:address/utxos": srv.explorerAddressessAddressUtxosHandler, + "GET /explorer/addresses/:address/events": srv.explorerAddressessAddressEventsHandler, + "GET /explorer/addresses/:address/balance": srv.explorerAddressessAddressBalanceHandler, + "GET /explorer/contracts/:id": srv.explorerContractIDHandler, + "GET /explorer/pubkey/:key/contracts": srv.explorerContractKeyHandler, "POST /explorer/contracts": srv.explorerContractsHandler, }) }