diff --git a/explorer/events.go b/explorer/events.go index 74b919c8..a8a9ed2d 100644 --- a/explorer/events.go +++ b/explorer/events.go @@ -85,11 +85,7 @@ type EventTransaction struct { } // An EventV2Transaction represents a v2 transaction that affects the wallet. -type EventV2Transaction struct { - Transaction V2Transaction `json:"transaction"` - HostAnnouncements []chain.HostAnnouncement `json:"hostAnnouncements"` - Fee types.Currency `json:"fee"` -} +type EventV2Transaction V2Transaction // An EventMinerPayout represents a miner payout from a block. type EventMinerPayout struct { @@ -221,7 +217,10 @@ func AppliedEvents(cs consensus.State, b types.Block, cu ChainUpdate) []Event { for _, a := range txn.Attestations { var ha chain.V2HostAnnouncement if ha.FromAttestation(a) == nil { - // TODO: handle attestation + e.HostAnnouncements = append(e.HostAnnouncements, V2HostAnnouncement{ + PublicKey: a.PublicKey, + V2HostAnnouncement: ha, + }) } } addEvent(types.Hash256(txn.ID()), cs.Index.Height, &e, relevant) // transaction maturity height is the current block height diff --git a/explorer/types.go b/explorer/types.go index baa612fe..6085e11e 100644 --- a/explorer/types.go +++ b/explorer/types.go @@ -299,9 +299,11 @@ type HostScan struct { // Host represents a host and the information gathered from scanning it. type Host struct { - PublicKey types.PublicKey `json:"publicKey"` - NetAddress string `json:"netAddress"` - CountryCode string `json:"countryCode"` + PublicKey types.PublicKey `json:"publicKey"` + NetAddress string `json:"netAddress"` + V2NetAddresses []chain.NetAddress `json:"v2NetAddresses,omitempty"` + + CountryCode string `json:"countryCode"` KnownSince time.Time `json:"knownSince"` LastScan time.Time `json:"lastScan"` diff --git a/internal/testutil/check.go b/internal/testutil/check.go index 41d3a3c8..42ea7007 100644 --- a/internal/testutil/check.go +++ b/internal/testutil/check.go @@ -107,6 +107,22 @@ func CheckTransaction(t *testing.T, expectTxn types.Transaction, gotTxn explorer // cf.X = make([]uint64, d.ReadPrefix()) and the prefix is 0 // testutil.Equal(t, "covered fields", expected.CoveredFields, got.CoveredFields) } + + var hostAnnouncements []chain.HostAnnouncement + for _, arb := range expectTxn.ArbitraryData { + var ha chain.HostAnnouncement + if ha.FromArbitraryData(arb) { + hostAnnouncements = append(hostAnnouncements, ha) + } + } + Equal(t, "host announcements", len(hostAnnouncements), len(gotTxn.HostAnnouncements)) + for i := range hostAnnouncements { + expected := hostAnnouncements[i] + got := gotTxn.HostAnnouncements[i] + + Equal(t, "public key", expected.PublicKey, got.PublicKey) + Equal(t, "net address", expected.NetAddress, got.NetAddress) + } } // CheckV2Transaction checks the inputs and outputs of the retrieved transaction @@ -195,7 +211,7 @@ func CheckV2Transaction(t *testing.T, expectTxn types.V2Transaction, gotTxn expl var hostAnnouncements []explorer.V2HostAnnouncement for _, attestation := range expectTxn.Attestations { var ha chain.V2HostAnnouncement - if ha.FromAttestation(attestation) != nil { + if ha.FromAttestation(attestation) == nil { hostAnnouncements = append(hostAnnouncements, explorer.V2HostAnnouncement{ V2HostAnnouncement: ha, PublicKey: attestation.PublicKey, diff --git a/persist/sqlite/addresses.go b/persist/sqlite/addresses.go index e9e33cd0..c7fd9a58 100644 --- a/persist/sqlite/addresses.go +++ b/persist/sqlite/addresses.go @@ -26,30 +26,16 @@ func scanEvent(tx *txn, s scanner) (ev explorer.Event, eventID int64, err error) if err != nil { return explorer.Event{}, 0, fmt.Errorf("failed to fetch transaction ID: %w", err) } - txns, err := getTransactions(tx, map[int64]transactionID{txnID: {id: types.TransactionID(ev.ID)}}) + txns, err := getTransactions(tx, map[int64]transactionID{0: {dbID: txnID, id: types.TransactionID(ev.ID)}}) if err != nil || len(txns) == 0 { return explorer.Event{}, 0, fmt.Errorf("failed to fetch transaction: %w", err) } - - rows, err := tx.Query(`SELECT public_key, net_address FROM host_announcements WHERE transaction_id = ? ORDER BY transaction_order ASC`, txnID) - if err != nil { - return explorer.Event{}, 0, fmt.Errorf("failed to get host announcements: %w", err) - } - defer rows.Close() - eventTx.Transaction = txns[0] - for rows.Next() { - var announcement chain.HostAnnouncement - if err := rows.Scan(decode(&announcement.PublicKey), &announcement.NetAddress); err != nil { - return explorer.Event{}, 0, fmt.Errorf("failed to scan announcement: %w", err) - } - eventTx.HostAnnouncements = append(eventTx.HostAnnouncements, announcement) - } + eventTx.HostAnnouncements = eventTx.Transaction.HostAnnouncements ev.Data = &eventTx case explorer.EventTypeV2Transaction: var txnID int64 - var eventTx explorer.EventV2Transaction - err = tx.QueryRow(`SELECT transaction_id, fee FROM v2_transaction_events WHERE event_id = ?`, eventID).Scan(&txnID, decode(&eventTx.Fee)) + err = tx.QueryRow(`SELECT transaction_id FROM v2_transaction_events WHERE event_id = ?`, eventID).Scan(&txnID) if err != nil { return explorer.Event{}, 0, fmt.Errorf("failed to fetch v2 transaction ID: %w", err) } @@ -57,12 +43,8 @@ func scanEvent(tx *txn, s scanner) (ev explorer.Event, eventID int64, err error) if err != nil || len(txns) == 0 { return explorer.Event{}, 0, fmt.Errorf("failed to fetch v2 transaction: %w", err) } - - rows, err := tx.Query(`SELECT public_key, net_address FROM v2_host_announcements WHERE transaction_id = ? ORDER BY transaction_order ASC`, txnID) - if err != nil { - return explorer.Event{}, 0, fmt.Errorf("failed to get host announcements: %w", err) - } - defer rows.Close() + eventTx := explorer.EventV2Transaction(txns[0]) + ev.Data = &eventTx case explorer.EventTypeContractPayout: var m explorer.EventContractPayout err = tx.QueryRow(`SELECT sce.output_id, sce.leaf_index, sce.maturity_height, sce.address, sce.value, fce.contract_id, fce.leaf_index, fce.filesize, fce.file_merkle_root, fce.window_start, fce.window_end, fce.payout, fce.unlock_hash, fce.revision_number, ev.missed @@ -113,14 +95,38 @@ func (st *Store) Hosts(pks []types.PublicKey) (result []explorer.Host, err error } defer rows.Close() + v2AddrStmt, err := tx.Prepare(`SELECT protocol,address FROM host_info_v2_netaddresses WHERE public_key = ? ORDER BY netaddress_order`) + if err != nil { + return err + } + defer v2AddrStmt.Close() + for rows.Next() { - var host explorer.Host - s, p := &host.Settings, &host.PriceTable - if err := rows.Scan(decode(&host.PublicKey), &host.NetAddress, &host.CountryCode, decode(&host.KnownSince), decode(&host.LastScan), &host.LastScanSuccessful, decode(&host.LastAnnouncement), &host.TotalScans, &host.SuccessfulInteractions, &host.FailedInteractions, &s.AcceptingContracts, decode(&s.MaxDownloadBatchSize), decode(&s.MaxDuration), decode(&s.MaxReviseBatchSize), &s.NetAddress, decode(&s.RemainingStorage), decode(&s.SectorSize), decode(&s.TotalStorage), decode(&s.Address), decode(&s.WindowSize), decode(&s.Collateral), decode(&s.MaxCollateral), decode(&s.BaseRPCPrice), decode(&s.ContractPrice), decode(&s.DownloadBandwidthPrice), decode(&s.SectorAccessPrice), decode(&s.StoragePrice), decode(&s.UploadBandwidthPrice), &s.EphemeralAccountExpiry, decode(&s.MaxEphemeralAccountBalance), decode(&s.RevisionNumber), &s.Version, &s.Release, &s.SiaMuxPort, decode(&p.UID), &p.Validity, decode(&p.HostBlockHeight), decode(&p.UpdatePriceTableCost), decode(&p.AccountBalanceCost), decode(&p.FundAccountCost), decode(&p.LatestRevisionCost), decode(&p.SubscriptionMemoryCost), decode(&p.SubscriptionNotificationCost), decode(&p.InitBaseCost), decode(&p.MemoryTimeCost), decode(&p.DownloadBandwidthCost), decode(&p.UploadBandwidthCost), decode(&p.DropSectorsBaseCost), decode(&p.DropSectorsUnitCost), decode(&p.HasSectorBaseCost), decode(&p.ReadBaseCost), decode(&p.ReadLengthCost), decode(&p.RenewContractCost), decode(&p.RevisionBaseCost), decode(&p.SwapSectorBaseCost), decode(&p.WriteBaseCost), decode(&p.WriteLengthCost), decode(&p.WriteStoreCost), decode(&p.TxnFeeMinRecommended), decode(&p.TxnFeeMaxRecommended), decode(&p.ContractPrice), decode(&p.CollateralCost), decode(&p.MaxCollateral), decode(&p.MaxDuration), decode(&p.WindowSize), decode(&p.RegistryEntriesLeft), decode(&p.RegistryEntriesTotal)); err != nil { + if err := func() error { + var host explorer.Host + s, p := &host.Settings, &host.PriceTable + if err := rows.Scan(decode(&host.PublicKey), &host.NetAddress, &host.CountryCode, decode(&host.KnownSince), decode(&host.LastScan), &host.LastScanSuccessful, decode(&host.LastAnnouncement), &host.TotalScans, &host.SuccessfulInteractions, &host.FailedInteractions, &s.AcceptingContracts, decode(&s.MaxDownloadBatchSize), decode(&s.MaxDuration), decode(&s.MaxReviseBatchSize), &s.NetAddress, decode(&s.RemainingStorage), decode(&s.SectorSize), decode(&s.TotalStorage), decode(&s.Address), decode(&s.WindowSize), decode(&s.Collateral), decode(&s.MaxCollateral), decode(&s.BaseRPCPrice), decode(&s.ContractPrice), decode(&s.DownloadBandwidthPrice), decode(&s.SectorAccessPrice), decode(&s.StoragePrice), decode(&s.UploadBandwidthPrice), &s.EphemeralAccountExpiry, decode(&s.MaxEphemeralAccountBalance), decode(&s.RevisionNumber), &s.Version, &s.Release, &s.SiaMuxPort, decode(&p.UID), &p.Validity, decode(&p.HostBlockHeight), decode(&p.UpdatePriceTableCost), decode(&p.AccountBalanceCost), decode(&p.FundAccountCost), decode(&p.LatestRevisionCost), decode(&p.SubscriptionMemoryCost), decode(&p.SubscriptionNotificationCost), decode(&p.InitBaseCost), decode(&p.MemoryTimeCost), decode(&p.DownloadBandwidthCost), decode(&p.UploadBandwidthCost), decode(&p.DropSectorsBaseCost), decode(&p.DropSectorsUnitCost), decode(&p.HasSectorBaseCost), decode(&p.ReadBaseCost), decode(&p.ReadLengthCost), decode(&p.RenewContractCost), decode(&p.RevisionBaseCost), decode(&p.SwapSectorBaseCost), decode(&p.WriteBaseCost), decode(&p.WriteLengthCost), decode(&p.WriteStoreCost), decode(&p.TxnFeeMinRecommended), decode(&p.TxnFeeMaxRecommended), decode(&p.ContractPrice), decode(&p.CollateralCost), decode(&p.MaxCollateral), decode(&p.MaxDuration), decode(&p.WindowSize), decode(&p.RegistryEntriesLeft), decode(&p.RegistryEntriesTotal)); err != nil { + return err + } + + v2AddrRows, err := v2AddrStmt.Query(encode(host.PublicKey)) + if err != nil { + return err + } + defer v2AddrRows.Close() + for v2AddrRows.Next() { + var netAddr chain.NetAddress + if err := v2AddrRows.Scan(&netAddr.Protocol, &netAddr.Address); err != nil { + return err + } + host.V2NetAddresses = append(host.V2NetAddresses, netAddr) + } + + result = append(result, host) + return nil + }(); err != nil { return err } - - result = append(result, host) } return nil }) diff --git a/persist/sqlite/consensus.go b/persist/sqlite/consensus.go index 07c26525..1a43cd43 100644 --- a/persist/sqlite/consensus.go +++ b/persist/sqlite/consensus.go @@ -625,24 +625,12 @@ func addEvents(tx *txn, scDBIds map[types.SiacoinOutputID]int64, fcDBIds map[exp } defer transactionEventStmt.Close() - v2TransactionEventStmt, err := tx.Prepare(`INSERT INTO v2_transaction_events (event_id, transaction_id, fee) VALUES (?, ?, ?)`) + v2TransactionEventStmt, err := tx.Prepare(`INSERT INTO v2_transaction_events (event_id, transaction_id) VALUES (?, ?)`) if err != nil { return fmt.Errorf("failed to prepare v2 transaction event statement: %w", err) } defer v2TransactionEventStmt.Close() - hostAnnouncementStmt, err := tx.Prepare(`INSERT INTO host_announcements (transaction_id, transaction_order, public_key, net_address) VALUES (?, ?, ?, ?)`) - if err != nil { - return fmt.Errorf("failed to prepare host anonouncement statement: %w", err) - } - defer hostAnnouncementStmt.Close() - - v2HostAnnouncementStmt, err := tx.Prepare(`INSERT INTO v2_host_announcements (transaction_id, transaction_order, public_key, net_address) VALUES (?, ?, ?, ?)`) - if err != nil { - return fmt.Errorf("failed to prepare host anonouncement statement: %w", err) - } - defer v2HostAnnouncementStmt.Close() - minerPayoutEventStmt, err := tx.Prepare(`INSERT INTO miner_payout_events (event_id, output_id) VALUES (?, ?)`) if err != nil { return fmt.Errorf("failed to prepare miner payout event statement: %w", err) @@ -684,10 +672,7 @@ func addEvents(tx *txn, scDBIds map[types.SiacoinOutputID]int64, fcDBIds map[exp return fmt.Errorf("failed to insert transaction event: %w", err) } var hosts []explorer.Host - for i, announcement := range v.HostAnnouncements { - if _, err = hostAnnouncementStmt.Exec(dbID, i, encode(announcement.PublicKey), announcement.NetAddress); err != nil { - return fmt.Errorf("failed to insert host announcement: %w", err) - } + for _, announcement := range v.HostAnnouncements { hosts = append(hosts, explorer.Host{ PublicKey: announcement.PublicKey, NetAddress: announcement.NetAddress, @@ -703,17 +688,14 @@ func addEvents(tx *txn, scDBIds map[types.SiacoinOutputID]int64, fcDBIds map[exp } case *explorer.EventV2Transaction: dbID := v2TxnDBIds[types.TransactionID(event.ID)].id - if _, err = v2TransactionEventStmt.Exec(eventID, dbID, encode(v.Fee)); err != nil { + if _, err = v2TransactionEventStmt.Exec(eventID, dbID); err != nil { return fmt.Errorf("failed to insert transaction event: %w", err) } var hosts []explorer.Host - for i, announcement := range v.HostAnnouncements { - if _, err = hostAnnouncementStmt.Exec(dbID, i, encode(announcement.PublicKey), announcement.NetAddress); err != nil { - return fmt.Errorf("failed to insert host announcement: %w", err) - } + for _, announcement := range v.HostAnnouncements { hosts = append(hosts, explorer.Host{ - PublicKey: announcement.PublicKey, - NetAddress: announcement.NetAddress, + PublicKey: announcement.PublicKey, + V2NetAddresses: []chain.NetAddress(announcement.V2HostAnnouncement), KnownSince: event.Timestamp, LastAnnouncement: event.Timestamp, @@ -1023,7 +1005,7 @@ func addMetrics(tx *txn, s explorer.UpdateState) error { } func (ut *updateTx) HostExists(pubkey types.PublicKey) (exists bool, err error) { - err = ut.tx.QueryRow(`SELECT EXISTS(SELECT public_key FROM host_announcements WHERE public_key = ?)`, encode(pubkey)).Scan(&exists) + err = ut.tx.QueryRow(`SELECT EXISTS(SELECT public_key FROM host_info WHERE public_key = ?)`, encode(pubkey)).Scan(&exists) return } @@ -1140,14 +1122,34 @@ func (ut *updateTx) RevertIndex(state explorer.UpdateState) error { func addHosts(tx *txn, scans []explorer.Host) error { stmt, err := tx.Prepare(`INSERT INTO host_info(public_key, net_address, country_code, known_since, last_scan, last_scan_successful, last_announcement, total_scans, successful_interactions, failed_interactions, settings_accepting_contracts, settings_max_download_batch_size, settings_max_duration, settings_max_revise_batch_size, settings_net_address, settings_remaining_storage, settings_sector_size, settings_total_storage, settings_address, settings_window_size, settings_collateral, settings_max_collateral, settings_base_rpc_price, settings_contract_price, settings_download_bandwidth_price, settings_sector_access_price, settings_storage_price, settings_upload_bandwidth_price, settings_ephemeral_account_expiry, settings_max_ephemeral_account_balance, settings_revision_number, settings_version, settings_release, settings_sia_mux_port, price_table_uid, price_table_validity, price_table_host_block_height, price_table_update_price_table_cost, price_table_account_balance_cost, price_table_fund_account_cost, price_table_latest_revision_cost, price_table_subscription_memory_cost, price_table_subscription_notification_cost, price_table_init_base_cost, price_table_memory_time_cost, price_table_download_bandwidth_cost, price_table_upload_bandwidth_cost, price_table_drop_sectors_base_cost, price_table_drop_sectors_unit_cost, price_table_has_sector_base_cost, price_table_read_base_cost, price_table_read_length_cost, price_table_renew_contract_cost, price_table_revision_base_cost, price_table_swap_sector_base_cost, price_table_write_base_cost, price_table_write_length_cost, price_table_write_store_cost, price_table_txn_fee_min_recommended, price_table_txn_fee_max_recommended, price_table_contract_price, price_table_collateral_cost, price_table_max_collateral, price_table_max_duration, price_table_window_size, price_table_registry_entries_left, price_table_registry_entries_total) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$30,$31,$32,$33,$34,$35,$36,$37,$38,$39,$40,$41,$42,$43,$44,$45,$46,$47,$48,$49,$50,$51,$52,$53,$54,$55,$56,$57,$58,$59,$60,$61,$62,$63,$64,$65,$66,$67) ON CONFLICT (public_key) DO UPDATE SET net_address = $2, country_code = $3, last_scan = $5, last_scan_successful = $6, last_announcement = CASE WHEN $7 > 0 THEN last_announcement ELSE $7 END, total_scans = $8, successful_interactions = $9, failed_interactions = failed_interactions + $10, settings_accepting_contracts = $11, settings_max_download_batch_size = $12, settings_max_duration = $13, settings_max_revise_batch_size = $14 , settings_net_address = $15, settings_remaining_storage = $16, settings_sector_size = $17, settings_total_storage = $18, settings_address = $19, settings_window_size = $20, settings_collateral = $21, settings_max_collateral = $22, settings_base_rpc_price = $23, settings_contract_price = $24, settings_download_bandwidth_price = $25, settings_sector_access_price = $26, settings_storage_price = $27, settings_upload_bandwidth_price = $28, settings_ephemeral_account_expiry = $29, settings_max_ephemeral_account_balance = $30, settings_revision_number = $31, settings_version = $32, settings_release = $33, settings_sia_mux_port = $34, price_table_uid = $35, price_table_validity = $36, price_table_host_block_height = $37, price_table_update_price_table_cost = $38, price_table_account_balance_cost = $39, price_table_fund_account_cost = $40, price_table_latest_revision_cost = $41, price_table_subscription_memory_cost = $42, price_table_subscription_notification_cost = $43, price_table_init_base_cost = $44, price_table_memory_time_cost = $45, price_table_download_bandwidth_cost = $46, price_table_upload_bandwidth_cost = $47, price_table_drop_sectors_base_cost = $48, price_table_drop_sectors_unit_cost = $49, price_table_has_sector_base_cost = $50, price_table_read_base_cost = $51, price_table_read_length_cost = $52, price_table_renew_contract_cost = $53, price_table_revision_base_cost = $54, price_table_swap_sector_base_cost = $55, price_table_write_base_cost = $56, price_table_write_length_cost = $57, price_table_write_store_cost = $58, price_table_txn_fee_min_recommended = $59, price_table_txn_fee_max_recommended = $60, price_table_contract_price = $61, price_table_collateral_cost = $62, price_table_max_collateral = $63, price_table_max_duration = $64, price_table_window_size = $65, price_table_registry_entries_left = $66, price_table_registry_entries_total = $67`) if err != nil { - return err + return fmt.Errorf("failed to prepare host_info stmt: %w", err) } defer stmt.Close() + deleteV2AddrStmt, err := tx.Prepare(`DELETE FROM host_info_v2_netaddresses WHERE public_key = ?`) + if err != nil { + return fmt.Errorf("failed to prepare delete v2 net address stmt: %w", err) + } + defer deleteV2AddrStmt.Close() + + addV2AddrStmt, err := tx.Prepare(`INSERT INTO host_info_v2_netaddresses(public_key, netaddress_order, protocol, address) VALUES (?, ?, ?, ?)`) + if err != nil { + return fmt.Errorf("failed to prepare add v2 net address stmt: %w", err) + } + defer addV2AddrStmt.Close() + for _, scan := range scans { s, p := scan.Settings, scan.PriceTable if _, err := stmt.Exec(encode(scan.PublicKey), scan.NetAddress, scan.CountryCode, encode(scan.KnownSince), encode(scan.LastScan), scan.LastScanSuccessful, encode(scan.LastAnnouncement), scan.TotalScans, scan.SuccessfulInteractions, scan.FailedInteractions, s.AcceptingContracts, encode(s.MaxDownloadBatchSize), encode(s.MaxDuration), encode(s.MaxReviseBatchSize), s.NetAddress, encode(s.RemainingStorage), encode(s.SectorSize), encode(s.TotalStorage), encode(s.Address), encode(s.WindowSize), encode(s.Collateral), encode(s.MaxCollateral), encode(s.BaseRPCPrice), encode(s.ContractPrice), encode(s.DownloadBandwidthPrice), encode(s.SectorAccessPrice), encode(s.StoragePrice), encode(s.UploadBandwidthPrice), s.EphemeralAccountExpiry, encode(s.MaxEphemeralAccountBalance), encode(s.RevisionNumber), s.Version, s.Release, s.SiaMuxPort, encode(p.UID), p.Validity, encode(p.HostBlockHeight), encode(p.UpdatePriceTableCost), encode(p.AccountBalanceCost), encode(p.FundAccountCost), encode(p.LatestRevisionCost), encode(p.SubscriptionMemoryCost), encode(p.SubscriptionNotificationCost), encode(p.InitBaseCost), encode(p.MemoryTimeCost), encode(p.DownloadBandwidthCost), encode(p.UploadBandwidthCost), encode(p.DropSectorsBaseCost), encode(p.DropSectorsUnitCost), encode(p.HasSectorBaseCost), encode(p.ReadBaseCost), encode(p.ReadLengthCost), encode(p.RenewContractCost), encode(p.RevisionBaseCost), encode(p.SwapSectorBaseCost), encode(p.WriteBaseCost), encode(p.WriteLengthCost), encode(p.WriteStoreCost), encode(p.TxnFeeMinRecommended), encode(p.TxnFeeMaxRecommended), encode(p.ContractPrice), encode(p.CollateralCost), encode(p.MaxCollateral), encode(p.MaxDuration), encode(p.WindowSize), encode(p.RegistryEntriesLeft), encode(p.RegistryEntriesTotal)); err != nil { - return err + return fmt.Errorf("failed to execute host_info stmt: %w", err) + } + if _, err := deleteV2AddrStmt.Exec(encode(scan.PublicKey)); err != nil { + return fmt.Errorf("failed to execute delete v2 net address stmt: %w", err) + } + for i, netAddr := range scan.V2NetAddresses { + if _, err := addV2AddrStmt.Exec(encode(scan.PublicKey), i, netAddr.Protocol, netAddr.Address); err != nil { + return fmt.Errorf("failed to execute add v2 net address stmt: %w", err) + } } } return nil diff --git a/persist/sqlite/consensus_test.go b/persist/sqlite/consensus_test.go index b8eeb145..7c648e68 100644 --- a/persist/sqlite/consensus_test.go +++ b/persist/sqlite/consensus_test.go @@ -2,6 +2,7 @@ package sqlite_test import ( "errors" + "math" "path/filepath" "testing" "time" @@ -1522,29 +1523,25 @@ func TestRevertSendTransactions(t *testing.T) { func TestHostAnnouncement(t *testing.T) { pk1 := types.GeneratePrivateKey() + addr1 := types.StandardUnlockHash(pk1.PublicKey()) + uc1 := types.StandardUnlockConditions(pk1.PublicKey()) + pk2 := types.GeneratePrivateKey() pk3 := types.GeneratePrivateKey() - _, _, cm, db := newStore(t, false, nil) - - checkHostAnnouncements := func(expectedArbitraryData [][]byte, got []chain.HostAnnouncement) { - t.Helper() - - var expected []chain.HostAnnouncement - for _, arb := range expectedArbitraryData { - var ha chain.HostAnnouncement - if ha.FromArbitraryData(arb) { - expected = append(expected, ha) - } - } - testutil.Equal(t, "len(hostAnnouncements)", len(expected), len(got)) - for i := range expected { - testutil.Equal(t, "host public key", expected[i].PublicKey, got[i].PublicKey) - testutil.Equal(t, "host net address", expected[i].NetAddress, got[i].NetAddress) - } - } + _, genesisBlock, cm, db := newStore(t, false, func(network *consensus.Network, genesisBlock types.Block) { + genesisBlock.Transactions[0].SiacoinOutputs[0].Address = addr1 + }) txn1 := types.Transaction{ + SiacoinInputs: []types.SiacoinInput{{ + ParentID: genesisBlock.Transactions[0].SiacoinOutputID(0), + UnlockConditions: uc1, + }}, + SiacoinOutputs: []types.SiacoinOutput{{ + Address: addr1, + Value: genesisBlock.Transactions[0].SiacoinOutputs[0].Value, + }}, ArbitraryData: [][]byte{ testutil.CreateAnnouncement(pk1, "127.0.0.1:1234"), }, @@ -1614,13 +1611,24 @@ func TestHostAnnouncement(t *testing.T) { testutil.Equal(t, "txns[3].ID", txn4.ID(), dbTxns[3].ID) } + { + events, err := db.AddressEvents(addr1, 0, math.MaxInt64) + if err != nil { + t.Fatal(err) + } + if v, ok := events[0].Data.(*explorer.EventTransaction); !ok { + t.Fatal("expected EventTransaction") + } else { + testutil.CheckTransaction(t, txn1, v.Transaction) + } + } + { dbTxns, err := db.Transactions([]types.TransactionID{txn1.ID()}) if err != nil { t.Fatal(err) } - testutil.Equal(t, "len(txns)", 1, len(dbTxns)) - checkHostAnnouncements(txn1.ArbitraryData, dbTxns[0].HostAnnouncements) + testutil.CheckTransaction(t, txn1, dbTxns[0]) } { @@ -1628,8 +1636,7 @@ func TestHostAnnouncement(t *testing.T) { if err != nil { t.Fatal(err) } - testutil.Equal(t, "len(txns)", 1, len(dbTxns)) - checkHostAnnouncements(txn2.ArbitraryData, dbTxns[0].HostAnnouncements) + testutil.CheckTransaction(t, txn2, dbTxns[0]) } { @@ -1637,8 +1644,7 @@ func TestHostAnnouncement(t *testing.T) { if err != nil { t.Fatal(err) } - testutil.Equal(t, "len(txns)", 1, len(dbTxns)) - checkHostAnnouncements(txn3.ArbitraryData, dbTxns[0].HostAnnouncements) + testutil.CheckTransaction(t, txn3, dbTxns[0]) } ts := time.Unix(0, 0) diff --git a/persist/sqlite/encoding.go b/persist/sqlite/encoding.go index 3aac5c9c..0952c38a 100644 --- a/persist/sqlite/encoding.go +++ b/persist/sqlite/encoding.go @@ -10,6 +10,7 @@ import ( "go.sia.tech/core/rhp/v3" "go.sia.tech/core/types" + "go.sia.tech/coreutils/chain" ) func encode(obj any) any { @@ -34,6 +35,12 @@ func encode(obj any) any { types.EncodeSlice(e, obj) e.Flush() return buf.Bytes() + case []chain.NetAddress: + var buf bytes.Buffer + e := types.NewEncoder(&buf) + types.EncodeSlice(e, obj) + e.Flush() + return buf.Bytes() case uint64: b := make([]byte, 8) binary.BigEndian.PutUint64(b, obj) @@ -74,6 +81,10 @@ func (d *decodable) Scan(src any) error { dec := types.NewBufDecoder(src) types.DecodeSlice(dec, v) return dec.Err() + case *[]chain.NetAddress: + dec := types.NewBufDecoder(src) + types.DecodeSlice(dec, v) + return dec.Err() case *uint64: *v = binary.BigEndian.Uint64(src) default: diff --git a/persist/sqlite/init.sql b/persist/sqlite/init.sql index cc772b59..6cc2ea96 100644 --- a/persist/sqlite/init.sql +++ b/persist/sqlite/init.sql @@ -364,16 +364,6 @@ CREATE TABLE event_addresses ( CREATE INDEX event_addresses_event_id_index ON event_addresses(event_id); CREATE INDEX event_addresses_address_id_index ON event_addresses(address_id); -CREATE TABLE host_announcements ( - transaction_id INTEGER REFERENCES transactions(id) ON DELETE CASCADE NOT NULL, - transaction_order INTEGER NOT NULL, - public_key BLOB NOT NULL, - net_address BLOB NOT NULL, - UNIQUE(transaction_id, transaction_order) -); -CREATE INDEX host_announcements_transaction_id_index ON host_announcements(transaction_id); -CREATE INDEX host_announcements_public_key_index ON host_announcements(public_key); - CREATE TABLE transaction_events ( event_id INTEGER PRIMARY KEY REFERENCES events(id) ON DELETE CASCADE NOT NULL, transaction_id INTEGER REFERENCES transactions(id) ON DELETE CASCADE NOT NULL, @@ -440,20 +430,9 @@ CREATE TABLE v2_last_contract_revision ( contract_element_id INTEGER UNIQUE REFERENCES v2_file_contract_elements(id) ON DELETE CASCADE NOT NULL ); -CREATE TABLE v2_host_announcements ( - transaction_id INTEGER REFERENCES v2_transactions(id) ON DELETE CASCADE NOT NULL, - transaction_order INTEGER NOT NULL, - public_key BLOB NOT NULL, - net_address BLOB NOT NULL, - UNIQUE(transaction_id, transaction_order) -); -CREATE INDEX v2_host_announcements_transaction_id_index ON v2_host_announcements(transaction_id); -CREATE INDEX v2_host_announcements_public_key_index ON v2_host_announcements(public_key); - CREATE TABLE v2_transaction_events ( event_id INTEGER PRIMARY KEY REFERENCES events(id) ON DELETE CASCADE NOT NULL, - transaction_id INTEGER REFERENCES v2_transactions(id) ON DELETE CASCADE NOT NULL, - fee BLOB NOT NULL + transaction_id INTEGER REFERENCES v2_transactions(id) ON DELETE CASCADE NOT NULL ); CREATE TABLE host_info ( @@ -528,6 +507,16 @@ CREATE TABLE host_info ( price_table_registry_entries_total BLOB NOT NULL ); +CREATE TABLE host_info_v2_netaddresses( + public_key BLOB REFERENCES host_info(public_key) ON DELETE CASCADE NOT NULL, + netaddress_order INTEGER NOT NULL, + protocol TEXT NOT NULL, + address TEXT NOT NULL, + + UNIQUE(public_key, netaddress_order) +); + +CREATE INDEX host_info_v2_netaddresses_public_key ON host_info_v2_netaddresses(public_key); -- initialize the global settings table INSERT INTO global_settings (id, db_version) VALUES (0, 0); -- should not be changed diff --git a/persist/sqlite/v2consensus_test.go b/persist/sqlite/v2consensus_test.go index 63a63655..e49ad447 100644 --- a/persist/sqlite/v2consensus_test.go +++ b/persist/sqlite/v2consensus_test.go @@ -1,6 +1,7 @@ package sqlite_test import ( + "math" "testing" "time" @@ -214,12 +215,17 @@ func TestV2FoundationAddress(t *testing.T) { func TestV2Attestations(t *testing.T) { pk1 := types.GeneratePrivateKey() + addr1 := types.StandardUnlockHash(pk1.PublicKey()) + addr1Policy := types.SpendPolicy{Type: types.PolicyTypeUnlockConditions(types.StandardUnlockConditions(pk1.PublicKey()))} + pk2 := types.GeneratePrivateKey() - _, _, cm, db := newStore(t, true, func(network *consensus.Network, genesisBlock types.Block) { + _, genesisBlock, cm, db := newStore(t, true, func(network *consensus.Network, genesisBlock types.Block) { network.HardforkV2.AllowHeight = 1 network.HardforkV2.RequireHeight = 2 + genesisBlock.Transactions[0].SiacoinOutputs[0].Address = addr1 }) + giftSC := genesisBlock.Transactions[0].SiacoinOutputs[0].Value cs := cm.TipState() ha1 := chain.V2HostAnnouncement{{ @@ -239,14 +245,32 @@ func TestV2Attestations(t *testing.T) { otherAttestation.Signature = pk1.SignHash(cs.AttestationSigHash(otherAttestation)) txn1 := types.V2Transaction{ + SiacoinInputs: []types.V2SiacoinInput{{ + Parent: getSCE(t, db, genesisBlock.Transactions[0].SiacoinOutputID(0)), + SatisfiedPolicy: types.SatisfiedPolicy{Policy: addr1Policy}, + }}, + MinerFee: giftSC, Attestations: []types.Attestation{ha1.ToAttestation(cs, pk1), otherAttestation, ha2.ToAttestation(cs, pk2)}, } + testutil.SignV2Transaction(cm.TipState(), pk1, &txn1) if err := cm.AddBlocks([]types.Block{testutil.MineV2Block(cs, []types.V2Transaction{txn1}, types.VoidAddress)}); err != nil { t.Fatal(err) } syncDB(t, db, cm) + { + events, err := db.AddressEvents(addr1, 0, math.MaxInt64) + if err != nil { + t.Fatal(err) + } + if v, ok := events[0].Data.(*explorer.EventV2Transaction); !ok { + t.Fatal("expected EventV2Transaction") + } else { + testutil.CheckV2Transaction(t, txn1, explorer.V2Transaction(*v)) + } + } + { dbTxns, err := db.V2Transactions([]types.TransactionID{txn1.ID()}) if err != nil { diff --git a/persist/sqlite/v2transactions.go b/persist/sqlite/v2transactions.go index 26bae0d0..bbc08980 100644 --- a/persist/sqlite/v2transactions.go +++ b/persist/sqlite/v2transactions.go @@ -84,7 +84,7 @@ func getV2Transactions(tx *txn, ids []types.TransactionID) ([]explorer.V2Transac for i := range txns { for _, attestation := range txns[i].Attestations { var ha chain.V2HostAnnouncement - if ha.FromAttestation(attestation) != nil { + if ha.FromAttestation(attestation) == nil { txns[i].HostAnnouncements = append(txns[i].HostAnnouncements, explorer.V2HostAnnouncement{ V2HostAnnouncement: ha, PublicKey: attestation.PublicKey,