Skip to content
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions packages/r/greg007/TrustFactor/admin.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package trustfactor

import "strings"

// =============================================================================
// ADMIN FUNCTIONS (OWNER ONLY)
// =============================================================================

// RemoveUser removes a malicious user from the system (emergency function)
func RemoveUser(target address) {
owner.AssertOwnedByCurrent()

trustScores.Remove(string(target))
voteHistory.Remove(string(target))

// Clean up all votes involving the target user
keysToRemove := []string{}
userVotes.Iterate("", "", func(key string, value interface{}) bool {
if strings.Contains(key, string(target)) {
keysToRemove = append(keysToRemove, key)
}
return false
})
for _, key := range keysToRemove {
userVotes.Remove(key)
}

totalUsers--
invalidateCache()
}
Comment thread
gfanton marked this conversation as resolved.

// TransferOwnership transfers contract ownership to a new address
func TransferOwnership(newOwner address) error {
return owner.TransferOwnership(newOwner)
}

// DropOwnership removes the owner, effectively disabling all admin functions
func DropOwnership() error {
return owner.DropOwnershipByCurrent()
}

// Owner returns the current owner address
func Owner() address {
return owner.Owner()
}
Comment thread
gfanton marked this conversation as resolved.
Outdated
144 changes: 144 additions & 0 deletions packages/r/greg007/TrustFactor/cache.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package trustfactor

import "time"

// =============================================================================
// PERFORMANCE OPTIMIZATION - CACHING & SORTING
// =============================================================================

// invalidateCache clears the sorted user cache when data changes
func invalidateCache() {
lastSortTime = 0
sortedUsers = make([]address, 0)
}

// GetSortedUsers returns users sorted by composite score with caching
func GetSortedUsers() []address {
now := time.Now().Unix()

// Check if cache is still valid
if now-lastSortTime < SORT_CACHE_TTL && len(sortedUsers) == totalUsers {
return sortedUsers
}

// Rebuild cache if expired or invalid
return rebuildSortCache()
}

// rebuildSortCache rebuilds the sorted user cache using optimized quicksort
func rebuildSortCache() []address {
// Convert AVL tree to slice for sorting
users := make([]address, 0, trustScores.Size())
trustScores.Iterate("", "", func(key string, value interface{}) bool {
users = append(users, address(key))
return false
})

// Quicksort by composite score (descending)
quickSortByScore(users, 0, len(users)-1)

// Update cache
sortedUsers = users
lastSortTime = time.Now().Unix()
totalUsers = len(users)

return sortedUsers
}

// quickSortByScore implements quicksort algorithm for user addresses by composite score
func quickSortByScore(users []address, low, high int) {
if low < high {
pi := partitionByScore(users, low, high)
quickSortByScore(users, low, pi-1)
quickSortByScore(users, pi+1, high)
}
}

// partitionByScore partitions the array for quicksort (descending order)
func partitionByScore(users []address, low, high int) int {
pivotValue, _ := trustScores.Get(string(users[high]))
pivotScore := calculateCompositeScore(*pivotValue.(*TrustScore))
i := low - 1

for j := low; j < high; j++ {
currentValue, _ := trustScores.Get(string(users[j]))
currentScore := calculateCompositeScore(*currentValue.(*TrustScore))
if currentScore > pivotScore {
i++
users[i], users[j] = users[j], users[i]
}
}

users[i+1], users[high] = users[high], users[i+1]
return i + 1
}

// =============================================================================
// SEARCH AND FILTERING
// =============================================================================

// FilterUsers returns paginated and filtered user results
func FilterUsers(searchTerm string, limit int, offset int) ([]address, int) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check if you can use p/nt/avl/pager for paging in this case instead of writing your own pager

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't, because I'm currently using an array and not a tree. If I switch to a tree, I think it will unnecessarily complicate the code.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not only about paging. In Gno, every global variable is persisted to storage after each transaction. If you maintain a sorted slice, any update requires reallocating and rewriting the entire slice to storage. With 100k users, that's 100k storage writes per update.
Instead, use an AVL tree keyed by score. AVL trees maintain lexicographic order automatically.

See gno.land/r/leon/hor for a pattern using padded score keys for numeric sorting.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if searchTerm == "" {
sorted := GetSortedUsers()
total := len(sorted)

end := offset + limit
if end > total {
end = total
}
if offset > total {
return []address{}, total
}

return sorted[offset:end], total
}

var filtered []address
for _, addr := range GetSortedUsers() {
addrStr := string(addr)
if containsSubstring(addrStr, searchTerm) {
filtered = append(filtered, addr)
}
}

total := len(filtered)
end := offset + limit
if end > total {
end = total
}
if offset > total {
return []address{}, total
}

return filtered[offset:end], total
}

