Skip to content

Commit

Permalink
settings,sqlite: support v2 announcements
Browse files Browse the repository at this point in the history
  • Loading branch information
n8maninger committed Oct 22, 2024
1 parent b6408a3 commit 1f3c894
Show file tree
Hide file tree
Showing 12 changed files with 266 additions and 118 deletions.
13 changes: 12 additions & 1 deletion config/config.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package config

import (
"go.sia.tech/coreutils/chain"
)

type (
// HTTP contains the configuration for the HTTP server.
HTTP struct {
Expand Down Expand Up @@ -43,9 +47,16 @@ type (
Address string `yaml:"address,omitempty"`
}

// RHP4AnnounceAddress contains the configuration for an RHP4 announce address.
RHP4AnnounceAddress struct {
Protocol chain.Protocol `yaml:"protocol,omitempty"`
Address string `yaml:"address,omitempty"`
}

// RHP4 contains the configuration for the RHP4 server.
RHP4 struct {
ListenAddresses []RHP4ListenAddress `yaml:"listenAddresses,omitempty"`
ListenAddresses []RHP4ListenAddress `yaml:"listenAddresses,omitempty"`
AnnounceAddresses []RHP4AnnounceAddress `yaml:"announceAddresses,omitempty"`
}

// LogFile configures the file output of the logger.
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ require (
github.com/hashicorp/golang-lru/v2 v2.0.7
github.com/mattn/go-sqlite3 v1.14.24
github.com/shopspring/decimal v1.4.0
go.sia.tech/core v0.4.8-0.20241021220756-063922806c77
go.sia.tech/coreutils v0.4.2-0.20241021220920-ba0a3a3731db
go.sia.tech/core v0.4.8-0.20241022155418-2652ab2ba1cf
go.sia.tech/coreutils v0.4.2-0.20241022210836-d816b0aa7f04
go.sia.tech/jape v0.12.1
go.sia.tech/mux v1.3.0
go.sia.tech/web/hostd v0.49.0
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0=
go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I=
go.sia.tech/core v0.4.8-0.20241021220756-063922806c77 h1:8Ynvew8X7nBxByE+/S/yxyPj+77urhvWCUQUgThvZ+0=
go.sia.tech/core v0.4.8-0.20241021220756-063922806c77/go.mod h1:P3C1BWa/7J4XgdzWuaYHBvLo2RzZ0UBaJM4TG1GWB2g=
go.sia.tech/coreutils v0.4.2-0.20241021220920-ba0a3a3731db h1:tIjUv2wYw47Ns/etqr75bIRKJzvkOTxhi+A2PF7Pq5U=
go.sia.tech/coreutils v0.4.2-0.20241021220920-ba0a3a3731db/go.mod h1:3DD62IhAD0sPiy9AvK2amVYvp3LWtpVrl3UaGurLjnE=
go.sia.tech/core v0.4.8-0.20241022155418-2652ab2ba1cf h1:JxSZc/0Ij4zfrTAzjO5BGPExXs4z8QgOXZpgPyi95l8=
go.sia.tech/core v0.4.8-0.20241022155418-2652ab2ba1cf/go.mod h1:P3C1BWa/7J4XgdzWuaYHBvLo2RzZ0UBaJM4TG1GWB2g=
go.sia.tech/coreutils v0.4.2-0.20241022210836-d816b0aa7f04 h1:dilLTgmRqEPeys9NGxECf2Vow+NGFSr8UGWf405p+VQ=
go.sia.tech/coreutils v0.4.2-0.20241022210836-d816b0aa7f04/go.mod h1:jAVJPz70Cuthj0z8yij1Bh0l4kSAxlNOtI11xxgZ+sQ=
go.sia.tech/jape v0.12.1 h1:xr+o9V8FO8ScRqbSaqYf9bjj1UJ2eipZuNcI1nYousU=
go.sia.tech/jape v0.12.1/go.mod h1:wU+h6Wh5olDjkPXjF0tbZ1GDgoZ6VTi4naFw91yyWC4=
go.sia.tech/mux v1.3.0 h1:hgR34IEkqvfBKUJkAzGi31OADeW2y7D6Bmy/Jcbop9c=
Expand Down
30 changes: 16 additions & 14 deletions host/settings/announce.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (

"go.sia.tech/core/types"
"go.sia.tech/coreutils/chain"
rhp4 "go.sia.tech/coreutils/rhp/v4"
"go.uber.org/zap"
)

Expand All @@ -22,19 +21,19 @@ type (

// Announce announces the host to the network
func (m *ConfigManager) Announce() error {
// get the current settings
settings := m.Settings()

if m.validateNetAddress {
if err := validateNetAddress(settings.NetAddress); err != nil {
return fmt.Errorf("failed to validate net address %q: %w", settings.NetAddress, err)
}
}

minerFee := m.chain.RecommendedFee().Mul64(announcementTxnSize)

cs := m.chain.TipState()
if cs.Index.Height < cs.Network.HardforkV2.AllowHeight {
// get the current settings
settings := m.Settings()

if m.validateNetAddress {
if err := validateNetAddress(settings.NetAddress); err != nil {
return fmt.Errorf("failed to validate net address %q: %w", settings.NetAddress, err)
}
}

ha := chain.HostAnnouncement{
PublicKey: m.hostKey.PublicKey(),
NetAddress: settings.NetAddress,
Expand Down Expand Up @@ -62,9 +61,7 @@ func (m *ConfigManager) Announce() error {
m.syncer.BroadcastTransactionSet(txnset)
m.log.Debug("broadcast announcement", zap.String("transactionID", txn.ID().String()), zap.String("netaddress", settings.NetAddress), zap.String("cost", minerFee.ExactString()))
} else {
ha := chain.V2HostAnnouncement{
{Protocol: rhp4.ProtocolTCPSiaMux, Address: settings.NetAddress}, // TODO this is not right
}
ha := chain.V2HostAnnouncement(m.rhp4AnnounceAddresses)
// create a v2 transaction with an announcement
txn := types.V2Transaction{
Attestations: []types.Attestation{
Expand All @@ -86,11 +83,16 @@ func (m *ConfigManager) Announce() error {
return fmt.Errorf("failed to add transaction to pool: %w", err)
}
m.syncer.BroadcastV2TransactionSet(cs.Index, txnset)
m.log.Debug("broadcast v2 announcement", zap.String("transactionID", txn.ID().String()), zap.String("netaddress", settings.NetAddress), zap.String("cost", minerFee.ExactString()))
addresses := make([]string, 0, len(m.rhp4AnnounceAddresses))
for _, addr := range m.rhp4AnnounceAddresses {
addresses = append(addresses, fmt.Sprintf("%s/%s", addr.Protocol, addr.Address)) // TODO: implement Stringer?
}
m.log.Debug("broadcast v2 announcement", zap.String("transactionID", txn.ID().String()), zap.Strings("addresses", addresses), zap.String("cost", minerFee.ExactString()))
}
return nil
}

// Deprecated: remove after hardfork
func validateNetAddress(netaddress string) error {
host, port, err := net.SplitHostPort(netaddress)
if err != nil {
Expand Down
125 changes: 81 additions & 44 deletions host/settings/announce_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"testing"

"go.sia.tech/core/types"
"go.sia.tech/coreutils/chain"
rhp4 "go.sia.tech/coreutils/rhp/v4"
"go.sia.tech/coreutils/wallet"
"go.sia.tech/hostd/host/contracts"
"go.sia.tech/hostd/host/settings"
Expand Down Expand Up @@ -46,7 +48,11 @@ func TestAutoAnnounce(t *testing.T) {
}
defer storage.Close()

sm, err := settings.NewConfigManager(hostKey, node.Store, node.Chain, node.Syncer, wm, storage, settings.WithLog(log.Named("settings")), settings.WithAnnounceInterval(50), settings.WithValidateNetAddress(false))
v2AnnounceAddresses := []chain.NetAddress{
{Protocol: rhp4.ProtocolTCPSiaMux, Address: "foo.bar:1234"},
{Protocol: rhp4.ProtocolTCPSiaMux, Address: "foo.bar:1236"},
}
sm, err := settings.NewConfigManager(hostKey, node.Store, node.Chain, node.Syncer, wm, storage, settings.WithLog(log.Named("settings")), settings.WithAnnounceInterval(50), settings.WithRHP4AnnounceAddresses(v2AnnounceAddresses))
if err != nil {
t.Fatal(err)
}
Expand All @@ -58,10 +64,6 @@ func TestAutoAnnounce(t *testing.T) {
}
defer idx.Close()

// fund the wallet
testutil.MineBlocks(t, node, wm.Address(), 150)
testutil.WaitForSync(t, node.Chain, idx)

settings := settings.DefaultSettings
settings.NetAddress = "foo.bar:1234"
sm.UpdateSettings(settings)
Expand All @@ -84,6 +86,33 @@ func TestAutoAnnounce(t *testing.T) {
}
}

assertV2Announcement := func(t *testing.T, addresses []chain.NetAddress, height uint64) {
t.Helper()

index, ok := node.Chain.BestIndex(height)
if !ok {
t.Fatal("failed to get index")
}

hash, announceIndex, err := node.Store.LastV2AnnouncementHash()
if err != nil {
t.Fatal(err)
}

h := types.NewHasher()
types.EncodeSlice(h.E, addresses)
if err := h.E.Flush(); err != nil {
t.Fatal(err)
}
expectedHash := h.Sum()

if hash != expectedHash {
t.Fatalf("expected hash %v, got %v", expectedHash, hash)
} else if announceIndex != index {
t.Fatalf("expected index %v, got %v", index, announceIndex)
}
}

// helper that mines blocks and waits for them to be processed before mining
// the next one. This is necessary because test blocks can be extremely fast
// and the host may not have time to process the broadcast before the next
Expand All @@ -98,30 +127,30 @@ func TestAutoAnnounce(t *testing.T) {
}
}

// trigger an auto-announce
mineAndSync(t, 2)
assertAnnouncement(t, "foo.bar:1234", 152)
// fund the wallet and trigger the first auto-announce
mineAndSync(t, 146)
assertAnnouncement(t, "foo.bar:1234", 146) // 144 (first maturity height) + 1 (funds available) + 1 (confirmation)
// mine until the next announcement and confirm it
mineAndSync(t, 51)
assertAnnouncement(t, "foo.bar:1234", 203) // 152 (first confirm) + 50 (interval) + 1 (confirmation)
assertAnnouncement(t, "foo.bar:1234", 197) // 146 (first confirm) + 50 (interval) + 1 (confirmation)

// change the address
settings.NetAddress = "baz.qux:5678"
sm.UpdateSettings(settings)

// trigger and confirm the new announcement
mineAndSync(t, 2)
assertAnnouncement(t, "baz.qux:5678", 205)
assertAnnouncement(t, "baz.qux:5678", 199)

// mine until the v2 hardfork activates. The host should re-announce with a
// v2 attestation.
n := node.Chain.TipState().Network
mineAndSync(t, int(n.HardforkV2.AllowHeight-node.Chain.Tip().Height)+1)
assertAnnouncement(t, "baz.qux:5678", n.HardforkV2.AllowHeight+1)
assertV2Announcement(t, v2AnnounceAddresses, n.HardforkV2.AllowHeight+1)

// mine a few more blocks to ensure the host doesn't re-announce
mineAndSync(t, 10)
assertAnnouncement(t, "baz.qux:5678", n.HardforkV2.AllowHeight+1)
assertV2Announcement(t, v2AnnounceAddresses, n.HardforkV2.AllowHeight+1)
}

func TestAutoAnnounceV2(t *testing.T) {
Expand Down Expand Up @@ -159,7 +188,11 @@ func TestAutoAnnounceV2(t *testing.T) {
}
defer storage.Close()

sm, err := settings.NewConfigManager(hostKey, node.Store, node.Chain, node.Syncer, wm, storage, settings.WithLog(log.Named("settings")), settings.WithAnnounceInterval(50))
v2AnnounceAddresses := []chain.NetAddress{
{Protocol: rhp4.ProtocolTCPSiaMux, Address: "foo.bar:1234"},
{Protocol: rhp4.ProtocolTCPSiaMux, Address: "foo.bar:1236"},
}
sm, err := settings.NewConfigManager(hostKey, node.Store, node.Chain, node.Syncer, wm, storage, settings.WithLog(log.Named("settings")), settings.WithAnnounceInterval(50), settings.WithRHP4AnnounceAddresses(v2AnnounceAddresses))
if err != nil {
t.Fatal(err)
}
Expand All @@ -171,58 +204,62 @@ func TestAutoAnnounceV2(t *testing.T) {
}
defer idx.Close()

// fund the wallet
testutil.MineBlocks(t, node, wm.Address(), 150)
testutil.WaitForSync(t, node.Chain, idx)
// helper that mines blocks and waits for them to be processed before mining
// the next one. This is necessary because test blocks can be extremely fast
// and the host may not have time to process the broadcast before the next
// block is mined.
mineAndSync := func(t *testing.T, numBlocks int) {
t.Helper()

settings := settings.DefaultSettings
settings.NetAddress = "foo.bar:1234"
sm.UpdateSettings(settings)
// waits for each block to be processed before mining the next one
for i := 0; i < numBlocks; i++ {
testutil.MineBlocks(t, node, wm.Address(), 1)
testutil.WaitForSync(t, node.Chain, idx)
}
}

assertAnnouncement := func(t *testing.T, expectedAddr string, height uint64) {
assertAnnouncement := func(t *testing.T, addresses []chain.NetAddress, height uint64) {
t.Helper()

index, ok := node.Chain.BestIndex(height)
if !ok {
t.Fatal("failed to get index")
}

ann, err := sm.LastAnnouncement()
ah, ai, err := node.Store.LastV2AnnouncementHash()
if err != nil {
t.Fatal(err)
} else if ann.Address != expectedAddr {
t.Fatalf("expected address %q, got %q", expectedAddr, ann.Address)
} else if ann.Index != index {
t.Fatalf("expected index %q, got %q", index, ann.Index)
}
}

// helper that mines blocks and waits for them to be processed before mining
// the next one. This is necessary because test blocks can be extremely fast
// and the host may not have time to process the broadcast before the next
// block is mined.
mineAndSync := func(t *testing.T, numBlocks int) {
t.Helper()

// waits for each block to be processed before mining the next one
for i := 0; i < numBlocks; i++ {
testutil.MineBlocks(t, node, wm.Address(), 1)
testutil.WaitForSync(t, node.Chain, idx)
h := types.NewHasher()
types.EncodeSlice(h.E, addresses)
if err := h.E.Flush(); err != nil {
t.Fatal(err)
}
expectedHash := h.Sum()
if ah != expectedHash {
t.Fatalf("expected hash %v, got %v", expectedHash, ah)
} else if ai != index {
t.Fatalf("expected index %v, got %v", index, ai)
}
}

// trigger an auto-announce
mineAndSync(t, 2)
assertAnnouncement(t, "foo.bar:1234", 152)
// fund the wallet and trigger the first auto-announce
mineAndSync(t, 146)
assertAnnouncement(t, v2AnnounceAddresses, 146) // 144 (first maturity height) + 1 (funds available) + 1 (confirmation)
// mine until the next announcement and confirm it
mineAndSync(t, 51)
assertAnnouncement(t, "foo.bar:1234", 203) // 152 (first confirm) + 50 (interval) + 1 (confirmation)
assertAnnouncement(t, v2AnnounceAddresses, 197) // 146 (first confirm) + 50 (interval) + 1 (confirmation)

// change the address
settings.NetAddress = "baz.qux:5678"
sm.UpdateSettings(settings)
v2AnnounceAddresses[1].Address = "baz.qux:5678"
sm, err = settings.NewConfigManager(hostKey, node.Store, node.Chain, node.Syncer, wm, storage, settings.WithLog(log.Named("settings")), settings.WithAnnounceInterval(50), settings.WithRHP4AnnounceAddresses(v2AnnounceAddresses))
if err != nil {
t.Fatal(err)
}
defer sm.Close()

// trigger and confirm the new announcement
mineAndSync(t, 2)
assertAnnouncement(t, "baz.qux:5678", 205)
assertAnnouncement(t, v2AnnounceAddresses, 199)
}
9 changes: 9 additions & 0 deletions host/settings/options.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package settings

import (
"go.sia.tech/coreutils/chain"
"go.uber.org/zap"
)

Expand Down Expand Up @@ -46,3 +47,11 @@ func WithInitialSettings(settings Settings) Option {
c.initialSettings = settings
}
}

// WithRHP4AnnounceAddresses sets the addresses to announce on the blockchain
// for RHP4.
func WithRHP4AnnounceAddresses(addresses []chain.NetAddress) Option {
return func(c *ConfigManager) {
c.rhp4AnnounceAddresses = addresses
}
}
Loading

0 comments on commit 1f3c894

Please sign in to comment.