Skip to content

Commit

Permalink
Merge pull request #129 from SiaFoundation/v2
Browse files Browse the repository at this point in the history
V2 changes
  • Loading branch information
n8maninger authored Nov 21, 2024
2 parents fc0681d + 8f52f3c commit 79c8cfd
Show file tree
Hide file tree
Showing 24 changed files with 3,547 additions and 673 deletions.
27 changes: 26 additions & 1 deletion api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ func (c *Client) Contract(id types.FileContractID) (resp explorer.ExtendedFileCo
return
}

// Contracts returns the transactions with the specified IDs.
// Contracts returns the contracts with the specified IDs.
func (c *Client) Contracts(ids []types.FileContractID) (resp []explorer.ExtendedFileContract, err error) {
err = c.c.POST("/contracts", ids, &resp)
return
Expand All @@ -210,6 +210,31 @@ func (c *Client) ContractRevisions(id types.FileContractID) (resp []explorer.Ext
return
}

// V2Contract returns the v2 file contract with the specified ID.
func (c *Client) V2Contract(id types.FileContractID) (resp explorer.V2FileContract, err error) {
err = c.c.GET(fmt.Sprintf("/v2/contracts/%s", id), &resp)
return
}

// V2Contracts returns the v2 contracts with the specified IDs.
func (c *Client) V2Contracts(ids []types.FileContractID) (resp []explorer.V2FileContract, err error) {
err = c.c.POST("/v2/contracts", ids, &resp)
return
}

// V2ContractRevisions returns all the revisions of the contract with the
// specified ID.
func (c *Client) V2ContractRevisions(id types.FileContractID) (resp []explorer.V2FileContract, err error) {
err = c.c.GET(fmt.Sprintf("/v2/contracts/%s/revisions", id), &resp)
return
}

// V2ContractsKey returns the v2 contracts for a particular ed25519 key.
func (c *Client) V2ContractsKey(key types.PublicKey) (resp []explorer.V2FileContract, err error) {
err = c.c.GET(fmt.Sprintf("/v2/pubkey/%s/contracts", key), &resp)
return
}

// Host returns information about the host with a given ed25519 key.
func (c *Client) Host(key types.PublicKey) (resp explorer.Host, err error) {
err = c.c.GET(fmt.Sprintf("/pubkey/%s/host", key), &resp)
Expand Down
72 changes: 72 additions & 0 deletions api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ type (
Contracts(ids []types.FileContractID) (result []explorer.ExtendedFileContract, err error)
ContractsKey(key types.PublicKey) (result []explorer.ExtendedFileContract, err error)
ContractRevisions(id types.FileContractID) (result []explorer.ExtendedFileContract, err error)
V2Contracts(ids []types.FileContractID) (result []explorer.V2FileContract, err error)
V2ContractsKey(key types.PublicKey) (result []explorer.V2FileContract, err error)
V2ContractRevisions(id types.FileContractID) (result []explorer.V2FileContract, err error)
Search(id types.Hash256) (explorer.SearchType, error)

Hosts(pks []types.PublicKey) ([]explorer.Host, error)
Expand Down Expand Up @@ -490,6 +493,7 @@ func (s *server) outputsSiafundHandler(jc jape.Context) {

jc.Encode(outputs[0])
}

func (s *server) contractsIDHandler(jc jape.Context) {
var id types.FileContractID
if jc.DecodeParam("id", &id) != nil {
Expand Down Expand Up @@ -537,6 +541,68 @@ func (s *server) contractsBatchHandler(jc jape.Context) {
jc.Encode(fcs)
}

func (s *server) v2ContractsIDHandler(jc jape.Context) {
var id types.FileContractID
if jc.DecodeParam("id", &id) != nil {
return
}
fcs, err := s.e.V2Contracts([]types.FileContractID{id})
if jc.Check("failed to get contract", err) != nil {
return
} else if len(fcs) == 0 {
jc.Error(explorer.ErrContractNotFound, http.StatusNotFound)
return
}
jc.Encode(fcs[0])
}

func (s *server) v2ContractsBatchHandler(jc jape.Context) {
var ids []types.FileContractID
if jc.Decode(&ids) != nil {
return
} else if len(ids) > maxIDs {
jc.Error(ErrTooManyIDs, http.StatusBadRequest)
return
}

fcs, err := s.e.V2Contracts(ids)
if jc.Check("failed to get contracts", err) != nil {
return
}
jc.Encode(fcs)
}

func (s *server) v2ContractsIDRevisionsHandler(jc jape.Context) {
var id types.FileContractID
if jc.DecodeParam("id", &id) != nil {
return
}

fcs, err := s.e.V2ContractRevisions(id)
if errors.Is(err, explorer.ErrContractNotFound) {
jc.Error(fmt.Errorf("%w: %v", err, id), http.StatusNotFound)
return
} else if jc.Check("failed to fetch contract revisions", err) != nil {
return
}
jc.Encode(fcs)
}

func (s *server) v2PubkeyContractsHandler(jc jape.Context) {
var key types.PublicKey
if jc.DecodeParam("key", &key) != nil {
return
}
fcs, err := s.e.V2ContractsKey(key)
if jc.Check("failed to get contracts", err) != nil {
return
} else if len(fcs) == 0 {
jc.Error(explorer.ErrContractNotFound, http.StatusNotFound)
return
}
jc.Encode(fcs)
}

func (s *server) pubkeyContractsHandler(jc jape.Context) {
var key types.PublicKey
if jc.DecodeParam("key", &key) != nil {
Expand Down Expand Up @@ -635,6 +701,12 @@ func NewServer(e Explorer, cm ChainManager, s Syncer) http.Handler {
"GET /contracts/:id/revisions": srv.contractsIDRevisionsHandler,
"POST /contracts": srv.contractsBatchHandler,

"GET /v2/contracts/:id": srv.v2ContractsIDHandler,
"GET /v2/contracts/:id/revisions": srv.v2ContractsIDRevisionsHandler,
"POST /v2/contracts": srv.v2ContractsBatchHandler,

"GET /v2/pubkey/:key/contracts": srv.v2PubkeyContractsHandler,

"GET /pubkey/:key/contracts": srv.pubkeyContractsHandler,
"GET /pubkey/:key/host": srv.pubkeyHostHandler,

Expand Down
11 changes: 5 additions & 6 deletions explorer/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
21 changes: 20 additions & 1 deletion explorer/explorer.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,14 @@ type Store interface {
Contracts(ids []types.FileContractID) (result []ExtendedFileContract, err error)
ContractsKey(key types.PublicKey) (result []ExtendedFileContract, err error)
ContractRevisions(id types.FileContractID) (result []ExtendedFileContract, err error)
V2Contracts(ids []types.FileContractID) (result []V2FileContract, err error)
V2ContractsKey(key types.PublicKey) (result []V2FileContract, err error)
V2ContractRevisions(id types.FileContractID) (result []V2FileContract, err error)
SiacoinElements(ids []types.SiacoinOutputID) (result []SiacoinOutput, err error)
SiafundElements(ids []types.SiafundOutputID) (result []SiafundOutput, err error)

Hosts(pks []types.PublicKey) ([]Host, error)
HostsForScanning(maxLastScan, minLastAnnouncement time.Time, offset, limit uint64) ([]chain.HostAnnouncement, error)
HostsForScanning(maxLastScan, minLastAnnouncement time.Time, offset, limit uint64) ([]Host, error)
}

// Explorer implements a Sia explorer.
Expand Down Expand Up @@ -262,6 +265,22 @@ func (e *Explorer) ContractRevisions(id types.FileContractID) (result []Extended
return e.s.ContractRevisions(id)
}

// V2Contracts returns the v2 contracts with the specified IDs.
func (e *Explorer) V2Contracts(ids []types.FileContractID) (result []V2FileContract, err error) {
return e.s.V2Contracts(ids)
}

// V2ContractsKey returns the v2 contracts for a particular ed25519 key.
func (e *Explorer) V2ContractsKey(key types.PublicKey) (result []V2FileContract, err error) {
return e.s.V2ContractsKey(key)
}

// V2ContractRevisions returns all the revisions of the v2 contract with the
// specified ID.
func (e *Explorer) V2ContractRevisions(id types.FileContractID) (result []V2FileContract, err error) {
return e.s.V2ContractRevisions(id)
}

// SiacoinElements returns the siacoin elements with the specified IDs.
func (e *Explorer) SiacoinElements(ids []types.SiacoinOutputID) (result []SiacoinOutput, err error) {
return e.s.SiacoinElements(ids)
Expand Down
78 changes: 69 additions & 9 deletions explorer/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (

crhpv2 "go.sia.tech/core/rhp/v2"
"go.sia.tech/core/types"
"go.sia.tech/coreutils/chain"
crhpv4 "go.sia.tech/coreutils/rhp/v4"
"go.sia.tech/explored/internal/geoip"
rhpv2 "go.sia.tech/explored/internal/rhp/v2"
rhpv3 "go.sia.tech/explored/internal/rhp/v3"
Expand Down Expand Up @@ -52,11 +52,12 @@ func (e *Explorer) waitForSync() error {
return nil
}

func (e *Explorer) scanHost(locator geoip.Locator, host chain.HostAnnouncement) (HostScan, error) {
func (e *Explorer) scanV1Host(locator geoip.Locator, host Host) (HostScan, error) {
ctx, cancel := context.WithTimeout(e.ctx, e.scanCfg.Timeout)
defer cancel()

dialer := (&net.Dialer{})

conn, err := dialer.DialContext(ctx, "tcp", host.NetAddress)
if err != nil {
return HostScan{}, fmt.Errorf("scanHost: failed to connect to host: %w", err)
Expand All @@ -80,7 +81,6 @@ func (e *Explorer) scanHost(locator geoip.Locator, host chain.HostAnnouncement)
}

resolved, err := net.ResolveIPAddr("ip", hostIP)
// if we can resolve the address
if err != nil {
return HostScan{}, fmt.Errorf("scanHost: failed to resolve host address: %w", err)
}
Expand Down Expand Up @@ -113,7 +113,53 @@ func (e *Explorer) scanHost(locator geoip.Locator, host chain.HostAnnouncement)
}, nil
}

func (e *Explorer) addHostScans(hosts chan chain.HostAnnouncement) {
func (e *Explorer) scanV2Host(locator geoip.Locator, host Host) (HostScan, error) {
ctx, cancel := context.WithTimeout(e.ctx, e.scanCfg.Timeout)
defer cancel()

addr, ok := host.V2SiamuxAddr()
if !ok {
return HostScan{}, fmt.Errorf("host has no v2 siamux address")
}

transport, err := crhpv4.DialSiaMux(ctx, addr, host.PublicKey)
if err != nil {
return HostScan{}, fmt.Errorf("failed to dial host: %w", err)
}
defer transport.Close()

settings, err := crhpv4.RPCSettings(ctx, transport)
if err != nil {
return HostScan{}, fmt.Errorf("failed to get host settings: %w", err)
}

hostIP, _, err := net.SplitHostPort(addr)
if err != nil {
return HostScan{}, fmt.Errorf("scanHost: failed to parse net address: %w", err)
}

resolved, err := net.ResolveIPAddr("ip", hostIP)
if err != nil {
return HostScan{}, fmt.Errorf("scanHost: failed to resolve host address: %w", err)
}

countryCode, err := locator.CountryCode(resolved)
if err != nil {
e.log.Debug("Failed to resolve IP geolocation, not setting country code", zap.String("addr", host.NetAddress))
countryCode = ""
}

return HostScan{
PublicKey: host.PublicKey,
CountryCode: countryCode,
Success: true,
Timestamp: types.CurrentTimestamp(),

RHPV4Settings: settings,
}, nil
}

func (e *Explorer) addHostScans(hosts chan Host) {
// use default included ip2location database
locator, err := geoip.NewIP2LocationLocator("")
if err != nil {
Expand All @@ -129,18 +175,32 @@ func (e *Explorer) addHostScans(hosts chan chain.HostAnnouncement) {
break
}

scan, err := e.scanHost(locator, host)
var scan HostScan
var addr string
var ok bool
var err error

if host.IsV2() {
addr, ok = host.V2SiamuxAddr()
if !ok {
e.log.Debug("Host did not have any v2 siamux net addresses in its announcement, unable to scan", zap.Stringer("pk", host.PublicKey))
continue
}
scan, err = e.scanV2Host(locator, host)
} else {
scan, err = e.scanV1Host(locator, host)
}
if err != nil {
scans = append(scans, HostScan{
PublicKey: host.PublicKey,
Success: false,
Timestamp: types.CurrentTimestamp(),
})
e.log.Debug("Scanning host failed", zap.String("addr", host.NetAddress), zap.Stringer("pk", host.PublicKey), zap.Error(err))
e.log.Debug("Scanning host failed", zap.String("addr", addr), zap.Stringer("pk", host.PublicKey), zap.Error(err))
continue
}

e.log.Debug("Scanning host succeeded", zap.String("addr", host.NetAddress), zap.Stringer("pk", host.PublicKey))
e.log.Debug("Scanning host succeeded", zap.String("addr", addr), zap.Stringer("pk", host.PublicKey))
scans = append(scans, scan)
}

Expand Down Expand Up @@ -172,7 +232,7 @@ func (e *Explorer) isClosed() bool {
}
}

func (e *Explorer) fetchHosts(hosts chan chain.HostAnnouncement) {
func (e *Explorer) fetchHosts(hosts chan Host) {
var exhausted bool
offset := 0

Expand Down Expand Up @@ -210,7 +270,7 @@ func (e *Explorer) scanHosts() {

for !e.isClosed() {
// fetch hosts
hosts := make(chan chain.HostAnnouncement, scanBatchSize)
hosts := make(chan Host, scanBatchSize)
e.wg.Add(1)
go func() {
defer e.wg.Done()
Expand Down
Loading

0 comments on commit 79c8cfd

Please sign in to comment.