diff --git a/framework/libra-framework/sources/ol_sources/tests/boundary.test.move b/framework/libra-framework/sources/ol_sources/tests/boundary.test.move index 3aa210406..a1257e019 100644 --- a/framework/libra-framework/sources/ol_sources/tests/boundary.test.move +++ b/framework/libra-framework/sources/ol_sources/tests/boundary.test.move @@ -107,109 +107,6 @@ module ol_framework::test_boundary { assert!(transaction_fee::system_fees_collected() == 9018000, 7357006); } - // #[test(root = @ol_framework, alice = @0x1000a, marlon_rando = @0x12345)] - // fun e2e_add_validator_happy(root: signer, alice: signer, marlon_rando: signer) { - // let initial_vals = common_test_setup(&root); - - // // generate credentials for validator registration - // ol_account::transfer(&alice, @0x12345, 200000); - // let (_sk, pk, pop) = stake::generate_identity(); - // let pk_bytes = bls12381::public_key_to_bytes(&pk); - // let pop_bytes = bls12381::proof_of_possession_to_bytes(&pop); - // validator_universe::register_validator(&marlon_rando, pk_bytes, pop_bytes, b"123", b"abc"); - - // // MARLON PAYS THE BID - // let vals = validator_universe::get_eligible_validators(); - // assert!(vector::length(&vals) == 11, 7357001); - - // mock::mock_bids(&vals); - - // // MARLON HAS MANY FRIENDS - // vouch::test_set_received_list(@0x12345, vals); - - // let (errs, _pass) = proof_of_fee::audit_qualification(@0x12345); - // assert!(vector::length(&errs) == 0, 7357002); - - // // get initial vals balance - // let balances = vector::map(initial_vals, |addr| { - // let (_unlocked, total) = ol_account::balance(addr); - // total - // }); - - // mock::trigger_epoch(&root); - - // assert!(epoch_boundary::get_reconfig_success(), 7357003); - - // // musical chairs recommended 11 seats BUT... - // assert!(epoch_boundary::get_max_seats_offered() == 11, 7357004); - - // // there were only 11 bidders for the 11 offered, which is not competitive enough - // assert!(epoch_boundary::get_qualified_bidders() == 11, 7357004); - - // // NOTE: now MARLON is INCLUDED in this, and we filled all the seats on offer. - // // all vals had winning bids, but it was less than the seats on offer - // let win = epoch_boundary::get_auction_winners(); - // // print(&vector::length(&win)); - - - // assert!(vector::length(&win) == 10, 7357005); - - // // // all of the auction winners became the validators ulitmately - // // assert!(vector::length(&epoch_boundary::get_actual_vals()) == 11, 7357006); - - // // // check initial vals rewards received and bid fees collected - // // // previous balance = current - reward + fee - // // let i = 0; - // // while (i < vector::length(&initial_vals)) { - // // let (_unlocked, current) = ol_account::balance(*vector::borrow(&initial_vals, i)); - // // let previous = *vector::borrow(&balances, i); - // // assert!(current == (previous + 1_000_000 - 1_000), 7357007); - // // i = i + 1; - // // }; - - // // // check Marlon's balance: 200_000 - 1_000 = 199_000 - // // let (_unlocked, marlon_balance) = ol_account::balance(@0x12345); - // // assert!(marlon_balance == 199_000, 7357008); - - // // // check subsidy for new rewards and fees collected - // // // fees collected = 11 * 1_000_000 + 11 * 1_000 = 11_011_000 - // // assert!(transaction_fee::system_fees_collected() == 11_011_000, 7357009); - - // // // another epoch and everyone is compliant as well - // // mock::mock_all_vals_good_performance(&root); - // // mock::trigger_epoch(&root); - - // // assert!(epoch_boundary::get_max_seats_offered() == 12, 7357010); - // // assert!(vector::length(&epoch_boundary::get_actual_vals()) == 11, 7357011); - - // // // check initial vals rewards received and bid fees collected - // // // previous balance = current - 2*reward + 2*fee - // // let i = 0; - // // while (i < vector::length(&initial_vals)) { - // // let (_unlocked, current) = ol_account::balance(*vector::borrow(&initial_vals, i)); - // // let previous = *vector::borrow(&balances, i); - // // assert!(current == (previous + 2_000_000 - 2_000), 7357012); - // // i = i + 1; - // // }; - - // // // check Marlon's balance: 200_000 + 1_000_000 - 2_000 = 1_198_000 - // // let (_unlocked, marlon_balance) = ol_account::balance(@0x12345); - // // assert!(marlon_balance == 1_198_000, 7357013); - - // // // CHECK BURNT FEES - - // // // prepare clean epoch - // // mock::trigger_epoch(&root); - - // // let (before, _) = burn::get_lifetime_tracker(); - - // // mock::trigger_epoch(&root); - - // // // check that only the entry fee sum is being burnt - // // let (after,_) = burn::get_lifetime_tracker(); - // // assert!(after - before == 11_000_000, 7357014); // scale 1_000 - // } - #[test(root = @ol_framework, alice = @0x1000a, marlon_rando = @0x12345)] fun e2e_competitive_set(root: signer, alice: signer, marlon_rando: signer) { diff --git a/framework/libra-framework/sources/ol_sources/tests/donor_voice.test.move b/framework/libra-framework/sources/ol_sources/tests/donor_voice.test.move index 402e626f5..ba1f6aace 100644 --- a/framework/libra-framework/sources/ol_sources/tests/donor_voice.test.move +++ b/framework/libra-framework/sources/ol_sources/tests/donor_voice.test.move @@ -132,6 +132,7 @@ module ol_framework::test_donor_voice { let (_unlocked_dv, dv_balance) = ol_account::balance(donor_voice_address); let (_, marlon_balance) = ol_account::balance(marlon_rando); + // TODO: add assert here diem_std::debug::print(&dv_balance); diem_std::debug::print(&marlon_balance); diff --git a/framework/libra-framework/sources/ol_sources/tests/page_rank.test.move b/framework/libra-framework/sources/ol_sources/tests/page_rank.test.move index 3e3a8cdc5..1a79a3b2b 100644 --- a/framework/libra-framework/sources/ol_sources/tests/page_rank.test.move +++ b/framework/libra-framework/sources/ol_sources/tests/page_rank.test.move @@ -344,7 +344,7 @@ module ol_framework::test_page_rank { #[test(framework = @ol_framework)] /// Tests that users don't gain trust score from roots vouching for each other. - /// Demonstrates that the root check in page_rank_lazy::walk_from_node prevents + /// Demonstrates that the root checks in the page rank algorithm prevent /// score accumulation through root-to-root vouches, ensuring that the trust /// system isn't exploitable through root account manipulation. fun test_no_accumulation_from_root_vouches(framework: &signer) { @@ -458,27 +458,440 @@ module ol_framework::test_page_rank { }; } + #[test(framework = @ol_framework)] + /// Tests the diminishing power of trust as the number of hops increases. + /// confirms that the score remains the same even + /// if prior users have neighbors that lead nowhere + fun test_diminishing_power_with_neighbors(framework: &signer) { + // Set up the test base with 10 root accounts + let roots_sig = test_base(framework); + + // create 10 root of trust accounts + let users_sig = mock::create_test_end_users(framework, 10, 11); + let users_addr = mock::collect_addresses(&users_sig); + + let decoy_users_sig = mock::create_test_end_users(framework, 10, 21); + let decoy_users_addr = mock::collect_addresses(&decoy_users_sig); + + + // First hop: the root vouches for user1 (at first hop) + let grantor_sig = vector::borrow(&roots_sig, 0); + let root0_score = page_rank_lazy::get_trust_score(signer::address_of(grantor_sig)); + // in a simple initialization root doesn't have any vouches themselves + assert!(root0_score == 0, 7357100); + + let max_single_score = page_rank_lazy::get_max_single_score(); + + let prev_score = max_single_score * 2; + let prev_vouch = 20; + // loop through the users and have the previous + // user vouch for the next user + let i = 0; + while (i < vector::length(&users_sig)) { + let beneficiary_addr = vector::borrow(&users_addr, i); + let beneficiary_sig = vector::borrow(&users_sig, i); + + vouch::init(beneficiary_sig); + page_rank_lazy::maybe_initialize_trust_record(beneficiary_sig); + + let grantor_acc = signer::address_of(grantor_sig); + vouch::test_set_received_list(grantor_acc, decoy_users_addr); + vouch::test_set_given_list(grantor_acc, decoy_users_addr); + + vouch::vouch_for(grantor_sig, *beneficiary_addr); + + + let user1_score = page_rank_lazy::get_trust_score(*beneficiary_addr); + + // in a simple initialization root doesn't have any vouches themselves + if (prev_score > 0) { + assert!(user1_score < prev_score, 7357101); + if (user1_score > 0) { + assert!(user1_score == prev_score/2, 7357102); + }; + }; + + let vouch_limit = vouch_limits::calculate_score_limit(*beneficiary_addr); + + // in a simple initialization root doesn't have any vouches themselves + if (prev_vouch > 1) { + assert!(vouch_limit < prev_vouch, 7357101); + }; + + // prep next loop + prev_score = user1_score; + prev_vouch = vouch_limit; + grantor_sig = vector::borrow(&users_sig, i); + i = i + 1; + }; + } + + #[test(framework = @ol_framework)] + /// Simple path + // Root0 -> Alice + // Alice -> Bob + // Bob -> Carol + fun simple_path(framework: &signer) { + // Set up the test base + let roots_sig = test_base(framework); + + let root0 = vector::borrow(&roots_sig, 0); + vouch::init(root0); + + let alice_sig = mock::create_user_from_u64(framework, 11); + let alice_addr = signer::address_of(&alice_sig); + vouch::init(&alice_sig); + page_rank_lazy::maybe_initialize_trust_record(&alice_sig); + + let bob_sig = mock::create_user_from_u64(framework, 12); + let bob_addr = signer::address_of(&bob_sig); + vouch::init(&bob_sig); + page_rank_lazy::maybe_initialize_trust_record(&bob_sig); + + let carol_sig = mock::create_user_from_u64(framework, 13); + let carol_addr = signer::address_of(&carol_sig); + vouch::init(&carol_sig); + page_rank_lazy::maybe_initialize_trust_record(&carol_sig); + + let carol_score_pre = page_rank_lazy::get_trust_score(carol_addr); + assert!(carol_score_pre == 0, 7357001); + + // Both roots vouch for alice + vouch_txs::vouch_for(root0, alice_addr); + vouch_txs::vouch_for(&alice_sig, bob_addr); + vouch_txs::vouch_for(&bob_sig, carol_addr); + + let (carol_score_post, _, _) = page_rank_lazy::calculate_score(carol_addr); + assert!(carol_score_post == 25_000, 7357002); + } + + #[test(framework = @ol_framework)] + /// A full cycle of vouches, outside of root of trust + // Root0 -> Alice -> Bob -> Carol -> Alice + fun cycle_pattern_single(framework: &signer) { + // Set up the test base + let roots_sig = test_base(framework); + + let root0 = vector::borrow(&roots_sig, 0); + vouch::init(root0); + + let alice_sig = mock::create_user_from_u64(framework, 11); + let alice_addr = signer::address_of(&alice_sig); + vouch::init(&alice_sig); + page_rank_lazy::maybe_initialize_trust_record(&alice_sig); + + let bob_sig = mock::create_user_from_u64(framework, 12); + let bob_addr = signer::address_of(&bob_sig); + vouch::init(&bob_sig); + page_rank_lazy::maybe_initialize_trust_record(&bob_sig); + + let carol_sig = mock::create_user_from_u64(framework, 13); + let carol_addr = signer::address_of(&carol_sig); + vouch::init(&carol_sig); + page_rank_lazy::maybe_initialize_trust_record(&carol_sig); + + let carol_score_pre = page_rank_lazy::get_trust_score(carol_addr); + assert!(carol_score_pre == 0, 7357001); + + // Both roots vouch for alice + vouch_txs::vouch_for(root0, alice_addr); + vouch_txs::vouch_for(&alice_sig, bob_addr); + vouch_txs::vouch_for(&bob_sig, carol_addr); + // close the cycle + vouch_txs::vouch_for(&carol_sig, alice_addr); + + // no change from single path + let (carol_score_post, _, _) = page_rank_lazy::calculate_score(carol_addr); + assert!(carol_score_post == 25_000, 7357002); + + // alice does not get score from downstream users + // TODO: is this the spec? + let (alice_score_post, _, _) = page_rank_lazy::calculate_score(alice_addr); + assert!(alice_score_post == 100_000, 7357002); + } + + #[test(framework = @ol_framework)] + /// A full cycle of vouches, outside of root of trust + // Root0 -> Alice -> Bob -> Carol -> Root0 + fun cycle_pattern_with_root(framework: &signer) { + // Set up the test base + let roots_sig = test_base(framework); + + let root0 = vector::borrow(&roots_sig, 0); + vouch::init(root0); + + let alice_sig = mock::create_user_from_u64(framework, 11); + let alice_addr = signer::address_of(&alice_sig); + vouch::init(&alice_sig); + page_rank_lazy::maybe_initialize_trust_record(&alice_sig); + + let bob_sig = mock::create_user_from_u64(framework, 12); + let bob_addr = signer::address_of(&bob_sig); + vouch::init(&bob_sig); + page_rank_lazy::maybe_initialize_trust_record(&bob_sig); + + let carol_sig = mock::create_user_from_u64(framework, 13); + let carol_addr = signer::address_of(&carol_sig); + vouch::init(&carol_sig); + page_rank_lazy::maybe_initialize_trust_record(&carol_sig); + + let carol_score_pre = page_rank_lazy::get_trust_score(carol_addr); + assert!(carol_score_pre == 0, 7357001); + + // Both roots vouch for alice + vouch_txs::vouch_for(root0, alice_addr); + vouch_txs::vouch_for(&alice_sig, bob_addr); + vouch_txs::vouch_for(&bob_sig, carol_addr); + // close the cycle to root0 + vouch_txs::vouch_for(&carol_sig, signer::address_of(root0)); + + // no change from single path + let (carol_score_post, _, _) = page_rank_lazy::calculate_score(carol_addr); + assert!(carol_score_post == 25_000, 7357002); + + // alice score does not increment with additional loop from + // TODO: is this the spec? + let (alice_score_post, _, _) = page_rank_lazy::calculate_score(alice_addr); + diem_std::debug::print(&alice_score_post); + assert!(alice_score_post == 100_000, 7357002); + } + + #[test(framework = @ol_framework)] + // A full cycle of vouches, outside of root of trust + // Root0 -> Alice -> Bob -> Carol -> Alice + // Bob -> Dave -> Alice + fun cycle_pattern_double(framework: &signer) { + // Set up the test base + let roots_sig = test_base(framework); + + let root0 = vector::borrow(&roots_sig, 0); + vouch::init(root0); + + let alice_sig = mock::create_user_from_u64(framework, 11); + let alice_addr = signer::address_of(&alice_sig); + vouch::init(&alice_sig); + page_rank_lazy::maybe_initialize_trust_record(&alice_sig); + + let bob_sig = mock::create_user_from_u64(framework, 12); + let bob_addr = signer::address_of(&bob_sig); + vouch::init(&bob_sig); + page_rank_lazy::maybe_initialize_trust_record(&bob_sig); + + let carol_sig = mock::create_user_from_u64(framework, 13); + let carol_addr = signer::address_of(&carol_sig); + vouch::init(&carol_sig); + page_rank_lazy::maybe_initialize_trust_record(&carol_sig); + + let dave_sig = mock::create_user_from_u64(framework, 14); + let dave_addr = signer::address_of(&dave_sig); + vouch::init(&dave_sig); + page_rank_lazy::maybe_initialize_trust_record(&dave_sig); + + + let carol_score_pre = page_rank_lazy::get_trust_score(carol_addr); + assert!(carol_score_pre == 0, 7357001); + + // Both roots vouch for alice + vouch_txs::vouch_for(root0, alice_addr); + vouch_txs::vouch_for(&alice_sig, bob_addr); + vouch_txs::vouch_for(&bob_sig, carol_addr); + // close the cycle to root0 + vouch_txs::vouch_for(&carol_sig, alice_addr); + + // second cycle + vouch::vouch_for(&bob_sig, dave_addr); // note: don't count toward limits + // close the cycle to root0 + vouch_txs::vouch_for(&dave_sig, alice_addr); + + // no change from single path + let (carol_score_post, _, _) = page_rank_lazy::calculate_score(carol_addr); + assert!(carol_score_post == 25_000, 7357002); + + // alice score does not increment with additional loop from + // TODO: is this the spec? + let (alice_score_post, _, _) = page_rank_lazy::calculate_score(alice_addr); + assert!(alice_score_post == 100_000, 7357002); + } #[test(framework = @ol_framework)] /// When a vouch branch occurs, /// and then merges back to a user, /// the transitive scores should accumulate - fun diamond_pattern_accumulates(framework: &signer) { + // Root0 -> Alice + // Root0 -> Bob + // Alice -> Carol + // Bob -> Carol + fun diamond_pattern(framework: &signer) { // Set up the test base let roots_sig = test_base(framework); + let root0 = vector::borrow(&roots_sig, 0); + vouch::init(root0); + let alice_sig = mock::create_user_from_u64(framework, 11); let alice_addr = signer::address_of(&alice_sig); vouch::init(&alice_sig); - // Initialize page rank for the new user page_rank_lazy::maybe_initialize_trust_record(&alice_sig); + let bob_sig = mock::create_user_from_u64(framework, 12); + let bob_addr = signer::address_of(&bob_sig); + vouch::init(&bob_sig); + page_rank_lazy::maybe_initialize_trust_record(&bob_sig); + + let carol_sig = mock::create_user_from_u64(framework, 13); + let carol_addr = signer::address_of(&carol_sig); + vouch::init(&carol_sig); + page_rank_lazy::maybe_initialize_trust_record(&carol_sig); + + let carol_score_pre = page_rank_lazy::get_trust_score(carol_addr); + assert!(carol_score_pre == 0, 7357001); + + // Root 0 vouches for alice and bob + vouch_txs::vouch_for(root0, alice_addr); + vouch_txs::vouch_for(root0, bob_addr); + + // alice and bob vouch for carol + vouch_txs::vouch_for(&alice_sig, carol_addr); + vouch_txs::vouch_for(&bob_sig, carol_addr); + + let (carol_score_post, _, _) = page_rank_lazy::calculate_score(carol_addr); + diem_std::debug::print(&carol_score_post); + assert!(carol_score_post == 100_000, 7357002); + } + + #[test(framework = @ol_framework)] + /// One side of the diamond pattern is longer + // Root0 -> Alice + // Root0 -> Bob + // Alice -> Dave + // Bob -> Carol -> Dave + fun diamond_pattern_asymmetrical(framework: &signer) { + // Set up the test base + let roots_sig = test_base(framework); + let root0 = vector::borrow(&roots_sig, 0); vouch::init(root0); + + let alice_sig = mock::create_user_from_u64(framework, 11); + let alice_addr = signer::address_of(&alice_sig); + vouch::init(&alice_sig); + page_rank_lazy::maybe_initialize_trust_record(&alice_sig); + + let bob_sig = mock::create_user_from_u64(framework, 12); + let bob_addr = signer::address_of(&bob_sig); + vouch::init(&bob_sig); + page_rank_lazy::maybe_initialize_trust_record(&bob_sig); + + let carol_sig = mock::create_user_from_u64(framework, 13); + let carol_addr = signer::address_of(&carol_sig); + vouch::init(&carol_sig); + page_rank_lazy::maybe_initialize_trust_record(&carol_sig); + + let dave_sig = mock::create_user_from_u64(framework, 14); + let dave_addr = signer::address_of(&dave_sig); + vouch::init(&dave_sig); + page_rank_lazy::maybe_initialize_trust_record(&dave_sig); + + + let dave_score_pre = page_rank_lazy::get_trust_score(dave_addr); + assert!(dave_score_pre == 0, 7357001); + + // Root 0 vouches for alice and bob vouch_txs::vouch_for(root0, alice_addr); + vouch_txs::vouch_for(root0, bob_addr); + + // alice vouches for dave (short path) + vouch_txs::vouch_for(&alice_sig, dave_addr); + + // bob vouches for carol who vouches for dave (long path) + vouch_txs::vouch_for(&bob_sig, carol_addr); + vouch_txs::vouch_for(&carol_sig, dave_addr); + + let (dave_score_post, _, _) = page_rank_lazy::calculate_score(dave_addr); + // expect 50K from alice, and 25K via bob and carol + diem_std::debug::print(&dave_score_post); + assert!(dave_score_post == 75_000, 7357002); + } + + + #[test(framework = @ol_framework)] + /// Order of the diamond pattern is inverted + /// this should have no effect + // Root0 -> Alice + // Root0 -> Bob + // Alice -> Carol -> Dave + // Bob -> Dave + fun diamond_pattern_asymmetrical_inverted(framework: &signer) { + // Set up the test base + let roots_sig = test_base(framework); + + let root0 = vector::borrow(&roots_sig, 0); + vouch::init(root0); + + let alice_sig = mock::create_user_from_u64(framework, 11); + let alice_addr = signer::address_of(&alice_sig); + vouch::init(&alice_sig); + page_rank_lazy::maybe_initialize_trust_record(&alice_sig); + + let bob_sig = mock::create_user_from_u64(framework, 12); + let bob_addr = signer::address_of(&bob_sig); + vouch::init(&bob_sig); + page_rank_lazy::maybe_initialize_trust_record(&bob_sig); + + let carol_sig = mock::create_user_from_u64(framework, 13); + let carol_addr = signer::address_of(&carol_sig); + vouch::init(&carol_sig); + page_rank_lazy::maybe_initialize_trust_record(&carol_sig); + + let dave_sig = mock::create_user_from_u64(framework, 14); + let dave_addr = signer::address_of(&dave_sig); + vouch::init(&dave_sig); + page_rank_lazy::maybe_initialize_trust_record(&dave_sig); + + + let dave_score_pre = page_rank_lazy::get_trust_score(dave_addr); + assert!(dave_score_pre == 0, 7357001); + + // Root 0 vouches for alice and bob + vouch_txs::vouch_for(root0, alice_addr); + vouch_txs::vouch_for(root0, bob_addr); + + // alice vouches for carol who vouches for dave (long path, inverted from above example) + vouch_txs::vouch_for(&alice_sig, carol_addr); + vouch_txs::vouch_for(&carol_sig, dave_addr); + + // bob vouches for dave (short path) + vouch_txs::vouch_for(&bob_sig, dave_addr); + + let (dave_score_post, _, _) = page_rank_lazy::calculate_score(dave_addr); + // expect 50K from alice, and 25K via bob and carol + diem_std::debug::print(&dave_score_post); + assert!(dave_score_post == 75_000, 7357002); + } + + #[test(framework = @ol_framework)] + /// Diamond pattern when alice has two vouches from root + // Root0 -> Alice + // Root0 -> Bob -> Alice + // Root1 -> Alice + fun diamond_pattern_two_roots(framework: &signer) { + // Set up the test base + let roots_sig = test_base(framework); + + let root0 = vector::borrow(&roots_sig, 0); + vouch::init(root0); let root1 = vector::borrow(&roots_sig, 1); vouch::init(root1); + + let alice_sig = mock::create_user_from_u64(framework, 11); + let alice_addr = signer::address_of(&alice_sig); + vouch::init(&alice_sig); + // Initialize page rank for the new user + page_rank_lazy::maybe_initialize_trust_record(&alice_sig); + + // Both roots vouch for alice + vouch_txs::vouch_for(root0, alice_addr); vouch_txs::vouch_for(root1, alice_addr); let (received, _) = vouch::get_received_vouches(alice_addr); @@ -505,9 +918,9 @@ module ol_framework::test_page_rank { vouch_txs::vouch_for(&bob_sig, alice_addr); let page_rank_score_finally = page_rank_lazy::get_trust_score(alice_addr); + diem_std::debug::print(&page_rank_score_finally); // 100k from root0, 100k from root1, 50k from bob assert!(page_rank_score_finally == 250_000, 7357005); - } @@ -560,11 +973,74 @@ module ol_framework::test_page_rank { vouch_txs::vouch_for(&bob_sig, root1_addr); let root1_cached_score = page_rank_lazy::get_cached_score(root1_addr); - diem_std::debug::print(&root1_cached_score); + assert!(root1_cached_score == 25_000, 7357004); + } + + + #[test(framework = @ol_framework)] + /// scenario, the graph reenters the root of trust + /// but it's dead end, code should halt, and score should + /// not be affected by finding a neighbor that's root of trust + /// root0 -> alice -> bob -> root1 + /// -> root2 + /// -> root3 + fun root_score_reduction_reentry(framework: &signer) { + // Set up the test base + let roots_sig = test_base(framework); - diem_std::debug::print(&7777777777777777777); + let root0 = vector::borrow(&roots_sig, 0); + vouch::init(root0); + let root0_addr = signer::address_of(root0); - page_rank_lazy::refresh_cache(root1_addr); - // diem_std::debug::print(&root1_score_post); + let root1 = vector::borrow(&roots_sig, 1); + vouch::init(root1); + let root1_addr = signer::address_of(root1); + + let root2 = vector::borrow(&roots_sig, 2); + vouch::init(root2); + let root2_addr = signer::address_of(root2); + + let root3 = vector::borrow(&roots_sig, 2); + vouch::init(root3); + let root3_addr = signer::address_of(root3); + + ///////// Root 0 score is initially 0 //////// + let root0_score_pre = page_rank_lazy::get_trust_score(root0_addr); + assert!(root0_score_pre == 0, 7357001); + let root1_score_pre = page_rank_lazy::get_trust_score(root1_addr); + assert!(root1_score_pre == 0, 7357002); + ///////// + + // Alice receives a vouch from root0 + let alice_sig = mock::create_user_from_u64(framework, 11); + let alice_addr = signer::address_of(&alice_sig); + vouch::init(&alice_sig); + // Initialize page rank for the new user + page_rank_lazy::maybe_initialize_trust_record(&alice_sig); + + // Alice vouches for bob + let bob_sig = mock::create_user_from_u64(framework, 12); + vouch::init(&bob_sig); + let bob_addr = signer::address_of(&bob_sig); + page_rank_lazy::maybe_initialize_trust_record(&bob_sig); + + vouch_txs::vouch_for(root0, alice_addr); + //////// THE TEST: alice vouches for bob AND a separate root of trust + vouch_txs::vouch_for(&alice_sig, bob_addr); + // don't run the epoch limit checks + vouch::vouch_for(&alice_sig, root2_addr); + vouch::vouch_for(&alice_sig, root3_addr); + + //////// + + let bob_final_score = page_rank_lazy::get_trust_score(bob_addr); + diem_std::debug::print(&bob_final_score); + assert!(bob_final_score == 50_000, 7357003); + + vouch_txs::vouch_for(&bob_sig, root1_addr); + + let root1_cached_score = page_rank_lazy::get_cached_score(root1_addr); + diem_std::debug::print(&root1_cached_score); + assert!(root1_cached_score == 25_000, 7357004); } } diff --git a/framework/libra-framework/sources/ol_sources/tests/vote_lib/multi_action.test.move b/framework/libra-framework/sources/ol_sources/tests/vote_lib/multi_action.test.move index 66e5dd5b6..2c4c6bd4e 100644 --- a/framework/libra-framework/sources/ol_sources/tests/vote_lib/multi_action.test.move +++ b/framework/libra-framework/sources/ol_sources/tests/vote_lib/multi_action.test.move @@ -13,10 +13,7 @@ module ol_framework::test_multi_action { use diem_framework::reconfiguration; use diem_framework::account; - // print - //use std::debug::print; - - struct DummyType has drop, store {} + struct DummyType has drop, store {} #[test(root = @ol_framework, carol = @0x1000c)] fun init_multi_action(root: &signer, carol: &signer) { @@ -36,7 +33,7 @@ module ol_framework::test_multi_action { fun propose_offer(root: &signer, carol: &signer) { mock::genesis_n_vals(root, 4); let carol_address = @0x1000c; - + // check the offer does not exist assert!(!multi_action::exists_offer(carol_address), 7357001); assert!(!multi_action::is_multi_action(carol_address), 7357002); @@ -140,7 +137,7 @@ module ol_framework::test_multi_action { // check the account is multi_action assert!(multi_action::is_multi_action(carol_address), 7357002); - + // check authorities let authorities = multi_action::get_authorities(carol_address); assert!(authorities == vector[@0x1000a, @0x1000b], 7357003); @@ -173,7 +170,7 @@ module ol_framework::test_multi_action { // check the account is multi_action assert!(multi_action::is_multi_action(carol_address), 7357002); - + // check authorities let authorities = multi_action::get_authorities(carol_address); assert!(authorities == vector[@0x1000a, @0x1000b], 7357003); @@ -388,7 +385,7 @@ module ol_framework::test_multi_action { mock::genesis_n_vals(root, 3); let carol_address = @0x1000c; multi_action::init_gov(carol); - + // invite bob multi_action::propose_offer(carol, vector[@0x1000b], option::none()); @@ -402,13 +399,13 @@ module ol_framework::test_multi_action { fun claim_expired_offer(root: &signer, carol: &signer, alice: &signer, bob: &signer) { mock::genesis_n_vals(root, 3); multi_action::init_gov(carol); - + // offer to alice and bob multi_action::propose_offer(carol, vector[@0x1000a, @0x1000b], option::some(2)); // alice claim the offer multi_action::claim_offer(alice, @0x1000c); - + mock::trigger_epoch(root); // epoch 1 valid mock::trigger_epoch(root); // epoch 2 valid mock::trigger_epoch(root); // epoch 3 expired @@ -418,7 +415,7 @@ module ol_framework::test_multi_action { } // Try to claim offer of an account without proposal - #[test(root = @ol_framework, alice = @0x1000a, bob = @0x1000b)] + #[test(root = @ol_framework, alice = @0x1000a, bob = @0x1000b)] #[expected_failure(abort_code = 0x60011, location = ol_framework::multi_action)] fun claim_offer_without_proposal(root: &signer, alice: &signer) { mock::genesis_n_vals(root, 2); @@ -462,7 +459,7 @@ module ol_framework::test_multi_action { fun finalize_without_enough_claimed(root: &signer, alice: &signer, bob: &signer) { mock::genesis_n_vals(root, 3); multi_action::init_gov(alice); - + // offer to bob and carol authority on the alice account multi_action::propose_offer(alice, vector[@0x1000b, @0x1000c], option::none()); @@ -480,7 +477,7 @@ module ol_framework::test_multi_action { mock::genesis_n_vals(root, 3); let alice_address = @0x1000a; multi_action::init_gov(alice); - + // offer bob and carol authority on the alice account multi_action::propose_offer(alice, vector[@0x1000b, @0x1000c], option::none()); @@ -494,7 +491,7 @@ module ol_framework::test_multi_action { } // Governance Tests - + // Happy Day: propose a new action and check zero votes #[test(root = @ol_framework, alice = @0x1000a, bob = @0x1000b, carol = @0x1000c)] fun propose_action(root: &signer, alice: &signer, bob: &signer, carol: &signer) { @@ -505,11 +502,11 @@ module ol_framework::test_multi_action { multi_action::init_gov(alice); multi_action::init_type(alice, true); multi_action::propose_offer(alice, vector[@0x1000b, @0x1000c], option::none()); - + // bob and alice claim the offer multi_action::claim_offer(bob, alice_address); - multi_action::claim_offer(carol, alice_address); - + multi_action::claim_offer(carol, alice_address); + // alice finalize multi action workflow to release control of the account multi_action::finalize_and_cage(alice, 2); @@ -546,8 +543,8 @@ module ol_framework::test_multi_action { // bob and alice claim the offer multi_action::claim_offer(bob, alice_address); - multi_action::claim_offer(carol, alice_address); - + multi_action::claim_offer(carol, alice_address); + // alice finalize multi action workflow to release control of the account multi_action::finalize_and_cage(alice, 2); @@ -590,10 +587,10 @@ module ol_framework::test_multi_action { let alice_address = @0x1000a; multi_action::init_gov(alice); // Ths is a simple multi_action: there is no capability being stored - multi_action::init_type(alice, false); + multi_action::init_type(alice, false); multi_action::propose_offer(alice, vector[@0x1000b, @0x1000c], option::none()); multi_action::claim_offer(bob, alice_address); - multi_action::claim_offer(carol, alice_address); + multi_action::claim_offer(carol, alice_address); multi_action::finalize_and_cage(alice, 2); // bob create a proposal @@ -628,7 +625,7 @@ module ol_framework::test_multi_action { multi_action::init_type(alice, true); multi_action::propose_offer(alice, vector[@0x1000b, @0x1000c], option::none()); multi_action::claim_offer(bob, alice_address); - multi_action::claim_offer(carol, alice_address); + multi_action::claim_offer(carol, alice_address); multi_action::finalize_and_cage(alice, 2); // bob create a proposal and vote @@ -650,7 +647,7 @@ module ol_framework::test_multi_action { assert!(option::is_some(&cap_opt), 7357003); let cap = option::extract(&mut cap_opt); let c = ol_account::withdraw_with_capability(&cap, 42,); - // deposit to erik account + // deposit to erik account ol_account::create_account(root, @0x1000e); ol_account::deposit_coins(@0x1000e, c); option::fill(&mut cap_opt, cap); @@ -681,15 +678,15 @@ module ol_framework::test_multi_action { // fund the account ol_account::transfer(alice, erik_address, 100); - + // offer alice and bob authority on the safe safe::init_payment_multisig(&erik, vector[@0x1000a, @0x1000b]); // both need to sign multi_action::claim_offer(alice, erik_address); - multi_action::claim_offer(bob, erik_address); + multi_action::claim_offer(bob, erik_address); multi_action::finalize_and_cage(&erik, 2); // make a proposal for governance, expires in 2 epoch from now - let id = multi_action::propose_governance(alice, erik_address, vector::empty(), true, + let id = multi_action::propose_governance(alice, erik_address, vector::empty(), true, option::some(1), option::some(2)); mock::trigger_epoch(root); // epoch 1 @@ -702,7 +699,7 @@ module ol_framework::test_multi_action { // trying to vote on a closed ballot will error let _passed = multi_action::vote_governance(bob, erik_address, &id); - } + } // Happy day: change the authorities of a multisig #[test(root = @ol_framework, alice = @0x1000a, bob = @0x1000b, carol = @0x1000c, dave = @0x1000d)] @@ -715,7 +712,7 @@ module ol_framework::test_multi_action { mock::ol_initialize_coin_and_fund_vals(root, 10000000, true); let carol_address = @0x1000c; let dave_address = @0x1000d; - + // fund the account ol_account::transfer(alice, carol_address, 100); // offer alice and bob authority on the safe @@ -724,7 +721,7 @@ module ol_framework::test_multi_action { let authorities = vector[@0x1000a, @0x1000b]; multi_action::propose_offer(carol, authorities, option::none()); multi_action::claim_offer(alice, carol_address); - multi_action::claim_offer(bob, carol_address); + multi_action::claim_offer(bob, carol_address); multi_action::finalize_and_cage(carol, 2); // alice is going to propose to change the authorities to add dave and increase the threshold to 3 @@ -786,7 +783,7 @@ module ol_framework::test_multi_action { // Happy day: change the threshold of a multisig #[test(root = @ol_framework, alice = @0x1000a, bob = @0x1000b, carol = @0x1000c, dave = @0x1000d)] fun governance_change_threshold(root: &signer, alice: &signer, bob: &signer, carol: &signer, dave: &signer) { - // Scenario: The multisig gets initiated with the 2 bob and carol as the only authorities. + // Scenario: The multisig gets initiated with the 2 bob and carol as the only authorities. // It takes 2-of-2 to sign. // They decide next only 1-of-2 will be needed. // Then they decide to invite dave and make it 3-of-3. @@ -807,12 +804,12 @@ module ol_framework::test_multi_action { multi_action::init_type(&resource_sig, false); multi_action::propose_offer(&resource_sig, vector[@0x1000b, @0x1000c], option::none()); multi_action::claim_offer(carol, new_resource_address); - multi_action::claim_offer(bob, new_resource_address); + multi_action::claim_offer(bob, new_resource_address); multi_action::finalize_and_cage(&resource_sig, 2); // carol is going to propose to change the threshold to 1 let id = multi_action::propose_governance(carol, new_resource_address, vector::empty(), true, option::some(1), option::none()); - + // check authorities and threshold let authorities = multi_action::get_authorities(new_resource_address); assert!(authorities == vector[@0x1000c, @0x1000b], 7357002); // no change @@ -844,7 +841,7 @@ module ol_framework::test_multi_action { // now bob decide to invite dave and make it 3-of-3. multi_action::propose_governance(bob, new_resource_address, vector[signer::address_of(dave)], true, option::some(3), option::none()); - + // check authorities and threshold did not change let authorities = multi_action::get_authorities(new_resource_address); assert!(vector::length(&authorities) == 2, 7357011); @@ -892,10 +889,10 @@ module ol_framework::test_multi_action { // carol is going to propose to change the authorities to add dave let id = multi_action::propose_governance(carol, alice_address, vector[@0x1000d], true, option::none(), option::none()); - + // bob votes and dave does not claims the offer multi_action::vote_governance(bob, alice_address, &id); - + // check authorities and threshold assert!(multi_action::get_authorities(alice_address) == vector[@0x1000b, @0x1000c], 7357001); let (n, _m) = multi_action::get_threshold(alice_address); @@ -930,7 +927,7 @@ module ol_framework::test_multi_action { #[expected_failure(abort_code = 0x3000C, location = ol_framework::multi_action)] fun vote_action_twice(root: &signer, alice: &signer, bob: &signer, carol: &signer) { // Scenario: Testing that a vote cannot be done twice on the same ballot. - + mock::genesis_n_vals(root, 4); mock::ol_initialize_coin_and_fund_vals(root, 10000000, true); let carol_address = @0x1000c; @@ -942,11 +939,11 @@ module ol_framework::test_multi_action { multi_action::init_type(carol, true); multi_action::propose_offer(carol, vector[@0x1000a, @0x1000b], option::none()); multi_action::claim_offer(alice, carol_address); - multi_action::claim_offer(bob, carol_address); + multi_action::claim_offer(bob, carol_address); multi_action::finalize_and_cage(carol, 2); // alice is going to propose to change the authorities to add dave - let id = multi_action::propose_governance(alice, carol_address, + let id = multi_action::propose_governance(alice, carol_address, vector[dave_address], true, option::none(), option::none()); // bob votes @@ -973,7 +970,7 @@ module ol_framework::test_multi_action { // carol is going to propose to change the authorities to add dave let id = multi_action::propose_governance(carol, alice_address, vector[@0xCAFE], true, option::none(), option::none()); - + // bob votes and dave does not claims the offer multi_action::vote_governance(bob, alice_address, &id); } @@ -993,7 +990,7 @@ module ol_framework::test_multi_action { multi_action::finalize_and_cage(alice, 2); // carol is going to propose to change the authorities to add dave twice - let _id = multi_action::propose_governance(carol, alice_address, + let _id = multi_action::propose_governance(carol, alice_address, vector[@0x1000d, @0x1000d], true, option::none(), option::none()); } @@ -1048,13 +1045,13 @@ module ol_framework::test_multi_action { multi_action::finalize_and_cage(alice, 2); // carol is going to propose to remove dave - let _id = multi_action::propose_governance(carol, alice_address, + let _id = multi_action::propose_governance(carol, alice_address, vector[@0x1000d], false, option::none(), option::none()); } // Two governance proposals at the same time #[test(root = @ol_framework, alice = @0x1000a, bob = @0x1000b, carol = @0x1000c, dave = @0x1000d, eve = @0x1000e)] - fun two_simultaneous_governance_vote(root: &signer, alice: &signer, bob: &signer, carol: &signer, dave: &signer, eve: &signer) { + fun two_simultaneous_governance_vote(root: &signer, alice: &signer, bob: &signer, carol: &signer, dave: &signer, eve: &signer) { mock::genesis_n_vals(root, 5); let alice_address = @0x1000a; @@ -1067,7 +1064,7 @@ module ol_framework::test_multi_action { // carol is going to propose to change the authorities to add dave let id_set_n_3 = multi_action::propose_governance(carol, alice_address, vector[@0x1000d], true, option::some(3), option::none()); - + // bob is going to propose to change the authorities to add erik let id_set_n_1 = multi_action::propose_governance(bob, alice_address, vector[@0x1000e], true, option::some(1), option::none()); diff --git a/framework/libra-framework/sources/ol_sources/vouch_lib/benchmarks.md b/framework/libra-framework/sources/ol_sources/vouch_lib/benchmarks.md new file mode 100644 index 000000000..7d891161d --- /dev/null +++ b/framework/libra-framework/sources/ol_sources/vouch_lib/benchmarks.md @@ -0,0 +1,24 @@ +## forward walk +#### from roots to target + +| Depth | Score | Max Depth Reached | Accounts Processed | +|------:|----------:|------------------:|-------------------:| +| 2 | 0 | 2 | 28 | +| 3 | 100000 | 3 | 87 | +| 4 | 250000 | 4 | 324 | +| 5 | 587500 | 5 | 1368 | +| 6 | EXECUTION_LIMIT_REACHED | | | + +--- + +## reverse walk +#### from target to roots of trust + +| Depth | Score | Max Depth Reached | Accounts Processed | +|------:|----------:|------------------:|-------------------:| +| 2 | 0 | 2 | 4 | +| 3 | 100000 | 3 | 12 | +| 4 | 250000 | 4 | 49 | +| 5 | 562500 | 5 | 217 | +| 6 | 981250 | 6 | 1235 | +| 7 | EXECUTION_LIMIT_REACHED | - | - | diff --git a/framework/libra-framework/sources/ol_sources/vouch_lib/distance_to_root.move b/framework/libra-framework/sources/ol_sources/vouch_lib/distance_to_root.move index 412788cb7..981d721ac 100644 --- a/framework/libra-framework/sources/ol_sources/vouch_lib/distance_to_root.move +++ b/framework/libra-framework/sources/ol_sources/vouch_lib/distance_to_root.move @@ -12,6 +12,7 @@ module ol_framework::distance_to_root { // Error codes const ENOT_INITIALIZED: u64 = 4; + const EMAX_PROCESSED_ADDRESSES: u64 = 5; // Per-user trust record - each user stores their own trust data struct ShortestPathRecord has key, drop { @@ -102,9 +103,7 @@ module ol_framework::distance_to_root { while (!vector::is_empty(&queue)) { // Circuit breaker - if (processed_count >= MAX_PROCESSED_ADDRESSES) { - return (false, 0) // Path not found within processing limits - }; + assert!(processed_count < MAX_PROCESSED_ADDRESSES, error::invalid_state(EMAX_PROCESSED_ADDRESSES)); processed_count = processed_count + 1; diff --git a/framework/libra-framework/sources/ol_sources/vouch_lib/page_rank_lazy.move b/framework/libra-framework/sources/ol_sources/vouch_lib/page_rank_lazy.move index a2808552f..7ec3a0cf9 100644 --- a/framework/libra-framework/sources/ol_sources/vouch_lib/page_rank_lazy.move +++ b/framework/libra-framework/sources/ol_sources/vouch_lib/page_rank_lazy.move @@ -16,32 +16,35 @@ module ol_framework::page_rank_lazy { friend ol_framework::mock; //////// ERROR CODES //////// - /// trust record not initialized + /// Thrown if a trust record is missing for the user. const ENOT_INITIALIZED: u64 = 2; - /// max addresses processed + /// Thrown if the maximum number of nodes to process is exceeded. const EMAX_PROCESSED_ADDRESSES: u64 = 3; //////// CONSTANTS //////// - // Max score on a single vouch + /// Maximum score assignable for a single vouch (upper bound for direct trust). const MAX_VOUCH_SCORE: u64 = 100_000; - // Circuit breaker to prevent stack overflow - const MAX_PROCESSED_ADDRESSES: u64 = 1_000; + /// Maximum number of nodes processed in a single traversal (prevents stack overflow). + const MAX_PROCESSED_ADDRESSES: u64 = 10_000; - // Per-user trust record - each user stores their own trust data + /// Maximum allowed depth for trust graph traversal. + const MAX_PATH_DEPTH: u64 = 5; + + /// Stores a user's trust score and its staleness state. struct UserTrustRecord has key, drop { - // No need to store active_vouches - we'll get this from vouch module - // Cached trust score + /// Last computed trust score for this user. cached_score: u64, - // When the score was last computed (timestamp) + /// Timestamp (seconds) when the score was last computed. score_computed_at_timestamp: u64, - // Whether this node's trust data is stale and needs recalculation + /// True if the trust score is outdated and needs recalculation. is_stale: bool, - // Shortest path to root now handled in a separate module + // Shortest path to root is managed in a separate module. } - // Initialize a user trust record if it doesn't exist + /// Initializes a trust record for the account if it does not already exist. + /// This creates the basic structure needed to track a user's trust score. public fun maybe_initialize_trust_record(account: &signer) { let addr = signer::address_of(account); if (!exists(addr)) { @@ -54,7 +57,18 @@ module ol_framework::page_rank_lazy { } - // Calculate or retrieve cached trust score + /// Returns the cached trust score for an address, or recalculates it if stale. + /// Uses a reverse PageRank-like algorithm to aggregate trust from roots. + /// + /// This function uses an optimized page rank algorithm that: + /// 1. Finds all possible paths from roots of trust to the target + /// 2. Accumulates scores from all valid paths, including diamond patterns + /// 3. Applies trust decay proportional to distance from roots + /// + /// The calculation considers the entire trust graph and properly handles: + /// - Multiple paths to the same target + /// - Branching and merging paths + /// - Root-of-trust special cases public(friend) fun get_trust_score(addr: address): u64 acquires UserTrustRecord { // If user has no trust record, they have no score @@ -69,19 +83,22 @@ module ol_framework::page_rank_lazy { set_score(addr) } - // always calculate the score + /// Recalculates and updates the trust score for an address. + /// Traverses the trust graph from roots to the target and updates the cache. + /// This is a costly operation and should be used sparingly. fun set_score(addr: address): u64 acquires UserTrustRecord { // If user has no trust record, they have no score assert!(exists(addr), error::invalid_state(ENOT_INITIALIZED)); // Cache is stale or expired - compute fresh score // Default roots to system account if no registry let roots = root_of_trust::get_current_roots_at_registry(@diem_framework); - // diem_std::debug::print(&roots); - // Compute score using selected algorithm - let score = traverse_graph(&roots, addr); - diem_std::debug::print(&score); - + let processed_count: u64 = 0; + let max_depth_reached: u64 = 0; + let visited = vector::empty
(); + let score = walk_backwards_from_target_with_stats( + addr, &roots, &mut visited, 2 * MAX_VOUCH_SCORE, 0, &mut processed_count, &mut max_depth_reached, MAX_PATH_DEPTH + ); // Update the cache let user_record_mut = borrow_global_mut(addr); user_record_mut.cached_score = score; @@ -91,152 +108,97 @@ module ol_framework::page_rank_lazy { score } - // Simplified graph traversal - only uses exhaustive walk - fun traverse_graph( - roots: &vector
, - target: address, - ): u64 { - let total_score = 0; - let root_idx = 0; - let roots_len = vector::length(roots); - - // For each root, calculate its contribution independently - while (root_idx < roots_len) { - let root = *vector::borrow(roots, root_idx); - - let visited = vector::empty
(); - vector::push_back(&mut visited, root); - - if (root != target) { - // NOTE: root, you don't don't give yourself points in the walk - - // Initial trust power is 100 (full trust from root) - total_score = total_score + walk_from_node( - root, target, &mut visited, 2 * MAX_VOUCH_SCORE - ); - }; - - - root_idx = root_idx + 1; - }; - - total_score - } - - // Simplified full graph traversal from a single node - returns weighted score - fun walk_from_node( + /// Walks backwards from the target toward roots of trust, accumulating trust score. + /// - Starts from the target user. + /// - Uses received vouches for traversal. + /// - Accumulates score when reaching a root. + /// - Avoids cycles and dead ends. + /// - Applies trust decay and limits neighbor exploration to control complexity. + fun walk_backwards_from_target_with_stats( current: address, - target: address, + roots: &vector
, visited: &mut vector
, - current_power: u64 + current_power: u64, + current_depth: u64, + processed_count: &mut u64, + max_depth_reached: &mut u64, + max_depth: u64 ): u64 { - if(!vouch::is_init(current)) { - return 0 + // Track maximum depth reached + if (current_depth > *max_depth_reached) { + *max_depth_reached = current_depth; }; - // Great, we found the target! - // then we get to return the power - // otherwise it will be zero - // when we run out of power or depth - if (current == target) { + // Early terminations that don't consume processing budget + if (current_depth >= max_depth) return 0; + if (vector::contains(visited, ¤t)) return 0; + if (!vouch::is_init(current)) return 0; + if (current_power < 2) return 0; + + // Check if we've reached a root of trust - this is our success condition! + if (vector::contains(roots, ¤t) && current_depth > 0) { return current_power }; - // Stop condition - only stop if power is too low - if (current_power < 2) { - return 0 - }; + // Budget check and consumption + assert!(*processed_count < MAX_PROCESSED_ADDRESSES, error::invalid_state(EMAX_PROCESSED_ADDRESSES)); + *processed_count = *processed_count + 1; - let (neighbors, _) = vouch::get_given_vouches(current); - let neighbor_count = vector::length(&neighbors); - diem_std::debug::print(¤t); - diem_std::debug::print(&neighbor_count); + // Get who vouched FOR this current user (backwards direction) + let (received_from, _) = vouch::get_received_vouches(current); + let neighbor_count = vector::length(&received_from); - // No neighbors means no path - if (neighbor_count == 0) { - return 0 - }; + if (neighbor_count == 0) return 0; - // Track total score from all paths let total_score = 0; - - - // Calculate power passed to neighbors (50% decay) let next_power = current_power / 2; + let next_depth = current_depth + 1; - // if the both current and target are a root of trust - // catch the case of a root of trust vouching for another root of trust - // and exit early - if ( - root_of_trust::is_root_at_registry(@diem_framework, current) && - root_of_trust::is_root_at_registry(@diem_framework, target) && - current != target && - vector::contains(&neighbors, &target) // Check if current directly vouches for target - ) { - vector::push_back(visited, current); - return next_power - }; + // Add current to visited and explore received vouches (backwards) + vector::push_back(visited, current); - // Check ALL neighbors for paths to target let i = 0; - while (i < neighbor_count) { - - let neighbor = *vector::borrow(&neighbors, i); - diem_std::debug::print(&neighbor); + while (i < neighbor_count) { + assert!(*processed_count < MAX_PROCESSED_ADDRESSES, error::invalid_state(EMAX_PROCESSED_ADDRESSES)); - // Only visit if not already in path (avoid cycles) + let neighbor = *vector::borrow(&received_from, i); if (!vector::contains(visited, &neighbor)) { - if (neighbor != target) { - // Mark neighbor as visited - // (Don't mark the target as visited - // because we want to be able to - // find it again) - - vector::push_back(visited, neighbor); - // we don't re-enter the root of - // trust list, because we don't - // want to accumulate points from - // roots vouching for each other. - if( - root_of_trust::is_root_at_registry(@diem_framework, neighbor) - ) { - continue - }; - }; - - - // Continue search from this neighbor with reduced power - let path_score = walk_from_node( + let visited_copy = *visited; + let path_score = walk_backwards_from_target_with_stats( neighbor, - target, - visited, - next_power + roots, + &mut visited_copy, + next_power, + next_depth, + processed_count, + max_depth_reached, + max_depth ); - - // Add to total score total_score = total_score + path_score; }; - i = i + 1; }; total_score } - // Mark a user's trust score as stale + /// Marks a user's trust score as stale and propagates staleness to downstream accounts. + /// Uses cycle detection and a processing limit to prevent infinite recursion. public(friend) fun mark_as_stale(user: address) acquires UserTrustRecord { let visited = vector::empty
(); let processed_count: u64 = 0; // Initialize as a mutable local variable walk_stale(user, &mut visited, &mut processed_count); // Pass as a mutable reference } - // Internal helper function with cycle detection for marking nodes as stale - // Uses vouch module to get outgoing vouches + /// Helper for `mark_as_stale`. Recursively marks downstream nodes as stale. + /// - Avoids revisiting nodes (cycle detection). + /// - Stops if the processing limit is reached. + /// - Only processes nodes initialized in the vouch system. fun walk_stale( user: address, visited: &mut vector
, - processed_count: &mut u64 // Changed to mutable reference + processed_count: &mut u64 ) acquires UserTrustRecord { // Skip if we've already visited this node in the current traversal (cycle detection) // This also ensures we only count/process each unique node once. @@ -246,9 +208,7 @@ module ol_framework::page_rank_lazy { // Check if the global limit for processed nodes has been reached *before* processing this one. // If *processed_count is already at the limit, we can't process another new node. - if (*processed_count >= MAX_PROCESSED_ADDRESSES) { - return - }; + assert!(*processed_count < MAX_PROCESSED_ADDRESSES, error::invalid_state(EMAX_PROCESSED_ADDRESSES)); // This node is new and will be processed. Increment the global count. *processed_count = *processed_count + 1; @@ -279,6 +239,9 @@ module ol_framework::page_rank_lazy { let i = 0; let len = vector::length(&outgoing_vouches); while (i < len) { + // Check again if we've hit the processing limit + if (*processed_count >= MAX_PROCESSED_ADDRESSES) break; + let each_vouchee = vector::borrow(&outgoing_vouches, i); // Pass the same mutable reference to processed_count. // The checks at the beginning of the recursive call (visited and limit) @@ -290,9 +253,8 @@ module ol_framework::page_rank_lazy { //////// CACHE //////// - /// Refresh the cache - /// state updates must be called by a user. - /// Vouch tree updates could be a DDOS vector + /// Refreshes the cached trust score for a user by recalculating it. + /// Only callable by the user. public entry fun refresh_cache(user: address) acquires UserTrustRecord{ // assert initialized assert!(exists(user), error::invalid_state(ENOT_INITIALIZED)); @@ -302,7 +264,7 @@ module ol_framework::page_rank_lazy { //////// GETTERS //////// #[view] - /// Get the cached trust score for a user + /// Returns the cached trust score for a user. public fun get_cached_score(addr: address): u64 acquires UserTrustRecord { assert!(exists(addr), error::invalid_state(ENOT_INITIALIZED)); let record = borrow_global(addr); @@ -310,7 +272,42 @@ module ol_framework::page_rank_lazy { } #[view] - // check if it's stale + /// Calculates a fresh trust score for a user without updating the cache. + /// Returns (score, max_depth_reached, accounts_processed). + /// Intended for diagnostics and testing only. + public fun calculate_score(addr: address): (u64, u64, u64) { + assert!(exists(addr), error::invalid_state(ENOT_INITIALIZED)); + // Cache is stale or expired - compute fresh score + // Default roots to system account if no registry + let roots = root_of_trust::get_current_roots_at_registry(@diem_framework); + // Compute score using selected algorithm + let processed_count: u64 = 0; + let max_depth_reached: u64 = 0; + let visited = vector::empty
(); + let score = walk_backwards_from_target_with_stats( + addr, &roots, &mut visited, 2 * MAX_VOUCH_SCORE, 0, &mut processed_count, &mut max_depth_reached, MAX_PATH_DEPTH + ); + (score, max_depth_reached, processed_count) + } + + #[view] + /// Calculates a fresh trust score for a user without updating the cache, using a custom max depth. + /// Returns (score, max_depth_reached, accounts_processed). + /// Intended for diagnostics and testing only. + public fun calculate_score_depth(addr: address, max_depth: u64): (u64, u64, u64) { + assert!(exists(addr), error::invalid_state(ENOT_INITIALIZED)); + let roots = root_of_trust::get_current_roots_at_registry(@diem_framework); + let processed_count: u64 = 0; + let max_depth_reached: u64 = 0; + let visited = vector::empty
(); + let score = walk_backwards_from_target_with_stats( + addr, &roots, &mut visited, 2 * MAX_VOUCH_SCORE, 0, &mut processed_count, &mut max_depth_reached, max_depth + ); + (score, max_depth_reached, processed_count) + } + + #[view] + /// Returns true if the user's trust score is marked as stale. public fun is_stale(addr: address): bool acquires UserTrustRecord { assert!(exists(addr), error::invalid_state(ENOT_INITIALIZED)); let record = borrow_global(addr); @@ -318,14 +315,16 @@ module ol_framework::page_rank_lazy { } #[view] - // get the const for highest vouch score + /// Returns the maximum possible score for a single vouch. public fun get_max_single_score(): u64 { MAX_VOUCH_SCORE } //////// TEST HELPERS /////// - #[test_only] + // Sets up a mock trust network for testing. + // - Initializes trust records and vouch structures for all test accounts. + // - Sets up vouching relationships and unrelated ancestry for each account. public fun setup_mock_trust_network( admin: &signer, root: &signer, diff --git a/framework/libra-framework/sources/ol_sources/vouch_lib/vouch.move b/framework/libra-framework/sources/ol_sources/vouch_lib/vouch.move index 26a18679e..be5e2587d 100644 --- a/framework/libra-framework/sources/ol_sources/vouch_lib/vouch.move +++ b/framework/libra-framework/sources/ol_sources/vouch_lib/vouch.move @@ -75,9 +75,6 @@ module ol_framework::vouch { /// Cooldown period (in epochs) required after a revocation before giving a new vouch const REVOCATION_COOLDOWN_EPOCHS: u64 = 3; - /// Maximum number of vouches allowed to be given per epoch - const MAX_VOUCHES_PER_EPOCH: u64 = 1; - //////// ERROR CODES //////// /// Trying to vouch for yourself?