Transaction

7237961D9E413E…D9CB19E5F8CF

Block 654,252 · index 0 · RPC

Summary

Hash
7237961D9E413EA214C6727194715E662B26785973AC5A9FEBF9D9CB19E5F8CF
Block
654,252
Size
18397 bytes
Gas used
29,027,842 / 100,000,000
Fee
10000000 ugnot
Status
success

Messages

#1AddPackagegno.land/r/nym-vikbez000/nightsky/v111 arguments

Arguments · 11

  1. #1nightsky
  2. #2admin.gno
  3. #3package nightsky import ( "gno.land/p/nt/avl/v0" "gno.land/p/nt/ownable/v0" nightsky "gno.land/p/nym-vikbez000/nightsky/v1" ) // OwnableMain holds the network admin. Ownable is safe to export as a // top-level object: its authority-mutating methods verify the caller against // the stored owner, so exporting it does not grant write access. var OwnableMain = ownable.NewWithAddress("g13kytw9mpyutwmyg5eq7arqxqcszfl6uq4p89zg") // RemoveTelescope allows the admin to remove a telescope from the network. // pkgPath is the telescope realm's package path (the registry key). func RemoveTelescope(cur realm, pkgPath string) { assertAdmin(cur) if _, removed := telescopes.Remove(pkgPath); !removed { panic("telescope not found") } } // ClearAllTelescopes removes all telescopes (emergency use only) func ClearAllTelescopes(cur realm) { assertAdmin(cur) telescopes = avl.NewTree() } // ClearCaptures clears the recent captures feed func ClearCaptures(cur realm) { assertAdmin(cur) recentCaptures = nightsky.NewCaptureFeed(maxRecentCaptures) } // RemoveCapture removes a single capture by its position in the newest-first // feed (0 = most recent), for moderating an individual entry. func RemoveCapture(cur realm, index int) { assertAdmin(cur) if !recentCaptures.RemoveAt(index) { panic("invalid capture index") } } // assertAdmin panics unless the calling EOA is the network admin. These are // crossing functions, so cur.Previous() is the statically-scoped immediate // caller and the AssertOwnedBy check is sound — no chain/runtime/unsafe needed. func assertAdmin(cur realm) { OwnableMain.AssertOwnedBy(cur.Previous().Address()) }
  4. #4gnomod.toml
  5. #5module = "gno.land/r/nym-vikbez000/nightsky/v1" gno = "0.9" private = false
  6. #6nightsky.gno
  7. #7package nightsky import ( "chain" "strings" "time" "gno.land/p/moul/md/v0" "gno.land/p/nt/avl/v0" "gno.land/p/nt/ufmt/v0" nightsky "gno.land/p/nym-vikbez000/nightsky/v1" "gno.land/r/sys/users" ) // maxRecentCaptures is the number of captures kept in the network-wide feed. const maxRecentCaptures = 50 var ( // Registry of all telescopes in the network, keyed by realm pkgPath. // An avl.Tree is used over a map: the registry grows over time, only the // search path is loaded on access, and iteration is in deterministic // (sorted) key order — important for stable Render output. telescopes = avl.NewTree() // Recent captures from all telescopes (bounded, newest-first). recentCaptures = nightsky.NewCaptureFeed(maxRecentCaptures) ) // Register registers a telescope realm in the NightSky network. // Call from your realm's init() function: // // nightsky.Register(cross(cur), config) func Register(cur realm, cfg nightsky.TelescopeConfig) { submission := cur.Previous() pkgPath := submission.PkgPath() if submission.IsUser() { panic("Register must be called from realm code, not a user transaction") } if telescopes.Has(pkgPath) { return // already registered, silently ignore duplicate } if cfg.Name == "" || cfg.Model == "" || cfg.Owner == "" { panic("telescope name, model, and owner cannot be empty") } cfg.RealmPath = pkgPath telescopes.Set(pkgPath, cfg) chain.Emit("TelescopeRegistered", "pkgPath", pkgPath, "name", cfg.Name, "owner", cfg.Owner) } // UpdateTelescopeStatus allows a registered telescope realm to update its status in the registry. // Call from your realm: nightsky.UpdateTelescopeStatus(cross(cur), "online") func UpdateTelescopeStatus(cur realm, status string) { submission := cur.Previous() pkgPath := submission.PkgPath() if submission.IsUser() { panic("must be called from realm code") } config := mustGetTelescope(pkgPath) config.UpdateStatus(status) telescopes.Set(pkgPath, config) chain.Emit("StatusUpdate", "pkgPath", pkgPath, "status", status) } // SubmitCapture allows a registered telescope realm to add a capture to the network feed. // Call from your realm: nightsky.SubmitCapture(cross(cur), imageURL, ra, dec, exposure, capturedBy) func SubmitCapture(cur realm, imageURL string, targetRA, targetDec float64, exposure int, capturedBy string) { submission := cur.Previous() pkgPath := submission.PkgPath() if submission.IsUser() { panic("must be called from realm code") } config := mustGetTelescope(pkgPath) if imageURL == "" { panic("image URL cannot be empty") } recentCaptures.Add(nightsky.CaptureResult{ ImageURL: imageURL, TargetRA: targetRA, TargetDec: targetDec, Exposure: exposure, CapturedBy: capturedBy, CapturedAt: time.Now(), TelescopeID: config.Name, }) chain.Emit("NetworkCapture", "pkgPath", pkgPath, "telescope", config.Name, "imageURL", imageURL, "capturedBy", capturedBy) } // UnregisterTelescope removes a telescope realm from the network. // Call from your realm: nightsky.UnregisterTelescope(cross(cur)) func UnregisterTelescope(cur realm) { submission := cur.Previous() pkgPath := submission.PkgPath() if submission.IsUser() { panic("must be called from realm code") } if _, removed := telescopes.Remove(pkgPath); !removed { panic("telescope not registered") } chain.Emit("TelescopeUnregistered", "pkgPath", pkgPath) } // mustGetTelescope returns the config for pkgPath or panics if not registered. func mustGetTelescope(pkgPath string) nightsky.TelescopeConfig { if !telescopes.Has(pkgPath) { panic("telescope not registered") } return telescopes.Get(pkgPath).(nightsky.TelescopeConfig) } // GetTelescopeCount returns the number of registered telescopes func GetTelescopeCount() int { return telescopes.Size() } // GetOnlineTelescopeCount returns the number of online telescopes func GetOnlineTelescopeCount() int { count := 0 telescopes.Iterate("", "", func(_ string, value any) bool { if value.(nightsky.TelescopeConfig).Status == "online" { count++ } return false }) return count } func usernameOrAddressLink(addr string) string { user := users.ResolveAddress(address(addr)) if user != nil { return user.RenderLink("") } return addr } func Render(path string) string { if path == "telescopes" { return renderTelescopeList() } if path == "captures" { return renderRecentCaptures() } if path == "map" { return renderTelescopeMap() } return renderMainPage() } func renderMainPage() string { out := "" out += md.H1("Gno NightSky 🔭") out += "Decentralized telescope network for stargazing\n\n" out += "Connect your smart telescope to the Gno.land blockchain and share access to the stars!\n\n" out += md.H2("Network Status") totalTelescopes := GetTelescopeCount() onlineTelescopes := GetOnlineTelescopeCount() out += ufmt.Sprintf("🔭 Total Telescopes: %d\n\n", totalTelescopes) out += ufmt.Sprintf("🟢 Online: %d\n\n", onlineTelescopes) out += ufmt.Sprintf("🔴 Offline: %d\n\n", totalTelescopes-onlineTelescopes) out += md.H2("Quick Links") out += md.Link("View All Telescopes", "/r/nym-vikbez000/nightsky/v1:telescopes") + "\n\n" out += md.Link("Recent Captures", "/r/nym-vikbez000/nightsky/v1:captures") + "\n\n" out += md.Link("Telescope Map", "/r/nym-vikbez000/nightsky/v1:map") + "\n\n" if latest, ok := recentCaptures.Latest(); ok { out += md.H2("Latest Capture") out += "![Latest capture](" + latest.ImageURL + ")\n\n" out += ufmt.Sprintf("_RA %.8fh · Dec %.8f° · %ds · %s_\n\n", latest.TargetRA, latest.TargetDec, latest.Exposure, latest.TelescopeID) } out += md.HorizontalRule() out += md.H2("How to Register Your Telescope") out += "Deploy your own telescope realm using the `/p/nym-vikbez000/nightsky/v1` package,\n" out += "then call `nightsky.Register(cross(cur), ...)` from your `init()` function.\n\n" out += "You can clone " + md.Link("my telescope realm", "/r/nym-vikbez000/telescope") + " as a starting point.\n\n" out += md.H2("About") out += "NightSky enables telescope owners to share access to their equipment through a decentralized network.\n\n" out += "Also take a look at " + md.Link("PiaGno", "https://github.com/gnoverse/piagno") + " 🎹\n\n" return out } func renderTelescopeList() string { out := "" out += md.H1("NightSky - Telescope Network") if telescopes.Size() == 0 { out += "\nNo telescopes registered yet.\n\n" out += md.Link("← Back to main page", "/r/nym-vikbez000/nightsky/v1") + "\n\n" return out } out += ufmt.Sprintf("\nTotal: %d telescopes\n\n", telescopes.Size()) telescopes.Iterate("", "", func(_ string, value any) bool { config := value.(nightsky.TelescopeConfig) statusIcon := "🔴" switch config.Status { case "online": statusIcon = "🟢" case "busy": statusIcon = "🟡" case "error": statusIcon = "🔴" } out += md.H3(statusIcon + " " + config.Name) out += ufmt.Sprintf("**Model:** %s\n\n", config.Model) out += ufmt.Sprintf("**Owner:** %s\n\n", usernameOrAddressLink(config.Owner)) out += ufmt.Sprintf("**Location:** %.4f°, %.4f°\n\n", config.Latitude, config.Longitude) out += ufmt.Sprintf("**Status:** %s\n\n", config.Status) out += ufmt.Sprintf("**Realm:** %s\n\n", md.Link("Visit", strings.TrimPrefix(config.RealmPath, "gno.land"))) out += md.HorizontalRule() return false }) out += md.Link("← Back to main page", "/r/nym-vikbez000/nightsky/v1") + "\n\n" return out } func renderRecentCaptures() string { out := "" out += md.H1("NightSky - Recent Captures") if recentCaptures.Len() == 0 { out += "\nNo captures yet.\n\n" out += md.Link("← Back to main page", "/r/nym-vikbez000/nightsky/v1") + "\n\n" return out } out += ufmt.Sprintf("\nShowing %d most recent captures\n\n", recentCaptures.Len()) i := 0 recentCaptures.IterateNewest(func(capture nightsky.CaptureResult) bool { i++ out += md.H3(ufmt.Sprintf("Capture #%d", i)) out += ufmt.Sprintf("**Telescope:** %s\n\n", capture.TelescopeID) out += ufmt.Sprintf("**Captured by:** %s\n\n", usernameOrAddressLink(capture.CapturedBy)) out += ufmt.Sprintf("**Target:** RA %.8fh, Dec %.8f°\n\n", capture.TargetRA, capture.TargetDec) out += ufmt.Sprintf("**Exposure:** %d seconds\n\n", capture.Exposure) out += ufmt.Sprintf("**Image:** %s\n\n", md.Link("View", capture.ImageURL)) out += md.HorizontalRule() return false }) out += md.Link("← Back to main page", "/r/nym-vikbez000/nightsky/v1") + "\n\n" return out } func renderTelescopeMap() string { out := "" out += md.H1("NightSky - Telescope Map") out += "\n📍 Interactive map coming soon!\n\n" out += "For now, here are the telescope locations:\n\n" if telescopes.Size() == 0 { out += "No telescopes registered yet.\n\n" } else { telescopes.Iterate("", "", func(_ string, value any) bool { config := value.(nightsky.TelescopeConfig) out += ufmt.Sprintf("- **%s**: %.4f°, %.4f° (%s)\n\n", config.Name, config.Latitude, config.Longitude, config.Status, ) return false }) } out += md.Link("← Back to main page", "/r/nym-vikbez000/nightsky/v1") + "\n\n" return out }
  8. #8nightsky_test.gno
  9. #9package nightsky import ( "testing" "gno.land/p/nt/avl/v0" "gno.land/p/nt/testutils/v0" "gno.land/p/nt/uassert/v0" "gno.land/p/nt/urequire/v0" nightsky "gno.land/p/nym-vikbez000/nightsky/v1" ) const scopeA = "gno.land/r/test/scopea" func reset() { telescopes = avl.NewTree() recentCaptures = nightsky.NewCaptureFeed(maxRecentCaptures) } func cfg(name, owner string) nightsky.TelescopeConfig { return nightsky.NewTelescopeConfig(name, "Seestar S50", 1, 2, owner, "") } // registerAs simulates a telescope code realm at pkgPath crossing into the // registry to register itself. func registerAs(cur realm, pkgPath string, c nightsky.TelescopeConfig) { testing.SetRealm(testing.NewCodeRealm(pkgPath)) Register(cross(cur), c) } func TestRegisterAndDedup(cur realm, t *testing.T) { reset() registerAs(cur, scopeA, cfg("Scope A", "g1owner")) urequire.Equal(t, 1, GetTelescopeCount()) // RealmPath is stamped from the caller's pkgPath, not the (empty) input. urequire.True(t, telescopes.Has(scopeA)) stored := telescopes.Get(scopeA) uassert.Equal(t, scopeA, stored.(nightsky.TelescopeConfig).RealmPath) // Duplicate registration from the same realm is ignored. registerAs(cur, scopeA, cfg("Scope A", "g1owner")) uassert.Equal(t, 1, GetTelescopeCount()) // A second telescope from a different realm is added. registerAs(cur, "gno.land/r/test/scopeb", cfg("Scope B", "g1owner2")) uassert.Equal(t, 2, GetTelescopeCount()) } func TestRegisterRejectsUserCall(cur realm, t *testing.T) { reset() // A direct user transaction (no intermediary realm) must be rejected. testing.SetRealm(testing.NewUserRealm(testutils.TestAddress("eoa"))) uassert.AbortsContains(t, cur, "realm code", func() { Register(cross(cur), cfg("Bad", "g1owner")) }) uassert.Equal(t, 0, GetTelescopeCount()) } func TestOnlineCountAndStatus(cur realm, t *testing.T) { reset() c := cfg("Scope A", "g1owner") c.UpdateStatus("online") registerAs(cur, scopeA, c) uassert.Equal(t, 1, GetOnlineTelescopeCount()) // The telescope realm updates its status to offline. testing.SetRealm(testing.NewCodeRealm(scopeA)) UpdateTelescopeStatus(cross(cur), "offline") uassert.Equal(t, 0, GetOnlineTelescopeCount()) uassert.Equal(t, 1, GetTelescopeCount()) } func TestSubmitCaptureFeed(cur realm, t *testing.T) { reset() registerAs(cur, scopeA, cfg("Scope A", "g1owner")) testing.SetRealm(testing.NewCodeRealm(scopeA)) SubmitCapture(cross(cur), "https://img/1.png", 5, 10, 30, "g1owner") SubmitCapture(cross(cur), "https://img/2.png", 6, 11, 40, "g1owner") latest, ok := recentCaptures.Latest() urequire.True(t, ok) uassert.Equal(t, "https://img/2.png", latest.ImageURL) uassert.Equal(t, 2, recentCaptures.Len()) } func TestAdminRemoveAndAuth(cur realm, t *testing.T) { reset() registerAs(cur, scopeA, cfg("Scope A", "g1owner")) urequire.Equal(t, 1, GetTelescopeCount()) // Non-admin cannot remove. testing.SetRealm(testing.NewUserRealm(testutils.TestAddress("intruder"))) uassert.AbortsContains(t, cur, "not owner", func() { RemoveTelescope(cross(cur), scopeA) }) uassert.Equal(t, 1, GetTelescopeCount()) // Admin can remove. testing.SetRealm(testing.NewUserRealm(OwnableMain.Owner())) RemoveTelescope(cross(cur), scopeA) uassert.Equal(t, 0, GetTelescopeCount()) // Removing a missing telescope aborts. uassert.AbortsContains(t, cur, "not found", func() { RemoveTelescope(cross(cur), scopeA) }) } // --- Adversarial ("steal a telescope") tests ------------------------------- // An attacker realm or EOA tries to hijack, impersonate, or wipe telescopes // that are not theirs. const ( victimPath = "gno.land/r/victim/scope" attackerPath = "gno.land/r/attacker/evil" ) // TestAttackerRealmCannotTouchVictim: registry mutations are keyed by the // caller's own pkgPath (cur.Previous().PkgPath()). An unregistered attacker // realm therefore cannot update, capture-to, or unregister a victim's entry — // it only ever addresses its own (nonexistent) registration. func TestAttackerRealmCannotTouchVictim(cur realm, t *testing.T) { reset() registerAs(cur, victimPath, cfg("Victim Scope", "g1victim")) urequire.Equal(t, 1, GetTelescopeCount()) testing.SetRealm(testing.NewCodeRealm(attackerPath)) uassert.AbortsContains(t, cur, "not registered", func() { UpdateTelescopeStatus(cross(cur), "error") }) uassert.AbortsContains(t, cur, "not registered", func() { SubmitCapture(cross(cur), "https://evil/forged.png", 1, 1, 10, "g1victim") }) uassert.AbortsContains(t, cur, "not registered", func() { UnregisterTelescope(cross(cur)) }) // Victim's entry is untouched. urequire.True(t, telescopes.Has(victimPath)) stored := telescopes.Get(victimPath) uassert.Equal(t, "Victim Scope", stored.(nightsky.TelescopeConfig).Name) uassert.Equal(t, 1, GetTelescopeCount()) } // TestAttackerCannotUnregisterVictim: even when the attacker is itself // registered, UnregisterTelescope removes only the caller's own entry — it // cannot take down a neighbour. func TestAttackerCannotUnregisterVictim(cur realm, t *testing.T) { reset() registerAs(cur, victimPath, cfg("Victim Scope", "g1victim")) registerAs(cur, attackerPath, cfg("Evil Scope", "g1attacker")) urequire.Equal(t, 2, GetTelescopeCount()) testing.SetRealm(testing.NewCodeRealm(attackerPath)) UnregisterTelescope(cross(cur)) // removes the attacker, nothing else uassert.False(t, telescopes.Has(attackerPath)) uassert.True(t, telescopes.Has(victimPath)) // victim survives uassert.Equal(t, 1, GetTelescopeCount()) } // TestAttackerCaptureCannotImpersonateVictim: the network feed stamps each // capture's TelescopeID from the caller's own registration, so an attacker // cannot make a forged image appear to come from the victim's telescope — // regardless of the capturedBy value they pass. func TestAttackerCaptureCannotImpersonateVictim(cur realm, t *testing.T) { reset() registerAs(cur, victimPath, cfg("Victim Scope", "g1victim")) registerAs(cur, attackerPath, cfg("Evil Scope", "g1attacker")) testing.SetRealm(testing.NewCodeRealm(attackerPath)) SubmitCapture(cross(cur), "https://evil/forged.png", 1, 1, 10, "g1victim") latest, ok := recentCaptures.Latest() urequire.True(t, ok) uassert.Equal(t, "Evil Scope", latest.TelescopeID) // not "Victim Scope" } // TestNonAdminCannotUseAdminPowers: the network-admin functions reject anyone // who is not the ownable owner, so an intruder cannot wipe the registry or the // capture feed. func TestNonAdminCannotUseAdminPowers(cur realm, t *testing.T) { reset() registerAs(cur, victimPath, cfg("Victim Scope", "g1victim")) urequire.Equal(t, 1, GetTelescopeCount()) testing.SetRealm(testing.NewUserRealm(testutils.TestAddress("intruder"))) uassert.AbortsContains(t, cur, "not owner", func() { RemoveTelescope(cross(cur), victimPath) }) uassert.AbortsContains(t, cur, "not owner", func() { ClearAllTelescopes(cross(cur)) }) uassert.AbortsContains(t, cur, "not owner", func() { RemoveCapture(cross(cur), 0) }) // Nothing was removed. uassert.Equal(t, 1, GetTelescopeCount()) }
  10. #10/gno.MemPackageType
  11. #11 MPUserAll

Result log

msg:0,success:true,log:,events:[]