// containsSubstring checks if a string contains a substring (case-sensitive)
func containsSubstring(str, substr string) bool {
Comment thread
gfanton marked this conversation as resolved.
if len(substr) == 0 {
return true
}
if len(substr) > len(str) {
return false
}

for i := 0; i <= len(str)-len(substr); i++ {
match := true
for j := 0; j < len(substr); j++ {
if str[i+j] != substr[j] {
match = false
break
}
}
if match {
return true
}
}
return false
}

// GetPerformanceStats returns cache and performance statistics
func GetPerformanceStats() (int, int, int64) {
return totalUsers, len(sortedUsers), lastSortTime
}
85 changes: 85 additions & 0 deletions packages/r/greg007/TrustFactor/comments.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package trustfactor

import (
"time"

Comment thread
gfanton marked this conversation as resolved.
Outdated
"chain/runtime"

"gno.land/p/nt/avl"
)

// =============================================================================
// COMMENT FUNCTIONS
// =============================================================================

// AddComment adds a comment to a user's profile
func AddComment(cur realm, target address, text string) {
caller := runtime.OriginCaller()

// Verify caller is registered
if !trustScores.Has(string(caller)) {
panic("commenter must be registered")
}

// Verify target exists
if !trustScores.Has(string(target)) {
panic("target user not registered")
}

// Validate comment text
if len(text) == 0 {
panic("comment text cannot be empty")
}
if len(text) > 500 {
panic("comment text too long (max 500 characters)")
}

// Initialize AVL tree for target if not exists
var targetTree *avl.Tree
if treeValue, exists := userComments.Get(string(target)); exists {
targetTree = treeValue.(*avl.Tree)
} else {
targetTree = avl.NewTree()
userComments.Set(string(target), targetTree)
}

// Create comment
comment := Comment{
Author: caller,
Text: text,
Timestamp: time.Now().Unix(),
}

// Use timestamp + author as unique key
key := string(comment.Timestamp) + "_" + string(caller)
targetTree.Set(key, comment)
}

// GetComments returns all comments for a user's profile
func GetComments(target address) []Comment {
treeValue, exists := userComments.Get(string(target))
if !exists {
return []Comment{}
}
tree := treeValue.(*avl.Tree)

comments := make([]Comment, 0)
tree.ReverseIterate("", "", func(key string, value interface{}) bool {
Comment thread
gfanton marked this conversation as resolved.
Outdated
if comment, ok := value.(Comment); ok {
comments = append(comments, comment)
}
return false
})

return comments
}

// GetCommentCount returns the number of comments for a user
func GetCommentCount(target address) int {
treeValue, exists := userComments.Get(string(target))
if !exists {
return 0
}
tree := treeValue.(*avl.Tree)
return tree.Size()
}
56 changes: 56 additions & 0 deletions packages/r/greg007/TrustFactor/getters.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package trustfactor

// =============================================================================
// BASIC GETTER FUNCTIONS
// =============================================================================

// GetTrustScore returns the raw trust score for a user
func GetTrustScore(addr address) (float64, bool) {
value, exists := trustScores.Get(string(addr))
if !exists {
return 0.0, false
}
trust := value.(*TrustScore)
return trust.Score, true
}

// GetCompositeScore returns the calculated composite score (with time decay and confidence)
func GetCompositeScore(addr address) (float64, bool) {
value, exists := trustScores.Get(string(addr))
if !exists {
return 0.0, false
}
trust := value.(*TrustScore)
return calculateCompositeScore(*trust), true
}

// GetTrustDetails returns the complete TrustScore struct for a user
func GetTrustDetails(addr address) TrustScore {
value, exists := trustScores.Get(string(addr))
if !exists {
return TrustScore{Score: 0, LastUpdate: 0, Evaluator: ""}
}
return *value.(*TrustScore)
}

// =============================================================================
// DATA ACCESS FUNCTIONS
// =============================================================================

// GetAllUsers returns the complete trust scores map (used internally)
func GetAllUsers() map[address]TrustScore {
Comment thread
gfanton marked this conversation as resolved.
Outdated
result := make(map[address]TrustScore)
trustScores.Iterate("", "", func(key string, value interface{}) bool {
Comment thread
gfanton marked this conversation as resolved.
Outdated
result[address(key)] = *value.(*TrustScore)
return false
})
return result
}

// GetVoteHistory returns the voting history for a specific user
func GetVoteHistory(addr address) []VoteHistory {
if historyValue, exists := voteHistory.Get(string(addr)); exists {
return historyValue.([]VoteHistory)
}
return []VoteHistory{}
}
2 changes: 2 additions & 0 deletions packages/r/greg007/TrustFactor/gnomod.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
module = "gno.land/r/greg007/trustfactor"
gno = "0.9"
Loading
Loading