-
Notifications
You must be signed in to change notification settings - Fork 7
feat: add Trust Factor app #17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 15 commits
31a7f31
41b4c40
bcf2234
725b171
478298e
7d8f21e
4a9fa29
76a631c
81ae662
fc0bba1
5731942
391b45d
b699819
0c9d450
cb20391
30a25b4
40542fb
07fcf45
0a34fc4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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() | ||
| } | ||
|
|
||
| // 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() | ||
| } | ||
|
gfanton marked this conversation as resolved.
Outdated
|
||
| 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) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. See
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 { | ||
|
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 | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| package trustfactor | ||
|
|
||
| import ( | ||
| "time" | ||
|
|
||
|
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 { | ||
|
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() | ||
| } | ||
| 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 { | ||
|
gfanton marked this conversation as resolved.
Outdated
|
||
| result := make(map[address]TrustScore) | ||
| trustScores.Iterate("", "", func(key string, value interface{}) bool { | ||
|
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{} | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| module = "gno.land/r/greg007/trustfactor" | ||
| gno = "0.9" |
Uh oh!
There was an error while loading. Please reload this page.